diff --git a/docs/api/api.abc.rst b/docs/api/api.abc.rst new file mode 100644 index 0000000000..0d878e2ca3 --- /dev/null +++ b/docs/api/api.abc.rst @@ -0,0 +1,227 @@ +ABC +=== + +Abstract base classes for documented interfaces + +MiningHeaderAPI +--------------- + +.. autoclass:: eth.abc.MiningHeaderAPI + :members: + + +BlockHeaderAPI +-------------- + +.. autoclass:: eth.abc.BlockHeaderAPI + :members: + + +LogAPI +------ + +.. autoclass:: eth.abc.LogAPI + :members: + + +ReceiptAPI +---------- + +.. autoclass:: eth.abc.ReceiptAPI + :members: + + +BaseTransactionAPI +------------------ + +.. autoclass:: eth.abc.BaseTransactionAPI + :members: + + +TransactionFieldsAPI +-------------------- + +.. autoclass:: eth.abc.TransactionFieldsAPI + :members: + + +UnsignedTransactionAPI +---------------------- + +.. autoclass:: eth.abc.UnsignedTransactionAPI + :members: + + +SignedTransactionAPI +-------------------- + +.. autoclass:: eth.abc.SignedTransactionAPI + :members: + + +BlockAPI +-------- + +.. autoclass:: eth.abc.BlockAPI + :members: + + +DatabaseAPI +----------- + +.. autoclass:: eth.abc.DatabaseAPI + :members: + + +AtomicDatabaseAPI +----------------- + +.. autoclass:: eth.abc.AtomicDatabaseAPI + :members: + + +HeaderDatabaseAPI +----------------- + +.. autoclass:: eth.abc.HeaderDatabaseAPI + :members: + + +ChainDatabaseAPI +---------------- + +.. autoclass:: eth.abc.ChainDatabaseAPI + :members: + + +GasMeterAPI +----------- + +.. autoclass:: eth.abc.GasMeterAPI + :members: + + +MessageAPI +---------- + +.. autoclass:: eth.abc.MessageAPI + :members: + + +OpcodeAPI +--------- + +.. autoclass:: eth.abc.OpcodeAPI + :members: + + +TransactionContextAPI +--------------------- + +.. autoclass:: eth.abc.TransactionContextAPI + :members: + + +MemoryAPI +--------- + +.. autoclass:: eth.abc.MemoryAPI + :members: + + +StackAPI +-------- + +.. autoclass:: eth.abc.StackAPI + :members: + + +CodeStreamAPI +------------- + +.. autoclass:: eth.abc.CodeStreamAPI + :members: + + +StackManipulationAPI +-------------------- + +.. autoclass:: eth.abc.StackManipulationAPI + :members: + + +ExecutionContextAPI +------------------- + +.. autoclass:: eth.abc.ExecutionContextAPI + :members: + + +ComputationAPI +-------------- + +.. autoclass:: eth.abc.ComputationAPI + :members: + + +AccountStorageDatabaseAPI +------------------------- + +.. autoclass:: eth.abc.AccountStorageDatabaseAPI + :members: + + +AccountDatabaseAPI +------------------ + +.. autoclass:: eth.abc.AccountDatabaseAPI + :members: + + +TransactionExecutorAPI +---------------------- + +.. autoclass:: eth.abc.TransactionExecutorAPI + :members: + + +ConfigurableAPI +--------------- + +.. autoclass:: eth.abc.ConfigurableAPI + :members: + + +StateAPI +-------- + +.. autoclass:: eth.abc.StateAPI + :members: + + +VirtualMachineAPI +----------------- + +.. autoclass:: eth.abc.VirtualMachineAPI + :members: + + +HeaderChainAPI +-------------- + +.. autoclass:: eth.abc.HeaderChainAPI + :members: + + +ChainAPI +-------- + +.. autoclass:: eth.abc.ChainAPI + :members: + + +MiningChainAPI +-------------- + +.. autoclass:: eth.abc.MiningChainAPI + :members: diff --git a/docs/api/db/api.db.account.rst b/docs/api/db/api.db.account.rst index 0ba3c6406e..2f737dde7a 100644 --- a/docs/api/db/api.db.account.rst +++ b/docs/api/db/api.db.account.rst @@ -1,12 +1,6 @@ Account ======== -BaseAccountDB -------------- - -.. autoclass:: eth.db.account.BaseAccountDB - :members: - AccountDB ------------- diff --git a/docs/api/db/api.db.chain.rst b/docs/api/db/api.db.chain.rst index aa1d06ca57..371a23b047 100644 --- a/docs/api/db/api.db.chain.rst +++ b/docs/api/db/api.db.chain.rst @@ -1,12 +1,6 @@ Chain ===== -BaseChainDB -~~~~~~~~~~~ - -.. autoclass:: eth.db.chain.BaseChainDB - :members: - ChainDB ~~~~~~~ diff --git a/docs/api/index.rst b/docs/api/index.rst index d42c1f3917..4dd663f0dd 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -12,6 +12,7 @@ This section aims to provide a detailed description of all APIs. If you are look :name: toc-api-eth + api.abc api.chain api.db api.exceptions diff --git a/docs/api/vm/api.vm.vm.rst b/docs/api/vm/api.vm.vm.rst index a4cf1cf152..eec084896e 100644 --- a/docs/api/vm/api.vm.vm.rst +++ b/docs/api/vm/api.vm.vm.rst @@ -1,13 +1,6 @@ VM == -BaseVM ------- - -.. autoclass:: eth.vm.base.BaseVM - :members: - - VM -- diff --git a/docs/contributing.rst b/docs/contributing.rst index 64859b74db..a9666d5859 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -65,7 +65,7 @@ All parameters as well as the return type of defs are expected to be typed with .. code:: python - def __init__(self, wrapped_db: BaseDB) -> None: + def __init__(self, wrapped_db: DatabaseAPI) -> None: self.wrapped_db = wrapped_db self.reset() diff --git a/eth/_utils/datatypes.py b/eth/_utils/datatypes.py index 321dd3d71f..2ec5fdd0e7 100644 --- a/eth/_utils/datatypes.py +++ b/eth/_utils/datatypes.py @@ -11,6 +11,8 @@ from typing import Any, Dict, Tuple, Type, TypeVar, Iterator, List +from eth.abc import ConfigurableAPI + def _is_local_prop(prop: str) -> bool: return len(prop.split('.')) == 1 @@ -64,7 +66,7 @@ def _get_top_level_keys(overrides: Dict[str, Any]) -> Iterator[str]: T = TypeVar('T') -class Configurable(object): +class Configurable(ConfigurableAPI): """ Base class for simple inline subclassing """ diff --git a/eth/_utils/db.py b/eth/_utils/db.py index d16281950b..06bd13c7f0 100644 --- a/eth/_utils/db.py +++ b/eth/_utils/db.py @@ -1,40 +1,32 @@ -from typing import ( - TYPE_CHECKING, -) - from eth_typing import ( Hash32, ) -from eth.rlp.headers import ( - BlockHeader, +from eth.abc import ( + BlockHeaderAPI, + ChainDatabaseAPI, + StateAPI, ) from eth.typing import ( AccountState, ) -from eth.vm.state import ( - BaseState, -) - -if TYPE_CHECKING: - from eth.db.chain import BaseChainDB # noqa: F401 -def get_parent_header(block_header: BlockHeader, db: 'BaseChainDB') -> BlockHeader: +def get_parent_header(block_header: BlockHeaderAPI, db: ChainDatabaseAPI) -> BlockHeaderAPI: """ Returns the header for the parent block. """ return db.get_block_header_by_hash(block_header.parent_hash) -def get_block_header_by_hash(block_hash: Hash32, db: 'BaseChainDB') -> BlockHeader: +def get_block_header_by_hash(block_hash: Hash32, db: ChainDatabaseAPI) -> BlockHeaderAPI: """ Returns the header for the parent block. """ return db.get_block_header_by_hash(block_hash) -def apply_state_dict(state: BaseState, state_dict: AccountState) -> None: +def apply_state_dict(state: StateAPI, state_dict: AccountState) -> None: for account, account_data in state_dict.items(): state.set_balance(account, account_data["balance"]) state.set_nonce(account, account_data["nonce"]) diff --git a/eth/_utils/spoof.py b/eth/_utils/spoof.py index a94bdf03b2..360bd53ba2 100644 --- a/eth/_utils/spoof.py +++ b/eth/_utils/spoof.py @@ -14,9 +14,9 @@ DEFAULT_SPOOF_S, ) -from eth.rlp.transactions import ( - BaseTransaction, - BaseUnsignedTransaction, +from eth.abc import ( + SignedTransactionAPI, + UnsignedTransactionAPI, ) SPOOF_ATTRIBUTES_DEFAULTS = { @@ -31,7 +31,7 @@ class SpoofAttributes: def __init__( self, - spoof_target: Union[BaseTransaction, BaseUnsignedTransaction], + spoof_target: Union[SignedTransactionAPI, UnsignedTransactionAPI], **overrides: Any) -> None: self.spoof_target = spoof_target self.overrides = overrides diff --git a/eth/abc.py b/eth/abc.py new file mode 100644 index 0000000000..f7cabfd474 --- /dev/null +++ b/eth/abc.py @@ -0,0 +1,1904 @@ +from abc import ( + ABC, + abstractmethod +) +from typing import ( + Any, + Callable, + ContextManager, + Dict, + Iterable, + Iterator, + MutableMapping, + Optional, + Sequence, + Tuple, + Type, + TypeVar, + Union, +) +from uuid import UUID + +import rlp + +from eth_bloom import BloomFilter + +from eth_typing import ( + Address, + BlockNumber, + Hash32, +) + +from eth_keys.datatypes import PrivateKey + +from eth.constants import ( + BLANK_ROOT_HASH, +) +from eth.exceptions import VMError +from eth.typing import ( + BytesOrView, + JournalDBCheckpoint, + AccountState, + HeaderParams, +) + +from eth.tools.logging import ExtendedDebugLogger + + +T = TypeVar('T') + + +class MiningHeaderAPI(rlp.Serializable, ABC): + parent_hash: Hash32 + uncles_hash: Hash32 + coinbase: Address + state_root: Hash32 + transaction_root: Hash32 + receipt_root: Hash32 + bloom: int + difficulty: int + block_number: BlockNumber + gas_limit: int + gas_used: int + timestamp: int + extra_data: bytes + + +class BlockHeaderAPI(MiningHeaderAPI): + mix_hash: Hash32 + nonce: bytes + + +class LogAPI(rlp.Serializable, ABC): + address: Address + topics: Sequence[int] + data: bytes + + @property + @abstractmethod + def bloomables(self) -> Tuple[bytes, ...]: + ... + + +class ReceiptAPI(rlp.Serializable, ABC): + state_root: bytes + gas_used: int + bloom: int + logs: Sequence[LogAPI] + + @property + @abstractmethod + def bloom_filter(self) -> BloomFilter: + ... + + +class BaseTransactionAPI(ABC): + @abstractmethod + def validate(self) -> None: + ... + + @property + @abstractmethod + def intrinsic_gas(self) -> int: + ... + + @abstractmethod + def get_intrinsic_gas(self) -> int: + ... + + @abstractmethod + def gas_used_by(self, computation: 'ComputationAPI') -> int: + ... + + @abstractmethod + def copy(self: T, **overrides: Any) -> T: + ... + + +class TransactionFieldsAPI(ABC): + nonce: int + gas_price: int + gas: int + to: Address + value: int + data: bytes + v: int + r: int + s: int + + @property + @abstractmethod + def hash(self) -> bytes: + ... + + +class UnsignedTransactionAPI(rlp.Serializable, BaseTransactionAPI): + nonce: int + gas_price: int + gas: int + to: Address + value: int + data: bytes + + # + # API that must be implemented by all Transaction subclasses. + # + @abstractmethod + def as_signed_transaction(self, private_key: PrivateKey) -> 'SignedTransactionAPI': + """ + Return a version of this transaction which has been signed using the + provided `private_key` + """ + ... + + +class SignedTransactionAPI(rlp.Serializable, BaseTransactionAPI, TransactionFieldsAPI): + @classmethod + @abstractmethod + def from_base_transaction(cls, transaction: 'SignedTransactionAPI') -> 'SignedTransactionAPI': + ... + + @property + @abstractmethod + def sender(self) -> Address: + ... + + # +-------------------------------------------------------------+ + # | API that must be implemented by all Transaction subclasses. | + # +-------------------------------------------------------------+ + + # + # Validation + # + @abstractmethod + def validate(self) -> None: + ... + + # + # Signature and Sender + # + @property + @abstractmethod + def is_signature_valid(self) -> bool: + ... + + @abstractmethod + def check_signature_validity(self) -> None: + """ + Checks signature validity, raising a ValidationError if the signature + is invalid. + """ + ... + + @abstractmethod + def get_sender(self) -> Address: + """ + Get the 20-byte address which sent this transaction. + + This can be a slow operation. ``transaction.sender`` is always preferred. + """ + ... + + # + # Conversion to and creation of unsigned transactions. + # + @abstractmethod + def get_message_for_signing(self) -> bytes: + """ + Return the bytestring that should be signed in order to create a signed transactions + """ + ... + + @classmethod + @abstractmethod + def create_unsigned_transaction(cls, + *, + nonce: int, + gas_price: int, + gas: int, + to: Address, + value: int, + data: bytes) -> UnsignedTransactionAPI: + """ + Create an unsigned transaction. + """ + ... + + +class BlockAPI(rlp.Serializable, ABC): + transaction_class: Type[SignedTransactionAPI] = None + + @classmethod + @abstractmethod + def get_transaction_class(cls) -> Type[SignedTransactionAPI]: + ... + + @classmethod + @abstractmethod + def from_header(cls, header: BlockHeaderAPI, chaindb: 'ChainDatabaseAPI') -> 'BlockAPI': + ... + + @property + @abstractmethod + def hash(self) -> Hash32: + ... + + @property + @abstractmethod + def number(self) -> int: + ... + + @property + @abstractmethod + def is_genesis(self) -> bool: + ... + + +class DatabaseAPI(MutableMapping[bytes, bytes], ABC): + @abstractmethod + def set(self, key: bytes, value: bytes) -> None: + ... + + @abstractmethod + def exists(self, key: bytes) -> bool: + ... + + @abstractmethod + def delete(self, key: bytes) -> None: + ... + + +class AtomicDatabaseAPI(DatabaseAPI): + @abstractmethod + def atomic_batch(self) -> ContextManager[DatabaseAPI]: + ... + + +class HeaderDatabaseAPI(ABC): + db: AtomicDatabaseAPI + + @abstractmethod + def __init__(self, db: AtomicDatabaseAPI) -> None: + ... + + # + # Canonical Chain API + # + @abstractmethod + def get_canonical_block_hash(self, block_number: BlockNumber) -> Hash32: + ... + + @abstractmethod + def get_canonical_block_header_by_number(self, block_number: BlockNumber) -> BlockHeaderAPI: + ... + + @abstractmethod + def get_canonical_head(self) -> BlockHeaderAPI: + ... + + # + # Header API + # + @abstractmethod + def get_block_header_by_hash(self, block_hash: Hash32) -> BlockHeaderAPI: + ... + + @abstractmethod + def get_score(self, block_hash: Hash32) -> int: + ... + + @abstractmethod + def header_exists(self, block_hash: Hash32) -> bool: + ... + + @abstractmethod + def persist_header(self, + header: BlockHeaderAPI + ) -> Tuple[Tuple[BlockHeaderAPI, ...], Tuple[BlockHeaderAPI, ...]]: + ... + + @abstractmethod + def persist_header_chain(self, + headers: Sequence[BlockHeaderAPI] + ) -> Tuple[Tuple[BlockHeaderAPI, ...], Tuple[BlockHeaderAPI, ...]]: + ... + + +class ChainDatabaseAPI(HeaderDatabaseAPI): + # + # Header API + # + @abstractmethod + def get_block_uncles(self, uncles_hash: Hash32) -> Tuple[BlockHeaderAPI, ...]: + ... + + # + # Block API + # + @abstractmethod + def persist_block(self, + block: BlockAPI + ) -> Tuple[Tuple[Hash32, ...], Tuple[Hash32, ...]]: + ... + + @abstractmethod + def persist_uncles(self, uncles: Tuple[BlockHeaderAPI]) -> Hash32: + ... + + # + # Transaction API + # + @abstractmethod + def add_receipt(self, + block_header: BlockHeaderAPI, + index_key: int, receipt: ReceiptAPI) -> Hash32: + ... + + @abstractmethod + def add_transaction(self, + block_header: BlockHeaderAPI, + index_key: int, transaction: SignedTransactionAPI) -> Hash32: + ... + + @abstractmethod + def get_block_transactions( + self, + block_header: BlockHeaderAPI, + transaction_class: Type[SignedTransactionAPI]) -> Sequence[SignedTransactionAPI]: + ... + + @abstractmethod + def get_block_transaction_hashes(self, block_header: BlockHeaderAPI) -> Tuple[Hash32, ...]: + ... + + @abstractmethod + def get_receipt_by_index(self, + block_number: BlockNumber, + receipt_index: int) -> ReceiptAPI: + ... + + @abstractmethod + def get_receipts(self, + header: BlockHeaderAPI, + receipt_class: Type[ReceiptAPI]) -> Tuple[ReceiptAPI, ...]: + ... + + @abstractmethod + def get_transaction_by_index( + self, + block_number: BlockNumber, + transaction_index: int, + transaction_class: Type[SignedTransactionAPI]) -> SignedTransactionAPI: + ... + + @abstractmethod + def get_transaction_index(self, transaction_hash: Hash32) -> Tuple[BlockNumber, int]: + ... + + # + # Raw Database API + # + @abstractmethod + def exists(self, key: bytes) -> bool: + ... + + @abstractmethod + def get(self, key: bytes) -> bytes: + ... + + @abstractmethod + def persist_trie_data_dict(self, trie_data_dict: Dict[Hash32, bytes]) -> None: + ... + + +class GasMeterAPI(ABC): + gas_refunded: int + gas_remaining: int + + # + # Write API + # + @abstractmethod + def consume_gas(self, amount: int, reason: str) -> None: + ... + + @abstractmethod + def return_gas(self, amount: int) -> None: + ... + + @abstractmethod + def refund_gas(self, amount: int) -> None: + ... + + +class MessageAPI(ABC): + """ + A message for VM computation. + """ + code: bytes + _code_address: Address + create_address: Address + data: BytesOrView + depth: int + gas: int + is_static: bool + sender: Address + should_transfer_value: bool + _storage_address: Address + to: Address + value: int + + __slots__ = [ + 'code', + '_code_address', + 'create_address', + 'data', + 'depth', + 'gas', + 'is_static', + 'sender', + 'should_transfer_value', + '_storage_address' + 'to', + 'value', + ] + + @property + @abstractmethod + def code_address(self) -> Address: + ... + + @property + @abstractmethod + def storage_address(self) -> Address: + ... + + @property + @abstractmethod + def is_create(self) -> bool: + ... + + @property + @abstractmethod + def data_as_bytes(self) -> bytes: + ... + + +class OpcodeAPI(ABC): + mnemonic: str + + @abstractmethod + def __call__(self, computation: 'ComputationAPI') -> None: + ... + + @classmethod + @abstractmethod + def as_opcode(cls: Type[T], + logic_fn: Callable[['ComputationAPI'], None], + mnemonic: str, + gas_cost: int) -> Type[T]: + ... + + @abstractmethod + def __copy__(self) -> 'OpcodeAPI': + ... + + @abstractmethod + def __deepcopy__(self, memo: Any) -> 'OpcodeAPI': + ... + + +class TransactionContextAPI(ABC): + @abstractmethod + def __init__(self, gas_price: int, origin: Address) -> None: + ... + + @abstractmethod + def get_next_log_counter(self) -> int: + ... + + @property + @abstractmethod + def gas_price(self) -> int: + ... + + @property + @abstractmethod + def origin(self) -> Address: + ... + + +class MemoryAPI(ABC): + @abstractmethod + def extend(self, start_position: int, size: int) -> None: + ... + + @abstractmethod + def __len__(self) -> int: + ... + + @abstractmethod + def write(self, start_position: int, size: int, value: bytes) -> None: + ... + + @abstractmethod + def read(self, start_position: int, size: int) -> memoryview: + ... + + @abstractmethod + def read_bytes(self, start_position: int, size: int) -> bytes: + ... + + +class StackAPI(ABC): + @abstractmethod + def push_int(self, value: int) -> None: + ... + + @abstractmethod + def push_bytes(self, value: bytes) -> None: + ... + + @abstractmethod + def pop1_bytes(self) -> bytes: + ... + + @abstractmethod + def pop1_int(self) -> int: + ... + + @abstractmethod + def pop1_any(self) -> Union[int, bytes]: + ... + + @abstractmethod + def pop_any(self, num_items: int) -> Tuple[Union[int, bytes], ...]: + ... + + @abstractmethod + def pop_ints(self, num_items: int) -> Tuple[int, ...]: + ... + + @abstractmethod + def pop_bytes(self, num_items: int) -> Tuple[bytes, ...]: + ... + + @abstractmethod + def swap(self, position: int) -> None: + ... + + @abstractmethod + def dup(self, position: int) -> None: + ... + + +class CodeStreamAPI(ABC): + pc: int + + @abstractmethod + def read(self, size: int) -> bytes: + ... + + @abstractmethod + def __len__(self) -> int: + ... + + @abstractmethod + def __getitem__(self, i: int) -> int: + ... + + @abstractmethod + def __iter__(self) -> Iterator[int]: + ... + + @abstractmethod + def peek(self) -> int: + ... + + @abstractmethod + def seek(self, pc: int) -> ContextManager['CodeStreamAPI']: + ... + + @abstractmethod + def is_valid_opcode(self, position: int) -> bool: + ... + + +class StackManipulationAPI(ABC): + @abstractmethod + def stack_pop_ints(self, num_items: int) -> Tuple[int, ...]: + ... + + @abstractmethod + def stack_pop_bytes(self, num_items: int) -> Tuple[bytes, ...]: + ... + + @abstractmethod + def stack_pop_any(self, num_items: int) -> Tuple[Union[int, bytes], ...]: + ... + + @abstractmethod + def stack_pop1_int(self) -> int: + ... + + @abstractmethod + def stack_pop1_bytes(self) -> bytes: + ... + + @abstractmethod + def stack_pop1_any(self) -> Union[int, bytes]: + ... + + @abstractmethod + def stack_push_int(self, value: int) -> None: + ... + + @abstractmethod + def stack_push_bytes(self, value: bytes) -> None: + ... + + +class ExecutionContextAPI(ABC): + coinbase: Address + timestamp: int + block_number: int + difficulty: int + gas_limit: int + prev_hashes: Sequence[Hash32] + + +class ComputationAPI(ContextManager['ComputationAPI'], StackManipulationAPI): + msg: MessageAPI + logger: ExtendedDebugLogger + code: CodeStreamAPI + opcodes: Dict[int, OpcodeAPI] = None + state: 'StateAPI' + return_data: bytes + + @abstractmethod + def __init__(self, + state: 'StateAPI', + message: MessageAPI, + transaction_context: TransactionContextAPI) -> None: + ... + + # + # Convenience + # + @property + @abstractmethod + def is_origin_computation(self) -> bool: + ... + + # + # Error handling + # + @property + @abstractmethod + def is_success(self) -> bool: + ... + + @property + @abstractmethod + def is_error(self) -> bool: + ... + + @property + @abstractmethod + def error(self) -> VMError: + ... + + @error.setter + def error(self, value: VMError) -> None: + # See: https://github.com/python/mypy/issues/4165 + # Since we can't also decorate this with abstract method we want to be + # sure that the setter doesn't actually get used as a noop. + raise NotImplementedError + + @abstractmethod + def raise_if_error(self) -> None: + ... + + @property + @abstractmethod + def should_burn_gas(self) -> bool: + ... + + @property + @abstractmethod + def should_return_gas(self) -> bool: + ... + + @property + @abstractmethod + def should_erase_return_data(self) -> bool: + ... + + # + # Memory Management + # + @abstractmethod + def extend_memory(self, start_position: int, size: int) -> None: + ... + + @abstractmethod + def memory_write(self, start_position: int, size: int, value: bytes) -> None: + ... + + @abstractmethod + def memory_read(self, start_position: int, size: int) -> memoryview: + ... + + @abstractmethod + def memory_read_bytes(self, start_position: int, size: int) -> bytes: + ... + + # + # Gas Consumption + # + @abstractmethod + def get_gas_meter(self) -> GasMeterAPI: + ... + + @abstractmethod + def consume_gas(self, amount: int, reason: str) -> None: + ... + + @abstractmethod + def return_gas(self, amount: int) -> None: + ... + + @abstractmethod + def refund_gas(self, amount: int) -> None: + ... + + @abstractmethod + def get_gas_refund(self) -> int: + ... + + @abstractmethod + def get_gas_used(self) -> int: + ... + + @abstractmethod + def get_gas_remaining(self) -> int: + ... + + # + # Stack management + # + @abstractmethod + def stack_swap(self, position: int) -> None: + ... + + @abstractmethod + def stack_dup(self, position: int) -> None: + ... + + # + # Computation result + # + @property + @abstractmethod + def output(self) -> bytes: + ... + + @output.setter + def output(self, value: bytes) -> None: + # See: https://github.com/python/mypy/issues/4165 + # Since we can't also decorate this with abstract method we want to be + # sure that the setter doesn't actually get used as a noop. + raise NotImplementedError + + # + # Runtime operations + # + @abstractmethod + def prepare_child_message(self, + gas: int, + to: Address, + value: int, + data: BytesOrView, + code: bytes, + **kwargs: Any) -> MessageAPI: + ... + + @abstractmethod + def apply_child_computation(self, child_msg: MessageAPI) -> 'ComputationAPI': + ... + + @abstractmethod + def generate_child_computation(self, child_msg: MessageAPI) -> 'ComputationAPI': + ... + + @abstractmethod + def add_child_computation(self, child_computation: 'ComputationAPI') -> None: + ... + + # + # Account management + # + @abstractmethod + def register_account_for_deletion(self, beneficiary: Address) -> None: + ... + + @abstractmethod + def get_accounts_for_deletion(self) -> Tuple[Tuple[Address, Address], ...]: + ... + + # + # EVM logging + # + @abstractmethod + def add_log_entry(self, account: Address, topics: Tuple[int, ...], data: bytes) -> None: + ... + + @abstractmethod + def get_raw_log_entries(self) -> Tuple[Tuple[int, bytes, Tuple[int, ...], bytes], ...]: + ... + + @abstractmethod + def get_log_entries(self) -> Tuple[Tuple[bytes, Tuple[int, ...], bytes], ...]: + ... + + # + # State Transition + # + @abstractmethod + def apply_message(self) -> 'ComputationAPI': + ... + + @abstractmethod + def apply_create_message(self) -> 'ComputationAPI': + ... + + @classmethod + @abstractmethod + def apply_computation(cls, + state: 'StateAPI', + message: MessageAPI, + transaction_context: TransactionContextAPI) -> 'ComputationAPI': + ... + + # + # Opcode API + # + @property + @abstractmethod + def precompiles(self) -> Dict[Address, Callable[['ComputationAPI'], None]]: + ... + + @abstractmethod + def get_opcode_fn(self, opcode: int) -> OpcodeAPI: + ... + + +class AccountStorageDatabaseAPI(ABC): + @abstractmethod + def get(self, slot: int, from_journal: bool=True) -> int: + ... + + @abstractmethod + def set(self, slot: int, value: int) -> None: + ... + + @abstractmethod + def delete(self) -> None: + ... + + @abstractmethod + def record(self, checkpoint: JournalDBCheckpoint) -> None: + ... + + @abstractmethod + def discard(self, checkpoint: JournalDBCheckpoint) -> None: + ... + + @abstractmethod + def commit(self, checkpoint: JournalDBCheckpoint) -> None: + ... + + @abstractmethod + def make_storage_root(self) -> None: + ... + + @property + @abstractmethod + def has_changed_root(self) -> bool: + ... + + @abstractmethod + def get_changed_root(self) -> Hash32: + ... + + @abstractmethod + def persist(self, db: DatabaseAPI) -> None: + ... + + +class AccountDatabaseAPI(ABC): + @abstractmethod + def __init__(self, db: AtomicDatabaseAPI, state_root: Hash32 = BLANK_ROOT_HASH) -> None: + ... + + @property + @abstractmethod + def state_root(self) -> Hash32: + ... + + @abstractmethod + def has_root(self, state_root: bytes) -> bool: + ... + + # + # Storage + # + @abstractmethod + def get_storage(self, address: Address, slot: int, from_journal: bool=True) -> int: + ... + + @abstractmethod + def set_storage(self, address: Address, slot: int, value: int) -> None: + ... + + @abstractmethod + def delete_storage(self, address: Address) -> None: + ... + + # + # Balance + # + @abstractmethod + def get_balance(self, address: Address) -> int: + ... + + @abstractmethod + def set_balance(self, address: Address, balance: int) -> None: + ... + + # + # Nonce + # + @abstractmethod + def get_nonce(self, address: Address) -> int: + ... + + @abstractmethod + def set_nonce(self, address: Address, nonce: int) -> None: + ... + + @abstractmethod + def increment_nonce(self, address: Address) -> None: + ... + + # + # Code + # + @abstractmethod + def set_code(self, address: Address, code: bytes) -> None: + ... + + @abstractmethod + def get_code(self, address: Address) -> bytes: + ... + + @abstractmethod + def get_code_hash(self, address: Address) -> Hash32: + ... + + @abstractmethod + def delete_code(self, address: Address) -> None: + ... + + # + # Account Methods + # + @abstractmethod + def account_has_code_or_nonce(self, address: Address) -> bool: + ... + + @abstractmethod + def delete_account(self, address: Address) -> None: + ... + + @abstractmethod + def account_exists(self, address: Address) -> bool: + ... + + @abstractmethod + def touch_account(self, address: Address) -> None: + ... + + @abstractmethod + def account_is_empty(self, address: Address) -> bool: + ... + + # + # Record and discard API + # + @abstractmethod + def record(self) -> JournalDBCheckpoint: + ... + + @abstractmethod + def discard(self, checkpoint: JournalDBCheckpoint) -> None: + ... + + @abstractmethod + def commit(self, checkpoint: JournalDBCheckpoint) -> None: + ... + + @abstractmethod + def make_state_root(self) -> Hash32: + """ + Generate the state root with all the current changes in AccountDB + + Current changes include every pending change to storage, as well as all account changes. + After generating all the required tries, the final account state root is returned. + + This is an expensive operation, so should be called as little as possible. For example, + pre-Byzantium, this is called after every transaction, because we need the state root + in each receipt. Byzantium+, we only need state roots at the end of the block, + so we *only* call it right before persistance. + + :return: the new state root + """ + ... + + @abstractmethod + def persist(self) -> None: + """ + Send changes to underlying database, including the trie state + so that it will forever be possible to read the trie from this checkpoint. + + :meth:`make_state_root` must be explicitly called before this method. + Otherwise persist will raise a ValidationError. + """ + ... + + +class TransactionExecutorAPI(ABC): + @abstractmethod + def __init__(self, vm_state: 'StateAPI') -> None: + ... + + @abstractmethod + def __call__(self, transaction: SignedTransactionAPI) -> 'ComputationAPI': + ... + + @abstractmethod + def validate_transaction(self, transaction: SignedTransactionAPI) -> None: + ... + + @abstractmethod + def build_evm_message(self, transaction: SignedTransactionAPI) -> MessageAPI: + ... + + @abstractmethod + def build_computation(self, + message: MessageAPI, + transaction: SignedTransactionAPI) -> 'ComputationAPI': + ... + + @abstractmethod + def finalize_computation(self, + transaction: SignedTransactionAPI, + computation: 'ComputationAPI') -> 'ComputationAPI': + ... + + +class ConfigurableAPI(ABC): + @classmethod + def configure(cls: Type[T], + __name__: str=None, + **overrides: Any) -> Type[T]: + ... + + +class StateAPI(ConfigurableAPI): + # + # Set from __init__ + # + execution_context: ExecutionContextAPI + + computation_class: Type[ComputationAPI] + transaction_context_class: Type[TransactionContextAPI] + account_db_class: Type[AccountDatabaseAPI] + transaction_executor_class: Type[TransactionExecutorAPI] = None + + @abstractmethod + def __init__( + self, + db: AtomicDatabaseAPI, + execution_context: ExecutionContextAPI, + state_root: bytes) -> None: + ... + + @property + @abstractmethod + def logger(self) -> ExtendedDebugLogger: + ... + + # + # Block Object Properties (in opcodes) + # + @property + @abstractmethod + def coinbase(self) -> Address: + ... + + @property + @abstractmethod + def timestamp(self) -> int: + ... + + @property + @abstractmethod + def block_number(self) -> int: + ... + + @property + @abstractmethod + def difficulty(self) -> int: + ... + + @property + @abstractmethod + def gas_limit(self) -> int: + ... + + # + # Access to account db + # + @classmethod + @abstractmethod + def get_account_db_class(cls) -> Type[AccountDatabaseAPI]: + ... + + @property + @abstractmethod + def state_root(self) -> Hash32: + ... + + @abstractmethod + def make_state_root(self) -> Hash32: + ... + + @abstractmethod + def get_storage(self, address: Address, slot: int, from_journal: bool=True) -> int: + ... + + @abstractmethod + def set_storage(self, address: Address, slot: int, value: int) -> None: + ... + + @abstractmethod + def delete_storage(self, address: Address) -> None: + ... + + @abstractmethod + def delete_account(self, address: Address) -> None: + ... + + @abstractmethod + def get_balance(self, address: Address) -> int: + ... + + @abstractmethod + def set_balance(self, address: Address, balance: int) -> None: + ... + + @abstractmethod + def delta_balance(self, address: Address, delta: int) -> None: + ... + + @abstractmethod + def get_nonce(self, address: Address) -> int: + ... + + @abstractmethod + def set_nonce(self, address: Address, nonce: int) -> None: + ... + + @abstractmethod + def increment_nonce(self, address: Address) -> None: + ... + + @abstractmethod + def get_code(self, address: Address) -> bytes: + ... + + @abstractmethod + def set_code(self, address: Address, code: bytes) -> None: + ... + + @abstractmethod + def get_code_hash(self, address: Address) -> Hash32: + ... + + @abstractmethod + def delete_code(self, address: Address) -> None: + ... + + @abstractmethod + def has_code_or_nonce(self, address: Address) -> bool: + ... + + @abstractmethod + def account_exists(self, address: Address) -> bool: + ... + + @abstractmethod + def touch_account(self, address: Address) -> None: + ... + + @abstractmethod + def account_is_empty(self, address: Address) -> bool: + ... + + # + # Access self._chaindb + # + @abstractmethod + def snapshot(self) -> Tuple[Hash32, UUID]: + ... + + @abstractmethod + def revert(self, snapshot: Tuple[Hash32, UUID]) -> None: + ... + + @abstractmethod + def commit(self, snapshot: Tuple[Hash32, UUID]) -> None: + ... + + @abstractmethod + def persist(self) -> None: + ... + + # + # Access self.prev_hashes (Read-only) + # + @abstractmethod + def get_ancestor_hash(self, block_number: int) -> Hash32: + ... + + # + # Computation + # + @abstractmethod + def get_computation(self, + message: MessageAPI, + transaction_context: TransactionContextAPI) -> ComputationAPI: + ... + + # + # Transaction context + # + @classmethod + @abstractmethod + def get_transaction_context_class(cls) -> Type[TransactionContextAPI]: + ... + + # + # Execution + # + @abstractmethod + def apply_transaction( + self, + transaction: SignedTransactionAPI) -> ComputationAPI: + ... + + @abstractmethod + def get_transaction_executor(self) -> TransactionExecutorAPI: + ... + + @abstractmethod + def costless_execute_transaction(self, + transaction: SignedTransactionAPI) -> ComputationAPI: + ... + + @abstractmethod + def override_transaction_context(self, gas_price: int) -> ContextManager[None]: + ... + + @abstractmethod + def execute_transaction(self, transaction: SignedTransactionAPI) -> ComputationAPI: + ... + + @abstractmethod + def validate_transaction(self, transaction: SignedTransactionAPI) -> None: + ... + + @classmethod + @abstractmethod + def get_transaction_context(cls, + transaction: SignedTransactionAPI) -> TransactionContextAPI: + ... + + +class VirtualMachineAPI(ConfigurableAPI): + fork: str # noqa: E701 # flake8 bug that's fixed in 3.6.0+ + chaindb: ChainDatabaseAPI + + @abstractmethod + def __init__(self, header: BlockHeaderAPI, chaindb: ChainDatabaseAPI) -> None: + ... + + @property + @abstractmethod + def state(self) -> StateAPI: + ... + + @classmethod + @abstractmethod + def build_state(cls, + db: AtomicDatabaseAPI, + header: BlockHeaderAPI, + previous_hashes: Iterable[Hash32] = () + ) -> StateAPI: + ... + + @abstractmethod + def get_header(self) -> BlockHeaderAPI: + ... + + @abstractmethod + def get_block(self) -> BlockAPI: + ... + + # + # Execution + # + @abstractmethod + def apply_transaction(self, + header: BlockHeaderAPI, + transaction: SignedTransactionAPI + ) -> Tuple[ReceiptAPI, ComputationAPI]: + ... + + @abstractmethod + def execute_bytecode(self, + origin: Address, + gas_price: int, + gas: int, + to: Address, + sender: Address, + value: int, + data: bytes, + code: bytes, + code_address: Address = None) -> ComputationAPI: + ... + + @abstractmethod + def apply_all_transactions( + self, + transactions: Sequence[SignedTransactionAPI], + base_header: BlockHeaderAPI + ) -> Tuple[BlockHeaderAPI, Tuple[ReceiptAPI, ...], Tuple[ComputationAPI, ...]]: + ... + + @abstractmethod + def make_receipt(self, + base_header: BlockHeaderAPI, + transaction: SignedTransactionAPI, + computation: ComputationAPI, + state: StateAPI) -> ReceiptAPI: + """ + Generate the receipt resulting from applying the transaction. + + :param base_header: the header of the block before the transaction was applied. + :param transaction: the transaction used to generate the receipt + :param computation: the result of running the transaction computation + :param state: the resulting state, after executing the computation + + :return: receipt + """ + ... + + # + # Mining + # + @abstractmethod + def import_block(self, block: BlockAPI) -> BlockAPI: + ... + + @abstractmethod + def mine_block(self, *args: Any, **kwargs: Any) -> BlockAPI: + ... + + @abstractmethod + def set_block_transactions(self, + base_block: BlockAPI, + new_header: BlockHeaderAPI, + transactions: Sequence[SignedTransactionAPI], + receipts: Sequence[ReceiptAPI]) -> BlockAPI: + ... + + # + # Finalization + # + @abstractmethod + def finalize_block(self, block: BlockAPI) -> BlockAPI: + ... + + @abstractmethod + def pack_block(self, block: BlockAPI, *args: Any, **kwargs: Any) -> BlockAPI: + ... + + # + # Headers + # + @abstractmethod + def add_receipt_to_header(self, + old_header: BlockHeaderAPI, + receipt: ReceiptAPI) -> BlockHeaderAPI: + """ + Apply the receipt to the old header, and return the resulting header. This may have + storage-related side-effects. For example, pre-Byzantium, the state root hash + is included in the receipt, and so must be stored into the database. + """ + ... + + @classmethod + @abstractmethod + def compute_difficulty(cls, parent_header: BlockHeaderAPI, timestamp: int) -> int: + """ + Compute the difficulty for a block header. + + :param parent_header: the parent header + :param timestamp: the timestamp of the child header + """ + ... + + @abstractmethod + def configure_header(self, **header_params: Any) -> BlockHeaderAPI: + """ + Setup the current header with the provided parameters. This can be + used to set fields like the gas limit or timestamp to value different + than their computed defaults. + """ + ... + + @classmethod + @abstractmethod + def create_header_from_parent(cls, + parent_header: BlockHeaderAPI, + **header_params: Any) -> BlockHeaderAPI: + """ + Creates and initializes a new block header from the provided + `parent_header`. + """ + ... + + # + # Blocks + # + @classmethod + @abstractmethod + def generate_block_from_parent_header_and_coinbase(cls, + parent_header: BlockHeaderAPI, + coinbase: Address) -> BlockAPI: + ... + + @classmethod + @abstractmethod + def get_block_class(cls) -> Type[BlockAPI]: + ... + + @staticmethod + @abstractmethod + def get_block_reward() -> int: + """ + Return the amount in **wei** that should be given to a miner as a reward + for this block. + + .. note:: + This is an abstract method that must be implemented in subclasses + """ + ... + + @classmethod + @abstractmethod + def get_nephew_reward(cls) -> int: + """ + Return the reward which should be given to the miner of the given `nephew`. + + .. note:: + This is an abstract method that must be implemented in subclasses + """ + ... + + @classmethod + @abstractmethod + def get_prev_hashes(cls, + last_block_hash: Hash32, + chaindb: ChainDatabaseAPI) -> Optional[Iterable[Hash32]]: + ... + + @staticmethod + @abstractmethod + def get_uncle_reward(block_number: int, uncle: BlockAPI) -> int: + """ + Return the reward which should be given to the miner of the given `uncle`. + + .. note:: + This is an abstract method that must be implemented in subclasses + """ + ... + + # + # Transactions + # + @abstractmethod + def create_transaction(self, *args: Any, **kwargs: Any) -> SignedTransactionAPI: + ... + + @classmethod + @abstractmethod + def create_unsigned_transaction(cls, + *, + nonce: int, + gas_price: int, + gas: int, + to: Address, + value: int, + data: bytes) -> UnsignedTransactionAPI: + ... + + @classmethod + @abstractmethod + def get_transaction_class(cls) -> Type[SignedTransactionAPI]: + ... + + # + # Validate + # + @classmethod + @abstractmethod + def validate_receipt(self, receipt: ReceiptAPI) -> None: + ... + + @abstractmethod + def validate_block(self, block: BlockAPI) -> None: + ... + + @classmethod + @abstractmethod + def validate_header(cls, + header: BlockHeaderAPI, + parent_header: BlockHeaderAPI, + check_seal: bool = True + ) -> None: + ... + + @abstractmethod + def validate_transaction_against_header(self, + base_header: BlockHeaderAPI, + transaction: SignedTransactionAPI) -> None: + """ + Validate that the given transaction is valid to apply to the given header. + + :param base_header: header before applying the transaction + :param transaction: the transaction to validate + + :raises: ValidationError if the transaction is not valid to apply + """ + ... + + @classmethod + @abstractmethod + def validate_seal(cls, header: BlockHeaderAPI) -> None: + ... + + @classmethod + @abstractmethod + def validate_uncle(cls, + block: BlockAPI, + uncle: BlockHeaderAPI, + uncle_parent: BlockHeaderAPI + ) -> None: + ... + + # + # State + # + @classmethod + @abstractmethod + def get_state_class(cls) -> Type[StateAPI]: + ... + + @abstractmethod + def state_in_temp_block(self) -> ContextManager[StateAPI]: + ... + + +class HeaderChainAPI(ABC): + header: BlockHeaderAPI + chain_id: int + vm_configuration: Tuple[Tuple[int, Type[VirtualMachineAPI]], ...] + + @abstractmethod + def __init__(self, base_db: AtomicDatabaseAPI, header: BlockHeaderAPI = None) -> None: + ... + + # + # Chain Initialization API + # + @classmethod + @abstractmethod + def from_genesis_header(cls, + base_db: AtomicDatabaseAPI, + genesis_header: BlockHeaderAPI) -> 'HeaderChainAPI': + ... + + # + # Helpers + # + @classmethod + @abstractmethod + def get_headerdb_class(cls) -> Type[HeaderDatabaseAPI]: + ... + + # + # Canonical Chain API + # + @abstractmethod + def get_canonical_block_header_by_number(self, block_number: BlockNumber) -> BlockHeaderAPI: + ... + + @abstractmethod + def get_canonical_head(self) -> BlockHeaderAPI: + ... + + # + # Header API + # + @abstractmethod + def get_block_header_by_hash(self, block_hash: Hash32) -> BlockHeaderAPI: + ... + + @abstractmethod + def header_exists(self, block_hash: Hash32) -> bool: + ... + + @abstractmethod + def import_header(self, + header: BlockHeaderAPI, + ) -> Tuple[Tuple[BlockHeaderAPI, ...], Tuple[BlockHeaderAPI, ...]]: + ... + + +class ChainAPI(ConfigurableAPI): + vm_configuration: Tuple[Tuple[int, Type[VirtualMachineAPI]], ...] + chain_id: int + chaindb: ChainDatabaseAPI + + # + # Helpers + # + @classmethod + @abstractmethod + def get_chaindb_class(cls) -> Type[ChainDatabaseAPI]: + ... + + # + # Chain API + # + @classmethod + @abstractmethod + def from_genesis(cls, + base_db: AtomicDatabaseAPI, + genesis_params: Dict[str, HeaderParams], + genesis_state: AccountState=None) -> 'ChainAPI': + ... + + @classmethod + @abstractmethod + def from_genesis_header(cls, + base_db: AtomicDatabaseAPI, + genesis_header: BlockHeaderAPI) -> 'ChainAPI': + ... + + # + # VM API + # + @classmethod + def get_vm_class(cls, header: BlockHeaderAPI) -> Type[VirtualMachineAPI]: + """ + Returns the VM instance for the given block number. + """ + ... + + @abstractmethod + def get_vm(self, header: BlockHeaderAPI = None) -> VirtualMachineAPI: + ... + + @classmethod + def get_vm_class_for_block_number(cls, block_number: BlockNumber) -> Type[VirtualMachineAPI]: + ... + + # + # Header API + # + @abstractmethod + def create_header_from_parent(self, + parent_header: BlockHeaderAPI, + **header_params: HeaderParams) -> BlockHeaderAPI: + ... + + @abstractmethod + def get_block_header_by_hash(self, block_hash: Hash32) -> BlockHeaderAPI: + ... + + @abstractmethod + def get_canonical_head(self) -> BlockHeaderAPI: + ... + + @abstractmethod + def get_score(self, block_hash: Hash32) -> int: + ... + + # + # Block API + # + @abstractmethod + def get_ancestors(self, limit: int, header: BlockHeaderAPI) -> Tuple[BlockAPI, ...]: + ... + + @abstractmethod + def get_block(self) -> BlockAPI: + ... + + @abstractmethod + def get_block_by_hash(self, block_hash: Hash32) -> BlockAPI: + ... + + @abstractmethod + def get_block_by_header(self, block_header: BlockHeaderAPI) -> BlockAPI: + ... + + @abstractmethod + def get_canonical_block_by_number(self, block_number: BlockNumber) -> BlockAPI: + ... + + @abstractmethod + def get_canonical_block_hash(self, block_number: BlockNumber) -> Hash32: + ... + + @abstractmethod + def build_block_with_transactions( + self, + transactions: Tuple[SignedTransactionAPI, ...], + parent_header: BlockHeaderAPI = None + ) -> Tuple[BlockAPI, Tuple[ReceiptAPI, ...], Tuple[ComputationAPI, ...]]: + ... + + # + # Transaction API + # + @abstractmethod + def create_transaction(self, *args: Any, **kwargs: Any) -> SignedTransactionAPI: + ... + + @abstractmethod + def create_unsigned_transaction(cls, + *, + nonce: int, + gas_price: int, + gas: int, + to: Address, + value: int, + data: bytes) -> UnsignedTransactionAPI: + ... + + @abstractmethod + def get_canonical_transaction(self, transaction_hash: Hash32) -> SignedTransactionAPI: + ... + + @abstractmethod + def get_transaction_receipt(self, transaction_hash: Hash32) -> ReceiptAPI: + ... + + # + # Execution API + # + @abstractmethod + def get_transaction_result( + self, + transaction: SignedTransactionAPI, + at_header: BlockHeaderAPI) -> bytes: + ... + + @abstractmethod + def estimate_gas( + self, + transaction: SignedTransactionAPI, + at_header: BlockHeaderAPI = None) -> int: + ... + + @abstractmethod + def import_block(self, + block: BlockAPI, + perform_validation: bool=True, + ) -> Tuple[BlockAPI, Tuple[BlockAPI, ...], Tuple[BlockAPI, ...]]: + ... + + # + # Validation API + # + @abstractmethod + def validate_receipt(self, receipt: ReceiptAPI, at_header: BlockHeaderAPI) -> None: + ... + + @abstractmethod + def validate_block(self, block: BlockAPI) -> None: + ... + + @abstractmethod + def validate_seal(self, header: BlockHeaderAPI) -> None: + ... + + @abstractmethod + def validate_gaslimit(self, header: BlockHeaderAPI) -> None: + ... + + @abstractmethod + def validate_uncles(self, block: BlockAPI) -> None: + ... + + @classmethod + @abstractmethod + def validate_chain( + cls, + root: BlockHeaderAPI, + descendants: Tuple[BlockHeaderAPI, ...], + seal_check_random_sample_rate: int = 1) -> None: + ... + + +class MiningChainAPI(ChainAPI): + header: BlockHeaderAPI + + @abstractmethod + def __init__(self, base_db: AtomicDatabaseAPI, header: BlockHeaderAPI = None) -> None: + ... + + @abstractmethod + def apply_transaction(self, + transaction: SignedTransactionAPI + ) -> Tuple[BlockAPI, ReceiptAPI, ComputationAPI]: + ... + + @abstractmethod + def import_block(self, + block: BlockAPI, + perform_validation: bool=True + ) -> Tuple[BlockAPI, Tuple[BlockAPI, ...], Tuple[BlockAPI, ...]]: + ... + + @abstractmethod + def mine_block(self, *args: Any, **kwargs: Any) -> BlockAPI: + ... + + def get_vm(self, at_header: BlockHeaderAPI = None) -> VirtualMachineAPI: + ... diff --git a/eth/chains/base.py b/eth/chains/base.py index 6497e58d4b..8c78ee85a5 100644 --- a/eth/chains/base.py +++ b/eth/chains/base.py @@ -1,9 +1,5 @@ from __future__ import absolute_import -from abc import ( - ABC, - abstractmethod -) import operator import random from typing import ( @@ -11,6 +7,7 @@ Callable, Dict, Iterable, + Sequence, Tuple, Type, ) @@ -30,18 +27,38 @@ sliding_window, ) -from eth.vm.base import ( - BaseVM, +from eth._utils.db import ( + apply_state_dict, +) +from eth._utils.datatypes import ( + Configurable, +) +from eth._utils.headers import ( + compute_gas_limit_bounds, +) +from eth._utils.rlp import ( + validate_imported_block_unchanged, +) +from eth.abc import ( + BlockAPI, + MiningChainAPI, + AtomicDatabaseAPI, + BlockHeaderAPI, + ChainAPI, + ChainDatabaseAPI, + VirtualMachineAPI, + ReceiptAPI, + ComputationAPI, + StateAPI, + SignedTransactionAPI, + UnsignedTransactionAPI, ) - from eth.constants import ( EMPTY_UNCLE_HASH, MAX_UNCLE_DEPTH, ) -from eth.db.backends.base import BaseAtomicDB from eth.db.chain import ( - BaseChainDB, ChainDB, ) from eth.db.header import ( @@ -57,48 +74,22 @@ VMNotFound, ) -from eth.rlp.blocks import ( - BaseBlock, -) from eth.rlp.headers import ( BlockHeader, - HeaderParams, -) -from eth.rlp.receipts import ( - Receipt, -) -from eth.rlp.transactions import ( - BaseTransaction, - BaseUnsignedTransaction, ) from eth.typing import ( AccountState, - BaseOrSpoofTransaction, + HeaderParams, StaticMethod, ) -from eth._utils.db import ( - apply_state_dict, -) -from eth._utils.datatypes import ( - Configurable, -) -from eth._utils.headers import ( - compute_gas_limit_bounds, -) -from eth._utils.rlp import ( - validate_imported_block_unchanged, -) - from eth.validation import ( validate_block_number, validate_uint256, validate_word, validate_vm_configuration, ) -from eth.vm.computation import BaseComputation -from eth.vm.state import BaseState from eth._warnings import catch_and_ignore_import_warning with catch_and_ignore_import_warning(): @@ -115,61 +106,17 @@ ) -class BaseChain(Configurable, ABC): +class BaseChain(Configurable, ChainAPI): """ The base class for all Chain objects """ - chaindb: BaseChainDB = None - chaindb_class: Type[BaseChainDB] = None - vm_configuration: Tuple[Tuple[int, Type[BaseVM]], ...] = None + chaindb: ChainDatabaseAPI = None + chaindb_class: Type[ChainDatabaseAPI] = None + vm_configuration: Tuple[Tuple[int, Type[VirtualMachineAPI]], ...] = None chain_id: int = None - @abstractmethod - def __init__(self) -> None: - raise NotImplementedError("Chain classes must implement this method") - - # - # Helpers - # - @classmethod - @abstractmethod - def get_chaindb_class(cls) -> Type[BaseChainDB]: - raise NotImplementedError("Chain classes must implement this method") - - # - # Chain API - # @classmethod - @abstractmethod - def from_genesis(cls, - base_db: BaseAtomicDB, - genesis_params: Dict[str, HeaderParams], - genesis_state: AccountState=None) -> 'BaseChain': - raise NotImplementedError("Chain classes must implement this method") - - @classmethod - @abstractmethod - def from_genesis_header(cls, - base_db: BaseAtomicDB, - genesis_header: BlockHeader) -> 'BaseChain': - raise NotImplementedError("Chain classes must implement this method") - - # - # VM API - # - @classmethod - def get_vm_class(cls, header: BlockHeader) -> Type['BaseVM']: - """ - Returns the VM instance for the given block number. - """ - return cls.get_vm_class_for_block_number(header.block_number) - - @abstractmethod - def get_vm(self, header: BlockHeader=None) -> 'BaseVM': - raise NotImplementedError("Chain classes must implement this method") - - @classmethod - def get_vm_class_for_block_number(cls, block_number: BlockNumber) -> Type['BaseVM']: + def get_vm_class_for_block_number(cls, block_number: BlockNumber) -> Type[VirtualMachineAPI]: """ Returns the VM class for the given block number. """ @@ -183,140 +130,14 @@ def get_vm_class_for_block_number(cls, block_number: BlockNumber) -> Type['BaseV else: raise VMNotFound("No vm available for block #{0}".format(block_number)) - # - # Header API - # - @abstractmethod - def create_header_from_parent(self, - parent_header: BlockHeader, - **header_params: HeaderParams) -> BlockHeader: - raise NotImplementedError("Chain classes must implement this method") - - @abstractmethod - def get_block_header_by_hash(self, block_hash: Hash32) -> BlockHeader: - raise NotImplementedError("Chain classes must implement this method") - - @abstractmethod - def get_canonical_head(self) -> BlockHeader: - raise NotImplementedError("Chain classes must implement this method") - - @abstractmethod - def get_score(self, block_hash: Hash32) -> int: - raise NotImplementedError("Chain classes must implement this method") - - # - # Block API - # - @abstractmethod - def get_ancestors(self, limit: int, header: BlockHeader) -> Tuple[BaseBlock, ...]: - raise NotImplementedError("Chain classes must implement this method") - - @abstractmethod - def get_block(self) -> BaseBlock: - raise NotImplementedError("Chain classes must implement this method") - - @abstractmethod - def get_block_by_hash(self, block_hash: Hash32) -> BaseBlock: - raise NotImplementedError("Chain classes must implement this method") - - @abstractmethod - def get_block_by_header(self, block_header: BlockHeader) -> BaseBlock: - raise NotImplementedError("Chain classes must implement this method") - - @abstractmethod - def get_canonical_block_by_number(self, block_number: BlockNumber) -> BaseBlock: - raise NotImplementedError("Chain classes must implement this method") - - @abstractmethod - def get_canonical_block_hash(self, block_number: BlockNumber) -> Hash32: - raise NotImplementedError("Chain classes must implement this method") - - @abstractmethod - def build_block_with_transactions( - self, - transactions: Tuple[BaseTransaction, ...], - parent_header: BlockHeader=None - ) -> Tuple[BaseBlock, Tuple[Receipt, ...], Tuple[BaseComputation, ...]]: - raise NotImplementedError("Chain classes must implement this method") - - # - # Transaction API - # - @abstractmethod - def create_transaction(self, *args: Any, **kwargs: Any) -> BaseTransaction: - raise NotImplementedError("Chain classes must implement this method") - - @abstractmethod - def create_unsigned_transaction(cls, - *, - nonce: int, - gas_price: int, - gas: int, - to: Address, - value: int, - data: bytes) -> BaseUnsignedTransaction: - raise NotImplementedError("Chain classes must implement this method") - - @abstractmethod - def get_canonical_transaction(self, transaction_hash: Hash32) -> BaseTransaction: - raise NotImplementedError("Chain classes must implement this method") - - @abstractmethod - def get_transaction_receipt(self, transaction_hash: Hash32) -> Receipt: - raise NotImplementedError("Chain classes must implement this method") - - # - # Execution API - # - @abstractmethod - def get_transaction_result( - self, - transaction: BaseOrSpoofTransaction, - at_header: BlockHeader) -> bytes: - raise NotImplementedError("Chain classes must implement this method") - - @abstractmethod - def estimate_gas( - self, - transaction: BaseOrSpoofTransaction, - at_header: BlockHeader=None) -> int: - raise NotImplementedError("Chain classes must implement this method") - - @abstractmethod - def import_block(self, - block: BaseBlock, - perform_validation: bool=True, - ) -> Tuple[BaseBlock, Tuple[BaseBlock, ...], Tuple[BaseBlock, ...]]: - raise NotImplementedError("Chain classes must implement this method") - # # Validation API # - @abstractmethod - def validate_receipt(self, receipt: Receipt, at_header: BlockHeader) -> None: - raise NotImplementedError("Chain classes must implement this method") - - @abstractmethod - def validate_block(self, block: BaseBlock) -> None: - raise NotImplementedError("Chain classes must implement this method") - - @abstractmethod - def validate_seal(self, header: BlockHeader) -> None: - raise NotImplementedError("Chain classes must implement this method") - - @abstractmethod - def validate_gaslimit(self, header: BlockHeader) -> None: - raise NotImplementedError("Chain classes must implement this method") - - @abstractmethod - def validate_uncles(self, block: BaseBlock) -> None: - raise NotImplementedError("Chain classes must implement this method") - @classmethod def validate_chain( cls, - root: BlockHeader, - descendants: Tuple[BlockHeader, ...], + root: BlockHeaderAPI, + descendants: Tuple[BlockHeaderAPI, ...], seal_check_random_sample_rate: int = 1) -> None: """ Validate that all of the descendents are valid, given that the root header is valid. @@ -362,11 +183,11 @@ class Chain(BaseChain): current block number. """ logger = logging.getLogger("eth.chain.chain.Chain") - gas_estimator: StaticMethod[Callable[[BaseState, BaseOrSpoofTransaction], int]] = None + gas_estimator: StaticMethod[Callable[[StateAPI, SignedTransactionAPI], int]] = None - chaindb_class: Type[BaseChainDB] = ChainDB + chaindb_class: Type[ChainDatabaseAPI] = ChainDB - def __init__(self, base_db: BaseAtomicDB) -> None: + def __init__(self, base_db: AtomicDatabaseAPI) -> None: if not self.vm_configuration: raise ValueError( "The Chain class cannot be instantiated with an empty `vm_configuration`" @@ -383,7 +204,7 @@ def __init__(self, base_db: BaseAtomicDB) -> None: # Helpers # @classmethod - def get_chaindb_class(cls) -> Type[BaseChainDB]: + def get_chaindb_class(cls) -> Type[ChainDatabaseAPI]: if cls.chaindb_class is None: raise AttributeError("`chaindb_class` not set") return cls.chaindb_class @@ -393,7 +214,7 @@ def get_chaindb_class(cls) -> Type[BaseChainDB]: # @classmethod def from_genesis(cls, - base_db: BaseAtomicDB, + base_db: AtomicDatabaseAPI, genesis_params: Dict[str, HeaderParams], genesis_state: AccountState=None) -> 'BaseChain': """ @@ -431,8 +252,8 @@ def from_genesis(cls, @classmethod def from_genesis_header(cls, - base_db: BaseAtomicDB, - genesis_header: BlockHeader) -> 'BaseChain': + base_db: AtomicDatabaseAPI, + genesis_header: BlockHeaderAPI) -> 'BaseChain': """ Initializes the chain from the genesis header. """ @@ -443,7 +264,7 @@ def from_genesis_header(cls, # # VM API # - def get_vm(self, at_header: BlockHeader=None) -> 'BaseVM': + def get_vm(self, at_header: BlockHeaderAPI = None) -> VirtualMachineAPI: """ Returns the VM instance for the given block number. """ @@ -455,17 +276,17 @@ def get_vm(self, at_header: BlockHeader=None) -> 'BaseVM': # Header API # def create_header_from_parent(self, - parent_header: BlockHeader, - **header_params: HeaderParams) -> BlockHeader: + parent_header: BlockHeaderAPI, + **header_params: HeaderParams) -> BlockHeaderAPI: """ Passthrough helper to the VM class of the block descending from the given header. """ return self.get_vm_class_for_block_number( - block_number=parent_header.block_number + 1, + block_number=BlockNumber(parent_header.block_number + 1), ).create_header_from_parent(parent_header, **header_params) - def get_block_header_by_hash(self, block_hash: Hash32) -> BlockHeader: + def get_block_header_by_hash(self, block_hash: Hash32) -> BlockHeaderAPI: """ Returns the requested block header as specified by block hash. @@ -474,7 +295,7 @@ def get_block_header_by_hash(self, block_hash: Hash32) -> BlockHeader: validate_word(block_hash, title="Block Hash") return self.chaindb.get_block_header_by_hash(block_hash) - def get_canonical_head(self) -> BlockHeader: + def get_canonical_head(self) -> BlockHeaderAPI: """ Returns the block header at the canonical chain head. @@ -490,7 +311,7 @@ def get_score(self, block_hash: Hash32) -> int: """ return self.headerdb.get_score(block_hash) - def ensure_header(self, header: BlockHeader=None) -> BlockHeader: + def ensure_header(self, header: BlockHeaderAPI = None) -> BlockHeaderAPI: """ Return ``header`` if it is not ``None``, otherwise return the header of the canonical head. @@ -504,7 +325,7 @@ def ensure_header(self, header: BlockHeader=None) -> BlockHeader: # # Block API # - def get_ancestors(self, limit: int, header: BlockHeader) -> Tuple[BaseBlock, ...]: + def get_ancestors(self, limit: int, header: BlockHeaderAPI) -> Tuple[BlockAPI, ...]: """ Return `limit` number of ancestor blocks from the current canonical head. """ @@ -526,13 +347,13 @@ def get_ancestors(self, limit: int, header: BlockHeader) -> Tuple[BaseBlock, ... return tuple(take(ancestor_count, ancestor_generator)) - def get_block(self) -> BaseBlock: + def get_block(self) -> BlockAPI: """ Returns the current TIP block. """ return self.get_vm().get_block() - def get_block_by_hash(self, block_hash: Hash32) -> BaseBlock: + def get_block_by_hash(self, block_hash: Hash32) -> BlockAPI: """ Returns the requested block as specified by block hash. """ @@ -540,14 +361,14 @@ def get_block_by_hash(self, block_hash: Hash32) -> BaseBlock: block_header = self.get_block_header_by_hash(block_hash) return self.get_block_by_header(block_header) - def get_block_by_header(self, block_header: BlockHeader) -> BaseBlock: + def get_block_by_header(self, block_header: BlockHeaderAPI) -> BlockAPI: """ Returns the requested block as specified by the block header. """ vm = self.get_vm(block_header) return vm.get_block() - def get_canonical_block_by_number(self, block_number: BlockNumber) -> BaseBlock: + def get_canonical_block_by_number(self, block_number: BlockNumber) -> BlockAPI: """ Returns the block with the given number in the canonical chain. @@ -568,9 +389,9 @@ def get_canonical_block_hash(self, block_number: BlockNumber) -> Hash32: def build_block_with_transactions( self, - transactions: Tuple[BaseTransaction, ...], - parent_header: BlockHeader=None - ) -> Tuple[BaseBlock, Tuple[Receipt, ...], Tuple[BaseComputation, ...]]: + transactions: Sequence[SignedTransactionAPI], + parent_header: BlockHeaderAPI = None + ) -> Tuple[BlockAPI, Tuple[ReceiptAPI, ...], Tuple[ComputationAPI, ...]]: """ Generate a block with the provided transactions. This does *not* import that block into your chain. If you want this new block in your chain, @@ -591,7 +412,7 @@ def build_block_with_transactions( # # Transaction API # - def get_canonical_transaction(self, transaction_hash: Hash32) -> BaseTransaction: + def get_canonical_transaction(self, transaction_hash: Hash32) -> SignedTransactionAPI: """ Returns the requested transaction as specified by the transaction hash from the canonical chain. @@ -618,7 +439,7 @@ def get_canonical_transaction(self, transaction_hash: Hash32) -> BaseTransaction index, )) - def create_transaction(self, *args: Any, **kwargs: Any) -> BaseTransaction: + def create_transaction(self, *args: Any, **kwargs: Any) -> SignedTransactionAPI: """ Passthrough helper to the current VM class. """ @@ -631,7 +452,7 @@ def create_unsigned_transaction(self, gas: int, to: Address, value: int, - data: bytes) -> BaseUnsignedTransaction: + data: bytes) -> UnsignedTransactionAPI: """ Passthrough helper to the current VM class. """ @@ -644,7 +465,7 @@ def create_unsigned_transaction(self, data=data, ) - def get_transaction_receipt(self, transaction_hash: Hash32) -> Receipt: + def get_transaction_receipt(self, transaction_hash: Hash32) -> ReceiptAPI: transaction_block_number, transaction_index = self.chaindb.get_transaction_index( transaction_hash, ) @@ -660,8 +481,8 @@ def get_transaction_receipt(self, transaction_hash: Hash32) -> Receipt: # def get_transaction_result( self, - transaction: BaseOrSpoofTransaction, - at_header: BlockHeader) -> bytes: + transaction: SignedTransactionAPI, + at_header: BlockHeaderAPI) -> bytes: """ Return the result of running the given transaction. This is referred to as a `call()` in web3. @@ -674,8 +495,8 @@ def get_transaction_result( def estimate_gas( self, - transaction: BaseOrSpoofTransaction, - at_header: BlockHeader=None) -> int: + transaction: SignedTransactionAPI, + at_header: BlockHeaderAPI = None) -> int: """ Returns an estimation of the amount of gas the given transaction will use if executed on top of the block specified by the given header. @@ -686,9 +507,9 @@ def estimate_gas( return self.gas_estimator(state, transaction) def import_block(self, - block: BaseBlock, + block: BlockAPI, perform_validation: bool=True - ) -> Tuple[BaseBlock, Tuple[BaseBlock, ...], Tuple[BaseBlock, ...]]: + ) -> Tuple[BlockAPI, Tuple[BlockAPI, ...], Tuple[BlockAPI, ...]]: """ Imports a complete block and returns a 3-tuple @@ -744,11 +565,11 @@ def import_block(self, # # Validation API # - def validate_receipt(self, receipt: Receipt, at_header: BlockHeader) -> None: + def validate_receipt(self, receipt: ReceiptAPI, at_header: BlockHeaderAPI) -> None: VM_class = self.get_vm_class(at_header) VM_class.validate_receipt(receipt) - def validate_block(self, block: BaseBlock) -> None: + def validate_block(self, block: BlockAPI) -> None: """ Performs validation on a block that is either being mined or imported. @@ -766,14 +587,14 @@ def validate_block(self, block: BaseBlock) -> None: self.validate_uncles(block) self.validate_gaslimit(block.header) - def validate_seal(self, header: BlockHeader) -> None: + def validate_seal(self, header: BlockHeaderAPI) -> None: """ Validate the seal on the given header. """ VM_class = self.get_vm_class_for_block_number(BlockNumber(header.block_number)) VM_class.validate_seal(header) - def validate_gaslimit(self, header: BlockHeader) -> None: + def validate_gaslimit(self, header: BlockHeaderAPI) -> None: """ Validate the gas limit on the given header. """ @@ -788,7 +609,7 @@ def validate_gaslimit(self, header: BlockHeader) -> None: "The gas limit on block {0} is too high: {1}. It must be at most {2}".format( encode_hex(header.hash), header.gas_limit, high_bound)) - def validate_uncles(self, block: BaseBlock) -> None: + def validate_uncles(self, block: BlockAPI) -> None: """ Validate the uncles for the given block. """ @@ -861,26 +682,26 @@ def validate_uncles(self, block: BaseBlock) -> None: @to_set -def _extract_uncle_hashes(blocks: Iterable[BaseBlock]) -> Iterable[Hash32]: +def _extract_uncle_hashes(blocks: Iterable[BlockAPI]) -> Iterable[Hash32]: for block in blocks: for uncle in block.uncles: yield uncle.hash -class MiningChain(Chain): - header: BlockHeader = None +class MiningChain(Chain, MiningChainAPI): + header: BlockHeaderAPI = None - def __init__(self, base_db: BaseAtomicDB, header: BlockHeader=None) -> None: + def __init__(self, base_db: AtomicDatabaseAPI, header: BlockHeaderAPI = None) -> None: super().__init__(base_db) self.header = self.ensure_header(header) def apply_transaction(self, - transaction: BaseTransaction - ) -> Tuple[BaseBlock, Receipt, BaseComputation]: + transaction: SignedTransactionAPI + ) -> Tuple[BlockAPI, ReceiptAPI, ComputationAPI]: """ Applies the transaction to the current tip block. - WARNING: Receipt and Transaction trie generation is computationally + WARNING: ReceiptAPI and Transaction trie generation is computationally heavy and incurs significant performance overhead. """ vm = self.get_vm(self.header) @@ -903,16 +724,16 @@ def apply_transaction(self, return new_block, receipt, computation def import_block(self, - block: BaseBlock, + block: BlockAPI, perform_validation: bool=True - ) -> Tuple[BaseBlock, Tuple[BaseBlock, ...], Tuple[BaseBlock, ...]]: + ) -> Tuple[BlockAPI, Tuple[BlockAPI, ...], Tuple[BlockAPI, ...]]: imported_block, new_canonical_blocks, old_canonical_blocks = super().import_block( block, perform_validation) self.header = self.ensure_header() return imported_block, new_canonical_blocks, old_canonical_blocks - def mine_block(self, *args: Any, **kwargs: Any) -> BaseBlock: + def mine_block(self, *args: Any, **kwargs: Any) -> BlockAPI: """ Mines the current block. Proxies to the current Virtual Machine. See VM. :meth:`~eth.vm.base.VM.mine_block` @@ -925,7 +746,7 @@ def mine_block(self, *args: Any, **kwargs: Any) -> BaseBlock: self.header = self.create_header_from_parent(mined_block.header) return mined_block - def get_vm(self, at_header: BlockHeader=None) -> 'BaseVM': + def get_vm(self, at_header: BlockHeaderAPI = None) -> VirtualMachineAPI: if at_header is None: at_header = self.header diff --git a/eth/chains/header.py b/eth/chains/header.py index cb97b1b3d1..fdb3eb3efd 100644 --- a/eth/chains/header.py +++ b/eth/chains/header.py @@ -1,4 +1,3 @@ -from abc import ABC, abstractmethod from typing import ( cast, Tuple, @@ -10,88 +9,32 @@ Hash32, ) +from eth.abc import ( + AtomicDatabaseAPI, + BlockHeaderAPI, + HeaderChainAPI, + HeaderDatabaseAPI, +) from eth.db.backends.base import ( BaseAtomicDB, - BaseDB, ) from eth.db.header import ( - BaseHeaderDB, HeaderDB, ) -from eth.rlp.headers import BlockHeader from eth._utils.datatypes import ( Configurable, ) -from eth.vm.base import BaseVM - - -class BaseHeaderChain(Configurable, ABC): - _base_db: BaseDB = None - - _headerdb_class: Type[BaseHeaderDB] = None - _headerdb: BaseHeaderDB = None - header: BlockHeader = None - chain_id: int = None - vm_configuration: Tuple[Tuple[int, Type[BaseVM]], ...] = None - - @abstractmethod - def __init__(self, base_db: BaseDB, header: BlockHeader=None) -> None: - raise NotImplementedError("Chain classes must implement this method") - - # - # Chain Initialization API - # - @classmethod - @abstractmethod - def from_genesis_header(cls, - base_db: BaseDB, - genesis_header: BlockHeader) -> 'BaseHeaderChain': - raise NotImplementedError("Chain classes must implement this method") - - # - # Helpers - # - @classmethod - @abstractmethod - def get_headerdb_class(cls) -> Type[BaseHeaderDB]: - raise NotImplementedError("Chain classes must implement this method") - - # - # Canonical Chain API - # - @abstractmethod - def get_canonical_block_header_by_number(self, block_number: BlockNumber) -> BlockHeader: - raise NotImplementedError("Chain classes must implement this method") - - @abstractmethod - def get_canonical_head(self) -> BlockHeader: - raise NotImplementedError("Chain classes must implement this method") - - # - # Header API - # - @abstractmethod - def get_block_header_by_hash(self, block_hash: Hash32) -> BlockHeader: - raise NotImplementedError("Chain classes must implement this method") - - @abstractmethod - def header_exists(self, block_hash: Hash32) -> bool: - raise NotImplementedError("Chain classes must implement this method") - - @abstractmethod - def import_header(self, - header: BlockHeader - ) -> Tuple[Tuple[BlockHeader, ...], Tuple[BlockHeader, ...]]: - raise NotImplementedError("Chain classes must implement this method") +class HeaderChain(Configurable, HeaderChainAPI): + _base_db: AtomicDatabaseAPI = None + _headerdb: HeaderDatabaseAPI = None -class HeaderChain(BaseHeaderChain): - _headerdb_class: Type[BaseHeaderDB] = HeaderDB + _headerdb_class: Type[HeaderDatabaseAPI] = HeaderDB - def __init__(self, base_db: BaseDB, header: BlockHeader=None) -> None: + def __init__(self, base_db: AtomicDatabaseAPI, header: BlockHeaderAPI = None) -> None: self.base_db = base_db - self.headerdb = self.get_headerdb_class()(cast(BaseAtomicDB, base_db)) + self.headerdb = self.get_headerdb_class()(base_db) if header is None: self.header = self.get_canonical_head() @@ -103,8 +46,8 @@ def __init__(self, base_db: BaseDB, header: BlockHeader=None) -> None: # @classmethod def from_genesis_header(cls, - base_db: BaseDB, - genesis_header: BlockHeader) -> 'BaseHeaderChain': + base_db: AtomicDatabaseAPI, + genesis_header: BlockHeaderAPI) -> HeaderChainAPI: """ Initializes the chain from the genesis header. """ @@ -116,7 +59,7 @@ def from_genesis_header(cls, # Helpers # @classmethod - def get_headerdb_class(cls) -> Type[BaseHeaderDB]: + def get_headerdb_class(cls) -> Type[HeaderDatabaseAPI]: """ Returns the class which should be used for the `headerdb` """ @@ -133,13 +76,13 @@ def get_canonical_block_hash(self, block_number: BlockNumber) -> Hash32: """ return self.headerdb.get_canonical_block_hash(block_number) - def get_canonical_block_header_by_number(self, block_number: BlockNumber) -> BlockHeader: + def get_canonical_block_header_by_number(self, block_number: BlockNumber) -> BlockHeaderAPI: """ Direct passthrough to `headerdb` """ return self.headerdb.get_canonical_block_header_by_number(block_number) - def get_canonical_head(self) -> BlockHeader: + def get_canonical_head(self) -> BlockHeaderAPI: """ Direct passthrough to `headerdb` """ @@ -148,7 +91,7 @@ def get_canonical_head(self) -> BlockHeader: # # Header API # - def get_block_header_by_hash(self, block_hash: Hash32) -> BlockHeader: + def get_block_header_by_hash(self, block_hash: Hash32) -> BlockHeaderAPI: """ Direct passthrough to `headerdb` """ @@ -161,8 +104,8 @@ def header_exists(self, block_hash: Hash32) -> bool: return self.headerdb.header_exists(block_hash) def import_header(self, - header: BlockHeader - ) -> Tuple[Tuple[BlockHeader, ...], Tuple[BlockHeader, ...]]: + header: BlockHeaderAPI + ) -> Tuple[Tuple[BlockHeaderAPI, ...], Tuple[BlockHeaderAPI, ...]]: """ Direct passthrough to `headerdb` diff --git a/eth/chains/mainnet/__init__.py b/eth/chains/mainnet/__init__.py index a526a401b3..dfd671618b 100644 --- a/eth/chains/mainnet/__init__.py +++ b/eth/chains/mainnet/__init__.py @@ -21,11 +21,14 @@ ) from eth import constants as eth_constants +from eth.abc import ( + BlockHeaderAPI, + VirtualMachineAPI, +) from eth.chains.base import ( Chain, ) from eth.rlp.headers import BlockHeader -from eth.vm.base import BaseVM from eth.vm.forks import ( ByzantiumVM, FrontierVM, @@ -41,8 +44,8 @@ class MainnetDAOValidatorVM(HomesteadVM): @classmethod def validate_header(cls, - header: BlockHeader, - previous_header: BlockHeader, + header: BlockHeaderAPI, + previous_header: BlockHeaderAPI, check_seal: bool=True) -> None: super().validate_header(header, previous_header, check_seal) @@ -95,7 +98,7 @@ class MainnetHomesteadVM(MainnetDAOValidatorVM): class BaseMainnetChain: chain_id = MAINNET_CHAIN_ID - vm_configuration: Tuple[Tuple[int, Type[BaseVM]], ...] = MAINNET_VM_CONFIGURATION + vm_configuration: Tuple[Tuple[int, Type[VirtualMachineAPI]], ...] = MAINNET_VM_CONFIGURATION class MainnetChain(BaseMainnetChain, Chain): diff --git a/eth/chains/ropsten/__init__.py b/eth/chains/ropsten/__init__.py index 5be02189ae..2c7bb93bb9 100644 --- a/eth/chains/ropsten/__init__.py +++ b/eth/chains/ropsten/__init__.py @@ -11,9 +11,9 @@ ) from eth import constants +from eth.abc import VirtualMachineAPI from eth.chains.base import Chain from eth.rlp.headers import BlockHeader -from eth.vm.base import BaseVM from eth.vm.forks import ( ByzantiumVM, ConstantinopleVM, @@ -34,7 +34,7 @@ class BaseRopstenChain: - vm_configuration: Tuple[Tuple[int, Type[BaseVM]], ...] = ROPSTEN_VM_CONFIGURATION + vm_configuration: Tuple[Tuple[int, Type[VirtualMachineAPI]], ...] = ROPSTEN_VM_CONFIGURATION chain_id: int = ROPSTEN_CHAIN_ID diff --git a/eth/chains/tester/__init__.py b/eth/chains/tester/__init__.py index 04ad1bcb89..fb8b3b82af 100644 --- a/eth/chains/tester/__init__.py +++ b/eth/chains/tester/__init__.py @@ -19,25 +19,23 @@ ValidationError, ) +from eth.abc import ( + BlockAPI, + BlockHeaderAPI, + VirtualMachineAPI, +) from eth.chains.base import Chain from eth.chains.mainnet import MainnetChain -from eth.rlp.blocks import ( - BaseBlock, -) -from eth.rlp.headers import ( - BlockHeader -) from eth.validation import ( validate_gte, ) -from eth.vm.base import BaseVM from eth.vm.forks.homestead import HomesteadVM class MaintainGasLimitMixin(object): @classmethod def create_header_from_parent(cls, - parent_header: BlockHeader, + parent_header: BlockHeaderAPI, **header_params: Any) -> 'MaintainGasLimitMixin': """ Call the parent class method maintaining the same gas_limit as the @@ -55,8 +53,8 @@ def create_header_from_parent(cls, in MainnetChain.vm_configuration ) -ForkStartBlocks = Sequence[Tuple[int, Union[str, Type[BaseVM]]]] -VMStartBlock = Tuple[int, Type[BaseVM]] +ForkStartBlocks = Sequence[Tuple[int, Union[str, Type[VirtualMachineAPI]]]] +VMStartBlock = Tuple[int, Type[VirtualMachineAPI]] @to_tuple @@ -112,7 +110,7 @@ def _generate_vm_configuration(*fork_start_blocks: ForkStartBlocks, # Iterate over the parameters, generating a tuple of 2-tuples in the form: # (start_block, vm_class) for start_block, fork in ordered_fork_start_blocks: - if isinstance(fork, type) and issubclass(fork, BaseVM): + if isinstance(fork, type) and issubclass(fork, VirtualMachineAPI): vm_class = fork elif isinstance(fork, str): vm_class = MAINNET_VMS[fork] @@ -134,7 +132,7 @@ def _generate_vm_configuration(*fork_start_blocks: ForkStartBlocks, class BaseMainnetTesterChain(Chain): - vm_configuration: Tuple[Tuple[int, Type[BaseVM]], ...] = _generate_vm_configuration() + vm_configuration: Tuple[Tuple[int, Type[VirtualMachineAPI]], ...] = _generate_vm_configuration() class MainnetTesterChain(BaseMainnetTesterChain): @@ -147,7 +145,7 @@ class MainnetTesterChain(BaseMainnetTesterChain): configuration of fork rules. """ @classmethod - def validate_seal(cls, block: BaseBlock) -> None: + def validate_seal(cls, block: BlockAPI) -> None: """ We don't validate the proof of work seal on the tester chain. """ diff --git a/eth/db/__init__.py b/eth/db/__init__.py index bb4e7c8e34..e57cec8e48 100644 --- a/eth/db/__init__.py +++ b/eth/db/__init__.py @@ -7,21 +7,22 @@ from eth_utils import import_string -from eth.db.backends.base import BaseAtomicDB +from eth.abc import AtomicDatabaseAPI DEFAULT_DB_BACKEND = 'eth.db.atomic.AtomicDB' -def get_db_backend_class(import_path: str = None) -> Type[BaseAtomicDB]: +def get_db_backend_class(import_path: str = None) -> Type[AtomicDatabaseAPI]: if import_path is None: import_path = os.environ.get( 'CHAIN_DB_BACKEND_CLASS', DEFAULT_DB_BACKEND, ) - return cast(Type[BaseAtomicDB], import_string(import_path)) + return cast(Type[AtomicDatabaseAPI], import_string(import_path)) -def get_db_backend(import_path: str = None, **init_kwargs: Any) -> BaseAtomicDB: +def get_db_backend(import_path: str = None, **init_kwargs: Any) -> AtomicDatabaseAPI: backend_class = get_db_backend_class(import_path) - return backend_class(**init_kwargs) + # mypy doesn't understand the constructor of AtomicDatabaseAPI + return backend_class(**init_kwargs) # type: ignore diff --git a/eth/db/account.py b/eth/db/account.py index 4acc7a3df3..2d9cd0bf54 100644 --- a/eth/db/account.py +++ b/eth/db/account.py @@ -1,7 +1,3 @@ -from abc import ( - ABC, - abstractmethod -) import logging from lru import LRU from typing import ( @@ -30,14 +26,16 @@ exceptions as trie_exceptions, ) +from eth.abc import ( + AccountDatabaseAPI, + AccountStorageDatabaseAPI, + AtomicDatabaseAPI, + DatabaseAPI, +) from eth.constants import ( BLANK_ROOT_HASH, EMPTY_SHA3, ) -from eth.db.backends.base import ( - BaseDB, - BaseAtomicDB, -) from eth.db.batch import ( BatchDB, ) @@ -53,7 +51,7 @@ from eth.db.storage import ( AccountStorageDB, ) -from eth.db.typing import ( +from eth.typing import ( JournalDBCheckpoint, ) from eth.vm.interrupt import ( @@ -75,142 +73,11 @@ from .hash_trie import HashTrie -class BaseAccountDB(ABC): - - @abstractmethod - def __init__(self) -> None: - raise NotImplementedError( - "Must be implemented by subclasses" - ) - - @property - @abstractmethod - def state_root(self) -> Hash32: - raise NotImplementedError("Must be implemented by subclasses") - - @abstractmethod - def has_root(self, state_root: bytes) -> bool: - raise NotImplementedError("Must be implemented by subclasses") - - # - # Storage - # - @abstractmethod - def get_storage(self, address: Address, slot: int, from_journal: bool=True) -> int: - raise NotImplementedError("Must be implemented by subclasses") - - @abstractmethod - def set_storage(self, address: Address, slot: int, value: int) -> None: - raise NotImplementedError("Must be implemented by subclasses") - - @abstractmethod - def delete_storage(self, address: Address) -> None: - """ - Delete *all* storage values in this account. This action is journaled, like set() actions. - - Unlike set(), deleting storage will not cause :meth:`make_storage_roots` to emit a new - storage root (and therefore will not persist deletes to the base database). - The account's storage root must be explicitly set to the empty root. - """ - raise NotImplementedError("Must be implemented by subclasses") - - # - # Nonce - # - @abstractmethod - def get_nonce(self, address: Address) -> int: - raise NotImplementedError("Must be implemented by subclasses") - - @abstractmethod - def set_nonce(self, address: Address, nonce: int) -> None: - raise NotImplementedError("Must be implemented by subclasses") - - # - # Balance - # - @abstractmethod - def get_balance(self, address: Address) -> int: - raise NotImplementedError("Must be implemented by subclasses") - - @abstractmethod - def set_balance(self, address: Address, balance: int) -> None: - raise NotImplementedError("Must be implemented by subclasses") - - # - # Code - # - @abstractmethod - def set_code(self, address: Address, code: bytes) -> None: - raise NotImplementedError("Must be implemented by subclasses") - - @abstractmethod - def get_code(self, address: Address) -> bytes: - raise NotImplementedError("Must be implemented by subclasses") - - @abstractmethod - def get_code_hash(self, address: Address) -> Hash32: - raise NotImplementedError("Must be implemented by subclasses") - - @abstractmethod - def delete_code(self, address: Address) -> None: - raise NotImplementedError("Must be implemented by subclasses") - - # - # Account Methods - # - @abstractmethod - def account_is_empty(self, address: Address) -> bool: - raise NotImplementedError("Must be implemented by subclass") - - # - # Record and discard API - # - @abstractmethod - def record(self) -> JournalDBCheckpoint: - raise NotImplementedError("Must be implemented by subclass") - - @abstractmethod - def discard(self, checkpoint: JournalDBCheckpoint) -> None: - raise NotImplementedError("Must be implemented by subclass") - - @abstractmethod - def commit(self, checkpoint: JournalDBCheckpoint) -> None: - raise NotImplementedError("Must be implemented by subclass") - - @abstractmethod - def make_state_root(self) -> Hash32: - """ - Generate the state root with all the current changes in AccountDB - - Current changes include every pending change to storage, as well as all account changes. - After generating all the required tries, the final account state root is returned. - - This is an expensive operation, so should be called as little as possible. For example, - pre-Byzantium, this is called after every transaction, because we need the state root - in each receipt. Byzantium+, we only need state roots at the end of the block, - so we *only* call it right before persistance. - - :return: the new state root - """ - raise NotImplementedError("Must be implemented by subclass") - - @abstractmethod - def persist(self) -> None: - """ - Send changes to underlying database, including the trie state - so that it will forever be possible to read the trie from this checkpoint. - - :meth:`make_state_root` must be explicitly called before this method. - Otherwise persist will raise a ValidationError. - """ - raise NotImplementedError("Must be implemented by subclass") - - -class AccountDB(BaseAccountDB): +class AccountDB(AccountDatabaseAPI): logger = cast(ExtendedDebugLogger, logging.getLogger('eth.db.account.AccountDB')) - def __init__(self, db: BaseAtomicDB, state_root: Hash32=BLANK_ROOT_HASH) -> None: + def __init__(self, db: AtomicDatabaseAPI, state_root: Hash32=BLANK_ROOT_HASH) -> None: r""" Internal implementation details (subject to rapid change): Database entries go through several pipes, like so... @@ -256,7 +123,7 @@ def __init__(self, db: BaseAtomicDB, state_root: Hash32=BLANK_ROOT_HASH) -> None self._trie_cache = CacheDB(self._trie) self._journaltrie = JournalDB(self._trie_cache) self._account_cache = LRU(2048) - self._account_stores: Dict[Address, AccountStorageDB] = {} + self._account_stores: Dict[Address, AccountStorageDatabaseAPI] = {} self._dirty_accounts: Set[Address] = set() self._root_hash_at_last_persist = state_root @@ -306,7 +173,7 @@ def _wipe_storage(self, address: Address) -> None: self._dirty_accounts.add(address) account_store.delete() - def _get_address_store(self, address: Address) -> AccountStorageDB: + def _get_address_store(self, address: Address) -> AccountStorageDatabaseAPI: if address in self._account_stores: store = self._account_stores[address] else: @@ -315,7 +182,7 @@ def _get_address_store(self, address: Address) -> AccountStorageDB: self._account_stores[address] = store return store - def _dirty_account_stores(self) -> Iterable[Tuple[Address, AccountStorageDB]]: + def _dirty_account_stores(self) -> Iterable[Tuple[Address, AccountStorageDatabaseAPI]]: for address in self._dirty_accounts: store = self._account_stores[address] yield address, store @@ -335,7 +202,7 @@ def _set_storage_root(self, address: Address, new_storage_root: Hash32) -> None: account = self._get_account(address) self._set_account(address, account.copy(storage_root=new_storage_root)) - def _validate_flushed_storage(self, address: Address, store: AccountStorageDB) -> None: + def _validate_flushed_storage(self, address: Address, store: AccountStorageDatabaseAPI) -> None: if store.has_changed_root: actual_storage_root = self._get_storage_root(address) expected_storage_root = store.get_changed_root() @@ -598,7 +465,7 @@ def _log_pending_accounts(self) -> None: self.account_exists(cast_deleted_address), ) - def _apply_account_diff_without_proof(self, diff: DBDiff, trie: BaseDB) -> None: + def _apply_account_diff_without_proof(self, diff: DBDiff, trie: DatabaseAPI) -> None: """ Apply diff of trie updates, when original nodes might be missing. Note that doing this naively will raise exceptions about missing nodes diff --git a/eth/db/atomic.py b/eth/db/atomic.py index 52f7cfaf79..d6229e5416 100644 --- a/eth/db/atomic.py +++ b/eth/db/atomic.py @@ -1,7 +1,6 @@ from contextlib import contextmanager import logging from typing import ( - Generator, Iterator, ) @@ -9,6 +8,10 @@ ValidationError, ) +from eth.abc import ( + DatabaseAPI, +) + from eth.db.diff import ( DBDiff, DBDiffTracker, @@ -25,10 +28,10 @@ class AtomicDB(BaseAtomicDB): """ logger = logging.getLogger("eth.db.AtomicDB") - wrapped_db: BaseDB = None + wrapped_db: DatabaseAPI = None _track_diff: DBDiffTracker = None - def __init__(self, wrapped_db: BaseDB = None) -> None: + def __init__(self, wrapped_db: DatabaseAPI = None) -> None: if wrapped_db is None: self.wrapped_db = MemoryDB() else: @@ -47,7 +50,7 @@ def _exists(self, key: bytes) -> bool: return key in self.wrapped_db @contextmanager - def atomic_batch(self) -> Generator['AtomicDBWriteBatch', None, None]: + def atomic_batch(self) -> Iterator['AtomicDBWriteBatch']: with AtomicDBWriteBatch._commit_unless_raises(self) as readable_batch: yield readable_batch @@ -59,10 +62,10 @@ class AtomicDBWriteBatch(BaseDB): """ logger = logging.getLogger("eth.db.AtomicDBWriteBatch") - _write_target_db: BaseDB = None + _write_target_db: DatabaseAPI = None _track_diff: DBDiffTracker = None - def __init__(self, write_target_db: BaseDB) -> None: + def __init__(self, write_target_db: DatabaseAPI) -> None: self._write_target_db = write_target_db self._track_diff = DBDiffTracker() @@ -113,7 +116,7 @@ def _exists(self, key: bytes) -> bool: @classmethod @contextmanager - def _commit_unless_raises(cls, write_target_db: BaseDB) -> Iterator['AtomicDBWriteBatch']: + def _commit_unless_raises(cls, write_target_db: DatabaseAPI) -> Iterator['AtomicDBWriteBatch']: """ Commit all writes inside the context, unless an exception was raised. diff --git a/eth/db/backends/base.py b/eth/db/backends/base.py index 9acb4fb3db..52f8f69a09 100644 --- a/eth/db/backends/base.py +++ b/eth/db/backends/base.py @@ -1,24 +1,14 @@ -from abc import ( - ABC, - abstractmethod -) -from collections.abc import ( - MutableMapping, -) - from typing import ( - Any, Iterator, - TYPE_CHECKING, ) -if TYPE_CHECKING: - MM = MutableMapping[bytes, bytes] -else: - MM = MutableMapping +from eth.abc import ( + AtomicDatabaseAPI, + DatabaseAPI, +) -class BaseDB(MM, ABC): +class BaseDB(DatabaseAPI): """ This is an abstract key/value lookup with all :class:`bytes` values, with some convenience methods for databases. As much as possible, @@ -33,13 +23,6 @@ class BaseDB(MM, ABC): Subclasses may optionally implement an _exists method that is type-checked for key and value. """ - - @abstractmethod - def __init__(self) -> None: - raise NotImplementedError( - "The `init` method must be implemented by subclasses of BaseDB" - ) - def set(self, key: bytes, value: bytes) -> None: self[key] = value @@ -66,7 +49,7 @@ def __len__(self) -> int: raise NotImplementedError("By default, DB classes cannot return the total number of keys.") -class BaseAtomicDB(BaseDB): +class BaseAtomicDB(BaseDB, AtomicDatabaseAPI): """ This is an abstract key/value lookup that permits batching of updates, such that the batch of changes are atomically saved. They are either all saved, or none are. @@ -91,6 +74,4 @@ class BaseAtomicDB(BaseDB): # when exiting the context, the values are saved either key and key2 will both be saved, # or neither will """ - @abstractmethod - def atomic_batch(self) -> Any: - raise NotImplementedError + pass diff --git a/eth/db/backends/level.py b/eth/db/backends/level.py index b7f0c177cd..24705013ba 100644 --- a/eth/db/backends/level.py +++ b/eth/db/backends/level.py @@ -8,6 +8,7 @@ from eth_utils import ValidationError +from eth.abc import DatabaseAPI from eth.db.diff import ( DBDiffTracker, DiffMissingError, @@ -81,7 +82,7 @@ class LevelDBWriteBatch(BaseDB): """ logger = logging.getLogger("eth.db.backends.LevelDBWriteBatch") - def __init__(self, original_read_db: BaseDB, write_batch: 'plyvel.WriteBatch') -> None: + def __init__(self, original_read_db: DatabaseAPI, write_batch: 'plyvel.WriteBatch') -> None: self._original_read_db = original_read_db self._write_batch = write_batch # keep track of the temporary changes made diff --git a/eth/db/batch.py b/eth/db/batch.py index 9ee6ea6533..a9835812e8 100644 --- a/eth/db/batch.py +++ b/eth/db/batch.py @@ -4,6 +4,7 @@ ValidationError, ) +from eth.abc import DatabaseAPI from eth.db.diff import ( DBDiff, DBDiffTracker, @@ -23,10 +24,10 @@ class BatchDB(BaseDB): """ logger = logging.getLogger("eth.db.BatchDB") - wrapped_db: BaseDB = None + wrapped_db: DatabaseAPI = None _track_diff: DBDiffTracker = None - def __init__(self, wrapped_db: BaseDB, read_through_deletes: bool = False) -> None: + def __init__(self, wrapped_db: DatabaseAPI, read_through_deletes: bool = False) -> None: self.wrapped_db = wrapped_db self._track_diff = DBDiffTracker() self._read_through_deletes = read_through_deletes @@ -48,7 +49,7 @@ def clear(self) -> None: def commit(self, apply_deletes: bool = True) -> None: self.commit_to(self.wrapped_db, apply_deletes) - def commit_to(self, target_db: BaseDB, apply_deletes: bool = True) -> None: + def commit_to(self, target_db: DatabaseAPI, apply_deletes: bool = True) -> None: if apply_deletes and self._read_through_deletes: raise ValidationError("BatchDB should never apply deletes when reading through deletes") diff = self.diff() diff --git a/eth/db/cache.py b/eth/db/cache.py index d43f62f239..6bb7bfddf7 100644 --- a/eth/db/cache.py +++ b/eth/db/cache.py @@ -1,5 +1,6 @@ from lru import LRU +from eth.abc import DatabaseAPI from eth.db.backends.base import BaseDB @@ -8,7 +9,7 @@ class CacheDB(BaseDB): Set and get decoded RLP objects, where the underlying db stores encoded objects. """ - def __init__(self, db: BaseDB, cache_size: int=2048) -> None: + def __init__(self, db: DatabaseAPI, cache_size: int=2048) -> None: self._db = db self._cache_size = cache_size self.reset_cache() diff --git a/eth/db/chain.py b/eth/db/chain.py index fa480d18dd..522379aff9 100644 --- a/eth/db/chain.py +++ b/eth/db/chain.py @@ -1,16 +1,11 @@ import functools import itertools -from abc import ( - abstractmethod -) from typing import ( Dict, Iterable, - List, Tuple, Type, - TYPE_CHECKING, ) from eth_typing import ( @@ -23,6 +18,15 @@ from eth_hash.auto import keccak +from eth.abc import ( + BlockAPI, + BlockHeaderAPI, + ChainDatabaseAPI, + DatabaseAPI, + AtomicDatabaseAPI, + ReceiptAPI, + SignedTransactionAPI, +) from eth.constants import ( EMPTY_UNCLE_HASH, ) @@ -31,11 +35,7 @@ ReceiptNotFound, TransactionNotFound, ) -from eth.db.header import BaseHeaderDB, HeaderDB -from eth.db.backends.base import ( - BaseAtomicDB, - BaseDB, -) +from eth.db.header import HeaderDB from eth.db.schema import SchemaV1 from eth.rlp.headers import ( BlockHeader, @@ -53,17 +53,10 @@ HexaryTrie, ) from eth_utils import ( - to_list, to_tuple, ValidationError, ) -if TYPE_CHECKING: - from eth.rlp.blocks import ( # noqa: F401 - BaseBlock, - BaseTransaction - ) - class TransactionKey(rlp.Serializable): fields = [ @@ -72,113 +65,20 @@ class TransactionKey(rlp.Serializable): ] -class BaseChainDB(BaseHeaderDB): - db: BaseAtomicDB = None - - @abstractmethod - def __init__(self, db: BaseAtomicDB) -> None: - raise NotImplementedError("ChainDB classes must implement this method") - - # - # Header API - # - @abstractmethod - def get_block_uncles(self, uncles_hash: Hash32) -> List[BlockHeader]: - raise NotImplementedError("ChainDB classes must implement this method") - - # - # Block API - # - @abstractmethod - def persist_block(self, - block: 'BaseBlock' - ) -> Tuple[Tuple[Hash32, ...], Tuple[Hash32, ...]]: - raise NotImplementedError("ChainDB classes must implement this method") - - @abstractmethod - def persist_uncles(self, uncles: Tuple[BlockHeader]) -> Hash32: - raise NotImplementedError("ChainDB classes must implement this method") - - # - # Transaction API - # - @abstractmethod - def add_receipt(self, - block_header: BlockHeader, - index_key: int, receipt: Receipt) -> Hash32: - raise NotImplementedError("ChainDB classes must implement this method") - - @abstractmethod - def add_transaction(self, - block_header: BlockHeader, - index_key: int, transaction: 'BaseTransaction') -> Hash32: - raise NotImplementedError("ChainDB classes must implement this method") - - @abstractmethod - def get_block_transactions( - self, - block_header: BlockHeader, - transaction_class: Type['BaseTransaction']) -> Iterable['BaseTransaction']: - raise NotImplementedError("ChainDB classes must implement this method") - - @abstractmethod - def get_block_transaction_hashes(self, block_header: BlockHeader) -> Iterable[Hash32]: - raise NotImplementedError("ChainDB classes must implement this method") - - @abstractmethod - def get_receipt_by_index(self, - block_number: BlockNumber, - receipt_index: int) -> Receipt: - raise NotImplementedError("ChainDB classes must implement this method") - - @abstractmethod - def get_receipts(self, - header: BlockHeader, - receipt_class: Type[Receipt]) -> Iterable[Receipt]: - raise NotImplementedError("ChainDB classes must implement this method") - - @abstractmethod - def get_transaction_by_index( - self, - block_number: BlockNumber, - transaction_index: int, - transaction_class: Type['BaseTransaction']) -> 'BaseTransaction': - raise NotImplementedError("ChainDB classes must implement this method") - - @abstractmethod - def get_transaction_index(self, transaction_hash: Hash32) -> Tuple[BlockNumber, int]: - raise NotImplementedError("ChainDB classes must implement this method") - - # - # Raw Database API - # - @abstractmethod - def exists(self, key: bytes) -> bool: - raise NotImplementedError("ChainDB classes must implement this method") - - @abstractmethod - def get(self, key: bytes) -> bytes: - raise NotImplementedError("ChainDB classes must implement this method") - - @abstractmethod - def persist_trie_data_dict(self, trie_data_dict: Dict[Hash32, bytes]) -> None: - raise NotImplementedError("ChainDB classes must implement this method") - - -class ChainDB(HeaderDB, BaseChainDB): - def __init__(self, db: BaseAtomicDB) -> None: +class ChainDB(HeaderDB, ChainDatabaseAPI): + def __init__(self, db: AtomicDatabaseAPI) -> None: self.db = db # # Header API # - def get_block_uncles(self, uncles_hash: Hash32) -> List[BlockHeader]: + def get_block_uncles(self, uncles_hash: Hash32) -> Tuple[BlockHeaderAPI, ...]: """ Returns an iterable of uncle headers specified by the given uncles_hash """ validate_word(uncles_hash, title="Uncles Hash") if uncles_hash == EMPTY_UNCLE_HASH: - return [] + return () try: encoded_uncles = self.db[uncles_hash] except KeyError: @@ -186,13 +86,13 @@ def get_block_uncles(self, uncles_hash: Hash32) -> List[BlockHeader]: "No uncles found for hash {0}".format(uncles_hash) ) else: - return rlp.decode(encoded_uncles, sedes=rlp.sedes.CountableList(BlockHeader)) + return tuple(rlp.decode(encoded_uncles, sedes=rlp.sedes.CountableList(BlockHeader))) @classmethod def _set_as_canonical_chain_head(cls, - db: BaseDB, + db: DatabaseAPI, block_hash: Hash32, - ) -> Tuple[Tuple[BlockHeader, ...], Tuple[BlockHeader, ...]]: + ) -> Tuple[Tuple[BlockHeaderAPI, ...], Tuple[BlockHeaderAPI, ...]]: # noqa: E501 try: header = cls._get_block_header_by_hash(db, block_hash) except HeaderNotFound: @@ -226,7 +126,7 @@ def _set_as_canonical_chain_head(cls, # Block API # def persist_block(self, - block: 'BaseBlock' + block: BlockAPI ) -> Tuple[Tuple[Hash32, ...], Tuple[Hash32, ...]]: """ Persist the given block's header and uncles. @@ -239,8 +139,8 @@ def persist_block(self, @classmethod def _persist_block( cls, - db: 'BaseDB', - block: 'BaseBlock') -> Tuple[Tuple[Hash32, ...], Tuple[Hash32, ...]]: + db: DatabaseAPI, + block: BlockAPI) -> Tuple[Tuple[Hash32, ...], Tuple[Hash32, ...]]: header_chain = (block.header, ) new_canonical_headers, old_canonical_headers = cls._persist_header_chain(db, header_chain) @@ -249,7 +149,7 @@ def _persist_block( # Most of the time this is called to persist a block whose parent is the current # head, so we optimize for that and read the tx hashes from the block itself. This # is specially important during a fast sync. - tx_hashes = [tx.hash for tx in block.transactions] + tx_hashes = tuple(tx.hash for tx in block.transactions) else: tx_hashes = cls._get_block_transaction_hashes(db, header) @@ -270,7 +170,7 @@ def _persist_block( return new_canonical_hashes, old_canonical_hashes - def persist_uncles(self, uncles: Tuple[BlockHeader]) -> Hash32: + def persist_uncles(self, uncles: Tuple[BlockHeaderAPI]) -> Hash32: """ Persists the list of uncles to the database. @@ -279,7 +179,7 @@ def persist_uncles(self, uncles: Tuple[BlockHeader]) -> Hash32: return self._persist_uncles(self.db, uncles) @staticmethod - def _persist_uncles(db: BaseDB, uncles: Tuple[BlockHeader]) -> Hash32: + def _persist_uncles(db: DatabaseAPI, uncles: Tuple[BlockHeaderAPI]) -> Hash32: uncles_hash = keccak(rlp.encode(uncles)) db.set( uncles_hash, @@ -289,7 +189,9 @@ def _persist_uncles(db: BaseDB, uncles: Tuple[BlockHeader]) -> Hash32: # # Transaction API # - def add_receipt(self, block_header: BlockHeader, index_key: int, receipt: Receipt) -> Hash32: + def add_receipt(self, + block_header: BlockHeaderAPI, + index_key: int, receipt: ReceiptAPI) -> Hash32: """ Adds the given receipt to the provided block header. @@ -300,9 +202,9 @@ def add_receipt(self, block_header: BlockHeader, index_key: int, receipt: Receip return receipt_db.root_hash def add_transaction(self, - block_header: BlockHeader, + block_header: BlockHeaderAPI, index_key: int, - transaction: 'BaseTransaction') -> Hash32: + transaction: SignedTransactionAPI) -> Hash32: """ Adds the given transaction to the provided block header. @@ -314,15 +216,15 @@ def add_transaction(self, def get_block_transactions( self, - header: BlockHeader, - transaction_class: Type['BaseTransaction']) -> Iterable['BaseTransaction']: + header: BlockHeaderAPI, + transaction_class: Type[SignedTransactionAPI]) -> Tuple[SignedTransactionAPI, ...]: """ Returns an iterable of transactions for the block speficied by the given block header. """ return self._get_block_transactions(header.transaction_root, transaction_class) - def get_block_transaction_hashes(self, block_header: BlockHeader) -> Iterable[Hash32]: + def get_block_transaction_hashes(self, block_header: BlockHeaderAPI) -> Tuple[Hash32, ...]: """ Returns an iterable of the transaction hashes from the block specified by the given block header. @@ -330,11 +232,11 @@ def get_block_transaction_hashes(self, block_header: BlockHeader) -> Iterable[Ha return self._get_block_transaction_hashes(self.db, block_header) @classmethod - @to_list + @to_tuple def _get_block_transaction_hashes( cls, - db: BaseDB, - block_header: BlockHeader) -> Iterable[Hash32]: + db: DatabaseAPI, + block_header: BlockHeaderAPI) -> Iterable[Hash32]: all_encoded_transactions = cls._get_block_transaction_data( db, block_header.transaction_root, @@ -344,8 +246,8 @@ def _get_block_transaction_hashes( @to_tuple def get_receipts(self, - header: BlockHeader, - receipt_class: Type[Receipt]) -> Iterable[Receipt]: + header: BlockHeaderAPI, + receipt_class: Type[ReceiptAPI]) -> Iterable[ReceiptAPI]: """ Returns an iterable of receipts for the block specified by the given block header. @@ -363,7 +265,7 @@ def get_transaction_by_index( self, block_number: BlockNumber, transaction_index: int, - transaction_class: Type['BaseTransaction']) -> 'BaseTransaction': + transaction_class: Type[SignedTransactionAPI]) -> SignedTransactionAPI: """ Returns the transaction at the specified `transaction_index` from the block specified by `block_number` from the canonical chain. @@ -404,7 +306,7 @@ def get_transaction_index(self, transaction_hash: Hash32) -> Tuple[BlockNumber, def get_receipt_by_index(self, block_number: BlockNumber, - receipt_index: int) -> Receipt: + receipt_index: int) -> ReceiptAPI: """ Returns the Receipt of the transaction at specified index for the block header obtained by the specified block number @@ -424,7 +326,7 @@ def get_receipt_by_index(self, "Receipt with index {} not found in block".format(receipt_index)) @staticmethod - def _get_block_transaction_data(db: BaseDB, transaction_root: Hash32) -> Iterable[Hash32]: + def _get_block_transaction_data(db: DatabaseAPI, transaction_root: Hash32) -> Iterable[Hash32]: """ Returns iterable of the encoded transactions for the given block header """ @@ -437,11 +339,11 @@ def _get_block_transaction_data(db: BaseDB, transaction_root: Hash32) -> Iterabl break @functools.lru_cache(maxsize=32) - @to_list + @to_tuple def _get_block_transactions( self, transaction_root: Hash32, - transaction_class: Type['BaseTransaction']) -> Iterable['BaseTransaction']: + transaction_class: Type[SignedTransactionAPI]) -> Iterable[SignedTransactionAPI]: """ Memoizable version of `get_block_transactions` """ @@ -449,7 +351,7 @@ def _get_block_transactions( yield rlp.decode(encoded_transaction, sedes=transaction_class) @staticmethod - def _remove_transaction_from_canonical_chain(db: BaseDB, transaction_hash: Hash32) -> None: + def _remove_transaction_from_canonical_chain(db: DatabaseAPI, transaction_hash: Hash32) -> None: """ Removes the transaction specified by the given hash from the canonical chain. @@ -457,9 +359,9 @@ def _remove_transaction_from_canonical_chain(db: BaseDB, transaction_hash: Hash3 db.delete(SchemaV1.make_transaction_hash_to_block_lookup_key(transaction_hash)) @staticmethod - def _add_transaction_to_canonical_chain(db: BaseDB, + def _add_transaction_to_canonical_chain(db: DatabaseAPI, transaction_hash: Hash32, - block_header: BlockHeader, + block_header: BlockHeaderAPI, index: int) -> None: """ :param bytes transaction_hash: the hash of the transaction to add the lookup for diff --git a/eth/db/diff.py b/eth/db/diff.py index e0ae78f827..aa84cb7389 100644 --- a/eth/db/diff.py +++ b/eth/db/diff.py @@ -16,7 +16,7 @@ to_tuple, ) -from eth.db.backends.base import BaseDB +from eth.abc import DatabaseAPI from eth.vm.interrupt import EVMMissingData if TYPE_CHECKING: @@ -54,7 +54,7 @@ def is_deleted(self) -> bool: class DBDiffTracker(ABC_Mutable_Mapping): """ - Records changes to a :class:`~eth.db.BaseDB` + Records changes to a :class:`~eth.abc.DatabaseAPI` If no value is available for a key, it could be for one of two reasons: - the key was never updated during tracking @@ -182,7 +182,7 @@ def pending_items(self) -> Iterable[Tuple[bytes, bytes]]: yield key, value # type: ignore # value can only be DELETED or actual new value def apply_to(self, - db: Union[BaseDB, ABC_Mutable_Mapping], + db: Union[DatabaseAPI, ABC_Mutable_Mapping], apply_deletes: bool = True) -> None: """ Apply the changes in this diff to the given database. diff --git a/eth/db/header.py b/eth/db/header.py index 8e35f5f648..de222296c1 100644 --- a/eth/db/header.py +++ b/eth/db/header.py @@ -1,4 +1,3 @@ -from abc import ABC, abstractmethod import functools from typing import Iterable, Tuple @@ -21,6 +20,12 @@ BlockNumber, ) +from eth.abc import ( + AtomicDatabaseAPI, + BlockHeaderAPI, + DatabaseAPI, + HeaderDatabaseAPI, +) from eth.constants import ( GENESIS_PARENT_HASH, ) @@ -29,10 +34,6 @@ HeaderNotFound, ParentNotFound, ) -from eth.db.backends.base import ( - BaseAtomicDB, - BaseDB, -) from eth.db.schema import SchemaV1 from eth.rlp.headers import BlockHeader from eth.validation import ( @@ -41,56 +42,10 @@ ) -class BaseHeaderDB(ABC): - db: BaseAtomicDB = None - - def __init__(self, db: BaseAtomicDB) -> None: +class HeaderDB(HeaderDatabaseAPI): + def __init__(self, db: AtomicDatabaseAPI) -> None: self.db = db - # - # Canonical Chain API - # - @abstractmethod - def get_canonical_block_hash(self, block_number: BlockNumber) -> Hash32: - raise NotImplementedError("ChainDB classes must implement this method") - - @abstractmethod - def get_canonical_block_header_by_number(self, block_number: BlockNumber) -> BlockHeader: - raise NotImplementedError("ChainDB classes must implement this method") - - @abstractmethod - def get_canonical_head(self) -> BlockHeader: - raise NotImplementedError("ChainDB classes must implement this method") - - # - # Header API - # - @abstractmethod - def get_block_header_by_hash(self, block_hash: Hash32) -> BlockHeader: - raise NotImplementedError("ChainDB classes must implement this method") - - @abstractmethod - def get_score(self, block_hash: Hash32) -> int: - raise NotImplementedError("ChainDB classes must implement this method") - - @abstractmethod - def header_exists(self, block_hash: Hash32) -> bool: - raise NotImplementedError("ChainDB classes must implement this method") - - @abstractmethod - def persist_header(self, - header: BlockHeader - ) -> Tuple[Tuple[BlockHeader, ...], Tuple[BlockHeader, ...]]: - raise NotImplementedError("ChainDB classes must implement this method") - - @abstractmethod - def persist_header_chain(self, - headers: Iterable[BlockHeader] - ) -> Tuple[Tuple[BlockHeader, ...], Tuple[BlockHeader, ...]]: - raise NotImplementedError("ChainDB classes must implement this method") - - -class HeaderDB(BaseHeaderDB): # # Canonical Chain API # @@ -104,7 +59,7 @@ def get_canonical_block_hash(self, block_number: BlockNumber) -> Hash32: return self._get_canonical_block_hash(self.db, block_number) @staticmethod - def _get_canonical_block_hash(db: BaseDB, block_number: BlockNumber) -> Hash32: + def _get_canonical_block_hash(db: DatabaseAPI, block_number: BlockNumber) -> Hash32: validate_block_number(block_number) number_to_hash_key = SchemaV1.make_block_number_to_hash_lookup_key(block_number) @@ -117,7 +72,7 @@ def _get_canonical_block_hash(db: BaseDB, block_number: BlockNumber) -> Hash32: else: return rlp.decode(encoded_key, sedes=rlp.sedes.binary) - def get_canonical_block_header_by_number(self, block_number: BlockNumber) -> BlockHeader: + def get_canonical_block_header_by_number(self, block_number: BlockNumber) -> BlockHeaderAPI: """ Returns the block header with the given number in the canonical chain. @@ -129,20 +84,20 @@ def get_canonical_block_header_by_number(self, block_number: BlockNumber) -> Blo @classmethod def _get_canonical_block_header_by_number( cls, - db: BaseDB, - block_number: BlockNumber) -> BlockHeader: + db: DatabaseAPI, + block_number: BlockNumber) -> BlockHeaderAPI: validate_block_number(block_number) canonical_block_hash = cls._get_canonical_block_hash(db, block_number) return cls._get_block_header_by_hash(db, canonical_block_hash) - def get_canonical_head(self) -> BlockHeader: + def get_canonical_head(self) -> BlockHeaderAPI: """ Returns the current block header at the head of the chain. """ return self._get_canonical_head(self.db) @classmethod - def _get_canonical_head(cls, db: BaseDB) -> BlockHeader: + def _get_canonical_head(cls, db: DatabaseAPI) -> BlockHeaderAPI: try: canonical_head_hash = db[SchemaV1.make_canonical_head_hash_lookup_key()] except KeyError: @@ -152,11 +107,11 @@ def _get_canonical_head(cls, db: BaseDB) -> BlockHeader: # # Header API # - def get_block_header_by_hash(self, block_hash: Hash32) -> BlockHeader: + def get_block_header_by_hash(self, block_hash: Hash32) -> BlockHeaderAPI: return self._get_block_header_by_hash(self.db, block_hash) @staticmethod - def _get_block_header_by_hash(db: BaseDB, block_hash: Hash32) -> BlockHeader: + def _get_block_header_by_hash(db: DatabaseAPI, block_hash: Hash32) -> BlockHeaderAPI: """ Returns the requested block header as specified by block hash. @@ -174,7 +129,7 @@ def get_score(self, block_hash: Hash32) -> int: return self._get_score(self.db, block_hash) @staticmethod - def _get_score(db: BaseDB, block_hash: Hash32) -> int: + def _get_score(db: DatabaseAPI, block_hash: Hash32) -> int: try: encoded_score = db[SchemaV1.make_block_hash_to_score_lookup_key(block_hash)] except KeyError: @@ -186,18 +141,18 @@ def header_exists(self, block_hash: Hash32) -> bool: return self._header_exists(self.db, block_hash) @staticmethod - def _header_exists(db: BaseDB, block_hash: Hash32) -> bool: + def _header_exists(db: DatabaseAPI, block_hash: Hash32) -> bool: validate_word(block_hash, title="Block Hash") return block_hash in db def persist_header(self, - header: BlockHeader - ) -> Tuple[Tuple[BlockHeader, ...], Tuple[BlockHeader, ...]]: + header: BlockHeaderAPI + ) -> Tuple[Tuple[BlockHeaderAPI, ...], Tuple[BlockHeaderAPI, ...]]: return self.persist_header_chain((header,)) def persist_header_chain(self, - headers: Iterable[BlockHeader] - ) -> Tuple[Tuple[BlockHeader, ...], Tuple[BlockHeader, ...]]: + headers: Iterable[BlockHeaderAPI] + ) -> Tuple[Tuple[BlockHeaderAPI, ...], Tuple[BlockHeaderAPI, ...]]: """ Return two iterable of headers, the first containing the new canonical headers, the second containing the old canonical headers @@ -208,8 +163,8 @@ def persist_header_chain(self, @classmethod def _set_hash_scores_to_db( cls, - db: BaseDB, - header: BlockHeader, + db: DatabaseAPI, + header: BlockHeaderAPI, score: int ) -> int: new_score = score + header.difficulty @@ -224,9 +179,9 @@ def _set_hash_scores_to_db( @classmethod def _persist_header_chain( cls, - db: BaseDB, - headers: Iterable[BlockHeader] - ) -> Tuple[Tuple[BlockHeader, ...], Tuple[BlockHeader, ...]]: + db: DatabaseAPI, + headers: Iterable[BlockHeaderAPI] + ) -> Tuple[Tuple[BlockHeaderAPI, ...], Tuple[BlockHeaderAPI, ...]]: headers_iterator = iter(headers) try: @@ -283,8 +238,8 @@ def _persist_header_chain( return tuple(), tuple() @classmethod - def _set_as_canonical_chain_head(cls, db: BaseDB, block_hash: Hash32 - ) -> Tuple[Tuple[BlockHeader, ...], Tuple[BlockHeader, ...]]: + def _set_as_canonical_chain_head(cls, db: DatabaseAPI, block_hash: Hash32 + ) -> Tuple[Tuple[BlockHeaderAPI, ...], Tuple[BlockHeaderAPI, ...]]: # noqa: E501 """ Sets the canonical chain HEAD to the block header as specified by the given block hash. @@ -321,7 +276,9 @@ def _set_as_canonical_chain_head(cls, db: BaseDB, block_hash: Hash32 @classmethod @to_tuple - def _find_new_ancestors(cls, db: BaseDB, header: BlockHeader) -> Iterable[BlockHeader]: + def _find_new_ancestors(cls, + db: DatabaseAPI, + header: BlockHeaderAPI) -> Iterable[BlockHeaderAPI]: """ Returns the chain leading up from the given header until (but not including) the first ancestor it has in common with our canonical chain. @@ -354,7 +311,7 @@ def _find_new_ancestors(cls, db: BaseDB, header: BlockHeader) -> Iterable[BlockH h = cls._get_block_header_by_hash(db, h.parent_hash) @staticmethod - def _add_block_number_to_hash_lookup(db: BaseDB, header: BlockHeader) -> None: + def _add_block_number_to_hash_lookup(db: DatabaseAPI, header: BlockHeaderAPI) -> None: """ Sets a record in the database to allow looking up this header by its block number. @@ -372,14 +329,14 @@ class AsyncHeaderDB(HeaderDB): async def coro_get_score(self, block_hash: Hash32) -> int: raise NotImplementedError() - async def coro_get_block_header_by_hash(self, block_hash: Hash32) -> BlockHeader: + async def coro_get_block_header_by_hash(self, block_hash: Hash32) -> BlockHeaderAPI: raise NotImplementedError() - async def coro_get_canonical_head(self) -> BlockHeader: + async def coro_get_canonical_head(self) -> BlockHeaderAPI: raise NotImplementedError() async def coro_get_canonical_block_header_by_number( - self, block_number: BlockNumber) -> BlockHeader: + self, block_number: BlockNumber) -> BlockHeaderAPI: raise NotImplementedError() async def coro_header_exists(self, block_hash: Hash32) -> bool: @@ -388,11 +345,12 @@ async def coro_header_exists(self, block_hash: Hash32) -> bool: async def coro_get_canonical_block_hash(self, block_number: BlockNumber) -> Hash32: raise NotImplementedError() - async def coro_persist_header(self, header: BlockHeader) -> Tuple[BlockHeader, ...]: + async def coro_persist_header(self, header: BlockHeaderAPI) -> Tuple[BlockHeaderAPI, ...]: raise NotImplementedError() async def coro_persist_header_chain(self, - headers: Iterable[BlockHeader]) -> Tuple[BlockHeader, ...]: + headers: Iterable[BlockHeaderAPI], + ) -> Tuple[BlockHeaderAPI, ...]: raise NotImplementedError() @@ -401,5 +359,5 @@ async def coro_persist_header_chain(self, # relatively expensive so we cache that here, but use a small cache because we *should* only # be looking up recent blocks. @functools.lru_cache(128) -def _decode_block_header(header_rlp: bytes) -> BlockHeader: +def _decode_block_header(header_rlp: bytes) -> BlockHeaderAPI: return rlp.decode(header_rlp, sedes=BlockHeader) diff --git a/eth/db/journal.py b/eth/db/journal.py index 7f3b245596..8e3eff1652 100644 --- a/eth/db/journal.py +++ b/eth/db/journal.py @@ -12,9 +12,11 @@ ValidationError, ) +from eth.abc import DatabaseAPI +from eth.typing import JournalDBCheckpoint + from .backends.base import BaseDB from .diff import DBDiff, DBDiffTracker -from .typing import JournalDBCheckpoint class DeletedEntry: @@ -309,7 +311,7 @@ class JournalDB(BaseDB): """ __slots__ = ['_wrapped_db', '_journal', 'record', 'commit'] - def __init__(self, wrapped_db: BaseDB) -> None: + def __init__(self, wrapped_db: DatabaseAPI) -> None: self._wrapped_db = wrapped_db self._journal = Journal() self.record = self._journal.record_checkpoint diff --git a/eth/db/keymap.py b/eth/db/keymap.py index 82ea25ccee..e65d1bcd2a 100644 --- a/eth/db/keymap.py +++ b/eth/db/keymap.py @@ -6,6 +6,7 @@ Any, ) +from eth.abc import DatabaseAPI from eth.db.backends.base import BaseDB @@ -14,7 +15,7 @@ class KeyMapDB(BaseDB): Modify keys when accessing the database, according to the abstract keymap function set in the subclass. """ - def __init__(self, db: BaseDB) -> None: + def __init__(self, db: DatabaseAPI) -> None: self._db = db @staticmethod diff --git a/eth/db/slow_journal.py b/eth/db/slow_journal.py index 3093f162cd..ad093d1a69 100644 --- a/eth/db/slow_journal.py +++ b/eth/db/slow_journal.py @@ -11,6 +11,7 @@ ValidationError, ) +from eth.abc import DatabaseAPI from eth.db.backends.base import BaseDB from eth.db.diff import DBDiff, DBDiffTracker @@ -281,7 +282,7 @@ class JournalDB(BaseDB): wrapped_db = None journal: Journal = None - def __init__(self, wrapped_db: BaseDB) -> None: + def __init__(self, wrapped_db: DatabaseAPI) -> None: self.wrapped_db = wrapped_db self.reset() diff --git a/eth/db/storage.py b/eth/db/storage.py index 3b5d79b065..1c820493d6 100644 --- a/eth/db/storage.py +++ b/eth/db/storage.py @@ -21,8 +21,12 @@ from eth._utils.padding import ( pad32, ) +from eth.abc import ( + AccountStorageDatabaseAPI, + AtomicDatabaseAPI, + DatabaseAPI, +) from eth.db.backends.base import ( - BaseAtomicDB, BaseDB, ) from eth.db.batch import ( @@ -34,15 +38,15 @@ from eth.db.journal import ( JournalDB, ) -from eth.db.typing import ( - JournalDBCheckpoint, -) from eth.vm.interrupt import ( MissingStorageTrieNode, ) from eth.tools.logging import ( ExtendedDebugLogger ) +from eth.typing import ( + JournalDBCheckpoint, +) class StorageLookup(BaseDB): @@ -54,7 +58,7 @@ class StorageLookup(BaseDB): """ logger = cast(ExtendedDebugLogger, logging.getLogger("eth.db.storage.StorageLookup")) - def __init__(self, db: BaseDB, storage_root: Hash32, address: Address) -> None: + def __init__(self, db: DatabaseAPI, storage_root: Hash32, address: Address) -> None: self._db = db self._starting_root_hash = storage_root self._address = address @@ -136,7 +140,7 @@ def _clear_changed_root(self) -> None: self._trie_nodes_batch = None self._starting_root_hash = None - def commit_to(self, db: BaseDB) -> None: + def commit_to(self, db: DatabaseAPI) -> None: """ Trying to commit changes when nothing has been written will raise a ValidationError @@ -151,14 +155,14 @@ def commit_to(self, db: BaseDB) -> None: self._clear_changed_root() -class AccountStorageDB: +class AccountStorageDB(AccountStorageDatabaseAPI): """ Storage cache and write batch for a single account. Changes are not merklized until :meth:`make_storage_root` is called. """ logger = cast(ExtendedDebugLogger, logging.getLogger("eth.db.storage.AccountStorageDB")) - def __init__(self, db: BaseAtomicDB, storage_root: Hash32, address: Address) -> None: + def __init__(self, db: AtomicDatabaseAPI, storage_root: Hash32, address: Address) -> None: """ Database entries go through several pipes, like so... @@ -267,7 +271,7 @@ def has_changed_root(self) -> bool: def get_changed_root(self) -> Hash32: return self._storage_lookup.get_changed_root() - def persist(self, db: BaseDB) -> None: + def persist(self, db: DatabaseAPI) -> None: self._validate_flushed() if self._storage_lookup.has_changed_root: self._storage_lookup.commit_to(db) diff --git a/eth/db/trie.py b/eth/db/trie.py index f42468a0a2..34140b24ee 100644 --- a/eth/db/trie.py +++ b/eth/db/trie.py @@ -1,5 +1,5 @@ import functools -from typing import Dict, Tuple, Union +from typing import Dict, Sequence, Tuple, Union import rlp from trie import ( @@ -8,13 +8,15 @@ from eth_typing import Hash32 +from eth.abc import ( + ReceiptAPI, + SignedTransactionAPI, +) from eth.constants import ( BLANK_ROOT_HASH, ) -from eth.rlp.receipts import Receipt -from eth.rlp.transactions import BaseTransaction -TransactionsOrReceipts = Union[Tuple[Receipt, ...], Tuple[BaseTransaction, ...]] +TransactionsOrReceipts = Union[Sequence[ReceiptAPI], Sequence[SignedTransactionAPI]] TrieRootAndData = Tuple[Hash32, Dict[Hash32, bytes]] diff --git a/eth/db/typing.py b/eth/db/typing.py deleted file mode 100644 index 92273171a6..0000000000 --- a/eth/db/typing.py +++ /dev/null @@ -1,3 +0,0 @@ -from typing import NewType - -JournalDBCheckpoint = NewType('JournalDBCheckpoint', int) diff --git a/eth/estimators/__init__.py b/eth/estimators/__init__.py index 7ee922f1f7..951cbda021 100644 --- a/eth/estimators/__init__.py +++ b/eth/estimators/__init__.py @@ -4,23 +4,21 @@ cast, ) -from eth.typing import ( - BaseOrSpoofTransaction, +from eth.abc import ( + SignedTransactionAPI, + StateAPI, ) from eth._utils.module_loading import ( import_string, ) -from eth.vm.state import ( - BaseState, -) -def get_gas_estimator() -> Callable[[BaseState, BaseOrSpoofTransaction], int]: +def get_gas_estimator() -> Callable[[StateAPI, SignedTransactionAPI], int]: import_path = os.environ.get( 'GAS_ESTIMATOR_BACKEND_FUNC', 'eth.estimators.gas.binary_gas_search_intrinsic_tolerance', ) return cast( - Callable[[BaseState, BaseOrSpoofTransaction], int], + Callable[[StateAPI, SignedTransactionAPI], int], import_string(import_path) ) diff --git a/eth/estimators/gas.py b/eth/estimators/gas.py index fc20097a65..1e04099a45 100644 --- a/eth/estimators/gas.py +++ b/eth/estimators/gas.py @@ -1,22 +1,24 @@ -from typing import Optional +from typing import cast, Optional from eth_utils.toolz import curry from eth.exceptions import VMError -from eth.rlp.transactions import BaseTransaction +from eth.abc import ( + SignedTransactionAPI, + StateAPI, +) from eth.vm.spoof import SpoofTransaction -from eth.vm.state import BaseState -def _get_computation_error(state: BaseState, transaction: SpoofTransaction) -> Optional[VMError]: +def _get_computation_error(state: StateAPI, transaction: SignedTransactionAPI) -> Optional[VMError]: snapshot = state.snapshot() try: computation = state.execute_transaction(transaction) if computation.is_error: - return computation._error + return computation.error else: return None @@ -25,7 +27,7 @@ def _get_computation_error(state: BaseState, transaction: SpoofTransaction) -> O @curry -def binary_gas_search(state: BaseState, transaction: BaseTransaction, tolerance: int=1) -> int: +def binary_gas_search(state: StateAPI, transaction: SignedTransactionAPI, tolerance: int=1) -> int: """ Run the transaction with various gas limits, progressively approaching the minimum needed to succeed without an OutOfGas exception. @@ -47,20 +49,20 @@ def binary_gas_search(state: BaseState, transaction: BaseTransaction, tolerance: "If sending an unsigned transaction, use SpoofTransaction and provide the", "sender using the 'from' parameter") - minimum_transaction = SpoofTransaction( + minimum_transaction = cast(SignedTransactionAPI, SpoofTransaction( transaction, gas=transaction.intrinsic_gas, gas_price=0, - ) + )) if _get_computation_error(state, minimum_transaction) is None: return transaction.intrinsic_gas - maximum_transaction = SpoofTransaction( + maximum_transaction = cast(SignedTransactionAPI, SpoofTransaction( transaction, gas=state.gas_limit, gas_price=0, - ) + )) error = _get_computation_error(state, maximum_transaction) if error is not None: raise error @@ -69,7 +71,7 @@ def binary_gas_search(state: BaseState, transaction: BaseTransaction, tolerance: maximum_out_of_gas = transaction.intrinsic_gas while minimum_viable - maximum_out_of_gas > tolerance: midpoint = (minimum_viable + maximum_out_of_gas) // 2 - test_transaction = SpoofTransaction(transaction, gas=midpoint) + test_transaction = cast(SignedTransactionAPI, SpoofTransaction(transaction, gas=midpoint)) if _get_computation_error(state, test_transaction) is None: minimum_viable = midpoint else: diff --git a/eth/rlp/blocks.py b/eth/rlp/blocks.py index 79adec6f44..eb9daf4ace 100644 --- a/eth/rlp/blocks.py +++ b/eth/rlp/blocks.py @@ -1,54 +1,26 @@ -from abc import ( - ABC, - abstractmethod -) from typing import ( Type ) -import rlp - -from eth_typing import ( - Hash32 -) - from eth._utils.datatypes import ( Configurable, ) -from eth.db.chain import BaseChainDB - -from .transactions import BaseTransaction -from .headers import BlockHeader +from eth.abc import ( + BlockAPI, + SignedTransactionAPI, +) -class BaseBlock(rlp.Serializable, Configurable, ABC): - transaction_class: Type[BaseTransaction] = None +class BaseBlock(Configurable, BlockAPI): + transaction_class: Type[SignedTransactionAPI] = None @classmethod - def get_transaction_class(cls) -> Type[BaseTransaction]: + def get_transaction_class(cls) -> Type[SignedTransactionAPI]: if cls.transaction_class is None: raise AttributeError("Block subclasses must declare a transaction_class") return cls.transaction_class - @classmethod - @abstractmethod - def from_header(cls, header: BlockHeader, chaindb: BaseChainDB) -> 'BaseBlock': - """ - Returns the block denoted by the given block header. - """ - raise NotImplementedError("Must be implemented by subclasses") - - @property - @abstractmethod - def hash(self) -> Hash32: - raise NotImplementedError("Must be implemented by subclasses") - - @property - @abstractmethod - def number(self) -> int: - raise NotImplementedError("Must be implemented by subclasses") - @property def is_genesis(self) -> bool: return self.header.is_genesis diff --git a/eth/rlp/headers.py b/eth/rlp/headers.py index 9b9c5d1e18..31ad2763a7 100644 --- a/eth/rlp/headers.py +++ b/eth/rlp/headers.py @@ -1,8 +1,7 @@ import time from typing import ( + Dict, Iterable, - Optional, - Union, overload, ) @@ -24,6 +23,10 @@ encode_hex, ) +from eth.abc import ( + BlockHeaderAPI, + MiningHeaderAPI, +) from eth.constants import ( ZERO_ADDRESS, ZERO_HASH32, @@ -32,7 +35,7 @@ GENESIS_PARENT_HASH, BLANK_ROOT_HASH, ) - +from eth.typing import HeaderParams from eth.vm.execution_context import ( ExecutionContext, ) @@ -45,7 +48,7 @@ ) -class MiningHeader(rlp.Serializable): +class MiningHeader(MiningHeaderAPI): fields = [ ('parent_hash', hash32), ('uncles_hash', hash32), @@ -63,10 +66,7 @@ class MiningHeader(rlp.Serializable): ] -HeaderParams = Union[Optional[int], bytes, Address, Hash32] - - -class BlockHeader(rlp.Serializable): +class BlockHeader(BlockHeaderAPI): fields = [ ('parent_hash', hash32), ('uncles_hash', hash32), @@ -168,7 +168,7 @@ def hex_hash(self) -> str: @classmethod def from_parent(cls, - parent: 'BlockHeader', + parent: BlockHeaderAPI, gas_limit: int, difficulty: int, timestamp: int, @@ -176,12 +176,12 @@ def from_parent(cls, nonce: bytes=None, extra_data: bytes=None, transaction_root: bytes=None, - receipt_root: bytes=None) -> 'BlockHeader': + receipt_root: bytes=None) -> BlockHeaderAPI: """ Initialize a new block header with the `parent` header as the block's parent hash. """ - header_kwargs = { + header_kwargs: Dict[str, HeaderParams] = { 'parent_hash': parent.hash, 'coinbase': coinbase, 'state_root': parent.state_root, diff --git a/eth/rlp/logs.py b/eth/rlp/logs.py index 7e42390c00..148b941ee5 100644 --- a/eth/rlp/logs.py +++ b/eth/rlp/logs.py @@ -1,4 +1,3 @@ -import rlp from rlp.sedes import ( CountableList, binary, @@ -8,13 +7,15 @@ Tuple, ) +from eth.abc import LogAPI + from .sedes import ( address, uint32, ) -class Log(rlp.Serializable): +class Log(LogAPI): fields = [ ('address', address), ('topics', CountableList(uint32)), diff --git a/eth/rlp/receipts.py b/eth/rlp/receipts.py index 606e6b67f4..9c5e7ef8e0 100644 --- a/eth/rlp/receipts.py +++ b/eth/rlp/receipts.py @@ -1,6 +1,5 @@ import itertools -import rlp from rlp.sedes import ( big_endian_int, CountableList, @@ -9,16 +8,18 @@ from eth_bloom import BloomFilter +from typing import Iterable + +from eth.abc import ReceiptAPI + from .sedes import ( uint256, ) from .logs import Log -from typing import Iterable - -class Receipt(rlp.Serializable): +class Receipt(ReceiptAPI): fields = [ ('state_root', binary), diff --git a/eth/rlp/transactions.py b/eth/rlp/transactions.py index 647360dcba..af29fb1a44 100644 --- a/eth/rlp/transactions.py +++ b/eth/rlp/transactions.py @@ -1,8 +1,3 @@ -from abc import ( - ABC, - abstractmethod -) - from cached_property import cached_property import rlp from rlp.sedes import ( @@ -15,23 +10,22 @@ ) from eth_hash.auto import keccak -from eth_keys.datatypes import ( - PrivateKey -) from eth_utils import ( ValidationError, ) -from eth.rlp.sedes import ( - address, +from eth.abc import ( + BaseTransactionAPI, + ComputationAPI, + SignedTransactionAPI, + TransactionFieldsAPI, + UnsignedTransactionAPI, ) -from eth.vm.computation import ( - BaseComputation -) +from .sedes import address -class BaseTransactionMethods: +class BaseTransactionMethods(BaseTransactionAPI): def validate(self) -> None: """ Hook called during instantiation to ensure that all transaction @@ -46,16 +40,7 @@ def intrinsic_gas(self) -> int: """ return self.get_intrinsic_gas() - @abstractmethod - def get_intrinsic_gas(self) -> int: - """ - Compute the baseline gas cost for this transaction. This is the amount - of gas needed to send this transaction (but that is not actually used - for computation). - """ - raise NotImplementedError("Must be implemented by subclasses") - - def gas_used_by(self, computation: BaseComputation) -> int: + def gas_used_by(self, computation: ComputationAPI) -> int: """ Return the gas used by the given computation. In Frontier, for example, this is sum of the intrinsic cost and the gas used @@ -64,28 +49,35 @@ def gas_used_by(self, computation: BaseComputation) -> int: return self.get_intrinsic_gas() + computation.get_gas_used() -class BaseTransactionFields(rlp.Serializable): +BASE_TRANSACTION_FIELDS = [ + ('nonce', big_endian_int), + ('gas_price', big_endian_int), + ('gas', big_endian_int), + ('to', address), + ('value', big_endian_int), + ('data', binary), + ('v', big_endian_int), + ('r', big_endian_int), + ('s', big_endian_int), +] - fields = [ - ('nonce', big_endian_int), - ('gas_price', big_endian_int), - ('gas', big_endian_int), - ('to', address), - ('value', big_endian_int), - ('data', binary), - ('v', big_endian_int), - ('r', big_endian_int), - ('s', big_endian_int), - ] + +class BaseTransactionFields(rlp.Serializable, TransactionFieldsAPI): + fields = BASE_TRANSACTION_FIELDS @property def hash(self) -> bytes: return keccak(rlp.encode(self)) -class BaseTransaction(BaseTransactionFields, BaseTransactionMethods): +class BaseTransaction(BaseTransactionFields, BaseTransactionMethods, SignedTransactionAPI): # noqa: E501 + # this is duplicated to make the rlp library happy, otherwise it complains + # about no fields being defined but inheriting from multiple `Serializable` + # bases. + fields = BASE_TRANSACTION_FIELDS + @classmethod - def from_base_transaction(cls, transaction: 'BaseTransaction') -> 'BaseTransaction': + def from_base_transaction(cls, transaction: SignedTransactionAPI) -> SignedTransactionAPI: return rlp.decode(rlp.encode(transaction), sedes=cls) @cached_property @@ -123,50 +115,8 @@ def is_signature_valid(self) -> bool: else: return True - @abstractmethod - def check_signature_validity(self) -> None: - """ - Checks signature validity, raising a ValidationError if the signature - is invalid. - """ - raise NotImplementedError("Must be implemented by subclasses") - - @abstractmethod - def get_sender(self) -> Address: - """ - Get the 20-byte address which sent this transaction. - - This can be a slow operation. ``transaction.sender`` is always preferred. - """ - raise NotImplementedError("Must be implemented by subclasses") - - # - # Conversion to and creation of unsigned transactions. - # - @abstractmethod - def get_message_for_signing(self) -> bytes: - """ - Return the bytestring that should be signed in order to create a signed transactions - """ - raise NotImplementedError("Must be implemented by subclasses") - @classmethod - @abstractmethod - def create_unsigned_transaction(cls, - *, - nonce: int, - gas_price: int, - gas: int, - to: Address, - value: int, - data: bytes) -> 'BaseUnsignedTransaction': - """ - Create an unsigned transaction. - """ - raise NotImplementedError("Must be implemented by subclasses") - - -class BaseUnsignedTransaction(rlp.Serializable, BaseTransactionMethods, ABC): +class BaseUnsignedTransaction(BaseTransactionMethods, UnsignedTransactionAPI): fields = [ ('nonce', big_endian_int), ('gas_price', big_endian_int), @@ -175,14 +125,3 @@ class BaseUnsignedTransaction(rlp.Serializable, BaseTransactionMethods, ABC): ('value', big_endian_int), ('data', binary), ] - - # - # API that must be implemented by all Transaction subclasses. - # - @abstractmethod - def as_signed_transaction(self, private_key: PrivateKey) -> 'BaseTransaction': - """ - Return a version of this transaction which has been signed using the - provided `private_key` - """ - raise NotImplementedError("Must be implemented by subclasses") diff --git a/eth/tools/builder/chain/builders.py b/eth/tools/builder/chain/builders.py index 7accd7958d..8feee97ecd 100644 --- a/eth/tools/builder/chain/builders.py +++ b/eth/tools/builder/chain/builders.py @@ -30,22 +30,19 @@ ) from eth import constants -from eth.chains.base import ( - BaseChain, - MiningChain, +from eth.abc import ( + AtomicDatabaseAPI, + BlockAPI, + BlockHeaderAPI, + ChainAPI, + MiningChainAPI, + VirtualMachineAPI, ) from eth.db.atomic import AtomicDB -from eth.db.backends.base import ( - BaseAtomicDB, -) from eth.db.backends.memory import ( MemoryDB, ) -from eth.rlp.blocks import ( - BaseBlock, -) from eth.rlp.headers import ( - BlockHeader, HeaderParams, ) from eth.tools.mining import POWMiningMixin @@ -64,9 +61,6 @@ from eth.validation import ( validate_vm_configuration, ) -from eth.vm.base import ( - BaseVM, -) from eth.vm.forks import ( FrontierVM, HomesteadVM, @@ -87,7 +81,7 @@ def build(obj: Any, *applicators: Callable[..., Any]) -> Any: applicators will be run on a copy of the chain and thus will not mutate the provided chain instance. """ - if isinstance(obj, BaseChain): + if isinstance(obj, ChainAPI): return pipe(obj, copy(), *applicators) else: return pipe(obj, *applicators) @@ -97,7 +91,7 @@ def build(obj: Any, *applicators: Callable[..., Any]) -> Any: # Constructors (creation of chain classes) # @curry -def name(class_name: str, chain_class: Type[BaseChain]) -> Type[BaseChain]: +def name(class_name: str, chain_class: Type[ChainAPI]) -> Type[ChainAPI]: """ Assign the given name to the chain class. """ @@ -105,7 +99,7 @@ def name(class_name: str, chain_class: Type[BaseChain]) -> Type[BaseChain]: @curry -def chain_id(chain_id: int, chain_class: Type[BaseChain]) -> Type[BaseChain]: +def chain_id(chain_id: int, chain_class: Type[ChainAPI]) -> Type[ChainAPI]: """ Set the ``chain_id`` for the chain class. """ @@ -113,7 +107,9 @@ def chain_id(chain_id: int, chain_class: Type[BaseChain]) -> Type[BaseChain]: @curry -def fork_at(vm_class: Type[BaseVM], at_block: int, chain_class: Type[BaseChain]) -> Type[BaseChain]: +def fork_at(vm_class: Type[VirtualMachineAPI], + at_block: int, + chain_class: Type[ChainAPI]) -> Type[ChainAPI]: """ Adds the ``vm_class`` to the chain's ``vm_configuration``. @@ -155,7 +151,7 @@ class FrontierOnlyChain(MiningChain): return chain_class.configure(vm_configuration=vm_configuration) -def _is_homestead(vm_class: Type[BaseVM]) -> bool: +def _is_homestead(vm_class: Type[VirtualMachineAPI]) -> bool: if not issubclass(vm_class, HomesteadVM): # It isn't a subclass of the HomesteadVM return False @@ -176,7 +172,7 @@ def _set_vm_dao_support_false(vm_configuration: VMConfiguration) -> Iterable[VMF @curry -def disable_dao_fork(chain_class: Type[BaseChain]) -> Type[BaseChain]: +def disable_dao_fork(chain_class: Type[ChainAPI]) -> Type[ChainAPI]: """ Set the ``support_dao_fork`` flag to ``False`` on the :class:`~eth.vm.forks.homestead.HomesteadVM`. Requires that presence of @@ -208,7 +204,7 @@ def _set_vm_dao_fork_block_number(dao_fork_block_number: BlockNumber, @curry def dao_fork_at(dao_fork_block_number: BlockNumber, - chain_class: Type[BaseChain]) -> Type[BaseChain]: + chain_class: Type[ChainAPI]) -> Type[ChainAPI]: """ Set the block number on which the DAO fork will happen. Requires that a version of the :class:`~eth.vm.forks.homestead.HomesteadVM` is present in @@ -279,7 +275,7 @@ def _mix_in_pow_mining(vm_configuration: VMConfiguration) -> Iterable[VMFork]: @curry -def enable_pow_mining(chain_class: Type[BaseChain]) -> Type[BaseChain]: +def enable_pow_mining(chain_class: Type[ChainAPI]) -> Type[ChainAPI]: """ Inject on demand generation of the proof of work mining seal on newly mined blocks into each of the chain's vms. @@ -293,13 +289,13 @@ def enable_pow_mining(chain_class: Type[BaseChain]) -> Type[BaseChain]: class NoChainSealValidationMixin: @classmethod - def validate_seal(cls, block: BaseBlock) -> None: + def validate_seal(cls, block: BlockAPI) -> None: pass class NoVMSealValidationMixin: @classmethod - def validate_seal(cls, header: BlockHeader) -> None: + def validate_seal(cls, header: BlockHeaderAPI) -> None: pass @@ -319,7 +315,7 @@ def _mix_in_disable_seal_validation(vm_configuration: VMConfiguration) -> Iterab @curry -def disable_pow_check(chain_class: Type[BaseChain]) -> Type[BaseChain]: +def disable_pow_check(chain_class: Type[ChainAPI]) -> Type[ChainAPI]: """ Disable the proof of work validation check for each of the chain's vms. This allows for block mining without generation of the proof of work seal. @@ -364,10 +360,10 @@ def _fill_and_normalize_state(simple_state: GeneralState) -> AccountState: @curry -def genesis(chain_class: BaseChain, - db: BaseAtomicDB=None, +def genesis(chain_class: ChainAPI, + db: AtomicDatabaseAPI=None, params: Dict[str, HeaderParams]=None, - state: GeneralState=None) -> BaseChain: + state: GeneralState=None) -> ChainAPI: """ Initialize the given chain class with the given genesis header parameters and chain state. @@ -385,7 +381,7 @@ def genesis(chain_class: BaseChain, genesis_params = merge(genesis_params_defaults, params) if db is None: - base_db: BaseAtomicDB = AtomicDB() + base_db: AtomicDatabaseAPI = AtomicDB() else: base_db = db @@ -396,24 +392,24 @@ def genesis(chain_class: BaseChain, # Builders (build actual block chain) # @curry -def mine_block(chain: MiningChain, **kwargs: Any) -> MiningChain: +def mine_block(chain: MiningChainAPI, **kwargs: Any) -> MiningChainAPI: """ Mine a new block on the chain. Header parameters for the new block can be overridden using keyword arguments. """ - if not isinstance(chain, MiningChain): + if not isinstance(chain, MiningChainAPI): raise ValidationError('`mine_block` may only be used on MiningChain instances') chain.mine_block(**kwargs) return chain @curry -def mine_blocks(num_blocks: int, chain: MiningChain) -> MiningChain: +def mine_blocks(num_blocks: int, chain: MiningChainAPI) -> MiningChainAPI: """ Variadic argument version of :func:`~eth.tools.builder.chain.mine_block` """ - if not isinstance(chain, MiningChain): + if not isinstance(chain, MiningChainAPI): raise ValidationError('`mine_block` may only be used on MiningChain instances') for _ in range(num_blocks): chain.mine_block() @@ -421,7 +417,7 @@ def mine_blocks(num_blocks: int, chain: MiningChain) -> MiningChain: @curry -def import_block(block: BaseBlock, chain: BaseChain) -> BaseChain: +def import_block(block: BlockAPI, chain: ChainAPI) -> ChainAPI: """ Import the provided ``block`` into the chain. """ @@ -429,12 +425,12 @@ def import_block(block: BaseBlock, chain: BaseChain) -> BaseChain: return chain -def import_blocks(*blocks: BaseBlock) -> Callable[[BaseChain], BaseChain]: +def import_blocks(*blocks: BlockAPI) -> Callable[[ChainAPI], ChainAPI]: """ Variadic argument version of :func:`~eth.tools.builder.chain.import_block` """ @functools.wraps(import_blocks) - def _import_blocks(chain: BaseChain) -> BaseChain: + def _import_blocks(chain: ChainAPI) -> ChainAPI: for block in blocks: chain.import_block(block) return chain @@ -443,12 +439,12 @@ def _import_blocks(chain: BaseChain) -> BaseChain: @curry -def copy(chain: MiningChain) -> MiningChain: +def copy(chain: MiningChainAPI) -> MiningChainAPI: """ Make a copy of the chain at the given state. Actions performed on the resulting chain will not affect the original chain. """ - if not isinstance(chain, MiningChain): + if not isinstance(chain, MiningChainAPI): raise ValidationError("`at_block_number` may only be used with 'MiningChain") base_db = chain.chaindb.db if not isinstance(base_db, AtomicDB): @@ -463,7 +459,7 @@ def copy(chain: MiningChain) -> MiningChain: return chain_copy -def chain_split(*splits: Iterable[Callable[..., Any]]) -> Callable[[BaseChain], Iterable[BaseChain]]: # noqa: E501 +def chain_split(*splits: Iterable[Callable[..., Any]]) -> Callable[[ChainAPI], Iterable[ChainAPI]]: # noqa: E501 """ Construct and execute multiple concurrent forks of the chain. @@ -488,7 +484,7 @@ def chain_split(*splits: Iterable[Callable[..., Any]]) -> Callable[[BaseChain], @functools.wraps(chain_split) @to_tuple - def _chain_split(chain: BaseChain) -> Iterable[BaseChain]: + def _chain_split(chain: ChainAPI) -> Iterable[ChainAPI]: for split_fns in splits: result = build( chain, @@ -500,13 +496,13 @@ def _chain_split(chain: BaseChain) -> Iterable[BaseChain]: @curry -def at_block_number(block_number: BlockNumber, chain: MiningChain) -> MiningChain: +def at_block_number(block_number: BlockNumber, chain: MiningChainAPI) -> MiningChainAPI: """ Rewind the chain back to the given block number. Calls to things like ``get_canonical_head`` will still return the canonical head of the chain, however, you can use ``mine_block`` to mine fork chains. """ - if not isinstance(chain, MiningChain): + if not isinstance(chain, MiningChainAPI): raise ValidationError("`at_block_number` may only be used with 'MiningChain") at_block = chain.get_canonical_block_by_number(block_number) diff --git a/eth/tools/fixtures/helpers.py b/eth/tools/fixtures/helpers.py index 929fbc93df..524c8b2bb6 100644 --- a/eth/tools/fixtures/helpers.py +++ b/eth/tools/fixtures/helpers.py @@ -18,13 +18,13 @@ ) from eth import MainnetChain -from eth.db.atomic import AtomicDB -from eth.rlp.blocks import ( - BaseBlock, -) -from eth.chains.base import ( - BaseChain, +from eth.abc import ( + BlockAPI, + ChainAPI, + StateAPI, + VirtualMachineAPI, ) +from eth.db.atomic import AtomicDB from eth.chains.mainnet import ( MainnetDAOValidatorVM, ) @@ -37,9 +37,6 @@ from eth._utils.state import ( diff_state, ) -from eth.vm.base import ( - BaseVM, -) from eth.vm.forks import ( PetersburgVM, ConstantinopleVM, @@ -49,15 +46,12 @@ HomesteadVM as BaseHomesteadVM, SpuriousDragonVM, ) -from eth.vm.state import ( - BaseState, -) # # State Setup # -def setup_state(desired_state: AccountState, state: BaseState) -> None: +def setup_state(desired_state: AccountState, state: StateAPI) -> None: for account, account_data in desired_state.items(): for slot, value in account_data['storage'].items(): state.set_storage(account, slot, value) @@ -72,7 +66,7 @@ def setup_state(desired_state: AccountState, state: BaseState) -> None: state.persist() -def verify_state(expected_state: AccountState, state: BaseState) -> None: +def verify_state(expected_state: AccountState, state: StateAPI) -> None: diff = diff_state(expected_state, state) if diff: error_messages = [] @@ -105,7 +99,7 @@ def verify_state(expected_state: AccountState, state: BaseState) -> None: ) -def chain_vm_configuration(fixture: Dict[str, Any]) -> Iterable[Tuple[int, Type[BaseVM]]]: +def chain_vm_configuration(fixture: Dict[str, Any]) -> Iterable[Tuple[int, Type[VirtualMachineAPI]]]: # noqa: E501 network = fixture['network'] if network == 'Frontier': @@ -192,7 +186,7 @@ def genesis_params_from_fixture(fixture: Dict[str, Any]) -> Dict[str, Any]: def new_chain_from_fixture(fixture: Dict[str, Any], - chain_cls: Type[BaseChain]=MainnetChain) -> BaseChain: + chain_cls: Type[ChainAPI] = MainnetChain) -> ChainAPI: base_db = AtomicDB() vm_config = chain_vm_configuration(fixture) @@ -214,8 +208,8 @@ def new_chain_from_fixture(fixture: Dict[str, Any], def apply_fixture_block_to_chain( block_fixture: Dict[str, Any], - chain: BaseChain, - perform_validation: bool=True) -> Tuple[BaseBlock, BaseBlock, BaseBlock]: + chain: ChainAPI, + perform_validation: bool=True) -> Tuple[BlockAPI, BlockAPI, BlockAPI]: """ :return: (premined_block, mined_block, rlp_encoded_mined_block) """ diff --git a/eth/typing.py b/eth/typing.py index b35bce7909..48645665b1 100644 --- a/eth/typing.py +++ b/eth/typing.py @@ -5,6 +5,7 @@ Generic, Iterable, List, + Optional, NewType, Sequence, Tuple, @@ -25,12 +26,10 @@ ) if TYPE_CHECKING: - from eth.rlp.transactions import BaseTransaction # noqa: F401 - from eth.vm.spoof import SpoofTransaction # noqa: F401 - from eth.vm.base import BaseVM # noqa: F401 + from eth.abc import VirtualMachineAPI # noqa: F401 -# TODO: Move into eth_typing +JournalDBCheckpoint = NewType('JournalDBCheckpoint', int) AccountDetails = TypedDict('AccountDetails', {'balance': int, @@ -42,8 +41,6 @@ AccountDiff = Iterable[Tuple[Address, str, Union[int, bytes], Union[int, bytes]]] -BaseOrSpoofTransaction = Union['BaseTransaction', 'SpoofTransaction'] - GeneralState = Union[ AccountState, List[Tuple[Address, Dict[str, Union[int, bytes, Dict[int, int]]]]] @@ -74,7 +71,7 @@ TransactionNormalizer = Callable[[TransactionDict], TransactionDict] -VMFork = Tuple[BlockNumber, Type['BaseVM']] +VMFork = Tuple[BlockNumber, Type['VirtualMachineAPI']] VMConfiguration = Sequence[VMFork] @@ -96,3 +93,6 @@ def __get__(self, oself: Any, owner: Any) -> TFunc: def __set__(self, oself: Any, value: TFunc) -> None: self._func = value + + +HeaderParams = Union[Optional[int], bytes, Address, Hash32] diff --git a/eth/validation.py b/eth/validation.py index 51ad9728e3..f755277368 100644 --- a/eth/validation.py +++ b/eth/validation.py @@ -7,7 +7,6 @@ Sequence, Tuple, Type, - TYPE_CHECKING, Union, ) @@ -25,6 +24,7 @@ from eth_utils.toolz import itertoolz +from eth.abc import VirtualMachineAPI from eth.constants import ( GAS_LIMIT_ADJUSTMENT_FACTOR, GAS_LIMIT_MAXIMUM, @@ -37,9 +37,6 @@ BytesOrView, ) -if TYPE_CHECKING: - from eth.vm.base import BaseVM # noqa: F401 - def validate_is_bytes(value: bytes, title: str="Value") -> None: if not isinstance(value, bytes): @@ -243,7 +240,8 @@ def validate_vm_block_numbers(vm_block_numbers: Iterable[int]) -> None: validate_block_number(block_number) -def validate_vm_configuration(vm_configuration: Tuple[Tuple[int, Type['BaseVM']], ...]) -> None: +def validate_vm_configuration(vm_configuration: Tuple[Tuple[int, Type[VirtualMachineAPI]], ...], + ) -> None: validate_vm_block_numbers(tuple( block_number for block_number, _ diff --git a/eth/vm/base.py b/eth/vm/base.py index a932e0d0ee..d0ad4c39fc 100644 --- a/eth/vm/base.py +++ b/eth/vm/base.py @@ -1,8 +1,3 @@ -from __future__ import absolute_import -from abc import ( - ABC, - abstractmethod, -) import contextlib import itertools import logging @@ -11,8 +6,10 @@ Iterable, Iterator, Optional, + Sequence, Tuple, Type, + Union, ) from typing import Set @@ -27,6 +24,18 @@ ) import rlp +from eth.abc import ( + AtomicDatabaseAPI, + BlockAPI, + BlockHeaderAPI, + ChainDatabaseAPI, + ComputationAPI, + ReceiptAPI, + SignedTransactionAPI, + StateAPI, + UnsignedTransactionAPI, + VirtualMachineAPI, +) from eth.consensus.pow import ( check_pow, ) @@ -35,28 +44,16 @@ MAX_PREV_HEADER_DEPTH, MAX_UNCLES, ) -from eth.db.backends.base import ( - BaseAtomicDB, -) from eth.db.trie import make_trie_root_and_nodes -from eth.db.chain import BaseChainDB from eth.exceptions import ( HeaderNotFound, ) -from eth.rlp.blocks import ( - BaseBlock, -) from eth.rlp.headers import ( BlockHeader, ) -from eth.rlp.receipts import Receipt from eth.rlp.sedes import ( uint32, ) -from eth.rlp.transactions import ( - BaseTransaction, - BaseUnsignedTransaction, -) from eth._utils.datatypes import ( Configurable, ) @@ -77,360 +74,58 @@ from eth.vm.message import ( Message, ) -from eth.vm.state import BaseState -from eth.vm.computation import BaseComputation - - -class BaseVM(Configurable, ABC): - block_class: Type[BaseBlock] = None - fork: str = None # noqa: E701 # flake8 bug that's fixed in 3.6.0+ - chaindb: BaseChainDB = None - _state_class: Type[BaseState] = None - - @abstractmethod - def __init__(self, header: BlockHeader, chaindb: BaseChainDB) -> None: - pass - - @property - @abstractmethod - def state(self) -> BaseState: - pass - - @classmethod - @abstractmethod - def build_state(cls, - db: BaseAtomicDB, - header: BlockHeader, - previous_hashes: Iterable[Hash32] = () - ) -> BaseState: - pass - - @abstractmethod - def get_header(self) -> BlockHeader: - pass - - @abstractmethod - def get_block(self) -> BaseBlock: - pass - - # - # Logging - # - @property - @abstractmethod - def logger(self) -> logging.Logger: - raise NotImplementedError("VM classes must implement this method") - - # - # Execution - # - @abstractmethod - def apply_transaction(self, - header: BlockHeader, - transaction: BaseTransaction - ) -> Tuple[Receipt, BaseComputation]: - raise NotImplementedError("VM classes must implement this method") - - @abstractmethod - def execute_bytecode(self, - origin: Address, - gas_price: int, - gas: int, - to: Address, - sender: Address, - value: int, - data: bytes, - code: bytes, - code_address: Address = None) -> BaseComputation: - raise NotImplementedError("VM classes must implement this method") - - @abstractmethod - def apply_all_transactions( - self, - transactions: Tuple[BaseTransaction, ...], - base_header: BlockHeader - ) -> Tuple[BlockHeader, Tuple[Receipt, ...], Tuple[BaseComputation, ...]]: - raise NotImplementedError("VM classes must implement this method") - - @abstractmethod - def make_receipt(self, - base_header: BlockHeader, - transaction: BaseTransaction, - computation: BaseComputation, - state: BaseState) -> Receipt: - """ - Generate the receipt resulting from applying the transaction. - - :param base_header: the header of the block before the transaction was applied. - :param transaction: the transaction used to generate the receipt - :param computation: the result of running the transaction computation - :param state: the resulting state, after executing the computation - - :return: receipt - """ - raise NotImplementedError("VM classes must implement this method") - - # - # Mining - # - @abstractmethod - def import_block(self, block: BaseBlock) -> BaseBlock: - raise NotImplementedError("VM classes must implement this method") - - @abstractmethod - def mine_block(self, *args: Any, **kwargs: Any) -> BaseBlock: - raise NotImplementedError("VM classes must implement this method") - - @abstractmethod - def set_block_transactions(self, - base_block: BaseBlock, - new_header: BlockHeader, - transactions: Tuple[BaseTransaction, ...], - receipts: Tuple[Receipt, ...]) -> BaseBlock: - raise NotImplementedError("VM classes must implement this method") - - # - # Finalization - # - @abstractmethod - def finalize_block(self, block: BaseBlock) -> BaseBlock: - raise NotImplementedError("VM classes must implement this method") - - @abstractmethod - def pack_block(self, block: BaseBlock, *args: Any, **kwargs: Any) -> BaseBlock: - raise NotImplementedError("VM classes must implement this method") - - # - # Headers - # - @abstractmethod - def add_receipt_to_header(self, old_header: BlockHeader, receipt: Receipt) -> BlockHeader: - """ - Apply the receipt to the old header, and return the resulting header. This may have - storage-related side-effects. For example, pre-Byzantium, the state root hash - is included in the receipt, and so must be stored into the database. - """ - pass - - @classmethod - @abstractmethod - def compute_difficulty(cls, parent_header: BlockHeader, timestamp: int) -> int: - """ - Compute the difficulty for a block header. - - :param parent_header: the parent header - :param timestamp: the timestamp of the child header - """ - raise NotImplementedError("VM classes must implement this method") - - @abstractmethod - def configure_header(self, **header_params: Any) -> BlockHeader: - """ - Setup the current header with the provided parameters. This can be - used to set fields like the gas limit or timestamp to value different - than their computed defaults. - """ - raise NotImplementedError("VM classes must implement this method") - - @classmethod - @abstractmethod - def create_header_from_parent(cls, - parent_header: BlockHeader, - **header_params: Any) -> BlockHeader: - """ - Creates and initializes a new block header from the provided - `parent_header`. - """ - raise NotImplementedError("VM classes must implement this method") - - # - # Blocks - # - @classmethod - @abstractmethod - def generate_block_from_parent_header_and_coinbase(cls, - parent_header: BlockHeader, - coinbase: Address) -> BaseBlock: - raise NotImplementedError("VM classes must implement this method") - - @classmethod - @abstractmethod - def get_block_class(cls) -> Type[BaseBlock]: - raise NotImplementedError("VM classes must implement this method") - - @staticmethod - @abstractmethod - def get_block_reward() -> int: - """ - Return the amount in **wei** that should be given to a miner as a reward - for this block. - - .. note:: - This is an abstract method that must be implemented in subclasses - """ - raise NotImplementedError("VM classes must implement this method") - - @classmethod - @abstractmethod - def get_nephew_reward(cls) -> int: - """ - Return the reward which should be given to the miner of the given `nephew`. - - .. note:: - This is an abstract method that must be implemented in subclasses - """ - raise NotImplementedError("VM classes must implement this method") - - @classmethod - @abstractmethod - def get_prev_hashes(cls, - last_block_hash: Hash32, - chaindb: BaseChainDB) -> Optional[Iterable[Hash32]]: - raise NotImplementedError("VM classes must implement this method") - - @staticmethod - @abstractmethod - def get_uncle_reward(block_number: int, uncle: BaseBlock) -> int: - """ - Return the reward which should be given to the miner of the given `uncle`. - - .. note:: - This is an abstract method that must be implemented in subclasses - """ - raise NotImplementedError("VM classes must implement this method") - - # - # Transactions - # - @abstractmethod - def create_transaction(self, *args: Any, **kwargs: Any) -> BaseTransaction: - raise NotImplementedError("VM classes must implement this method") - - @classmethod - @abstractmethod - def create_unsigned_transaction(cls, - *, - nonce: int, - gas_price: int, - gas: int, - to: Address, - value: int, - data: bytes) -> BaseUnsignedTransaction: - raise NotImplementedError("VM classes must implement this method") - - @classmethod - @abstractmethod - def get_transaction_class(cls) -> Type[BaseTransaction]: - raise NotImplementedError("VM classes must implement this method") - - # - # Validate - # - @classmethod - @abstractmethod - def validate_receipt(self, receipt: Receipt) -> None: - raise NotImplementedError("VM classes must implement this method") - - @abstractmethod - def validate_block(self, block: BaseBlock) -> None: - raise NotImplementedError("VM classes must implement this method") - - @classmethod - @abstractmethod - def validate_header(cls, - header: BlockHeader, - parent_header: BlockHeader, - check_seal: bool = True - ) -> None: - raise NotImplementedError("VM classes must implement this method") - - @abstractmethod - def validate_transaction_against_header(self, - base_header: BlockHeader, - transaction: BaseTransaction) -> None: - """ - Validate that the given transaction is valid to apply to the given header. - :param base_header: header before applying the transaction - :param transaction: the transaction to validate - :raises: ValidationError if the transaction is not valid to apply - """ - raise NotImplementedError("VM classes must implement this method") - - @classmethod - @abstractmethod - def validate_seal(cls, header: BlockHeader) -> None: - raise NotImplementedError("VM classes must implement this method") - - @classmethod - @abstractmethod - def validate_uncle(cls, - block: BaseBlock, - uncle: BlockHeader, - uncle_parent: BlockHeader - ) -> None: - raise NotImplementedError("VM classes must implement this method") - - # - # State - # - @classmethod - @abstractmethod - def get_state_class(cls) -> Type[BaseState]: - raise NotImplementedError("VM classes must implement this method") - - @abstractmethod - @contextlib.contextmanager - def state_in_temp_block(self) -> Iterator[BaseState]: - raise NotImplementedError("VM classes must implement this method") - - -class VM(BaseVM): - cls_logger = logging.getLogger('eth.vm.base.VM') +class VM(Configurable, VirtualMachineAPI): """ - The :class:`~eth.vm.base.BaseVM` class represents the Chain rules for a + The :class:`~eth.abc.VirtualMachineAPI` class represents the Chain rules for a specific protocol definition such as the Frontier or Homestead network. .. note:: - Each :class:`~eth.vm.base.BaseVM` class must be configured with: + Each :class:`~eth.abc.VirtualMachineAPI` class must be configured with: - - ``block_class``: The :class:`~eth.rlp.blocks.Block` class for blocks in this VM ruleset. - - ``_state_class``: The :class:`~eth.vm.state.State` class used by this VM for execution. + - ``block_class``: The :class:`~eth.abc.BlockAPI` class for blocks in this VM ruleset. + - ``_state_class``: The :class:`~eth.abc.StateAPI` class used by this VM for execution. """ + block_class: Type[BlockAPI] = None + fork: str = None # noqa: E701 # flake8 bug that's fixed in 3.6.0+ + chaindb: ChainDatabaseAPI = None + _state_class: Type[StateAPI] = None _state = None _block = None - def __init__(self, header: BlockHeader, chaindb: BaseChainDB) -> None: + cls_logger = logging.getLogger('eth.vm.base.VM') + + def __init__(self, header: BlockHeaderAPI, chaindb: ChainDatabaseAPI) -> None: self.chaindb = chaindb self._initial_header = header - def get_header(self) -> BlockHeader: + def get_header(self) -> BlockHeaderAPI: if self._block is None: return self._initial_header else: return self._block.header - def get_block(self) -> BaseBlock: + def get_block(self) -> BlockAPI: if self._block is None: block_class = self.get_block_class() self._block = block_class.from_header(header=self._initial_header, chaindb=self.chaindb) return self._block @property - def state(self) -> BaseState: + def state(self) -> StateAPI: if self._state is None: self._state = self.build_state(self.chaindb.db, self.get_header(), self.previous_hashes) return self._state @classmethod def build_state(cls, - db: BaseAtomicDB, - header: BlockHeader, + db: AtomicDatabaseAPI, + header: BlockHeaderAPI, previous_hashes: Iterable[Hash32] = () - ) -> BaseState: + ) -> StateAPI: """ You probably want `VM().state` instead of this. @@ -452,9 +147,9 @@ def logger(self) -> logging.Logger: # Execution # def apply_transaction(self, - header: BlockHeader, - transaction: BaseTransaction - ) -> Tuple[Receipt, BaseComputation]: + header: BlockHeaderAPI, + transaction: SignedTransactionAPI + ) -> Tuple[ReceiptAPI, ComputationAPI]: """ Apply the transaction to the current block. This is a wrapper around :func:`~eth.vm.state.State.apply_transaction` with some extra orchestration logic. @@ -479,7 +174,7 @@ def execute_bytecode(self, data: bytes, code: bytes, code_address: Address = None, - ) -> BaseComputation: + ) -> ComputationAPI: """ Execute raw bytecode in the context of the current state of the virtual machine. @@ -513,9 +208,9 @@ def execute_bytecode(self, def apply_all_transactions( self, - transactions: Tuple[BaseTransaction, ...], - base_header: BlockHeader - ) -> Tuple[BlockHeader, Tuple[Receipt, ...], Tuple[BaseComputation, ...]]: + transactions: Sequence[SignedTransactionAPI], + base_header: BlockHeaderAPI + ) -> Tuple[BlockHeaderAPI, Tuple[ReceiptAPI, ...], Tuple[ComputationAPI, ...]]: """ Determine the results of applying all transactions to the base header. This does *not* update the current block or header of the VM. @@ -561,7 +256,7 @@ def apply_all_transactions( # # Mining # - def import_block(self, block: BaseBlock) -> BaseBlock: + def import_block(self, block: BlockAPI) -> BlockAPI: """ Import the given block to the chain. """ @@ -601,7 +296,7 @@ def import_block(self, block: BaseBlock) -> BaseBlock: return self.mine_block() - def mine_block(self, *args: Any, **kwargs: Any) -> BaseBlock: + def mine_block(self, *args: Any, **kwargs: Any) -> BlockAPI: """ Mine the current block. Proxies to self.pack_block method. """ @@ -615,10 +310,10 @@ def mine_block(self, *args: Any, **kwargs: Any) -> BaseBlock: return final_block def set_block_transactions(self, - base_block: BaseBlock, - new_header: BlockHeader, - transactions: Tuple[BaseTransaction, ...], - receipts: Tuple[Receipt, ...]) -> BaseBlock: + base_block: BlockAPI, + new_header: BlockHeaderAPI, + transactions: Sequence[SignedTransactionAPI], + receipts: Sequence[ReceiptAPI]) -> BlockAPI: tx_root_hash, tx_kv_nodes = make_trie_root_and_nodes(transactions) self.chaindb.persist_trie_data_dict(tx_kv_nodes) @@ -637,7 +332,7 @@ def set_block_transactions(self, # # Finalization # - def _assign_block_rewards(self, block: BaseBlock) -> None: + def _assign_block_rewards(self, block: BlockAPI) -> None: block_reward = self.get_block_reward() + ( len(block.uncles) * self.get_nephew_reward() ) @@ -658,7 +353,7 @@ def _assign_block_rewards(self, block: BaseBlock) -> None: uncle.coinbase, ) - def finalize_block(self, block: BaseBlock) -> BaseBlock: + def finalize_block(self, block: BlockAPI) -> BlockAPI: """ Perform any finalization steps like awarding the block mining reward, and persisting the final state root. @@ -679,7 +374,7 @@ def finalize_block(self, block: BaseBlock) -> BaseBlock: return block.copy(header=block.header.copy(state_root=self.state.state_root)) - def pack_block(self, block: BaseBlock, *args: Any, **kwargs: Any) -> BaseBlock: + def pack_block(self, block: BlockAPI, *args: Any, **kwargs: Any) -> BlockAPI: """ Pack block for mining. @@ -723,8 +418,8 @@ def pack_block(self, block: BaseBlock, *args: Any, **kwargs: Any) -> BaseBlock: # @classmethod def generate_block_from_parent_header_and_coinbase(cls, - parent_header: BlockHeader, - coinbase: Address) -> BaseBlock: + parent_header: BlockHeaderAPI, + coinbase: Address) -> BlockAPI: """ Generate block from parent header and coinbase. """ @@ -742,7 +437,7 @@ def generate_block_from_parent_header_and_coinbase(cls, return block @classmethod - def get_block_class(cls) -> Type[BaseBlock]: + def get_block_class(cls) -> Type[BlockAPI]: """ Return the :class:`~eth.rlp.blocks.Block` class that this VM uses for blocks. """ @@ -754,7 +449,7 @@ def get_block_class(cls) -> Type[BaseBlock]: @classmethod def get_prev_hashes(cls, last_block_hash: Hash32, - chaindb: BaseChainDB) -> Optional[Iterable[Hash32]]: + chaindb: ChainDatabaseAPI) -> Optional[Iterable[Hash32]]: if last_block_hash == GENESIS_PARENT_HASH: return @@ -777,7 +472,7 @@ def previous_hashes(self) -> Optional[Iterable[Hash32]]: # # Transactions # - def create_transaction(self, *args: Any, **kwargs: Any) -> BaseTransaction: + def create_transaction(self, *args: Any, **kwargs: Any) -> SignedTransactionAPI: """ Proxy for instantiating a signed transaction for this VM. """ @@ -791,7 +486,7 @@ def create_unsigned_transaction(cls, gas: int, to: Address, value: int, - data: bytes) -> 'BaseUnsignedTransaction': + data: bytes) -> UnsignedTransactionAPI: """ Proxy for instantiating an unsigned transaction for this VM. """ @@ -805,7 +500,7 @@ def create_unsigned_transaction(cls, ) @classmethod - def get_transaction_class(cls) -> Type[BaseTransaction]: + def get_transaction_class(cls) -> Type[SignedTransactionAPI]: """ Return the class that this VM uses for transactions. """ @@ -815,8 +510,8 @@ def get_transaction_class(cls) -> Type[BaseTransaction]: # Validate # @classmethod - def validate_receipt(cls, receipt: Receipt) -> None: - already_checked: Set[Hash32] = set() + def validate_receipt(cls, receipt: ReceiptAPI) -> None: + already_checked: Set[Union[Address, int]] = set() for log_idx, log in enumerate(receipt.logs): if log.address in already_checked: @@ -840,7 +535,7 @@ def validate_receipt(cls, receipt: Receipt) -> None: ) already_checked.add(topic) - def validate_block(self, block: BaseBlock) -> None: + def validate_block(self, block: BlockAPI) -> None: """ Validate the the given block. """ @@ -892,8 +587,8 @@ def validate_block(self, block: BaseBlock) -> None: @classmethod def validate_header(cls, - header: BlockHeader, - parent_header: BlockHeader, + header: BlockHeaderAPI, + parent_header: BlockHeaderAPI, check_seal: bool = True) -> None: """ :raise eth.exceptions.ValidationError: if the header is not valid @@ -937,7 +632,7 @@ def validate_header(cls, raise @classmethod - def validate_seal(cls, header: BlockHeader) -> None: + def validate_seal(cls, header: BlockHeaderAPI) -> None: """ Validate the seal on the given header. """ @@ -946,7 +641,7 @@ def validate_seal(cls, header: BlockHeader) -> None: header.mix_hash, header.nonce, header.difficulty) @classmethod - def validate_uncle(cls, block: BaseBlock, uncle: BaseBlock, uncle_parent: BaseBlock) -> None: + def validate_uncle(cls, block: BlockAPI, uncle: BlockAPI, uncle_parent: BlockAPI) -> None: """ Validate the given uncle in the context of the given block. """ @@ -972,7 +667,7 @@ def validate_uncle(cls, block: BaseBlock, uncle: BaseBlock, uncle_parent: BaseBl # State # @classmethod - def get_state_class(cls) -> Type[BaseState]: + def get_state_class(cls) -> Type[StateAPI]: """ Return the class that this VM uses for states. """ @@ -982,7 +677,7 @@ def get_state_class(cls) -> Type[BaseState]: return cls._state_class @contextlib.contextmanager - def state_in_temp_block(self) -> Iterator[BaseState]: + def state_in_temp_block(self) -> Iterator[StateAPI]: header = self.get_header() temp_block = self.generate_block_from_parent_header_and_coinbase(header, header.coinbase) prev_hashes = itertools.chain((header.hash,), self.previous_hashes) diff --git a/eth/vm/code_stream.py b/eth/vm/code_stream.py index db63741abe..dd3a0487f0 100644 --- a/eth/vm/code_stream.py +++ b/eth/vm/code_stream.py @@ -5,15 +5,18 @@ Set ) +from eth.abc import CodeStreamAPI from eth.validation import ( validate_is_bytes, ) -from eth.vm import opcode_values - -PUSH1, PUSH32, STOP = opcode_values.PUSH1, opcode_values.PUSH32, opcode_values.STOP +from eth.vm.opcode_values import ( + PUSH1, + PUSH32, + STOP, +) -class CodeStream: +class CodeStream(CodeStreamAPI): __slots__ = ['_length_cache', '_raw_code_bytes', 'invalid_positions', 'valid_positions', 'pc'] logger = logging.getLogger('eth.vm.CodeStream') diff --git a/eth/vm/computation.py b/eth/vm/computation.py index 2803f9ea6d..79d46a0039 100644 --- a/eth/vm/computation.py +++ b/eth/vm/computation.py @@ -1,16 +1,18 @@ from abc import ( - ABC, abstractmethod, ) import itertools import logging +from types import TracebackType from typing import ( Any, Callable, cast, Dict, List, + Optional, Tuple, + Type, Union, ) @@ -22,6 +24,17 @@ encode_hex, ) +from eth.abc import ( + MemoryAPI, + StackAPI, + GasMeterAPI, + MessageAPI, + OpcodeAPI, + CodeStreamAPI, + ComputationAPI, + StateAPI, + TransactionContextAPI, +) from eth.constants import ( GAS_MEMORY, GAS_MEMORY_QUADRATIC_DENOMINATOR, @@ -62,21 +75,12 @@ from eth.vm.message import ( Message, ) -from eth.vm.opcode import ( - Opcode -) from eth.vm.stack import ( Stack, ) -from eth.vm.state import ( - BaseState, -) -from eth.vm.transaction_context import ( - BaseTransactionContext -) -def NO_RESULT(computation: 'BaseComputation') -> None: +def NO_RESULT(computation: ComputationAPI) -> None: """ This is a special method intended for usage as the "no precompile found" result. The type signature is designed to match the other precompiles. @@ -93,94 +97,7 @@ def memory_gas_cost(size_in_bytes: int) -> int: return total_cost -class BaseStackManipulation: - @abstractmethod - def stack_pop_ints(self, num_items: int) -> Tuple[int, ...]: - """ - Pop and return a tuple of integers of length ``num_items`` from the stack. - - Raise `eth.exceptions.InsufficientStack` if there are not enough items on - the stack. - - Items are ordered with the top of the stack as the first item in the tuple. - """ - pass - - @abstractmethod - def stack_pop_bytes(self, num_items: int) -> Tuple[bytes, ...]: - """ - Pop and return a tuple of bytes of length ``num_items`` from the stack. - - Raise `eth.exceptions.InsufficientStack` if there are not enough items on - the stack. - - Items are ordered with the top of the stack as the first item in the tuple. - """ - pass - - @abstractmethod - def stack_pop_any(self, num_items: int) -> Tuple[Union[int, bytes], ...]: - """ - Pop and return a tuple of items of length ``num_items`` from the stack. - The type of each element will be int or bytes, depending on whether it was - pushed with stack_push_bytes or stack_push_int. - - Raise `eth.exceptions.InsufficientStack` if there are not enough items on - the stack. - - Items are ordered with the top of the stack as the first item in the tuple. - """ - pass - - @abstractmethod - def stack_pop1_int(self) -> int: - """ - Pop and return an integer from the stack. - - Raise `eth.exceptions.InsufficientStack` if the stack was empty. - """ - pass - - @abstractmethod - def stack_pop1_bytes(self) -> bytes: - """ - Pop and return a bytes element from the stack. - - Raise `eth.exceptions.InsufficientStack` if the stack was empty. - """ - pass - - @abstractmethod - def stack_pop1_any(self) -> Union[int, bytes]: - """ - Pop and return an element from the stack. - The type of each element will be int or bytes, depending on whether it was - pushed with stack_push_bytes or stack_push_int. - - Raise `eth.exceptions.InsufficientStack` if the stack was empty. - """ - pass - - @abstractmethod - def stack_push_int(self, value: int) -> None: - """ - Push ``value`` onto the stack. - - Raise `eth.exceptions.StackDepthLimit` if the stack is full. - """ - pass - - @abstractmethod - def stack_push_bytes(self, value: bytes) -> None: - """ - Push ``value`` onto the stack. - - Raise `eth.exceptions.StackDepthLimit` if the stack is full. - """ - pass - - -class BaseComputation(Configurable, BaseStackManipulation, ABC): +class BaseComputation(Configurable, ComputationAPI): """ The base class for all execution computations. @@ -193,35 +110,36 @@ class BaseComputation(Configurable, BaseStackManipulation, ABC): ``_precompiles``: A mapping of contract address to the precompile function for execution of precompiled contracts. """ - state = None - msg = None - transaction_context = None + state: StateAPI = None + msg: MessageAPI = None + transaction_context: TransactionContextAPI = None - _memory = None - _stack = None - _gas_meter = None + _memory: MemoryAPI = None + _stack: StackAPI = None + _gas_meter: GasMeterAPI = None - code = None + code: CodeStreamAPI = None - children: List['BaseComputation'] = None + children: List[ComputationAPI] = None - _output = b'' - return_data = b'' + _output: bytes = b'' + return_data: bytes = b'' _error: VMError = None + # TODO: use a NamedTuple for log entries _log_entries: List[Tuple[int, Address, Tuple[int, ...], bytes]] = None accounts_to_delete: Dict[Address, Address] = None # VM configuration - opcodes: Dict[int, Any] = None - _precompiles: Dict[Address, Callable[['BaseComputation'], 'BaseComputation']] = None + opcodes: Dict[int, OpcodeAPI] = None + _precompiles: Dict[Address, Callable[[ComputationAPI], ComputationAPI]] = None logger = cast(ExtendedDebugLogger, logging.getLogger('eth.vm.computation.Computation')) def __init__(self, - state: BaseState, - message: Message, - transaction_context: BaseTransactionContext) -> None: + state: StateAPI, + message: MessageAPI, + transaction_context: TransactionContextAPI) -> None: self.state = state self.msg = message @@ -265,6 +183,18 @@ def is_error(self) -> bool: """ return not self.is_success + @property + def error(self) -> VMError: + if self._error is not None: + return self._error + raise AttributeError("Computation does not have an error") + + @error.setter + def error(self, value: VMError) -> None: + if self._error is not None: + raise AttributeError(f"Computation already has an error set: {self._error}") + self._error = value + def raise_if_error(self) -> None: """ If there was an error during computation, raise it as an exception immediately. @@ -358,7 +288,7 @@ def memory_read_bytes(self, start_position: int, size: int) -> bytes: # # Gas Consumption # - def get_gas_meter(self) -> GasMeter: + def get_gas_meter(self) -> GasMeterAPI: return GasMeter(self.msg.gas) def consume_gas(self, amount: int, reason: str) -> None: @@ -481,7 +411,7 @@ def prepare_child_message(self, value: int, data: BytesOrView, code: bytes, - **kwargs: Any) -> Message: + **kwargs: Any) -> MessageAPI: """ Helper method for creating a child computation. """ @@ -498,7 +428,7 @@ def prepare_child_message(self, ) return child_message - def apply_child_computation(self, child_msg: Message) -> 'BaseComputation': + def apply_child_computation(self, child_msg: MessageAPI) -> ComputationAPI: """ Apply the vm message ``child_msg`` as a child computation. """ @@ -506,7 +436,7 @@ def apply_child_computation(self, child_msg: Message) -> 'BaseComputation': self.add_child_computation(child_computation) return child_computation - def generate_child_computation(self, child_msg: Message) -> 'BaseComputation': + def generate_child_computation(self, child_msg: MessageAPI) -> ComputationAPI: if child_msg.is_create: child_computation = self.__class__( self.state, @@ -521,7 +451,7 @@ def generate_child_computation(self, child_msg: Message) -> 'BaseComputation': ).apply_message() return child_computation - def add_child_computation(self, child_computation: 'BaseComputation') -> None: + def add_child_computation(self, child_computation: ComputationAPI) -> None: if child_computation.is_error: if child_computation.msg.is_create: self.return_data = child_computation.output @@ -569,7 +499,7 @@ def add_log_entry(self, account: Address, topics: Tuple[int, ...], data: bytes) self._log_entries.append( (self.transaction_context.get_next_log_counter(), account, topics, data)) - def _get_log_entries(self) -> List[Tuple[int, bytes, Tuple[int, ...], bytes]]: + def get_raw_log_entries(self) -> Tuple[Tuple[int, bytes, Tuple[int, ...], bytes], ...]: """ Return the log entries for this computation and its children. @@ -577,20 +507,20 @@ def _get_log_entries(self) -> List[Tuple[int, bytes, Tuple[int, ...], bytes]]: include the sequential counter as the first element of the tuple representing every entry. """ if self.is_error: - return [] + return () else: - return sorted(itertools.chain( + return tuple(sorted(itertools.chain( self._log_entries, - *(child._get_log_entries() for child in self.children) - )) + *(child.get_raw_log_entries() for child in self.children) + ))) def get_log_entries(self) -> Tuple[Tuple[bytes, Tuple[int, ...], bytes], ...]: - return tuple(log[1:] for log in self._get_log_entries()) + return tuple(log[1:] for log in self.get_raw_log_entries()) # # Context Manager API # - def __enter__(self) -> 'BaseComputation': + def __enter__(self) -> ComputationAPI: if self.logger.show_debug2: self.logger.debug2( ( @@ -607,7 +537,10 @@ def __enter__(self) -> 'BaseComputation': return self - def __exit__(self, exc_type: None, exc_value: None, traceback: None) -> None: + def __exit__(self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType]) -> Union[None, bool]: if exc_value and isinstance(exc_value, VMError): if self.logger.show_debug2: self.logger.debug2( @@ -650,18 +583,20 @@ def __exit__(self, exc_type: None, exc_value: None, traceback: None) -> None: self._gas_meter.gas_remaining, ) + return None + # # State Transition # @abstractmethod - def apply_message(self) -> 'BaseComputation': + def apply_message(self) -> ComputationAPI: """ Execution of a VM message. """ raise NotImplementedError("Must be implemented by subclasses") @abstractmethod - def apply_create_message(self) -> 'BaseComputation': + def apply_create_message(self) -> ComputationAPI: """ Execution of a VM message to create a new contract. """ @@ -669,9 +604,9 @@ def apply_create_message(self) -> 'BaseComputation': @classmethod def apply_computation(cls, - state: BaseState, - message: Message, - transaction_context: BaseTransactionContext) -> 'BaseComputation': + state: StateAPI, + message: MessageAPI, + transaction_context: TransactionContextAPI) -> ComputationAPI: """ Perform the computation that would be triggered by the VM message. """ @@ -709,13 +644,13 @@ def apply_computation(cls, # Opcode API # @property - def precompiles(self) -> Dict[Address, Callable[['BaseComputation'], Any]]: + def precompiles(self) -> Dict[Address, Callable[[ComputationAPI], Any]]: if self._precompiles is None: return dict() else: return self._precompiles - def get_opcode_fn(self, opcode: int) -> Opcode: + def get_opcode_fn(self, opcode: int) -> OpcodeAPI: try: return self.opcodes[opcode] except KeyError: diff --git a/eth/vm/forks/byzantium/__init__.py b/eth/vm/forks/byzantium/__init__.py index 98fa29fe91..0efd09a6bf 100644 --- a/eth/vm/forks/byzantium/__init__.py +++ b/eth/vm/forks/byzantium/__init__.py @@ -13,20 +13,22 @@ ValidationError, ) +from eth.abc import ( + BlockAPI, + BlockHeaderAPI, + ComputationAPI, + ReceiptAPI, + SignedTransactionAPI, + StateAPI, +) from eth.constants import ( MAX_UNCLE_DEPTH, ) -from eth.rlp.blocks import BaseBlock -from eth.rlp.headers import BlockHeader -from eth.rlp.receipts import Receipt -from eth.rlp.transactions import BaseTransaction from eth.validation import ( validate_lte, ) from eth.vm.forks.spurious_dragon import SpuriousDragonVM from eth.vm.forks.frontier import make_frontier_receipt -from eth.vm.computation import BaseComputation -from eth.vm.state import BaseState from .blocks import ByzantiumBlock from .constants import ( @@ -43,7 +45,7 @@ @curry -def get_uncle_reward(block_reward: int, block_number: int, uncle: BaseBlock) -> int: +def get_uncle_reward(block_reward: int, block_number: int, uncle: BlockAPI) -> int: block_number_delta = block_number - uncle.block_number validate_lte(block_number_delta, MAX_UNCLE_DEPTH) return (8 - block_number_delta) * block_reward // 8 @@ -60,8 +62,8 @@ class ByzantiumVM(SpuriousDragonVM): fork = 'byzantium' # classes - block_class: Type[BaseBlock] = ByzantiumBlock - _state_class: Type[BaseState] = ByzantiumState + block_class: Type[BlockAPI] = ByzantiumBlock + _state_class: Type[StateAPI] = ByzantiumState # Methods create_header_from_parent = staticmethod(create_byzantium_header_from_parent) # type: ignore @@ -73,7 +75,7 @@ class ByzantiumVM(SpuriousDragonVM): get_uncle_reward = staticmethod(get_uncle_reward) @classmethod - def validate_receipt(cls, receipt: Receipt) -> None: + def validate_receipt(cls, receipt: ReceiptAPI) -> None: super().validate_receipt(receipt) if receipt.state_root not in EIP658_STATUS_CODES: raise ValidationError( @@ -89,7 +91,9 @@ def validate_receipt(cls, receipt: Receipt) -> None: def get_block_reward() -> int: return EIP649_BLOCK_REWARD - def add_receipt_to_header(self, old_header: BlockHeader, receipt: Receipt) -> BlockHeader: + def add_receipt_to_header(self, + old_header: BlockHeaderAPI, + receipt: ReceiptAPI) -> BlockHeaderAPI: # Skip merkelizing the account data and persisting it to disk on every transaction. # Starting in Byzantium, this is no longer necessary, because the state root isn't # in the receipt anymore. @@ -100,10 +104,10 @@ def add_receipt_to_header(self, old_header: BlockHeader, receipt: Receipt) -> Bl @staticmethod def make_receipt( - base_header: BlockHeader, - transaction: BaseTransaction, - computation: BaseComputation, - state: BaseState) -> Receipt: + base_header: BlockHeaderAPI, + transaction: SignedTransactionAPI, + computation: ComputationAPI, + state: StateAPI) -> ReceiptAPI: receipt_without_state_root = make_frontier_receipt(base_header, transaction, computation) diff --git a/eth/vm/forks/byzantium/headers.py b/eth/vm/forks/byzantium/headers.py index fd2f918ecf..47116ff52d 100644 --- a/eth/vm/forks/byzantium/headers.py +++ b/eth/vm/forks/byzantium/headers.py @@ -5,6 +5,11 @@ from eth_utils.toolz import ( curry, ) + +from eth.abc import ( + BlockHeaderAPI, + VirtualMachineAPI, +) from eth.constants import ( EMPTY_UNCLE_HASH, DIFFICULTY_ADJUSTMENT_DENOMINATOR, @@ -12,9 +17,6 @@ BOMB_EXPONENTIAL_PERIOD, BOMB_EXPONENTIAL_FREE_PERIODS, ) -from eth.rlp.headers import ( - BlockHeader, -) from eth._utils.db import ( get_parent_header, ) @@ -22,9 +24,6 @@ validate_gt, validate_header_params_for_configuration, ) -from eth.vm.base import ( - BaseVM -) from eth.vm.forks.frontier.headers import ( create_frontier_header_from_parent, ) @@ -37,7 +36,7 @@ @curry def compute_difficulty( bomb_delay: int, - parent_header: BlockHeader, + parent_header: BlockHeaderAPI, timestamp: int) -> int: """ https://github.com/ethereum/EIPs/issues/100 @@ -74,9 +73,9 @@ def compute_difficulty( @curry -def create_header_from_parent(difficulty_fn: Callable[[BlockHeader, int], int], - parent_header: BlockHeader, - **header_params: Any) -> BlockHeader: +def create_header_from_parent(difficulty_fn: Callable[[BlockHeaderAPI, int], int], + parent_header: BlockHeaderAPI, + **header_params: Any) -> BlockHeaderAPI: if 'difficulty' not in header_params: header_params.setdefault('timestamp', parent_header.timestamp + 1) @@ -89,9 +88,9 @@ def create_header_from_parent(difficulty_fn: Callable[[BlockHeader, int], int], @curry -def configure_header(difficulty_fn: Callable[[BlockHeader, int], int], - vm: BaseVM, - **header_params: Any) -> BlockHeader: +def configure_header(difficulty_fn: Callable[[BlockHeaderAPI, int], int], + vm: VirtualMachineAPI, + **header_params: Any) -> BlockHeaderAPI: validate_header_params_for_configuration(header_params) with vm.get_header().build_changeset(**header_params) as changeset: diff --git a/eth/vm/forks/byzantium/opcodes.py b/eth/vm/forks/byzantium/opcodes.py index de939af8f0..2760b29e9e 100644 --- a/eth/vm/forks/byzantium/opcodes.py +++ b/eth/vm/forks/byzantium/opcodes.py @@ -1,5 +1,6 @@ import copy import functools +from typing import Dict from eth_utils.toolz import merge @@ -10,6 +11,7 @@ from eth import constants +from eth.abc import OpcodeAPI from eth.exceptions import ( WriteProtection, ) @@ -128,7 +130,7 @@ def inner(computation: BaseComputation) -> Callable[..., Any]: } -BYZANTIUM_OPCODES = merge( +BYZANTIUM_OPCODES: Dict[int, OpcodeAPI] = merge( copy.deepcopy(SPURIOUS_DRAGON_OPCODES), UPDATED_OPCODES, ) diff --git a/eth/vm/forks/frontier/__init__.py b/eth/vm/forks/frontier/__init__.py index d43d691eba..1ba89f49da 100644 --- a/eth/vm/forks/frontier/__init__.py +++ b/eth/vm/forks/frontier/__init__.py @@ -4,21 +4,23 @@ BloomFilter, ) +from eth.abc import ( + BlockAPI, + BlockHeaderAPI, + ReceiptAPI, + StateAPI, + SignedTransactionAPI, + ComputationAPI, +) from eth.constants import ( BLOCK_REWARD, UNCLE_DEPTH_PENALTY_FACTOR, ZERO_HASH32, ) - -from eth.rlp.blocks import BaseBlock -from eth.rlp.headers import BlockHeader from eth.rlp.logs import Log from eth.rlp.receipts import Receipt -from eth.rlp.transactions import BaseTransaction from eth.vm.base import VM -from eth.vm.computation import BaseComputation -from eth.vm.state import BaseState from .blocks import FrontierBlock from .state import FrontierState @@ -30,9 +32,9 @@ from .validation import validate_frontier_transaction_against_header -def make_frontier_receipt(base_header: BlockHeader, - transaction: BaseTransaction, - computation: BaseComputation) -> Receipt: +def make_frontier_receipt(base_header: BlockHeaderAPI, + transaction: SignedTransactionAPI, + computation: ComputationAPI) -> ReceiptAPI: # Reusable for other forks # This skips setting the state root (set to 0 instead). The logic for making a state root # lives in the FrontierVM, so that state merkelization at each receipt is skipped at Byzantium+. @@ -67,8 +69,8 @@ class FrontierVM(VM): fork: str = 'frontier' # noqa: E701 # flake8 bug that's fixed in 3.6.0+ # classes - block_class: Type[BaseBlock] = FrontierBlock - _state_class: Type[BaseState] = FrontierState + block_class: Type[BlockAPI] = FrontierBlock + _state_class: Type[StateAPI] = FrontierState # methods create_header_from_parent = staticmethod(create_frontier_header_from_parent) # type: ignore @@ -81,7 +83,7 @@ def get_block_reward() -> int: return BLOCK_REWARD @staticmethod - def get_uncle_reward(block_number: int, uncle: BaseBlock) -> int: + def get_uncle_reward(block_number: int, uncle: BlockAPI) -> int: return BLOCK_REWARD * ( UNCLE_DEPTH_PENALTY_FACTOR + uncle.block_number - block_number ) // UNCLE_DEPTH_PENALTY_FACTOR @@ -90,7 +92,9 @@ def get_uncle_reward(block_number: int, uncle: BaseBlock) -> int: def get_nephew_reward(cls) -> int: return cls.get_block_reward() // 32 - def add_receipt_to_header(self, old_header: BlockHeader, receipt: Receipt) -> BlockHeader: + def add_receipt_to_header(self, + old_header: BlockHeaderAPI, + receipt: ReceiptAPI) -> BlockHeaderAPI: return old_header.copy( bloom=int(BloomFilter(old_header.bloom) | receipt.bloom), gas_used=receipt.gas_used, @@ -99,10 +103,10 @@ def add_receipt_to_header(self, old_header: BlockHeader, receipt: Receipt) -> Bl @staticmethod def make_receipt( - base_header: BlockHeader, - transaction: BaseTransaction, - computation: BaseComputation, - state: BaseState) -> Receipt: + base_header: BlockHeaderAPI, + transaction: SignedTransactionAPI, + computation: ComputationAPI, + state: StateAPI) -> ReceiptAPI: receipt_without_state_root = make_frontier_receipt(base_header, transaction, computation) diff --git a/eth/vm/forks/frontier/blocks.py b/eth/vm/forks/frontier/blocks.py index cf340c2f6d..7d652a3f52 100644 --- a/eth/vm/forks/frontier/blocks.py +++ b/eth/vm/forks/frontier/blocks.py @@ -1,6 +1,6 @@ from typing import ( - Iterable, - List, + Sequence, + Tuple, Type, ) @@ -19,14 +19,15 @@ from eth_hash.auto import keccak +from eth.abc import ( + BlockHeaderAPI, + ChainDatabaseAPI, + ReceiptAPI, + SignedTransactionAPI, +) from eth.constants import ( EMPTY_UNCLE_HASH, ) - -from eth.db.chain import ( - BaseChainDB, -) - from eth.rlp.blocks import ( BaseBlock, ) @@ -36,9 +37,6 @@ from eth.rlp.receipts import ( Receipt, ) -from eth.rlp.transactions import ( - BaseTransaction, -) from .transactions import ( FrontierTransaction, @@ -56,9 +54,9 @@ class FrontierBlock(BaseBlock): bloom_filter = None def __init__(self, - header: BlockHeader, - transactions: Iterable[BaseTransaction]=None, - uncles: Iterable[BlockHeader]=None) -> None: + header: BlockHeaderAPI, + transactions: Sequence[SignedTransactionAPI]=None, + uncles: Sequence[BlockHeaderAPI]=None) -> None: if transactions is None: transactions = [] if uncles is None: @@ -88,25 +86,25 @@ def hash(self) -> Hash32: # Transaction class for this block class # @classmethod - def get_transaction_class(cls) -> Type[BaseTransaction]: + def get_transaction_class(cls) -> Type[SignedTransactionAPI]: return cls.transaction_class # # Receipts API # - def get_receipts(self, chaindb: BaseChainDB) -> Iterable[Receipt]: + def get_receipts(self, chaindb: ChainDatabaseAPI) -> Tuple[ReceiptAPI, ...]: return chaindb.get_receipts(self.header, Receipt) # # Header API # @classmethod - def from_header(cls, header: BlockHeader, chaindb: BaseChainDB) -> BaseBlock: + def from_header(cls, header: BlockHeaderAPI, chaindb: ChainDatabaseAPI) -> "FrontierBlock": """ Returns the block denoted by the given block header. """ if header.uncles_hash == EMPTY_UNCLE_HASH: - uncles: List[BlockHeader] = [] + uncles: Tuple[BlockHeader, ...] = () else: uncles = chaindb.get_block_uncles(header.uncles_hash) @@ -121,7 +119,7 @@ def from_header(cls, header: BlockHeader, chaindb: BaseChainDB) -> BaseBlock: # # Execution API # - def add_uncle(self, uncle: BlockHeader) -> "FrontierBlock": + def add_uncle(self, uncle: BlockHeaderAPI) -> "FrontierBlock": self.uncles.append(uncle) self.header.uncles_hash = keccak(rlp.encode(self.uncles)) return self diff --git a/eth/vm/forks/frontier/computation.py b/eth/vm/forks/frontier/computation.py index 28aafea544..ee9e63e66e 100644 --- a/eth/vm/forks/frontier/computation.py +++ b/eth/vm/forks/frontier/computation.py @@ -10,16 +10,17 @@ STACK_DEPTH_LIMIT, ) +from eth._utils.address import ( + force_bytes_to_address, +) +from eth.abc import ( + ComputationAPI, +) from eth.exceptions import ( OutOfGas, InsufficientFunds, StackDepthLimit, ) - -from eth._utils.address import ( - force_bytes_to_address, -) - from eth.vm.computation import ( BaseComputation, ) @@ -44,7 +45,7 @@ class FrontierComputation(BaseComputation): opcodes = FRONTIER_OPCODES _precompiles = FRONTIER_PRECOMPILES # type: ignore # https://github.com/python/mypy/issues/708 # noqa: E501 - def apply_message(self) -> BaseComputation: + def apply_message(self) -> ComputationAPI: snapshot = self.state.snapshot() if self.msg.depth > STACK_DEPTH_LIMIT: @@ -83,7 +84,7 @@ def apply_message(self) -> BaseComputation: return computation - def apply_create_message(self) -> BaseComputation: + def apply_create_message(self) -> ComputationAPI: computation = self.apply_message() if computation.is_error: diff --git a/eth/vm/forks/frontier/opcodes.py b/eth/vm/forks/frontier/opcodes.py index b9b32ea30c..66cdbb703c 100644 --- a/eth/vm/forks/frontier/opcodes.py +++ b/eth/vm/forks/frontier/opcodes.py @@ -1,5 +1,8 @@ +from typing import Dict + from eth import constants +from eth.abc import OpcodeAPI from eth.vm import mnemonics from eth.vm import opcode_values from eth.vm.logic import ( @@ -23,7 +26,7 @@ ) -FRONTIER_OPCODES = { +FRONTIER_OPCODES: Dict[int, OpcodeAPI] = { # # Arithmetic # diff --git a/eth/vm/forks/frontier/state.py b/eth/vm/forks/frontier/state.py index bb580b44e1..59ee32196a 100644 --- a/eth/vm/forks/frontier/state.py +++ b/eth/vm/forks/frontier/state.py @@ -1,10 +1,18 @@ -from __future__ import absolute_import +from typing import Type from eth_hash.auto import keccak from eth_utils import ( encode_hex, ) +from eth.abc import ( + AccountDatabaseAPI, + ComputationAPI, + SignedTransactionAPI, + MessageAPI, + TransactionContextAPI, + TransactionExecutorAPI, +) from eth.constants import CREATE_CONTRACT_ADDRESS from eth.db.account import ( AccountDB, @@ -13,16 +21,10 @@ ContractCreationCollision, ) -from eth.typing import ( - BaseOrSpoofTransaction, -) from eth._utils.address import ( generate_contract_address, ) -from eth.vm.computation import ( - BaseComputation, -) from eth.vm.message import ( Message, ) @@ -41,16 +43,13 @@ class FrontierTransactionExecutor(BaseTransactionExecutor): - - def validate_transaction(self, transaction: BaseOrSpoofTransaction) -> BaseOrSpoofTransaction: + def validate_transaction(self, transaction: SignedTransactionAPI) -> None: # Validate the transaction transaction.validate() self.vm_state.validate_transaction(transaction) - return transaction - - def build_evm_message(self, transaction: BaseOrSpoofTransaction) -> Message: + def build_evm_message(self, transaction: SignedTransactionAPI) -> MessageAPI: gas_fee = transaction.gas * transaction.gas_price @@ -103,8 +102,8 @@ def build_evm_message(self, transaction: BaseOrSpoofTransaction) -> Message: return message def build_computation(self, - message: Message, - transaction: BaseOrSpoofTransaction) -> BaseComputation: + message: MessageAPI, + transaction: SignedTransactionAPI) -> ComputationAPI: """Apply the message to the VM.""" transaction_context = self.vm_state.get_transaction_context(transaction) if message.is_create: @@ -116,7 +115,7 @@ def build_computation(self, # The address of the newly created contract has *somehow* collided # with an existing contract address. computation = self.vm_state.get_computation(message, transaction_context) - computation._error = ContractCreationCollision( + computation.error = ContractCreationCollision( "Address collision while creating contract: {0}".format( encode_hex(message.storage_address), ) @@ -138,8 +137,8 @@ def build_computation(self, return computation def finalize_computation(self, - transaction: BaseOrSpoofTransaction, - computation: BaseComputation) -> BaseComputation: + transaction: SignedTransactionAPI, + computation: ComputationAPI) -> ComputationAPI: # Self Destruct Refunds num_deletions = len(computation.get_accounts_for_deletion()) if num_deletions: @@ -186,13 +185,14 @@ def finalize_computation(self, class FrontierState(BaseState): - computation_class = FrontierComputation - transaction_context_class = FrontierTransactionContext # Type[BaseTransactionContext] - account_db_class = AccountDB # Type[BaseAccountDB] - transaction_executor = FrontierTransactionExecutor # Type[BaseTransactionExecutor] - - validate_transaction = validate_frontier_transaction + computation_class: Type[ComputationAPI] = FrontierComputation + transaction_context_class: Type[TransactionContextAPI] = FrontierTransactionContext + account_db_class: Type[AccountDatabaseAPI] = AccountDB + transaction_executor_class: Type[TransactionExecutorAPI] = FrontierTransactionExecutor - def execute_transaction(self, transaction: BaseOrSpoofTransaction) -> BaseComputation: + def execute_transaction(self, transaction: SignedTransactionAPI) -> ComputationAPI: executor = self.get_transaction_executor() return executor(transaction) + + def validate_transaction(self, transaction: SignedTransactionAPI) -> None: + validate_frontier_transaction(self, transaction) diff --git a/eth/vm/forks/frontier/validation.py b/eth/vm/forks/frontier/validation.py index ddfaa0fca0..2cb4c7a449 100644 --- a/eth/vm/forks/frontier/validation.py +++ b/eth/vm/forks/frontier/validation.py @@ -2,17 +2,16 @@ ValidationError, ) -from eth.rlp.headers import BlockHeader -from eth.rlp.transactions import BaseTransaction -from eth.typing import ( - BaseOrSpoofTransaction +from eth.abc import ( + BlockHeaderAPI, + SignedTransactionAPI, + StateAPI, + VirtualMachineAPI, ) -from eth.vm.base import BaseVM -from eth.vm.state import BaseState -def validate_frontier_transaction(state: BaseState, - transaction: BaseOrSpoofTransaction) -> None: +def validate_frontier_transaction(state: StateAPI, + transaction: SignedTransactionAPI) -> None: gas_cost = transaction.gas * transaction.gas_price sender_balance = state.get_balance(transaction.sender) @@ -34,9 +33,9 @@ def validate_frontier_transaction(state: BaseState, raise ValidationError("Invalid transaction nonce") -def validate_frontier_transaction_against_header(_vm: BaseVM, - base_header: BlockHeader, - transaction: BaseTransaction) -> None: +def validate_frontier_transaction_against_header(_vm: VirtualMachineAPI, + base_header: BlockHeaderAPI, + transaction: SignedTransactionAPI) -> None: if base_header.gas_used + transaction.gas > base_header.gas_limit: raise ValidationError( "Transaction exceeds gas limit: using {}, bringing total to {}, but limit is {}".format( diff --git a/eth/vm/forks/homestead/__init__.py b/eth/vm/forks/homestead/__init__.py index bb448dda69..8035572aec 100644 --- a/eth/vm/forks/homestead/__init__.py +++ b/eth/vm/forks/homestead/__init__.py @@ -1,9 +1,11 @@ from typing import Optional, Type -from eth.rlp.blocks import BaseBlock -from eth.vm.state import BaseState from eth_typing import BlockNumber +from eth.abc import ( + BlockAPI, + StateAPI, +) from eth.vm.forks.frontier import FrontierVM from .blocks import HomesteadBlock @@ -31,8 +33,8 @@ class HomesteadVM(MetaHomesteadVM): fork: str = 'homestead' # noqa: E701 # flake8 bug that's fixed in 3.6.0+ # classes - block_class: Type[BaseBlock] = HomesteadBlock - _state_class: Type[BaseState] = HomesteadState + block_class: Type[BlockAPI] = HomesteadBlock + _state_class: Type[StateAPI] = HomesteadState # method overrides create_header_from_parent = staticmethod(create_homestead_header_from_parent) # type: ignore diff --git a/eth/vm/forks/homestead/computation.py b/eth/vm/forks/homestead/computation.py index ee685b87be..a002a9034b 100644 --- a/eth/vm/forks/homestead/computation.py +++ b/eth/vm/forks/homestead/computation.py @@ -7,7 +7,8 @@ from eth_utils import ( encode_hex, ) -from eth.vm.computation import BaseComputation + +from eth.abc import ComputationAPI from eth.vm.forks.frontier.computation import ( FrontierComputation, ) @@ -23,7 +24,7 @@ class HomesteadComputation(FrontierComputation): # Override opcodes = HOMESTEAD_OPCODES - def apply_create_message(self) -> BaseComputation: + def apply_create_message(self) -> ComputationAPI: snapshot = self.state.snapshot() computation = self.apply_message() @@ -44,7 +45,7 @@ def apply_create_message(self) -> BaseComputation: except OutOfGas as err: # Different from Frontier: reverts state on gas failure while # writing contract code. - computation._error = err + computation.error = err self.state.revert(snapshot) else: if self.logger: diff --git a/eth/vm/forks/homestead/opcodes.py b/eth/vm/forks/homestead/opcodes.py index 548339ae32..a3104dcf25 100644 --- a/eth/vm/forks/homestead/opcodes.py +++ b/eth/vm/forks/homestead/opcodes.py @@ -1,8 +1,10 @@ import copy +from typing import Dict from eth_utils.toolz import merge from eth import constants +from eth.abc import OpcodeAPI from eth.vm import mnemonics from eth.vm import opcode_values from eth.vm.logic import ( @@ -21,7 +23,7 @@ } -HOMESTEAD_OPCODES = merge( +HOMESTEAD_OPCODES: Dict[int, OpcodeAPI] = merge( copy.deepcopy(FRONTIER_OPCODES), NEW_OPCODES ) diff --git a/eth/vm/forks/homestead/state.py b/eth/vm/forks/homestead/state.py index f8e4302e43..e828bf1b55 100644 --- a/eth/vm/forks/homestead/state.py +++ b/eth/vm/forks/homestead/state.py @@ -1,3 +1,9 @@ +from typing import Type + +from eth.abc import ( + ComputationAPI, + SignedTransactionAPI, +) from eth.vm.forks.frontier.state import ( FrontierState, FrontierTransactionExecutor, @@ -8,9 +14,10 @@ class HomesteadState(FrontierState): - computation_class = HomesteadComputation + computation_class: Type[ComputationAPI] = HomesteadComputation - validate_transaction = validate_homestead_transaction + def validate_transaction(self, transaction: SignedTransactionAPI) -> None: + validate_homestead_transaction(self, transaction) class HomesteadTransactionExecutor(FrontierTransactionExecutor): diff --git a/eth/vm/forks/homestead/validation.py b/eth/vm/forks/homestead/validation.py index 59c93b4f12..b02ecf5031 100644 --- a/eth/vm/forks/homestead/validation.py +++ b/eth/vm/forks/homestead/validation.py @@ -6,15 +6,17 @@ SECPK1_N, ) -from eth.typing import BaseOrSpoofTransaction +from eth.abc import ( + SignedTransactionAPI, + StateAPI, +) from eth.vm.forks.frontier.validation import ( validate_frontier_transaction, ) -from eth.vm.state import BaseState -def validate_homestead_transaction(state: BaseState, - transaction: BaseOrSpoofTransaction) -> None: +def validate_homestead_transaction(state: StateAPI, + transaction: SignedTransactionAPI) -> None: if transaction.s > SECPK1_N // 2 or transaction.s == 0: raise ValidationError("Invalid signature S value") diff --git a/eth/vm/forks/petersburg/__init__.py b/eth/vm/forks/petersburg/__init__.py index 39c0aba791..a45afc5050 100644 --- a/eth/vm/forks/petersburg/__init__.py +++ b/eth/vm/forks/petersburg/__init__.py @@ -2,12 +2,14 @@ Type, ) -from eth.rlp.blocks import BaseBlock +from eth.abc import ( + StateAPI, + BlockAPI, +) from eth.vm.forks.byzantium import ( ByzantiumVM, get_uncle_reward, ) -from eth.vm.state import BaseState from .blocks import PetersburgBlock from .constants import EIP1234_BLOCK_REWARD @@ -24,8 +26,8 @@ class PetersburgVM(ByzantiumVM): fork = 'petersburg' # classes - block_class: Type[BaseBlock] = PetersburgBlock - _state_class: Type[BaseState] = PetersburgState + block_class: Type[BlockAPI] = PetersburgBlock + _state_class: Type[StateAPI] = PetersburgState # Methods create_header_from_parent = staticmethod(create_petersburg_header_from_parent) # type: ignore diff --git a/eth/vm/forks/petersburg/opcodes.py b/eth/vm/forks/petersburg/opcodes.py index b591eab9c6..dc26b9a62a 100644 --- a/eth/vm/forks/petersburg/opcodes.py +++ b/eth/vm/forks/petersburg/opcodes.py @@ -1,4 +1,6 @@ import copy +from typing import Dict + from eth_utils.toolz import ( merge ) @@ -6,6 +8,7 @@ from eth import ( constants ) +from eth.abc import OpcodeAPI from eth.vm import ( mnemonics, opcode_values, @@ -54,7 +57,7 @@ )(), } -PETERSBURG_OPCODES = merge( +PETERSBURG_OPCODES: Dict[int, OpcodeAPI] = merge( copy.deepcopy(BYZANTIUM_OPCODES), UPDATED_OPCODES, ) diff --git a/eth/vm/forks/spurious_dragon/__init__.py b/eth/vm/forks/spurious_dragon/__init__.py index 1829facf96..a0bc07e995 100644 --- a/eth/vm/forks/spurious_dragon/__init__.py +++ b/eth/vm/forks/spurious_dragon/__init__.py @@ -1,6 +1,9 @@ from typing import Type -from eth.rlp.blocks import BaseBlock -from eth.vm.state import BaseState + +from eth.abc import ( + BlockAPI, + StateAPI, +) from ..tangerine_whistle import TangerineWhistleVM @@ -13,5 +16,5 @@ class SpuriousDragonVM(TangerineWhistleVM): fork: str = 'spurious-dragon' # noqa: E701 # flake8 bug that's fixed in 3.6.0+ # classes - block_class: Type[BaseBlock] = SpuriousDragonBlock - _state_class: Type[BaseState] = SpuriousDragonState + block_class: Type[BlockAPI] = SpuriousDragonBlock + _state_class: Type[StateAPI] = SpuriousDragonState diff --git a/eth/vm/forks/spurious_dragon/computation.py b/eth/vm/forks/spurious_dragon/computation.py index 6d9219689c..525e64f925 100644 --- a/eth/vm/forks/spurious_dragon/computation.py +++ b/eth/vm/forks/spurious_dragon/computation.py @@ -4,10 +4,10 @@ ) from eth import constants +from eth.abc import ComputationAPI from eth.exceptions import ( OutOfGas, ) -from eth.vm.computation import BaseComputation from eth.vm.forks.homestead.computation import ( HomesteadComputation, ) @@ -24,7 +24,7 @@ class SpuriousDragonComputation(HomesteadComputation): # Override opcodes = SPURIOUS_DRAGON_OPCODES - def apply_create_message(self) -> BaseComputation: + def apply_create_message(self) -> ComputationAPI: snapshot = self.state.snapshot() # EIP161 nonce incrementation @@ -39,7 +39,7 @@ def apply_create_message(self) -> BaseComputation: contract_code = computation.output if contract_code and len(contract_code) >= EIP170_CODE_SIZE_LIMIT: - computation._error = OutOfGas( + computation.error = OutOfGas( "Contract code size exceeds EIP170 limit of {0}. Got code of " "size: {1}".format( EIP170_CODE_SIZE_LIMIT, @@ -57,7 +57,7 @@ def apply_create_message(self) -> BaseComputation: except OutOfGas as err: # Different from Frontier: reverts state on gas failure while # writing contract code. - computation._error = err + computation.error = err self.state.revert(snapshot) else: if self.logger: diff --git a/eth/vm/forks/spurious_dragon/opcodes.py b/eth/vm/forks/spurious_dragon/opcodes.py index 0a1ebae428..eed5820eda 100644 --- a/eth/vm/forks/spurious_dragon/opcodes.py +++ b/eth/vm/forks/spurious_dragon/opcodes.py @@ -1,7 +1,9 @@ import copy +from typing import Dict from eth_utils.toolz import merge +from eth.abc import OpcodeAPI from eth.vm.forks.tangerine_whistle.constants import ( GAS_SELFDESTRUCT_EIP150, GAS_CALL_EIP150 @@ -41,7 +43,7 @@ } -SPURIOUS_DRAGON_OPCODES = merge( +SPURIOUS_DRAGON_OPCODES: Dict[int, OpcodeAPI] = merge( copy.deepcopy(TANGERINE_WHISTLE_OPCODES), UPDATED_OPCODES, ) diff --git a/eth/vm/forks/spurious_dragon/state.py b/eth/vm/forks/spurious_dragon/state.py index 01d7d538f2..8360eb7e31 100644 --- a/eth/vm/forks/spurious_dragon/state.py +++ b/eth/vm/forks/spurious_dragon/state.py @@ -1,13 +1,14 @@ +from typing import Type + from eth_utils import ( encode_hex, ) -from eth.typing import ( - BaseOrSpoofTransaction, +from eth.abc import ( + ComputationAPI, + SignedTransactionAPI, + TransactionExecutorAPI, ) - -from eth.vm.computation import BaseComputation - from eth.vm.forks.homestead.state import ( HomesteadState, HomesteadTransactionExecutor, @@ -19,8 +20,8 @@ class SpuriousDragonTransactionExecutor(HomesteadTransactionExecutor): def finalize_computation(self, - transaction: BaseOrSpoofTransaction, - computation: BaseComputation) -> BaseComputation: + transaction: SignedTransactionAPI, + computation: ComputationAPI) -> ComputationAPI: computation = super().finalize_computation(transaction, computation) # @@ -44,5 +45,5 @@ def finalize_computation(self, class SpuriousDragonState(HomesteadState): - computation_class = SpuriousDragonComputation - transaction_executor = SpuriousDragonTransactionExecutor # Type[BaseTransactionExecutor] + computation_class: Type[ComputationAPI] = SpuriousDragonComputation + transaction_executor_class: Type[TransactionExecutorAPI] = SpuriousDragonTransactionExecutor diff --git a/eth/vm/forks/tangerine_whistle/__init__.py b/eth/vm/forks/tangerine_whistle/__init__.py index aae5b3c061..8b0f112415 100644 --- a/eth/vm/forks/tangerine_whistle/__init__.py +++ b/eth/vm/forks/tangerine_whistle/__init__.py @@ -1,6 +1,6 @@ from typing import Type -from eth.vm.state import BaseState +from eth.abc import StateAPI from eth.vm.forks.homestead import HomesteadVM from .state import TangerineWhistleState @@ -11,7 +11,7 @@ class TangerineWhistleVM(HomesteadVM): fork: str = 'tangerine-whistle' # noqa # classes - _state_class: Type[BaseState] = TangerineWhistleState + _state_class: Type[StateAPI] = TangerineWhistleState # Don't bother with any DAO logic in Tangerine VM or later # This is how we skip DAO logic on Ropsten, for example diff --git a/eth/vm/gas_meter.py b/eth/vm/gas_meter.py index 0c702b8246..ffa9044098 100644 --- a/eth/vm/gas_meter.py +++ b/eth/vm/gas_meter.py @@ -6,6 +6,8 @@ from eth_utils import ( ValidationError, ) + +from eth.abc import GasMeterAPI from eth.exceptions import ( OutOfGas, ) @@ -31,7 +33,7 @@ def allow_negative_refund_strategy(gas_refunded_total: int, amount: int) -> int: RefundStrategy = Callable[[int, int], int] -class GasMeter(object): +class GasMeter(GasMeterAPI): start_gas: int = None diff --git a/eth/vm/logic/call.py b/eth/vm/logic/call.py index 141f398360..ebec7ec120 100644 --- a/eth/vm/logic/call.py +++ b/eth/vm/logic/call.py @@ -13,6 +13,9 @@ Address, ) +from eth.abc import ( + ComputationAPI, +) from eth.exceptions import ( OutOfGas, WriteProtection, @@ -21,8 +24,6 @@ Opcode, ) -from eth.vm.computation import BaseComputation - from eth._utils.address import ( force_bytes_to_address, ) @@ -34,18 +35,18 @@ class BaseCall(Opcode, ABC): @abstractmethod def compute_msg_extra_gas(self, - computation: BaseComputation, + computation: ComputationAPI, gas: int, to: Address, value: int) -> int: raise NotImplementedError("Must be implemented by subclasses") @abstractmethod - def get_call_params(self, computation: BaseComputation) -> CallParams: + def get_call_params(self, computation: ComputationAPI) -> CallParams: raise NotImplementedError("Must be implemented by subclasses") def compute_msg_gas(self, - computation: BaseComputation, + computation: ComputationAPI, gas: int, to: Address, value: int) -> Tuple[int, int]: @@ -54,7 +55,7 @@ def compute_msg_gas(self, child_msg_gas = gas + (constants.GAS_CALLSTIPEND if value else 0) return child_msg_gas, total_fee - def __call__(self, computation: BaseComputation) -> None: + def __call__(self, computation: ComputationAPI) -> None: computation.consume_gas( self.gas_cost, reason=self.mnemonic, @@ -155,7 +156,7 @@ def __call__(self, computation: BaseComputation) -> None: class Call(BaseCall): def compute_msg_extra_gas(self, - computation: BaseComputation, + computation: ComputationAPI, gas: int, to: Address, value: int) -> int: @@ -165,7 +166,7 @@ def compute_msg_extra_gas(self, create_gas_fee = constants.GAS_NEWACCOUNT if not account_exists else 0 return transfer_gas_fee + create_gas_fee - def get_call_params(self, computation: BaseComputation) -> CallParams: + def get_call_params(self, computation: ComputationAPI) -> CallParams: gas = computation.stack_pop1_int() to = force_bytes_to_address(computation.stack_pop1_bytes()) @@ -194,13 +195,13 @@ def get_call_params(self, computation: BaseComputation) -> CallParams: class CallCode(BaseCall): def compute_msg_extra_gas(self, - computation: BaseComputation, + computation: ComputationAPI, gas: int, to: Address, value: int) -> int: return constants.GAS_CALLVALUE if value else 0 - def get_call_params(self, computation: BaseComputation) -> CallParams: + def get_call_params(self, computation: ComputationAPI) -> CallParams: gas = computation.stack_pop1_int() code_address = force_bytes_to_address(computation.stack_pop1_bytes()) @@ -232,20 +233,20 @@ def get_call_params(self, computation: BaseComputation) -> CallParams: class DelegateCall(BaseCall): def compute_msg_gas(self, - computation: BaseComputation, + computation: ComputationAPI, gas: int, to: Address, value: int) -> Tuple[int, int]: return gas, gas def compute_msg_extra_gas(self, - computation: BaseComputation, + computation: ComputationAPI, gas: int, to: Address, value: int) -> int: return 0 - def get_call_params(self, computation: BaseComputation) -> CallParams: + def get_call_params(self, computation: ComputationAPI) -> CallParams: gas = computation.stack_pop1_int() code_address = force_bytes_to_address(computation.stack_pop1_bytes()) @@ -280,7 +281,7 @@ def get_call_params(self, computation: BaseComputation) -> CallParams: # class CallEIP150(Call): def compute_msg_gas(self, - computation: BaseComputation, + computation: ComputationAPI, gas: int, to: Address, value: int) -> Tuple[int, int]: @@ -297,7 +298,7 @@ def compute_msg_gas(self, class CallCodeEIP150(CallCode): def compute_msg_gas(self, - computation: BaseComputation, + computation: ComputationAPI, gas: int, to: Address, value: int) -> Tuple[int, int]: @@ -314,7 +315,7 @@ def compute_msg_gas(self, class DelegateCallEIP150(DelegateCall): def compute_msg_gas(self, - computation: BaseComputation, + computation: ComputationAPI, gas: int, to: Address, value: int) -> Tuple[int, int]: @@ -335,7 +336,7 @@ def max_child_gas_eip150(gas: int) -> int: def compute_eip150_msg_gas(*, - computation: BaseComputation, + computation: ComputationAPI, gas: int, extra_gas: int, value: int, @@ -362,7 +363,7 @@ def compute_eip150_msg_gas(*, # class CallEIP161(CallEIP150): def compute_msg_extra_gas(self, - computation: BaseComputation, + computation: ComputationAPI, gas: int, to: Address, value: int) -> int: @@ -380,7 +381,7 @@ def compute_msg_extra_gas(self, # Byzantium # class StaticCall(CallEIP161): - def get_call_params(self, computation: BaseComputation) -> CallParams: + def get_call_params(self, computation: ComputationAPI) -> CallParams: gas = computation.stack_pop1_int() to = force_bytes_to_address(computation.stack_pop1_bytes()) @@ -407,7 +408,7 @@ def get_call_params(self, computation: BaseComputation) -> CallParams: class CallByzantium(CallEIP161): - def get_call_params(self, computation: BaseComputation) -> CallParams: + def get_call_params(self, computation: ComputationAPI) -> CallParams: call_params = super().get_call_params(computation) value = call_params[1] if computation.msg.is_static and value != 0: diff --git a/eth/vm/logic/invalid.py b/eth/vm/logic/invalid.py index 34a2c944d9..48ac553562 100644 --- a/eth/vm/logic/invalid.py +++ b/eth/vm/logic/invalid.py @@ -1,13 +1,7 @@ -from typing import ( - TYPE_CHECKING, -) - +from eth.abc import ComputationAPI from eth.exceptions import InvalidInstruction from eth.vm.opcode import Opcode -if TYPE_CHECKING: - from eth.vm.computation import BaseComputation # noqa: F401 - class InvalidOpcode(Opcode): mnemonic = "INVALID" @@ -17,7 +11,7 @@ def __init__(self, value: int) -> None: self.value = value super().__init__() - def __call__(self, computation: 'BaseComputation') -> None: + def __call__(self, computation: ComputationAPI) -> None: raise InvalidInstruction("Invalid opcode 0x{0:x} @ {1}".format( self.value, computation.code.pc - 1, diff --git a/eth/vm/logic/system.py b/eth/vm/logic/system.py index fa4b2fddf0..dac56117f2 100644 --- a/eth/vm/logic/system.py +++ b/eth/vm/logic/system.py @@ -19,15 +19,17 @@ from eth._utils.numeric import ( ceil32, ) +from eth.abc import ( + ComputationAPI, + MessageAPI, +) from eth.vm import mnemonics -from eth.vm.computation import BaseComputation -from eth.vm.message import Message from eth.vm.opcode import Opcode from .call import max_child_gas_eip150 -def return_op(computation: BaseComputation) -> None: +def return_op(computation: ComputationAPI) -> None: start_position, size = computation.stack_pop_ints(2) computation.extend_memory(start_position, size) @@ -36,7 +38,7 @@ def return_op(computation: BaseComputation) -> None: raise Halt('RETURN') -def revert(computation: BaseComputation) -> None: +def revert(computation: ComputationAPI) -> None: start_position, size = computation.stack_pop_ints(2) computation.extend_memory(start_position, size) @@ -45,12 +47,12 @@ def revert(computation: BaseComputation) -> None: raise Revert(computation.output) -def selfdestruct(computation: BaseComputation) -> None: +def selfdestruct(computation: ComputationAPI) -> None: beneficiary = force_bytes_to_address(computation.stack_pop1_bytes()) _selfdestruct(computation, beneficiary) -def selfdestruct_eip150(computation: BaseComputation) -> None: +def selfdestruct_eip150(computation: ComputationAPI) -> None: beneficiary = force_bytes_to_address(computation.stack_pop1_bytes()) if not computation.state.account_exists(beneficiary): computation.consume_gas( @@ -60,7 +62,7 @@ def selfdestruct_eip150(computation: BaseComputation) -> None: _selfdestruct(computation, beneficiary) -def selfdestruct_eip161(computation: BaseComputation) -> None: +def selfdestruct_eip161(computation: ComputationAPI) -> None: beneficiary = force_bytes_to_address(computation.stack_pop1_bytes()) is_dead = ( not computation.state.account_exists(beneficiary) or @@ -74,7 +76,7 @@ def selfdestruct_eip161(computation: BaseComputation) -> None: _selfdestruct(computation, beneficiary) -def _selfdestruct(computation: BaseComputation, beneficiary: Address) -> None: +def _selfdestruct(computation: ComputationAPI, beneficiary: Address) -> None: local_balance = computation.state.get_balance(computation.msg.storage_address) beneficiary_balance = computation.state.get_balance(beneficiary) @@ -122,7 +124,7 @@ def get_gas_cost(self, data: CreateOpcodeStackData) -> int: def generate_contract_address(self, stack_data: CreateOpcodeStackData, call_data: bytes, - computation: BaseComputation) -> Address: + computation: ComputationAPI) -> Address: creation_nonce = computation.state.get_nonce(computation.msg.storage_address) computation.state.increment_nonce(computation.msg.storage_address) @@ -134,12 +136,12 @@ def generate_contract_address(self, return contract_address - def get_stack_data(self, computation: BaseComputation) -> CreateOpcodeStackData: + def get_stack_data(self, computation: ComputationAPI) -> CreateOpcodeStackData: endowment, memory_start, memory_length = computation.stack_pop_ints(3) return CreateOpcodeStackData(endowment, memory_start, memory_length) - def __call__(self, computation: BaseComputation) -> None: + def __call__(self, computation: ComputationAPI) -> None: stack_data = self.get_stack_data(computation) @@ -188,7 +190,7 @@ def __call__(self, computation: BaseComputation) -> None: ) self.apply_create_message(computation, child_msg) - def apply_create_message(self, computation: BaseComputation, child_msg: Message) -> None: + def apply_create_message(self, computation: ComputationAPI, child_msg: MessageAPI) -> None: child_computation = computation.apply_child_computation(child_msg) if child_computation.is_error: @@ -205,7 +207,7 @@ def max_child_gas_modifier(self, gas: int) -> int: class CreateByzantium(CreateEIP150): - def __call__(self, computation: BaseComputation) -> None: + def __call__(self, computation: ComputationAPI) -> None: if computation.msg.is_static: raise WriteProtection("Cannot modify state while inside of a STATICCALL context") return super().__call__(computation) @@ -213,7 +215,7 @@ def __call__(self, computation: BaseComputation) -> None: class Create2(CreateByzantium): - def get_stack_data(self, computation: BaseComputation) -> CreateOpcodeStackData: + def get_stack_data(self, computation: ComputationAPI) -> CreateOpcodeStackData: endowment, memory_start, memory_length, salt = computation.stack_pop_ints(4) return CreateOpcodeStackData(endowment, memory_start, memory_length, salt) @@ -224,7 +226,7 @@ def get_gas_cost(self, data: CreateOpcodeStackData) -> int: def generate_contract_address(self, stack_data: CreateOpcodeStackData, call_data: bytes, - computation: BaseComputation) -> Address: + computation: ComputationAPI) -> Address: computation.state.increment_nonce(computation.msg.storage_address) return generate_safe_contract_address( @@ -233,7 +235,7 @@ def generate_contract_address(self, call_data ) - def apply_create_message(self, computation: BaseComputation, child_msg: Message) -> None: + def apply_create_message(self, computation: ComputationAPI, child_msg: MessageAPI) -> None: # We need to ensure that creation operates on empty storage **and** # that if the initialization code fails that we revert the account back # to its original state root. diff --git a/eth/vm/memory.py b/eth/vm/memory.py index 759d10e3f7..262ff258d0 100644 --- a/eth/vm/memory.py +++ b/eth/vm/memory.py @@ -11,9 +11,10 @@ from eth._utils.numeric import ( ceil32, ) +from eth.abc import MemoryAPI -class Memory(object): +class Memory(MemoryAPI): """ VM Memory """ diff --git a/eth/vm/message.py b/eth/vm/message.py index 25289756cf..319e3360dd 100644 --- a/eth/vm/message.py +++ b/eth/vm/message.py @@ -2,6 +2,7 @@ from eth_typing import Address +from eth.abc import MessageAPI from eth.constants import ( CREATE_CONTRACT_ADDRESS, ) @@ -19,7 +20,7 @@ ) -class Message(object): +class Message(MessageAPI): """ A message for VM computation. """ diff --git a/eth/vm/opcode.py b/eth/vm/opcode.py index ce6bd77be3..88d62774d0 100644 --- a/eth/vm/opcode.py +++ b/eth/vm/opcode.py @@ -1,31 +1,27 @@ import functools import logging -from abc import ( - ABC, - abstractmethod -) - from typing import ( Any, Callable, cast, Type, TypeVar, - TYPE_CHECKING, ) from eth.tools.logging import ExtendedDebugLogger from eth._utils.datatypes import Configurable +from eth.abc import ( + ComputationAPI, + OpcodeAPI, +) -if TYPE_CHECKING: - from computation import BaseComputation # noqa: F401 T = TypeVar('T') -class Opcode(Configurable, ABC): +class Opcode(Configurable, OpcodeAPI): mnemonic: str = None gas_cost: int = None @@ -35,13 +31,6 @@ def __init__(self) -> None: if self.gas_cost is None: raise TypeError("Opcode class {0} missing opcode gas_cost".format(type(self))) - @abstractmethod - def __call__(self, computation: 'BaseComputation') -> Any: - """ - Hook for performing the actual VM execution. - """ - raise NotImplementedError("Must be implemented by subclasses") - @property def logger(self) -> ExtendedDebugLogger: logger_obj = logging.getLogger('eth.vm.logic.{0}'.format(self.mnemonic)) @@ -51,13 +40,13 @@ def logger(self) -> ExtendedDebugLogger: def as_opcode(cls: Type[T], logic_fn: Callable[..., Any], mnemonic: str, - gas_cost: int) -> Type[T]: + gas_cost: int) -> T: """ Class factory method for turning vanilla functions into Opcode classes. """ if gas_cost: @functools.wraps(logic_fn) - def wrapped_logic_fn(computation: 'BaseComputation') -> Any: + def wrapped_logic_fn(computation: ComputationAPI) -> Any: """ Wrapper functionf or the logic function which consumes the base opcode gas cost prior to execution. diff --git a/eth/vm/spoof.py b/eth/vm/spoof.py index 9cf6268b47..1c81eec130 100644 --- a/eth/vm/spoof.py +++ b/eth/vm/spoof.py @@ -1,9 +1,11 @@ -from typing import Any +from typing import Any, Union -from eth.rlp.transactions import BaseTransaction +from eth.abc import SignedTransactionAPI, UnsignedTransactionAPI from eth._utils.spoof import SpoofAttributes class SpoofTransaction(SpoofAttributes): - def __init__(self, transaction: BaseTransaction, **overrides: Any) -> None: + def __init__(self, + transaction: Union[SignedTransactionAPI, UnsignedTransactionAPI], + **overrides: Any) -> None: super().__init__(transaction, **overrides) diff --git a/eth/vm/stack.py b/eth/vm/stack.py index 2d78c269d5..b0cdb31a49 100644 --- a/eth/vm/stack.py +++ b/eth/vm/stack.py @@ -20,6 +20,8 @@ Union ) +from eth.abc import StackAPI + def _busted_type(item_type: type, value: Union[int, bytes]) -> ValidationError: return ValidationError( @@ -30,7 +32,7 @@ def _busted_type(item_type: type, value: Union[int, bytes]) -> ValidationError: ) -class Stack(object): +class Stack(StackAPI): """ VM Stack """ diff --git a/eth/vm/state.py b/eth/vm/state.py index d3600cd403..9a22ec408f 100644 --- a/eth/vm/state.py +++ b/eth/vm/state.py @@ -1,7 +1,3 @@ -from abc import ( - ABC, - abstractmethod -) import contextlib import logging from typing import ( @@ -9,7 +5,6 @@ Iterator, Tuple, Type, - TYPE_CHECKING, ) from uuid import UUID @@ -19,57 +14,44 @@ ) from eth_utils.toolz import nth +from eth.abc import ( + AccountDatabaseAPI, + AtomicDatabaseAPI, + ComputationAPI, + ExecutionContextAPI, + MessageAPI, + SignedTransactionAPI, + StateAPI, + TransactionContextAPI, + TransactionExecutorAPI, +) from eth.constants import ( BLANK_ROOT_HASH, MAX_PREV_HEADER_DEPTH, ) -from eth.db.account import ( - BaseAccountDB, -) -from eth.db.backends.base import ( - BaseAtomicDB, -) from eth.exceptions import StateRootNotFound from eth.tools.logging import ( ExtendedDebugLogger, ) -from eth.typing import ( - BaseOrSpoofTransaction, -) from eth._utils.datatypes import ( Configurable, ) -from eth.vm.execution_context import ( - ExecutionContext, -) -from eth.vm.message import Message - -if TYPE_CHECKING: - from eth.computation import ( # noqa: F401 - BaseComputation, - ) - from eth.rlp.transactions import ( # noqa: F401 - BaseTransaction, - ) - from eth.vm.transaction_context import ( # noqa: F401 - BaseTransactionContext, - ) -class BaseState(Configurable, ABC): +class BaseState(Configurable, StateAPI): """ The base class that encapsulates all of the various moving parts related to the state of the VM during execution. - Each :class:`~eth.vm.base.BaseVM` must be configured with a subclass of the - :class:`~eth.vm.state.BaseState`. + Each :class:`~eth.abc.VirtualMachineAPI` must be configured with a subclass of the + :class:`~eth.abc.StateAPI`. .. note:: - Each :class:`~eth.vm.state.BaseState` class must be configured with: + Each :class:`~eth.abc.StateAPI` class must be configured with: - - ``computation_class``: The :class:`~eth.vm.computation.BaseComputation` class for + - ``computation_class``: The :class:`~eth.abc.ComputationAPI` class for vm execution. - - ``transaction_context_class``: The :class:`~eth.vm.transaction_context.TransactionContext` + - ``transaction_context_class``: The :class:`~eth.abc.TransactionContextAPI` class for vm execution. """ # @@ -77,15 +59,15 @@ class for vm execution. # __slots__ = ['_db', 'execution_context', '_account_db'] - computation_class: Type['BaseComputation'] = None - transaction_context_class: Type['BaseTransactionContext'] = None - account_db_class: Type['BaseAccountDB'] = None - transaction_executor: Type['BaseTransactionExecutor'] = None + computation_class: Type[ComputationAPI] = None + transaction_context_class: Type[TransactionContextAPI] = None + account_db_class: Type[AccountDatabaseAPI] = None + transaction_executor_class: Type[TransactionExecutorAPI] = None def __init__( self, - db: BaseAtomicDB, - execution_context: ExecutionContext, + db: AtomicDatabaseAPI, + execution_context: ExecutionContextAPI, state_root: bytes) -> None: self._db = db self.execution_context = execution_context @@ -142,9 +124,9 @@ def gas_limit(self) -> int: # Access to account db # @classmethod - def get_account_db_class(cls) -> Type[BaseAccountDB]: + def get_account_db_class(cls) -> Type[AccountDatabaseAPI]: """ - Return the :class:`~eth.db.account.BaseAccountDB` class that the + Return the :class:`~eth.abc.AccountDatabaseAPI` class that the state class uses. """ if cls.account_db_class is None: @@ -277,8 +259,8 @@ def get_ancestor_hash(self, block_number: int) -> Hash32: # Computation # def get_computation(self, - message: Message, - transaction_context: 'BaseTransactionContext') -> 'BaseComputation': + message: MessageAPI, + transaction_context: TransactionContextAPI) -> ComputationAPI: """ Return a computation instance for the given `message` and `transaction_context` """ @@ -292,7 +274,7 @@ def get_computation(self, # Transaction context # @classmethod - def get_transaction_context_class(cls) -> Type['BaseTransactionContext']: + def get_transaction_context_class(cls) -> Type[TransactionContextAPI]: """ Return the :class:`~eth.vm.transaction_context.BaseTransactionContext` class that the state class uses. @@ -306,7 +288,7 @@ def get_transaction_context_class(cls) -> Type['BaseTransactionContext']: # def apply_transaction( self, - transaction: BaseOrSpoofTransaction) -> 'BaseComputation': + transaction: SignedTransactionAPI) -> ComputationAPI: """ Apply transaction to the vm state @@ -318,11 +300,11 @@ def apply_transaction( else: return self.execute_transaction(transaction) - def get_transaction_executor(self) -> 'BaseTransactionExecutor': - return self.transaction_executor(self) + def get_transaction_executor(self) -> TransactionExecutorAPI: + return self.transaction_executor_class(self) def costless_execute_transaction(self, - transaction: BaseOrSpoofTransaction) -> 'BaseComputation': + transaction: SignedTransactionAPI) -> ComputationAPI: with self.override_transaction_context(gas_price=transaction.gas_price): free_transaction = transaction.copy(gas_price=0) return self.execute_transaction(free_transaction) @@ -331,60 +313,33 @@ def costless_execute_transaction(self, def override_transaction_context(self, gas_price: int) -> Iterator[None]: original_context = self.get_transaction_context - def get_custom_transaction_context(transaction: 'BaseTransaction') -> 'BaseTransactionContext': # noqa: E501 + def get_custom_transaction_context(transaction: SignedTransactionAPI) -> TransactionContextAPI: # noqa: E501 custom_transaction = transaction.copy(gas_price=gas_price) return original_context(custom_transaction) - self.get_transaction_context = get_custom_transaction_context + # mypy doesn't like assigning to an existing method + self.get_transaction_context = get_custom_transaction_context # type: ignore try: yield finally: self.get_transaction_context = original_context # type: ignore # Remove ignore if https://github.com/python/mypy/issues/708 is fixed. # noqa: E501 - @abstractmethod - def execute_transaction(self, transaction: BaseOrSpoofTransaction) -> 'BaseComputation': - raise NotImplementedError() - - @abstractmethod - def validate_transaction(self, transaction: BaseOrSpoofTransaction) -> None: - raise NotImplementedError - @classmethod def get_transaction_context(cls, - transaction: BaseOrSpoofTransaction) -> 'BaseTransactionContext': + transaction: SignedTransactionAPI) -> TransactionContextAPI: return cls.get_transaction_context_class()( gas_price=transaction.gas_price, origin=transaction.sender, ) -class BaseTransactionExecutor(ABC): - def __init__(self, vm_state: BaseState) -> None: +class BaseTransactionExecutor(TransactionExecutorAPI): + def __init__(self, vm_state: StateAPI) -> None: self.vm_state = vm_state - def __call__(self, transaction: BaseOrSpoofTransaction) -> 'BaseComputation': - valid_transaction = self.validate_transaction(transaction) - message = self.build_evm_message(valid_transaction) - computation = self.build_computation(message, valid_transaction) - finalized_computation = self.finalize_computation(valid_transaction, computation) + def __call__(self, transaction: SignedTransactionAPI) -> ComputationAPI: + self.validate_transaction(transaction) + message = self.build_evm_message(transaction) + computation = self.build_computation(message, transaction) + finalized_computation = self.finalize_computation(transaction, computation) return finalized_computation - - @abstractmethod - def validate_transaction(self, transaction: BaseOrSpoofTransaction) -> BaseOrSpoofTransaction: - raise NotImplementedError - - @abstractmethod - def build_evm_message(self, transaction: BaseOrSpoofTransaction) -> Message: - raise NotImplementedError() - - @abstractmethod - def build_computation(self, - message: Message, - transaction: BaseOrSpoofTransaction) -> 'BaseComputation': - raise NotImplementedError() - - @abstractmethod - def finalize_computation(self, - transaction: BaseOrSpoofTransaction, - computation: 'BaseComputation') -> 'BaseComputation': - raise NotImplementedError() diff --git a/eth/vm/transaction_context.py b/eth/vm/transaction_context.py index d8e0c56bd0..24c1d0c347 100644 --- a/eth/vm/transaction_context.py +++ b/eth/vm/transaction_context.py @@ -2,13 +2,14 @@ from eth_typing import Address +from eth.abc import TransactionContextAPI from eth.validation import ( validate_canonical_address, validate_uint256, ) -class BaseTransactionContext: +class BaseTransactionContext(TransactionContextAPI): """ This immutable object houses information that remains constant for the entire context of the VM execution. diff --git a/scripts/benchmark/_utils/chain_plumbing.py b/scripts/benchmark/_utils/chain_plumbing.py index 6ac1ea2c6f..692c68425c 100644 --- a/scripts/benchmark/_utils/chain_plumbing.py +++ b/scripts/benchmark/_utils/chain_plumbing.py @@ -26,15 +26,13 @@ from eth import ( constants, ) +from eth.abc import VirtualMachineAPI from eth.chains.base import ( MiningChain, ) from eth.db.backends.level import ( LevelDB, ) -from eth.vm.base import ( - BaseVM, -) from eth.chains.mainnet import ( BaseMainnetChain, ) @@ -88,7 +86,7 @@ GenesisState = Iterable[Tuple[Address, Dict[str, Any]]] -def get_chain(vm: Type[BaseVM], genesis_state: GenesisState) -> Iterable[MiningChain]: +def get_chain(vm: Type[VirtualMachineAPI], genesis_state: GenesisState) -> Iterable[MiningChain]: with tempfile.TemporaryDirectory() as temp_dir: level_db_obj = LevelDB(Path(temp_dir))