From 98514c7a2cb4ac1c00f9621f1cd8718f941ea504 Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Tue, 23 Aug 2022 21:21:16 +0000 Subject: [PATCH] chore(lint): resolve `pylint` complaints --- pyhmy/__init__.py | 5 +- pyhmy/_version.py | 4 +- pyhmy/account.py | 121 +++++------ pyhmy/bech32/__init__.py | 1 - pyhmy/blockchain.py | 408 ++++++++++++++++-------------------- pyhmy/cli.py | 191 +++++++++-------- pyhmy/constants.py | 18 ++ pyhmy/contract.py | 88 ++++---- pyhmy/exceptions.py | 22 +- pyhmy/logging.py | 120 +++++------ pyhmy/numbers.py | 11 +- pyhmy/rpc/exceptions.py | 17 +- pyhmy/rpc/request.py | 20 +- pyhmy/signing.py | 93 ++++---- pyhmy/staking.py | 212 +++++++++---------- pyhmy/staking_signing.py | 105 +++++----- pyhmy/staking_structures.py | 52 +++-- pyhmy/transaction.py | 185 ++++++++-------- pyhmy/util.py | 71 +++---- pyhmy/validator.py | 238 +++++++++------------ 20 files changed, 958 insertions(+), 1024 deletions(-) create mode 100644 pyhmy/constants.py diff --git a/pyhmy/__init__.py b/pyhmy/__init__.py index ebdebee..17593b8 100644 --- a/pyhmy/__init__.py +++ b/pyhmy/__init__.py @@ -1,10 +1,11 @@ +""" +`pyhmy` for interacting with the Harmony blockchain +""" import sys import warnings from ._version import __version__ -from .util import Typgpy, get_gopath, get_goversion, get_bls_build_variables, json_load - if sys.version_info.major < 3: warnings.simplefilter("always", DeprecationWarning) warnings.warn( diff --git a/pyhmy/_version.py b/pyhmy/_version.py index dc1891a..8f7cbe4 100644 --- a/pyhmy/_version.py +++ b/pyhmy/_version.py @@ -1,6 +1,4 @@ -""" -Provides pyhmy version information. -""" +"""Provides pyhmy version information.""" # This file is auto-generated! Do not edit! # Use `python -m incremental.update pyhmy` to change this file. diff --git a/pyhmy/account.py b/pyhmy/account.py index 5c53b3b..9a4f869 100644 --- a/pyhmy/account.py +++ b/pyhmy/account.py @@ -1,3 +1,6 @@ +""" +Interact with accounts on the Harmony blockchain +""" from .rpc.request import rpc_request from .rpc.exceptions import RPCError, RequestsError, RequestsTimeoutError @@ -8,10 +11,7 @@ from .blockchain import get_sharding_structure from .bech32.bech32 import bech32_decode -_default_endpoint = "http://localhost:9500" -_default_timeout = 30 -_address_length = 42 - +from .constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT def is_valid_address(address) -> bool: """ @@ -36,9 +36,8 @@ def is_valid_address(address) -> bool: return True -def get_balance(address, endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get current account balance +def get_balance(address, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: + """Get current account balance. Parameters ---------- @@ -70,15 +69,14 @@ def get_balance(address, endpoint=_default_endpoint, timeout=_default_timeout) - method, params=params, endpoint=endpoint, timeout=timeout )["result"] return int(balance) # v2 returns the result as it is - except TypeError as e: # check will work if rpc returns None - raise InvalidRPCReplyError(method, endpoint) from e + except TypeError as exception: # check will work if rpc returns None + raise InvalidRPCReplyError(method, endpoint) from exception def get_balance_by_block( - address, block_num, endpoint=_default_endpoint, timeout=_default_timeout + address, block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> int: - """ - Get account balance for address at a given block number + """Get account balance for address at a given block number. Parameters ---------- @@ -113,15 +111,14 @@ def get_balance_by_block( method, params=params, endpoint=endpoint, timeout=timeout )["result"] return int(balance) - except TypeError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except TypeError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_account_nonce( - address, block_num="latest", endpoint=_default_endpoint, timeout=_default_timeout + address, block_num="latest", endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> int: - """ - Get the account nonce + """Get the account nonce. Parameters ---------- @@ -155,26 +152,24 @@ def get_account_nonce( "result" ] return int(nonce) - except TypeError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except TypeError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_nonce( - address, block_num="latest", endpoint=_default_endpoint, timeout=_default_timeout + address, block_num="latest", endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> int: - """ - See get_account_nonce - """ + """See get_account_nonce.""" return get_account_nonce(address, block_num, endpoint, timeout) def get_transaction_count( - address, block_num, endpoint=_default_endpoint, timeout=_default_timeout + address, block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> int: - """ - Get the number of transactions the given address has sent for the given block number - Legacy for apiv1. For apiv2, please use get_account_nonce/get_transactions_count/get_staking_transactions_count apis for - more granular transaction counts queries + """Get the number of transactions the given address has sent for the given + block number Legacy for apiv1. For apiv2, please use + get_account_nonce/get_transactions_count/get_staking_transactions_count + apis for more granular transaction counts queries. Parameters ---------- @@ -208,15 +203,14 @@ def get_transaction_count( "result" ] return int(nonce) - except TypeError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except TypeError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_transactions_count( - address, tx_type, endpoint=_default_endpoint, timeout=_default_timeout + address, tx_type, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> int: - """ - Get the number of regular transactions from genesis of input type + """Get the number of regular transactions from genesis of input type. Parameters ---------- @@ -252,15 +246,15 @@ def get_transactions_count( method, params=params, endpoint=endpoint, timeout=timeout )["result"] return int(tx_count) - except TypeError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except TypeError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_staking_transactions_count( - address, tx_type, endpoint=_default_endpoint, timeout=_default_timeout + address, tx_type, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> int: - """ - Get the number of staking transactions from genesis of input type ("SENT", "RECEIVED", "ALL") + """Get the number of staking transactions from genesis of input type + ("SENT", "RECEIVED", "ALL") Parameters ---------- @@ -296,22 +290,21 @@ def get_staking_transactions_count( method, params=params, endpoint=endpoint, timeout=timeout )["result"] return int(tx_count) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + except (KeyError, TypeError) as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def get_transaction_history( +def get_transaction_history( # pylint: disable=too-many-arguments address, page=0, page_size=1000, include_full_tx=False, tx_type="ALL", order="ASC", - endpoint=_default_endpoint, - timeout=_default_timeout, + endpoint=DEFAULT_ENDPOINT, + timeout=DEFAULT_TIMEOUT, ) -> list: - """ - Get list of transactions sent and/or received by the account + """Get list of transactions sent and/or received by the account. Parameters ---------- @@ -369,22 +362,21 @@ def get_transaction_history( method, params=params, endpoint=endpoint, timeout=timeout ) return tx_history["result"]["transactions"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def get_staking_transaction_history( +def get_staking_transaction_history( # pylint: disable=too-many-arguments address, page=0, page_size=1000, include_full_tx=False, tx_type="ALL", order="ASC", - endpoint=_default_endpoint, - timeout=_default_timeout, + endpoint=DEFAULT_ENDPOINT, + timeout=DEFAULT_TIMEOUT, ) -> list: - """ - Get list of staking transactions sent by the account + """Get list of staking transactions sent by the account. Parameters ---------- @@ -411,7 +403,8 @@ def get_staking_transaction_history( ------- list of transactions if include_full_tx is True, each transaction is a dictionary with the following kets - blockHash: :obj:`str` Block hash that transaction was finalized; "0x0000000000000000000000000000000000000000000000000000000000000000" if tx is pending + blockHash: :obj:`str` Block hash that transaction was finalized or + "0x0000000000000000000000000000000000000000000000000000000000000000" if tx is pending blockNumber: :obj:`int` Block number that transaction was finalized; None if tx is pending from: :obj:`str` Wallet address timestamp: :obj:`int` Timestamp in Unix time when transaction was finalized @@ -420,7 +413,8 @@ def get_staking_transaction_history( hash: :obj:`str` Transaction hash nonce: :obj:`int` Wallet nonce for the transaction transactionIndex: :obj:`int` Index of transaction in block; None if tx is pending - type: :obj:`str` Type of staking transaction, for example, "CollectRewards", "Delegate", "Undelegate" + type: :obj:`str` Type of staking transaction + for example, "CollectRewards", "Delegate", "Undelegate" msg: :obj:`dict` Message attached to the staking transaction r: :obj:`str` First 32 bytes of the transaction signature s: :obj:`str` Next 32 bytes of the transaction signature @@ -454,15 +448,15 @@ def get_staking_transaction_history( method, params=params, endpoint=endpoint, timeout=timeout )["result"] return stx_history["staking_transactions"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_balance_on_all_shards( - address, skip_error=True, endpoint=_default_endpoint, timeout=_default_timeout + address, skip_error=True, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> list: - """ - Get current account balance in all shards & optionally report errors getting account balance for a shard + """Get current account balance in all shards & optionally report errors + getting account balance for a shard. Parameters ---------- @@ -507,10 +501,9 @@ def get_balance_on_all_shards( def get_total_balance( - address, endpoint=_default_endpoint, timeout=_default_timeout + address, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> int: - """ - Get total account balance on all shards + """Get total account balance on all shards. Parameters ---------- @@ -540,5 +533,5 @@ def get_total_balance( address, skip_error=False, endpoint=endpoint, timeout=timeout ) return sum(b["balance"] for b in balances) - except TypeError as e: - raise RuntimeError from e + except TypeError as exception: + raise RuntimeError from exception diff --git a/pyhmy/bech32/__init__.py b/pyhmy/bech32/__init__.py index 8b13789..e69de29 100644 --- a/pyhmy/bech32/__init__.py +++ b/pyhmy/bech32/__init__.py @@ -1 +0,0 @@ - diff --git a/pyhmy/blockchain.py b/pyhmy/blockchain.py index 9962c14..05f43cd 100644 --- a/pyhmy/blockchain.py +++ b/pyhmy/blockchain.py @@ -1,17 +1,20 @@ +""" +Interact with the Harmony blockchain to fetch +blocks, headers, transaction pool, node status, etc. +""" +# pylint: disable=too-many-lines from .rpc.request import rpc_request from .exceptions import InvalidRPCReplyError -_default_endpoint = "http://localhost:9500" -_default_timeout = 30 +from .constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT ############################# # Node / network level RPCs # ############################# -def get_bad_blocks(endpoint=_default_endpoint, timeout=_default_timeout) -> list: - """ - [WIP] Get list of bad blocks in memory of specific node - Known issues with RPC not returning correctly +def get_bad_blocks(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> list: + """[WIP] Get list of bad blocks in memory of specific node Known issues + with RPC not returning correctly. Parameters ---------- @@ -36,13 +39,12 @@ def get_bad_blocks(endpoint=_default_endpoint, timeout=_default_timeout) -> list method = "hmyv2_getCurrentBadBlocks" try: return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def chain_id(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: - """ - Chain id of the chain +def chain_id(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict: + """Chain id of the chain. Parameters ---------- @@ -68,13 +70,12 @@ def chain_id(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: try: data = rpc_request(method, endpoint=endpoint, timeout=timeout) return data["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def get_node_metadata(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: - """ - Get config for the node +def get_node_metadata(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict: + """Get config for the node. Parameters ---------- @@ -89,25 +90,8 @@ def get_node_metadata(endpoint=_default_endpoint, timeout=_default_timeout) -> d blskey: :obj:`list` of BLS keys on the node version: :obj:`str` representing the Harmony binary version network: :obj:`str` the Network name that the node is on (Mainnet or Testnet) - chain-config: :obj:`dict` with the following keys (more are added over time): - chain-id: :obj:`int` Chain ID of the network - cross-tx-epoch: :obj:`int` Epoch at which cross shard transactions were enabled - cross-link-epoch: :obj:`int` Epoch at which cross links were enabled - staking-epoch: :obj:`int` Epoch at which staking was enabled - prestaking-epoch: :obj:`int` Epoch at which staking features without election were allowed - quick-unlock-epoch: :obj:`int` Epoch at which undelegations unlocked in one epoch - eip155-epoch: :obj:`int` Epoch at with EIP155 was enabled - s3-epoch: :obj:`int` Epoch at which Mainnet V0 was launched - receipt-log-epoch: :obj:`int` Epoch at which receipt logs were enabled - eth-compatible-chain-id: :obj:`int` EVM network compatible chain ID - eth-compatible-epoch: :obj:`int` Epoch at which EVM compatibility was launched - eth-compatible-shard-0-chain-id: :obj:`int` EVM network compatible chain ID on shard 0 - five-seconds-epoch: :obj:`int` Epoch at which five second finality was enabled and block rewards adjusted to 17.5 ONE/block - istanbul-epoch: :obj:`int` Epoch at which Ethereum's Istanbul upgrade was added to Harmony - no-early-unlock-epoch: :obj:`int` Epoch at which early unlock of tokens was disabled (https://github.com/harmony-one/harmony/pull/3605) - redelegation-epoch: :obj:`int` Epoch at which redelegation was enabled (staking) - sixty-percent-epoch: :obj:`int` Epoch when internal voting power reduced from 68% to 60% - two-seconds-epoch: :obj:`int` Epoch at which two second finality was enabled and block rewards adjusted to 7 ONE/block + chain-config: :obj:`dict` with the hard fork epochs list, and `chain-id` + both as :obj:`int` is-leader: :obj:`bool` Whether the node is currently leader or not shard-id: :obj:`int` Shard that the node is on current-epoch: :obj:`int` Current epoch @@ -138,20 +122,19 @@ def get_node_metadata(endpoint=_default_endpoint, timeout=_default_timeout) -> d API Reference ------------- https://api.hmny.io/#03c39b56-8dfc-48ce-bdad-f85776dd8aec - https://github.com/harmony-one/harmony/blob/v1.10.2/internal/params/config.go#L233 for chain-config dict - https://github.com/harmony-one/harmony/blob/9f320436ff30d9babd957bc5f2e15a1818c86584/node/api.go#L110 for consensus dict + https://github.com/harmony-one/harmony/blob/v1.10.2/internal/params/config.go#L233 + https://github.com/harmony-one/harmony/blob/9f320436ff30d9babd957bc5f2e15a1818c86584/node/api.go#L110 """ method = "hmyv2_getNodeMetadata" try: metadata = rpc_request(method, endpoint=endpoint, timeout=timeout) return metadata["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def get_peer_info(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: - """ - Get peer info for the node +def get_peer_info(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict: + """Get peer info for the node. Parameters ---------- @@ -183,13 +166,12 @@ def get_peer_info(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: method = "hmyv2_getPeerInfo" try: return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def protocol_version(endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get the current Harmony protocol version this node supports +def protocol_version(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: + """Get the current Harmony protocol version this node supports. Parameters ---------- @@ -216,13 +198,12 @@ def protocol_version(endpoint=_default_endpoint, timeout=_default_timeout) -> in try: value = rpc_request(method, endpoint=endpoint, timeout=timeout) return value["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def get_num_peers(endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get number of peers connected to the node +def get_num_peers(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: + """Get number of peers connected to the node. Parameters ---------- @@ -250,13 +231,12 @@ def get_num_peers(endpoint=_default_endpoint, timeout=_default_timeout) -> int: return int( rpc_request(method, endpoint=endpoint, timeout=timeout)["result"], 16 ) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + except (KeyError, TypeError) as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def get_version(endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get version of the EVM network (https://chainid.network/) +def get_version(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: + """Get version of the EVM network (https://chainid.network/) Parameters ---------- @@ -284,13 +264,12 @@ def get_version(endpoint=_default_endpoint, timeout=_default_timeout) -> int: return int( rpc_request(method, endpoint=endpoint, timeout=timeout)["result"], 16 ) # this is hexadecimal - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + except (KeyError, TypeError) as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def in_sync(endpoint=_default_endpoint, timeout=_default_timeout) -> bool: - """ - Whether the shard chain is in sync or syncing (not out of sync) +def in_sync(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> bool: + """Whether the shard chain is in sync or syncing (not out of sync) Parameters ---------- @@ -315,13 +294,12 @@ def in_sync(endpoint=_default_endpoint, timeout=_default_timeout) -> bool: method = "hmyv2_inSync" try: return bool(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + except (KeyError, TypeError) as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def beacon_in_sync(endpoint=_default_endpoint, timeout=_default_timeout) -> bool: - """ - Whether the beacon chain is in sync or syncing (not out of sync) +def beacon_in_sync(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> bool: + """Whether the beacon chain is in sync or syncing (not out of sync) Parameters ---------- @@ -346,13 +324,12 @@ def beacon_in_sync(endpoint=_default_endpoint, timeout=_default_timeout) -> bool method = "hmyv2_beaconInSync" try: return bool(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + except (KeyError, TypeError) as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def get_staking_epoch(endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get epoch number when blockchain switches to EPoS election +def get_staking_epoch(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: + """Get epoch number when blockchain switches to EPoS election. Parameters ---------- @@ -383,13 +360,13 @@ def get_staking_epoch(endpoint=_default_endpoint, timeout=_default_timeout) -> i try: data = rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return int(data["chain-config"]["staking-epoch"]) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + except (KeyError, TypeError) as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def get_prestaking_epoch(endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get epoch number when blockchain switches to allow staking features without election +def get_prestaking_epoch(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: + """Get epoch number when blockchain switches to allow staking features + without election. Parameters ---------- @@ -420,16 +397,15 @@ def get_prestaking_epoch(endpoint=_default_endpoint, timeout=_default_timeout) - try: data = rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return int(data["chain-config"]["prestaking-epoch"]) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + except (KeyError, TypeError) as exception: + raise InvalidRPCReplyError(method, endpoint) from exception ######################## # Sharding information # ######################## -def get_shard(endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get shard ID of the node +def get_shard(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: + """Get shard ID of the node. Parameters ---------- @@ -457,15 +433,14 @@ def get_shard(endpoint=_default_endpoint, timeout=_default_timeout) -> int: return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"][ "shard-id" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_sharding_structure( - endpoint=_default_endpoint, timeout=_default_timeout + endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> list: - """ - Get network sharding structure + """Get network sharding structure. Parameters ---------- @@ -494,16 +469,15 @@ def get_sharding_structure( method = "hmyv2_getShardingStructure" try: return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception ############################# # Current status of network # ############################# -def get_leader_address(endpoint=_default_endpoint, timeout=_default_timeout) -> str: - """ - Get current leader one address +def get_leader_address(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> str: + """Get current leader one address. Parameters ---------- @@ -529,15 +503,14 @@ def get_leader_address(endpoint=_default_endpoint, timeout=_default_timeout) -> method = "hmyv2_getLeader" try: return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def is_last_block( - block_num, endpoint=_default_endpoint, timeout=_default_timeout + block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> bool: - """ - If the block at block_num is the last block + """If the block at block_num is the last block. Parameters ---------- @@ -571,15 +544,14 @@ def is_last_block( "result" ] ) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + except (KeyError, TypeError) as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def epoch_last_block( - epoch, endpoint=_default_endpoint, timeout=_default_timeout + epoch, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> int: - """ - Returns the number of the last block in the epoch + """Returns the number of the last block in the epoch. Parameters ---------- @@ -613,13 +585,12 @@ def epoch_last_block( "result" ] ) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + except (KeyError, TypeError) as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def get_circulating_supply(endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get current circulation supply of tokens in ONE +def get_circulating_supply(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: + """Get current circulation supply of tokens in ONE. Parameters ---------- @@ -645,13 +616,12 @@ def get_circulating_supply(endpoint=_default_endpoint, timeout=_default_timeout) method = "hmyv2_getCirculatingSupply" try: return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def get_total_supply(endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get total number of pre-mined tokens +def get_total_supply(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: + """Get total number of pre-mined tokens. Parameters ---------- @@ -677,13 +647,12 @@ def get_total_supply(endpoint=_default_endpoint, timeout=_default_timeout) -> in method = "hmyv2_getTotalSupply" try: rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def get_block_number(endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get current block number +def get_block_number(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: + """Get current block number. Parameters ---------- @@ -709,13 +678,12 @@ def get_block_number(endpoint=_default_endpoint, timeout=_default_timeout) -> in method = "hmyv2_blockNumber" try: return int(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + except (KeyError, TypeError) as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def get_current_epoch(endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get current epoch number +def get_current_epoch(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: + """Get current epoch number. Parameters ---------- @@ -741,13 +709,12 @@ def get_current_epoch(endpoint=_default_endpoint, timeout=_default_timeout) -> i method = "hmyv2_getEpoch" try: return int(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + except (KeyError, TypeError) as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def get_last_cross_links(endpoint=_default_endpoint, timeout=_default_timeout) -> list: - """ - Get last cross shard links +def get_last_cross_links(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> list: + """Get last cross shard links. Parameters ---------- @@ -780,13 +747,12 @@ def get_last_cross_links(endpoint=_default_endpoint, timeout=_default_timeout) - method = "hmyv2_getLastCrossLinks" try: return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def get_gas_price(endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get network gas price +def get_gas_price(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: + """Get network gas price. Parameters ---------- @@ -812,16 +778,15 @@ def get_gas_price(endpoint=_default_endpoint, timeout=_default_timeout) -> int: method = "hmyv2_gasPrice" try: return int(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + except (KeyError, TypeError) as exception: + raise InvalidRPCReplyError(method, endpoint) from exception ############## # Block RPCs # ############## -def get_latest_header(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: - """ - Get block header of latest block +def get_latest_header(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict: + """Get block header of latest block. Parameters ---------- @@ -836,14 +801,16 @@ def get_latest_header(endpoint=_default_endpoint, timeout=_default_timeout) -> d blockHash: :obj:`str` Block hash blockNumber: :obj:`int` Block number shardID: :obj:`int` Shard ID - leader: :obj:`str` Wallet address of leader that proposed this block if prestaking, otherwise sha256 hash of leader's public bls key + leader: :obj:`str` Wallet address of leader that proposed this block if prestaking + otherwise sha256 hash of leader's public bls key viewID: :obj:`int` View ID of the block epoch: :obj:`int` Epoch of block timestamp: :obj:`str` Timestamp that the block was finalized in human readable format unixtime: :obj:`int` Timestamp that the block was finalized in Unix time lastCommitSig: :obj:`str` Hex representation of aggregated signatures of the previous block - lastCommitBitmap: :obj:`str` Hex representation of aggregated signature bitmap of the previous block - crossLinks: list of dicts describing the cross shard links, each dict to have the following keys: + lastCommitBitmap: :obj:`str` + Hex representation of aggregated signature bitmap of the previous block + crossLinks:list of dicts describing the cross shard links: block-number: :obj:`int` Number of the cross link block epoch-number: :obj:`int` Epoch of the cross link block hash: :obj:`str` Hash of the cross link block @@ -863,15 +830,14 @@ def get_latest_header(endpoint=_default_endpoint, timeout=_default_timeout) -> d method = "hmyv2_latestHeader" try: return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_header_by_number( - block_num, endpoint=_default_endpoint, timeout=_default_timeout + block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> dict: - """ - Get block header of block at block_num + """Get block header of block at block_num. Parameters ---------- @@ -901,15 +867,14 @@ def get_header_by_number( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_latest_chain_headers( - endpoint=_default_endpoint, timeout=_default_timeout + endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> dict: - """ - Get block header of latest block for beacon chain & shard chain + """Get block header of latest block for beacon chain & shard chain. Parameters ---------- @@ -921,7 +886,7 @@ def get_latest_chain_headers( Returns ------- dict with two keys: - beacon-chain-header: :obj:`dict` with the following keys, applicable to the beacon chain (cross shard links) + beacon-chain-header: :obj:`dict` with the following keys, applicable to the beacon chain shard-chain-header: :obj:`dict` with the following keys, applicable to the shard chain difficulty: legacy epoch: :obj:`int` Epoch of the block @@ -955,21 +920,20 @@ def get_latest_chain_headers( method = "hmyv2_getLatestChainHeaders" try: return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def get_block_by_number( +def get_block_by_number( # pylint: disable=too-many-arguments block_num, full_tx=False, include_tx=False, include_staking_tx=False, include_signers=False, - endpoint=_default_endpoint, - timeout=_default_timeout, + endpoint=DEFAULT_ENDPOINT, + timeout=DEFAULT_TIMEOUT, ) -> dict: - """ - Get block by number + """Get block by number. Parameters ---------- @@ -1007,12 +971,14 @@ def get_block_by_number( signers: :obj:`list` List of signers (only if include_signers is set to True) size: :obj:`int` Block size in bytes stakingTransactions: :obj:`list` - if full_tx is True: List of dictionaries, each containing a staking transaction (see account.get_staking_transaction_history) + if full_tx is True: List of dictionaries, + each containing a staking transaction (see account.get_staking_transaction_history) if full_tx is False: List of staking transaction hashes stateRoot: :obj:`str` Hash of state root timestamp: :obj:`int` Unix timestamp of the block transactions: :obj:`list` - if full_tx is True: List of dictionaries, each containing a transaction (see account.get_transaction_history) + if full_tx is True: List of dictionaries, + each containing a transaction (see account.get_transaction_history) if full_tx is False: List of transaction hashes transactionsRoot: :obj:`str` Hash of transactions root uncles: :obj:`str` legacy @@ -1042,21 +1008,20 @@ def get_block_by_number( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def get_block_by_hash( +def get_block_by_hash( # pylint: disable=too-many-arguments block_hash, full_tx=False, include_tx=False, include_staking_tx=False, include_signers=False, - endpoint=_default_endpoint, - timeout=_default_timeout, + endpoint=DEFAULT_ENDPOINT, + timeout=DEFAULT_TIMEOUT, ) -> dict: - """ - Get block by hash + """Get block by hash. Parameters ---------- @@ -1100,15 +1065,14 @@ def get_block_by_hash( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_block_transaction_count_by_number( - block_num, endpoint=_default_endpoint, timeout=_default_timeout + block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> int: - """ - Get transaction count for specific block number + """Get transaction count for specific block number. Parameters ---------- @@ -1143,15 +1107,14 @@ def get_block_transaction_count_by_number( "result" ] ) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + except (KeyError, TypeError) as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_block_transaction_count_by_hash( - block_hash, endpoint=_default_endpoint, timeout=_default_timeout + block_hash, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> int: - """ - Get transaction count for specific block hash + """Get transaction count for specific block hash. Parameters ---------- @@ -1186,15 +1149,14 @@ def get_block_transaction_count_by_hash( "result" ] ) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + except (KeyError, TypeError) as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_block_staking_transaction_count_by_number( - block_num, endpoint=_default_endpoint, timeout=_default_timeout + block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> int: - """ - Get staking transaction count for specific block number + """Get staking transaction count for specific block number. Parameters ---------- @@ -1229,15 +1191,14 @@ def get_block_staking_transaction_count_by_number( "result" ] ) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + except (KeyError, TypeError) as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_block_staking_transaction_count_by_hash( - block_hash, endpoint=_default_endpoint, timeout=_default_timeout + block_hash, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> int: - """ - Get staking transaction count for specific block hash + """Get staking transaction count for specific block hash. Parameters ---------- @@ -1272,22 +1233,21 @@ def get_block_staking_transaction_count_by_hash( "result" ] ) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + except (KeyError, TypeError) as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def get_blocks( +def get_blocks( # pylint: disable=too-many-arguments start_block, end_block, full_tx=False, include_tx=False, include_staking_tx=False, include_signers=False, - endpoint=_default_endpoint, - timeout=_default_timeout, + endpoint=DEFAULT_ENDPOINT, + timeout=DEFAULT_TIMEOUT, ) -> list: - """ - Get list of blocks from a range + """Get list of blocks from a range. Parameters ---------- @@ -1336,15 +1296,14 @@ def get_blocks( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_block_signers( - block_num, endpoint=_default_endpoint, timeout=_default_timeout + block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> list: - """ - Get list of block signers for specific block number + """Get list of block signers for specific block number. Parameters ---------- @@ -1375,15 +1334,14 @@ def get_block_signers( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_block_signers_keys( - block_num, endpoint=_default_endpoint, timeout=_default_timeout + block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> list: - """ - Get list of block signer public bls keys for specific block number + """Get list of block signer public bls keys for specific block number. Parameters ---------- @@ -1414,15 +1372,15 @@ def get_block_signers_keys( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def is_block_signer( - block_num, address, endpoint=_default_endpoint, timeout=_default_timeout + block_num, address, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> bool: - """ - Determine if the account at address is a signer for the block at block_num + """Determine if the account at address is a signer for the block at + block_num. Parameters ---------- @@ -1454,15 +1412,15 @@ def is_block_signer( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_signed_blocks( - address, endpoint=_default_endpoint, timeout=_default_timeout + address, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> bool: - """ - The number of blocks a particular validator signed for last blocksPeriod (1 epoch) + """The number of blocks a particular validator signed for last blocksPeriod + (1 epoch) Parameters ---------- @@ -1494,13 +1452,12 @@ def get_signed_blocks( "result" ] ) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + except (KeyError, TypeError) as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def get_validators(epoch, endpoint=_default_endpoint, timeout=_default_timeout) -> dict: - """ - Get list of validators for specific epoch number +def get_validators(epoch, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict: + """Get list of validators for specific epoch number. Parameters ---------- @@ -1534,15 +1491,14 @@ def get_validators(epoch, endpoint=_default_endpoint, timeout=_default_timeout) return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_validator_keys( - epoch, endpoint=_default_endpoint, timeout=_default_timeout + epoch, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> list: - """ - Get list of validator public bls keys for specific epoch number + """Get list of validator public bls keys for specific epoch number. Parameters ---------- @@ -1573,5 +1529,5 @@ def get_validator_keys( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception diff --git a/pyhmy/cli.py b/pyhmy/cli.py index fd96890..55adae8 100644 --- a/pyhmy/cli.py +++ b/pyhmy/cli.py @@ -7,7 +7,8 @@ Example: Below is a demo of how to import, manage keys, and interact with the CLI:: >>> from pyhmy import cli >>> cli.single_call("hmy keys add test1") - '**Important** write this seed phrase in a safe place, it is the only way to recover your account if you ever forget your password + '**Important** write this seed phrase in a safe place, + it is the only way to recover your account if you ever forget your password craft ... tobacco' >>> cli.get_accounts_keystore() {'test1': 'one1aqfeed538xf7n0cfh60tjaeat7yw333pmj6sfu'} @@ -15,7 +16,7 @@ Example: >>> cli.get_accounts(check_addr) ['test1'] >>> cli.single_call("hmy keys list", timeout=2) - 'NAME \t\t ADDRESS\n\ntest1 \tone1aqfeed538xf7n0cfh60tjaeat7yw333pmj6sfu\n' + 'NAME \t\t ADDRESS\n\ntest1 \tone1aqfeed538xf7n0cfh60tjaeat7yw333pmj6sfu\n' >>> cli.get_accounts_keystore() {} @@ -40,14 +41,13 @@ For more details, reference the documentation here: TODO gitbook docs """ import subprocess -import pexpect import os import shutil import re import stat import sys -from multiprocessing import Lock from pathlib import Path +import pexpect import requests @@ -69,44 +69,61 @@ else: "libgmpxx.4.dylib", "libmcl.dylib", } -_accounts = {} # Internal accounts keystore, make sure to sync when needed. -_account_keystore_path = "~/.hmy/account-keys" # Internal path to account keystore, will match the current binary. -_binary_path = "hmy" # Internal binary path. -_arg_prefix = "__PYHMY_ARG_PREFIX__" -_keystore_cache_lock = Lock() +# Internal accounts keystore, make sure to sync when needed. +_accounts = {} +# Internal path to account keystore, will match the current binary. +ARG_PREFIX = "__PYHMY_ARG_PREFIX__" +# _keystore_cache_lock = Lock() environment = os.environ.copy() # The environment for the CLI to execute in. -# TODO: completely remove caching... we need to improve getting address better internally to REDUCE single calls.... -def _cache_and_lock_accounts_keystore(fn): - """ - Internal decorator to cache the accounts keystore and - prevent concurrent accesses with locks. - """ - cached_accounts = {} - last_mod = None +# completely remove caching... +# we need to improve getting address better internally to REDUCE single calls.... +# def _cache_and_lock_accounts_keystore(fn): +# """Internal decorator to cache the accounts keystore and prevent concurrent +# accesses with locks.""" +# cached_accounts = {} +# last_mod = None - def wrap(*args): - nonlocal last_mod - _keystore_cache_lock.acquire() - files_in_dir = str(os.listdir(_account_keystore_path)) - dir_mod_time = str(os.path.getmtime(_account_keystore_path)) - curr_mod = hash(files_in_dir + dir_mod_time + _binary_path) - if curr_mod != last_mod: - cached_accounts.clear() - cached_accounts.update(fn(*args)) - last_mod = curr_mod - accounts = cached_accounts.copy() - _keystore_cache_lock.release() - return accounts +# def wrap(*args): +# nonlocal last_mod +# _keystore_cache_lock.acquire() +# files_in_dir = str(os.listdir(ACCOUNT_KEYSTORE_PATH)) +# dir_mod_time = str(os.path.getmtime(ACCOUNT_KEYSTORE_PATH)) +# curr_mod = hash(files_in_dir + dir_mod_time + BINARY_PATH) +# if curr_mod != last_mod: +# cached_accounts.clear() +# cached_accounts.update(fn(*args)) +# last_mod = curr_mod +# accounts = cached_accounts.copy() +# _keystore_cache_lock.release() +# return accounts - return wrap +# return wrap +def account_keystore_path(value=None): + """ + Gets or sets the ACCOUNT_KEYSTORE_PATH + """ + if "value" not in account_keystore_path.__dict__: + account_keystore_path.value = "~/.hmy/account-keys" + if value: + account_keystore_path.value = value + return account_keystore_path.value -def _get_current_accounts_keystore(): +def binary_path(value=None): """ - Internal function that gets the current keystore from the CLI. + Gets or sets the BINARY_PATH + """ + if "value" not in binary_path.__dict__: + binary_path.value = "hmy" + if value: + binary_path.value = value + return binary_path.value + +def _get_current_accounts_keystore(): + """Internal function that gets the current keystore from the CLI. :returns A dictionary where the keys are the account names/aliases and the values are their 'one1...' addresses. @@ -128,32 +145,28 @@ def _get_current_accounts_keystore(): def _set_account_keystore_path(): - """ - Internal function to set the account keystore path according to the binary. - """ - global _account_keystore_path + """Internal function to set the account keystore path according to the + binary.""" response = single_call("hmy keys location").strip() if not os.path.exists(response): os.mkdir(response) - _account_keystore_path = response + account_keystore_path(response) def _sync_accounts(): - """ - Internal function that UPDATES the accounts keystore with the CLI's keystore. - """ + """Internal function that UPDATES the accounts keystore with the CLI's + keystore.""" new_keystore = _get_current_accounts_keystore() - for key in new_keystore.keys(): - if key not in _accounts.keys(): - _accounts[key] = new_keystore[key] - acc_keys_to_remove = [k for k in _accounts.keys() if k not in new_keystore.keys()] + for key, value in new_keystore.items(): + if key not in _accounts: + _accounts[key] = value + acc_keys_to_remove = [k for k in _accounts if k not in new_keystore] for key in acc_keys_to_remove: del _accounts[key] def _make_call_command(command): - """ - Internal function that processes a command String or String Arg List for + """Internal function that processes a command String or String Arg List for underlying pexpect or subprocess call. Note that single quote is not respected for strings. @@ -162,18 +175,20 @@ def _make_call_command(command): command_toks = command else: all_strings = sorted( - re.findall(r'"(.*?)"', command), key=lambda e: len(e), reverse=True + re.findall(r'"(.*?)"', command), + key=lambda e: len(e), # pylint: disable=unnecessary-lambda + reverse=True ) for i, string in enumerate(all_strings): - command = command.replace(string, f"{_arg_prefix}_{i}") + command = command.replace(string, f"{ARG_PREFIX}_{i}") command_toks_prefix = [el for el in command.split(" ") if el] command_toks = [] - for el in command_toks_prefix: - if el.startswith(f'"{_arg_prefix}_') and el.endswith(f'"'): - index = int(el.replace(f'"{_arg_prefix}_', "").replace('"', "")) + for element in command_toks_prefix: + if element.startswith(f'"{ARG_PREFIX}_') and element.endswith('"'): + index = int(element.replace(f'"{ARG_PREFIX}_', "").replace('"', "")) command_toks.append(all_strings[index]) else: - command_toks.append(el) + command_toks.append(element) if re.match(".*hmy", command_toks[0]): command_toks = command_toks[1:] return command_toks @@ -197,16 +212,16 @@ def is_valid_binary(path): path = os.path.realpath(path) os.chmod(path, os.stat(path).st_mode | stat.S_IEXEC) try: - proc = subprocess.Popen( + with subprocess.Popen( [path, "version"], env=environment, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - ) - out, err = proc.communicate() - if not err: - return False - return "harmony" in err.decode().strip().lower() + ) as proc: + _, err = proc.communicate() + if not err: + return False + return "harmony" in err.decode().strip().lower() except (OSError, subprocess.CalledProcessError, subprocess.SubprocessError): return False @@ -218,13 +233,12 @@ def set_binary(path): Note that the exposed keystore will be updated accordingly. """ - global _binary_path path = os.path.realpath(path) assert os.path.exists(path) os.chmod(path, os.stat(path).st_mode | stat.S_IEXEC) if not is_valid_binary(path): return False - _binary_path = path + binary_path(path) _set_account_keystore_path() _sync_accounts() return True @@ -234,33 +248,33 @@ def get_binary_path(): """ :return: The absolute path of the CLI binary. """ - return os.path.abspath(_binary_path) + return os.path.abspath(binary_path()) def get_version(): """ :return: The version string of the CLI binary. """ - proc = subprocess.Popen( - [_binary_path, "version"], + with subprocess.Popen( + [binary_path(), "version"], env=environment, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - ) - out, err = proc.communicate() - if not err: - raise RuntimeError( - f"Could not get version.\n" - f"\tGot exit code {proc.returncode}. Expected non-empty error message." - ) - return err.decode().strip() + ) as proc: + _, err = proc.communicate() + if not err: + raise RuntimeError( + f"Could not get version.\n" + f"\tGot exit code {proc.returncode}. Expected non-empty error message." + ) + return err.decode().strip() def get_account_keystore_path(): """ :return: The absolute path to the account keystore of the CLI binary. """ - return os.path.abspath(_account_keystore_path) + return os.path.abspath(account_keystore_path()) def check_address(address): @@ -291,8 +305,7 @@ def get_accounts(address): def remove_account(name): - """ - Note that this edits the keystore directly since there is currently no + """Note that this edits the keystore directly since there is currently no way to remove an address using the CLI. :param name: The alias of a key used in the CLI's keystore. @@ -327,7 +340,7 @@ def single_call(command, timeout=60, error_ok=False): :returns: Decoded string of response from hmy CLI call :raises: RuntimeError if bad command """ - command_toks = [_binary_path] + _make_call_command(command) + command_toks = [binary_path()] + _make_call_command(command) try: return subprocess.check_output( command_toks, env=environment, timeout=timeout @@ -350,7 +363,7 @@ def expect_call(command, timeout=60): command_toks = _make_call_command(command) try: proc = pexpect.spawn( - f"{_binary_path}", command_toks, env=environment, timeout=timeout + f"{binary_path()}", command_toks, env=environment, timeout=timeout ) proc.delaybeforesend = None except pexpect.ExceptionPexpect as err: @@ -361,9 +374,8 @@ def expect_call(command, timeout=60): def download(path="./bin/hmy", replace=True, verbose=True): - """ - Download the CLI binary to the specified path. - Related files will be saved in the same directory. + """Download the CLI binary to the specified path. Related files will be + saved in the same directory. :param path: The desired path (absolute or relative) of the saved binary. :param replace: A flag to force a replacement of the binary/file. @@ -381,8 +393,8 @@ def download(path="./bin/hmy", replace=True, verbose=True): os.makedirs(parent_dir, exist_ok=True) os.chdir(parent_dir) hmy_script_path = os.path.join(parent_dir, "hmy.sh") - with open(hmy_script_path, "w") as f: - f.write( + with open(hmy_script_path, "w", encoding='utf8') as script_file: + script_file.write( requests.get( "https://raw.githubusercontent.com/harmony-one/go-sdk/master/scripts/hmy.sh" ).content.decode() @@ -399,11 +411,12 @@ def download(path="./bin/hmy", replace=True, verbose=True): if verbose: subprocess.call([hmy_script_path, "-d"]) else: - subprocess.call( - [hmy_script_path, "-d"], - stdout=open(os.devnull, "w"), - stderr=subprocess.STDOUT, - ) + with open(os.devnull, "w", encoding = "UTF-8") as devnull: + subprocess.call( + [hmy_script_path, "-d"], + stdout=devnull, + stderr=subprocess.STDOUT, + ) os.rename(os.path.join(parent_dir, "hmy"), path) if same_name_file: os.rename( @@ -427,8 +440,8 @@ def download(path="./bin/hmy", replace=True, verbose=True): raise RuntimeWarning( f"Could not get environment for downloaded hmy CLI at `{path}`" ) - except Exception as e: + except Exception as exception: raise RuntimeWarning( f"Could not get environment for downloaded hmy CLI at `{path}`" - ) from e + ) from exception return env diff --git a/pyhmy/constants.py b/pyhmy/constants.py new file mode 100644 index 0000000..3bd545e --- /dev/null +++ b/pyhmy/constants.py @@ -0,0 +1,18 @@ +""" +Constants +""" + +# localnet constants +DEFAULT_ENDPOINT = 'http://localhost:9500' +DEFAULT_TIMEOUT = 30 + +# staking percentage constants +PRECISION = 18 +MAX_DECIMAL = 1000000000000000000 + +NAME_CHAR_LIMIT = 140 +IDENTITY_CHAR_LIMIT = 140 +WEBSITE_CHAR_LIMIT = 140 +SECURITY_CONTACT_CHAR_LIMIT = 140 +DETAILS_CHAR_LIMIT = 280 +MIN_REQUIRED_DELEGATION = int(10000 * 1e18) diff --git a/pyhmy/contract.py b/pyhmy/contract.py index abcfa5a..b5849cc 100644 --- a/pyhmy/contract.py +++ b/pyhmy/contract.py @@ -1,34 +1,36 @@ +""" +Basic smart contract functions on Harmony +For full ABI driven interaction, use something like web3py or brownie +""" + from .rpc.request import rpc_request from .transaction import get_transaction_receipt from .exceptions import InvalidRPCReplyError - -_default_endpoint = "http://localhost:9500" -_default_timeout = 30 +from .constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT ######################### # Smart contract RPCs ######################### -def call( - to, +def call( # pylint: disable=too-many-arguments + to_address, block_num, from_address=None, gas=None, gas_price=None, value=None, data=None, - endpoint=_default_endpoint, - timeout=_default_timeout, + endpoint=DEFAULT_ENDPOINT, + timeout=DEFAULT_TIMEOUT, ) -> str: - """ - Execute a smart contract without saving state + """Execute a smart contract without saving state. Parameters ---------- - to: :obj:`str` + to_address: :obj:`str` Address of the smart contract block_num: :obj:`int` Block number to execute the contract for @@ -55,7 +57,7 @@ def call( Raises ------ InvalidRPCReplyError - If received unknown result from endpoint, or + If received unknown result from exceptionndpoint, or API Reference ------------- @@ -63,7 +65,7 @@ def call( """ params = [ { - "to": to, + "to": to_address, "from": from_address, "gas": gas, "gasPrice": gas_price, @@ -77,26 +79,25 @@ def call( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def estimate_gas( - to, +def estimate_gas( # pylint: disable=too-many-arguments + to_address, from_address=None, gas=None, gas_price=None, value=None, data=None, - endpoint=_default_endpoint, - timeout=_default_timeout, + endpoint=DEFAULT_ENDPOINT, + timeout=DEFAULT_TIMEOUT, ) -> int: - """ - Estimate the gas price needed for a smart contract call + """Estimate the gas price needed for a smart contract call. Parameters ---------- - to: :obj:`str` + to_address: :obj:`str` Address of the smart contract from_address: :obj:`str`, optional Wallet address @@ -121,7 +122,7 @@ def estimate_gas( Raises ------ InvalidRPCReplyError - If received unknown result from endpoint, or + If received unknown result from exceptionndpoint, or API Reference ------------- @@ -129,7 +130,7 @@ def estimate_gas( """ params = [ { - "to": to, + "to": to_address, "from": from_address, "gas": gas, "gasPrice": gas_price, @@ -145,15 +146,15 @@ def estimate_gas( ], 16, ) - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_code( - address, block_num, endpoint=_default_endpoint, timeout=_default_timeout + address, block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> str: - """ - Get the code stored at the given address in the state for the given block number + """Get the code stored at the given address in the state for the given + block number. Parameters ---------- @@ -174,7 +175,7 @@ def get_code( Raises ------ InvalidRPCReplyError - If received unknown result from endpoint, or + If received unknown result from exceptionndpoint, or API Reference ------------- @@ -187,15 +188,15 @@ def get_code( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_storage_at( - address, key, block_num, endpoint=_default_endpoint, timeout=_default_timeout + address, key, block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> str: - """ - Get the storage from the state at the given address, the key and the block number + """Get the storage from the state at the given address, the key and the + block number. Parameters ---------- @@ -218,7 +219,7 @@ def get_storage_at( Raises ------ InvalidRPCReplyError - If received unknown result from endpoint, or + If received unknown result from exceptionndpoint, or API Reference ------------- @@ -231,16 +232,15 @@ def get_storage_at( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_contract_address_from_hash( - tx_hash, endpoint=_default_endpoint, timeout=_default_timeout + tx_hash, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> str: - """ - Get address of the contract which was deployed in the transaction - represented by tx_hash + """Get address of the contract which was deployed in the transaction + represented by tx_hash. Parameters ---------- @@ -259,7 +259,7 @@ def get_contract_address_from_hash( Raises ------ InvalidRPCReplyError - If received unknown result from endpoint, or + If received unknown result from exceptionndpoint, or API Reference ------------- @@ -267,5 +267,5 @@ def get_contract_address_from_hash( """ try: return get_transaction_receipt(tx_hash, endpoint, timeout)["contractAddress"] - except KeyError as e: - raise InvalidRPCReplyError("hmyv2_getTransactionReceipt", endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError("hmyv2_getTransactionReceipt", endpoint) from exception diff --git a/pyhmy/exceptions.py b/pyhmy/exceptions.py index 91a6882..b8a5fb1 100644 --- a/pyhmy/exceptions.py +++ b/pyhmy/exceptions.py @@ -1,20 +1,18 @@ -from .rpc.exceptions import RPCError, RequestsError, RequestsTimeoutError - +""" +Exceptions used by pyhmy +""" class InvalidRPCReplyError(RuntimeError): - """ - Exception raised when RPC call returns unexpected result - Generally indicates Harmony API has been updated & pyhmy library needs to be updated as well - """ + """Exception raised when RPC call returns unexpected result Generally + indicates Harmony API has been updated & pyhmy library needs to be updated + as well.""" def __init__(self, method, endpoint): super().__init__(f"Unexpected reply for {method} from {endpoint}") class InvalidValidatorError(ValueError): - """ - Exception raised Validator does not pass sanity checks - """ + """Exception raised Validator does not pass sanity checks.""" errors = { 1: "Invalid ONE address", @@ -34,10 +32,8 @@ class InvalidValidatorError(ValueError): class TxConfirmationTimedoutError(AssertionError): - """ - Exception raised when a transaction is sent to the chain - But not confirmed during the timeout period specified - """ + """Exception raised when a transaction is sent to the chain But not + confirmed during the timeout period specified.""" def __init__(self, msg): super().__init__(f"{msg}") diff --git a/pyhmy/logging.py b/pyhmy/logging.py index 498cf70..b49e6c1 100644 --- a/pyhmy/logging.py +++ b/pyhmy/logging.py @@ -1,3 +1,7 @@ +""" +Logger for pyhmy +""" + import threading import datetime import gzip @@ -6,21 +10,18 @@ import logging import logging.handlers -class _GZipRotator: +class _GZipRotator: # pylint: disable=too-few-public-methods def __call__(self, source, dest): os.rename(source, dest) - f_in = open(dest, "rb") - f_out = gzip.open("%s.gz" % dest, "wb") - f_out.writelines(f_in) - f_out.close() - f_in.close() + with open(dest, "rb") as f_in: + with gzip.open(f"{dest}.gz", "wb") as f_out: + f_out.writelines(f_in) os.remove(dest) -class ControlledLogger: - """ - A simple logger that only writes to file when the 'write' method is called. - """ +class ControlledLogger: # pylint: disable=too-many-instance-attributes + """A simple logger that only writes to file when the 'write' method is + called.""" def __init__(self, logger_name, log_dir, backup_count=5): """ @@ -51,9 +52,7 @@ class ControlledLogger: return f"" def _clear(self): - """ - Internal method to clear the log buffer. - """ + """Internal method to clear the log buffer.""" self.info_buffer.clear() self.debug_buffer.clear() self.warning_buffer.clear() @@ -63,85 +62,74 @@ class ControlledLogger: """ :param msg: The info message to log """ - self._lock.acquire() - self.info_buffer.append( - f"[{threading.get_ident()}] " f"{datetime.datetime.utcnow()} : {msg}" - ) - self._lock.release() + with self._lock: + self.info_buffer.append( + f"[{threading.get_ident()}] " f"{datetime.datetime.utcnow()} : {msg}" + ) def debug(self, msg): """ :param msg: The debug message to log """ - self._lock.acquire() - self.debug_buffer.append( - f"[{threading.get_ident()}] " f"{datetime.datetime.utcnow()} : {msg}" - ) - self._lock.release() + with self._lock: + self.debug_buffer.append( + f"[{threading.get_ident()}] " f"{datetime.datetime.utcnow()} : {msg}" + ) def warning(self, msg): """ :param msg: The warning message to log """ - self._lock.acquire() - self.warning_buffer.append( - f"[{threading.get_ident()}] " f"{datetime.datetime.utcnow()} : {msg}" - ) - self._lock.release() + with self._lock: + self.warning_buffer.append( + f"[{threading.get_ident()}] " f"{datetime.datetime.utcnow()} : {msg}" + ) def error(self, msg): """ :param msg: The error message to log """ - self._lock.acquire() - self.error_buffer.append( - f"[{threading.get_ident()}] " f"{datetime.datetime.utcnow()} : {msg}" - ) - self._lock.release() + with self._lock: + self.error_buffer.append( + f"[{threading.get_ident()}] " f"{datetime.datetime.utcnow()} : {msg}" + ) def print_info(self): - """ - Prints the current info buffer but does not flush it to log file. - """ + """Prints the current info buffer but does not flush it to log file.""" print("\n".join(self.info_buffer)) def print_debug(self): - """ - Prints the current debug buffer but does not flush it to log file. - """ + """Prints the current debug buffer but does not flush it to log + file.""" print("\n".join(self.debug_buffer)) def print_warning(self): - """ - Prints the current warning buffer but does not flush it to log file. - """ + """Prints the current warning buffer but does not flush it to log + file.""" print("\n".join(self.warning_buffer)) def print_error(self): - """ - Prints the current error buffer but does not flush it to log file. - """ + """Prints the current error buffer but does not flush it to log + file.""" print("\n".join(self.error_buffer)) def write(self): - """ - Flushes ALL of the log buffers to the log file via the logger. - - Note that directly after this method call, the respective prints will print - nothing since all log messages are flushed to file. - """ - self._lock.acquire() - self.logger.setLevel(logging.DEBUG) - for line in self.debug_buffer: - self.logger.debug(line) - self.logger.setLevel(logging.WARNING) - for line in self.warning_buffer: - self.logger.warning(line) - self.logger.setLevel(logging.ERROR) - for line in self.error_buffer: - self.logger.error(line) - self.logger.setLevel(logging.INFO) - for line in self.info_buffer: - self.logger.info(line) - self._clear() - self._lock.release() + """Flushes ALL of the log buffers to the log file via the logger. + + Note that directly after this method call, the respective prints + will print nothing since all log messages are flushed to file. + """ + with self._lock: + self.logger.setLevel(logging.DEBUG) + for line in self.debug_buffer: + self.logger.debug(line) + self.logger.setLevel(logging.WARNING) + for line in self.warning_buffer: + self.logger.warning(line) + self.logger.setLevel(logging.ERROR) + for line in self.error_buffer: + self.logger.error(line) + self.logger.setLevel(logging.INFO) + for line in self.info_buffer: + self.logger.info(line) + self._clear() diff --git a/pyhmy/numbers.py b/pyhmy/numbers.py index 9d62a11..de413ef 100644 --- a/pyhmy/numbers.py +++ b/pyhmy/numbers.py @@ -1,3 +1,8 @@ +""" +Handles conversion of ONE to ATTO and vice versa +For more granular conversions, see Web3.toWei +""" + from decimal import Decimal @@ -5,8 +10,7 @@ _conversion_unit = Decimal(1e18) def convert_atto_to_one(atto) -> Decimal: - """ - Convert ATTO to ONE + """Convert ATTO to ONE. Parameters ---------- @@ -25,8 +29,7 @@ def convert_atto_to_one(atto) -> Decimal: def convert_one_to_atto(one) -> Decimal: - """ - Convert ONE to ATTO + """Convert ONE to ATTO. Parameters ---------- diff --git a/pyhmy/rpc/exceptions.py b/pyhmy/rpc/exceptions.py index a6ad3ac..fbbca44 100644 --- a/pyhmy/rpc/exceptions.py +++ b/pyhmy/rpc/exceptions.py @@ -1,10 +1,11 @@ -import requests +""" +RPC Specific Exceptions +""" +import requests class RPCError(RuntimeError): - """ - Exception raised when RPC call returns an error - """ + """Exception raised when RPC call returns an error.""" def __init__(self, method, endpoint, error): self.error = error @@ -12,18 +13,14 @@ class RPCError(RuntimeError): class RequestsError(requests.exceptions.RequestException): - """ - Wrapper for requests lib exceptions - """ + """Wrapper for requests lib exceptions.""" def __init__(self, endpoint): super().__init__(f"Error connecting to {endpoint}") class RequestsTimeoutError(requests.exceptions.Timeout): - """ - Wrapper for requests lib Timeout exceptions - """ + """Wrapper for requests lib Timeout exceptions.""" def __init__(self, endpoint): super().__init__(f"Error connecting to {endpoint}") diff --git a/pyhmy/rpc/request.py b/pyhmy/rpc/request.py index be1c4e8..30384cd 100644 --- a/pyhmy/rpc/request.py +++ b/pyhmy/rpc/request.py @@ -1,19 +1,19 @@ +""" +RPC wrapper around requests library +""" import json import requests from .exceptions import RequestsError, RequestsTimeoutError, RPCError - -_default_endpoint = "http://localhost:9500" -_default_timeout = 30 +from ..constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT def base_request( - method, params=None, endpoint=_default_endpoint, timeout=_default_timeout + method, params=None, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> str: - """ - Basic RPC request + """Basic RPC request. Parameters --------- @@ -65,10 +65,9 @@ def base_request( def rpc_request( - method, params=None, endpoint=_default_endpoint, timeout=_default_timeout + method, params=None, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> dict: - """ - RPC request + """RPC request. Parameters --------- @@ -110,6 +109,3 @@ def rpc_request( return resp except json.decoder.JSONDecodeError as err: raise RPCError(method, endpoint, raw_resp) from err - - -# TODO: Add GET requests diff --git a/pyhmy/signing.py b/pyhmy/signing.py index 4fddde8..7e96128 100644 --- a/pyhmy/signing.py +++ b/pyhmy/signing.py @@ -1,29 +1,33 @@ +""" +Sign Harmony or Ethereum transactions +Harmony staking transaction signing is not covered by this module +""" + +# pylint: disable=protected-access, no-member + +from functools import partial +from toolz import dissoc, pipe, merge + import rlp from eth_utils.curried import keccak, to_int, hexstr_if_str, apply_formatters_to_dict from rlp.sedes import big_endian_int, Binary, binary -from eth_account import Account - from eth_rlp import HashableRLP from hexbytes import HexBytes -from eth_account._utils.signing import sign_transaction_hash - +from eth_account import Account +from eth_account.datastructures import SignedTransaction from eth_account._utils.legacy_transactions import ( Transaction as SignedEthereumTxData, UnsignedTransaction as UnsignedEthereumTxData, LEGACY_TRANSACTION_FORMATTERS as ETHEREUM_FORMATTERS, TRANSACTION_DEFAULTS, chain_id_to_v, - UNSIGNED_TRANSACTION_FIELDS, ) - -from cytoolz import dissoc, pipe, merge, partial - -from eth_account.datastructures import SignedTransaction +from eth_account._utils.signing import sign_transaction_hash from .util import chain_id_to_int, convert_one_to_hex @@ -35,6 +39,11 @@ HARMONY_FORMATTERS = dict( class UnsignedHarmonyTxData(HashableRLP): + """ + Unsigned Harmony transaction data + Includes `shardID` and `toShardID` + as the difference against Eth + """ fields = ( ("nonce", big_endian_int), ("gasPrice", big_endian_int), @@ -48,18 +57,23 @@ class UnsignedHarmonyTxData(HashableRLP): class SignedHarmonyTxData(HashableRLP): + """ + Signed Harmony transaction data + Includes `shardID` and `toShardID` + as the difference against Eth + """ fields = UnsignedHarmonyTxData._meta.fields + ( ("v", big_endian_int), # Recovery value + 27 ("r", big_endian_int), # First 32 bytes ("s", big_endian_int), # Next 32 bytes ) - +# https://github.com/ethereum/eth-account/blob/00e7b10005c5fa7090086fcef37a76296c524e17/eth_account/_utils/transactions.py#L55 def encode_transaction( unsigned_transaction, vrs -): # https://github.com/ethereum/eth-account/blob/00e7b10005c5fa7090086fcef37a76296c524e17/eth_account/_utils/transactions.py#L55 - """serialize and encode an unsigned transaction with v,r,s""" - (v, r, s) = vrs +): + """serialize and encode an unsigned transaction with v,r,s.""" + (v, r, s) = vrs # pylint: disable=invalid-name chain_naive_transaction = dissoc(unsigned_transaction.as_dict(), "v", "r", "s") if isinstance(unsigned_transaction, (UnsignedHarmonyTxData, SignedHarmonyTxData)): serializer = SignedHarmonyTxData @@ -70,7 +84,7 @@ def encode_transaction( def serialize_transaction(filled_transaction): - """serialize a signed/unsigned transaction""" + """serialize a signed/unsigned transaction.""" if "v" in filled_transaction: if "shardID" in filled_transaction: serializer = SignedHarmonyTxData @@ -81,41 +95,40 @@ def serialize_transaction(filled_transaction): serializer = UnsignedHarmonyTxData else: serializer = UnsignedEthereumTxData - for f, _ in serializer._meta.fields: - assert f in filled_transaction, f"Could not find {f} in transaction" + for field, _ in serializer._meta.fields: + assert field in filled_transaction, f"Could not find {field} in transaction" return serializer.from_dict( - {f: filled_transaction[f] for f, _ in serializer._meta.fields} + {field: filled_transaction[field] for field, _ in serializer._meta.fields} ) +# https://github.com/ethereum/eth-account/blob/00e7b10005c5fa7090086fcef37a76296c524e17/eth_account/account.py#L650 def sanitize_transaction(transaction_dict, private_key): - """remove the originating address from the dict and convert chainId to int""" - account = Account.from_key( + """remove the originating address from the dict and convert chainId to + int.""" + account = Account.from_key( # pylint: disable=no-value-for-parameter private_key - ) # get account, from which you can derive public + private key - transaction_dict = transaction_dict.copy() # do not alter the original dictionary - if "from" in transaction_dict: - transaction_dict["from"] = convert_one_to_hex(transaction_dict["from"]) + ) + sanitized_transaction = transaction_dict.copy() # do not alter the original dictionary + if "from" in sanitized_transaction: + sanitized_transaction["from"] = convert_one_to_hex(transaction_dict["from"]) if ( - transaction_dict["from"] == account.address - ): # https://github.com/ethereum/eth-account/blob/00e7b10005c5fa7090086fcef37a76296c524e17/eth_account/account.py#L650 - sanitized_transaction = dissoc(transaction_dict, "from") + sanitized_transaction["from"] == account.address + ): + sanitized_transaction = dissoc(sanitized_transaction, "from") else: raise TypeError( - "from field must match key's %s, but it was %s" - % ( - account.address, - transaction_dict["from"], - ) + "from field must match key's {account.address}, " + "but it was {sanitized_transaction['from']}" ) - if "chainId" in transaction_dict: - transaction_dict["chainId"] = chain_id_to_int(transaction_dict["chainId"]) - return account, transaction_dict + if "chainId" in sanitized_transaction: + sanitized_transaction["chainId"] = chain_id_to_int(sanitized_transaction["chainId"]) + return account, sanitized_transaction def sign_transaction(transaction_dict, private_key) -> SignedTransaction: - """ - Sign a (non-staking) transaction dictionary with the specified private key + """Sign a (non-staking) transaction dictionary with the specified private + key. Parameters ---------- @@ -161,7 +174,8 @@ def sign_transaction(transaction_dict, private_key) -> SignedTransaction: account, sanitized_transaction = sanitize_transaction(transaction_dict, private_key) if "to" in sanitized_transaction and sanitized_transaction["to"] is not None: sanitized_transaction["to"] = convert_one_to_hex(sanitized_transaction["to"]) - filled_transaction = pipe( # https://github.com/ethereum/eth-account/blob/00e7b10005c5fa7090086fcef37a76296c524e17/eth_account/_utils/transactions.py#L39 + # https://github.com/ethereum/eth-account/blob/00e7b10005c5fa7090086fcef37a76296c524e17/eth_account/_utils/transactions.py#L39 + filled_transaction = pipe( sanitized_transaction, dict, partial(merge, TRANSACTION_DEFAULTS), @@ -171,13 +185,14 @@ def sign_transaction(transaction_dict, private_key) -> SignedTransaction: unsigned_transaction = serialize_transaction(filled_transaction) transaction_hash = unsigned_transaction.hash() + # https://github.com/ethereum/eth-account/blob/00e7b10005c5fa7090086fcef37a76296c524e17/eth_account/_utils/signing.py#L26 if isinstance( unsigned_transaction, (UnsignedEthereumTxData, UnsignedHarmonyTxData) ): - chain_id = None # https://github.com/ethereum/eth-account/blob/00e7b10005c5fa7090086fcef37a76296c524e17/eth_account/_utils/signing.py#L26 + chain_id = None else: chain_id = unsigned_transaction.v - (v, r, s) = sign_transaction_hash(account._key_obj, transaction_hash, chain_id) + (v, r, s) = sign_transaction_hash(account._key_obj, transaction_hash, chain_id) # pylint: disable=invalid-name encoded_transaction = encode_transaction(unsigned_transaction, vrs=(v, r, s)) signed_transaction_hash = keccak(encoded_transaction) return SignedTransaction( diff --git a/pyhmy/staking.py b/pyhmy/staking.py index a70174a..7c54d1b 100644 --- a/pyhmy/staking.py +++ b/pyhmy/staking.py @@ -1,18 +1,20 @@ +""" +Call Harmony's staking API +""" + from .rpc.request import rpc_request from .exceptions import InvalidRPCReplyError -_default_endpoint = "http://localhost:9500" -_default_timeout = 30 +from .constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT ################## # Validator RPCs # ################## def get_all_validator_addresses( - endpoint=_default_endpoint, timeout=_default_timeout + endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> list: - """ - Get list of all created validator addresses on chain + """Get list of all created validator addresses on chain. Parameters ---------- @@ -38,15 +40,14 @@ def get_all_validator_addresses( method = "hmyv2_getAllValidatorAddresses" try: return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_validator_information( - validator_addr, endpoint=_default_endpoint, timeout=_default_timeout + validator_addr, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> dict: - """ - Get validator information for validator address + """Get validator information for validator address. Parameters ---------- @@ -65,7 +66,8 @@ def get_validator_information( bls-public-keys: :obj:`list` List of associated public BLS keys last-epoch-in-committee: :obj:`int` Last epoch any key of the validator was elected min-self-delegation: :obj:`int` Amount that validator must delegate to self in ATTO - max-total-delegation: :obj:`int` Total amount that validator will aceept delegations until, in ATTO + max-total-delegation: :obj:`int` + Total amount that validator will aceept delegations until, in ATTO rate: :obj:`str` Current commission rate max-rate: :obj:`str` Max commission rate a validator can charge max-change-rate: :obj:`str` Maximum amount the commission rate can increase in one epoch @@ -76,8 +78,9 @@ def get_validator_information( security-contact: :obj:`str` Method to contact the validators details: :obj:`str` Validator details, displayed on the Staking Dashboard creation-height: :obj:`int` Block number in which the validator was created - delegations: :obj:`list` List of delegations, see get_delegations_by_delegator for format - metrics: :obj:`dict` BLS key earning metrics for current epoch (or None if no earnings in the current epoch) + delegations: :obj:`list` + List of delegations, see get_delegations_by_delegator for format + metrics: :obj:`dict` BLS key earning metrics for current epoch (or None) by-bls-key: :obj:`list` List of dictionaries, each with the following keys key: :obj:`dict` Dictionary with the following keys bls-public-key: :obj:`str` BLS public key @@ -90,7 +93,8 @@ def get_validator_information( earned-reward: :obj:`int` Lifetime reward key has earned total-delegation: :obj:`int` Total amount delegated to validator currently-in-committee: :obj:`bool` if key is currently elected - epos-status: :obj:`str` Currently elected, eligible to be elected next epoch, or not eligible to be elected next epoch + epos-status: :obj:`str` Currently elected, eligible to be elected next epoch, + or not eligible to be elected next epoch epos-winning-stake: :obj:`str` Total effective stake of the validator booted-status: :obj:`str` Banned status active-status: :obj:`str` Active or inactive @@ -119,15 +123,14 @@ def get_validator_information( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_elected_validator_addresses( - endpoint=_default_endpoint, timeout=_default_timeout + endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> list: - """ - Get list of elected validator addresses + """Get list of elected validator addresses. Parameters ---------- @@ -154,13 +157,12 @@ def get_elected_validator_addresses( method = "hmyv2_getElectedValidatorAddresses" try: return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def get_validators(epoch, endpoint=_default_endpoint, timeout=_default_timeout) -> list: - """ - Get validators list for a particular epoch +def get_validators(epoch, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> list: + """Get validators list for a particular epoch. Parameters ---------- @@ -194,15 +196,14 @@ def get_validators(epoch, endpoint=_default_endpoint, timeout=_default_timeout) return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_validator_keys( - epoch, endpoint=_default_endpoint, timeout=_default_timeout + epoch, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> list: - """ - Get validator BLS keys in the committee for a particular epoch + """Get validator BLS keys in the committee for a particular epoch. Parameters ---------- @@ -232,15 +233,14 @@ def get_validator_keys( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_validator_information_by_block_number( - validator_addr, block_num, endpoint=_default_endpoint, timeout=_default_timeout + validator_addr, block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ): - """ - Get validator information for validator address at a block + """Get validator information for validator address at a block. Parameters ---------- @@ -272,15 +272,14 @@ def get_validator_information_by_block_number( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_all_validator_information( - page=0, endpoint=_default_endpoint, timeout=_default_timeout + page=0, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> list: - """ - Get validator information for all validators on chain + """Get validator information for all validators on chain. Parameters ---------- @@ -310,15 +309,14 @@ def get_all_validator_information( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_validator_self_delegation( - address, endpoint=_default_endpoint, timeout=_default_timeout + address, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> int: - """ - Get the amount self delegated by validator + """Get the amount self delegated by validator. Parameters ---------- @@ -350,15 +348,14 @@ def get_validator_self_delegation( "result" ] ) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + except (KeyError, TypeError) as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_validator_total_delegation( - address, endpoint=_default_endpoint, timeout=_default_timeout + address, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> int: - """ - Get the total amount delegated t ovalidator (including self delegated) + """Get the total amount delegated t ovalidator (including self delegated) Parameters ---------- @@ -390,15 +387,14 @@ def get_validator_total_delegation( "result" ] ) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + except (KeyError, TypeError) as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_all_validator_information_by_block_number( - block_num, page=0, endpoint=_default_endpoint, timeout=_default_timeout + block_num, page=0, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> list: - """ - Get validator information at block number for all validators on chain + """Get validator information at block number for all validators on chain. Parameters ---------- @@ -431,18 +427,17 @@ def get_all_validator_information_by_block_number( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception ################### # Delegation RPCs # ################### def get_all_delegation_information( - page=0, endpoint=_default_endpoint, timeout=_default_timeout + page=0, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> list: - """ - Get delegation information for all delegators on chain + """Get delegation information for all delegators on chain. Parameters ---------- @@ -476,15 +471,14 @@ def get_all_delegation_information( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_delegations_by_delegator( - delegator_addr, endpoint=_default_endpoint, timeout=_default_timeout + delegator_addr, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> list: - """ - Get list of delegations by a delegator + """Get list of delegations by a delegator. Parameters ---------- @@ -521,15 +515,14 @@ def get_delegations_by_delegator( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_delegations_by_delegator_by_block_number( - delegator_addr, block_num, endpoint=_default_endpoint, timeout=_default_timeout + delegator_addr, block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> list: - """ - Get list of delegations by a delegator at a specific block + """Get list of delegations by a delegator at a specific block. Parameters ---------- @@ -561,18 +554,17 @@ def get_delegations_by_delegator_by_block_number( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_delegation_by_delegator_and_validator( delegator_addr, validator_address, - endpoint=_default_endpoint, - timeout=_default_timeout, + endpoint=DEFAULT_ENDPOINT, + timeout=DEFAULT_TIMEOUT, ) -> dict: - """ - Get list of delegations by a delegator at a specific block + """Get list of delegations by a delegator at a specific block. Parameters ---------- @@ -587,7 +579,8 @@ def get_delegation_by_delegator_and_validator( Returns ------- - one delegation (or None if such delegation doesn't exist), see get_delegations_by_delegator for fields + one delegation (or None if such delegation doesn't exist) + see get_delegations_by_delegator for fields Raises ------ @@ -604,15 +597,14 @@ def get_delegation_by_delegator_and_validator( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_available_redelegation_balance( - delegator_addr, endpoint=_default_endpoint, timeout=_default_timeout + delegator_addr, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> int: - """ - Get amount of locked undelegated tokens + """Get amount of locked undelegated tokens. Parameters ---------- @@ -644,15 +636,14 @@ def get_available_redelegation_balance( "result" ] ) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + except (KeyError, TypeError) as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_delegations_by_validator( - validator_addr, endpoint=_default_endpoint, timeout=_default_timeout + validator_addr, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> list: - """ - Get list of delegations to a validator + """Get list of delegations to a validator. Parameters ---------- @@ -682,18 +673,17 @@ def get_delegations_by_validator( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception ######################## # Staking Network RPCs # ######################## def get_current_utility_metrics( - endpoint=_default_endpoint, timeout=_default_timeout + endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> dict: - """ - Get current utility metrics of network + """Get current utility metrics of network. Parameters ---------- @@ -722,15 +712,14 @@ def get_current_utility_metrics( method = "hmyv2_getCurrentUtilityMetrics" try: return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_staking_network_info( - endpoint=_default_endpoint, timeout=_default_timeout + endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> dict: - """ - Get staking network information + """Get staking network information. Parameters ---------- @@ -760,13 +749,12 @@ def get_staking_network_info( method = "hmyv2_getStakingNetworkInfo" try: return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def get_super_committees(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: - """ - Get voting committees for current & previous epoch +def get_super_committees(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict: + """Get voting committees for current & previous epoch. Parameters ---------- @@ -814,13 +802,12 @@ def get_super_committees(endpoint=_default_endpoint, timeout=_default_timeout) - method = "hmyv2_getSuperCommittees" try: return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def get_total_staking(endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get total staking by validators, only meant to be called on beaconchain +def get_total_staking(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: + """Get total staking by validators, only meant to be called on beaconchain. Parameters ---------- @@ -845,15 +832,14 @@ def get_total_staking(endpoint=_default_endpoint, timeout=_default_timeout) -> i method = "hmyv2_getTotalStaking" try: return int(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + except (KeyError, TypeError) as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_raw_median_stake_snapshot( - endpoint=_default_endpoint, timeout=_default_timeout + endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> dict: - """ - Get median stake & additional committee data of the current epoch + """Get median stake & additional committee data of the current epoch. Parameters ---------- @@ -891,5 +877,5 @@ def get_raw_median_stake_snapshot( method = "hmyv2_getMedianRawStakeSnapshot" try: return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception diff --git a/pyhmy/staking_signing.py b/pyhmy/staking_signing.py index 03c96c6..bffbcf8 100644 --- a/pyhmy/staking_signing.py +++ b/pyhmy/staking_signing.py @@ -1,7 +1,15 @@ -from cytoolz import ( +""" +Sign Harmony staking transactions +""" + +import math + +from decimal import Decimal + +from functools import partial +from toolz import ( pipe, dissoc, - partial, merge, identity, ) @@ -10,10 +18,6 @@ from hexbytes import HexBytes import rlp -import math - -from decimal import Decimal - from eth_account.datastructures import SignedTransaction from eth_account._utils.signing import sign_transaction_hash @@ -30,11 +34,12 @@ from eth_utils.curried import ( apply_formatter_to_array, ) +from .constants import PRECISION, MAX_DECIMAL + from .signing import sanitize_transaction from .staking_structures import ( FORMATTERS, - StakingSettings, Directive, CreateValidator, EditValidator, @@ -45,12 +50,13 @@ from .staking_structures import ( from .util import convert_one_to_hex +# https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L335 def _convert_staking_percentage_to_number( value, -): # https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L335 - """ - Convert from staking percentage to integer - For example, 0.1 becomes 1000000000000000000 +): + """Convert from staking percentage to integer For example, 0.1 becomes + 1000000000000000000. Since Python floats are problematic with precision, + this function is used as a workaround. Parameters --------- @@ -90,24 +96,23 @@ def _convert_staking_percentage_to_number( combined_str += splitted[1] elif len(splitted) > 2: raise ValueError("Too many periods to be a StakingDecimal string") - if length > StakingSettings.PRECISION: + if length > PRECISION: raise ValueError( - "Too much precision, must be less than {StakingSettings.PRECISION}" + "Too much precision, must be less than {PRECISION}" ) - zeroes_to_add = StakingSettings.PRECISION - length + zeroes_to_add = PRECISION - length combined_str += ( "0" * zeroes_to_add ) # This will not have any periods, so it is effectively a large integer val = int(combined_str) - assert val <= StakingSettings.MAX_DECIMAL, "Staking percentage is too large" + assert val <= MAX_DECIMAL, "Staking percentage is too large" return val def _get_account_and_transaction(transaction_dict, private_key): - """ - Create account from private key and sanitize the transaction - Sanitization involves removal of 'from' key - And conversion of chainId key from str to int (if present) + """Create account from private key and sanitize the transaction + Sanitization involves removal of 'from' key And conversion of chainId key + from str to int (if present) Parameters ---------- @@ -134,10 +139,10 @@ def _get_account_and_transaction(transaction_dict, private_key): ].value # convert to value, like in TypeScript return account, sanitized_transaction - +# pylint: disable=too-many-locals,protected-access,invalid-name def _sign_transaction_generic(account, sanitized_transaction, parent_serializer): - """ - Sign a generic staking transaction, given the serializer base class and account + """Sign a generic staking transaction, given the serializer base class and + account. Paramters --------- @@ -167,7 +172,8 @@ def _sign_transaction_generic(account, sanitized_transaction, parent_serializer) parent_serializer.SignedChainId(), ) # since chain_id_to_v adds v/r/s, unsigned is not used here # fill the transaction - filled_transaction = pipe( # https://github.com/ethereum/eth-account/blob/00e7b10005c5fa7090086fcef37a76296c524e17/eth_account/_utils/transactions.py#L39 + # https://github.com/ethereum/eth-account/blob/00e7b10005c5fa7090086fcef37a76296c524e17/eth_account/_utils/transactions.py#L39 + filled_transaction = pipe( sanitized_transaction, dict, partial(merge, {"chainId": None}), @@ -175,8 +181,8 @@ def _sign_transaction_generic(account, sanitized_transaction, parent_serializer) apply_formatters_to_dict(FORMATTERS), ) # get the unsigned transaction - for f, _ in unsigned_serializer._meta.fields: - assert f in filled_transaction, f"Could not find {f} in transaction" + for field, _ in unsigned_serializer._meta.fields: + assert field in filled_transaction, f"Could not find {field} in transaction" unsigned_transaction = unsigned_serializer.from_dict( {f: filled_transaction[f] for f, _ in unsigned_serializer._meta.fields} ) # drop extras silently @@ -191,11 +197,12 @@ def _sign_transaction_generic(account, sanitized_transaction, parent_serializer) unsigned_transaction.as_dict(), "v", "r", "s" ) # remove extra v/r/s added by chain_id_to_v # serialize it + # https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L207 signed_transaction = signed_serializer( v=v + ( 8 if chain_id is None else 0 - ), # copied from https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L207 + ), r=r, s=s, # in the below statement, remove everything not expected by signed_serializer **{ @@ -218,11 +225,9 @@ def _sign_transaction_generic(account, sanitized_transaction, parent_serializer) ) -def _sign_delegate_or_undelegate(transaction_dict, private_key, delegate): - """ - Sign a delegate or undelegate transaction - See sign_staking_transaction for details - """ +def _sign_delegate_or_undelegate(transaction_dict, private_key): + """Sign a delegate or undelegate transaction See sign_staking_transaction + for details.""" # preliminary steps if transaction_dict["directive"] not in [Directive.Delegate, Directive.Undelegate]: raise TypeError( @@ -247,10 +252,8 @@ def _sign_delegate_or_undelegate(transaction_dict, private_key, delegate): def _sign_collect_rewards(transaction_dict, private_key): - """ - Sign a collect rewards transaction - See sign_staking_transaction for details - """ + """Sign a collect rewards transaction See sign_staking_transaction for + details.""" # preliminary steps if transaction_dict["directive"] != Directive.CollectRewards: raise TypeError("Only CollectRewards is supported by _sign_collect_rewards") @@ -268,10 +271,8 @@ def _sign_collect_rewards(transaction_dict, private_key): def _sign_create_validator(transaction_dict, private_key): - """ - Sign a create validator transaction - See sign_staking_transaction for details - """ + """Sign a create validator transaction See sign_staking_transaction for + details.""" # preliminary steps if transaction_dict["directive"] != Directive.CreateValidator: raise TypeError( @@ -343,10 +344,8 @@ def _sign_create_validator(transaction_dict, private_key): def _sign_edit_validator(transaction_dict, private_key): - """ - Sign an edit validator transaction - See sign_staking_transaction for details - """ + """Sign an edit validator transaction See sign_staking_transaction for + details.""" # preliminary steps if transaction_dict["directive"] != Directive.EditValidator: raise TypeError( @@ -377,6 +376,7 @@ def _sign_edit_validator(transaction_dict, private_key): ), # max total delegation (in ONE), decimals are silently dropped hexstr_if_str(to_bytes), # key to remove hexstr_if_str(to_bytes), # key to add + hexstr_if_str(to_bytes), # key to add sig ], [ convert_one_to_hex(sanitized_transaction.pop("validatorAddress")), @@ -388,14 +388,14 @@ def _sign_edit_validator(transaction_dict, private_key): math.floor(sanitized_transaction.pop("max-total-delegation")), sanitized_transaction.pop("bls-key-to-remove"), sanitized_transaction.pop("bls-key-to-add"), + sanitized_transaction.pop("bls-key-to-add-sig"), ], ) return _sign_transaction_generic(account, sanitized_transaction, EditValidator) def sign_staking_transaction(transaction_dict, private_key): - """ - Sign a supplied transaction_dict with the private_key + """Sign a supplied transaction_dict with the private_key. Parameters ---------- @@ -464,7 +464,7 @@ def sign_staking_transaction(transaction_dict, private_key): assert isinstance( transaction_dict, dict ), "Only dictionaries are supported" # OrderedDict is a subclass - # chain_id missing => 'rlp: input string too long for uint64, decoding into (types.StakingTransaction)(types.txdata).GasLimit' + # chain_id missing => results in rlp decoding error for GasLimit assert "chainId" in transaction_dict, "chainId missing" assert "directive" in transaction_dict, "Staking transaction type not specified" assert isinstance( @@ -472,11 +472,12 @@ def sign_staking_transaction(transaction_dict, private_key): ), "Unknown staking transaction type" if transaction_dict["directive"] == Directive.CollectRewards: return _sign_collect_rewards(transaction_dict, private_key) - elif transaction_dict["directive"] == Directive.Delegate: - return _sign_delegate_or_undelegate(transaction_dict, private_key, True) - elif transaction_dict["directive"] == Directive.Undelegate: - return _sign_delegate_or_undelegate(transaction_dict, private_key, False) - elif transaction_dict["directive"] == Directive.CreateValidator: + if transaction_dict["directive"] == Directive.Delegate: + return _sign_delegate_or_undelegate(transaction_dict, private_key) + if transaction_dict["directive"] == Directive.Undelegate: + return _sign_delegate_or_undelegate(transaction_dict, private_key) + if transaction_dict["directive"] == Directive.CreateValidator: return _sign_create_validator(transaction_dict, private_key) - elif transaction_dict["directive"] == Directive.EditValidator: + if transaction_dict["directive"] == Directive.EditValidator: return _sign_edit_validator(transaction_dict, private_key) + raise ValueError('Unknown staking transaction type') diff --git a/pyhmy/staking_structures.py b/pyhmy/staking_structures.py index 44f7102..ced6a0b 100644 --- a/pyhmy/staking_structures.py +++ b/pyhmy/staking_structures.py @@ -1,3 +1,9 @@ +""" +Helper module for signing Harmony staking transactions +""" +# disable most of the Lint here +# pylint: disable=protected-access,no-member,invalid-name,missing-class-docstring,missing-function-docstring + from enum import Enum, auto from rlp.sedes import big_endian_int, Binary, CountableList, List, Text @@ -9,16 +15,11 @@ from eth_utils.curried import ( hexstr_if_str, ) - -class StakingSettings: - PRECISION = 18 - MAX_DECIMAL = 1000000000000000000 - - +# https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L120 class Directive( Enum -): # https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L120 - def _generate_next_value_(name, start, count, last_values): +): + def _generate_next_value_(name, start, count, last_values): # pylint: disable=no-self-argument return count CreateValidator = auto() @@ -38,7 +39,6 @@ FORMATTERS = { "chainId": hexstr_if_str(to_int), } - class CollectRewards: @staticmethod def UnsignedChainId(): @@ -159,23 +159,28 @@ class CreateValidator: "stakeMsg", List( [ # list with the following members + # validatorAddress Binary.fixed_length( 20, allow_empty=True - ), # validatorAddress + ), + # description is Text of 5 elements List( [Text()] * 5, True - ), # description is Text of 5 elements + ), + # commission rate is made up of 3 integers in an array List( [List([big_endian_int], True)] * 3, True - ), # commission rate is made up of 3 integers in an array [ [int1], [int2], [int3] ] + ), big_endian_int, # min self delegation big_endian_int, # max total delegation + # bls-public-keys array of unspecified length, each key of 48 CountableList( Binary.fixed_length(48, allow_empty=True) - ), # bls-public-keys array of unspecified length, each key of 48 + ), + # bls-key-sigs array of unspecified length, each sig of 96 CountableList( Binary.fixed_length(96, allow_empty=True) - ), # bls-key-sigs array of unspecified length, each sig of 96 + ), big_endian_int, # amount ], True, @@ -233,21 +238,30 @@ class EditValidator: "stakeMsg", List( [ # list with the following members + # validatorAddress Binary.fixed_length( 20, allow_empty=True - ), # validatorAddress + ), + # description is Text of 5 elements List( [Text()] * 5, True - ), # description is Text of 5 elements - List([big_endian_int], True), # new rate is in a list + ), + # new rate is in a list + List([big_endian_int], True), big_endian_int, # min self delegation big_endian_int, # max total delegation + # slot key to remove Binary.fixed_length( 48, allow_empty=True - ), # slot key to remove + ), + # slot key to add Binary.fixed_length( 48, allow_empty=True - ), # slot key to add + ), + # slot key to add sig + Binary.fixed_length( + 96, allow_empty=True + ), ], True, ), diff --git a/pyhmy/transaction.py b/pyhmy/transaction.py index b1c890c..97eb407 100644 --- a/pyhmy/transaction.py +++ b/pyhmy/transaction.py @@ -1,20 +1,21 @@ -from .rpc.request import rpc_request -from .exceptions import TxConfirmationTimedoutError, InvalidRPCReplyError +""" +Interact with Harmony's transaction RPC API +""" + import time import random - -_default_endpoint = "http://localhost:9500" -_default_timeout = 30 +from .constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT +from .rpc.request import rpc_request +from .exceptions import TxConfirmationTimedoutError, InvalidRPCReplyError ######################### # Transaction Pool RPCs # ######################### def get_pending_transactions( - endpoint=_default_endpoint, timeout=_default_timeout + endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> list: - """ - Get list of pending transactions + """Get list of pending transactions. Parameters ---------- @@ -39,15 +40,14 @@ def get_pending_transactions( method = "hmyv2_pendingTransactions" try: return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_transaction_error_sink( - endpoint=_default_endpoint, timeout=_default_timeout + endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> list: - """ - Get current transactions error sink + """Get current transactions error sink. Parameters ---------- @@ -75,15 +75,14 @@ def get_transaction_error_sink( method = "hmyv2_getCurrentTransactionErrorSink" try: return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_pending_staking_transactions( - endpoint=_default_endpoint, timeout=_default_timeout + endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> list: - """ - Get list of pending staking transactions + """Get list of pending staking transactions. Parameters ---------- @@ -108,15 +107,14 @@ def get_pending_staking_transactions( method = "hmyv2_pendingStakingTransactions" try: return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_staking_transaction_error_sink( - endpoint=_default_endpoint, timeout=_default_timeout + endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> list: - """ - Get current staking transactions error sink + """Get current staking transactions error sink. Parameters ---------- @@ -145,13 +143,13 @@ def get_staking_transaction_error_sink( method = "hmyv2_getCurrentStakingErrorSink" try: return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception -def get_pool_stats(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: - """ - Get stats of the pool, that is, number of pending and queued (non-executable) transactions +def get_pool_stats(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict: + """Get stats of the pool, that is, number of pending and queued (non- + executable) transactions. Parameters ---------- @@ -178,18 +176,17 @@ def get_pool_stats(endpoint=_default_endpoint, timeout=_default_timeout) -> dict method = "hmyv2_getPoolStats" try: return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception #################### # Transaction RPCs # #################### def get_transaction_by_hash( - tx_hash, endpoint=_default_endpoint, timeout=_default_timeout + tx_hash, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> dict: - """ - Get transaction by hash + """Get transaction by hash. Parameters ---------- @@ -239,15 +236,15 @@ def get_transaction_by_hash( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_transaction_by_block_hash_and_index( - block_hash, tx_index, endpoint=_default_endpoint, timeout=_default_timeout + block_hash, tx_index, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> dict: - """ - Get transaction based on index in list of transactions in a block by block hash + """Get transaction based on index in list of transactions in a block by + block hash. Parameters ---------- @@ -279,15 +276,15 @@ def get_transaction_by_block_hash_and_index( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_transaction_by_block_number_and_index( - block_num, tx_index, endpoint=_default_endpoint, timeout=_default_timeout + block_num, tx_index, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> dict: - """ - Get transaction based on index in list of transactions in a block by block number + """Get transaction based on index in list of transactions in a block by + block number. Parameters ---------- @@ -319,15 +316,14 @@ def get_transaction_by_block_number_and_index( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_transaction_receipt( - tx_hash, endpoint=_default_endpoint, timeout=_default_timeout + tx_hash, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> dict: - """ - Get transaction receipt corresponding to tx_hash + """Get transaction receipt corresponding to tx_hash. Parameters ---------- @@ -348,7 +344,9 @@ def get_transaction_receipt( from: :obj:`str` Sender wallet address gasUsed: :obj:`int` Gas used for the transaction logs: :obj:`list` List of logs, each being a dict with keys as follows: - address, blockHash, blockNumber, data, logIndex, removed, topics, transactionHash, transactionIndex + address, blockHash, blockNumber + data, logIndex, removed + topics, transactionHash, transactionIndex logsBloom :obj:`str` Bloom logs shardID :obj:`int` Shard ID status :obj:`int` Status of transaction (0: pending, 1: success) @@ -371,15 +369,14 @@ def get_transaction_receipt( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def send_raw_transaction( - signed_tx, endpoint=_default_endpoint, timeout=_default_timeout + signed_tx, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> str: - """ - Send signed transaction + """Send signed transaction. Parameters ---------- @@ -412,15 +409,14 @@ def send_raw_transaction( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def send_and_confirm_raw_transaction( - signed_tx, endpoint=_default_endpoint, timeout=_default_timeout + signed_tx, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> list: - """ - Send signed transaction and wait for it to be confirmed + """Send signed transaction and wait for it to be confirmed. Parameters ---------- @@ -466,10 +462,9 @@ def send_and_confirm_raw_transaction( # CrossShard Transaction RPCs # ############################### def get_pending_cx_receipts( - endpoint=_default_endpoint, timeout=_default_timeout + endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> list: - """ - Get list of pending cross shard transactions + """Get list of pending cross shard transactions. Parameters ---------- @@ -483,7 +478,7 @@ def get_pending_cx_receipts( list of CX receipts, each a dict with the following keys commitBitmap: :obj:`str` Hex represenation of aggregated signature bitmap commitSig: :obj:`str` Hex representation of aggregated signature - receipts: :obj:`list` list of dictionaries, each representing a cross shard transaction receipt + receipts: :obj:`list` list of dictionaries, each a cross shard transaction receipt amount: :obj:`int` Amount in ATTO from: :obj:`str` From address to: :obj:`str` From address @@ -517,15 +512,14 @@ def get_pending_cx_receipts( method = "hmyv2_getPendingCXReceipts" try: return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_cx_receipt_by_hash( - cx_hash, endpoint=_default_endpoint, timeout=_default_timeout + cx_hash, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> dict: - """ - Get cross shard receipt by hash on the receiving shard end point + """Get cross shard receipt by hash on the receiving shard end point. Parameters ---------- @@ -564,15 +558,15 @@ def get_cx_receipt_by_hash( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def resend_cx_receipt( - cx_hash, endpoint=_default_endpoint, timeout=_default_timeout + cx_hash, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> bool: - """ - Resend the cross shard receipt to the receiving shard to re-process if the transaction did not pay out + """Resend the cross shard receipt to the receiving shard to re-process if + the transaction did not pay out. Parameters ---------- @@ -603,18 +597,17 @@ def resend_cx_receipt( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception ############################ # Staking Transaction RPCs # ############################ def get_staking_transaction_by_hash( - tx_hash, endpoint=_default_endpoint, timeout=_default_timeout + tx_hash, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> dict: - """ - Get staking transaction by hash + """Get staking transaction by hash. Parameters ---------- @@ -658,15 +651,14 @@ def get_staking_transaction_by_hash( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_staking_transaction_by_block_hash_and_index( - block_hash, tx_index, endpoint=_default_endpoint, timeout=_default_timeout + block_hash, tx_index, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> dict: - """ - Get staking transaction by block hash and transaction index + """Get staking transaction by block hash and transaction index. Parameters ---------- @@ -698,15 +690,14 @@ def get_staking_transaction_by_block_hash_and_index( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def get_staking_transaction_by_block_number_and_index( - block_num, tx_index, endpoint=_default_endpoint, timeout=_default_timeout + block_num, tx_index, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> dict: - """ - Get staking transaction by block number and transaction index + """Get staking transaction by block number and transaction index. Parameters ---------- @@ -738,15 +729,14 @@ def get_staking_transaction_by_block_number_and_index( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def send_raw_staking_transaction( - raw_tx, endpoint=_default_endpoint, timeout=_default_timeout + raw_tx, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> str: - """ - Send signed staking transaction + """Send signed staking transaction. Parameters ---------- @@ -779,15 +769,14 @@ def send_raw_staking_transaction( return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ "result" ] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + except KeyError as exception: + raise InvalidRPCReplyError(method, endpoint) from exception def send_and_confirm_raw_staking_transaction( - signed_tx, endpoint=_default_endpoint, timeout=_default_timeout + signed_tx, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> list: - """ - Send signed staking transaction and wait for it to be confirmed + """Send signed staking transaction and wait for it to be confirmed. Parameters ---------- diff --git a/pyhmy/util.py b/pyhmy/util.py index d287821..e055b95 100644 --- a/pyhmy/util.py +++ b/pyhmy/util.py @@ -1,10 +1,15 @@ +""" +Basic pyhmy utils like is_shard_active +ONE address format conversion +Chain id (str) to int conversion +""" import json import subprocess import os import sys import datetime -import requests +from eth_utils import to_checksum_address from .blockchain import get_latest_header @@ -18,16 +23,11 @@ from .account import is_valid_address from .bech32.bech32 import bech32_decode, bech32_encode, convertbits -from eth_utils import to_checksum_address - -datetime_format = "%Y-%m-%d %H:%M:%S.%f" - - class Typgpy(str): - """ - Typography constants for pretty printing. + """Typography constants for pretty printing. - Note that an ENDC is needed to mark the end of a 'highlighted' text segment. + Note that an ENDC is needed to mark the end of a 'highlighted' text + segment. """ HEADER = "\033[95m" @@ -40,8 +40,14 @@ class Typgpy(str): UNDERLINE = "\033[4m" -def chain_id_to_int(chainId): - chainIds = dict( +def chain_id_to_int(chain_id): + """ + If chain_id is a string, converts it to int. + If chain_id is an int, returns the int. + + Else raises TypeError + """ + chain_ids = dict( Default=0, EthMainnet=1, Morden=2, @@ -61,15 +67,14 @@ def chain_id_to_int(chainId): ) # do not validate integer chainids, only known strings - if isinstance(chainId, str): + if isinstance(chain_id, str): assert ( - chainId in chainIds - ), f"Chain {chainId} unknown, specify an integer chainId" - return chainIds.get(chainId) - elif isinstance(chainId, int): - return chainId - else: - raise TypeError("chainId must be str or int") + chain_id in chain_ids + ), f"Chain {chain_id} unknown, specify an integer chainId" + return chain_ids.get(chain_id) + if isinstance(chain_id, int): + return chain_id + raise TypeError("chainId must be str or int") def get_gopath(): @@ -87,29 +92,25 @@ def get_goversion(): def convert_one_to_hex(addr): - """ - Given a one address, convert it to hex checksum address - """ + """Given a one address, convert it to hex checksum address.""" if not is_valid_address(addr): return to_checksum_address(addr) - hrp, data = bech32_decode(addr) + _, data = bech32_decode(addr) buf = convertbits(data, 5, 8, False) - address = "0x" + "".join("{:02x}".format(x) for x in buf) - return to_checksum_address(address) + address = "0x" + "".join(f"{x:02x}" for x in buf) + return str(to_checksum_address(address)) def convert_hex_to_one(addr): - """ - Given a hex address, convert it to a one address - """ + """Given a hex address, convert it to a one address.""" if is_valid_address(addr): return addr - checksum_addr = to_checksum_address(addr) + checksum_addr = str(to_checksum_address(addr)) data = bytearray.fromhex( checksum_addr[2:] if checksum_addr.startswith("0x") else checksum_addr ) buf = convertbits(data, 8, 5) - return bech32_encode("one", buf) + return str(bech32_encode("one", buf)) def is_active_shard(endpoint, delay_tolerance=60): @@ -146,10 +147,10 @@ def get_bls_build_variables(): subprocess.check_output(["which", "openssl"]) .decode() .strip() - .split("\n")[0] + .split("\n", maxsplit=1)[0] ) - except (IndexError, subprocess.CalledProcessError) as e: - raise RuntimeError("`openssl` not found") from e + except (IndexError, subprocess.CalledProcessError) as exception: + raise RuntimeError("`openssl` not found") from exception hmy_path = f"{get_gopath()}/src/github.com/harmony-one" bls_dir = f"{hmy_path}/bls" mcl_dir = f"{hmy_path}/mcl" @@ -179,6 +180,6 @@ def json_load(string, **kwargs): """ try: return json.loads(string, **kwargs) - except Exception as e: + except Exception as exception: print(f"{Typgpy.FAIL}Could not parse input: '{string}'{Typgpy.ENDC}") - raise e from e + raise exception diff --git a/pyhmy/validator.py b/pyhmy/validator.py index c2178fc..1b77093 100644 --- a/pyhmy/validator.py +++ b/pyhmy/validator.py @@ -1,15 +1,28 @@ +""" +Load validator information from Harmony blockchain +Create and edit validators +""" import json +from decimal import Decimal, InvalidOperation from eth_account.datastructures import SignedTransaction -from decimal import Decimal, InvalidOperation - -from .account import get_balance, is_valid_address +from .account import is_valid_address + +from .constants import ( + DEFAULT_ENDPOINT, + DEFAULT_TIMEOUT, + NAME_CHAR_LIMIT, + IDENTITY_CHAR_LIMIT, + WEBSITE_CHAR_LIMIT, + SECURITY_CONTACT_CHAR_LIMIT, + DETAILS_CHAR_LIMIT, + MIN_REQUIRED_DELEGATION, +) -from .numbers import convert_one_to_atto +from .exceptions import InvalidValidatorError -from .exceptions import ( - InvalidValidatorError, +from .rpc.exceptions import ( RPCError, RequestsError, RequestsTimeoutError, @@ -21,18 +34,10 @@ from .staking_structures import Directive from .staking_signing import sign_staking_transaction -_default_endpoint = "http://localhost:9500" -_default_timeout = 30 - -# TODO: Add unit testing -class Validator: - - name_char_limit = 140 - identity_char_limit = 140 - website_char_limit = 140 - security_contact_char_limit = 140 - details_char_limit = 280 - min_required_delegation = convert_one_to_atto(10000) # in ATTO +class Validator: # pylint: disable=too-many-instance-attributes, too-many-public-methods + """ + Harmony validator + """ def __init__(self, address): if not isinstance(address, str): @@ -58,8 +63,7 @@ class Validator: self._max_rate = None def _sanitize_input(self, data, check_str=False) -> str: - """ - If data is None, return '' else return data + """If data is None, return '' else return data. Raises ------ @@ -69,14 +73,13 @@ class Validator: if not isinstance(data, str): raise InvalidValidatorError( 3, - f"Expected data to be string to avoid floating point precision issues but got {data}", + "Expected data to be string " + f"to avoid floating point precision issues but got {data}", ) return "" if not data else str(data) def __str__(self) -> str: - """ - Returns JSON string representation of Validator fields - """ + """Returns JSON string representation of Validator fields.""" info = self.export() for key, value in info.items(): if isinstance(value, Decimal): @@ -87,8 +90,7 @@ class Validator: return f"" def get_address(self) -> str: - """ - Get validator address + """Get validator address. Returns ------- @@ -98,8 +100,7 @@ class Validator: return self._address def add_bls_key(self, key) -> bool: - """ - Add BLS public key to validator BLS keys if not already in list + """Add BLS public key to validator BLS keys if not already in list. Returns ------- @@ -113,8 +114,7 @@ class Validator: return False def remove_bls_key(self, key) -> bool: - """ - Remove BLS public key from validator BLS keys if exists + """Remove BLS public key from validator BLS keys if exists. Returns ------- @@ -128,8 +128,7 @@ class Validator: return False def get_bls_keys(self) -> list: - """ - Get list of validator BLS keys + """Get list of validator BLS keys. Returns ------- @@ -139,8 +138,7 @@ class Validator: return self._bls_keys def add_bls_key_sig(self, key) -> bool: - """ - Add BLS public key to validator BLS keys if not already in list + """Add BLS public key to validator BLS keys if not already in list. Returns ------- @@ -154,8 +152,7 @@ class Validator: return False def remove_bls_key_sig(self, key) -> bool: - """ - Remove BLS public key from validator BLS keys if exists + """Remove BLS public key from validator BLS keys if exists. Returns ------- @@ -169,8 +166,7 @@ class Validator: return False def get_bls_key_sigs(self) -> list: - """ - Get list of validator BLS keys + """Get list of validator BLS keys. Returns ------- @@ -180,8 +176,7 @@ class Validator: return self._bls_key_sigs def set_name(self, name): - """ - Set validator name + """Set validator name. Parameters ---------- @@ -194,15 +189,14 @@ class Validator: If input is invalid """ name = self._sanitize_input(name) - if len(name) > self.name_char_limit: + if len(name) > NAME_CHAR_LIMIT: raise InvalidValidatorError( - 3, f"Name must be less than {self.name_char_limit} characters" + 3, f"Name must be less than {NAME_CHAR_LIMIT} characters" ) self._name = name def get_name(self) -> str: - """ - Get validator name + """Get validator name. Returns ------- @@ -212,8 +206,7 @@ class Validator: return self._name def set_identity(self, identity): - """ - Set validator identity + """Set validator identity. Parameters ---------- @@ -226,15 +219,14 @@ class Validator: If input is invalid """ identity = self._sanitize_input(identity) - if len(identity) > self.identity_char_limit: + if len(identity) > IDENTITY_CHAR_LIMIT: raise InvalidValidatorError( - 3, f"Identity must be less than {self.identity_char_limit} characters" + 3, f"Identity must be less than {IDENTITY_CHAR_LIMIT} characters" ) self._identity = identity def get_identity(self) -> str: - """ - Get validator identity + """Get validator identity. Returns ------- @@ -244,8 +236,7 @@ class Validator: return self._identity def set_website(self, website): - """ - Set validator website + """Set validator website. Parameters ---------- @@ -258,15 +249,14 @@ class Validator: If input is invalid """ website = self._sanitize_input(website) - if len(website) > self.website_char_limit: + if len(website) > WEBSITE_CHAR_LIMIT: raise InvalidValidatorError( - 3, f"Website must be less than {self.website_char_limit} characters" + 3, f"Website must be less than {WEBSITE_CHAR_LIMIT} characters" ) self._website = website def get_website(self) -> str: - """ - Get validator website + """Get validator website. Returns ------- @@ -276,8 +266,7 @@ class Validator: return self._website def set_security_contact(self, contact): - """ - Set validator security contact + """Set validator security contact. Parameters ---------- @@ -290,16 +279,15 @@ class Validator: If input is invalid """ contact = self._sanitize_input(contact) - if len(contact) > self.security_contact_char_limit: + if len(contact) > SECURITY_CONTACT_CHAR_LIMIT: raise InvalidValidatorError( 3, - f"Security contact must be less than {self.security_contact_char_limit} characters", + f"Security contact must be less than {SECURITY_CONTACT_CHAR_LIMIT} characters", ) self._security_contact = contact def get_security_contact(self) -> str: - """ - Get validator security contact + """Get validator security contact. Returns ------- @@ -309,8 +297,7 @@ class Validator: return self._security_contact def set_details(self, details): - """ - Set validator details + """Set validator details. Parameters ---------- @@ -323,15 +310,14 @@ class Validator: If input is invalid """ details = self._sanitize_input(details) - if len(details) > self.details_char_limit: + if len(details) > DETAILS_CHAR_LIMIT: raise InvalidValidatorError( - 3, f"Details must be less than {self.details_char_limit} characters" + 3, f"Details must be less than {DETAILS_CHAR_LIMIT} characters" ) self._details = details def get_details(self) -> str: - """ - Get validator details + """Get validator details. Returns ------- @@ -341,8 +327,7 @@ class Validator: return self._details def set_min_self_delegation(self, delegation): - """ - Set validator min self delegation + """Set validator min self delegation. Parameters ---------- @@ -357,20 +342,19 @@ class Validator: delegation = self._sanitize_input(delegation) try: delegation = Decimal(delegation) - except (TypeError, InvalidOperation) as e: + except (TypeError, InvalidOperation) as exception: raise InvalidValidatorError( 3, "Min self delegation must be a number" - ) from e - if delegation < self.min_required_delegation: + ) from exception + if delegation < MIN_REQUIRED_DELEGATION: raise InvalidValidatorError( 3, - f"Min self delegation must be greater than {self.min_required_delegation} ATTO", + f"Min self delegation must be greater than {MIN_REQUIRED_DELEGATION} ATTO", ) self._min_self_delegation = delegation def get_min_self_delegation(self) -> Decimal: - """ - Get validator min self delegation + """Get validator min self delegation. Returns ------- @@ -380,8 +364,7 @@ class Validator: return self._min_self_delegation def set_max_total_delegation(self, max_delegation): - """ - Set validator max total delegation + """Set validator max total delegation. Parameters ---------- @@ -396,16 +379,16 @@ class Validator: max_delegation = self._sanitize_input(max_delegation) try: max_delegation = Decimal(max_delegation) - except (TypeError, InvalidOperation) as e: + except (TypeError, InvalidOperation) as exception: raise InvalidValidatorError( 3, "Max total delegation must be a number" - ) from e + ) from exception if self._min_self_delegation: if max_delegation < self._min_self_delegation: raise InvalidValidatorError( 3, - f"Max total delegation must be greater than min self delegation: " - "{self._min_self_delegation}", + "Max total delegation must be greater than min self delegation: " + f"{self._min_self_delegation}", ) else: raise InvalidValidatorError( @@ -414,8 +397,7 @@ class Validator: self._max_total_delegation = max_delegation def get_max_total_delegation(self) -> Decimal: - """ - Get validator max total delegation + """Get validator max total delegation. Returns ------- @@ -425,8 +407,7 @@ class Validator: return self._max_total_delegation def set_amount(self, amount): - """ - Set validator initial delegation amount + """Set validator initial delegation amount. Parameters ---------- @@ -441,8 +422,8 @@ class Validator: amount = self._sanitize_input(amount) try: amount = Decimal(amount) - except (TypeError, InvalidOperation) as e: - raise InvalidValidatorError(3, "Amount must be a number") from e + except (TypeError, InvalidOperation) as exception: + raise InvalidValidatorError(3, "Amount must be a number") from exception if self._min_self_delegation: if amount < self._min_self_delegation: raise InvalidValidatorError( @@ -468,8 +449,7 @@ class Validator: self._inital_delegation = amount def get_amount(self) -> Decimal: - """ - Get validator initial delegation amount + """Get validator initial delegation amount. Returns ------- @@ -479,8 +459,7 @@ class Validator: return self._inital_delegation def set_max_rate(self, rate): - """ - Set validator max commission rate + """Set validator max commission rate. Parameters ---------- @@ -495,15 +474,14 @@ class Validator: rate = self._sanitize_input(rate, True) try: rate = Decimal(rate) - except (TypeError, InvalidOperation) as e: - raise InvalidValidatorError(3, "Max rate must be a number") from e + except (TypeError, InvalidOperation) as exception: + raise InvalidValidatorError(3, "Max rate must be a number") from exception if rate < 0 or rate > 1: raise InvalidValidatorError(3, "Max rate must be between 0 and 1") self._max_rate = rate def get_max_rate(self) -> Decimal: - """ - Get validator max commission rate + """Get validator max commission rate. Returns ------- @@ -513,8 +491,7 @@ class Validator: return self._max_rate def set_max_change_rate(self, rate): - """ - Set validator max commission change rate + """Set validator max commission change rate. Parameters ---------- @@ -529,8 +506,8 @@ class Validator: rate = self._sanitize_input(rate, True) try: rate = Decimal(rate) - except (TypeError, InvalidOperation) as e: - raise InvalidValidatorError(3, "Max change rate must be a number") from e + except (TypeError, InvalidOperation) as exception: + raise InvalidValidatorError(3, "Max change rate must be a number") from exception if rate < 0: raise InvalidValidatorError( 3, "Max change rate must be greater than or equal to 0" @@ -548,8 +525,7 @@ class Validator: self._max_change_rate = rate def get_max_change_rate(self) -> Decimal: - """ - Get validator max commission change rate + """Get validator max commission change rate. Returns ------- @@ -559,8 +535,7 @@ class Validator: return self._max_change_rate def set_rate(self, rate): - """ - Set validator commission rate + """Set validator commission rate. Parameters ---------- @@ -575,8 +550,8 @@ class Validator: rate = self._sanitize_input(rate, True) try: rate = Decimal(rate) - except (TypeError, InvalidOperation) as e: - raise InvalidValidatorError(3, "Rate must be a number") from e + except (TypeError, InvalidOperation) as exception: + raise InvalidValidatorError(3, "Rate must be a number") from exception if rate < 0: raise InvalidValidatorError(3, "Rate must be greater than or equal to 0") if self._max_rate: @@ -589,8 +564,7 @@ class Validator: self._rate = rate def get_rate(self) -> Decimal: - """ - Get validator commission rate + """Get validator commission rate. Returns ------- @@ -600,10 +574,9 @@ class Validator: return self._rate def does_validator_exist( - self, endpoint=_default_endpoint, timeout=_default_timeout + self, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ) -> bool: - """ - Check if validator exists on blockchain + """Check if validator exists on blockchain. Parameters ---------- @@ -628,8 +601,7 @@ class Validator: return False def load(self, info): - """ - Import validator information + """Import validator information. Parameters ---------- @@ -680,16 +652,15 @@ class Validator: self._bls_key_sigs = [] for key in info["bls-key-sigs"]: self.add_bls_key_sig(key) - except KeyError as e: - raise InvalidValidatorError(3, "Info has missing key") from e + except KeyError as exception: + raise InvalidValidatorError(3, "Info has missing key") from exception def load_from_blockchain( - self, endpoint=_default_endpoint, timeout=_default_timeout + self, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT ): - """ - Import validator information from blockchain with given address - At the moment, this is unable to fetch the BLS Signature, which is not implemented - in the Node API + """Import validator information from blockchain with given address At + the moment, this is unable to fetch the BLS Signature, which is not + implemented in the Node API. Parameters ---------- @@ -708,16 +679,16 @@ class Validator: raise InvalidValidatorError( 5, f"Validator does not exist on chain according to {endpoint}" ) - except (RPCError, RequestsError, RequestsTimeoutError) as e: + except (RPCError, RequestsError, RequestsTimeoutError) as exception: raise InvalidValidatorError( 5, "Error requesting validator information" - ) from e + ) from exception try: validator_info = get_validator_information(self._address, endpoint, timeout) - except (RPCError, RequestsError, RequestsTimeoutError) as e: + except (RPCError, RequestsError, RequestsTimeoutError) as exception: raise InvalidValidatorError( 5, "Error requesting validator information" - ) from e + ) from exception # Skip additional sanity checks when importing from chain try: @@ -738,14 +709,13 @@ class Validator: self._max_change_rate = Decimal(info["max-change-rate"]) self._rate = Decimal(info["rate"]) self._bls_keys = info["bls-public-keys"] - except KeyError as e: + except KeyError as exception: raise InvalidValidatorError( 5, "Error importing validator information from RPC result" - ) from e + ) from exception def export(self) -> dict: - """ - Export validator information as dict + """Export validator information as dict. Returns ------- @@ -770,11 +740,11 @@ class Validator: } return info - def sign_create_validator_transaction( + def sign_create_validator_transaction( # pylint: disable=too-many-arguments self, nonce, gas_price, gas_limit, private_key, chain_id=None ) -> SignedTransaction: - """ - Create but not post a transaction to Create the Validator using private_key + """Create but not post a transaction to Create the Validator using + private_key. Returns ------- @@ -799,7 +769,7 @@ class Validator: info["chainId"] = chain_id return sign_staking_transaction(info, private_key) - def sign_edit_validator_transaction( + def sign_edit_validator_transaction( # pylint: disable=too-many-arguments self, nonce, gas_price, @@ -811,8 +781,8 @@ class Validator: private_key, chain_id=None, ) -> SignedTransaction: - """ - Create but not post a transaction to Edit the Validator using private_key + """Create but not post a transaction to Edit the Validator using + private_key. Returns -------