diff --git a/eth/beacon/aggregation.py b/eth/beacon/aggregation.py index f014fac7ad..6f9b62fd39 100644 --- a/eth/beacon/aggregation.py +++ b/eth/beacon/aggregation.py @@ -6,7 +6,6 @@ pipe ) - from eth_typing import ( Hash32, ) @@ -62,7 +61,7 @@ def verify_votes( def aggregate_votes(bitfield: bytes, sigs: Iterable[bytes], voting_sigs: Iterable[bytes], - voting_committee_indices: Iterable[int]) -> Tuple[bytes, Tuple[int]]: + voting_committee_indices: Iterable[int]) -> Tuple[bytes, Tuple[int, int]]: """ Aggregate the votes. """ diff --git a/eth/chains/base.py b/eth/chains/base.py index a184b44de2..3768670f1c 100644 --- a/eth/chains/base.py +++ b/eth/chains/base.py @@ -23,6 +23,10 @@ import logging +from mypy_extensions import ( + TypedDict, +) + from eth_typing import ( Address, BlockNumber, @@ -105,11 +109,16 @@ from eth.vm.base import BaseVM # noqa: F401 -# Mapping from address to account state. # 'balance', 'nonce' -> int # 'code' -> bytes # 'storage' -> Dict[int, int] -AccountState = Dict[Address, Dict[str, Union[int, bytes, Dict[int, int]]]] +AccountDetails = TypedDict('AccountDetails', + {'balance': int, + 'nonce': int, + 'code': bytes, + 'storage': Dict[int, int] + }) +AccountState = Dict[Address, AccountDetails] class BaseChain(Configurable, ABC): diff --git a/eth/estimators/__init__.py b/eth/estimators/__init__.py index 6493ee2368..a1d077d231 100644 --- a/eth/estimators/__init__.py +++ b/eth/estimators/__init__.py @@ -1,12 +1,14 @@ import os -from typing import Callable +from typing import ( + Any, +) from eth.utils.module_loading import ( import_string, ) -def get_gas_estimator() -> Callable: +def get_gas_estimator() -> Any: import_path = os.environ.get( 'GAS_ESTIMATOR_BACKEND_FUNC', 'eth.estimators.gas.binary_gas_search_intrinsic_tolerance', diff --git a/eth/tools/builder/chain/builders.py b/eth/tools/builder/chain/builders.py index 9a6fc97807..9510491d6b 100644 --- a/eth/tools/builder/chain/builders.py +++ b/eth/tools/builder/chain/builders.py @@ -1,6 +1,15 @@ import functools import time -from typing import TYPE_CHECKING +from typing import ( + Any, + Callable, + Dict, + Iterable, + List, + Tuple, + Type, + Union, +) from cytoolz import ( curry, @@ -8,6 +17,15 @@ pipe, ) +from mypy_extensions import ( + TypedDict, +) + +from eth_typing import ( + Address, + BlockNumber, +) + from eth_utils import ( to_dict, to_tuple, @@ -19,10 +37,19 @@ BaseChain, MiningChain, ) -from eth.db.backends.memory import MemoryDB from eth.db.atomic import AtomicDB -from eth.validation import ( - validate_vm_configuration, +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 from eth.tools._utils.mappings import ( @@ -31,6 +58,12 @@ from eth.tools._utils.normalization import ( normalize_state, ) +from eth.validation import ( + validate_vm_configuration, +) +from eth.vm.base import ( + BaseVM, +) from eth.vm.forks import ( FrontierVM, HomesteadVM, @@ -40,11 +73,24 @@ ConstantinopleVM, ) -if TYPE_CHECKING: - from typing import Dict, Union # noqa: F401 +VMConfigurationType = Iterable[Tuple[int, Type[BaseVM]]] -def build(obj, *applicators): + +# 'balance', 'nonce' -> int +# 'code' -> bytes +# 'storage' -> Dict[int, int] +AccountDetails = TypedDict('AccountDetails', + {'balance': int, + 'nonce': int, + 'code': bytes, + 'storage': Dict[int, int] + }) +AccountState = Dict[Address, AccountDetails] +GeneralStateType = Union[AccountState, List[Tuple[Address, Dict[str, Union[int, bytes, Dict[int, int]]]]]] # noqa: E501 + + +def build(obj: Any, *applicators: Callable[..., Any]) -> Any: """ Run the provided object through the series of applicator functions. @@ -62,7 +108,7 @@ def build(obj, *applicators): # Constructors (creation of chain classes) # @curry -def name(class_name, chain_class): +def name(class_name: str, chain_class: BaseChain) -> type: """ Assign the given name to the chain class. """ @@ -70,7 +116,7 @@ def name(class_name, chain_class): @curry -def chain_id(chain_id, chain_class): +def chain_id(chain_id: int, chain_class: BaseChain) -> type: """ Set the ``chain_id`` for the chain class. """ @@ -78,7 +124,7 @@ def chain_id(chain_id, chain_class): @curry -def fork_at(vm_class, at_block, chain_class): +def fork_at(vm_class: Type[BaseVM], at_block: int, chain_class: BaseChain) -> type: """ Adds the ``vm_class`` to the chain's ``vm_configuration``. @@ -117,7 +163,7 @@ class FrontierOnlyChain(MiningChain): return chain_class.configure(vm_configuration=vm_configuration) -def _is_homestead(vm_class): +def _is_homestead(vm_class: Type[BaseVM]) -> bool: if not issubclass(vm_class, HomesteadVM): # It isn't a subclass of the HomesteadVM return False @@ -129,7 +175,7 @@ def _is_homestead(vm_class): @to_tuple -def _set_vm_dao_support_false(vm_configuration): +def _set_vm_dao_support_false(vm_configuration: VMConfigurationType) -> VMConfigurationType: for fork_block, vm_class in vm_configuration: if _is_homestead(vm_class): yield fork_block, vm_class.configure(support_dao_fork=False) @@ -138,7 +184,7 @@ def _set_vm_dao_support_false(vm_configuration): @curry -def disable_dao_fork(chain_class): +def disable_dao_fork(chain_class: BaseChain) -> type: """ Set the ``support_dao_fork`` flag to ``False`` on the :class:`~eth.vm.forks.homestead.HomesteadVM`. Requires that presence of @@ -156,7 +202,8 @@ def disable_dao_fork(chain_class): @to_tuple -def _set_vm_dao_fork_block_number(dao_fork_block_number, vm_configuration): +def _set_vm_dao_fork_block_number(dao_fork_block_number: int, + vm_configuration: VMConfigurationType) -> VMConfigurationType: for fork_block, vm_class in vm_configuration: if _is_homestead(vm_class): yield fork_block, vm_class.configure( @@ -168,7 +215,7 @@ def _set_vm_dao_fork_block_number(dao_fork_block_number, vm_configuration): @curry -def dao_fork_at(dao_fork_block_number, chain_class): +def dao_fork_at(dao_fork_block_number: int, chain_class: BaseChain) -> type: """ 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 @@ -214,7 +261,7 @@ def dao_fork_at(dao_fork_block_number, chain_class): @to_dict -def _get_default_genesis_params(genesis_state): +def _get_default_genesis_params(genesis_state: AccountState) -> Iterable[Tuple[str, object]]: for key, value in GENESIS_DEFAULTS: if key == 'state_root' and genesis_state: # leave out the `state_root` if a genesis state was specified @@ -225,14 +272,14 @@ def _get_default_genesis_params(genesis_state): @to_tuple -def _mix_in_pow_mining(vm_configuration): +def _mix_in_pow_mining(vm_configuration: VMConfigurationType) -> VMConfigurationType: for fork_block, vm_class in vm_configuration: vm_class_with_pow_mining = type(vm_class.__name__, (POWMiningMixin, vm_class), {}) yield fork_block, vm_class_with_pow_mining @curry -def enable_pow_mining(chain_class): +def enable_pow_mining(chain_class: BaseChain) -> type: """ Inject on demand generation of the proof of work mining seal on newly mined blocks into each of the chain's vms. @@ -246,18 +293,18 @@ def enable_pow_mining(chain_class): class NoChainSealValidationMixin: @classmethod - def validate_seal(cls, block): + def validate_seal(cls, block: BaseBlock) -> None: pass class NoVMSealValidationMixin: @classmethod - def validate_seal(cls, header): + def validate_seal(cls, header: BlockHeader) -> None: pass @to_tuple -def _mix_in_disable_seal_validation(vm_configuration): +def _mix_in_disable_seal_validation(vm_configuration: VMConfigurationType) -> VMConfigurationType: for fork_block, vm_class in vm_configuration: vm_class_without_seal_validation = type( vm_class.__name__, @@ -268,7 +315,7 @@ def _mix_in_disable_seal_validation(vm_configuration): @curry -def disable_pow_check(chain_class): +def disable_pow_check(chain_class: Type[BaseChain]) -> type: """ 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. @@ -296,7 +343,7 @@ def disable_pow_check(chain_class): # # Initializers (initialization of chain state and chain class instantiation) # -def _fill_and_normalize_state(simple_state): +def _fill_and_normalize_state(simple_state: GeneralStateType) -> AccountState: base_state = normalize_state(simple_state) defaults = {address: { "balance": 0, @@ -309,13 +356,16 @@ def _fill_and_normalize_state(simple_state): @curry -def genesis(chain_class, db=None, params=None, state=None): +def genesis(chain_class: BaseChain, + db: BaseAtomicDB=None, + params: Dict[str, HeaderParams]=None, + state: GeneralStateType=None) -> BaseChain: """ Initialize the given chain class with the given genesis header parameters and chain state. """ if state is None: - genesis_state = {} # type: Dict[str, Union[int, bytes, Dict[int, int]]] + genesis_state = {} # type: AccountState else: genesis_state = _fill_and_normalize_state(state) @@ -327,7 +377,7 @@ def genesis(chain_class, db=None, params=None, state=None): genesis_params = merge(genesis_params_defaults, params) if db is None: - base_db = AtomicDB() + base_db = AtomicDB() # type: BaseAtomicDB else: base_db = db @@ -338,7 +388,7 @@ def genesis(chain_class, db=None, params=None, state=None): # Builders (build actual block chain) # @curry -def mine_block(chain, **kwargs): +def mine_block(chain: MiningChain, **kwargs: Any) -> MiningChain: """ Mine a new block on the chain. Header parameters for the new block can be overridden using keyword arguments. @@ -351,7 +401,7 @@ def mine_block(chain, **kwargs): @curry -def mine_blocks(num_blocks, chain): +def mine_blocks(num_blocks: int, chain: MiningChain) -> MiningChain: """ Variadic argument version of :func:`~eth.tools.builder.chain.mine_block` """ @@ -363,7 +413,7 @@ def mine_blocks(num_blocks, chain): @curry -def import_block(block, chain): +def import_block(block: BaseBlock, chain: BaseChain) -> BaseChain: """ Import the provided ``block`` into the chain. """ @@ -371,20 +421,21 @@ def import_block(block, chain): return chain -def import_blocks(*blocks): +def import_blocks(*blocks: BaseBlock) -> Callable[[BaseChain], BaseChain]: """ Variadic argument version of :func:`~eth.tools.builder.chain.import_block` """ @functools.wraps(import_blocks) - def _import_blocks(chain): + def _import_blocks(chain: BaseChain) -> BaseChain: for block in blocks: chain.import_block(block) return chain + return _import_blocks @curry -def copy(chain): +def copy(chain: MiningChain) -> MiningChain: """ Make a copy of the chain at the given state. Actions performed on the resulting chain will not affect the original chain. @@ -404,14 +455,14 @@ def copy(chain): return chain_copy -def chain_split(*splits): +def chain_split(*splits: Iterable[Callable[..., Any]]) -> Callable[[BaseChain], Iterable[BaseChain]]: # noqa: E501 """ Construct and execute multiple concurrent forks of the chain. Any number of forks may be executed. For each fork, provide an iterable of commands. - Returns the resulting chian objects for each fork. + Returns the resulting chain objects for each fork. .. code-block:: python @@ -429,7 +480,7 @@ def chain_split(*splits): @functools.wraps(chain_split) @to_tuple - def _chain_split(chain): + def _chain_split(chain: BaseChain) -> Iterable[BaseChain]: for split_fns in splits: result = build( chain, @@ -441,7 +492,7 @@ def _chain_split(chain): @curry -def at_block_number(block_number, chain): +def at_block_number(block_number: BlockNumber, chain: MiningChain) -> MiningChain: """ 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, diff --git a/eth/tools/mining.py b/eth/tools/mining.py index 0ce6afc4fc..5988304c90 100644 --- a/eth/tools/mining.py +++ b/eth/tools/mining.py @@ -1,4 +1,10 @@ -from eth.consensus import pow +from eth.consensus import ( + pow, +) + +from eth.rlp.blocks import ( + BaseBlock, +) class POWMiningMixin: @@ -6,7 +12,7 @@ class POWMiningMixin: A VM that does POW mining as well. Should be used only in tests, when we need to programatically populate a ChainDB. """ - def finalize_block(self, block): + def finalize_block(self, block: BaseBlock) -> BaseBlock: block = super().finalize_block(block) # type: ignore nonce, mix_hash = pow.mine_pow_nonce( block.number, block.header.mining_hash, block.header.difficulty) diff --git a/eth/utils/blobs.py b/eth/utils/blobs.py index 061a4413c9..78f9a6a9e1 100644 --- a/eth/utils/blobs.py +++ b/eth/utils/blobs.py @@ -47,7 +47,7 @@ def calc_chunk_root(collation_body: bytes) -> Hash32: return calc_merkle_root(chunks) -def check_body_size(body): +def check_body_size(body: bytes) -> bytes: if len(body) != COLLATION_SIZE: raise ValidationError("{} byte collation body exceeds maximum allowed size".format( len(body) diff --git a/eth/utils/bls.py b/eth/utils/bls.py index 19679b13cc..e87ebcbba9 100644 --- a/eth/utils/bls.py +++ b/eth/utils/bls.py @@ -1,5 +1,7 @@ from typing import ( # noqa: F401 Dict, + Iterable, + Tuple, ) @@ -26,7 +28,7 @@ from eth.utils.blake import blake -CACHE = {} # type: Dict[int, int] +CACHE = {} # type: Dict[bytes, Tuple[FQ2, FQ2, FQ2]] # 16th root of unity HEX_ROOT = FQ2([21573744529824266246521972077326577680729363968861965890554801909984373949499, 16854739155576650954933913186877292401521110422362946064090026408937773542853]) @@ -36,12 +38,12 @@ assert HEX_ROOT ** 16 == FQ2([1, 0]) -def compress_G1(pt): +def compress_G1(pt: Tuple[FQ2, FQ2, FQ2]) -> int: x, y = normalize(pt) return x.n + 2**255 * (y.n % 2) -def decompress_G1(p): +def decompress_G1(p: int) -> Tuple[FQ, FQ, FQ]: if p == 0: return (FQ(1), FQ(1), FQ(0)) x = p % 2**255 @@ -53,14 +55,14 @@ def decompress_G1(p): return (FQ(x), FQ(y), FQ(1)) -def sqrt_fq2(x): +def sqrt_fq2(x: FQ2) -> FQ2: y = x ** ((field_modulus ** 2 + 15) // 32) while y**2 != x: y *= HEX_ROOT return y -def hash_to_G2(m): +def hash_to_G2(m: bytes) -> Tuple[FQ2, FQ2, FQ2]: """ WARNING: this function has not been standardized yet. """ @@ -82,13 +84,13 @@ def hash_to_G2(m): return o -def compress_G2(pt): +def compress_G2(pt: Tuple[FQ2, FQ2, FQ2]) -> Tuple[int, int]: assert is_on_curve(pt, b2) x, y = normalize(pt) return (x.coeffs[0] + 2**255 * (y.coeffs[0] % 2), x.coeffs[1]) -def decompress_G2(p): +def decompress_G2(p: bytes) -> Tuple[FQ2, FQ2, FQ2]: x1 = p[0] % 2**255 y1_mod_2 = p[0] // 2**255 x2 = p[1] @@ -102,15 +104,15 @@ def decompress_G2(p): return x, y, FQ2([1, 0]) -def sign(m, k): +def sign(m: bytes, k: int) -> Tuple[int, int]: return compress_G2(multiply(hash_to_G2(m), k)) -def privtopub(k): +def privtopub(k: int) -> int: return compress_G1(multiply(G1, k)) -def verify(m, pub, sig): +def verify(m: bytes, pub: int, sig: bytes) -> bool: final_exponentiation = final_exponentiate( pairing(decompress_G2(sig), G1, False) * pairing(hash_to_G2(m), neg(decompress_G1(pub)), False) @@ -118,14 +120,14 @@ def verify(m, pub, sig): return final_exponentiation == FQ12.one() -def aggregate_sigs(sigs): +def aggregate_sigs(sigs: Iterable[bytes]) -> Tuple[int, int]: o = Z2 for s in sigs: o = add(o, decompress_G2(s)) return compress_G2(o) -def aggregate_pubs(pubs): +def aggregate_pubs(pubs: Iterable[int]) -> int: o = Z1 for p in pubs: o = add(o, decompress_G1(p)) diff --git a/eth/utils/datatypes.py b/eth/utils/datatypes.py index f3a7074c7d..49b70dc5a6 100644 --- a/eth/utils/datatypes.py +++ b/eth/utils/datatypes.py @@ -67,8 +67,8 @@ class Configurable(object): """ @classmethod def configure(cls, - __name__=None, - **overrides): + __name__: str=None, + **overrides: Any) -> type: if __name__ is None: __name__ = cls.__name__ diff --git a/eth/utils/db.py b/eth/utils/db.py index 416b19a4f5..3262c0e5b0 100644 --- a/eth/utils/db.py +++ b/eth/utils/db.py @@ -1,10 +1,38 @@ -from eth.rlp.headers import BlockHeader +from typing import ( + Dict, + TYPE_CHECKING, +) -from typing import TYPE_CHECKING +from mypy_extensions import ( + TypedDict, +) + +from eth.db.account import ( + BaseAccountDB, +) + +from eth.rlp.headers import ( + BlockHeader, +) + +from eth_typing import ( + Address, +) if TYPE_CHECKING: from eth.db.chain import BaseChainDB # noqa: F401 +# 'balance', 'nonce' -> int +# 'code' -> bytes +# 'storage' -> Dict[int, int] +AccountDetails = TypedDict('AccountDetails', + {'balance': int, + 'nonce': int, + 'code': bytes, + 'storage': Dict[int, int] + }) +AccountState = Dict[Address, AccountDetails] + def get_parent_header(block_header: BlockHeader, db: 'BaseChainDB') -> BlockHeader: """ @@ -20,7 +48,8 @@ def get_block_header_by_hash(block_hash: BlockHeader, db: 'BaseChainDB') -> Bloc return db.get_block_header_by_hash(block_hash) -def apply_state_dict(account_db, state_dict): +def apply_state_dict(account_db: BaseAccountDB, state_dict: AccountState) -> BaseAccountDB: + for account, account_data in state_dict.items(): account_db.set_balance(account, account_data["balance"]) account_db.set_nonce(account, account_data["nonce"]) diff --git a/eth/utils/env.py b/eth/utils/env.py index 12beaa7216..49d0303531 100644 --- a/eth/utils/env.py +++ b/eth/utils/env.py @@ -7,13 +7,13 @@ from typing import ( # noqa: F401 Any, - Type, + Callable, + Dict, Iterable, List, - Union, + Type, TypeVar, - Dict, - Callable + Union, ) @@ -169,7 +169,7 @@ def env_string(name: str, required: bool=False, default: Union[Type[empty], str] def env_list(name: str, separator: str =',', required: bool=False, - default: Union[Type[empty], List]=empty) -> List: + default: Union[Type[empty], List[Any]]=empty) -> List[Any]: """Pulls an environment variable out of the environment, splitting it on a separator, and returning it as a list. Extra whitespace on the list values is stripped. List values that evaluate as falsy are removed. If not present @@ -237,7 +237,7 @@ def get(name: str, 'list': env_list, list: env_list, - } # type: Dict[Union[str, Type], Callable[..., Any]] + } # type: Dict[Union[str, Type[Any]], Callable[..., Any]] fn = fns.get(type, env_string) return fn(name, default=default, required=required) diff --git a/eth/utils/hexadecimal.py b/eth/utils/hexadecimal.py index 3629126f10..912ae4a67c 100644 --- a/eth/utils/hexadecimal.py +++ b/eth/utils/hexadecimal.py @@ -3,10 +3,10 @@ import codecs -def encode_hex(value): - return '0x' + codecs.decode(codecs.encode(value, 'hex'), 'utf8') +def encode_hex(value: bytes) -> str: + return '0x' + codecs.decode(codecs.encode(value, 'hex'), 'utf8') # type: ignore -def decode_hex(value): +def decode_hex(value: str) -> bytes: _, _, hex_part = value.rpartition('x') - return codecs.decode(hex_part, 'hex') + return codecs.decode(hex_part, 'hex') # type: ignore diff --git a/eth/utils/module_loading.py b/eth/utils/module_loading.py index 9079b5eac1..01e70c6269 100644 --- a/eth/utils/module_loading.py +++ b/eth/utils/module_loading.py @@ -1,8 +1,13 @@ import operator from importlib import import_module +from typing import ( + Any, + Tuple, +) -def import_string(dotted_path): + +def import_string(dotted_path: str) -> Any: """ Source: django.utils.module_loading Import a dotted module path and return the attribute/class designated by the @@ -24,7 +29,7 @@ def import_string(dotted_path): raise ImportError(msg) -def split_at_longest_importable_path(dotted_path): +def split_at_longest_importable_path(dotted_path: str) -> Tuple[str, str]: num_path_parts = len(dotted_path.split('.')) for i in range(1, num_path_parts): diff --git a/eth/utils/numeric.py b/eth/utils/numeric.py index d883fad187..1ae6de0529 100644 --- a/eth/utils/numeric.py +++ b/eth/utils/numeric.py @@ -1,5 +1,8 @@ import functools import itertools +from typing import ( + Union, +) from cytoolz import ( curry, @@ -12,7 +15,7 @@ ) -def int_to_bytes32(value): +def int_to_bytes32(value: Union[int, bool]) -> bytes: if not isinstance(value, int) or isinstance(value, bool): raise ValueError( "Value must be an integer: Got: {0}".format( @@ -47,14 +50,14 @@ def ceilXX(value: int, ceiling: int) -> int: ceil8 = functools.partial(ceilXX, ceiling=8) -def unsigned_to_signed(value): +def unsigned_to_signed(value: int) -> int: if value <= UINT_255_MAX: return value else: return value - UINT_256_CEILING -def signed_to_unsigned(value): +def signed_to_unsigned(value: int) -> int: if value < 0: return value + UINT_256_CEILING else: @@ -69,13 +72,15 @@ def is_odd(value: int) -> bool: return value % 2 == 1 -def get_highest_bit_index(value): +def get_highest_bit_index(value: int) -> int: value >>= 1 for bit_length in itertools.count(): if not value: return bit_length value >>= 1 + raise Exception("Invariant: unreachable code path") + @curry def clamp(inclusive_lower_bound: int, diff --git a/eth/utils/rlp.py b/eth/utils/rlp.py index 9e861bcc01..fc741ce64a 100644 --- a/eth/utils/rlp.py +++ b/eth/utils/rlp.py @@ -1,6 +1,11 @@ from __future__ import absolute_import import rlp +from typing import ( + Iterable, + Optional, + Tuple, +) from cytoolz import ( curry, @@ -11,9 +16,14 @@ ValidationError, ) +from eth.rlp.blocks import ( + BaseBlock, +) + @to_tuple -def diff_rlp_object(left, right): +def diff_rlp_object(left: BaseBlock, + right: BaseBlock) -> Optional[Iterable[Tuple[str, str, str]]]: if left != right: rlp_type = type(left) @@ -46,7 +56,10 @@ def diff_rlp_object(left, right): @curry -def validate_rlp_equal(obj_a, obj_b, obj_a_name=None, obj_b_name=None): +def validate_rlp_equal(obj_a: BaseBlock, + obj_b: BaseBlock, + obj_a_name: str=None, + obj_b_name: str=None) -> None: if obj_a == obj_b: return diff --git a/eth/utils/spoof.py b/eth/utils/spoof.py index d0450f78a8..bdc81909b7 100644 --- a/eth/utils/spoof.py +++ b/eth/utils/spoof.py @@ -1,3 +1,9 @@ +from typing import ( + Any, + TypeVar, + Union, +) + from cytoolz import ( merge, ) @@ -8,12 +14,10 @@ DEFAULT_SPOOF_S, ) - from eth.rlp.transactions import ( BaseTransaction, BaseUnsignedTransaction, ) -from typing import Callable, Union, Any SPOOF_ATTRIBUTES_DEFAULTS = { 'v': DEFAULT_SPOOF_V, @@ -21,6 +25,8 @@ 's': DEFAULT_SPOOF_S } +T = TypeVar('T', bound='SpoofAttributes') + class SpoofAttributes: def __init__( @@ -43,13 +49,13 @@ def __init__( if not hasattr(spoof_target, attr): overrides[attr] = value - def __getattr__(self, attr: str) -> Union[int, Callable, bytes]: + def __getattr__(self, attr: str) -> Any: if attr in self.overrides: return self.overrides[attr] else: return getattr(self.spoof_target, attr) - def copy(self, **kwargs): + def copy(self: T, **kwargs: Any) -> T: new_target = self.spoof_target.copy(**kwargs) new_overrides = merge(self.overrides, kwargs) return type(self)(new_target, **new_overrides) diff --git a/eth/utils/state.py b/eth/utils/state.py index 45588f9590..2a7afa84f8 100644 --- a/eth/utils/state.py +++ b/eth/utils/state.py @@ -1,14 +1,49 @@ +from typing import ( + Dict, + Iterable, + Tuple, + Union, +) + +from mypy_extensions import ( + TypedDict, +) + +from eth_typing import ( + Address, +) + from eth_utils import ( to_tuple, ) +from eth.db.account import ( + BaseAccountDB, +) + + +# 'balance', 'nonce' -> int +# 'code' -> bytes +# 'storage' -> Dict[int, int] +AccountDetails = TypedDict('AccountDetails', + {'balance': int, + 'nonce': int, + 'code': bytes, + 'storage': Dict[int, int] + }) +AccountState = Dict[Address, AccountDetails] + +AccountDiff = Iterable[Tuple[Address, str, Union[int, bytes], Union[int, bytes]]] + @to_tuple -def diff_account_db(expected_state, account_db): +def diff_account_db(expected_state: AccountState, + account_db: BaseAccountDB) -> AccountDiff: + for account, account_data in sorted(expected_state.items()): + expected_balance = account_data['balance'] expected_nonce = account_data['nonce'] expected_code = account_data['code'] - expected_balance = account_data['balance'] actual_nonce = account_db.get_nonce(account) actual_code = account_db.get_code(account) diff --git a/eth/utils/transactions.py b/eth/utils/transactions.py index 5c0170d077..c5428341e4 100644 --- a/eth/utils/transactions.py +++ b/eth/utils/transactions.py @@ -1,7 +1,11 @@ - import rlp +from typing import ( + Tuple, +) + from eth_keys import keys +from eth_keys import datatypes from eth_keys.exceptions import ( BadSignature, ) @@ -43,7 +47,10 @@ def extract_signature_v(v: int) -> int: return V_OFFSET -def create_transaction_signature(unsigned_txn, private_key, chain_id=None): +def create_transaction_signature(unsigned_txn: BaseTransaction, + private_key: datatypes.PrivateKey, + chain_id: int=None) -> Tuple[int, int, int]: + transaction_parts = rlp.decode(rlp.encode(unsigned_txn)) if chain_id: diff --git a/eth/utils/version.py b/eth/utils/version.py index d4d40178d2..d7a02c76c5 100644 --- a/eth/utils/version.py +++ b/eth/utils/version.py @@ -3,7 +3,7 @@ from eth import __version__ -def construct_evm_runtime_identifier(): +def construct_evm_runtime_identifier() -> str: """ Constructs the EVM runtime identifier string diff --git a/fixtures b/fixtures index 95a3092038..f4faae91c5 160000 --- a/fixtures +++ b/fixtures @@ -1 +1 @@ -Subproject commit 95a309203890e6244c6d4353ca411671973c13b5 +Subproject commit f4faae91c5ba192c3fd9b8cf418c24e627786312 diff --git a/setup.py b/setup.py index 4168868342..f5b96028a1 100644 --- a/setup.py +++ b/setup.py @@ -12,6 +12,7 @@ "eth-typing>=2.0.0,<3.0.0", "eth-utils>=1.3.0b0,<2.0.0", "lru-dict>=1.1.6", + "mypy==0.630", "py-ecc>=1.4.2,<2.0.0", "pyethash>=0.1.27,<1.0.0", "rlp>=1.0.3,<2.0.0", diff --git a/tox.ini b/tox.ini index f92f6fe755..91faa92e3a 100644 --- a/tox.ini +++ b/tox.ini @@ -105,6 +105,9 @@ commands= flake8 {toxinidir}/tests --exclude="trinity,p2p" # TODO: Drop --ignore-missing-imports once we have type annotations for eth_utils, coincurve and cytoolz mypy --follow-imports=silent --warn-unused-ignores --ignore-missing-imports --no-strict-optional --check-untyped-defs --disallow-incomplete-defs -p eth + mypy --follow-imports=silent --warn-unused-ignores --ignore-missing-imports --no-strict-optional --check-untyped-defs --disallow-incomplete-defs --disallow-untyped-defs --disallow-any-generics -p eth.utils + mypy --follow-imports=silent --warn-unused-ignores --ignore-missing-imports --no-strict-optional --check-untyped-defs --disallow-incomplete-defs --disallow-untyped-defs --disallow-any-generics -p eth.tools.builder + [testenv:py36-lint] deps = {[common-lint]deps}