chore(lint): resolve `pylint` complaints

pull/35/head
MaxMustermann2 2 years ago
parent a3295b9650
commit 98514c7a2c
No known key found for this signature in database
GPG Key ID: 4F4AB9DB6FF24C94
  1. 5
      pyhmy/__init__.py
  2. 4
      pyhmy/_version.py
  3. 121
      pyhmy/account.py
  4. 1
      pyhmy/bech32/__init__.py
  5. 408
      pyhmy/blockchain.py
  6. 191
      pyhmy/cli.py
  7. 18
      pyhmy/constants.py
  8. 88
      pyhmy/contract.py
  9. 22
      pyhmy/exceptions.py
  10. 120
      pyhmy/logging.py
  11. 11
      pyhmy/numbers.py
  12. 17
      pyhmy/rpc/exceptions.py
  13. 20
      pyhmy/rpc/request.py
  14. 93
      pyhmy/signing.py
  15. 212
      pyhmy/staking.py
  16. 105
      pyhmy/staking_signing.py
  17. 52
      pyhmy/staking_structures.py
  18. 185
      pyhmy/transaction.py
  19. 71
      pyhmy/util.py
  20. 238
      pyhmy/validator.py

@ -1,10 +1,11 @@
"""
`pyhmy` for interacting with the Harmony blockchain
"""
import sys import sys
import warnings import warnings
from ._version import __version__ from ._version import __version__
from .util import Typgpy, get_gopath, get_goversion, get_bls_build_variables, json_load
if sys.version_info.major < 3: if sys.version_info.major < 3:
warnings.simplefilter("always", DeprecationWarning) warnings.simplefilter("always", DeprecationWarning)
warnings.warn( warnings.warn(

@ -1,6 +1,4 @@
""" """Provides pyhmy version information."""
Provides pyhmy version information.
"""
# This file is auto-generated! Do not edit! # This file is auto-generated! Do not edit!
# Use `python -m incremental.update pyhmy` to change this file. # Use `python -m incremental.update pyhmy` to change this file.

@ -1,3 +1,6 @@
"""
Interact with accounts on the Harmony blockchain
"""
from .rpc.request import rpc_request from .rpc.request import rpc_request
from .rpc.exceptions import RPCError, RequestsError, RequestsTimeoutError from .rpc.exceptions import RPCError, RequestsError, RequestsTimeoutError
@ -8,10 +11,7 @@ from .blockchain import get_sharding_structure
from .bech32.bech32 import bech32_decode from .bech32.bech32 import bech32_decode
_default_endpoint = "http://localhost:9500" from .constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT
_default_timeout = 30
_address_length = 42
def is_valid_address(address) -> bool: def is_valid_address(address) -> bool:
""" """
@ -36,9 +36,8 @@ def is_valid_address(address) -> bool:
return True return True
def get_balance(address, endpoint=_default_endpoint, timeout=_default_timeout) -> int: def get_balance(address, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int:
""" """Get current account balance.
Get current account balance
Parameters Parameters
---------- ----------
@ -70,15 +69,14 @@ def get_balance(address, endpoint=_default_endpoint, timeout=_default_timeout) -
method, params=params, endpoint=endpoint, timeout=timeout method, params=params, endpoint=endpoint, timeout=timeout
)["result"] )["result"]
return int(balance) # v2 returns the result as it is return int(balance) # v2 returns the result as it is
except TypeError as e: # check will work if rpc returns None except TypeError as exception: # check will work if rpc returns None
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_balance_by_block( def get_balance_by_block(
address, block_num, endpoint=_default_endpoint, timeout=_default_timeout address, block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> int: ) -> int:
""" """Get account balance for address at a given block number.
Get account balance for address at a given block number
Parameters Parameters
---------- ----------
@ -113,15 +111,14 @@ def get_balance_by_block(
method, params=params, endpoint=endpoint, timeout=timeout method, params=params, endpoint=endpoint, timeout=timeout
)["result"] )["result"]
return int(balance) return int(balance)
except TypeError as e: except TypeError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_account_nonce( 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: ) -> int:
""" """Get the account nonce.
Get the account nonce
Parameters Parameters
---------- ----------
@ -155,26 +152,24 @@ def get_account_nonce(
"result" "result"
] ]
return int(nonce) return int(nonce)
except TypeError as e: except TypeError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_nonce( def get_nonce(
address, block_num="latest", endpoint=_default_endpoint, timeout=_default_timeout address, block_num="latest", endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> int: ) -> int:
""" """See get_account_nonce."""
See get_account_nonce
"""
return get_account_nonce(address, block_num, endpoint, timeout) return get_account_nonce(address, block_num, endpoint, timeout)
def get_transaction_count( def get_transaction_count(
address, block_num, endpoint=_default_endpoint, timeout=_default_timeout address, block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> int: ) -> int:
""" """Get the number of transactions the given address has sent for the given
Get the number of transactions the given address has sent for the given block number block number Legacy for apiv1. For apiv2, please use
Legacy for apiv1. For apiv2, please use get_account_nonce/get_transactions_count/get_staking_transactions_count apis for get_account_nonce/get_transactions_count/get_staking_transactions_count
more granular transaction counts queries apis for more granular transaction counts queries.
Parameters Parameters
---------- ----------
@ -208,15 +203,14 @@ def get_transaction_count(
"result" "result"
] ]
return int(nonce) return int(nonce)
except TypeError as e: except TypeError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_transactions_count( def get_transactions_count(
address, tx_type, endpoint=_default_endpoint, timeout=_default_timeout address, tx_type, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> int: ) -> int:
""" """Get the number of regular transactions from genesis of input type.
Get the number of regular transactions from genesis of input type
Parameters Parameters
---------- ----------
@ -252,15 +246,15 @@ def get_transactions_count(
method, params=params, endpoint=endpoint, timeout=timeout method, params=params, endpoint=endpoint, timeout=timeout
)["result"] )["result"]
return int(tx_count) return int(tx_count)
except TypeError as e: except TypeError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_staking_transactions_count( def get_staking_transactions_count(
address, tx_type, endpoint=_default_endpoint, timeout=_default_timeout address, tx_type, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> int: ) -> int:
""" """Get the number of staking transactions from genesis of input type
Get the number of staking transactions from genesis of input type ("SENT", "RECEIVED", "ALL") ("SENT", "RECEIVED", "ALL")
Parameters Parameters
---------- ----------
@ -296,22 +290,21 @@ def get_staking_transactions_count(
method, params=params, endpoint=endpoint, timeout=timeout method, params=params, endpoint=endpoint, timeout=timeout
)["result"] )["result"]
return int(tx_count) return int(tx_count)
except (KeyError, TypeError) as e: except (KeyError, TypeError) as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_transaction_history( def get_transaction_history( # pylint: disable=too-many-arguments
address, address,
page=0, page=0,
page_size=1000, page_size=1000,
include_full_tx=False, include_full_tx=False,
tx_type="ALL", tx_type="ALL",
order="ASC", order="ASC",
endpoint=_default_endpoint, endpoint=DEFAULT_ENDPOINT,
timeout=_default_timeout, timeout=DEFAULT_TIMEOUT,
) -> list: ) -> list:
""" """Get list of transactions sent and/or received by the account.
Get list of transactions sent and/or received by the account
Parameters Parameters
---------- ----------
@ -369,22 +362,21 @@ def get_transaction_history(
method, params=params, endpoint=endpoint, timeout=timeout method, params=params, endpoint=endpoint, timeout=timeout
) )
return tx_history["result"]["transactions"] return tx_history["result"]["transactions"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_staking_transaction_history( def get_staking_transaction_history( # pylint: disable=too-many-arguments
address, address,
page=0, page=0,
page_size=1000, page_size=1000,
include_full_tx=False, include_full_tx=False,
tx_type="ALL", tx_type="ALL",
order="ASC", order="ASC",
endpoint=_default_endpoint, endpoint=DEFAULT_ENDPOINT,
timeout=_default_timeout, timeout=DEFAULT_TIMEOUT,
) -> list: ) -> list:
""" """Get list of staking transactions sent by the account.
Get list of staking transactions sent by the account
Parameters Parameters
---------- ----------
@ -411,7 +403,8 @@ def get_staking_transaction_history(
------- -------
list of transactions list of transactions
if include_full_tx is True, each transaction is a dictionary with the following kets 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 blockNumber: :obj:`int` Block number that transaction was finalized; None if tx is pending
from: :obj:`str` Wallet address from: :obj:`str` Wallet address
timestamp: :obj:`int` Timestamp in Unix time when transaction was finalized 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 hash: :obj:`str` Transaction hash
nonce: :obj:`int` Wallet nonce for the transaction nonce: :obj:`int` Wallet nonce for the transaction
transactionIndex: :obj:`int` Index of transaction in block; None if tx is pending 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 msg: :obj:`dict` Message attached to the staking transaction
r: :obj:`str` First 32 bytes of the transaction signature r: :obj:`str` First 32 bytes of the transaction signature
s: :obj:`str` Next 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 method, params=params, endpoint=endpoint, timeout=timeout
)["result"] )["result"]
return stx_history["staking_transactions"] return stx_history["staking_transactions"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_balance_on_all_shards( 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: ) -> list:
""" """Get current account balance in all shards & optionally report errors
Get current account balance in all shards & optionally report errors getting account balance for a shard getting account balance for a shard.
Parameters Parameters
---------- ----------
@ -507,10 +501,9 @@ def get_balance_on_all_shards(
def get_total_balance( def get_total_balance(
address, endpoint=_default_endpoint, timeout=_default_timeout address, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> int: ) -> int:
""" """Get total account balance on all shards.
Get total account balance on all shards
Parameters Parameters
---------- ----------
@ -540,5 +533,5 @@ def get_total_balance(
address, skip_error=False, endpoint=endpoint, timeout=timeout address, skip_error=False, endpoint=endpoint, timeout=timeout
) )
return sum(b["balance"] for b in balances) return sum(b["balance"] for b in balances)
except TypeError as e: except TypeError as exception:
raise RuntimeError from e raise RuntimeError from exception

@ -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 .rpc.request import rpc_request
from .exceptions import InvalidRPCReplyError from .exceptions import InvalidRPCReplyError
_default_endpoint = "http://localhost:9500" from .constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT
_default_timeout = 30
############################# #############################
# Node / network level RPCs # # Node / network level RPCs #
############################# #############################
def get_bad_blocks(endpoint=_default_endpoint, timeout=_default_timeout) -> list: def get_bad_blocks(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> list:
""" """[WIP] Get list of bad blocks in memory of specific node Known issues
[WIP] Get list of bad blocks in memory of specific node with RPC not returning correctly.
Known issues with RPC not returning correctly
Parameters Parameters
---------- ----------
@ -36,13 +39,12 @@ def get_bad_blocks(endpoint=_default_endpoint, timeout=_default_timeout) -> list
method = "hmyv2_getCurrentBadBlocks" method = "hmyv2_getCurrentBadBlocks"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def chain_id(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: def chain_id(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict:
""" """Chain id of the chain.
Chain id of the chain
Parameters Parameters
---------- ----------
@ -68,13 +70,12 @@ def chain_id(endpoint=_default_endpoint, timeout=_default_timeout) -> dict:
try: try:
data = rpc_request(method, endpoint=endpoint, timeout=timeout) data = rpc_request(method, endpoint=endpoint, timeout=timeout)
return data["result"] return data["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_node_metadata(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: def get_node_metadata(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict:
""" """Get config for the node.
Get config for the node
Parameters 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 blskey: :obj:`list` of BLS keys on the node
version: :obj:`str` representing the Harmony binary version version: :obj:`str` representing the Harmony binary version
network: :obj:`str` the Network name that the node is on (Mainnet or Testnet) 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-config: :obj:`dict` with the hard fork epochs list, and `chain-id`
chain-id: :obj:`int` Chain ID of the network both as :obj:`int`
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
is-leader: :obj:`bool` Whether the node is currently leader or not is-leader: :obj:`bool` Whether the node is currently leader or not
shard-id: :obj:`int` Shard that the node is on shard-id: :obj:`int` Shard that the node is on
current-epoch: :obj:`int` Current epoch current-epoch: :obj:`int` Current epoch
@ -138,20 +122,19 @@ def get_node_metadata(endpoint=_default_endpoint, timeout=_default_timeout) -> d
API Reference API Reference
------------- -------------
https://api.hmny.io/#03c39b56-8dfc-48ce-bdad-f85776dd8aec 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/v1.10.2/internal/params/config.go#L233
https://github.com/harmony-one/harmony/blob/9f320436ff30d9babd957bc5f2e15a1818c86584/node/api.go#L110 for consensus dict https://github.com/harmony-one/harmony/blob/9f320436ff30d9babd957bc5f2e15a1818c86584/node/api.go#L110
""" """
method = "hmyv2_getNodeMetadata" method = "hmyv2_getNodeMetadata"
try: try:
metadata = rpc_request(method, endpoint=endpoint, timeout=timeout) metadata = rpc_request(method, endpoint=endpoint, timeout=timeout)
return metadata["result"] return metadata["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_peer_info(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: def get_peer_info(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict:
""" """Get peer info for the node.
Get peer info for the node
Parameters Parameters
---------- ----------
@ -183,13 +166,12 @@ def get_peer_info(endpoint=_default_endpoint, timeout=_default_timeout) -> dict:
method = "hmyv2_getPeerInfo" method = "hmyv2_getPeerInfo"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def protocol_version(endpoint=_default_endpoint, timeout=_default_timeout) -> int: def protocol_version(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int:
""" """Get the current Harmony protocol version this node supports.
Get the current Harmony protocol version this node supports
Parameters Parameters
---------- ----------
@ -216,13 +198,12 @@ def protocol_version(endpoint=_default_endpoint, timeout=_default_timeout) -> in
try: try:
value = rpc_request(method, endpoint=endpoint, timeout=timeout) value = rpc_request(method, endpoint=endpoint, timeout=timeout)
return value["result"] return value["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_num_peers(endpoint=_default_endpoint, timeout=_default_timeout) -> int: def get_num_peers(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int:
""" """Get number of peers connected to the node.
Get number of peers connected to the node
Parameters Parameters
---------- ----------
@ -250,13 +231,12 @@ def get_num_peers(endpoint=_default_endpoint, timeout=_default_timeout) -> int:
return int( return int(
rpc_request(method, endpoint=endpoint, timeout=timeout)["result"], 16 rpc_request(method, endpoint=endpoint, timeout=timeout)["result"], 16
) )
except (KeyError, TypeError) as e: except (KeyError, TypeError) as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_version(endpoint=_default_endpoint, timeout=_default_timeout) -> int: def get_version(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int:
""" """Get version of the EVM network (https://chainid.network/)
Get version of the EVM network (https://chainid.network/)
Parameters Parameters
---------- ----------
@ -284,13 +264,12 @@ def get_version(endpoint=_default_endpoint, timeout=_default_timeout) -> int:
return int( return int(
rpc_request(method, endpoint=endpoint, timeout=timeout)["result"], 16 rpc_request(method, endpoint=endpoint, timeout=timeout)["result"], 16
) # this is hexadecimal ) # this is hexadecimal
except (KeyError, TypeError) as e: except (KeyError, TypeError) as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def in_sync(endpoint=_default_endpoint, timeout=_default_timeout) -> bool: def in_sync(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> bool:
""" """Whether the shard chain is in sync or syncing (not out of sync)
Whether the shard chain is in sync or syncing (not out of sync)
Parameters Parameters
---------- ----------
@ -315,13 +294,12 @@ def in_sync(endpoint=_default_endpoint, timeout=_default_timeout) -> bool:
method = "hmyv2_inSync" method = "hmyv2_inSync"
try: try:
return bool(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]) return bool(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"])
except (KeyError, TypeError) as e: except (KeyError, TypeError) as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def beacon_in_sync(endpoint=_default_endpoint, timeout=_default_timeout) -> bool: def beacon_in_sync(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> bool:
""" """Whether the beacon chain is in sync or syncing (not out of sync)
Whether the beacon chain is in sync or syncing (not out of sync)
Parameters Parameters
---------- ----------
@ -346,13 +324,12 @@ def beacon_in_sync(endpoint=_default_endpoint, timeout=_default_timeout) -> bool
method = "hmyv2_beaconInSync" method = "hmyv2_beaconInSync"
try: try:
return bool(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]) return bool(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"])
except (KeyError, TypeError) as e: except (KeyError, TypeError) as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_staking_epoch(endpoint=_default_endpoint, timeout=_default_timeout) -> int: def get_staking_epoch(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int:
""" """Get epoch number when blockchain switches to EPoS election.
Get epoch number when blockchain switches to EPoS election
Parameters Parameters
---------- ----------
@ -383,13 +360,13 @@ def get_staking_epoch(endpoint=_default_endpoint, timeout=_default_timeout) -> i
try: try:
data = rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] data = rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]
return int(data["chain-config"]["staking-epoch"]) return int(data["chain-config"]["staking-epoch"])
except (KeyError, TypeError) as e: except (KeyError, TypeError) as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_prestaking_epoch(endpoint=_default_endpoint, timeout=_default_timeout) -> int: def get_prestaking_epoch(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int:
""" """Get epoch number when blockchain switches to allow staking features
Get epoch number when blockchain switches to allow staking features without election without election.
Parameters Parameters
---------- ----------
@ -420,16 +397,15 @@ def get_prestaking_epoch(endpoint=_default_endpoint, timeout=_default_timeout) -
try: try:
data = rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] data = rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]
return int(data["chain-config"]["prestaking-epoch"]) return int(data["chain-config"]["prestaking-epoch"])
except (KeyError, TypeError) as e: except (KeyError, TypeError) as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
######################## ########################
# Sharding information # # Sharding information #
######################## ########################
def get_shard(endpoint=_default_endpoint, timeout=_default_timeout) -> int: def get_shard(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int:
""" """Get shard ID of the node.
Get shard ID of the node
Parameters Parameters
---------- ----------
@ -457,15 +433,14 @@ def get_shard(endpoint=_default_endpoint, timeout=_default_timeout) -> int:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"][ return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"][
"shard-id" "shard-id"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_sharding_structure( def get_sharding_structure(
endpoint=_default_endpoint, timeout=_default_timeout endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> list: ) -> list:
""" """Get network sharding structure.
Get network sharding structure
Parameters Parameters
---------- ----------
@ -494,16 +469,15 @@ def get_sharding_structure(
method = "hmyv2_getShardingStructure" method = "hmyv2_getShardingStructure"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
############################# #############################
# Current status of network # # Current status of network #
############################# #############################
def get_leader_address(endpoint=_default_endpoint, timeout=_default_timeout) -> str: def get_leader_address(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> str:
""" """Get current leader one address.
Get current leader one address
Parameters Parameters
---------- ----------
@ -529,15 +503,14 @@ def get_leader_address(endpoint=_default_endpoint, timeout=_default_timeout) ->
method = "hmyv2_getLeader" method = "hmyv2_getLeader"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def is_last_block( def is_last_block(
block_num, endpoint=_default_endpoint, timeout=_default_timeout block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> bool: ) -> bool:
""" """If the block at block_num is the last block.
If the block at block_num is the last block
Parameters Parameters
---------- ----------
@ -571,15 +544,14 @@ def is_last_block(
"result" "result"
] ]
) )
except (KeyError, TypeError) as e: except (KeyError, TypeError) as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def epoch_last_block( def epoch_last_block(
epoch, endpoint=_default_endpoint, timeout=_default_timeout epoch, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> int: ) -> int:
""" """Returns the number of the last block in the epoch.
Returns the number of the last block in the epoch
Parameters Parameters
---------- ----------
@ -613,13 +585,12 @@ def epoch_last_block(
"result" "result"
] ]
) )
except (KeyError, TypeError) as e: except (KeyError, TypeError) as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_circulating_supply(endpoint=_default_endpoint, timeout=_default_timeout) -> int: def get_circulating_supply(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int:
""" """Get current circulation supply of tokens in ONE.
Get current circulation supply of tokens in ONE
Parameters Parameters
---------- ----------
@ -645,13 +616,12 @@ def get_circulating_supply(endpoint=_default_endpoint, timeout=_default_timeout)
method = "hmyv2_getCirculatingSupply" method = "hmyv2_getCirculatingSupply"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_total_supply(endpoint=_default_endpoint, timeout=_default_timeout) -> int: def get_total_supply(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int:
""" """Get total number of pre-mined tokens.
Get total number of pre-mined tokens
Parameters Parameters
---------- ----------
@ -677,13 +647,12 @@ def get_total_supply(endpoint=_default_endpoint, timeout=_default_timeout) -> in
method = "hmyv2_getTotalSupply" method = "hmyv2_getTotalSupply"
try: try:
rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_block_number(endpoint=_default_endpoint, timeout=_default_timeout) -> int: def get_block_number(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int:
""" """Get current block number.
Get current block number
Parameters Parameters
---------- ----------
@ -709,13 +678,12 @@ def get_block_number(endpoint=_default_endpoint, timeout=_default_timeout) -> in
method = "hmyv2_blockNumber" method = "hmyv2_blockNumber"
try: try:
return int(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]) return int(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"])
except (KeyError, TypeError) as e: except (KeyError, TypeError) as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_current_epoch(endpoint=_default_endpoint, timeout=_default_timeout) -> int: def get_current_epoch(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int:
""" """Get current epoch number.
Get current epoch number
Parameters Parameters
---------- ----------
@ -741,13 +709,12 @@ def get_current_epoch(endpoint=_default_endpoint, timeout=_default_timeout) -> i
method = "hmyv2_getEpoch" method = "hmyv2_getEpoch"
try: try:
return int(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]) return int(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"])
except (KeyError, TypeError) as e: except (KeyError, TypeError) as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_last_cross_links(endpoint=_default_endpoint, timeout=_default_timeout) -> list: def get_last_cross_links(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> list:
""" """Get last cross shard links.
Get last cross shard links
Parameters Parameters
---------- ----------
@ -780,13 +747,12 @@ def get_last_cross_links(endpoint=_default_endpoint, timeout=_default_timeout) -
method = "hmyv2_getLastCrossLinks" method = "hmyv2_getLastCrossLinks"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_gas_price(endpoint=_default_endpoint, timeout=_default_timeout) -> int: def get_gas_price(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int:
""" """Get network gas price.
Get network gas price
Parameters Parameters
---------- ----------
@ -812,16 +778,15 @@ def get_gas_price(endpoint=_default_endpoint, timeout=_default_timeout) -> int:
method = "hmyv2_gasPrice" method = "hmyv2_gasPrice"
try: try:
return int(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]) return int(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"])
except (KeyError, TypeError) as e: except (KeyError, TypeError) as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
############## ##############
# Block RPCs # # Block RPCs #
############## ##############
def get_latest_header(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: def get_latest_header(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict:
""" """Get block header of latest block.
Get block header of latest block
Parameters Parameters
---------- ----------
@ -836,14 +801,16 @@ def get_latest_header(endpoint=_default_endpoint, timeout=_default_timeout) -> d
blockHash: :obj:`str` Block hash blockHash: :obj:`str` Block hash
blockNumber: :obj:`int` Block number blockNumber: :obj:`int` Block number
shardID: :obj:`int` Shard ID 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 viewID: :obj:`int` View ID of the block
epoch: :obj:`int` Epoch of block epoch: :obj:`int` Epoch of block
timestamp: :obj:`str` Timestamp that the block was finalized in human readable format 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 unixtime: :obj:`int` Timestamp that the block was finalized in Unix time
lastCommitSig: :obj:`str` Hex representation of aggregated signatures of the previous block 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 lastCommitBitmap: :obj:`str`
crossLinks: list of dicts describing the cross shard links, each dict to have the following keys: 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 block-number: :obj:`int` Number of the cross link block
epoch-number: :obj:`int` Epoch of the cross link block epoch-number: :obj:`int` Epoch of the cross link block
hash: :obj:`str` Hash 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" method = "hmyv2_latestHeader"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_header_by_number( def get_header_by_number(
block_num, endpoint=_default_endpoint, timeout=_default_timeout block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> dict: ) -> dict:
""" """Get block header of block at block_num.
Get block header of block at block_num
Parameters Parameters
---------- ----------
@ -901,15 +867,14 @@ def get_header_by_number(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_latest_chain_headers( def get_latest_chain_headers(
endpoint=_default_endpoint, timeout=_default_timeout endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> dict: ) -> dict:
""" """Get block header of latest block for beacon chain & shard chain.
Get block header of latest block for beacon chain & shard chain
Parameters Parameters
---------- ----------
@ -921,7 +886,7 @@ def get_latest_chain_headers(
Returns Returns
------- -------
dict with two keys: 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 shard-chain-header: :obj:`dict` with the following keys, applicable to the shard chain
difficulty: legacy difficulty: legacy
epoch: :obj:`int` Epoch of the block epoch: :obj:`int` Epoch of the block
@ -955,21 +920,20 @@ def get_latest_chain_headers(
method = "hmyv2_getLatestChainHeaders" method = "hmyv2_getLatestChainHeaders"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_block_by_number( def get_block_by_number( # pylint: disable=too-many-arguments
block_num, block_num,
full_tx=False, full_tx=False,
include_tx=False, include_tx=False,
include_staking_tx=False, include_staking_tx=False,
include_signers=False, include_signers=False,
endpoint=_default_endpoint, endpoint=DEFAULT_ENDPOINT,
timeout=_default_timeout, timeout=DEFAULT_TIMEOUT,
) -> dict: ) -> dict:
""" """Get block by number.
Get block by number
Parameters Parameters
---------- ----------
@ -1007,12 +971,14 @@ def get_block_by_number(
signers: :obj:`list` List of signers (only if include_signers is set to True) signers: :obj:`list` List of signers (only if include_signers is set to True)
size: :obj:`int` Block size in bytes size: :obj:`int` Block size in bytes
stakingTransactions: :obj:`list` 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 if full_tx is False: List of staking transaction hashes
stateRoot: :obj:`str` Hash of state root stateRoot: :obj:`str` Hash of state root
timestamp: :obj:`int` Unix timestamp of the block timestamp: :obj:`int` Unix timestamp of the block
transactions: :obj:`list` 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 if full_tx is False: List of transaction hashes
transactionsRoot: :obj:`str` Hash of transactions root transactionsRoot: :obj:`str` Hash of transactions root
uncles: :obj:`str` legacy uncles: :obj:`str` legacy
@ -1042,21 +1008,20 @@ def get_block_by_number(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_block_by_hash( def get_block_by_hash( # pylint: disable=too-many-arguments
block_hash, block_hash,
full_tx=False, full_tx=False,
include_tx=False, include_tx=False,
include_staking_tx=False, include_staking_tx=False,
include_signers=False, include_signers=False,
endpoint=_default_endpoint, endpoint=DEFAULT_ENDPOINT,
timeout=_default_timeout, timeout=DEFAULT_TIMEOUT,
) -> dict: ) -> dict:
""" """Get block by hash.
Get block by hash
Parameters Parameters
---------- ----------
@ -1100,15 +1065,14 @@ def get_block_by_hash(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_block_transaction_count_by_number( def get_block_transaction_count_by_number(
block_num, endpoint=_default_endpoint, timeout=_default_timeout block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> int: ) -> int:
""" """Get transaction count for specific block number.
Get transaction count for specific block number
Parameters Parameters
---------- ----------
@ -1143,15 +1107,14 @@ def get_block_transaction_count_by_number(
"result" "result"
] ]
) )
except (KeyError, TypeError) as e: except (KeyError, TypeError) as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_block_transaction_count_by_hash( def get_block_transaction_count_by_hash(
block_hash, endpoint=_default_endpoint, timeout=_default_timeout block_hash, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> int: ) -> int:
""" """Get transaction count for specific block hash.
Get transaction count for specific block hash
Parameters Parameters
---------- ----------
@ -1186,15 +1149,14 @@ def get_block_transaction_count_by_hash(
"result" "result"
] ]
) )
except (KeyError, TypeError) as e: except (KeyError, TypeError) as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_block_staking_transaction_count_by_number( 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: ) -> int:
""" """Get staking transaction count for specific block number.
Get staking transaction count for specific block number
Parameters Parameters
---------- ----------
@ -1229,15 +1191,14 @@ def get_block_staking_transaction_count_by_number(
"result" "result"
] ]
) )
except (KeyError, TypeError) as e: except (KeyError, TypeError) as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_block_staking_transaction_count_by_hash( 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: ) -> int:
""" """Get staking transaction count for specific block hash.
Get staking transaction count for specific block hash
Parameters Parameters
---------- ----------
@ -1272,22 +1233,21 @@ def get_block_staking_transaction_count_by_hash(
"result" "result"
] ]
) )
except (KeyError, TypeError) as e: except (KeyError, TypeError) as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_blocks( def get_blocks( # pylint: disable=too-many-arguments
start_block, start_block,
end_block, end_block,
full_tx=False, full_tx=False,
include_tx=False, include_tx=False,
include_staking_tx=False, include_staking_tx=False,
include_signers=False, include_signers=False,
endpoint=_default_endpoint, endpoint=DEFAULT_ENDPOINT,
timeout=_default_timeout, timeout=DEFAULT_TIMEOUT,
) -> list: ) -> list:
""" """Get list of blocks from a range.
Get list of blocks from a range
Parameters Parameters
---------- ----------
@ -1336,15 +1296,14 @@ def get_blocks(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_block_signers( def get_block_signers(
block_num, endpoint=_default_endpoint, timeout=_default_timeout block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> list: ) -> list:
""" """Get list of block signers for specific block number.
Get list of block signers for specific block number
Parameters Parameters
---------- ----------
@ -1375,15 +1334,14 @@ def get_block_signers(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_block_signers_keys( def get_block_signers_keys(
block_num, endpoint=_default_endpoint, timeout=_default_timeout block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> list: ) -> 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 Parameters
---------- ----------
@ -1414,15 +1372,15 @@ def get_block_signers_keys(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def is_block_signer( def is_block_signer(
block_num, address, endpoint=_default_endpoint, timeout=_default_timeout block_num, address, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> bool: ) -> bool:
""" """Determine if the account at address is a signer for the block at
Determine if the account at address is a signer for the block at block_num block_num.
Parameters Parameters
---------- ----------
@ -1454,15 +1412,15 @@ def is_block_signer(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_signed_blocks( def get_signed_blocks(
address, endpoint=_default_endpoint, timeout=_default_timeout address, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> bool: ) -> bool:
""" """The number of blocks a particular validator signed for last blocksPeriod
The number of blocks a particular validator signed for last blocksPeriod (1 epoch) (1 epoch)
Parameters Parameters
---------- ----------
@ -1494,13 +1452,12 @@ def get_signed_blocks(
"result" "result"
] ]
) )
except (KeyError, TypeError) as e: except (KeyError, TypeError) as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_validators(epoch, endpoint=_default_endpoint, timeout=_default_timeout) -> dict: def get_validators(epoch, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict:
""" """Get list of validators for specific epoch number.
Get list of validators for specific epoch number
Parameters 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)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_validator_keys( def get_validator_keys(
epoch, endpoint=_default_endpoint, timeout=_default_timeout epoch, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> list: ) -> list:
""" """Get list of validator public bls keys for specific epoch number.
Get list of validator public bls keys for specific epoch number
Parameters Parameters
---------- ----------
@ -1573,5 +1529,5 @@ def get_validator_keys(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception

@ -7,7 +7,8 @@ Example:
Below is a demo of how to import, manage keys, and interact with the CLI:: Below is a demo of how to import, manage keys, and interact with the CLI::
>>> from pyhmy import cli >>> from pyhmy import cli
>>> cli.single_call("hmy keys add test1") >>> 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' craft ... tobacco'
>>> cli.get_accounts_keystore() >>> cli.get_accounts_keystore()
{'test1': 'one1aqfeed538xf7n0cfh60tjaeat7yw333pmj6sfu'} {'test1': 'one1aqfeed538xf7n0cfh60tjaeat7yw333pmj6sfu'}
@ -15,7 +16,7 @@ Example:
>>> cli.get_accounts(check_addr) >>> cli.get_accounts(check_addr)
['test1'] ['test1']
>>> cli.single_call("hmy keys list", timeout=2) >>> 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() >>> cli.get_accounts_keystore()
{} {}
@ -40,14 +41,13 @@ For more details, reference the documentation here: TODO gitbook docs
""" """
import subprocess import subprocess
import pexpect
import os import os
import shutil import shutil
import re import re
import stat import stat
import sys import sys
from multiprocessing import Lock
from pathlib import Path from pathlib import Path
import pexpect
import requests import requests
@ -69,44 +69,61 @@ else:
"libgmpxx.4.dylib", "libgmpxx.4.dylib",
"libmcl.dylib", "libmcl.dylib",
} }
_accounts = {} # Internal accounts keystore, make sure to sync when needed. # 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. _accounts = {}
_binary_path = "hmy" # Internal binary path. # Internal path to account keystore, will match the current binary.
_arg_prefix = "__PYHMY_ARG_PREFIX__" ARG_PREFIX = "__PYHMY_ARG_PREFIX__"
_keystore_cache_lock = Lock() # _keystore_cache_lock = Lock()
environment = os.environ.copy() # The environment for the CLI to execute in. 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.... # completely remove caching...
def _cache_and_lock_accounts_keystore(fn): # 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 # """Internal decorator to cache the accounts keystore and prevent concurrent
prevent concurrent accesses with locks. # accesses with locks."""
""" # cached_accounts = {}
cached_accounts = {} # last_mod = None
last_mod = None
def wrap(*args): # def wrap(*args):
nonlocal last_mod # nonlocal last_mod
_keystore_cache_lock.acquire() # _keystore_cache_lock.acquire()
files_in_dir = str(os.listdir(_account_keystore_path)) # files_in_dir = str(os.listdir(ACCOUNT_KEYSTORE_PATH))
dir_mod_time = str(os.path.getmtime(_account_keystore_path)) # dir_mod_time = str(os.path.getmtime(ACCOUNT_KEYSTORE_PATH))
curr_mod = hash(files_in_dir + dir_mod_time + _binary_path) # curr_mod = hash(files_in_dir + dir_mod_time + BINARY_PATH)
if curr_mod != last_mod: # if curr_mod != last_mod:
cached_accounts.clear() # cached_accounts.clear()
cached_accounts.update(fn(*args)) # cached_accounts.update(fn(*args))
last_mod = curr_mod # last_mod = curr_mod
accounts = cached_accounts.copy() # accounts = cached_accounts.copy()
_keystore_cache_lock.release() # _keystore_cache_lock.release()
return accounts # 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 :returns A dictionary where the keys are the account names/aliases and the
values are their 'one1...' addresses. values are their 'one1...' addresses.
@ -128,32 +145,28 @@ def _get_current_accounts_keystore():
def _set_account_keystore_path(): def _set_account_keystore_path():
""" """Internal function to set the account keystore path according to the
Internal function to set the account keystore path according to the binary. binary."""
"""
global _account_keystore_path
response = single_call("hmy keys location").strip() response = single_call("hmy keys location").strip()
if not os.path.exists(response): if not os.path.exists(response):
os.mkdir(response) os.mkdir(response)
_account_keystore_path = response account_keystore_path(response)
def _sync_accounts(): def _sync_accounts():
""" """Internal function that UPDATES the accounts keystore with the CLI's
Internal function that UPDATES the accounts keystore with the CLI's keystore. keystore."""
"""
new_keystore = _get_current_accounts_keystore() new_keystore = _get_current_accounts_keystore()
for key in new_keystore.keys(): for key, value in new_keystore.items():
if key not in _accounts.keys(): if key not in _accounts:
_accounts[key] = new_keystore[key] _accounts[key] = value
acc_keys_to_remove = [k for k in _accounts.keys() if k not in new_keystore.keys()] acc_keys_to_remove = [k for k in _accounts if k not in new_keystore]
for key in acc_keys_to_remove: for key in acc_keys_to_remove:
del _accounts[key] del _accounts[key]
def _make_call_command(command): 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. underlying pexpect or subprocess call.
Note that single quote is not respected for strings. Note that single quote is not respected for strings.
@ -162,18 +175,20 @@ def _make_call_command(command):
command_toks = command command_toks = command
else: else:
all_strings = sorted( 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): 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_prefix = [el for el in command.split(" ") if el]
command_toks = [] command_toks = []
for el in command_toks_prefix: for element in command_toks_prefix:
if el.startswith(f'"{_arg_prefix}_') and el.endswith(f'"'): if element.startswith(f'"{ARG_PREFIX}_') and element.endswith('"'):
index = int(el.replace(f'"{_arg_prefix}_', "").replace('"', "")) index = int(element.replace(f'"{ARG_PREFIX}_', "").replace('"', ""))
command_toks.append(all_strings[index]) command_toks.append(all_strings[index])
else: else:
command_toks.append(el) command_toks.append(element)
if re.match(".*hmy", command_toks[0]): if re.match(".*hmy", command_toks[0]):
command_toks = command_toks[1:] command_toks = command_toks[1:]
return command_toks return command_toks
@ -197,16 +212,16 @@ def is_valid_binary(path):
path = os.path.realpath(path) path = os.path.realpath(path)
os.chmod(path, os.stat(path).st_mode | stat.S_IEXEC) os.chmod(path, os.stat(path).st_mode | stat.S_IEXEC)
try: try:
proc = subprocess.Popen( with subprocess.Popen(
[path, "version"], [path, "version"],
env=environment, env=environment,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
) ) as proc:
out, err = proc.communicate() _, err = proc.communicate()
if not err: if not err:
return False return False
return "harmony" in err.decode().strip().lower() return "harmony" in err.decode().strip().lower()
except (OSError, subprocess.CalledProcessError, subprocess.SubprocessError): except (OSError, subprocess.CalledProcessError, subprocess.SubprocessError):
return False return False
@ -218,13 +233,12 @@ def set_binary(path):
Note that the exposed keystore will be updated accordingly. Note that the exposed keystore will be updated accordingly.
""" """
global _binary_path
path = os.path.realpath(path) path = os.path.realpath(path)
assert os.path.exists(path) assert os.path.exists(path)
os.chmod(path, os.stat(path).st_mode | stat.S_IEXEC) os.chmod(path, os.stat(path).st_mode | stat.S_IEXEC)
if not is_valid_binary(path): if not is_valid_binary(path):
return False return False
_binary_path = path binary_path(path)
_set_account_keystore_path() _set_account_keystore_path()
_sync_accounts() _sync_accounts()
return True return True
@ -234,33 +248,33 @@ def get_binary_path():
""" """
:return: The absolute path of the CLI binary. :return: The absolute path of the CLI binary.
""" """
return os.path.abspath(_binary_path) return os.path.abspath(binary_path())
def get_version(): def get_version():
""" """
:return: The version string of the CLI binary. :return: The version string of the CLI binary.
""" """
proc = subprocess.Popen( with subprocess.Popen(
[_binary_path, "version"], [binary_path(), "version"],
env=environment, env=environment,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
) ) as proc:
out, err = proc.communicate() _, err = proc.communicate()
if not err: if not err:
raise RuntimeError( raise RuntimeError(
f"Could not get version.\n" f"Could not get version.\n"
f"\tGot exit code {proc.returncode}. Expected non-empty error message." f"\tGot exit code {proc.returncode}. Expected non-empty error message."
) )
return err.decode().strip() return err.decode().strip()
def get_account_keystore_path(): def get_account_keystore_path():
""" """
:return: The absolute path to the account keystore of the CLI binary. :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): def check_address(address):
@ -291,8 +305,7 @@ def get_accounts(address):
def remove_account(name): 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. way to remove an address using the CLI.
:param name: The alias of a key used in the CLI's keystore. :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 :returns: Decoded string of response from hmy CLI call
:raises: RuntimeError if bad command :raises: RuntimeError if bad command
""" """
command_toks = [_binary_path] + _make_call_command(command) command_toks = [binary_path()] + _make_call_command(command)
try: try:
return subprocess.check_output( return subprocess.check_output(
command_toks, env=environment, timeout=timeout command_toks, env=environment, timeout=timeout
@ -350,7 +363,7 @@ def expect_call(command, timeout=60):
command_toks = _make_call_command(command) command_toks = _make_call_command(command)
try: try:
proc = pexpect.spawn( proc = pexpect.spawn(
f"{_binary_path}", command_toks, env=environment, timeout=timeout f"{binary_path()}", command_toks, env=environment, timeout=timeout
) )
proc.delaybeforesend = None proc.delaybeforesend = None
except pexpect.ExceptionPexpect as err: except pexpect.ExceptionPexpect as err:
@ -361,9 +374,8 @@ def expect_call(command, timeout=60):
def download(path="./bin/hmy", replace=True, verbose=True): def download(path="./bin/hmy", replace=True, verbose=True):
""" """Download the CLI binary to the specified path. Related files will be
Download the CLI binary to the specified path. saved in the same directory.
Related files will be saved in the same directory.
:param path: The desired path (absolute or relative) of the saved binary. :param path: The desired path (absolute or relative) of the saved binary.
:param replace: A flag to force a replacement of the binary/file. :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.makedirs(parent_dir, exist_ok=True)
os.chdir(parent_dir) os.chdir(parent_dir)
hmy_script_path = os.path.join(parent_dir, "hmy.sh") hmy_script_path = os.path.join(parent_dir, "hmy.sh")
with open(hmy_script_path, "w") as f: with open(hmy_script_path, "w", encoding='utf8') as script_file:
f.write( script_file.write(
requests.get( requests.get(
"https://raw.githubusercontent.com/harmony-one/go-sdk/master/scripts/hmy.sh" "https://raw.githubusercontent.com/harmony-one/go-sdk/master/scripts/hmy.sh"
).content.decode() ).content.decode()
@ -399,11 +411,12 @@ def download(path="./bin/hmy", replace=True, verbose=True):
if verbose: if verbose:
subprocess.call([hmy_script_path, "-d"]) subprocess.call([hmy_script_path, "-d"])
else: else:
subprocess.call( with open(os.devnull, "w", encoding = "UTF-8") as devnull:
[hmy_script_path, "-d"], subprocess.call(
stdout=open(os.devnull, "w"), [hmy_script_path, "-d"],
stderr=subprocess.STDOUT, stdout=devnull,
) stderr=subprocess.STDOUT,
)
os.rename(os.path.join(parent_dir, "hmy"), path) os.rename(os.path.join(parent_dir, "hmy"), path)
if same_name_file: if same_name_file:
os.rename( os.rename(
@ -427,8 +440,8 @@ def download(path="./bin/hmy", replace=True, verbose=True):
raise RuntimeWarning( raise RuntimeWarning(
f"Could not get environment for downloaded hmy CLI at `{path}`" f"Could not get environment for downloaded hmy CLI at `{path}`"
) )
except Exception as e: except Exception as exception:
raise RuntimeWarning( raise RuntimeWarning(
f"Could not get environment for downloaded hmy CLI at `{path}`" f"Could not get environment for downloaded hmy CLI at `{path}`"
) from e ) from exception
return env return env

@ -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)

@ -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 .rpc.request import rpc_request
from .transaction import get_transaction_receipt from .transaction import get_transaction_receipt
from .exceptions import InvalidRPCReplyError from .exceptions import InvalidRPCReplyError
from .constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT
_default_endpoint = "http://localhost:9500"
_default_timeout = 30
######################### #########################
# Smart contract RPCs # Smart contract RPCs
######################### #########################
def call( def call( # pylint: disable=too-many-arguments
to, to_address,
block_num, block_num,
from_address=None, from_address=None,
gas=None, gas=None,
gas_price=None, gas_price=None,
value=None, value=None,
data=None, data=None,
endpoint=_default_endpoint, endpoint=DEFAULT_ENDPOINT,
timeout=_default_timeout, timeout=DEFAULT_TIMEOUT,
) -> str: ) -> str:
""" """Execute a smart contract without saving state.
Execute a smart contract without saving state
Parameters Parameters
---------- ----------
to: :obj:`str` to_address: :obj:`str`
Address of the smart contract Address of the smart contract
block_num: :obj:`int` block_num: :obj:`int`
Block number to execute the contract for Block number to execute the contract for
@ -55,7 +57,7 @@ def call(
Raises Raises
------ ------
InvalidRPCReplyError InvalidRPCReplyError
If received unknown result from endpoint, or If received unknown result from exceptionndpoint, or
API Reference API Reference
------------- -------------
@ -63,7 +65,7 @@ def call(
""" """
params = [ params = [
{ {
"to": to, "to": to_address,
"from": from_address, "from": from_address,
"gas": gas, "gas": gas,
"gasPrice": gas_price, "gasPrice": gas_price,
@ -77,26 +79,25 @@ def call(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def estimate_gas( def estimate_gas( # pylint: disable=too-many-arguments
to, to_address,
from_address=None, from_address=None,
gas=None, gas=None,
gas_price=None, gas_price=None,
value=None, value=None,
data=None, data=None,
endpoint=_default_endpoint, endpoint=DEFAULT_ENDPOINT,
timeout=_default_timeout, timeout=DEFAULT_TIMEOUT,
) -> int: ) -> int:
""" """Estimate the gas price needed for a smart contract call.
Estimate the gas price needed for a smart contract call
Parameters Parameters
---------- ----------
to: :obj:`str` to_address: :obj:`str`
Address of the smart contract Address of the smart contract
from_address: :obj:`str`, optional from_address: :obj:`str`, optional
Wallet address Wallet address
@ -121,7 +122,7 @@ def estimate_gas(
Raises Raises
------ ------
InvalidRPCReplyError InvalidRPCReplyError
If received unknown result from endpoint, or If received unknown result from exceptionndpoint, or
API Reference API Reference
------------- -------------
@ -129,7 +130,7 @@ def estimate_gas(
""" """
params = [ params = [
{ {
"to": to, "to": to_address,
"from": from_address, "from": from_address,
"gas": gas, "gas": gas,
"gasPrice": gas_price, "gasPrice": gas_price,
@ -145,15 +146,15 @@ def estimate_gas(
], ],
16, 16,
) )
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_code( def get_code(
address, block_num, endpoint=_default_endpoint, timeout=_default_timeout address, block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> str: ) -> str:
""" """Get the code stored at the given address in the state for the given
Get the code stored at the given address in the state for the given block number block number.
Parameters Parameters
---------- ----------
@ -174,7 +175,7 @@ def get_code(
Raises Raises
------ ------
InvalidRPCReplyError InvalidRPCReplyError
If received unknown result from endpoint, or If received unknown result from exceptionndpoint, or
API Reference API Reference
------------- -------------
@ -187,15 +188,15 @@ def get_code(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_storage_at( 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: ) -> str:
""" """Get the storage from the state at the given address, the key and the
Get the storage from the state at the given address, the key and the block number block number.
Parameters Parameters
---------- ----------
@ -218,7 +219,7 @@ def get_storage_at(
Raises Raises
------ ------
InvalidRPCReplyError InvalidRPCReplyError
If received unknown result from endpoint, or If received unknown result from exceptionndpoint, or
API Reference API Reference
------------- -------------
@ -231,16 +232,15 @@ def get_storage_at(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_contract_address_from_hash( def get_contract_address_from_hash(
tx_hash, endpoint=_default_endpoint, timeout=_default_timeout tx_hash, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> str: ) -> str:
""" """Get address of the contract which was deployed in the transaction
Get address of the contract which was deployed in the transaction represented by tx_hash.
represented by tx_hash
Parameters Parameters
---------- ----------
@ -259,7 +259,7 @@ def get_contract_address_from_hash(
Raises Raises
------ ------
InvalidRPCReplyError InvalidRPCReplyError
If received unknown result from endpoint, or If received unknown result from exceptionndpoint, or
API Reference API Reference
------------- -------------
@ -267,5 +267,5 @@ def get_contract_address_from_hash(
""" """
try: try:
return get_transaction_receipt(tx_hash, endpoint, timeout)["contractAddress"] return get_transaction_receipt(tx_hash, endpoint, timeout)["contractAddress"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError("hmyv2_getTransactionReceipt", endpoint) from e raise InvalidRPCReplyError("hmyv2_getTransactionReceipt", endpoint) from exception

@ -1,20 +1,18 @@
from .rpc.exceptions import RPCError, RequestsError, RequestsTimeoutError """
Exceptions used by pyhmy
"""
class InvalidRPCReplyError(RuntimeError): class InvalidRPCReplyError(RuntimeError):
""" """Exception raised when RPC call returns unexpected result Generally
Exception raised when RPC call returns unexpected result indicates Harmony API has been updated & pyhmy library needs to be updated
Generally indicates Harmony API has been updated & pyhmy library needs to be updated as well as well."""
"""
def __init__(self, method, endpoint): def __init__(self, method, endpoint):
super().__init__(f"Unexpected reply for {method} from {endpoint}") super().__init__(f"Unexpected reply for {method} from {endpoint}")
class InvalidValidatorError(ValueError): class InvalidValidatorError(ValueError):
""" """Exception raised Validator does not pass sanity checks."""
Exception raised Validator does not pass sanity checks
"""
errors = { errors = {
1: "Invalid ONE address", 1: "Invalid ONE address",
@ -34,10 +32,8 @@ class InvalidValidatorError(ValueError):
class TxConfirmationTimedoutError(AssertionError): class TxConfirmationTimedoutError(AssertionError):
""" """Exception raised when a transaction is sent to the chain But not
Exception raised when a transaction is sent to the chain confirmed during the timeout period specified."""
But not confirmed during the timeout period specified
"""
def __init__(self, msg): def __init__(self, msg):
super().__init__(f"{msg}") super().__init__(f"{msg}")

@ -1,3 +1,7 @@
"""
Logger for pyhmy
"""
import threading import threading
import datetime import datetime
import gzip import gzip
@ -6,21 +10,18 @@ import logging
import logging.handlers import logging.handlers
class _GZipRotator: class _GZipRotator: # pylint: disable=too-few-public-methods
def __call__(self, source, dest): def __call__(self, source, dest):
os.rename(source, dest) os.rename(source, dest)
f_in = open(dest, "rb") with open(dest, "rb") as f_in:
f_out = gzip.open("%s.gz" % dest, "wb") with gzip.open(f"{dest}.gz", "wb") as f_out:
f_out.writelines(f_in) f_out.writelines(f_in)
f_out.close()
f_in.close()
os.remove(dest) os.remove(dest)
class ControlledLogger: class ControlledLogger: # pylint: disable=too-many-instance-attributes
""" """A simple logger that only writes to file when the 'write' method is
A simple logger that only writes to file when the 'write' method is called. called."""
"""
def __init__(self, logger_name, log_dir, backup_count=5): def __init__(self, logger_name, log_dir, backup_count=5):
""" """
@ -51,9 +52,7 @@ class ControlledLogger:
return f"<ControlledLogger @ {self.filepath} : {self.logger}>" return f"<ControlledLogger @ {self.filepath} : {self.logger}>"
def _clear(self): def _clear(self):
""" """Internal method to clear the log buffer."""
Internal method to clear the log buffer.
"""
self.info_buffer.clear() self.info_buffer.clear()
self.debug_buffer.clear() self.debug_buffer.clear()
self.warning_buffer.clear() self.warning_buffer.clear()
@ -63,85 +62,74 @@ class ControlledLogger:
""" """
:param msg: The info message to log :param msg: The info message to log
""" """
self._lock.acquire() with self._lock:
self.info_buffer.append( self.info_buffer.append(
f"[{threading.get_ident()}] " f"{datetime.datetime.utcnow()} : {msg}" f"[{threading.get_ident()}] " f"{datetime.datetime.utcnow()} : {msg}"
) )
self._lock.release()
def debug(self, msg): def debug(self, msg):
""" """
:param msg: The debug message to log :param msg: The debug message to log
""" """
self._lock.acquire() with self._lock:
self.debug_buffer.append( self.debug_buffer.append(
f"[{threading.get_ident()}] " f"{datetime.datetime.utcnow()} : {msg}" f"[{threading.get_ident()}] " f"{datetime.datetime.utcnow()} : {msg}"
) )
self._lock.release()
def warning(self, msg): def warning(self, msg):
""" """
:param msg: The warning message to log :param msg: The warning message to log
""" """
self._lock.acquire() with self._lock:
self.warning_buffer.append( self.warning_buffer.append(
f"[{threading.get_ident()}] " f"{datetime.datetime.utcnow()} : {msg}" f"[{threading.get_ident()}] " f"{datetime.datetime.utcnow()} : {msg}"
) )
self._lock.release()
def error(self, msg): def error(self, msg):
""" """
:param msg: The error message to log :param msg: The error message to log
""" """
self._lock.acquire() with self._lock:
self.error_buffer.append( self.error_buffer.append(
f"[{threading.get_ident()}] " f"{datetime.datetime.utcnow()} : {msg}" f"[{threading.get_ident()}] " f"{datetime.datetime.utcnow()} : {msg}"
) )
self._lock.release()
def print_info(self): 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)) print("\n".join(self.info_buffer))
def print_debug(self): def print_debug(self):
""" """Prints the current debug buffer but does not flush it to log
Prints the current debug buffer but does not flush it to log file. file."""
"""
print("\n".join(self.debug_buffer)) print("\n".join(self.debug_buffer))
def print_warning(self): def print_warning(self):
""" """Prints the current warning buffer but does not flush it to log
Prints the current warning buffer but does not flush it to log file. file."""
"""
print("\n".join(self.warning_buffer)) print("\n".join(self.warning_buffer))
def print_error(self): def print_error(self):
""" """Prints the current error buffer but does not flush it to log
Prints the current error buffer but does not flush it to log file. file."""
"""
print("\n".join(self.error_buffer)) print("\n".join(self.error_buffer))
def write(self): def write(self):
""" """Flushes ALL of the log buffers to the log file via the logger.
Flushes ALL of the log buffers to the log file via the logger.
Note that directly after this method call, the respective prints
Note that directly after this method call, the respective prints will print will print nothing since all log messages are flushed to file.
nothing since all log messages are flushed to file. """
""" with self._lock:
self._lock.acquire() self.logger.setLevel(logging.DEBUG)
self.logger.setLevel(logging.DEBUG) for line in self.debug_buffer:
for line in self.debug_buffer: self.logger.debug(line)
self.logger.debug(line) self.logger.setLevel(logging.WARNING)
self.logger.setLevel(logging.WARNING) for line in self.warning_buffer:
for line in self.warning_buffer: self.logger.warning(line)
self.logger.warning(line) self.logger.setLevel(logging.ERROR)
self.logger.setLevel(logging.ERROR) for line in self.error_buffer:
for line in self.error_buffer: self.logger.error(line)
self.logger.error(line) self.logger.setLevel(logging.INFO)
self.logger.setLevel(logging.INFO) for line in self.info_buffer:
for line in self.info_buffer: self.logger.info(line)
self.logger.info(line) self._clear()
self._clear()
self._lock.release()

@ -1,3 +1,8 @@
"""
Handles conversion of ONE to ATTO and vice versa
For more granular conversions, see Web3.toWei
"""
from decimal import Decimal from decimal import Decimal
@ -5,8 +10,7 @@ _conversion_unit = Decimal(1e18)
def convert_atto_to_one(atto) -> Decimal: def convert_atto_to_one(atto) -> Decimal:
""" """Convert ATTO to ONE.
Convert ATTO to ONE
Parameters Parameters
---------- ----------
@ -25,8 +29,7 @@ def convert_atto_to_one(atto) -> Decimal:
def convert_one_to_atto(one) -> Decimal: def convert_one_to_atto(one) -> Decimal:
""" """Convert ONE to ATTO.
Convert ONE to ATTO
Parameters Parameters
---------- ----------

@ -1,10 +1,11 @@
import requests """
RPC Specific Exceptions
"""
import requests
class RPCError(RuntimeError): 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): def __init__(self, method, endpoint, error):
self.error = error self.error = error
@ -12,18 +13,14 @@ class RPCError(RuntimeError):
class RequestsError(requests.exceptions.RequestException): class RequestsError(requests.exceptions.RequestException):
""" """Wrapper for requests lib exceptions."""
Wrapper for requests lib exceptions
"""
def __init__(self, endpoint): def __init__(self, endpoint):
super().__init__(f"Error connecting to {endpoint}") super().__init__(f"Error connecting to {endpoint}")
class RequestsTimeoutError(requests.exceptions.Timeout): class RequestsTimeoutError(requests.exceptions.Timeout):
""" """Wrapper for requests lib Timeout exceptions."""
Wrapper for requests lib Timeout exceptions
"""
def __init__(self, endpoint): def __init__(self, endpoint):
super().__init__(f"Error connecting to {endpoint}") super().__init__(f"Error connecting to {endpoint}")

@ -1,19 +1,19 @@
"""
RPC wrapper around requests library
"""
import json import json
import requests import requests
from .exceptions import RequestsError, RequestsTimeoutError, RPCError from .exceptions import RequestsError, RequestsTimeoutError, RPCError
from ..constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT
_default_endpoint = "http://localhost:9500"
_default_timeout = 30
def base_request( def base_request(
method, params=None, endpoint=_default_endpoint, timeout=_default_timeout method, params=None, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> str: ) -> str:
""" """Basic RPC request.
Basic RPC request
Parameters Parameters
--------- ---------
@ -65,10 +65,9 @@ def base_request(
def rpc_request( def rpc_request(
method, params=None, endpoint=_default_endpoint, timeout=_default_timeout method, params=None, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> dict: ) -> dict:
""" """RPC request.
RPC request
Parameters Parameters
--------- ---------
@ -110,6 +109,3 @@ def rpc_request(
return resp return resp
except json.decoder.JSONDecodeError as err: except json.decoder.JSONDecodeError as err:
raise RPCError(method, endpoint, raw_resp) from err raise RPCError(method, endpoint, raw_resp) from err
# TODO: Add GET requests

@ -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 import rlp
from eth_utils.curried import keccak, to_int, hexstr_if_str, apply_formatters_to_dict 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 rlp.sedes import big_endian_int, Binary, binary
from eth_account import Account
from eth_rlp import HashableRLP from eth_rlp import HashableRLP
from hexbytes import HexBytes 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 ( from eth_account._utils.legacy_transactions import (
Transaction as SignedEthereumTxData, Transaction as SignedEthereumTxData,
UnsignedTransaction as UnsignedEthereumTxData, UnsignedTransaction as UnsignedEthereumTxData,
LEGACY_TRANSACTION_FORMATTERS as ETHEREUM_FORMATTERS, LEGACY_TRANSACTION_FORMATTERS as ETHEREUM_FORMATTERS,
TRANSACTION_DEFAULTS, TRANSACTION_DEFAULTS,
chain_id_to_v, chain_id_to_v,
UNSIGNED_TRANSACTION_FIELDS,
) )
from eth_account._utils.signing import sign_transaction_hash
from cytoolz import dissoc, pipe, merge, partial
from eth_account.datastructures import SignedTransaction
from .util import chain_id_to_int, convert_one_to_hex from .util import chain_id_to_int, convert_one_to_hex
@ -35,6 +39,11 @@ HARMONY_FORMATTERS = dict(
class UnsignedHarmonyTxData(HashableRLP): class UnsignedHarmonyTxData(HashableRLP):
"""
Unsigned Harmony transaction data
Includes `shardID` and `toShardID`
as the difference against Eth
"""
fields = ( fields = (
("nonce", big_endian_int), ("nonce", big_endian_int),
("gasPrice", big_endian_int), ("gasPrice", big_endian_int),
@ -48,18 +57,23 @@ class UnsignedHarmonyTxData(HashableRLP):
class SignedHarmonyTxData(HashableRLP): class SignedHarmonyTxData(HashableRLP):
"""
Signed Harmony transaction data
Includes `shardID` and `toShardID`
as the difference against Eth
"""
fields = UnsignedHarmonyTxData._meta.fields + ( fields = UnsignedHarmonyTxData._meta.fields + (
("v", big_endian_int), # Recovery value + 27 ("v", big_endian_int), # Recovery value + 27
("r", big_endian_int), # First 32 bytes ("r", big_endian_int), # First 32 bytes
("s", big_endian_int), # Next 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( def encode_transaction(
unsigned_transaction, vrs 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""" """serialize and encode an unsigned transaction with v,r,s."""
(v, r, s) = vrs (v, r, s) = vrs # pylint: disable=invalid-name
chain_naive_transaction = dissoc(unsigned_transaction.as_dict(), "v", "r", "s") chain_naive_transaction = dissoc(unsigned_transaction.as_dict(), "v", "r", "s")
if isinstance(unsigned_transaction, (UnsignedHarmonyTxData, SignedHarmonyTxData)): if isinstance(unsigned_transaction, (UnsignedHarmonyTxData, SignedHarmonyTxData)):
serializer = SignedHarmonyTxData serializer = SignedHarmonyTxData
@ -70,7 +84,7 @@ def encode_transaction(
def serialize_transaction(filled_transaction): def serialize_transaction(filled_transaction):
"""serialize a signed/unsigned transaction""" """serialize a signed/unsigned transaction."""
if "v" in filled_transaction: if "v" in filled_transaction:
if "shardID" in filled_transaction: if "shardID" in filled_transaction:
serializer = SignedHarmonyTxData serializer = SignedHarmonyTxData
@ -81,41 +95,40 @@ def serialize_transaction(filled_transaction):
serializer = UnsignedHarmonyTxData serializer = UnsignedHarmonyTxData
else: else:
serializer = UnsignedEthereumTxData serializer = UnsignedEthereumTxData
for f, _ in serializer._meta.fields: for field, _ in serializer._meta.fields:
assert f in filled_transaction, f"Could not find {f} in transaction" assert field in filled_transaction, f"Could not find {field} in transaction"
return serializer.from_dict( 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): def sanitize_transaction(transaction_dict, private_key):
"""remove the originating address from the dict and convert chainId to int""" """remove the originating address from the dict and convert chainId to
account = Account.from_key( int."""
account = Account.from_key( # pylint: disable=no-value-for-parameter
private_key private_key
) # get account, from which you can derive public + private key )
transaction_dict = transaction_dict.copy() # do not alter the original dictionary sanitized_transaction = transaction_dict.copy() # do not alter the original dictionary
if "from" in transaction_dict: if "from" in sanitized_transaction:
transaction_dict["from"] = convert_one_to_hex(transaction_dict["from"]) sanitized_transaction["from"] = convert_one_to_hex(transaction_dict["from"])
if ( if (
transaction_dict["from"] == account.address sanitized_transaction["from"] == account.address
): # https://github.com/ethereum/eth-account/blob/00e7b10005c5fa7090086fcef37a76296c524e17/eth_account/account.py#L650 ):
sanitized_transaction = dissoc(transaction_dict, "from") sanitized_transaction = dissoc(sanitized_transaction, "from")
else: else:
raise TypeError( raise TypeError(
"from field must match key's %s, but it was %s" "from field must match key's {account.address}, "
% ( "but it was {sanitized_transaction['from']}"
account.address,
transaction_dict["from"],
)
) )
if "chainId" in transaction_dict: if "chainId" in sanitized_transaction:
transaction_dict["chainId"] = chain_id_to_int(transaction_dict["chainId"]) sanitized_transaction["chainId"] = chain_id_to_int(sanitized_transaction["chainId"])
return account, transaction_dict return account, sanitized_transaction
def sign_transaction(transaction_dict, private_key) -> SignedTransaction: def sign_transaction(transaction_dict, private_key) -> SignedTransaction:
""" """Sign a (non-staking) transaction dictionary with the specified private
Sign a (non-staking) transaction dictionary with the specified private key key.
Parameters Parameters
---------- ----------
@ -161,7 +174,8 @@ def sign_transaction(transaction_dict, private_key) -> SignedTransaction:
account, sanitized_transaction = sanitize_transaction(transaction_dict, private_key) account, sanitized_transaction = sanitize_transaction(transaction_dict, private_key)
if "to" in sanitized_transaction and sanitized_transaction["to"] is not None: if "to" in sanitized_transaction and sanitized_transaction["to"] is not None:
sanitized_transaction["to"] = convert_one_to_hex(sanitized_transaction["to"]) 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, sanitized_transaction,
dict, dict,
partial(merge, TRANSACTION_DEFAULTS), partial(merge, TRANSACTION_DEFAULTS),
@ -171,13 +185,14 @@ def sign_transaction(transaction_dict, private_key) -> SignedTransaction:
unsigned_transaction = serialize_transaction(filled_transaction) unsigned_transaction = serialize_transaction(filled_transaction)
transaction_hash = unsigned_transaction.hash() transaction_hash = unsigned_transaction.hash()
# https://github.com/ethereum/eth-account/blob/00e7b10005c5fa7090086fcef37a76296c524e17/eth_account/_utils/signing.py#L26
if isinstance( if isinstance(
unsigned_transaction, (UnsignedEthereumTxData, UnsignedHarmonyTxData) 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: else:
chain_id = unsigned_transaction.v 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)) encoded_transaction = encode_transaction(unsigned_transaction, vrs=(v, r, s))
signed_transaction_hash = keccak(encoded_transaction) signed_transaction_hash = keccak(encoded_transaction)
return SignedTransaction( return SignedTransaction(

@ -1,18 +1,20 @@
"""
Call Harmony's staking API
"""
from .rpc.request import rpc_request from .rpc.request import rpc_request
from .exceptions import InvalidRPCReplyError from .exceptions import InvalidRPCReplyError
_default_endpoint = "http://localhost:9500" from .constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT
_default_timeout = 30
################## ##################
# Validator RPCs # # Validator RPCs #
################## ##################
def get_all_validator_addresses( def get_all_validator_addresses(
endpoint=_default_endpoint, timeout=_default_timeout endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> list: ) -> list:
""" """Get list of all created validator addresses on chain.
Get list of all created validator addresses on chain
Parameters Parameters
---------- ----------
@ -38,15 +40,14 @@ def get_all_validator_addresses(
method = "hmyv2_getAllValidatorAddresses" method = "hmyv2_getAllValidatorAddresses"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_validator_information( def get_validator_information(
validator_addr, endpoint=_default_endpoint, timeout=_default_timeout validator_addr, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> dict: ) -> dict:
""" """Get validator information for validator address.
Get validator information for validator address
Parameters Parameters
---------- ----------
@ -65,7 +66,8 @@ def get_validator_information(
bls-public-keys: :obj:`list` List of associated public BLS keys 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 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 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 rate: :obj:`str` Current commission rate
max-rate: :obj:`str` Max commission rate a validator can charge 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 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 security-contact: :obj:`str` Method to contact the validators
details: :obj:`str` Validator details, displayed on the Staking Dashboard details: :obj:`str` Validator details, displayed on the Staking Dashboard
creation-height: :obj:`int` Block number in which the validator was created 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 delegations: :obj:`list`
metrics: :obj:`dict` BLS key earning metrics for current epoch (or None if no earnings in the current epoch) 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 by-bls-key: :obj:`list` List of dictionaries, each with the following keys
key: :obj:`dict` Dictionary with the following keys key: :obj:`dict` Dictionary with the following keys
bls-public-key: :obj:`str` BLS public key 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 earned-reward: :obj:`int` Lifetime reward key has earned
total-delegation: :obj:`int` Total amount delegated to validator total-delegation: :obj:`int` Total amount delegated to validator
currently-in-committee: :obj:`bool` if key is currently elected 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 epos-winning-stake: :obj:`str` Total effective stake of the validator
booted-status: :obj:`str` Banned status booted-status: :obj:`str` Banned status
active-status: :obj:`str` Active or inactive 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)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_elected_validator_addresses( def get_elected_validator_addresses(
endpoint=_default_endpoint, timeout=_default_timeout endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> list: ) -> list:
""" """Get list of elected validator addresses.
Get list of elected validator addresses
Parameters Parameters
---------- ----------
@ -154,13 +157,12 @@ def get_elected_validator_addresses(
method = "hmyv2_getElectedValidatorAddresses" method = "hmyv2_getElectedValidatorAddresses"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_validators(epoch, endpoint=_default_endpoint, timeout=_default_timeout) -> list: def get_validators(epoch, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> list:
""" """Get validators list for a particular epoch.
Get validators list for a particular epoch
Parameters 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)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_validator_keys( def get_validator_keys(
epoch, endpoint=_default_endpoint, timeout=_default_timeout epoch, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> list: ) -> list:
""" """Get validator BLS keys in the committee for a particular epoch.
Get validator BLS keys in the committee for a particular epoch
Parameters Parameters
---------- ----------
@ -232,15 +233,14 @@ def get_validator_keys(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_validator_information_by_block_number( 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 Parameters
---------- ----------
@ -272,15 +272,14 @@ def get_validator_information_by_block_number(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_all_validator_information( def get_all_validator_information(
page=0, endpoint=_default_endpoint, timeout=_default_timeout page=0, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> list: ) -> list:
""" """Get validator information for all validators on chain.
Get validator information for all validators on chain
Parameters Parameters
---------- ----------
@ -310,15 +309,14 @@ def get_all_validator_information(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_validator_self_delegation( def get_validator_self_delegation(
address, endpoint=_default_endpoint, timeout=_default_timeout address, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> int: ) -> int:
""" """Get the amount self delegated by validator.
Get the amount self delegated by validator
Parameters Parameters
---------- ----------
@ -350,15 +348,14 @@ def get_validator_self_delegation(
"result" "result"
] ]
) )
except (KeyError, TypeError) as e: except (KeyError, TypeError) as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_validator_total_delegation( def get_validator_total_delegation(
address, endpoint=_default_endpoint, timeout=_default_timeout address, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> int: ) -> int:
""" """Get the total amount delegated t ovalidator (including self delegated)
Get the total amount delegated t ovalidator (including self delegated)
Parameters Parameters
---------- ----------
@ -390,15 +387,14 @@ def get_validator_total_delegation(
"result" "result"
] ]
) )
except (KeyError, TypeError) as e: except (KeyError, TypeError) as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_all_validator_information_by_block_number( 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: ) -> list:
""" """Get validator information at block number for all validators on chain.
Get validator information at block number for all validators on chain
Parameters Parameters
---------- ----------
@ -431,18 +427,17 @@ def get_all_validator_information_by_block_number(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
################### ###################
# Delegation RPCs # # Delegation RPCs #
################### ###################
def get_all_delegation_information( def get_all_delegation_information(
page=0, endpoint=_default_endpoint, timeout=_default_timeout page=0, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> list: ) -> list:
""" """Get delegation information for all delegators on chain.
Get delegation information for all delegators on chain
Parameters Parameters
---------- ----------
@ -476,15 +471,14 @@ def get_all_delegation_information(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_delegations_by_delegator( def get_delegations_by_delegator(
delegator_addr, endpoint=_default_endpoint, timeout=_default_timeout delegator_addr, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> list: ) -> list:
""" """Get list of delegations by a delegator.
Get list of delegations by a delegator
Parameters Parameters
---------- ----------
@ -521,15 +515,14 @@ def get_delegations_by_delegator(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_delegations_by_delegator_by_block_number( 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: ) -> list:
""" """Get list of delegations by a delegator at a specific block.
Get list of delegations by a delegator at a specific block
Parameters Parameters
---------- ----------
@ -561,18 +554,17 @@ def get_delegations_by_delegator_by_block_number(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_delegation_by_delegator_and_validator( def get_delegation_by_delegator_and_validator(
delegator_addr, delegator_addr,
validator_address, validator_address,
endpoint=_default_endpoint, endpoint=DEFAULT_ENDPOINT,
timeout=_default_timeout, timeout=DEFAULT_TIMEOUT,
) -> dict: ) -> dict:
""" """Get list of delegations by a delegator at a specific block.
Get list of delegations by a delegator at a specific block
Parameters Parameters
---------- ----------
@ -587,7 +579,8 @@ def get_delegation_by_delegator_and_validator(
Returns 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 Raises
------ ------
@ -604,15 +597,14 @@ def get_delegation_by_delegator_and_validator(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_available_redelegation_balance( def get_available_redelegation_balance(
delegator_addr, endpoint=_default_endpoint, timeout=_default_timeout delegator_addr, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> int: ) -> int:
""" """Get amount of locked undelegated tokens.
Get amount of locked undelegated tokens
Parameters Parameters
---------- ----------
@ -644,15 +636,14 @@ def get_available_redelegation_balance(
"result" "result"
] ]
) )
except (KeyError, TypeError) as e: except (KeyError, TypeError) as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_delegations_by_validator( def get_delegations_by_validator(
validator_addr, endpoint=_default_endpoint, timeout=_default_timeout validator_addr, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> list: ) -> list:
""" """Get list of delegations to a validator.
Get list of delegations to a validator
Parameters Parameters
---------- ----------
@ -682,18 +673,17 @@ def get_delegations_by_validator(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
######################## ########################
# Staking Network RPCs # # Staking Network RPCs #
######################## ########################
def get_current_utility_metrics( def get_current_utility_metrics(
endpoint=_default_endpoint, timeout=_default_timeout endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> dict: ) -> dict:
""" """Get current utility metrics of network.
Get current utility metrics of network
Parameters Parameters
---------- ----------
@ -722,15 +712,14 @@ def get_current_utility_metrics(
method = "hmyv2_getCurrentUtilityMetrics" method = "hmyv2_getCurrentUtilityMetrics"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_staking_network_info( def get_staking_network_info(
endpoint=_default_endpoint, timeout=_default_timeout endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> dict: ) -> dict:
""" """Get staking network information.
Get staking network information
Parameters Parameters
---------- ----------
@ -760,13 +749,12 @@ def get_staking_network_info(
method = "hmyv2_getStakingNetworkInfo" method = "hmyv2_getStakingNetworkInfo"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_super_committees(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: def get_super_committees(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict:
""" """Get voting committees for current & previous epoch.
Get voting committees for current & previous epoch
Parameters Parameters
---------- ----------
@ -814,13 +802,12 @@ def get_super_committees(endpoint=_default_endpoint, timeout=_default_timeout) -
method = "hmyv2_getSuperCommittees" method = "hmyv2_getSuperCommittees"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_total_staking(endpoint=_default_endpoint, timeout=_default_timeout) -> int: def get_total_staking(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int:
""" """Get total staking by validators, only meant to be called on beaconchain.
Get total staking by validators, only meant to be called on beaconchain
Parameters Parameters
---------- ----------
@ -845,15 +832,14 @@ def get_total_staking(endpoint=_default_endpoint, timeout=_default_timeout) -> i
method = "hmyv2_getTotalStaking" method = "hmyv2_getTotalStaking"
try: try:
return int(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]) return int(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"])
except (KeyError, TypeError) as e: except (KeyError, TypeError) as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_raw_median_stake_snapshot( def get_raw_median_stake_snapshot(
endpoint=_default_endpoint, timeout=_default_timeout endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> dict: ) -> dict:
""" """Get median stake & additional committee data of the current epoch.
Get median stake & additional committee data of the current epoch
Parameters Parameters
---------- ----------
@ -891,5 +877,5 @@ def get_raw_median_stake_snapshot(
method = "hmyv2_getMedianRawStakeSnapshot" method = "hmyv2_getMedianRawStakeSnapshot"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception

@ -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, pipe,
dissoc, dissoc,
partial,
merge, merge,
identity, identity,
) )
@ -10,10 +18,6 @@ from hexbytes import HexBytes
import rlp import rlp
import math
from decimal import Decimal
from eth_account.datastructures import SignedTransaction from eth_account.datastructures import SignedTransaction
from eth_account._utils.signing import sign_transaction_hash from eth_account._utils.signing import sign_transaction_hash
@ -30,11 +34,12 @@ from eth_utils.curried import (
apply_formatter_to_array, apply_formatter_to_array,
) )
from .constants import PRECISION, MAX_DECIMAL
from .signing import sanitize_transaction from .signing import sanitize_transaction
from .staking_structures import ( from .staking_structures import (
FORMATTERS, FORMATTERS,
StakingSettings,
Directive, Directive,
CreateValidator, CreateValidator,
EditValidator, EditValidator,
@ -45,12 +50,13 @@ from .staking_structures import (
from .util import convert_one_to_hex 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( def _convert_staking_percentage_to_number(
value, 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
Convert from staking percentage to integer 1000000000000000000. Since Python floats are problematic with precision,
For example, 0.1 becomes 1000000000000000000 this function is used as a workaround.
Parameters Parameters
--------- ---------
@ -90,24 +96,23 @@ def _convert_staking_percentage_to_number(
combined_str += splitted[1] combined_str += splitted[1]
elif len(splitted) > 2: elif len(splitted) > 2:
raise ValueError("Too many periods to be a StakingDecimal string") raise ValueError("Too many periods to be a StakingDecimal string")
if length > StakingSettings.PRECISION: if length > PRECISION:
raise ValueError( 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 += ( combined_str += (
"0" * zeroes_to_add "0" * zeroes_to_add
) # This will not have any periods, so it is effectively a large integer ) # This will not have any periods, so it is effectively a large integer
val = int(combined_str) 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 return val
def _get_account_and_transaction(transaction_dict, private_key): def _get_account_and_transaction(transaction_dict, private_key):
""" """Create account from private key and sanitize the transaction
Create account from private key and sanitize the transaction Sanitization involves removal of 'from' key And conversion of chainId key
Sanitization involves removal of 'from' key from str to int (if present)
And conversion of chainId key from str to int (if present)
Parameters Parameters
---------- ----------
@ -134,10 +139,10 @@ def _get_account_and_transaction(transaction_dict, private_key):
].value # convert to value, like in TypeScript ].value # convert to value, like in TypeScript
return account, sanitized_transaction return account, sanitized_transaction
# pylint: disable=too-many-locals,protected-access,invalid-name
def _sign_transaction_generic(account, sanitized_transaction, parent_serializer): def _sign_transaction_generic(account, sanitized_transaction, parent_serializer):
""" """Sign a generic staking transaction, given the serializer base class and
Sign a generic staking transaction, given the serializer base class and account account.
Paramters Paramters
--------- ---------
@ -167,7 +172,8 @@ def _sign_transaction_generic(account, sanitized_transaction, parent_serializer)
parent_serializer.SignedChainId(), parent_serializer.SignedChainId(),
) # since chain_id_to_v adds v/r/s, unsigned is not used here ) # since chain_id_to_v adds v/r/s, unsigned is not used here
# fill the transaction # 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, sanitized_transaction,
dict, dict,
partial(merge, {"chainId": None}), partial(merge, {"chainId": None}),
@ -175,8 +181,8 @@ def _sign_transaction_generic(account, sanitized_transaction, parent_serializer)
apply_formatters_to_dict(FORMATTERS), apply_formatters_to_dict(FORMATTERS),
) )
# get the unsigned transaction # get the unsigned transaction
for f, _ in unsigned_serializer._meta.fields: for field, _ in unsigned_serializer._meta.fields:
assert f in filled_transaction, f"Could not find {f} in transaction" assert field in filled_transaction, f"Could not find {field} in transaction"
unsigned_transaction = unsigned_serializer.from_dict( unsigned_transaction = unsigned_serializer.from_dict(
{f: filled_transaction[f] for f, _ in unsigned_serializer._meta.fields} {f: filled_transaction[f] for f, _ in unsigned_serializer._meta.fields}
) # drop extras silently ) # drop extras silently
@ -191,11 +197,12 @@ def _sign_transaction_generic(account, sanitized_transaction, parent_serializer)
unsigned_transaction.as_dict(), "v", "r", "s" unsigned_transaction.as_dict(), "v", "r", "s"
) # remove extra v/r/s added by chain_id_to_v ) # remove extra v/r/s added by chain_id_to_v
# serialize it # serialize it
# https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L207
signed_transaction = signed_serializer( signed_transaction = signed_serializer(
v=v v=v
+ ( + (
8 if chain_id is None else 0 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, r=r,
s=s, # in the below statement, remove everything not expected by signed_serializer 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): def _sign_delegate_or_undelegate(transaction_dict, private_key):
""" """Sign a delegate or undelegate transaction See sign_staking_transaction
Sign a delegate or undelegate transaction for details."""
See sign_staking_transaction for details
"""
# preliminary steps # preliminary steps
if transaction_dict["directive"] not in [Directive.Delegate, Directive.Undelegate]: if transaction_dict["directive"] not in [Directive.Delegate, Directive.Undelegate]:
raise TypeError( raise TypeError(
@ -247,10 +252,8 @@ def _sign_delegate_or_undelegate(transaction_dict, private_key, delegate):
def _sign_collect_rewards(transaction_dict, private_key): def _sign_collect_rewards(transaction_dict, private_key):
""" """Sign a collect rewards transaction See sign_staking_transaction for
Sign a collect rewards transaction details."""
See sign_staking_transaction for details
"""
# preliminary steps # preliminary steps
if transaction_dict["directive"] != Directive.CollectRewards: if transaction_dict["directive"] != Directive.CollectRewards:
raise TypeError("Only CollectRewards is supported by _sign_collect_rewards") 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): def _sign_create_validator(transaction_dict, private_key):
""" """Sign a create validator transaction See sign_staking_transaction for
Sign a create validator transaction details."""
See sign_staking_transaction for details
"""
# preliminary steps # preliminary steps
if transaction_dict["directive"] != Directive.CreateValidator: if transaction_dict["directive"] != Directive.CreateValidator:
raise TypeError( raise TypeError(
@ -343,10 +344,8 @@ def _sign_create_validator(transaction_dict, private_key):
def _sign_edit_validator(transaction_dict, private_key): def _sign_edit_validator(transaction_dict, private_key):
""" """Sign an edit validator transaction See sign_staking_transaction for
Sign an edit validator transaction details."""
See sign_staking_transaction for details
"""
# preliminary steps # preliminary steps
if transaction_dict["directive"] != Directive.EditValidator: if transaction_dict["directive"] != Directive.EditValidator:
raise TypeError( raise TypeError(
@ -377,6 +376,7 @@ def _sign_edit_validator(transaction_dict, private_key):
), # max total delegation (in ONE), decimals are silently dropped ), # max total delegation (in ONE), decimals are silently dropped
hexstr_if_str(to_bytes), # key to remove hexstr_if_str(to_bytes), # key to remove
hexstr_if_str(to_bytes), # key to add 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")), 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")), math.floor(sanitized_transaction.pop("max-total-delegation")),
sanitized_transaction.pop("bls-key-to-remove"), sanitized_transaction.pop("bls-key-to-remove"),
sanitized_transaction.pop("bls-key-to-add"), sanitized_transaction.pop("bls-key-to-add"),
sanitized_transaction.pop("bls-key-to-add-sig"),
], ],
) )
return _sign_transaction_generic(account, sanitized_transaction, EditValidator) return _sign_transaction_generic(account, sanitized_transaction, EditValidator)
def sign_staking_transaction(transaction_dict, private_key): 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 Parameters
---------- ----------
@ -464,7 +464,7 @@ def sign_staking_transaction(transaction_dict, private_key):
assert isinstance( assert isinstance(
transaction_dict, dict transaction_dict, dict
), "Only dictionaries are supported" # OrderedDict is a subclass ), "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 "chainId" in transaction_dict, "chainId missing"
assert "directive" in transaction_dict, "Staking transaction type not specified" assert "directive" in transaction_dict, "Staking transaction type not specified"
assert isinstance( assert isinstance(
@ -472,11 +472,12 @@ def sign_staking_transaction(transaction_dict, private_key):
), "Unknown staking transaction type" ), "Unknown staking transaction type"
if transaction_dict["directive"] == Directive.CollectRewards: if transaction_dict["directive"] == Directive.CollectRewards:
return _sign_collect_rewards(transaction_dict, private_key) return _sign_collect_rewards(transaction_dict, private_key)
elif transaction_dict["directive"] == Directive.Delegate: if transaction_dict["directive"] == Directive.Delegate:
return _sign_delegate_or_undelegate(transaction_dict, private_key, True) return _sign_delegate_or_undelegate(transaction_dict, private_key)
elif transaction_dict["directive"] == Directive.Undelegate: if transaction_dict["directive"] == Directive.Undelegate:
return _sign_delegate_or_undelegate(transaction_dict, private_key, False) return _sign_delegate_or_undelegate(transaction_dict, private_key)
elif transaction_dict["directive"] == Directive.CreateValidator: if transaction_dict["directive"] == Directive.CreateValidator:
return _sign_create_validator(transaction_dict, private_key) 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) return _sign_edit_validator(transaction_dict, private_key)
raise ValueError('Unknown staking transaction type')

@ -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 enum import Enum, auto
from rlp.sedes import big_endian_int, Binary, CountableList, List, Text from rlp.sedes import big_endian_int, Binary, CountableList, List, Text
@ -9,16 +15,11 @@ from eth_utils.curried import (
hexstr_if_str, hexstr_if_str,
) )
# https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L120
class StakingSettings:
PRECISION = 18
MAX_DECIMAL = 1000000000000000000
class Directive( class Directive(
Enum 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 return count
CreateValidator = auto() CreateValidator = auto()
@ -38,7 +39,6 @@ FORMATTERS = {
"chainId": hexstr_if_str(to_int), "chainId": hexstr_if_str(to_int),
} }
class CollectRewards: class CollectRewards:
@staticmethod @staticmethod
def UnsignedChainId(): def UnsignedChainId():
@ -159,23 +159,28 @@ class CreateValidator:
"stakeMsg", "stakeMsg",
List( List(
[ # list with the following members [ # list with the following members
# validatorAddress
Binary.fixed_length( Binary.fixed_length(
20, allow_empty=True 20, allow_empty=True
), # validatorAddress ),
# description is Text of 5 elements
List( List(
[Text()] * 5, True [Text()] * 5, True
), # description is Text of 5 elements ),
# commission rate is made up of 3 integers in an array
List( List(
[List([big_endian_int], True)] * 3, True [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, # min self delegation
big_endian_int, # max total delegation big_endian_int, # max total delegation
# bls-public-keys array of unspecified length, each key of 48
CountableList( CountableList(
Binary.fixed_length(48, allow_empty=True) 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( CountableList(
Binary.fixed_length(96, allow_empty=True) Binary.fixed_length(96, allow_empty=True)
), # bls-key-sigs array of unspecified length, each sig of 96 ),
big_endian_int, # amount big_endian_int, # amount
], ],
True, True,
@ -233,21 +238,30 @@ class EditValidator:
"stakeMsg", "stakeMsg",
List( List(
[ # list with the following members [ # list with the following members
# validatorAddress
Binary.fixed_length( Binary.fixed_length(
20, allow_empty=True 20, allow_empty=True
), # validatorAddress ),
# description is Text of 5 elements
List( List(
[Text()] * 5, True [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, # min self delegation
big_endian_int, # max total delegation big_endian_int, # max total delegation
# slot key to remove
Binary.fixed_length( Binary.fixed_length(
48, allow_empty=True 48, allow_empty=True
), # slot key to remove ),
# slot key to add
Binary.fixed_length( Binary.fixed_length(
48, allow_empty=True 48, allow_empty=True
), # slot key to add ),
# slot key to add sig
Binary.fixed_length(
96, allow_empty=True
),
], ],
True, True,
), ),

@ -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 time
import random import random
from .constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT
_default_endpoint = "http://localhost:9500" from .rpc.request import rpc_request
_default_timeout = 30 from .exceptions import TxConfirmationTimedoutError, InvalidRPCReplyError
######################### #########################
# Transaction Pool RPCs # # Transaction Pool RPCs #
######################### #########################
def get_pending_transactions( def get_pending_transactions(
endpoint=_default_endpoint, timeout=_default_timeout endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> list: ) -> list:
""" """Get list of pending transactions.
Get list of pending transactions
Parameters Parameters
---------- ----------
@ -39,15 +40,14 @@ def get_pending_transactions(
method = "hmyv2_pendingTransactions" method = "hmyv2_pendingTransactions"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_transaction_error_sink( def get_transaction_error_sink(
endpoint=_default_endpoint, timeout=_default_timeout endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> list: ) -> list:
""" """Get current transactions error sink.
Get current transactions error sink
Parameters Parameters
---------- ----------
@ -75,15 +75,14 @@ def get_transaction_error_sink(
method = "hmyv2_getCurrentTransactionErrorSink" method = "hmyv2_getCurrentTransactionErrorSink"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_pending_staking_transactions( def get_pending_staking_transactions(
endpoint=_default_endpoint, timeout=_default_timeout endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> list: ) -> list:
""" """Get list of pending staking transactions.
Get list of pending staking transactions
Parameters Parameters
---------- ----------
@ -108,15 +107,14 @@ def get_pending_staking_transactions(
method = "hmyv2_pendingStakingTransactions" method = "hmyv2_pendingStakingTransactions"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_staking_transaction_error_sink( def get_staking_transaction_error_sink(
endpoint=_default_endpoint, timeout=_default_timeout endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> list: ) -> list:
""" """Get current staking transactions error sink.
Get current staking transactions error sink
Parameters Parameters
---------- ----------
@ -145,13 +143,13 @@ def get_staking_transaction_error_sink(
method = "hmyv2_getCurrentStakingErrorSink" method = "hmyv2_getCurrentStakingErrorSink"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_pool_stats(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: def get_pool_stats(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict:
""" """Get stats of the pool, that is, number of pending and queued (non-
Get stats of the pool, that is, number of pending and queued (non-executable) transactions executable) transactions.
Parameters Parameters
---------- ----------
@ -178,18 +176,17 @@ def get_pool_stats(endpoint=_default_endpoint, timeout=_default_timeout) -> dict
method = "hmyv2_getPoolStats" method = "hmyv2_getPoolStats"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
#################### ####################
# Transaction RPCs # # Transaction RPCs #
#################### ####################
def get_transaction_by_hash( def get_transaction_by_hash(
tx_hash, endpoint=_default_endpoint, timeout=_default_timeout tx_hash, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> dict: ) -> dict:
""" """Get transaction by hash.
Get transaction by hash
Parameters Parameters
---------- ----------
@ -239,15 +236,15 @@ def get_transaction_by_hash(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_transaction_by_block_hash_and_index( 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: ) -> dict:
""" """Get transaction based on index in list of transactions in a block by
Get transaction based on index in list of transactions in a block by block hash block hash.
Parameters Parameters
---------- ----------
@ -279,15 +276,15 @@ def get_transaction_by_block_hash_and_index(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_transaction_by_block_number_and_index( 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: ) -> dict:
""" """Get transaction based on index in list of transactions in a block by
Get transaction based on index in list of transactions in a block by block number block number.
Parameters Parameters
---------- ----------
@ -319,15 +316,14 @@ def get_transaction_by_block_number_and_index(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_transaction_receipt( def get_transaction_receipt(
tx_hash, endpoint=_default_endpoint, timeout=_default_timeout tx_hash, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> dict: ) -> dict:
""" """Get transaction receipt corresponding to tx_hash.
Get transaction receipt corresponding to tx_hash
Parameters Parameters
---------- ----------
@ -348,7 +344,9 @@ def get_transaction_receipt(
from: :obj:`str` Sender wallet address from: :obj:`str` Sender wallet address
gasUsed: :obj:`int` Gas used for the transaction gasUsed: :obj:`int` Gas used for the transaction
logs: :obj:`list` List of logs, each being a dict with keys as follows: 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 logsBloom :obj:`str` Bloom logs
shardID :obj:`int` Shard ID shardID :obj:`int` Shard ID
status :obj:`int` Status of transaction (0: pending, 1: success) 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)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def send_raw_transaction( def send_raw_transaction(
signed_tx, endpoint=_default_endpoint, timeout=_default_timeout signed_tx, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> str: ) -> str:
""" """Send signed transaction.
Send signed transaction
Parameters Parameters
---------- ----------
@ -412,15 +409,14 @@ def send_raw_transaction(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def send_and_confirm_raw_transaction( def send_and_confirm_raw_transaction(
signed_tx, endpoint=_default_endpoint, timeout=_default_timeout signed_tx, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> list: ) -> list:
""" """Send signed transaction and wait for it to be confirmed.
Send signed transaction and wait for it to be confirmed
Parameters Parameters
---------- ----------
@ -466,10 +462,9 @@ def send_and_confirm_raw_transaction(
# CrossShard Transaction RPCs # # CrossShard Transaction RPCs #
############################### ###############################
def get_pending_cx_receipts( def get_pending_cx_receipts(
endpoint=_default_endpoint, timeout=_default_timeout endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> list: ) -> list:
""" """Get list of pending cross shard transactions.
Get list of pending cross shard transactions
Parameters Parameters
---------- ----------
@ -483,7 +478,7 @@ def get_pending_cx_receipts(
list of CX receipts, each a dict with the following keys list of CX receipts, each a dict with the following keys
commitBitmap: :obj:`str` Hex represenation of aggregated signature bitmap commitBitmap: :obj:`str` Hex represenation of aggregated signature bitmap
commitSig: :obj:`str` Hex representation of aggregated signature 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 amount: :obj:`int` Amount in ATTO
from: :obj:`str` From address from: :obj:`str` From address
to: :obj:`str` From address to: :obj:`str` From address
@ -517,15 +512,14 @@ def get_pending_cx_receipts(
method = "hmyv2_getPendingCXReceipts" method = "hmyv2_getPendingCXReceipts"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_cx_receipt_by_hash( def get_cx_receipt_by_hash(
cx_hash, endpoint=_default_endpoint, timeout=_default_timeout cx_hash, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> dict: ) -> 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 Parameters
---------- ----------
@ -564,15 +558,15 @@ def get_cx_receipt_by_hash(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def resend_cx_receipt( def resend_cx_receipt(
cx_hash, endpoint=_default_endpoint, timeout=_default_timeout cx_hash, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> bool: ) -> bool:
""" """Resend the cross shard receipt to the receiving shard to re-process if
Resend the cross shard receipt to the receiving shard to re-process if the transaction did not pay out the transaction did not pay out.
Parameters Parameters
---------- ----------
@ -603,18 +597,17 @@ def resend_cx_receipt(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
############################ ############################
# Staking Transaction RPCs # # Staking Transaction RPCs #
############################ ############################
def get_staking_transaction_by_hash( def get_staking_transaction_by_hash(
tx_hash, endpoint=_default_endpoint, timeout=_default_timeout tx_hash, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> dict: ) -> dict:
""" """Get staking transaction by hash.
Get staking transaction by hash
Parameters Parameters
---------- ----------
@ -658,15 +651,14 @@ def get_staking_transaction_by_hash(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_staking_transaction_by_block_hash_and_index( 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: ) -> dict:
""" """Get staking transaction by block hash and transaction index.
Get staking transaction by block hash and transaction index
Parameters Parameters
---------- ----------
@ -698,15 +690,14 @@ def get_staking_transaction_by_block_hash_and_index(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def get_staking_transaction_by_block_number_and_index( 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: ) -> dict:
""" """Get staking transaction by block number and transaction index.
Get staking transaction by block number and transaction index
Parameters Parameters
---------- ----------
@ -738,15 +729,14 @@ def get_staking_transaction_by_block_number_and_index(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def send_raw_staking_transaction( def send_raw_staking_transaction(
raw_tx, endpoint=_default_endpoint, timeout=_default_timeout raw_tx, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> str: ) -> str:
""" """Send signed staking transaction.
Send signed staking transaction
Parameters Parameters
---------- ----------
@ -779,15 +769,14 @@ def send_raw_staking_transaction(
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[
"result" "result"
] ]
except KeyError as e: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from e raise InvalidRPCReplyError(method, endpoint) from exception
def send_and_confirm_raw_staking_transaction( def send_and_confirm_raw_staking_transaction(
signed_tx, endpoint=_default_endpoint, timeout=_default_timeout signed_tx, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> list: ) -> list:
""" """Send signed staking transaction and wait for it to be confirmed.
Send signed staking transaction and wait for it to be confirmed
Parameters Parameters
---------- ----------

@ -1,10 +1,15 @@
"""
Basic pyhmy utils like is_shard_active
ONE address format conversion
Chain id (str) to int conversion
"""
import json import json
import subprocess import subprocess
import os import os
import sys import sys
import datetime import datetime
import requests from eth_utils import to_checksum_address
from .blockchain import get_latest_header 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 .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): 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" HEADER = "\033[95m"
@ -40,8 +40,14 @@ class Typgpy(str):
UNDERLINE = "\033[4m" UNDERLINE = "\033[4m"
def chain_id_to_int(chainId): def chain_id_to_int(chain_id):
chainIds = dict( """
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, Default=0,
EthMainnet=1, EthMainnet=1,
Morden=2, Morden=2,
@ -61,15 +67,14 @@ def chain_id_to_int(chainId):
) )
# do not validate integer chainids, only known strings # do not validate integer chainids, only known strings
if isinstance(chainId, str): if isinstance(chain_id, str):
assert ( assert (
chainId in chainIds chain_id in chain_ids
), f"Chain {chainId} unknown, specify an integer chainId" ), f"Chain {chain_id} unknown, specify an integer chainId"
return chainIds.get(chainId) return chain_ids.get(chain_id)
elif isinstance(chainId, int): if isinstance(chain_id, int):
return chainId return chain_id
else: raise TypeError("chainId must be str or int")
raise TypeError("chainId must be str or int")
def get_gopath(): def get_gopath():
@ -87,29 +92,25 @@ def get_goversion():
def convert_one_to_hex(addr): 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): if not is_valid_address(addr):
return to_checksum_address(addr) return to_checksum_address(addr)
hrp, data = bech32_decode(addr) _, data = bech32_decode(addr)
buf = convertbits(data, 5, 8, False) buf = convertbits(data, 5, 8, False)
address = "0x" + "".join("{:02x}".format(x) for x in buf) address = "0x" + "".join(f"{x:02x}" for x in buf)
return to_checksum_address(address) return str(to_checksum_address(address))
def convert_hex_to_one(addr): 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): if is_valid_address(addr):
return addr return addr
checksum_addr = to_checksum_address(addr) checksum_addr = str(to_checksum_address(addr))
data = bytearray.fromhex( data = bytearray.fromhex(
checksum_addr[2:] if checksum_addr.startswith("0x") else checksum_addr checksum_addr[2:] if checksum_addr.startswith("0x") else checksum_addr
) )
buf = convertbits(data, 8, 5) buf = convertbits(data, 8, 5)
return bech32_encode("one", buf) return str(bech32_encode("one", buf))
def is_active_shard(endpoint, delay_tolerance=60): def is_active_shard(endpoint, delay_tolerance=60):
@ -146,10 +147,10 @@ def get_bls_build_variables():
subprocess.check_output(["which", "openssl"]) subprocess.check_output(["which", "openssl"])
.decode() .decode()
.strip() .strip()
.split("\n")[0] .split("\n", maxsplit=1)[0]
) )
except (IndexError, subprocess.CalledProcessError) as e: except (IndexError, subprocess.CalledProcessError) as exception:
raise RuntimeError("`openssl` not found") from e raise RuntimeError("`openssl` not found") from exception
hmy_path = f"{get_gopath()}/src/github.com/harmony-one" hmy_path = f"{get_gopath()}/src/github.com/harmony-one"
bls_dir = f"{hmy_path}/bls" bls_dir = f"{hmy_path}/bls"
mcl_dir = f"{hmy_path}/mcl" mcl_dir = f"{hmy_path}/mcl"
@ -179,6 +180,6 @@ def json_load(string, **kwargs):
""" """
try: try:
return json.loads(string, **kwargs) return json.loads(string, **kwargs)
except Exception as e: except Exception as exception:
print(f"{Typgpy.FAIL}Could not parse input: '{string}'{Typgpy.ENDC}") print(f"{Typgpy.FAIL}Could not parse input: '{string}'{Typgpy.ENDC}")
raise e from e raise exception

@ -1,15 +1,28 @@
"""
Load validator information from Harmony blockchain
Create and edit validators
"""
import json import json
from decimal import Decimal, InvalidOperation
from eth_account.datastructures import SignedTransaction from eth_account.datastructures import SignedTransaction
from decimal import Decimal, InvalidOperation from .account import is_valid_address
from .account import get_balance, 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 ( from .rpc.exceptions import (
InvalidValidatorError,
RPCError, RPCError,
RequestsError, RequestsError,
RequestsTimeoutError, RequestsTimeoutError,
@ -21,18 +34,10 @@ from .staking_structures import Directive
from .staking_signing import sign_staking_transaction from .staking_signing import sign_staking_transaction
_default_endpoint = "http://localhost:9500" class Validator: # pylint: disable=too-many-instance-attributes, too-many-public-methods
_default_timeout = 30 """
Harmony validator
# 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
def __init__(self, address): def __init__(self, address):
if not isinstance(address, str): if not isinstance(address, str):
@ -58,8 +63,7 @@ class Validator:
self._max_rate = None self._max_rate = None
def _sanitize_input(self, data, check_str=False) -> str: 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 Raises
------ ------
@ -69,14 +73,13 @@ class Validator:
if not isinstance(data, str): if not isinstance(data, str):
raise InvalidValidatorError( raise InvalidValidatorError(
3, 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) return "" if not data else str(data)
def __str__(self) -> str: def __str__(self) -> str:
""" """Returns JSON string representation of Validator fields."""
Returns JSON string representation of Validator fields
"""
info = self.export() info = self.export()
for key, value in info.items(): for key, value in info.items():
if isinstance(value, Decimal): if isinstance(value, Decimal):
@ -87,8 +90,7 @@ class Validator:
return f"<Validator: {hex(id(self))}>" return f"<Validator: {hex(id(self))}>"
def get_address(self) -> str: def get_address(self) -> str:
""" """Get validator address.
Get validator address
Returns Returns
------- -------
@ -98,8 +100,7 @@ class Validator:
return self._address return self._address
def add_bls_key(self, key) -> bool: 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 Returns
------- -------
@ -113,8 +114,7 @@ class Validator:
return False return False
def remove_bls_key(self, key) -> bool: 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 Returns
------- -------
@ -128,8 +128,7 @@ class Validator:
return False return False
def get_bls_keys(self) -> list: def get_bls_keys(self) -> list:
""" """Get list of validator BLS keys.
Get list of validator BLS keys
Returns Returns
------- -------
@ -139,8 +138,7 @@ class Validator:
return self._bls_keys return self._bls_keys
def add_bls_key_sig(self, key) -> bool: 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 Returns
------- -------
@ -154,8 +152,7 @@ class Validator:
return False return False
def remove_bls_key_sig(self, key) -> bool: 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 Returns
------- -------
@ -169,8 +166,7 @@ class Validator:
return False return False
def get_bls_key_sigs(self) -> list: def get_bls_key_sigs(self) -> list:
""" """Get list of validator BLS keys.
Get list of validator BLS keys
Returns Returns
------- -------
@ -180,8 +176,7 @@ class Validator:
return self._bls_key_sigs return self._bls_key_sigs
def set_name(self, name): def set_name(self, name):
""" """Set validator name.
Set validator name
Parameters Parameters
---------- ----------
@ -194,15 +189,14 @@ class Validator:
If input is invalid If input is invalid
""" """
name = self._sanitize_input(name) name = self._sanitize_input(name)
if len(name) > self.name_char_limit: if len(name) > NAME_CHAR_LIMIT:
raise InvalidValidatorError( 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 self._name = name
def get_name(self) -> str: def get_name(self) -> str:
""" """Get validator name.
Get validator name
Returns Returns
------- -------
@ -212,8 +206,7 @@ class Validator:
return self._name return self._name
def set_identity(self, identity): def set_identity(self, identity):
""" """Set validator identity.
Set validator identity
Parameters Parameters
---------- ----------
@ -226,15 +219,14 @@ class Validator:
If input is invalid If input is invalid
""" """
identity = self._sanitize_input(identity) identity = self._sanitize_input(identity)
if len(identity) > self.identity_char_limit: if len(identity) > IDENTITY_CHAR_LIMIT:
raise InvalidValidatorError( 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 self._identity = identity
def get_identity(self) -> str: def get_identity(self) -> str:
""" """Get validator identity.
Get validator identity
Returns Returns
------- -------
@ -244,8 +236,7 @@ class Validator:
return self._identity return self._identity
def set_website(self, website): def set_website(self, website):
""" """Set validator website.
Set validator website
Parameters Parameters
---------- ----------
@ -258,15 +249,14 @@ class Validator:
If input is invalid If input is invalid
""" """
website = self._sanitize_input(website) website = self._sanitize_input(website)
if len(website) > self.website_char_limit: if len(website) > WEBSITE_CHAR_LIMIT:
raise InvalidValidatorError( 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 self._website = website
def get_website(self) -> str: def get_website(self) -> str:
""" """Get validator website.
Get validator website
Returns Returns
------- -------
@ -276,8 +266,7 @@ class Validator:
return self._website return self._website
def set_security_contact(self, contact): def set_security_contact(self, contact):
""" """Set validator security contact.
Set validator security contact
Parameters Parameters
---------- ----------
@ -290,16 +279,15 @@ class Validator:
If input is invalid If input is invalid
""" """
contact = self._sanitize_input(contact) contact = self._sanitize_input(contact)
if len(contact) > self.security_contact_char_limit: if len(contact) > SECURITY_CONTACT_CHAR_LIMIT:
raise InvalidValidatorError( raise InvalidValidatorError(
3, 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 self._security_contact = contact
def get_security_contact(self) -> str: def get_security_contact(self) -> str:
""" """Get validator security contact.
Get validator security contact
Returns Returns
------- -------
@ -309,8 +297,7 @@ class Validator:
return self._security_contact return self._security_contact
def set_details(self, details): def set_details(self, details):
""" """Set validator details.
Set validator details
Parameters Parameters
---------- ----------
@ -323,15 +310,14 @@ class Validator:
If input is invalid If input is invalid
""" """
details = self._sanitize_input(details) details = self._sanitize_input(details)
if len(details) > self.details_char_limit: if len(details) > DETAILS_CHAR_LIMIT:
raise InvalidValidatorError( 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 self._details = details
def get_details(self) -> str: def get_details(self) -> str:
""" """Get validator details.
Get validator details
Returns Returns
------- -------
@ -341,8 +327,7 @@ class Validator:
return self._details return self._details
def set_min_self_delegation(self, delegation): def set_min_self_delegation(self, delegation):
""" """Set validator min self delegation.
Set validator min self delegation
Parameters Parameters
---------- ----------
@ -357,20 +342,19 @@ class Validator:
delegation = self._sanitize_input(delegation) delegation = self._sanitize_input(delegation)
try: try:
delegation = Decimal(delegation) delegation = Decimal(delegation)
except (TypeError, InvalidOperation) as e: except (TypeError, InvalidOperation) as exception:
raise InvalidValidatorError( raise InvalidValidatorError(
3, "Min self delegation must be a number" 3, "Min self delegation must be a number"
) from e ) from exception
if delegation < self.min_required_delegation: if delegation < MIN_REQUIRED_DELEGATION:
raise InvalidValidatorError( raise InvalidValidatorError(
3, 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 self._min_self_delegation = delegation
def get_min_self_delegation(self) -> Decimal: def get_min_self_delegation(self) -> Decimal:
""" """Get validator min self delegation.
Get validator min self delegation
Returns Returns
------- -------
@ -380,8 +364,7 @@ class Validator:
return self._min_self_delegation return self._min_self_delegation
def set_max_total_delegation(self, max_delegation): def set_max_total_delegation(self, max_delegation):
""" """Set validator max total delegation.
Set validator max total delegation
Parameters Parameters
---------- ----------
@ -396,16 +379,16 @@ class Validator:
max_delegation = self._sanitize_input(max_delegation) max_delegation = self._sanitize_input(max_delegation)
try: try:
max_delegation = Decimal(max_delegation) max_delegation = Decimal(max_delegation)
except (TypeError, InvalidOperation) as e: except (TypeError, InvalidOperation) as exception:
raise InvalidValidatorError( raise InvalidValidatorError(
3, "Max total delegation must be a number" 3, "Max total delegation must be a number"
) from e ) from exception
if self._min_self_delegation: if self._min_self_delegation:
if max_delegation < self._min_self_delegation: if max_delegation < self._min_self_delegation:
raise InvalidValidatorError( raise InvalidValidatorError(
3, 3,
f"Max total delegation must be greater than min self delegation: " "Max total delegation must be greater than min self delegation: "
"{self._min_self_delegation}", f"{self._min_self_delegation}",
) )
else: else:
raise InvalidValidatorError( raise InvalidValidatorError(
@ -414,8 +397,7 @@ class Validator:
self._max_total_delegation = max_delegation self._max_total_delegation = max_delegation
def get_max_total_delegation(self) -> Decimal: def get_max_total_delegation(self) -> Decimal:
""" """Get validator max total delegation.
Get validator max total delegation
Returns Returns
------- -------
@ -425,8 +407,7 @@ class Validator:
return self._max_total_delegation return self._max_total_delegation
def set_amount(self, amount): def set_amount(self, amount):
""" """Set validator initial delegation amount.
Set validator initial delegation amount
Parameters Parameters
---------- ----------
@ -441,8 +422,8 @@ class Validator:
amount = self._sanitize_input(amount) amount = self._sanitize_input(amount)
try: try:
amount = Decimal(amount) amount = Decimal(amount)
except (TypeError, InvalidOperation) as e: except (TypeError, InvalidOperation) as exception:
raise InvalidValidatorError(3, "Amount must be a number") from e raise InvalidValidatorError(3, "Amount must be a number") from exception
if self._min_self_delegation: if self._min_self_delegation:
if amount < self._min_self_delegation: if amount < self._min_self_delegation:
raise InvalidValidatorError( raise InvalidValidatorError(
@ -468,8 +449,7 @@ class Validator:
self._inital_delegation = amount self._inital_delegation = amount
def get_amount(self) -> Decimal: def get_amount(self) -> Decimal:
""" """Get validator initial delegation amount.
Get validator initial delegation amount
Returns Returns
------- -------
@ -479,8 +459,7 @@ class Validator:
return self._inital_delegation return self._inital_delegation
def set_max_rate(self, rate): def set_max_rate(self, rate):
""" """Set validator max commission rate.
Set validator max commission rate
Parameters Parameters
---------- ----------
@ -495,15 +474,14 @@ class Validator:
rate = self._sanitize_input(rate, True) rate = self._sanitize_input(rate, True)
try: try:
rate = Decimal(rate) rate = Decimal(rate)
except (TypeError, InvalidOperation) as e: except (TypeError, InvalidOperation) as exception:
raise InvalidValidatorError(3, "Max rate must be a number") from e raise InvalidValidatorError(3, "Max rate must be a number") from exception
if rate < 0 or rate > 1: if rate < 0 or rate > 1:
raise InvalidValidatorError(3, "Max rate must be between 0 and 1") raise InvalidValidatorError(3, "Max rate must be between 0 and 1")
self._max_rate = rate self._max_rate = rate
def get_max_rate(self) -> Decimal: def get_max_rate(self) -> Decimal:
""" """Get validator max commission rate.
Get validator max commission rate
Returns Returns
------- -------
@ -513,8 +491,7 @@ class Validator:
return self._max_rate return self._max_rate
def set_max_change_rate(self, rate): def set_max_change_rate(self, rate):
""" """Set validator max commission change rate.
Set validator max commission change rate
Parameters Parameters
---------- ----------
@ -529,8 +506,8 @@ class Validator:
rate = self._sanitize_input(rate, True) rate = self._sanitize_input(rate, True)
try: try:
rate = Decimal(rate) rate = Decimal(rate)
except (TypeError, InvalidOperation) as e: except (TypeError, InvalidOperation) as exception:
raise InvalidValidatorError(3, "Max change rate must be a number") from e raise InvalidValidatorError(3, "Max change rate must be a number") from exception
if rate < 0: if rate < 0:
raise InvalidValidatorError( raise InvalidValidatorError(
3, "Max change rate must be greater than or equal to 0" 3, "Max change rate must be greater than or equal to 0"
@ -548,8 +525,7 @@ class Validator:
self._max_change_rate = rate self._max_change_rate = rate
def get_max_change_rate(self) -> Decimal: def get_max_change_rate(self) -> Decimal:
""" """Get validator max commission change rate.
Get validator max commission change rate
Returns Returns
------- -------
@ -559,8 +535,7 @@ class Validator:
return self._max_change_rate return self._max_change_rate
def set_rate(self, rate): def set_rate(self, rate):
""" """Set validator commission rate.
Set validator commission rate
Parameters Parameters
---------- ----------
@ -575,8 +550,8 @@ class Validator:
rate = self._sanitize_input(rate, True) rate = self._sanitize_input(rate, True)
try: try:
rate = Decimal(rate) rate = Decimal(rate)
except (TypeError, InvalidOperation) as e: except (TypeError, InvalidOperation) as exception:
raise InvalidValidatorError(3, "Rate must be a number") from e raise InvalidValidatorError(3, "Rate must be a number") from exception
if rate < 0: if rate < 0:
raise InvalidValidatorError(3, "Rate must be greater than or equal to 0") raise InvalidValidatorError(3, "Rate must be greater than or equal to 0")
if self._max_rate: if self._max_rate:
@ -589,8 +564,7 @@ class Validator:
self._rate = rate self._rate = rate
def get_rate(self) -> Decimal: def get_rate(self) -> Decimal:
""" """Get validator commission rate.
Get validator commission rate
Returns Returns
------- -------
@ -600,10 +574,9 @@ class Validator:
return self._rate return self._rate
def does_validator_exist( def does_validator_exist(
self, endpoint=_default_endpoint, timeout=_default_timeout self, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT
) -> bool: ) -> bool:
""" """Check if validator exists on blockchain.
Check if validator exists on blockchain
Parameters Parameters
---------- ----------
@ -628,8 +601,7 @@ class Validator:
return False return False
def load(self, info): def load(self, info):
""" """Import validator information.
Import validator information
Parameters Parameters
---------- ----------
@ -680,16 +652,15 @@ class Validator:
self._bls_key_sigs = [] self._bls_key_sigs = []
for key in info["bls-key-sigs"]: for key in info["bls-key-sigs"]:
self.add_bls_key_sig(key) self.add_bls_key_sig(key)
except KeyError as e: except KeyError as exception:
raise InvalidValidatorError(3, "Info has missing key") from e raise InvalidValidatorError(3, "Info has missing key") from exception
def load_from_blockchain( 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
Import validator information from blockchain with given address the moment, this is unable to fetch the BLS Signature, which is not
At the moment, this is unable to fetch the BLS Signature, which is not implemented implemented in the Node API.
in the Node API
Parameters Parameters
---------- ----------
@ -708,16 +679,16 @@ class Validator:
raise InvalidValidatorError( raise InvalidValidatorError(
5, f"Validator does not exist on chain according to {endpoint}" 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( raise InvalidValidatorError(
5, "Error requesting validator information" 5, "Error requesting validator information"
) from e ) from exception
try: try:
validator_info = get_validator_information(self._address, endpoint, timeout) validator_info = get_validator_information(self._address, endpoint, timeout)
except (RPCError, RequestsError, RequestsTimeoutError) as e: except (RPCError, RequestsError, RequestsTimeoutError) as exception:
raise InvalidValidatorError( raise InvalidValidatorError(
5, "Error requesting validator information" 5, "Error requesting validator information"
) from e ) from exception
# Skip additional sanity checks when importing from chain # Skip additional sanity checks when importing from chain
try: try:
@ -738,14 +709,13 @@ class Validator:
self._max_change_rate = Decimal(info["max-change-rate"]) self._max_change_rate = Decimal(info["max-change-rate"])
self._rate = Decimal(info["rate"]) self._rate = Decimal(info["rate"])
self._bls_keys = info["bls-public-keys"] self._bls_keys = info["bls-public-keys"]
except KeyError as e: except KeyError as exception:
raise InvalidValidatorError( raise InvalidValidatorError(
5, "Error importing validator information from RPC result" 5, "Error importing validator information from RPC result"
) from e ) from exception
def export(self) -> dict: def export(self) -> dict:
""" """Export validator information as dict.
Export validator information as dict
Returns Returns
------- -------
@ -770,11 +740,11 @@ class Validator:
} }
return info 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 self, nonce, gas_price, gas_limit, private_key, chain_id=None
) -> SignedTransaction: ) -> SignedTransaction:
""" """Create but not post a transaction to Create the Validator using
Create but not post a transaction to Create the Validator using private_key private_key.
Returns Returns
------- -------
@ -799,7 +769,7 @@ class Validator:
info["chainId"] = chain_id info["chainId"] = chain_id
return sign_staking_transaction(info, private_key) return sign_staking_transaction(info, private_key)
def sign_edit_validator_transaction( def sign_edit_validator_transaction( # pylint: disable=too-many-arguments
self, self,
nonce, nonce,
gas_price, gas_price,
@ -811,8 +781,8 @@ class Validator:
private_key, private_key,
chain_id=None, chain_id=None,
) -> SignedTransaction: ) -> SignedTransaction:
""" """Create but not post a transaction to Edit the Validator using
Create but not post a transaction to Edit the Validator using private_key private_key.
Returns Returns
------- -------

Loading…
Cancel
Save