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. 163
      pyhmy/cli.py
  7. 18
      pyhmy/constants.py
  8. 88
      pyhmy/contract.py
  9. 22
      pyhmy/exceptions.py
  10. 64
      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. 69
      pyhmy/util.py
  20. 238
      pyhmy/validator.py

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

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

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

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

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

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

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

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

@ -1,10 +1,11 @@
import requests
"""
RPC Specific Exceptions
"""
import requests
class RPCError(RuntimeError):
"""
Exception raised when RPC call returns an error
"""
"""Exception raised when RPC call returns an error."""
def __init__(self, method, endpoint, error):
self.error = error
@ -12,18 +13,14 @@ class RPCError(RuntimeError):
class RequestsError(requests.exceptions.RequestException):
"""
Wrapper for requests lib exceptions
"""
"""Wrapper for requests lib exceptions."""
def __init__(self, endpoint):
super().__init__(f"Error connecting to {endpoint}")
class RequestsTimeoutError(requests.exceptions.Timeout):
"""
Wrapper for requests lib Timeout exceptions
"""
"""Wrapper for requests lib Timeout exceptions."""
def __init__(self, endpoint):
super().__init__(f"Error connecting to {endpoint}")

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

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

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

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

@ -1,3 +1,9 @@
"""
Helper module for signing Harmony staking transactions
"""
# disable most of the Lint here
# pylint: disable=protected-access,no-member,invalid-name,missing-class-docstring,missing-function-docstring
from enum import Enum, auto
from rlp.sedes import big_endian_int, Binary, CountableList, List, Text
@ -9,16 +15,11 @@ from eth_utils.curried import (
hexstr_if_str,
)
class StakingSettings:
PRECISION = 18
MAX_DECIMAL = 1000000000000000000
# https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L120
class Directive(
Enum
): # https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L120
def _generate_next_value_(name, start, count, last_values):
):
def _generate_next_value_(name, start, count, last_values): # pylint: disable=no-self-argument
return count
CreateValidator = auto()
@ -38,7 +39,6 @@ FORMATTERS = {
"chainId": hexstr_if_str(to_int),
}
class CollectRewards:
@staticmethod
def UnsignedChainId():
@ -159,23 +159,28 @@ class CreateValidator:
"stakeMsg",
List(
[ # list with the following members
# validatorAddress
Binary.fixed_length(
20, allow_empty=True
), # validatorAddress
),
# description is Text of 5 elements
List(
[Text()] * 5, True
), # description is Text of 5 elements
),
# commission rate is made up of 3 integers in an array
List(
[List([big_endian_int], True)] * 3, True
), # commission rate is made up of 3 integers in an array [ [int1], [int2], [int3] ]
),
big_endian_int, # min self delegation
big_endian_int, # max total delegation
# bls-public-keys array of unspecified length, each key of 48
CountableList(
Binary.fixed_length(48, allow_empty=True)
), # bls-public-keys array of unspecified length, each key of 48
),
# bls-key-sigs array of unspecified length, each sig of 96
CountableList(
Binary.fixed_length(96, allow_empty=True)
), # bls-key-sigs array of unspecified length, each sig of 96
),
big_endian_int, # amount
],
True,
@ -233,21 +238,30 @@ class EditValidator:
"stakeMsg",
List(
[ # list with the following members
# validatorAddress
Binary.fixed_length(
20, allow_empty=True
), # validatorAddress
),
# description is Text of 5 elements
List(
[Text()] * 5, True
), # description is Text of 5 elements
List([big_endian_int], True), # new rate is in a list
),
# new rate is in a list
List([big_endian_int], True),
big_endian_int, # min self delegation
big_endian_int, # max total delegation
# slot key to remove
Binary.fixed_length(
48, allow_empty=True
), # slot key to remove
),
# slot key to add
Binary.fixed_length(
48, allow_empty=True
), # slot key to add
),
# slot key to add sig
Binary.fixed_length(
96, allow_empty=True
),
],
True,
),

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

@ -1,10 +1,15 @@
"""
Basic pyhmy utils like is_shard_active
ONE address format conversion
Chain id (str) to int conversion
"""
import json
import subprocess
import os
import sys
import datetime
import requests
from eth_utils import to_checksum_address
from .blockchain import get_latest_header
@ -18,16 +23,11 @@ from .account import is_valid_address
from .bech32.bech32 import bech32_decode, bech32_encode, convertbits
from eth_utils import to_checksum_address
datetime_format = "%Y-%m-%d %H:%M:%S.%f"
class Typgpy(str):
"""
Typography constants for pretty printing.
"""Typography constants for pretty printing.
Note that an ENDC is needed to mark the end of a 'highlighted' text segment.
Note that an ENDC is needed to mark the end of a 'highlighted' text
segment.
"""
HEADER = "\033[95m"
@ -40,8 +40,14 @@ class Typgpy(str):
UNDERLINE = "\033[4m"
def chain_id_to_int(chainId):
chainIds = dict(
def chain_id_to_int(chain_id):
"""
If chain_id is a string, converts it to int.
If chain_id is an int, returns the int.
Else raises TypeError
"""
chain_ids = dict(
Default=0,
EthMainnet=1,
Morden=2,
@ -61,14 +67,13 @@ def chain_id_to_int(chainId):
)
# do not validate integer chainids, only known strings
if isinstance(chainId, str):
if isinstance(chain_id, str):
assert (
chainId in chainIds
), f"Chain {chainId} unknown, specify an integer chainId"
return chainIds.get(chainId)
elif isinstance(chainId, int):
return chainId
else:
chain_id in chain_ids
), f"Chain {chain_id} unknown, specify an integer chainId"
return chain_ids.get(chain_id)
if isinstance(chain_id, int):
return chain_id
raise TypeError("chainId must be str or int")
@ -87,29 +92,25 @@ def get_goversion():
def convert_one_to_hex(addr):
"""
Given a one address, convert it to hex checksum address
"""
"""Given a one address, convert it to hex checksum address."""
if not is_valid_address(addr):
return to_checksum_address(addr)
hrp, data = bech32_decode(addr)
_, data = bech32_decode(addr)
buf = convertbits(data, 5, 8, False)
address = "0x" + "".join("{:02x}".format(x) for x in buf)
return to_checksum_address(address)
address = "0x" + "".join(f"{x:02x}" for x in buf)
return str(to_checksum_address(address))
def convert_hex_to_one(addr):
"""
Given a hex address, convert it to a one address
"""
"""Given a hex address, convert it to a one address."""
if is_valid_address(addr):
return addr
checksum_addr = to_checksum_address(addr)
checksum_addr = str(to_checksum_address(addr))
data = bytearray.fromhex(
checksum_addr[2:] if checksum_addr.startswith("0x") else checksum_addr
)
buf = convertbits(data, 8, 5)
return bech32_encode("one", buf)
return str(bech32_encode("one", buf))
def is_active_shard(endpoint, delay_tolerance=60):
@ -146,10 +147,10 @@ def get_bls_build_variables():
subprocess.check_output(["which", "openssl"])
.decode()
.strip()
.split("\n")[0]
.split("\n", maxsplit=1)[0]
)
except (IndexError, subprocess.CalledProcessError) as e:
raise RuntimeError("`openssl` not found") from e
except (IndexError, subprocess.CalledProcessError) as exception:
raise RuntimeError("`openssl` not found") from exception
hmy_path = f"{get_gopath()}/src/github.com/harmony-one"
bls_dir = f"{hmy_path}/bls"
mcl_dir = f"{hmy_path}/mcl"
@ -179,6 +180,6 @@ def json_load(string, **kwargs):
"""
try:
return json.loads(string, **kwargs)
except Exception as e:
except Exception as exception:
print(f"{Typgpy.FAIL}Could not parse input: '{string}'{Typgpy.ENDC}")
raise e from e
raise exception

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

Loading…
Cancel
Save