chore(yapf): run yapf

pull/35/head
MaxMustermann2 2 years ago
parent 98514c7a2c
commit ca6a6ae1a8
No known key found for this signature in database
GPG Key ID: 4F4AB9DB6FF24C94
  1. 404
      .style.yapf
  2. 14
      pyhmy/__init__.py
  3. 4
      pyhmy/_version.py
  4. 185
      pyhmy/account.py
  5. 116
      pyhmy/bech32/bech32.py
  6. 541
      pyhmy/blockchain.py
  7. 240
      pyhmy/cli.py
  8. 2
      pyhmy/constants.py
  9. 72
      pyhmy/contract.py
  10. 23
      pyhmy/exceptions.py
  11. 98
      pyhmy/logging.py
  12. 19
      pyhmy/numbers.py
  13. 24
      pyhmy/rpc/exceptions.py
  14. 45
      pyhmy/rpc/request.py
  15. 132
      pyhmy/signing.py
  16. 323
      pyhmy/staking.py
  17. 323
      pyhmy/staking_signing.py
  18. 103
      pyhmy/staking_structures.py
  19. 290
      pyhmy/transaction.py
  20. 136
      pyhmy/util.py
  21. 388
      pyhmy/validator.py
  22. 4
      tests/bech32-pyhmy/test_bech32.py
  23. 57
      tests/cli-pyhmy/test_cli.py
  24. 20
      tests/logging-pyhmy/test_logging.py
  25. 32
      tests/numbers-pyhmy/test_numbers.py
  26. 75
      tests/request-pyhmy/test_request.py
  27. 180
      tests/sdk-pyhmy/conftest.py
  28. 145
      tests/sdk-pyhmy/test_account.py
  29. 457
      tests/sdk-pyhmy/test_blockchain.py
  30. 84
      tests/sdk-pyhmy/test_contract.py
  31. 9
      tests/sdk-pyhmy/test_signing.py
  32. 272
      tests/sdk-pyhmy/test_staking.py
  33. 12
      tests/sdk-pyhmy/test_staking_signing.py
  34. 235
      tests/sdk-pyhmy/test_transaction.py
  35. 132
      tests/sdk-pyhmy/test_validator.py
  36. 43
      tests/util-pyhmy/test_util.py

@ -0,0 +1,404 @@
[style]
# Align closing bracket with visual indentation.
align_closing_bracket_with_visual_indent=True
# Allow dictionary keys to exist on multiple lines. For example:
#
# x = {
# ('this is the first element of a tuple',
# 'this is the second element of a tuple'):
# value,
# }
allow_multiline_dictionary_keys=False
# Allow lambdas to be formatted on more than one line.
allow_multiline_lambdas=False
# Allow splitting before a default / named assignment in an argument list.
allow_split_before_default_or_named_assigns=True
# Allow splits before the dictionary value.
allow_split_before_dict_value=False
# Let spacing indicate operator precedence. For example:
#
# a = 1 * 2 + 3 / 4
# b = 1 / 2 - 3 * 4
# c = (1 + 2) * (3 - 4)
# d = (1 - 2) / (3 + 4)
# e = 1 * 2 - 3
# f = 1 + 2 + 3 + 4
#
# will be formatted as follows to indicate precedence:
#
# a = 1*2 + 3/4
# b = 1/2 - 3*4
# c = (1+2) * (3-4)
# d = (1-2) / (3+4)
# e = 1*2 - 3
# f = 1 + 2 + 3 + 4
#
arithmetic_precedence_indication=False
# Number of blank lines surrounding top-level function and class
# definitions.
blank_lines_around_top_level_definition=2
# Number of blank lines between top-level imports and variable
# definitions.
blank_lines_between_top_level_imports_and_variables=1
# Insert a blank line before a class-level docstring.
blank_line_before_class_docstring=False
# Insert a blank line before a module docstring.
blank_line_before_module_docstring=False
# Insert a blank line before a 'def' or 'class' immediately nested
# within another 'def' or 'class'. For example:
#
# class Foo:
# # <------ this blank line
# def method():
# ...
blank_line_before_nested_class_or_def=False
# Do not split consecutive brackets. Only relevant when
# dedent_closing_brackets is set. For example:
#
# call_func_that_takes_a_dict(
# {
# 'key1': 'value1',
# 'key2': 'value2',
# }
# )
#
# would reformat to:
#
# call_func_that_takes_a_dict({
# 'key1': 'value1',
# 'key2': 'value2',
# })
coalesce_brackets=False
# The column limit.
column_limit=80
# The style for continuation alignment. Possible values are:
#
# - SPACE: Use spaces for continuation alignment. This is default behavior.
# - FIXED: Use fixed number (CONTINUATION_INDENT_WIDTH) of columns
# (ie: CONTINUATION_INDENT_WIDTH/INDENT_WIDTH tabs or
# CONTINUATION_INDENT_WIDTH spaces) for continuation alignment.
# - VALIGN-RIGHT: Vertically align continuation lines to multiple of
# INDENT_WIDTH columns. Slightly right (one tab or a few spaces) if
# cannot vertically align continuation lines with indent characters.
continuation_align_style=SPACE
# Indent width used for line continuations.
continuation_indent_width=4
# Put closing brackets on a separate line, dedented, if the bracketed
# expression can't fit in a single line. Applies to all kinds of brackets,
# including function definitions and calls. For example:
#
# config = {
# 'key1': 'value1',
# 'key2': 'value2',
# } # <--- this bracket is dedented and on a separate line
#
# time_series = self.remote_client.query_entity_counters(
# entity='dev3246.region1',
# key='dns.query_latency_tcp',
# transform=Transformation.AVERAGE(window=timedelta(seconds=60)),
# start_ts=now()-timedelta(days=3),
# end_ts=now(),
# ) # <--- this bracket is dedented and on a separate line
dedent_closing_brackets=True
# Disable the heuristic which places each list element on a separate line
# if the list is comma-terminated.
disable_ending_comma_heuristic=True
# Place each dictionary entry onto its own line.
each_dict_entry_on_separate_line=True
# Require multiline dictionary even if it would normally fit on one line.
# For example:
#
# config = {
# 'key1': 'value1'
# }
force_multiline_dict=True
# The regex for an i18n comment. The presence of this comment stops
# reformatting of that line, because the comments are required to be
# next to the string they translate.
i18n_comment=#\..*
# The i18n function call names. The presence of this function stops
# reformattting on that line, because the string it has cannot be moved
# away from the i18n comment.
i18n_function_call=N_, _
# Indent blank lines.
indent_blank_lines=False
# Put closing brackets on a separate line, indented, if the bracketed
# expression can't fit in a single line. Applies to all kinds of brackets,
# including function definitions and calls. For example:
#
# config = {
# 'key1': 'value1',
# 'key2': 'value2',
# } # <--- this bracket is indented and on a separate line
#
# time_series = self.remote_client.query_entity_counters(
# entity='dev3246.region1',
# key='dns.query_latency_tcp',
# transform=Transformation.AVERAGE(window=timedelta(seconds=60)),
# start_ts=now()-timedelta(days=3),
# end_ts=now(),
# ) # <--- this bracket is indented and on a separate line
indent_closing_brackets=False
# Indent the dictionary value if it cannot fit on the same line as the
# dictionary key. For example:
#
# config = {
# 'key1':
# 'value1',
# 'key2': value1 +
# value2,
# }
indent_dictionary_value=False
# The number of columns to use for indentation.
indent_width=4
# Join short lines into one line. E.g., single line 'if' statements.
join_multiple_lines=False
# Do not include spaces around selected binary operators. For example:
#
# 1 + 2 * 3 - 4 / 5
#
# will be formatted as follows when configured with "*,/":
#
# 1 + 2*3 - 4/5
no_spaces_around_selected_binary_operators=
# Use spaces around default or named assigns.
spaces_around_default_or_named_assign=True
# Adds a space after the opening '{' and before the ending '}' dict
# delimiters.
#
# {1: 2}
#
# will be formatted as:
#
# { 1: 2 }
spaces_around_dict_delimiters=True
# Adds a space after the opening '[' and before the ending ']' list
# delimiters.
#
# [1, 2]
#
# will be formatted as:
#
# [ 1, 2 ]
spaces_around_list_delimiters=True
# Use spaces around the power operator.
spaces_around_power_operator=False
# Use spaces around the subscript / slice operator. For example:
#
# my_list[1 : 10 : 2]
spaces_around_subscript_colon=True
# Adds a space after the opening '(' and before the ending ')' tuple
# delimiters.
#
# (1, 2, 3)
#
# will be formatted as:
#
# ( 1, 2, 3 )
spaces_around_tuple_delimiters=True
# The number of spaces required before a trailing comment.
# This can be a single value (representing the number of spaces
# before each trailing comment) or list of values (representing
# alignment column values; trailing comments within a block will
# be aligned to the first column value that is greater than the maximum
# line length within the block). For example:
#
# With spaces_before_comment=5:
#
# 1 + 1 # Adding values
#
# will be formatted as:
#
# 1 + 1 # Adding values <-- 5 spaces between the end of the
# # statement and comment
#
# With spaces_before_comment=15, 20:
#
# 1 + 1 # Adding values
# two + two # More adding
#
# longer_statement # This is a longer statement
# short # This is a shorter statement
#
# a_very_long_statement_that_extends_beyond_the_final_column # Comment
# short # This is a shorter statement
#
# will be formatted as:
#
# 1 + 1 # Adding values <-- end of line comments in block
# # aligned to col 15
# two + two # More adding
#
# longer_statement # This is a longer statement <-- end of line
# # comments in block aligned to col 20
# short # This is a shorter statement
#
# a_very_long_statement_that_extends_beyond_the_final_column # Comment <-- the end of line comments are aligned based on the line length
# short # This is a shorter statement
#
spaces_before_comment=2
# Insert a space between the ending comma and closing bracket of a list,
# etc.
space_between_ending_comma_and_closing_bracket=True
# Use spaces inside brackets, braces, and parentheses. For example:
#
# method_call( 1 )
# my_dict[ 3 ][ 1 ][ get_index( *args, **kwargs ) ]
# my_set = { 1, 2, 3 }
space_inside_brackets=True
# Split before arguments
split_all_comma_separated_values=True
# Split before arguments, but do not split all subexpressions recursively
# (unless needed).
split_all_top_level_comma_separated_values=False
# Split before arguments if the argument list is terminated by a
# comma.
split_arguments_when_comma_terminated=True
# Set to True to prefer splitting before '+', '-', '*', '/', '//', or '@'
# rather than after.
split_before_arithmetic_operator=False
# Set to True to prefer splitting before '&', '|' or '^' rather than
# after.
split_before_bitwise_operator=False
# Split before the closing bracket if a list or dict literal doesn't fit on
# a single line.
split_before_closing_bracket=True
# Split before a dictionary or set generator (comp_for). For example, note
# the split before the 'for':
#
# foo = {
# variable: 'Hello world, have a nice day!'
# for variable in bar if variable != 42
# }
split_before_dict_set_generator=True
# Split before the '.' if we need to split a longer expression:
#
# foo = ('This is a really long string: {}, {}, {}, {}'.format(a, b, c, d))
#
# would reformat to something like:
#
# foo = ('This is a really long string: {}, {}, {}, {}'
# .format(a, b, c, d))
split_before_dot=True
# Split after the opening paren which surrounds an expression if it doesn't
# fit on a single line.
split_before_expression_after_opening_paren=False
# If an argument / parameter list is going to be split, then split before
# the first argument.
split_before_first_argument=False
# Set to True to prefer splitting before 'and' or 'or' rather than
# after.
split_before_logical_operator=False
# Split named assignments onto individual lines.
split_before_named_assigns=True
# Set to True to split list comprehensions and generators that have
# non-trivial expressions and multiple clauses before each of these
# clauses. For example:
#
# result = [
# a_long_var + 100 for a_long_var in xrange(1000)
# if a_long_var % 10]
#
# would reformat to something like:
#
# result = [
# a_long_var + 100
# for a_long_var in xrange(1000)
# if a_long_var % 10]
split_complex_comprehension=True
# The penalty for splitting right after the opening bracket.
split_penalty_after_opening_bracket=300
# The penalty for splitting the line after a unary operator.
split_penalty_after_unary_operator=10000
# The penalty of splitting the line around the '+', '-', '*', '/', '//',
# ``%``, and '@' operators.
split_penalty_arithmetic_operator=300
# The penalty for splitting right before an if expression.
split_penalty_before_if_expr=0
# The penalty of splitting the line around the '&', '|', and '^'
# operators.
split_penalty_bitwise_operator=300
# The penalty for splitting a list comprehension or generator
# expression.
split_penalty_comprehension=2100
# The penalty for characters over the column limit.
split_penalty_excess_character=7000
# The penalty incurred by adding a line split to the logical line. The
# more line splits added the higher the penalty.
split_penalty_for_added_line_split=30
# The penalty of splitting a list of "import as" names. For example:
#
# from a_very_long_or_indented_module_name_yada_yad import (long_argument_1,
# long_argument_2,
# long_argument_3)
#
# would reformat to something like:
#
# from a_very_long_or_indented_module_name_yada_yad import (
# long_argument_1, long_argument_2, long_argument_3)
split_penalty_import_names=0
# The penalty of splitting the line around the 'and' and 'or'
# operators.
split_penalty_logical_operator=300
# Use the Tab character for indentation.
use_tabs=False

@ -7,13 +7,17 @@ import warnings
from ._version import __version__ from ._version import __version__
if sys.version_info.major < 3: if sys.version_info.major < 3:
warnings.simplefilter("always", DeprecationWarning) warnings.simplefilter( "always", DeprecationWarning )
warnings.warn( warnings.warn(
DeprecationWarning("`pyhmy` does not support Python 2. Please use Python 3.") DeprecationWarning(
"`pyhmy` does not support Python 2. Please use Python 3."
)
) )
warnings.resetwarnings() warnings.resetwarnings()
if sys.platform.startswith("win32") or sys.platform.startswith("cygwin"): if sys.platform.startswith( "win32" ) or sys.platform.startswith( "cygwin" ):
warnings.simplefilter("always", ImportWarning) warnings.simplefilter( "always", ImportWarning )
warnings.warn(ImportWarning("`pyhmy` does not work on Windows or Cygwin.")) warnings.warn(
ImportWarning( "`pyhmy` does not work on Windows or Cygwin." )
)
warnings.resetwarnings() warnings.resetwarnings()

@ -5,5 +5,5 @@
from incremental import Version from incremental import Version
__version__ = Version("pyhmy", 20, 5, 20) __version__ = Version( "pyhmy", 20, 5, 20 )
__all__ = ["__version__"] __all__ = [ "__version__" ]

@ -13,7 +13,8 @@ from .bech32.bech32 import bech32_decode
from .constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT from .constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT
def is_valid_address(address) -> bool:
def is_valid_address( address ) -> bool:
""" """
Check if given string is valid one address Check if given string is valid one address
NOTE: This function is NOT thread safe due to the C function used by the bech32 library. NOTE: This function is NOT thread safe due to the C function used by the bech32 library.
@ -28,15 +29,19 @@ def is_valid_address(address) -> bool:
bool bool
Is valid address Is valid address
""" """
if not address.startswith("one1"): if not address.startswith( "one1" ):
return False return False
hrp, _ = bech32_decode(address) hrp, _ = bech32_decode( address )
if not hrp: if not hrp:
return False return False
return True return True
def get_balance(address, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: def get_balance(
address,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""Get current account balance. """Get current account balance.
Parameters Parameters
@ -63,18 +68,24 @@ def get_balance(address, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) ->
https://api.hmny.io/#da8901d2-d237-4c3b-9d7d-10af9def05c4 https://api.hmny.io/#da8901d2-d237-4c3b-9d7d-10af9def05c4
""" """
method = "hmyv2_getBalance" method = "hmyv2_getBalance"
params = [address] params = [ address ]
try: try:
balance = rpc_request( balance = rpc_request(
method, params=params, endpoint=endpoint, timeout=timeout method,
)["result"] params = params,
return int(balance) # v2 returns the result as it is endpoint = endpoint,
timeout = timeout
)[ "result" ]
return int( balance ) # v2 returns the result as it is
except TypeError as exception: # check will work if rpc returns None except TypeError as exception: # check will work if rpc returns None
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_balance_by_block( def get_balance_by_block(
address, block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT address,
block_num,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int: ) -> int:
"""Get account balance for address at a given block number. """Get account balance for address at a given block number.
@ -105,18 +116,24 @@ def get_balance_by_block(
https://github.com/harmony-one/harmony/blob/9f320436ff30d9babd957bc5f2e15a1818c86584/rpc/blockchain.go#L92 https://github.com/harmony-one/harmony/blob/9f320436ff30d9babd957bc5f2e15a1818c86584/rpc/blockchain.go#L92
""" """
method = "hmyv2_getBalanceByBlockNumber" method = "hmyv2_getBalanceByBlockNumber"
params = [address, block_num] params = [ address, block_num ]
try: try:
balance = rpc_request( balance = rpc_request(
method, params=params, endpoint=endpoint, timeout=timeout method,
)["result"] params = params,
return int(balance) endpoint = endpoint,
timeout = timeout
)[ "result" ]
return int( balance )
except TypeError as exception: except TypeError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_account_nonce( def get_account_nonce(
address, block_num="latest", endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT address,
block_num = "latest",
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int: ) -> int:
"""Get the account nonce. """Get the account nonce.
@ -146,25 +163,34 @@ def get_account_nonce(
https://github.com/harmony-one/harmony/blob/9f320436ff30d9babd957bc5f2e15a1818c86584/rpc/transaction.go#L51 https://github.com/harmony-one/harmony/blob/9f320436ff30d9babd957bc5f2e15a1818c86584/rpc/transaction.go#L51
""" """
method = "hmyv2_getAccountNonce" method = "hmyv2_getAccountNonce"
params = [address, block_num] params = [ address, block_num ]
try: try:
nonce = rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ nonce = rpc_request(
"result" method,
] params = params,
return int(nonce) endpoint = endpoint,
timeout = timeout
)[ "result" ]
return int( nonce )
except TypeError as exception: except TypeError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_nonce( def get_nonce(
address, block_num="latest", endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT address,
block_num = "latest",
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int: ) -> int:
"""See get_account_nonce.""" """See get_account_nonce."""
return get_account_nonce(address, block_num, endpoint, timeout) return get_account_nonce( address, block_num, endpoint, timeout )
def get_transaction_count( def get_transaction_count(
address, block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT address,
block_num,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int: ) -> int:
"""Get the number of transactions the given address has sent for the given """Get the number of transactions the given address has sent for the given
block number Legacy for apiv1. For apiv2, please use block number Legacy for apiv1. For apiv2, please use
@ -197,18 +223,24 @@ def get_transaction_count(
https://github.com/harmony-one/harmony/blob/9f320436ff30d9babd957bc5f2e15a1818c86584/rpc/transaction.go#L69 https://github.com/harmony-one/harmony/blob/9f320436ff30d9babd957bc5f2e15a1818c86584/rpc/transaction.go#L69
""" """
method = "hmyv2_getTransactionCount" method = "hmyv2_getTransactionCount"
params = [address, block_num] params = [ address, block_num ]
try: try:
nonce = rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ nonce = rpc_request(
"result" method,
] params = params,
return int(nonce) endpoint = endpoint,
timeout = timeout
)[ "result" ]
return int( nonce )
except TypeError as exception: except TypeError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_transactions_count( def get_transactions_count(
address, tx_type, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT address,
tx_type,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int: ) -> int:
"""Get the number of regular transactions from genesis of input type. """Get the number of regular transactions from genesis of input type.
@ -240,18 +272,24 @@ def get_transactions_count(
https://github.com/harmony-one/harmony/blob/9f320436ff30d9babd957bc5f2e15a1818c86584/rpc/transaction.go#L114 https://github.com/harmony-one/harmony/blob/9f320436ff30d9babd957bc5f2e15a1818c86584/rpc/transaction.go#L114
""" """
method = "hmyv2_getTransactionsCount" method = "hmyv2_getTransactionsCount"
params = [address, tx_type] params = [ address, tx_type ]
try: try:
tx_count = rpc_request( tx_count = rpc_request(
method, params=params, endpoint=endpoint, timeout=timeout method,
)["result"] params = params,
return int(tx_count) endpoint = endpoint,
timeout = timeout
)[ "result" ]
return int( tx_count )
except TypeError as exception: except TypeError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_staking_transactions_count( def get_staking_transactions_count(
address, tx_type, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT address,
tx_type,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int: ) -> int:
"""Get the number of staking transactions from genesis of input type """Get the number of staking transactions from genesis of input type
("SENT", "RECEIVED", "ALL") ("SENT", "RECEIVED", "ALL")
@ -284,14 +322,17 @@ def get_staking_transactions_count(
https://github.com/harmony-one/harmony/blob/9f320436ff30d9babd957bc5f2e15a1818c86584/rpc/transaction.go#L134 https://github.com/harmony-one/harmony/blob/9f320436ff30d9babd957bc5f2e15a1818c86584/rpc/transaction.go#L134
""" """
method = "hmyv2_getStakingTransactionsCount" method = "hmyv2_getStakingTransactionsCount"
params = [address, tx_type] params = [ address, tx_type ]
try: try:
tx_count = rpc_request( tx_count = rpc_request(
method, params=params, endpoint=endpoint, timeout=timeout method,
)["result"] params = params,
return int(tx_count) endpoint = endpoint,
except (KeyError, TypeError) as exception: timeout = timeout
raise InvalidRPCReplyError(method, endpoint) from exception )[ "result" ]
return int( tx_count )
except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_transaction_history( # pylint: disable=too-many-arguments def get_transaction_history( # pylint: disable=too-many-arguments
@ -359,11 +400,14 @@ def get_transaction_history( # pylint: disable=too-many-arguments
method = "hmyv2_getTransactionsHistory" method = "hmyv2_getTransactionsHistory"
try: try:
tx_history = rpc_request( tx_history = rpc_request(
method, params=params, endpoint=endpoint, timeout=timeout method,
params = params,
endpoint = endpoint,
timeout = timeout
) )
return tx_history["result"]["transactions"] return tx_history[ "result" ][ "transactions" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_staking_transaction_history( # pylint: disable=too-many-arguments def get_staking_transaction_history( # pylint: disable=too-many-arguments
@ -445,15 +489,21 @@ def get_staking_transaction_history( # pylint: disable=too-many-arguments
method = "hmyv2_getStakingTransactionsHistory" method = "hmyv2_getStakingTransactionsHistory"
try: try:
stx_history = rpc_request( stx_history = rpc_request(
method, params=params, endpoint=endpoint, timeout=timeout method,
)["result"] params = params,
return stx_history["staking_transactions"] endpoint = endpoint,
timeout = timeout
)[ "result" ]
return stx_history[ "staking_transactions" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_balance_on_all_shards( def get_balance_on_all_shards(
address, skip_error=True, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT address,
skip_error = True,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list: ) -> list:
"""Get current account balance in all shards & optionally report errors """Get current account balance in all shards & optionally report errors
getting account balance for a shard. getting account balance for a shard.
@ -483,25 +533,37 @@ def get_balance_on_all_shards(
] ]
""" """
balances = [] balances = []
sharding_structure = get_sharding_structure(endpoint=endpoint, timeout=timeout) sharding_structure = get_sharding_structure(
endpoint = endpoint,
timeout = timeout
)
for shard in sharding_structure: for shard in sharding_structure:
try: try:
balances.append( balances.append(
{ {
"shard": shard["shardID"], "shard": shard[ "shardID" ],
"balance": get_balance( "balance": get_balance(
address, endpoint=shard["http"], timeout=timeout address,
endpoint = shard[ "http" ],
timeout = timeout
), ),
} }
) )
except (KeyError, RPCError, RequestsError, RequestsTimeoutError): except ( KeyError, RPCError, RequestsError, RequestsTimeoutError ):
if not skip_error: if not skip_error:
balances.append({"shard": shard["shardID"], "balance": None}) balances.append(
{
"shard": shard[ "shardID" ],
"balance": None
}
)
return balances return balances
def get_total_balance( def get_total_balance(
address, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT address,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int: ) -> int:
"""Get total account balance on all shards. """Get total account balance on all shards.
@ -530,8 +592,11 @@ def get_total_balance(
""" """
try: try:
balances = get_balance_on_all_shards( balances = get_balance_on_all_shards(
address, skip_error=False, endpoint=endpoint, timeout=timeout address,
skip_error = False,
endpoint = endpoint,
timeout = timeout
) )
return sum(b["balance"] for b in balances) return sum( b[ "balance" ] for b in balances )
except TypeError as exception: except TypeError as exception:
raise RuntimeError from exception raise RuntimeError from exception

@ -17,108 +17,106 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE. # THE SOFTWARE.
"""Reference implementation for Bech32 and segwit addresses.""" """Reference implementation for Bech32 and segwit addresses."""
CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
def bech32_polymod(values): def bech32_polymod( values ):
"""Internal function that computes the Bech32 checksum.""" """Internal function that computes the Bech32 checksum."""
generator = [0x3B6A57B2, 0x26508E6D, 0x1EA119FA, 0x3D4233DD, 0x2A1462B3] generator = [ 0x3B6A57B2, 0x26508E6D, 0x1EA119FA, 0x3D4233DD, 0x2A1462B3 ]
chk = 1 chk = 1
for value in values: for value in values:
top = chk >> 25 top = chk >> 25
chk = (chk & 0x1FFFFFF) << 5 ^ value chk = ( chk & 0x1FFFFFF ) << 5 ^ value
for i in range(5): for i in range( 5 ):
chk ^= generator[i] if ((top >> i) & 1) else 0 chk ^= generator[ i ] if ( ( top >> i ) & 1 ) else 0
return chk return chk
def bech32_hrp_expand(hrp): def bech32_hrp_expand( hrp ):
"""Expand the HRP into values for checksum computation.""" """Expand the HRP into values for checksum computation."""
return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp] return [ ord( x ) >> 5 for x in hrp ] + [ 0
] + [ ord( x ) & 31 for x in hrp ]
def bech32_verify_checksum(hrp, data): def bech32_verify_checksum( hrp, data ):
"""Verify a checksum given HRP and converted data characters.""" """Verify a checksum given HRP and converted data characters."""
return bech32_polymod(bech32_hrp_expand(hrp) + data) == 1 return bech32_polymod( bech32_hrp_expand( hrp ) + data ) == 1
def bech32_create_checksum(hrp, data): def bech32_create_checksum( hrp, data ):
"""Compute the checksum values given HRP and data.""" """Compute the checksum values given HRP and data."""
values = bech32_hrp_expand(hrp) + data values = bech32_hrp_expand( hrp ) + data
polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ 1 polymod = bech32_polymod( values + [ 0, 0, 0, 0, 0, 0 ] ) ^ 1
return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)] return [ ( polymod >> 5 * ( 5 - i ) ) & 31 for i in range( 6 ) ]
def bech32_encode(hrp, data): def bech32_encode( hrp, data ):
"""Compute a Bech32 string given HRP and data values.""" """Compute a Bech32 string given HRP and data values."""
combined = data + bech32_create_checksum(hrp, data) combined = data + bech32_create_checksum( hrp, data )
return hrp + "1" + "".join([CHARSET[d] for d in combined]) return hrp + "1" + "".join( [ CHARSET[ d ] for d in combined ] )
def bech32_decode(bech): def bech32_decode( bech ):
"""Validate a Bech32 string, and determine HRP and data.""" """Validate a Bech32 string, and determine HRP and data."""
if (any(ord(x) < 33 or ord(x) > 126 for x in bech)) or ( if ( any( ord( x ) < 33 or ord( x ) > 126 for x in bech
bech.lower() != bech and bech.upper() != bech ) ) or ( bech.lower() != bech and bech.upper() != bech ):
): return ( None, None )
return (None, None)
bech = bech.lower() bech = bech.lower()
pos = bech.rfind("1") pos = bech.rfind( "1" )
if pos < 1 or pos + 7 > len(bech) or len(bech) > 90: if pos < 1 or pos + 7 > len( bech ) or len( bech ) > 90:
return (None, None) return ( None, None )
if not all(x in CHARSET for x in bech[pos + 1 :]): if not all( x in CHARSET for x in bech[ pos + 1 : ] ):
return (None, None) return ( None, None )
hrp = bech[:pos] hrp = bech[ : pos ]
data = [CHARSET.find(x) for x in bech[pos + 1 :]] data = [ CHARSET.find( x ) for x in bech[ pos + 1 : ] ]
if not bech32_verify_checksum(hrp, data): if not bech32_verify_checksum( hrp, data ):
return (None, None) return ( None, None )
return (hrp, data[:-6]) return ( hrp, data[ :-6 ] )
def convertbits(data, frombits, tobits, pad=True): def convertbits( data, frombits, tobits, pad = True ):
"""General power-of-2 base conversion.""" """General power-of-2 base conversion."""
acc = 0 acc = 0
bits = 0 bits = 0
ret = [] ret = []
maxv = (1 << tobits) - 1 maxv = ( 1 << tobits ) - 1
max_acc = (1 << (frombits + tobits - 1)) - 1 max_acc = ( 1 << ( frombits + tobits - 1 ) ) - 1
for value in data: for value in data:
if value < 0 or (value >> frombits): if value < 0 or ( value >> frombits ):
return None return None
acc = ((acc << frombits) | value) & max_acc acc = ( ( acc << frombits ) | value ) & max_acc
bits += frombits bits += frombits
while bits >= tobits: while bits >= tobits:
bits -= tobits bits -= tobits
ret.append((acc >> bits) & maxv) ret.append( ( acc >> bits ) & maxv )
if pad: if pad:
if bits: if bits:
ret.append((acc << (tobits - bits)) & maxv) ret.append( ( acc << ( tobits - bits ) ) & maxv )
elif bits >= frombits or ((acc << (tobits - bits)) & maxv): elif bits >= frombits or ( ( acc << ( tobits - bits ) ) & maxv ):
return None return None
return ret return ret
def decode(hrp, addr): def decode( hrp, addr ):
"""Decode a segwit address.""" """Decode a segwit address."""
hrpgot, data = bech32_decode(addr) hrpgot, data = bech32_decode( addr )
if hrpgot != hrp: if hrpgot != hrp:
return (None, None) return ( None, None )
decoded = convertbits(data[1:], 5, 8, False) decoded = convertbits( data[ 1 : ], 5, 8, False )
if decoded is None or len(decoded) < 2 or len(decoded) > 40: if decoded is None or len( decoded ) < 2 or len( decoded ) > 40:
return (None, None) return ( None, None )
if data[0] > 16: if data[ 0 ] > 16:
return (None, None) return ( None, None )
if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32: if data[ 0 ] == 0 and len( decoded ) != 20 and len( decoded ) != 32:
return (None, None) return ( None, None )
return (data[0], decoded) return ( data[ 0 ], decoded )
def encode(hrp, witver, witprog): def encode( hrp, witver, witprog ):
"""Encode a segwit address.""" """Encode a segwit address."""
ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5)) ret = bech32_encode( hrp, [ witver ] + convertbits( witprog, 8, 5 ) )
if decode(hrp, ret) == (None, None): if decode( hrp, ret ) == ( None, None ):
return None return None
return ret return ret

@ -9,10 +9,14 @@ from .exceptions import InvalidRPCReplyError
from .constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT from .constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT
############################# #############################
# Node / network level RPCs # # Node / network level RPCs #
############################# #############################
def get_bad_blocks(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> list: def get_bad_blocks(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list:
"""[WIP] Get list of bad blocks in memory of specific node Known issues """[WIP] Get list of bad blocks in memory of specific node Known issues
with RPC not returning correctly. with RPC not returning correctly.
@ -38,12 +42,14 @@ def get_bad_blocks(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> list:
""" """
method = "hmyv2_getCurrentBadBlocks" method = "hmyv2_getCurrentBadBlocks"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def chain_id(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict: def chain_id( endpoint = DEFAULT_ENDPOINT, timeout = DEFAULT_TIMEOUT ) -> dict:
"""Chain id of the chain. """Chain id of the chain.
Parameters Parameters
@ -68,13 +74,16 @@ def chain_id(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict:
""" """
method = "hmyv2_chainId" method = "hmyv2_chainId"
try: try:
data = rpc_request(method, endpoint=endpoint, timeout=timeout) data = rpc_request( method, endpoint = endpoint, timeout = timeout )
return data["result"] return data[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_node_metadata(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict: def get_node_metadata(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict:
"""Get config for the node. """Get config for the node.
Parameters Parameters
@ -127,13 +136,16 @@ def get_node_metadata(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dic
""" """
method = "hmyv2_getNodeMetadata" method = "hmyv2_getNodeMetadata"
try: try:
metadata = rpc_request(method, endpoint=endpoint, timeout=timeout) metadata = rpc_request( method, endpoint = endpoint, timeout = timeout )
return metadata["result"] return metadata[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_peer_info(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict: def get_peer_info(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict:
"""Get peer info for the node. """Get peer info for the node.
Parameters Parameters
@ -165,12 +177,17 @@ def get_peer_info(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict:
""" """
method = "hmyv2_getPeerInfo" method = "hmyv2_getPeerInfo"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def protocol_version(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: def protocol_version(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""Get the current Harmony protocol version this node supports. """Get the current Harmony protocol version this node supports.
Parameters Parameters
@ -196,13 +213,16 @@ def protocol_version(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int:
""" """
method = "hmyv2_protocolVersion" method = "hmyv2_protocolVersion"
try: try:
value = rpc_request(method, endpoint=endpoint, timeout=timeout) value = rpc_request( method, endpoint = endpoint, timeout = timeout )
return value["result"] return value[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_num_peers(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: def get_num_peers(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""Get number of peers connected to the node. """Get number of peers connected to the node.
Parameters Parameters
@ -229,13 +249,19 @@ def get_num_peers(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int:
method = "net_peerCount" method = "net_peerCount"
try: # Number of peers represented as a hex string try: # Number of peers represented as a hex string
return int( return int(
rpc_request(method, endpoint=endpoint, timeout=timeout)["result"], 16 rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ],
16
) )
except (KeyError, TypeError) as exception: except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_version(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: def get_version(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""Get version of the EVM network (https://chainid.network/) """Get version of the EVM network (https://chainid.network/)
Parameters Parameters
@ -262,13 +288,16 @@ def get_version(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int:
method = "net_version" method = "net_version"
try: try:
return int( return int(
rpc_request(method, endpoint=endpoint, timeout=timeout)["result"], 16 rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ],
16
) # this is hexadecimal ) # this is hexadecimal
except (KeyError, TypeError) as exception: except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def in_sync(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> bool: def in_sync( endpoint = DEFAULT_ENDPOINT, timeout = DEFAULT_TIMEOUT ) -> bool:
"""Whether the shard chain is in sync or syncing (not out of sync) """Whether the shard chain is in sync or syncing (not out of sync)
Parameters Parameters
@ -293,12 +322,19 @@ def in_sync(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> bool:
""" """
method = "hmyv2_inSync" method = "hmyv2_inSync"
try: try:
return bool(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]) return bool(
except (KeyError, TypeError) as exception: rpc_request( method,
raise InvalidRPCReplyError(method, endpoint) from exception endpoint = endpoint,
timeout = timeout )[ "result" ]
)
except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def beacon_in_sync(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> bool: def beacon_in_sync(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> bool:
"""Whether the beacon chain is in sync or syncing (not out of sync) """Whether the beacon chain is in sync or syncing (not out of sync)
Parameters Parameters
@ -323,12 +359,19 @@ def beacon_in_sync(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> bool:
""" """
method = "hmyv2_beaconInSync" method = "hmyv2_beaconInSync"
try: try:
return bool(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]) return bool(
except (KeyError, TypeError) as exception: rpc_request( method,
raise InvalidRPCReplyError(method, endpoint) from exception endpoint = endpoint,
timeout = timeout )[ "result" ]
)
except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_staking_epoch(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: def get_staking_epoch(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""Get epoch number when blockchain switches to EPoS election. """Get epoch number when blockchain switches to EPoS election.
Parameters Parameters
@ -358,13 +401,18 @@ def get_staking_epoch(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int
""" """
method = "hmyv2_getNodeMetadata" method = "hmyv2_getNodeMetadata"
try: try:
data = rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] data = rpc_request( method,
return int(data["chain-config"]["staking-epoch"]) endpoint = endpoint,
except (KeyError, TypeError) as exception: timeout = timeout )[ "result" ]
raise InvalidRPCReplyError(method, endpoint) from exception return int( data[ "chain-config" ][ "staking-epoch" ] )
except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_prestaking_epoch(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: def get_prestaking_epoch(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""Get epoch number when blockchain switches to allow staking features """Get epoch number when blockchain switches to allow staking features
without election. without election.
@ -395,16 +443,18 @@ def get_prestaking_epoch(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) ->
""" """
method = "hmyv2_getNodeMetadata" method = "hmyv2_getNodeMetadata"
try: try:
data = rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] data = rpc_request( method,
return int(data["chain-config"]["prestaking-epoch"]) endpoint = endpoint,
except (KeyError, TypeError) as exception: timeout = timeout )[ "result" ]
raise InvalidRPCReplyError(method, endpoint) from exception return int( data[ "chain-config" ][ "prestaking-epoch" ] )
except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
######################## ########################
# Sharding information # # Sharding information #
######################## ########################
def get_shard(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: def get_shard( endpoint = DEFAULT_ENDPOINT, timeout = DEFAULT_TIMEOUT ) -> int:
"""Get shard ID of the node. """Get shard ID of the node.
Parameters Parameters
@ -430,15 +480,16 @@ def get_shard(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int:
""" """
method = "hmyv2_getNodeMetadata" method = "hmyv2_getNodeMetadata"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"][ return rpc_request( method,
"shard-id" endpoint = endpoint,
] timeout = timeout )[ "result" ][ "shard-id" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_sharding_structure( def get_sharding_structure(
endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list: ) -> list:
"""Get network sharding structure. """Get network sharding structure.
@ -468,15 +519,20 @@ def get_sharding_structure(
""" """
method = "hmyv2_getShardingStructure" method = "hmyv2_getShardingStructure"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
############################# #############################
# Current status of network # # Current status of network #
############################# #############################
def get_leader_address(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> str: def get_leader_address(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> str:
"""Get current leader one address. """Get current leader one address.
Parameters Parameters
@ -502,13 +558,17 @@ def get_leader_address(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> st
""" """
method = "hmyv2_getLeader" method = "hmyv2_getLeader"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def is_last_block( def is_last_block(
block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT block_num,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> bool: ) -> bool:
"""If the block at block_num is the last block. """If the block at block_num is the last block.
@ -534,22 +594,25 @@ def is_last_block(
------------- -------------
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/blockchain.go#L286 https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/blockchain.go#L286
""" """
params = [ params = [ block_num, ]
block_num,
]
method = "hmyv2_isLastBlock" method = "hmyv2_isLastBlock"
try: try:
return bool( return bool(
rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
) )
except (KeyError, TypeError) as exception: except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def epoch_last_block( def epoch_last_block(
epoch, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT epoch,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int: ) -> int:
"""Returns the number of the last block in the epoch. """Returns the number of the last block in the epoch.
@ -575,21 +638,25 @@ def epoch_last_block(
------------- -------------
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/blockchain.go#L294 https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/blockchain.go#L294
""" """
params = [ params = [ epoch, ]
epoch,
]
method = "hmyv2_epochLastBlock" method = "hmyv2_epochLastBlock"
try: try:
return int( return int(
rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
) )
except (KeyError, TypeError) as exception: except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_circulating_supply(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: def get_circulating_supply(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""Get current circulation supply of tokens in ONE. """Get current circulation supply of tokens in ONE.
Parameters Parameters
@ -615,12 +682,17 @@ def get_circulating_supply(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -
""" """
method = "hmyv2_getCirculatingSupply" method = "hmyv2_getCirculatingSupply"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_total_supply(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: def get_total_supply(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""Get total number of pre-mined tokens. """Get total number of pre-mined tokens.
Parameters Parameters
@ -646,12 +718,17 @@ def get_total_supply(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int:
""" """
method = "hmyv2_getTotalSupply" method = "hmyv2_getTotalSupply"
try: try:
rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_block_number(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: def get_block_number(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""Get current block number. """Get current block number.
Parameters Parameters
@ -677,12 +754,19 @@ def get_block_number(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int:
""" """
method = "hmyv2_blockNumber" method = "hmyv2_blockNumber"
try: try:
return int(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]) return int(
except (KeyError, TypeError) as exception: rpc_request( method,
raise InvalidRPCReplyError(method, endpoint) from exception endpoint = endpoint,
timeout = timeout )[ "result" ]
)
except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_current_epoch(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: def get_current_epoch(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""Get current epoch number. """Get current epoch number.
Parameters Parameters
@ -708,12 +792,19 @@ def get_current_epoch(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int
""" """
method = "hmyv2_getEpoch" method = "hmyv2_getEpoch"
try: try:
return int(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]) return int(
except (KeyError, TypeError) as exception: rpc_request( method,
raise InvalidRPCReplyError(method, endpoint) from exception endpoint = endpoint,
timeout = timeout )[ "result" ]
)
except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_last_cross_links(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> list: def get_last_cross_links(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list:
"""Get last cross shard links. """Get last cross shard links.
Parameters Parameters
@ -746,12 +837,17 @@ def get_last_cross_links(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) ->
""" """
method = "hmyv2_getLastCrossLinks" method = "hmyv2_getLastCrossLinks"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_gas_price(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: def get_gas_price(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""Get network gas price. """Get network gas price.
Parameters Parameters
@ -777,15 +873,22 @@ def get_gas_price(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int:
""" """
method = "hmyv2_gasPrice" method = "hmyv2_gasPrice"
try: try:
return int(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]) return int(
except (KeyError, TypeError) as exception: rpc_request( method,
raise InvalidRPCReplyError(method, endpoint) from exception endpoint = endpoint,
timeout = timeout )[ "result" ]
)
except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
############## ##############
# Block RPCs # # Block RPCs #
############## ##############
def get_latest_header(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict: def get_latest_header(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict:
"""Get block header of latest block. """Get block header of latest block.
Parameters Parameters
@ -829,13 +932,17 @@ def get_latest_header(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dic
""" """
method = "hmyv2_latestHeader" method = "hmyv2_latestHeader"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_header_by_number( def get_header_by_number(
block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT block_num,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict: ) -> dict:
"""Get block header of block at block_num. """Get block header of block at block_num.
@ -862,17 +969,21 @@ def get_header_by_number(
https://api.hmny.io/#01148e4f-72bb-426d-a123-718a161eaec0 https://api.hmny.io/#01148e4f-72bb-426d-a123-718a161eaec0
""" """
method = "hmyv2_getHeaderByNumber" method = "hmyv2_getHeaderByNumber"
params = [block_num] params = [ block_num ]
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_latest_chain_headers( def get_latest_chain_headers(
endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict: ) -> dict:
"""Get block header of latest block for beacon chain & shard chain. """Get block header of latest block for beacon chain & shard chain.
@ -919,9 +1030,11 @@ def get_latest_chain_headers(
""" """
method = "hmyv2_getLatestChainHeaders" method = "hmyv2_getLatestChainHeaders"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_block_by_number( # pylint: disable=too-many-arguments def get_block_by_number( # pylint: disable=too-many-arguments
@ -1005,11 +1118,14 @@ def get_block_by_number( # pylint: disable=too-many-arguments
] ]
method = "hmyv2_getBlockByNumber" method = "hmyv2_getBlockByNumber"
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_block_by_hash( # pylint: disable=too-many-arguments def get_block_by_hash( # pylint: disable=too-many-arguments
@ -1062,15 +1178,20 @@ def get_block_by_hash( # pylint: disable=too-many-arguments
] ]
method = "hmyv2_getBlockByHash" method = "hmyv2_getBlockByHash"
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_block_transaction_count_by_number( def get_block_transaction_count_by_number(
block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT block_num,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int: ) -> int:
"""Get transaction count for specific block number. """Get transaction count for specific block number.
@ -1099,20 +1220,25 @@ def get_block_transaction_count_by_number(
------------- -------------
https://api.hmny.io/#26c5adfb-d757-4595-9eb7-c6efef63df32 https://api.hmny.io/#26c5adfb-d757-4595-9eb7-c6efef63df32
""" """
params = [block_num] params = [ block_num ]
method = "hmyv2_getBlockTransactionCountByNumber" method = "hmyv2_getBlockTransactionCountByNumber"
try: try:
return int( return int(
rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
) )
except (KeyError, TypeError) as exception: except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_block_transaction_count_by_hash( def get_block_transaction_count_by_hash(
block_hash, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT block_hash,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int: ) -> int:
"""Get transaction count for specific block hash. """Get transaction count for specific block hash.
@ -1141,20 +1267,25 @@ def get_block_transaction_count_by_hash(
------------- -------------
https://api.hmny.io/#66c68844-0208-49bb-a83b-08722bc113eb https://api.hmny.io/#66c68844-0208-49bb-a83b-08722bc113eb
""" """
params = [block_hash] params = [ block_hash ]
method = "hmyv2_getBlockTransactionCountByHash" method = "hmyv2_getBlockTransactionCountByHash"
try: try:
return int( return int(
rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
) )
except (KeyError, TypeError) as exception: except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_block_staking_transaction_count_by_number( def get_block_staking_transaction_count_by_number(
block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT block_num,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int: ) -> int:
"""Get staking transaction count for specific block number. """Get staking transaction count for specific block number.
@ -1183,20 +1314,25 @@ def get_block_staking_transaction_count_by_number(
------------- -------------
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/transaction.go#L494 https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/transaction.go#L494
""" """
params = [block_num] params = [ block_num ]
method = "hmyv2_getBlockStakingTransactionCountByNumber" method = "hmyv2_getBlockStakingTransactionCountByNumber"
try: try:
return int( return int(
rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
) )
except (KeyError, TypeError) as exception: except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_block_staking_transaction_count_by_hash( def get_block_staking_transaction_count_by_hash(
block_hash, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT block_hash,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int: ) -> int:
"""Get staking transaction count for specific block hash. """Get staking transaction count for specific block hash.
@ -1225,16 +1361,19 @@ def get_block_staking_transaction_count_by_hash(
------------- -------------
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/transaction.go#L523 https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/transaction.go#L523
""" """
params = [block_hash] params = [ block_hash ]
method = "hmyv2_getBlockStakingTransactionCountByHash" method = "hmyv2_getBlockStakingTransactionCountByHash"
try: try:
return int( return int(
rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
) )
except (KeyError, TypeError) as exception: except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_blocks( # pylint: disable=too-many-arguments def get_blocks( # pylint: disable=too-many-arguments
@ -1293,15 +1432,20 @@ def get_blocks( # pylint: disable=too-many-arguments
] ]
method = "hmyv2_getBlocks" method = "hmyv2_getBlocks"
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_block_signers( def get_block_signers(
block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT block_num,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list: ) -> list:
"""Get list of block signers for specific block number. """Get list of block signers for specific block number.
@ -1328,18 +1472,23 @@ def get_block_signers(
------------- -------------
https://api.hmny.io/#1e4b5f41-9db6-4dea-92fb-4408db78e622 https://api.hmny.io/#1e4b5f41-9db6-4dea-92fb-4408db78e622
""" """
params = [block_num] params = [ block_num ]
method = "hmyv2_getBlockSigners" method = "hmyv2_getBlockSigners"
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_block_signers_keys( def get_block_signers_keys(
block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT block_num,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list: ) -> list:
"""Get list of block signer public bls keys for specific block number. """Get list of block signer public bls keys for specific block number.
@ -1366,18 +1515,24 @@ def get_block_signers_keys(
------------- -------------
https://api.hmny.io/#9f9c8298-1a4e-4901-beac-f34b59ed02f1 https://api.hmny.io/#9f9c8298-1a4e-4901-beac-f34b59ed02f1
""" """
params = [block_num] params = [ block_num ]
method = "hmyv2_getBlockSignerKeys" method = "hmyv2_getBlockSignerKeys"
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def is_block_signer( def is_block_signer(
block_num, address, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT block_num,
address,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> bool: ) -> bool:
"""Determine if the account at address is a signer for the block at """Determine if the account at address is a signer for the block at
block_num. block_num.
@ -1406,18 +1561,23 @@ def is_block_signer(
------------- -------------
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/blockchain.go#L368 https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/blockchain.go#L368
""" """
params = [block_num, address] params = [ block_num, address ]
method = "hmyv2_isBlockSigner" method = "hmyv2_isBlockSigner"
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_signed_blocks( def get_signed_blocks(
address, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT address,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> bool: ) -> bool:
"""The number of blocks a particular validator signed for last blocksPeriod """The number of blocks a particular validator signed for last blocksPeriod
(1 epoch) (1 epoch)
@ -1444,19 +1604,26 @@ def get_signed_blocks(
------------- -------------
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/blockchain.go#L406 https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/blockchain.go#L406
""" """
params = [address] params = [ address ]
method = "hmyv2_getSignedBlocks" method = "hmyv2_getSignedBlocks"
try: try:
return int( return int(
rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
) )
except (KeyError, TypeError) as exception: except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_validators(epoch, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict: def get_validators(
epoch,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict:
"""Get list of validators for specific epoch number. """Get list of validators for specific epoch number.
Parameters Parameters
@ -1485,18 +1652,23 @@ def get_validators(epoch, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) ->
------------- -------------
https://api.hmny.io/#4dfe91ad-71fa-4c7d-83f3-d1c86a804da5 https://api.hmny.io/#4dfe91ad-71fa-4c7d-83f3-d1c86a804da5
""" """
params = [epoch] params = [ epoch ]
method = "hmyv2_getValidators" method = "hmyv2_getValidators"
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_validator_keys( def get_validator_keys(
epoch, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT epoch,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list: ) -> list:
"""Get list of validator public bls keys for specific epoch number. """Get list of validator public bls keys for specific epoch number.
@ -1523,11 +1695,14 @@ def get_validator_keys(
------------- -------------
https://api.hmny.io/#1439b580-fa3c-4d44-a79d-303390997a8c https://api.hmny.io/#1439b580-fa3c-4d44-a79d-303390997a8c
""" """
params = [epoch] params = [ epoch ]
method = "hmyv2_getValidatorKeys" method = "hmyv2_getValidatorKeys"
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception

@ -53,7 +53,7 @@ import requests
from .util import get_bls_build_variables, get_gopath from .util import get_bls_build_variables, get_gopath
if sys.platform.startswith("linux"): if sys.platform.startswith( "linux" ):
_libs = { _libs = {
"libbls384_256.so", "libbls384_256.so",
"libcrypto.so.10", "libcrypto.so.10",
@ -77,7 +77,6 @@ ARG_PREFIX = "__PYHMY_ARG_PREFIX__"
environment = os.environ.copy() # The environment for the CLI to execute in. environment = os.environ.copy() # The environment for the CLI to execute in.
# completely remove caching... # completely remove caching...
# we need to improve getting address better internally to REDUCE single calls.... # we need to improve getting address better internally to REDUCE single calls....
# def _cache_and_lock_accounts_keystore(fn): # def _cache_and_lock_accounts_keystore(fn):
@ -102,7 +101,8 @@ environment = os.environ.copy() # The environment for the CLI to execute in.
# return wrap # return wrap
def account_keystore_path(value=None):
def account_keystore_path( value = None ):
""" """
Gets or sets the ACCOUNT_KEYSTORE_PATH Gets or sets the ACCOUNT_KEYSTORE_PATH
""" """
@ -112,7 +112,8 @@ def account_keystore_path(value=None):
account_keystore_path.value = value account_keystore_path.value = value
return account_keystore_path.value return account_keystore_path.value
def binary_path(value=None):
def binary_path( value = None ):
""" """
Gets or sets the BINARY_PATH Gets or sets the BINARY_PATH
""" """
@ -122,6 +123,7 @@ def binary_path(value=None):
binary_path.value = value binary_path.value = value
return binary_path.value return binary_path.value
def _get_current_accounts_keystore(): 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.
@ -129,28 +131,32 @@ def _get_current_accounts_keystore():
values are their 'one1...' addresses. values are their 'one1...' addresses.
""" """
curr_addresses = {} curr_addresses = {}
response = single_call("hmy keys list") response = single_call( "hmy keys list" )
lines = response.split("\n") lines = response.split( "\n" )
if "NAME" not in lines[0] or "ADDRESS" not in lines[0]: if "NAME" not in lines[ 0 ] or "ADDRESS" not in lines[ 0 ]:
raise ValueError("Name or Address not found on first line of key list") raise ValueError(
if lines[1] != "": "Name or Address not found on first line of key list"
raise ValueError("Unknown format: No blank line between label and data") )
for line in lines[2:]: if lines[ 1 ] != "":
columns = line.split("\t") raise ValueError(
if len(columns) != 2: "Unknown format: No blank line between label and data"
)
for line in lines[ 2 : ]:
columns = line.split( "\t" )
if len( columns ) != 2:
break # Done iterating through all of the addresses. break # Done iterating through all of the addresses.
name, address = columns name, address = columns
curr_addresses[name.strip()] = address curr_addresses[ name.strip() ] = address
return curr_addresses return curr_addresses
def _set_account_keystore_path(): def _set_account_keystore_path():
"""Internal function to set the account keystore path according to the """Internal function to set the account keystore path according to the
binary.""" binary."""
response = single_call("hmy keys location").strip() response = single_call( "hmy keys location" ).strip()
if not os.path.exists(response): if not os.path.exists( response ):
os.mkdir(response) os.mkdir( response )
account_keystore_path(response) account_keystore_path( response )
def _sync_accounts(): def _sync_accounts():
@ -159,19 +165,19 @@ def _sync_accounts():
new_keystore = _get_current_accounts_keystore() new_keystore = _get_current_accounts_keystore()
for key, value in new_keystore.items(): for key, value in new_keystore.items():
if key not in _accounts: if key not in _accounts:
_accounts[key] = value _accounts[ key ] = value
acc_keys_to_remove = [k for k in _accounts if k not in new_keystore] acc_keys_to_remove = [ k for k in _accounts if k not in new_keystore ]
for key in acc_keys_to_remove: for key in acc_keys_to_remove:
del _accounts[key] del _accounts[ key ]
def _make_call_command(command): def _make_call_command( command ):
"""Internal function that processes a command String or String Arg List for """Internal function that processes a command String or String Arg List for
underlying pexpect or subprocess call. underlying pexpect or subprocess call.
Note that single quote is not respected for strings. Note that single quote is not respected for strings.
""" """
if isinstance(command, list): if isinstance( command, list ):
command_toks = command command_toks = command
else: else:
all_strings = sorted( all_strings = sorted(
@ -179,18 +185,23 @@ def _make_call_command(command):
key=lambda e: len(e), # pylint: disable=unnecessary-lambda key=lambda e: len(e), # pylint: disable=unnecessary-lambda
reverse=True reverse=True
) )
for i, string in enumerate(all_strings): for i, string in enumerate( all_strings ):
command = command.replace(string, f"{ARG_PREFIX}_{i}") command = command.replace( string, f"{ARG_PREFIX}_{i}" )
command_toks_prefix = [el for el in command.split(" ") if el] command_toks_prefix = [ el for el in command.split( " " ) if el ]
command_toks = [] command_toks = []
for element in command_toks_prefix: for element in command_toks_prefix:
if element.startswith(f'"{ARG_PREFIX}_') and element.endswith('"'): if element.startswith( f'"{ARG_PREFIX}_'
index = int(element.replace(f'"{ARG_PREFIX}_', "").replace('"', "")) ) and element.endswith( '"' ):
command_toks.append(all_strings[index]) index = int(
element.replace( f'"{ARG_PREFIX}_',
"" ).replace( '"',
"" )
)
command_toks.append( all_strings[ index ] )
else: else:
command_toks.append(element) command_toks.append( element )
if re.match(".*hmy", command_toks[0]): if re.match( ".*hmy", command_toks[ 0 ] ):
command_toks = command_toks[1:] command_toks = command_toks[ 1 : ]
return command_toks return command_toks
@ -204,41 +215,46 @@ def get_accounts_keystore():
return _accounts return _accounts
def is_valid_binary(path): def is_valid_binary( path ):
""" """
:param path: Path to the Harmony CLI binary (absolute or relative). :param path: Path to the Harmony CLI binary (absolute or relative).
:return: If the file at the path is a CLI binary. :return: If the file at the path is a CLI binary.
""" """
path = os.path.realpath(path) path = os.path.realpath( path )
os.chmod(path, os.stat(path).st_mode | stat.S_IEXEC) os.chmod( path, os.stat( path ).st_mode | stat.S_IEXEC )
try: try:
with subprocess.Popen( with subprocess.Popen(
[path, "version"], [ path,
env=environment, "version" ],
stdout=subprocess.PIPE, env = environment,
stderr=subprocess.PIPE, stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
) as proc: ) as proc:
_, err = proc.communicate() _, err = proc.communicate()
if not err: if not err:
return False return False
return "harmony" in err.decode().strip().lower() return "harmony" in err.decode().strip().lower()
except (OSError, subprocess.CalledProcessError, subprocess.SubprocessError): except (
OSError,
subprocess.CalledProcessError,
subprocess.SubprocessError
):
return False return False
def set_binary(path): def set_binary( path ):
""" """
:param path: The path of the CLI binary to use. :param path: The path of the CLI binary to use.
:returns If the binary has been set. :returns If the binary has been set.
Note that the exposed keystore will be updated accordingly. Note that the exposed keystore will be updated accordingly.
""" """
path = os.path.realpath(path) path = os.path.realpath( path )
assert os.path.exists(path) assert os.path.exists( path )
os.chmod(path, os.stat(path).st_mode | stat.S_IEXEC) os.chmod( path, os.stat( path ).st_mode | stat.S_IEXEC )
if not is_valid_binary(path): if not is_valid_binary( path ):
return False return False
binary_path(path) binary_path( path )
_set_account_keystore_path() _set_account_keystore_path()
_sync_accounts() _sync_accounts()
return True return True
@ -248,7 +264,7 @@ def get_binary_path():
""" """
:return: The absolute path of the CLI binary. :return: The absolute path of the CLI binary.
""" """
return os.path.abspath(binary_path()) return os.path.abspath( binary_path() )
def get_version(): def get_version():
@ -256,10 +272,11 @@ def get_version():
:return: The version string of the CLI binary. :return: The version string of the CLI binary.
""" """
with subprocess.Popen( with subprocess.Popen(
[binary_path(), "version"], [ binary_path(),
env=environment, "version" ],
stdout=subprocess.PIPE, env = environment,
stderr=subprocess.PIPE, stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
) as proc: ) as proc:
_, err = proc.communicate() _, err = proc.communicate()
if not err: if not err:
@ -274,10 +291,10 @@ def get_account_keystore_path():
""" """
:return: The absolute path to the account keystore of the CLI binary. :return: The absolute path to the account keystore of the CLI binary.
""" """
return os.path.abspath(account_keystore_path()) return os.path.abspath( account_keystore_path() )
def check_address(address): def check_address( address ):
""" """
:param address: A 'one1...' address. :param address: A 'one1...' address.
:return: Boolean of if the address is in the CLI's keystore. :return: Boolean of if the address is in the CLI's keystore.
@ -285,15 +302,15 @@ def check_address(address):
return address in get_accounts_keystore().values() return address in get_accounts_keystore().values()
def get_address(name): def get_address( name ):
""" """
:param name: The alias of a key used in the CLI's keystore. :param name: The alias of a key used in the CLI's keystore.
:return: The associated 'one1...' address. :return: The associated 'one1...' address.
""" """
return get_accounts_keystore().get(name, None) return get_accounts_keystore().get( name, None )
def get_accounts(address): def get_accounts( address ):
""" """
:param address: The 'one1...' address :param address: The 'one1...' address
:return: A list of account names associated with the param :return: A list of account names associated with the param
@ -301,38 +318,42 @@ def get_accounts(address):
Note that a list of account names is needed because 1 address can Note that a list of account names is needed because 1 address can
have multiple names within the CLI's keystore. have multiple names within the CLI's keystore.
""" """
return [acc for acc, addr in get_accounts_keystore().items() if address == addr] return [
acc for acc,
addr in get_accounts_keystore().items() if address == addr
]
def remove_account(name): def remove_account( name ):
"""Note that this edits the keystore directly since there is currently no """Note that this edits the keystore directly since there is currently no
way to remove an address using the CLI. way to remove an address using the CLI.
:param name: The alias of a key used in the CLI's keystore. :param name: The alias of a key used in the CLI's keystore.
:raises RuntimeError: If it failed to remove an account. :raises RuntimeError: If it failed to remove an account.
""" """
if not get_address(name): if not get_address( name ):
return return
keystore_path = f"{get_account_keystore_path()}/{name}" keystore_path = f"{get_account_keystore_path()}/{name}"
try: try:
shutil.rmtree(keystore_path) shutil.rmtree( keystore_path )
except (shutil.Error, FileNotFoundError) as err: except ( shutil.Error, FileNotFoundError ) as err:
raise RuntimeError( raise RuntimeError(
f"Failed to delete dir: {keystore_path}\n" f"\tException: {err}" f"Failed to delete dir: {keystore_path}\n"
f"\tException: {err}"
) from err ) from err
_sync_accounts() _sync_accounts()
def remove_address(address): def remove_address( address ):
""" """
:param address: The 'one1...' address to be removed. :param address: The 'one1...' address to be removed.
""" """
for name in get_accounts(address): for name in get_accounts( address ):
remove_account(name) remove_account( name )
_sync_accounts() _sync_accounts()
def single_call(command, timeout=60, error_ok=False): def single_call( command, timeout = 60, error_ok = False ):
""" """
:param command: String or String Arg List of command to execute on CLI. :param command: String or String Arg List of command to execute on CLI.
:param timeout: Optional timeout in seconds :param timeout: Optional timeout in seconds
@ -340,40 +361,47 @@ def single_call(command, timeout=60, error_ok=False):
:returns: Decoded string of response from hmy CLI call :returns: Decoded string of response from hmy CLI call
:raises: RuntimeError if bad command :raises: RuntimeError if bad command
""" """
command_toks = [binary_path()] + _make_call_command(command) command_toks = [ binary_path() ] + _make_call_command( command )
try: try:
return subprocess.check_output( return subprocess.check_output(
command_toks, env=environment, timeout=timeout command_toks,
env = environment,
timeout = timeout
).decode() ).decode()
except subprocess.CalledProcessError as err: except subprocess.CalledProcessError as err:
if not error_ok: if not error_ok:
raise RuntimeError( raise RuntimeError(
f"Bad CLI args: `{command}`\n " f"\tException: {err}" f"Bad CLI args: `{command}`\n "
f"\tException: {err}"
) from err ) from err
return err.output.decode() return err.output.decode()
def expect_call(command, timeout=60): def expect_call( command, timeout = 60 ):
""" """
:param command: String or String Arg List of command to execute on CLI. :param command: String or String Arg List of command to execute on CLI.
:param timeout: Optional timeout in seconds :param timeout: Optional timeout in seconds
:returns: A pexpect child program :returns: A pexpect child program
:raises: RuntimeError if bad command :raises: RuntimeError if bad command
""" """
command_toks = _make_call_command(command) command_toks = _make_call_command( command )
try: try:
proc = pexpect.spawn( proc = pexpect.spawn(
f"{binary_path()}", command_toks, env=environment, timeout=timeout f"{binary_path()}",
command_toks,
env = environment,
timeout = timeout
) )
proc.delaybeforesend = None proc.delaybeforesend = None
except pexpect.ExceptionPexpect as err: except pexpect.ExceptionPexpect as err:
raise RuntimeError( raise RuntimeError(
f"Bad CLI args: `{command}`\n " f"\tException: {err}" f"Bad CLI args: `{command}`\n "
f"\tException: {err}"
) from err ) from err
return proc return proc
def download(path="./bin/hmy", replace=True, verbose=True): def download( path = "./bin/hmy", replace = True, verbose = True ):
"""Download the CLI binary to the specified path. Related files will be """Download the CLI binary to the specified path. Related files will be
saved in the same directory. saved in the same directory.
@ -382,60 +410,74 @@ def download(path="./bin/hmy", replace=True, verbose=True):
:param verbose: A flag to enable a report message once the binary is downloaded. :param verbose: A flag to enable a report message once the binary is downloaded.
:returns the environment to run the saved CLI binary. :returns the environment to run the saved CLI binary.
""" """
path = os.path.realpath(path) path = os.path.realpath( path )
parent_dir = Path(path).parent parent_dir = Path( path ).parent
assert not os.path.isdir( assert not os.path.isdir(
path path
), f"path `{path}` must specify a file, not a directory." ), f"path `{path}` must specify a file, not a directory."
if not os.path.exists(path) or replace: if not os.path.exists( path ) or replace:
old_cwd = os.getcwd() old_cwd = os.getcwd()
os.makedirs(parent_dir, exist_ok=True) os.makedirs( parent_dir, exist_ok = True )
os.chdir(parent_dir) os.chdir( parent_dir )
hmy_script_path = os.path.join(parent_dir, "hmy.sh") hmy_script_path = os.path.join( parent_dir, "hmy.sh" )
with open(hmy_script_path, "w", encoding='utf8') as script_file: with open( hmy_script_path, "w", encoding = 'utf8' ) as script_file:
script_file.write( script_file.write(
requests.get( requests.get(
"https://raw.githubusercontent.com/harmony-one/go-sdk/master/scripts/hmy.sh" "https://raw.githubusercontent.com/harmony-one/go-sdk/master/scripts/hmy.sh"
).content.decode() ).content.decode()
) )
os.chmod(hmy_script_path, os.stat(hmy_script_path).st_mode | stat.S_IEXEC) os.chmod(
hmy_script_path,
os.stat( hmy_script_path ).st_mode | stat.S_IEXEC
)
same_name_file = False same_name_file = False
if ( if (
os.path.exists(os.path.join(parent_dir, "hmy")) and Path(path).name != "hmy" os.path.exists( os.path.join( parent_dir,
"hmy" ) ) and
Path( path ).name != "hmy"
): # Save same name file. ): # Save same name file.
same_name_file = True same_name_file = True
os.rename( os.rename(
os.path.join(parent_dir, "hmy"), os.path.join(parent_dir, ".hmy_tmp") os.path.join( parent_dir,
"hmy" ),
os.path.join( parent_dir,
".hmy_tmp" )
) )
if verbose: if verbose:
subprocess.call([hmy_script_path, "-d"]) subprocess.call( [ hmy_script_path, "-d" ] )
else: else:
with open(os.devnull, "w", encoding = "UTF-8") as devnull: with open( os.devnull, "w", encoding = "UTF-8" ) as devnull:
subprocess.call( subprocess.call(
[hmy_script_path, "-d"], [ hmy_script_path,
stdout=devnull, "-d" ],
stderr=subprocess.STDOUT, stdout = devnull,
stderr = subprocess.STDOUT,
) )
os.rename(os.path.join(parent_dir, "hmy"), path) os.rename( os.path.join( parent_dir, "hmy" ), path )
if same_name_file: if same_name_file:
os.rename( os.rename(
os.path.join(parent_dir, ".hmy_tmp"), os.path.join(parent_dir, "hmy") os.path.join( parent_dir,
".hmy_tmp" ),
os.path.join( parent_dir,
"hmy" )
) )
if verbose: if verbose:
print(f"Saved harmony binary to: `{path}`") print( f"Saved harmony binary to: `{path}`" )
os.chdir(old_cwd) os.chdir( old_cwd )
env = os.environ.copy() env = os.environ.copy()
if sys.platform.startswith("darwin"): # Dynamic linking for darwin if sys.platform.startswith( "darwin" ): # Dynamic linking for darwin
try: try:
files_in_parent_dir = set(os.listdir(parent_dir)) files_in_parent_dir = set( os.listdir( parent_dir ) )
if files_in_parent_dir.intersection(_libs) == _libs: if files_in_parent_dir.intersection( _libs ) == _libs:
env["DYLD_FALLBACK_LIBRARY_PATH"] = parent_dir env[ "DYLD_FALLBACK_LIBRARY_PATH" ] = parent_dir
elif os.path.exists( elif os.path.exists(
f"{get_gopath()}/src/github.com/harmony-one/bls" f"{get_gopath()}/src/github.com/harmony-one/bls"
) and os.path.exists(f"{get_gopath()}/src/github.com/harmony-one/mcl"): ) and os.path.exists(
env.update(get_bls_build_variables()) f"{get_gopath()}/src/github.com/harmony-one/mcl"
):
env.update( get_bls_build_variables() )
else: else:
raise RuntimeWarning( raise RuntimeWarning(
f"Could not get environment for downloaded hmy CLI at `{path}`" f"Could not get environment for downloaded hmy CLI at `{path}`"

@ -15,4 +15,4 @@ IDENTITY_CHAR_LIMIT = 140
WEBSITE_CHAR_LIMIT = 140 WEBSITE_CHAR_LIMIT = 140
SECURITY_CONTACT_CHAR_LIMIT = 140 SECURITY_CONTACT_CHAR_LIMIT = 140
DETAILS_CHAR_LIMIT = 280 DETAILS_CHAR_LIMIT = 280
MIN_REQUIRED_DELEGATION = int(10000 * 1e18) MIN_REQUIRED_DELEGATION = int( 10000 * 1e18 )

@ -76,11 +76,14 @@ def call( # pylint: disable=too-many-arguments
] ]
method = "hmyv2_call" method = "hmyv2_call"
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def estimate_gas( # pylint: disable=too-many-arguments def estimate_gas( # pylint: disable=too-many-arguments
@ -141,17 +144,23 @@ def estimate_gas( # pylint: disable=too-many-arguments
method = "hmyv2_estimateGas" method = "hmyv2_estimateGas"
try: try:
return int( return int(
rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ rpc_request(
"result" method,
], params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ],
16, 16,
) )
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_code( def get_code(
address, block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT address,
block_num,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> str: ) -> str:
"""Get the code stored at the given address in the state for the given """Get the code stored at the given address in the state for the given
block number. block number.
@ -182,18 +191,25 @@ def get_code(
https://api.hmny.io/?version=latest#e13e9d78-9322-4dc8-8917-f2e721a8e556 https://api.hmny.io/?version=latest#e13e9d78-9322-4dc8-8917-f2e721a8e556
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/contract.go#L59 https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/contract.go#L59
""" """
params = [address, block_num] params = [ address, block_num ]
method = "hmyv2_getCode" method = "hmyv2_getCode"
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_storage_at( def get_storage_at(
address, key, block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT address,
key,
block_num,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> str: ) -> str:
"""Get the storage from the state at the given address, the key and the """Get the storage from the state at the given address, the key and the
block number. block number.
@ -226,18 +242,23 @@ def get_storage_at(
https://api.hmny.io/?version=latest#fa8ac8bd-952d-4149-968c-857ca76da43f https://api.hmny.io/?version=latest#fa8ac8bd-952d-4149-968c-857ca76da43f
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/contract.go#L84 https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/contract.go#L84
""" """
params = [address, key, block_num] params = [ address, key, block_num ]
method = "hmyv2_getStorageAt" method = "hmyv2_getStorageAt"
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_contract_address_from_hash( def get_contract_address_from_hash(
tx_hash, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT tx_hash,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> str: ) -> str:
"""Get address of the contract which was deployed in the transaction """Get address of the contract which was deployed in the transaction
represented by tx_hash. represented by tx_hash.
@ -266,6 +287,11 @@ def get_contract_address_from_hash(
https://github.com/harmony-one/harmony-test/blob/master/localnet/rpc_tests/test_contract.py#L36 https://github.com/harmony-one/harmony-test/blob/master/localnet/rpc_tests/test_contract.py#L36
""" """
try: try:
return get_transaction_receipt(tx_hash, endpoint, timeout)["contractAddress"] return get_transaction_receipt( tx_hash,
endpoint,
timeout )[ "contractAddress" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError("hmyv2_getTransactionReceipt", endpoint) from exception raise InvalidRPCReplyError(
"hmyv2_getTransactionReceipt",
endpoint
) from exception

@ -2,16 +2,16 @@
Exceptions used by pyhmy Exceptions used by pyhmy
""" """
class InvalidRPCReplyError(RuntimeError):
class InvalidRPCReplyError( RuntimeError ):
"""Exception raised when RPC call returns unexpected result Generally """Exception raised when RPC call returns unexpected result Generally
indicates Harmony API has been updated & pyhmy library needs to be updated indicates Harmony API has been updated & pyhmy library needs to be updated
as well.""" as well."""
def __init__( self, method, endpoint ):
def __init__(self, method, endpoint): super().__init__( f"Unexpected reply for {method} from {endpoint}" )
super().__init__(f"Unexpected reply for {method} from {endpoint}")
class InvalidValidatorError(ValueError): class InvalidValidatorError( ValueError ):
"""Exception raised Validator does not pass sanity checks.""" """Exception raised Validator does not pass sanity checks."""
errors = { errors = {
@ -22,18 +22,17 @@ class InvalidValidatorError(ValueError):
5: "Unable to import validator information from blockchain", 5: "Unable to import validator information from blockchain",
} }
def __init__(self, err_code, msg): def __init__( self, err_code, msg ):
self.code = err_code self.code = err_code
self.msg = msg self.msg = msg
super().__init__(msg) super().__init__( msg )
def __str__(self): def __str__( self ):
return f"[Errno {self.code}] {self.errors[self.code]}: {self.msg}" return f"[Errno {self.code}] {self.errors[self.code]}: {self.msg}"
class TxConfirmationTimedoutError(AssertionError): class TxConfirmationTimedoutError( AssertionError ):
"""Exception raised when a transaction is sent to the chain But not """Exception raised when a transaction is sent to the chain But not
confirmed during the timeout period specified.""" confirmed during the timeout period specified."""
def __init__( self, msg ):
def __init__(self, msg): super().__init__( f"{msg}" )
super().__init__(f"{msg}")

@ -10,37 +10,41 @@ import logging
import logging.handlers import logging.handlers
class _GZipRotator: # pylint: disable=too-few-public-methods class _GZipRotator: # pylint: disable=too-few-public-methods
def __call__(self, source, dest): def __call__( self, source, dest ):
os.rename(source, dest) os.rename( source, dest )
with open(dest, "rb") as f_in: with open( dest, "rb" ) as f_in:
with gzip.open(f"{dest}.gz", "wb") as f_out: with gzip.open( f"{dest}.gz", "wb" ) as f_out:
f_out.writelines(f_in) f_out.writelines( f_in )
os.remove(dest) os.remove( dest )
class ControlledLogger: # pylint: disable=too-many-instance-attributes class ControlledLogger: # pylint: disable=too-many-instance-attributes
"""A simple logger that only writes to file when the 'write' method is """A simple logger that only writes to file when the 'write' method is
called.""" called."""
def __init__( self, logger_name, log_dir, backup_count = 5 ):
def __init__(self, logger_name, log_dir, backup_count=5):
""" """
:param logger_name: The name of the logger and logfile :param logger_name: The name of the logger and logfile
:param log_dir: The directory in which to save this log file (can be abs or relative). :param log_dir: The directory in which to save this log file (can be abs or relative).
""" """
if log_dir.endswith("/"): if log_dir.endswith( "/" ):
log_dir = log_dir[:-1] log_dir = log_dir[ :-1 ]
log_dir = os.path.realpath(log_dir) log_dir = os.path.realpath( log_dir )
os.makedirs(log_dir, exist_ok=True) os.makedirs( log_dir, exist_ok = True )
handler = logging.handlers.TimedRotatingFileHandler( handler = logging.handlers.TimedRotatingFileHandler(
f"{log_dir}/{logger_name}.log", "midnight", 1, backupCount=backup_count f"{log_dir}/{logger_name}.log",
"midnight",
1,
backupCount = backup_count
)
handler.setFormatter(
logging.Formatter( "%(levelname)s - %(message)s" )
) )
handler.setFormatter(logging.Formatter("%(levelname)s - %(message)s"))
handler.rotator = _GZipRotator() handler.rotator = _GZipRotator()
self.filename = handler.baseFilename self.filename = handler.baseFilename
self.logger = logging.getLogger(logger_name) self.logger = logging.getLogger( logger_name )
self.logger.addHandler(handler) self.logger.addHandler( handler )
self._lock = threading.Lock() self._lock = threading.Lock()
self.filepath = f"{log_dir}/{logger_name}.log" self.filepath = f"{log_dir}/{logger_name}.log"
self.info_buffer = [] self.info_buffer = []
@ -48,88 +52,92 @@ class ControlledLogger: # pylint: disable=too-many-instance-attributes
self.warning_buffer = [] self.warning_buffer = []
self.error_buffer = [] self.error_buffer = []
def __repr__(self): def __repr__( self ):
return f"<ControlledLogger @ {self.filepath} : {self.logger}>" return f"<ControlledLogger @ {self.filepath} : {self.logger}>"
def _clear(self): def _clear( self ):
"""Internal method to clear the log buffer.""" """Internal method to clear the log buffer."""
self.info_buffer.clear() self.info_buffer.clear()
self.debug_buffer.clear() self.debug_buffer.clear()
self.warning_buffer.clear() self.warning_buffer.clear()
self.error_buffer.clear() self.error_buffer.clear()
def info(self, msg): def info( self, msg ):
""" """
:param msg: The info message to log :param msg: The info message to log
""" """
with self._lock: with self._lock:
self.info_buffer.append( self.info_buffer.append(
f"[{threading.get_ident()}] " f"{datetime.datetime.utcnow()} : {msg}" f"[{threading.get_ident()}] "
f"{datetime.datetime.utcnow()} : {msg}"
) )
def debug(self, msg): def debug( self, msg ):
""" """
:param msg: The debug message to log :param msg: The debug message to log
""" """
with self._lock: with self._lock:
self.debug_buffer.append( self.debug_buffer.append(
f"[{threading.get_ident()}] " f"{datetime.datetime.utcnow()} : {msg}" f"[{threading.get_ident()}] "
f"{datetime.datetime.utcnow()} : {msg}"
) )
def warning(self, msg): def warning( self, msg ):
""" """
:param msg: The warning message to log :param msg: The warning message to log
""" """
with self._lock: with self._lock:
self.warning_buffer.append( self.warning_buffer.append(
f"[{threading.get_ident()}] " f"{datetime.datetime.utcnow()} : {msg}" f"[{threading.get_ident()}] "
f"{datetime.datetime.utcnow()} : {msg}"
) )
def error(self, msg): def error( self, msg ):
""" """
:param msg: The error message to log :param msg: The error message to log
""" """
with self._lock: with self._lock:
self.error_buffer.append( self.error_buffer.append(
f"[{threading.get_ident()}] " f"{datetime.datetime.utcnow()} : {msg}" f"[{threading.get_ident()}] "
f"{datetime.datetime.utcnow()} : {msg}"
) )
def print_info(self): def print_info( self ):
"""Prints the current info buffer but does not flush it to log file.""" """Prints the current info buffer but does not flush it to log file."""
print("\n".join(self.info_buffer)) print( "\n".join( self.info_buffer ) )
def print_debug(self): def print_debug( self ):
"""Prints the current debug buffer but does not flush it to log """Prints the current debug buffer but does not flush it to log
file.""" file."""
print("\n".join(self.debug_buffer)) print( "\n".join( self.debug_buffer ) )
def print_warning(self): def print_warning( self ):
"""Prints the current warning buffer but does not flush it to log """Prints the current warning buffer but does not flush it to log
file.""" file."""
print("\n".join(self.warning_buffer)) print( "\n".join( self.warning_buffer ) )
def print_error(self): def print_error( self ):
"""Prints the current error buffer but does not flush it to log """Prints the current error buffer but does not flush it to log
file.""" file."""
print("\n".join(self.error_buffer)) print( "\n".join( self.error_buffer ) )
def write(self): def write( self ):
"""Flushes ALL of the log buffers to the log file via the logger. """Flushes ALL of the log buffers to the log file via the logger.
Note that directly after this method call, the respective prints Note that directly after this method call, the respective prints
will print nothing since all log messages are flushed to file. will print nothing since all log messages are flushed to file.
""" """
with self._lock: with self._lock:
self.logger.setLevel(logging.DEBUG) self.logger.setLevel( logging.DEBUG )
for line in self.debug_buffer: for line in self.debug_buffer:
self.logger.debug(line) self.logger.debug( line )
self.logger.setLevel(logging.WARNING) self.logger.setLevel( logging.WARNING )
for line in self.warning_buffer: for line in self.warning_buffer:
self.logger.warning(line) self.logger.warning( line )
self.logger.setLevel(logging.ERROR) self.logger.setLevel( logging.ERROR )
for line in self.error_buffer: for line in self.error_buffer:
self.logger.error(line) self.logger.error( line )
self.logger.setLevel(logging.INFO) self.logger.setLevel( logging.INFO )
for line in self.info_buffer: for line in self.info_buffer:
self.logger.info(line) self.logger.info( line )
self._clear() self._clear()

@ -5,11 +5,10 @@ For more granular conversions, see Web3.toWei
from decimal import Decimal from decimal import Decimal
_conversion_unit = Decimal( 1e18 )
_conversion_unit = Decimal(1e18)
def convert_atto_to_one( atto ) -> Decimal:
def convert_atto_to_one(atto) -> Decimal:
"""Convert ATTO to ONE. """Convert ATTO to ONE.
Parameters Parameters
@ -23,12 +22,12 @@ def convert_atto_to_one(atto) -> Decimal:
decimal decimal
Converted value in ONE Converted value in ONE
""" """
if isinstance(atto, float): if isinstance( atto, float ):
atto = int(atto) atto = int( atto )
return Decimal(atto) / _conversion_unit return Decimal( atto ) / _conversion_unit
def convert_one_to_atto(one) -> Decimal: def convert_one_to_atto( one ) -> Decimal:
"""Convert ONE to ATTO. """Convert ONE to ATTO.
Parameters Parameters
@ -41,6 +40,6 @@ def convert_one_to_atto(one) -> Decimal:
decimal decimal
Converted value in ATTO Converted value in ATTO
""" """
if isinstance(one, float): if isinstance( one, float ):
one = str(one) one = str( one )
return Decimal(one) * _conversion_unit return Decimal( one ) * _conversion_unit

@ -4,23 +4,23 @@ RPC Specific Exceptions
import requests import requests
class RPCError(RuntimeError):
"""Exception raised when RPC call returns an error."""
def __init__(self, method, endpoint, error): class RPCError( RuntimeError ):
"""Exception raised when RPC call returns an error."""
def __init__( self, method, endpoint, error ):
self.error = error self.error = error
super().__init__(f"Error in reply from {endpoint}: {method} returned {error}") super().__init__(
f"Error in reply from {endpoint}: {method} returned {error}"
)
class RequestsError(requests.exceptions.RequestException): 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}" )
def __init__(self, endpoint):
super().__init__(f"Error connecting to {endpoint}")
class RequestsTimeoutError( requests.exceptions.Timeout ):
class RequestsTimeoutError(requests.exceptions.Timeout):
"""Wrapper for requests lib Timeout exceptions.""" """Wrapper for requests lib Timeout exceptions."""
def __init__( self, endpoint ):
def __init__(self, endpoint): super().__init__( f"Error connecting to {endpoint}" )
super().__init__(f"Error connecting to {endpoint}")

@ -11,7 +11,10 @@ from ..constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT
def base_request( def base_request(
method, params=None, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT method,
params = None,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> str: ) -> str:
"""Basic RPC request. """Basic RPC request.
@ -42,30 +45,40 @@ def base_request(
""" """
if params is None: if params is None:
params = [] params = []
elif not isinstance(params, list): elif not isinstance( params, list ):
raise TypeError(f"invalid type {params.__class__}") raise TypeError( f"invalid type {params.__class__}" )
try: try:
payload = {"id": "1", "jsonrpc": "2.0", "method": method, "params": params} payload = {
headers = {"Content-Type": "application/json"} "id": "1",
"jsonrpc": "2.0",
"method": method,
"params": params
}
headers = {
"Content-Type": "application/json"
}
resp = requests.request( resp = requests.request(
"POST", "POST",
endpoint, endpoint,
headers=headers, headers = headers,
data=json.dumps(payload), data = json.dumps( payload ),
timeout=timeout, timeout = timeout,
allow_redirects=True, allow_redirects = True,
) )
return resp.content return resp.content
except requests.exceptions.Timeout as err: except requests.exceptions.Timeout as err:
raise RequestsTimeoutError(endpoint) from err raise RequestsTimeoutError( endpoint ) from err
except requests.exceptions.RequestException as err: except requests.exceptions.RequestException as err:
raise RequestsError(endpoint) from err raise RequestsError( endpoint ) from err
def rpc_request( def rpc_request(
method, params=None, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT method,
params = None,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict: ) -> dict:
"""RPC request. """RPC request.
@ -100,12 +113,12 @@ def rpc_request(
-------- --------
base_request base_request
""" """
raw_resp = base_request(method, params, endpoint, timeout) raw_resp = base_request( method, params, endpoint, timeout )
try: try:
resp = json.loads(raw_resp) resp = json.loads( raw_resp )
if "error" in resp: if "error" in resp:
raise RPCError(method, endpoint, str(resp["error"])) raise RPCError( method, endpoint, str( resp[ "error" ] ) )
return resp return resp
except json.decoder.JSONDecodeError as err: except json.decoder.JSONDecodeError as err:
raise RPCError(method, endpoint, raw_resp) from err raise RPCError( method, endpoint, raw_resp ) from err

@ -38,25 +38,34 @@ HARMONY_FORMATTERS = dict(
) )
class UnsignedHarmonyTxData(HashableRLP): class UnsignedHarmonyTxData( HashableRLP ):
""" """
Unsigned Harmony transaction data Unsigned Harmony transaction data
Includes `shardID` and `toShardID` Includes `shardID` and `toShardID`
as the difference against Eth as the difference against Eth
""" """
fields = ( fields = (
("nonce", big_endian_int), ( "nonce",
("gasPrice", big_endian_int), big_endian_int ),
("gas", big_endian_int), ( "gasPrice",
("shardID", big_endian_int), big_endian_int ),
("toShardID", big_endian_int), ( "gas",
("to", Binary.fixed_length(20, allow_empty=True)), big_endian_int ),
("value", big_endian_int), ( "shardID",
("data", binary), big_endian_int ),
( "toShardID",
big_endian_int ),
( "to",
Binary.fixed_length( 20,
allow_empty = True ) ),
( "value",
big_endian_int ),
( "data",
binary ),
) )
class SignedHarmonyTxData(HashableRLP): class SignedHarmonyTxData( HashableRLP ):
""" """
Signed Harmony transaction data Signed Harmony transaction data
Includes `shardID` and `toShardID` Includes `shardID` and `toShardID`
@ -68,22 +77,35 @@ class SignedHarmonyTxData(HashableRLP):
("s", big_endian_int), # Next 32 bytes ("s", big_endian_int), # Next 32 bytes
) )
# https://github.com/ethereum/eth-account/blob/00e7b10005c5fa7090086fcef37a76296c524e17/eth_account/_utils/transactions.py#L55 # https://github.com/ethereum/eth-account/blob/00e7b10005c5fa7090086fcef37a76296c524e17/eth_account/_utils/transactions.py#L55
def encode_transaction( def encode_transaction( unsigned_transaction, vrs ):
unsigned_transaction, vrs
):
"""serialize and encode an unsigned transaction with v,r,s.""" """serialize and encode an unsigned transaction with v,r,s."""
(v, r, s) = vrs # pylint: disable=invalid-name ( v, r, s ) = vrs # pylint: disable=invalid-name
chain_naive_transaction = dissoc(unsigned_transaction.as_dict(), "v", "r", "s") chain_naive_transaction = dissoc(
if isinstance(unsigned_transaction, (UnsignedHarmonyTxData, SignedHarmonyTxData)): unsigned_transaction.as_dict(),
"v",
"r",
"s"
)
if isinstance(
unsigned_transaction,
( UnsignedHarmonyTxData,
SignedHarmonyTxData )
):
serializer = SignedHarmonyTxData serializer = SignedHarmonyTxData
else: else:
serializer = SignedEthereumTxData serializer = SignedEthereumTxData
signed_transaction = serializer(v=v, r=r, s=s, **chain_naive_transaction) signed_transaction = serializer(
return rlp.encode(signed_transaction) v = v,
r = r,
s = s,
**chain_naive_transaction
)
return rlp.encode( signed_transaction )
def serialize_transaction(filled_transaction): def serialize_transaction( filled_transaction ):
"""serialize a signed/unsigned transaction.""" """serialize a signed/unsigned transaction."""
if "v" in filled_transaction: if "v" in filled_transaction:
if "shardID" in filled_transaction: if "shardID" in filled_transaction:
@ -98,35 +120,42 @@ def serialize_transaction(filled_transaction):
for field, _ in serializer._meta.fields: for field, _ in serializer._meta.fields:
assert field in filled_transaction, f"Could not find {field} in transaction" assert field in filled_transaction, f"Could not find {field} in transaction"
return serializer.from_dict( return serializer.from_dict(
{field: filled_transaction[field] for field, _ 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 # https://github.com/ethereum/eth-account/blob/00e7b10005c5fa7090086fcef37a76296c524e17/eth_account/account.py#L650
def sanitize_transaction(transaction_dict, private_key): def sanitize_transaction( transaction_dict, private_key ):
"""remove the originating address from the dict and convert chainId to """remove the originating address from the dict and convert chainId to
int.""" int."""
account = Account.from_key( # pylint: disable=no-value-for-parameter account = Account.from_key( # pylint: disable=no-value-for-parameter
private_key private_key
) )
sanitized_transaction = transaction_dict.copy() # do not alter the original dictionary sanitized_transaction = transaction_dict.copy(
) # do not alter the original dictionary
if "from" in sanitized_transaction: if "from" in sanitized_transaction:
sanitized_transaction["from"] = convert_one_to_hex(transaction_dict["from"]) sanitized_transaction[ "from" ] = convert_one_to_hex(
if ( transaction_dict[ "from" ]
sanitized_transaction["from"] == account.address )
): if sanitized_transaction[ "from" ] == account.address:
sanitized_transaction = dissoc(sanitized_transaction, "from") sanitized_transaction = dissoc( sanitized_transaction, "from" )
else: else:
raise TypeError( raise TypeError(
"from field must match key's {account.address}, " "from field must match key's {account.address}, "
"but it was {sanitized_transaction['from']}" "but it was {sanitized_transaction['from']}"
) )
if "chainId" in sanitized_transaction: if "chainId" in sanitized_transaction:
sanitized_transaction["chainId"] = chain_id_to_int(sanitized_transaction["chainId"]) sanitized_transaction[ "chainId" ] = chain_id_to_int(
sanitized_transaction[ "chainId" ]
)
return account, sanitized_transaction return account, sanitized_transaction
def sign_transaction(transaction_dict, private_key) -> SignedTransaction: def sign_transaction( transaction_dict, private_key ) -> SignedTransaction:
"""Sign a (non-staking) transaction dictionary with the specified private """Sign a (non-staking) transaction dictionary with the specified private
key. key.
@ -172,33 +201,50 @@ def sign_transaction(transaction_dict, private_key) -> SignedTransaction:
https://readthedocs.org/projects/eth-account/downloads/pdf/stable/ https://readthedocs.org/projects/eth-account/downloads/pdf/stable/
""" """
account, sanitized_transaction = sanitize_transaction(transaction_dict, private_key) account, sanitized_transaction = sanitize_transaction(transaction_dict, private_key)
if "to" in sanitized_transaction and sanitized_transaction["to"] is not None: if "to" in sanitized_transaction and sanitized_transaction[ "to"
sanitized_transaction["to"] = convert_one_to_hex(sanitized_transaction["to"]) ] is not None:
sanitized_transaction[ "to" ] = convert_one_to_hex(
sanitized_transaction[ "to" ]
)
# 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( filled_transaction = pipe(
sanitized_transaction, sanitized_transaction,
dict, dict,
partial(merge, TRANSACTION_DEFAULTS), partial( merge,
TRANSACTION_DEFAULTS ),
chain_id_to_v, chain_id_to_v,
apply_formatters_to_dict(HARMONY_FORMATTERS), apply_formatters_to_dict( HARMONY_FORMATTERS ),
) )
unsigned_transaction = serialize_transaction(filled_transaction) unsigned_transaction = serialize_transaction( filled_transaction )
transaction_hash = unsigned_transaction.hash() transaction_hash = unsigned_transaction.hash()
# https://github.com/ethereum/eth-account/blob/00e7b10005c5fa7090086fcef37a76296c524e17/eth_account/_utils/signing.py#L26 # https://github.com/ethereum/eth-account/blob/00e7b10005c5fa7090086fcef37a76296c524e17/eth_account/_utils/signing.py#L26
if isinstance( if isinstance(
unsigned_transaction, (UnsignedEthereumTxData, UnsignedHarmonyTxData) unsigned_transaction,
( UnsignedEthereumTxData,
UnsignedHarmonyTxData )
): ):
chain_id = None chain_id = None
else: else:
chain_id = unsigned_transaction.v chain_id = unsigned_transaction.v
(v, r, s) = sign_transaction_hash(account._key_obj, transaction_hash, chain_id) # pylint: disable=invalid-name ( v, # pylint: disable=invalid-name
encoded_transaction = encode_transaction(unsigned_transaction, vrs=(v, r, s)) r, # pylint: disable=invalid-name
signed_transaction_hash = keccak(encoded_transaction) s ) = sign_transaction_hash( # pylint: disable=invalid-name
account._key_obj,
transaction_hash,
chain_id
)
encoded_transaction = encode_transaction(
unsigned_transaction,
vrs = ( v,
r,
s )
)
signed_transaction_hash = keccak( encoded_transaction )
return SignedTransaction( return SignedTransaction(
rawTransaction=HexBytes(encoded_transaction), rawTransaction = HexBytes( encoded_transaction ),
hash=HexBytes(signed_transaction_hash), hash = HexBytes( signed_transaction_hash ),
r=r, r = r,
s=s, s = s,
v=v, v = v,
) )

@ -8,11 +8,13 @@ from .exceptions import InvalidRPCReplyError
from .constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT from .constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT
################## ##################
# Validator RPCs # # Validator RPCs #
################## ##################
def get_all_validator_addresses( def get_all_validator_addresses(
endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list: ) -> list:
"""Get list of all created validator addresses on chain. """Get list of all created validator addresses on chain.
@ -39,13 +41,17 @@ def get_all_validator_addresses(
""" """
method = "hmyv2_getAllValidatorAddresses" method = "hmyv2_getAllValidatorAddresses"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_validator_information( def get_validator_information(
validator_addr, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT validator_addr,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict: ) -> dict:
"""Get validator information for validator address. """Get validator information for validator address.
@ -118,17 +124,21 @@ def get_validator_information(
https://api.hmny.io/#659ad999-14ca-4498-8f74-08ed347cab49 https://api.hmny.io/#659ad999-14ca-4498-8f74-08ed347cab49
""" """
method = "hmyv2_getValidatorInformation" method = "hmyv2_getValidatorInformation"
params = [validator_addr] params = [ validator_addr ]
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_elected_validator_addresses( def get_elected_validator_addresses(
endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list: ) -> list:
"""Get list of elected validator addresses. """Get list of elected validator addresses.
@ -156,12 +166,18 @@ def get_elected_validator_addresses(
""" """
method = "hmyv2_getElectedValidatorAddresses" method = "hmyv2_getElectedValidatorAddresses"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_validators(epoch, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> list: def get_validators(
epoch,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list:
"""Get validators list for a particular epoch. """Get validators list for a particular epoch.
Parameters Parameters
@ -191,17 +207,22 @@ def get_validators(epoch, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) ->
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L152 https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L152
""" """
method = "hmyv2_getValidators" method = "hmyv2_getValidators"
params = [epoch] params = [ epoch ]
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_validator_keys( def get_validator_keys(
epoch, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT epoch,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list: ) -> list:
"""Get validator BLS keys in the committee for a particular epoch. """Get validator BLS keys in the committee for a particular epoch.
@ -228,17 +249,23 @@ def get_validator_keys(
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L152 https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L152
""" """
method = "hmyv2_getValidatorKeys" method = "hmyv2_getValidatorKeys"
params = [epoch] params = [ epoch ]
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_validator_information_by_block_number( def get_validator_information_by_block_number(
validator_addr, block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT validator_addr,
block_num,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
): ):
"""Get validator information for validator address at a block. """Get validator information for validator address at a block.
@ -267,17 +294,22 @@ def get_validator_information_by_block_number(
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L319 https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L319
""" """
method = "hmyv2_getValidatorInformationByBlockNumber" method = "hmyv2_getValidatorInformationByBlockNumber"
params = [validator_addr, block_num] params = [ validator_addr, block_num ]
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_all_validator_information( def get_all_validator_information(
page=0, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT page = 0,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list: ) -> list:
"""Get validator information for all validators on chain. """Get validator information for all validators on chain.
@ -304,17 +336,22 @@ def get_all_validator_information(
https://api.hmny.io/#df5f1631-7397-48e8-87b4-8dd873235b9c https://api.hmny.io/#df5f1631-7397-48e8-87b4-8dd873235b9c
""" """
method = "hmyv2_getAllValidatorInformation" method = "hmyv2_getAllValidatorInformation"
params = [page] params = [ page ]
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_validator_self_delegation( def get_validator_self_delegation(
address, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT address,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int: ) -> int:
"""Get the amount self delegated by validator. """Get the amount self delegated by validator.
@ -341,19 +378,24 @@ def get_validator_self_delegation(
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L352 https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L352
""" """
method = "hmyv2_getValidatorSelfDelegation" method = "hmyv2_getValidatorSelfDelegation"
params = [address] params = [ address ]
try: try:
return int( return int(
rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
) )
except (KeyError, TypeError) as exception: except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_validator_total_delegation( def get_validator_total_delegation(
address, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT address,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int: ) -> int:
"""Get the total amount delegated t ovalidator (including self delegated) """Get the total amount delegated t ovalidator (including self delegated)
@ -380,19 +422,25 @@ def get_validator_total_delegation(
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L379 https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L379
""" """
method = "hmyv2_getValidatorTotalDelegation" method = "hmyv2_getValidatorTotalDelegation"
params = [address] params = [ address ]
try: try:
return int( return int(
rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
) )
except (KeyError, TypeError) as exception: except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_all_validator_information_by_block_number( def get_all_validator_information_by_block_number(
block_num, page=0, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT block_num,
page = 0,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list: ) -> list:
"""Get validator information at block number for all validators on chain. """Get validator information at block number for all validators on chain.
@ -422,20 +470,25 @@ def get_all_validator_information_by_block_number(
https://api.hmny.io/#a229253f-ca76-4b9d-88f5-9fd96e40d583 https://api.hmny.io/#a229253f-ca76-4b9d-88f5-9fd96e40d583
""" """
method = "hmyv2_getAllValidatorInformationByBlockNumber" method = "hmyv2_getAllValidatorInformationByBlockNumber"
params = [page, block_num] params = [ page, block_num ]
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
################### ###################
# Delegation RPCs # # Delegation RPCs #
################### ###################
def get_all_delegation_information( def get_all_delegation_information(
page=0, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT page = 0,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list: ) -> list:
"""Get delegation information for all delegators on chain. """Get delegation information for all delegators on chain.
@ -464,19 +517,22 @@ def get_all_delegation_information(
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L413 https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L413
""" """
method = "hmyv2_getAllDelegationInformation" method = "hmyv2_getAllDelegationInformation"
params = [ params = [ page, ]
page,
]
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_delegations_by_delegator( def get_delegations_by_delegator(
delegator_addr, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT delegator_addr,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list: ) -> list:
"""Get list of delegations by a delegator. """Get list of delegations by a delegator.
@ -510,17 +566,23 @@ def get_delegations_by_delegator(
https://api.hmny.io/#454b032c-6072-4ecb-bf24-38b3d6d2af69 https://api.hmny.io/#454b032c-6072-4ecb-bf24-38b3d6d2af69
""" """
method = "hmyv2_getDelegationsByDelegator" method = "hmyv2_getDelegationsByDelegator"
params = [delegator_addr] params = [ delegator_addr ]
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_delegations_by_delegator_by_block_number( def get_delegations_by_delegator_by_block_number(
delegator_addr, block_num, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT delegator_addr,
block_num,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list: ) -> list:
"""Get list of delegations by a delegator at a specific block. """Get list of delegations by a delegator at a specific block.
@ -549,20 +611,23 @@ def get_delegations_by_delegator_by_block_number(
https://api.hmny.io/#8ce13bda-e768-47b9-9dbe-193aba410b0a https://api.hmny.io/#8ce13bda-e768-47b9-9dbe-193aba410b0a
""" """
method = "hmyv2_getDelegationsByDelegatorByBlockNumber" method = "hmyv2_getDelegationsByDelegatorByBlockNumber"
params = [delegator_addr, block_num] params = [ delegator_addr, block_num ]
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_delegation_by_delegator_and_validator( def get_delegation_by_delegator_and_validator(
delegator_addr, delegator_addr,
validator_address, validator_address,
endpoint=DEFAULT_ENDPOINT, endpoint = DEFAULT_ENDPOINT,
timeout=DEFAULT_TIMEOUT, timeout = DEFAULT_TIMEOUT,
) -> dict: ) -> dict:
"""Get list of delegations by a delegator at a specific block. """Get list of delegations by a delegator at a specific block.
@ -592,17 +657,22 @@ def get_delegation_by_delegator_and_validator(
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L605 https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L605
""" """
method = "hmyv2_getDelegationByDelegatorAndValidator" method = "hmyv2_getDelegationByDelegatorAndValidator"
params = [delegator_addr, validator_address] params = [ delegator_addr, validator_address ]
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_available_redelegation_balance( def get_available_redelegation_balance(
delegator_addr, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT delegator_addr,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int: ) -> int:
"""Get amount of locked undelegated tokens. """Get amount of locked undelegated tokens.
@ -629,19 +699,24 @@ def get_available_redelegation_balance(
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L653 https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L653
""" """
method = "hmyv2_getAvailableRedelegationBalance" method = "hmyv2_getAvailableRedelegationBalance"
params = [delegator_addr] params = [ delegator_addr ]
try: try:
return int( return int(
rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
) )
except (KeyError, TypeError) as exception: except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_delegations_by_validator( def get_delegations_by_validator(
validator_addr, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT validator_addr,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list: ) -> list:
"""Get list of delegations to a validator. """Get list of delegations to a validator.
@ -668,20 +743,24 @@ def get_delegations_by_validator(
https://api.hmny.io/#2e02d8db-8fec-41d9-a672-2c9862f63f39 https://api.hmny.io/#2e02d8db-8fec-41d9-a672-2c9862f63f39
""" """
method = "hmyv2_getDelegationsByValidator" method = "hmyv2_getDelegationsByValidator"
params = [validator_addr] params = [ validator_addr ]
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
######################## ########################
# Staking Network RPCs # # Staking Network RPCs #
######################## ########################
def get_current_utility_metrics( def get_current_utility_metrics(
endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict: ) -> dict:
"""Get current utility metrics of network. """Get current utility metrics of network.
@ -711,13 +790,16 @@ def get_current_utility_metrics(
""" """
method = "hmyv2_getCurrentUtilityMetrics" method = "hmyv2_getCurrentUtilityMetrics"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_staking_network_info( def get_staking_network_info(
endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict: ) -> dict:
"""Get staking network information. """Get staking network information.
@ -748,12 +830,17 @@ def get_staking_network_info(
""" """
method = "hmyv2_getStakingNetworkInfo" method = "hmyv2_getStakingNetworkInfo"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_super_committees(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict: def get_super_committees(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict:
"""Get voting committees for current & previous epoch. """Get voting committees for current & previous epoch.
Parameters Parameters
@ -801,12 +888,17 @@ def get_super_committees(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) ->
""" """
method = "hmyv2_getSuperCommittees" method = "hmyv2_getSuperCommittees"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_total_staking(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int: def get_total_staking(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""Get total staking by validators, only meant to be called on beaconchain. """Get total staking by validators, only meant to be called on beaconchain.
Parameters Parameters
@ -831,13 +923,18 @@ def get_total_staking(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> int
""" """
method = "hmyv2_getTotalStaking" method = "hmyv2_getTotalStaking"
try: try:
return int(rpc_request(method, endpoint=endpoint, timeout=timeout)["result"]) return int(
except (KeyError, TypeError) as exception: rpc_request( method,
raise InvalidRPCReplyError(method, endpoint) from exception endpoint = endpoint,
timeout = timeout )[ "result" ]
)
except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_raw_median_stake_snapshot( def get_raw_median_stake_snapshot(
endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict: ) -> dict:
"""Get median stake & additional committee data of the current epoch. """Get median stake & additional committee data of the current epoch.
@ -876,6 +973,8 @@ def get_raw_median_stake_snapshot(
""" """
method = "hmyv2_getMedianRawStakeSnapshot" method = "hmyv2_getMedianRawStakeSnapshot"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception

@ -7,12 +7,7 @@ import math
from decimal import Decimal from decimal import Decimal
from functools import partial from functools import partial
from toolz import ( from toolz import ( pipe, dissoc, merge, identity, )
pipe,
dissoc,
merge,
identity,
)
from hexbytes import HexBytes from hexbytes import HexBytes
@ -32,7 +27,7 @@ from eth_utils.curried import (
to_int, to_int,
apply_formatters_to_sequence, apply_formatters_to_sequence,
apply_formatter_to_array, apply_formatter_to_array,
) )
from .constants import PRECISION, MAX_DECIMAL from .constants import PRECISION, MAX_DECIMAL
@ -45,15 +40,13 @@ from .staking_structures import (
EditValidator, EditValidator,
DelegateOrUndelegate, DelegateOrUndelegate,
CollectRewards, CollectRewards,
) )
from .util import convert_one_to_hex from .util import convert_one_to_hex
# https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L335 # https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L335
def _convert_staking_percentage_to_number( def _convert_staking_percentage_to_number( value, ):
value,
):
"""Convert from staking percentage to integer For example, 0.1 becomes """Convert from staking percentage to integer For example, 0.1 becomes
1000000000000000000. Since Python floats are problematic with precision, 1000000000000000000. Since Python floats are problematic with precision,
this function is used as a workaround. this function is used as a workaround.
@ -72,47 +65,45 @@ def _convert_staking_percentage_to_number(
AssertionError, if data types are not as expected AssertionError, if data types are not as expected
ValueError, if the input type is not supported ValueError, if the input type is not supported
""" """
assert isinstance(value, (str, Decimal)), "Only strings or decimals are supported" assert isinstance( value, ( str, Decimal ) ), "Only strings or decimals are supported"
if isinstance(value, Decimal): if isinstance( value, Decimal ):
value = str(value) value = str( value )
value1 = value value1 = value
if value[0] == "-": if value[ 0 ] == "-":
raise ValueError("Negative numbers are not accepted") raise ValueError( "Negative numbers are not accepted" )
if value[0] == "+": if value[ 0 ] == "+":
value1 = value[1:] value1 = value[ 1 : ]
if len(value1) == 0: if len( value1 ) == 0:
raise ValueError("StakingDecimal string is empty") raise ValueError( "StakingDecimal string is empty" )
spaced = value1.split(" ") spaced = value1.split( " " )
if len(spaced) > 1: if len( spaced ) > 1:
raise ValueError("Bad decimal string") raise ValueError( "Bad decimal string" )
splitted = value1.split(".") splitted = value1.split( "." )
combined_str = splitted[0] combined_str = splitted[ 0 ]
if len(splitted) == 2: if len( splitted ) == 2:
length = len(splitted[1]) length = len( splitted[ 1 ] )
if length == 0 or len(combined_str) == 0: if length == 0 or len( combined_str ) == 0:
raise ValueError("Bad StakingDecimal length") raise ValueError( "Bad StakingDecimal length" )
if splitted[1][0] == "-": if splitted[ 1 ][ 0 ] == "-":
raise ValueError("Bad StakingDecimal string") raise ValueError( "Bad StakingDecimal string" )
combined_str += splitted[1] combined_str += splitted[ 1 ]
elif len(splitted) > 2: elif len( splitted ) > 2:
raise ValueError("Too many periods to be a StakingDecimal string") raise ValueError( "Too many periods to be a StakingDecimal string" )
if length > PRECISION: if length > PRECISION:
raise ValueError( raise ValueError( "Too much precision, must be less than {PRECISION}" )
"Too much precision, must be less than {PRECISION}"
)
zeroes_to_add = PRECISION - length zeroes_to_add = PRECISION - length
combined_str += ( combined_str += (
"0" * zeroes_to_add "0" * zeroes_to_add
) # This will not have any periods, so it is effectively a large integer ) # This will not have any periods, so it is effectively a large integer
val = int(combined_str) val = int( combined_str )
assert val <= MAX_DECIMAL, "Staking percentage is too large" assert val <= MAX_DECIMAL, "Staking percentage is too large"
return val return val
def _get_account_and_transaction(transaction_dict, private_key): def _get_account_and_transaction( transaction_dict, private_key ):
"""Create account from private key and sanitize the transaction """Create account from private key and sanitize the transaction
Sanitization involves removal of 'from' key And conversion of chainId key Sanitization involves removal of 'from' key And conversion of chainId key
from str to int (if present) from str to int ( if present )
Parameters Parameters
---------- ----------
@ -133,21 +124,25 @@ def _get_account_and_transaction(transaction_dict, private_key):
""" """
account, sanitized_transaction = sanitize_transaction( account, sanitized_transaction = sanitize_transaction(
transaction_dict, private_key transaction_dict, private_key
) # remove from, convert chain id (if present) to integer ) # remove from, convert chain id ( if present ) to integer
sanitized_transaction["directive"] = sanitized_transaction[ sanitized_transaction[ "directive" ] = sanitized_transaction[
"directive" "directive" ].value # convert to value, like in TypeScript
].value # convert to value, like in TypeScript
return account, sanitized_transaction return account, sanitized_transaction
# pylint: disable=too-many-locals,protected-access,invalid-name # pylint: disable=too-many-locals,protected-access,invalid-name
def _sign_transaction_generic(account, sanitized_transaction, parent_serializer): def _sign_transaction_generic(
account,
sanitized_transaction,
parent_serializer
):
"""Sign a generic staking transaction, given the serializer base class and """Sign a generic staking transaction, given the serializer base class and
account. account.
Paramters Paramters
--------- ---------
account: :obj:`eth_account.Account`, the account to use for signing account: :obj:`eth_account.Account`, the account to use for signing
sanitized_transaction: :obj:`dict`, The sanitized transaction (chainId checks and no from key) sanitized_transaction: :obj:`dict`, The sanitized transaction ( chainId checks and no from key )
parent_serializer: :obj: The serializer class from staking_structures parent_serializer: :obj: The serializer class from staking_structures
Returns Returns
@ -161,40 +156,52 @@ def _sign_transaction_generic(account, sanitized_transaction, parent_serializer)
rlp.exceptions.ObjectSerializationError, if data types are not as expected rlp.exceptions.ObjectSerializationError, if data types are not as expected
""" """
# obtain the serializers # obtain the serializers
if sanitized_transaction.get("chainId", 0) == 0: if sanitized_transaction.get( "chainId", 0 ) == 0:
unsigned_serializer, signed_serializer = ( unsigned_serializer, signed_serializer = (
parent_serializer.Unsigned(), parent_serializer.Unsigned( ),
parent_serializer.Signed(), parent_serializer.Signed( ),
) # unsigned, signed ) # unsigned, signed
else: else:
unsigned_serializer, signed_serializer = ( unsigned_serializer, signed_serializer = (
parent_serializer.SignedChainId(), parent_serializer.SignedChainId( ),
parent_serializer.SignedChainId(), parent_serializer.SignedChainId( ),
) # since chain_id_to_v adds v/r/s, unsigned is not used here ) # since chain_id_to_v adds v/r/s, unsigned is not used here
# fill the transaction # fill the transaction
# 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( filled_transaction = pipe(
sanitized_transaction, sanitized_transaction,
dict, dict,
partial(merge, {"chainId": None}), partial( merge, { "chainId": None } ),
chain_id_to_v, # will move chain id to v and add v/r/s chain_id_to_v, # will move chain id to v and add v/r/s
apply_formatters_to_dict(FORMATTERS), apply_formatters_to_dict( FORMATTERS ),
) )
# get the unsigned transaction # get the unsigned transaction
for field, _ in unsigned_serializer._meta.fields: for field, _ in unsigned_serializer._meta.fields:
assert field in filled_transaction, f"Could not find {field} in transaction" assert field in filled_transaction, f"Could not find {field} in transaction"
unsigned_transaction = unsigned_serializer.from_dict( unsigned_transaction = unsigned_serializer.from_dict(
{f: filled_transaction[f] for f, _ in unsigned_serializer._meta.fields} {
f: filled_transaction[ f ]
for f,
_ in unsigned_serializer._meta.fields
}
) # drop extras silently ) # drop extras silently
# sign the unsigned transaction # sign the unsigned transaction
if "v" in unsigned_transaction.as_dict(): if "v" in unsigned_transaction.as_dict( ):
chain_id = unsigned_transaction.v chain_id = unsigned_transaction.v
else: else:
chain_id = None chain_id = None
transaction_hash = unsigned_transaction.hash() transaction_hash = unsigned_transaction.hash( )
(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 )
chain_naive_transaction = dissoc( chain_naive_transaction = dissoc(
unsigned_transaction.as_dict(), "v", "r", "s" unsigned_transaction.as_dict( ),
"v",
"r",
"s"
) # remove extra v/r/s added by chain_id_to_v ) # remove extra v/r/s added by chain_id_to_v
# serialize it # serialize it
# https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L207 # https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L207
@ -206,30 +213,33 @@ def _sign_transaction_generic(account, sanitized_transaction, parent_serializer)
r=r, r=r,
s=s, # in the below statement, remove everything not expected by signed_serializer s=s, # in the below statement, remove everything not expected by signed_serializer
**{ **{
f: chain_naive_transaction[f] f: chain_naive_transaction[ f ]
for f, _ in signed_serializer._meta.fields for f, _ in signed_serializer._meta.fields
if f not in "vrs" if f not in "vrs"
}, },
) )
# encode it # encode it
encoded_transaction = rlp.encode(signed_transaction) encoded_transaction = rlp.encode( signed_transaction )
# hash it # hash it
signed_transaction_hash = keccak(encoded_transaction) signed_transaction_hash = keccak( encoded_transaction )
# return is # return is
return SignedTransaction( return SignedTransaction(
rawTransaction=HexBytes(encoded_transaction), rawTransaction = HexBytes( encoded_transaction ),
hash=HexBytes(signed_transaction_hash), hash = HexBytes( signed_transaction_hash ),
r=r, r = r,
s=s, s = s,
v=v, v = v,
) )
def _sign_delegate_or_undelegate(transaction_dict, private_key): def _sign_delegate_or_undelegate( transaction_dict, private_key ):
"""Sign a delegate or undelegate transaction See sign_staking_transaction """Sign a delegate or undelegate transaction See sign_staking_transaction
for details.""" for details."""
# preliminary steps # preliminary steps
if transaction_dict["directive"] not in [Directive.Delegate, Directive.Undelegate]: if transaction_dict[ "directive" ] not in [
Directive.Delegate,
Directive.Undelegate
]:
raise TypeError( raise TypeError(
"Only Delegate or Undelegate are supported by _sign_delegate_or_undelegate" "Only Delegate or Undelegate are supported by _sign_delegate_or_undelegate"
) )
@ -238,43 +248,61 @@ def _sign_delegate_or_undelegate(transaction_dict, private_key):
transaction_dict, private_key transaction_dict, private_key
) )
# encode the stakeMsg # encode the stakeMsg
sanitized_transaction["stakeMsg"] = apply_formatters_to_sequence( sanitized_transaction[ "stakeMsg" ] = apply_formatters_to_sequence(
[hexstr_if_str(to_bytes), hexstr_if_str(to_bytes), hexstr_if_str(to_int)], [
hexstr_if_str( to_bytes ),
hexstr_if_str( to_bytes ),
hexstr_if_str( to_int )
],
[ [
convert_one_to_hex(sanitized_transaction.pop("delegatorAddress")), convert_one_to_hex(
convert_one_to_hex(sanitized_transaction.pop("validatorAddress")), sanitized_transaction.pop( "delegatorAddress" )
sanitized_transaction.pop("amount"), ),
convert_one_to_hex(
sanitized_transaction.pop( "validatorAddress" )
),
sanitized_transaction.pop( "amount" ),
], ],
) )
return _sign_transaction_generic( return _sign_transaction_generic(
account, sanitized_transaction, DelegateOrUndelegate account,
sanitized_transaction,
DelegateOrUndelegate
) )
def _sign_collect_rewards(transaction_dict, private_key): def _sign_collect_rewards( transaction_dict, private_key ):
"""Sign a collect rewards transaction See sign_staking_transaction for """Sign a collect rewards transaction See sign_staking_transaction for
details.""" details."""
# preliminary steps # preliminary steps
if transaction_dict["directive"] != Directive.CollectRewards: if transaction_dict[ "directive" ] != Directive.CollectRewards:
raise TypeError("Only CollectRewards is supported by _sign_collect_rewards") raise TypeError(
"Only CollectRewards is supported by _sign_collect_rewards"
)
# first common step # first common step
account, sanitized_transaction = _get_account_and_transaction( account, sanitized_transaction = _get_account_and_transaction(
transaction_dict, private_key transaction_dict, private_key
) )
# encode the stakeMsg # encode the stakeMsg
sanitized_transaction["stakeMsg"] = [ sanitized_transaction[ "stakeMsg" ] = [
hexstr_if_str(to_bytes)( hexstr_if_str( to_bytes )(
convert_one_to_hex(sanitized_transaction.pop("delegatorAddress")) convert_one_to_hex(
sanitized_transaction.pop( "delegatorAddress" )
)
) )
] ]
return _sign_transaction_generic(account, sanitized_transaction, CollectRewards) return _sign_transaction_generic(
account,
sanitized_transaction,
CollectRewards
)
def _sign_create_validator(transaction_dict, private_key): def _sign_create_validator( transaction_dict, private_key ):
"""Sign a create validator transaction See sign_staking_transaction for """Sign a create validator transaction See sign_staking_transaction for
details.""" details."""
# preliminary steps # preliminary steps
if transaction_dict["directive"] != Directive.CreateValidator: if transaction_dict[ "directive" ] != Directive.CreateValidator:
raise TypeError( raise TypeError(
"Only CreateValidator is supported by _sign_create_or_edit_validator" "Only CreateValidator is supported by _sign_create_or_edit_validator"
) )
@ -284,70 +312,75 @@ def _sign_create_validator(transaction_dict, private_key):
) )
# encode the stakeMsg # encode the stakeMsg
description = [ description = [
sanitized_transaction.pop("name"), sanitized_transaction.pop( "name" ),
sanitized_transaction.pop("identity"), sanitized_transaction.pop( "identity" ),
sanitized_transaction.pop("website"), sanitized_transaction.pop( "website" ),
sanitized_transaction.pop("security-contact"), sanitized_transaction.pop( "security-contact" ),
sanitized_transaction.pop("details"), sanitized_transaction.pop( "details" ),
] ]
commission = apply_formatter_to_array( commission = apply_formatter_to_array(
hexstr_if_str(to_int), # formatter hexstr_if_str( to_int ), # formatter
[ [
_convert_staking_percentage_to_number(sanitized_transaction.pop("rate")), _convert_staking_percentage_to_number( sanitized_transaction.pop( "rate" ) ),
_convert_staking_percentage_to_number( _convert_staking_percentage_to_number(
sanitized_transaction.pop("max-rate") sanitized_transaction.pop( "max-rate" )
), ),
_convert_staking_percentage_to_number( _convert_staking_percentage_to_number(
sanitized_transaction.pop("max-change-rate") sanitized_transaction.pop( "max-change-rate" )
), ),
], ],
) )
commission = [[element] for element in commission] commission = [ [ element ] for element in commission ]
bls_keys = apply_formatter_to_array( bls_keys = apply_formatter_to_array(
hexstr_if_str(to_bytes), # formatter hexstr_if_str( to_bytes ), # formatter
sanitized_transaction.pop("bls-public-keys"), sanitized_transaction.pop( "bls-public-keys" ),
) )
bls_key_sigs = apply_formatter_to_array( bls_key_sigs = apply_formatter_to_array(
hexstr_if_str(to_bytes), sanitized_transaction.pop("bls-key-sigs") # formatter hexstr_if_str( to_bytes ),
sanitized_transaction.pop( "bls-key-sigs" ) # formatter
) )
sanitized_transaction["stakeMsg"] = apply_formatters_to_sequence( sanitized_transaction[ "stakeMsg" ] = apply_formatters_to_sequence(
[ [
hexstr_if_str(to_bytes), # address hexstr_if_str( to_bytes ), # address
identity, # description identity, # description
identity, # commission rates identity, # commission rates
hexstr_if_str( hexstr_if_str(
to_int to_int
), # min self delegation (in ONE), decimals are silently dropped ), # min self delegation ( in ONE ), decimals are silently dropped
hexstr_if_str( hexstr_if_str(
to_int to_int
), # max total delegation (in ONE), decimals are silently dropped ), # max total delegation ( in ONE ), decimals are silently dropped
identity, # bls public keys identity, # bls public keys
identity, # bls key sigs identity, # bls key sigs
hexstr_if_str( hexstr_if_str(
to_int to_int
), # amount (the Hexlify in the SDK drops the decimals, which is what we will do too) ), # amount ( the Hexlify in the SDK drops the decimals, which is what we will do too )
], ],
[ [
convert_one_to_hex(sanitized_transaction.pop("validatorAddress")), convert_one_to_hex( sanitized_transaction.pop( "validatorAddress" ) ),
description, description,
commission, commission,
math.floor( math.floor(
sanitized_transaction.pop("min-self-delegation") sanitized_transaction.pop( "min-self-delegation" )
), # Decimal floors it correctly ), # Decimal floors it correctly
math.floor(sanitized_transaction.pop("max-total-delegation")), math.floor( sanitized_transaction.pop( "max-total-delegation" ) ),
bls_keys, bls_keys,
bls_key_sigs, bls_key_sigs,
math.floor(sanitized_transaction.pop("amount")), math.floor( sanitized_transaction.pop( "amount" ) ),
], ],
) )
return _sign_transaction_generic(account, sanitized_transaction, CreateValidator) return _sign_transaction_generic(
account,
sanitized_transaction,
CreateValidator
)
def _sign_edit_validator(transaction_dict, private_key): def _sign_edit_validator( transaction_dict, private_key ):
"""Sign an edit validator transaction See sign_staking_transaction for """Sign an edit validator transaction See sign_staking_transaction for
details.""" details."""
# preliminary steps # preliminary steps
if transaction_dict["directive"] != Directive.EditValidator: if transaction_dict[ "directive" ] != Directive.EditValidator:
raise TypeError( raise TypeError(
"Only EditValidator is supported by _sign_create_or_edit_validator" "Only EditValidator is supported by _sign_create_or_edit_validator"
) )
@ -357,44 +390,48 @@ def _sign_edit_validator(transaction_dict, private_key):
) )
# encode the stakeMsg # encode the stakeMsg
description = [ description = [
sanitized_transaction.pop("name"), sanitized_transaction.pop( "name" ),
sanitized_transaction.pop("identity"), sanitized_transaction.pop( "identity" ),
sanitized_transaction.pop("website"), sanitized_transaction.pop( "website" ),
sanitized_transaction.pop("security-contact"), sanitized_transaction.pop( "security-contact" ),
sanitized_transaction.pop("details"), sanitized_transaction.pop( "details" ),
] ]
sanitized_transaction["stakeMsg"] = apply_formatters_to_sequence( sanitized_transaction[ "stakeMsg" ] = apply_formatters_to_sequence(
[ [
hexstr_if_str(to_bytes), # address hexstr_if_str( to_bytes ), # address
identity, # description identity, # description
identity, # new rate (it's in a list so can't do hexstr_if_str) identity, # new rate ( it's in a list so can't do hexstr_if_str )
hexstr_if_str( hexstr_if_str(
to_int to_int
), # min self delegation (in ONE), decimals are silently dropped ), # min self delegation ( in ONE ), decimals are silently dropped
hexstr_if_str( hexstr_if_str(
to_int to_int
), # max total delegation (in ONE), decimals are silently dropped ), # max total delegation ( in ONE ), decimals are silently dropped
hexstr_if_str(to_bytes), # key to remove hexstr_if_str( to_bytes ), # key to remove
hexstr_if_str(to_bytes), # key to add hexstr_if_str( to_bytes ), # key to add
hexstr_if_str(to_bytes), # key to add sig hexstr_if_str( to_bytes ), # key to add sig
], ],
[ [
convert_one_to_hex(sanitized_transaction.pop("validatorAddress")), convert_one_to_hex( sanitized_transaction.pop( "validatorAddress" ) ),
description, description,
[_convert_staking_percentage_to_number(sanitized_transaction.pop("rate"))], [ _convert_staking_percentage_to_number( sanitized_transaction.pop( "rate" ) ) ],
math.floor( math.floor(
sanitized_transaction.pop("min-self-delegation") sanitized_transaction.pop( "min-self-delegation" )
), # Decimal floors it correctly ), # Decimal floors it correctly
math.floor(sanitized_transaction.pop("max-total-delegation")), math.floor( sanitized_transaction.pop( "max-total-delegation" ) ),
sanitized_transaction.pop("bls-key-to-remove"), sanitized_transaction.pop( "bls-key-to-remove" ),
sanitized_transaction.pop("bls-key-to-add"), sanitized_transaction.pop( "bls-key-to-add" ),
sanitized_transaction.pop("bls-key-to-add-sig"), sanitized_transaction.pop( "bls-key-to-add-sig" ),
], ],
) )
return _sign_transaction_generic(account, sanitized_transaction, EditValidator) return _sign_transaction_generic(
account,
sanitized_transaction,
EditValidator
)
def sign_staking_transaction(transaction_dict, private_key): def sign_staking_transaction( transaction_dict, private_key ):
"""Sign a supplied transaction_dict with the private_key. """Sign a supplied transaction_dict with the private_key.
Parameters Parameters
@ -412,7 +449,7 @@ def sign_staking_transaction(transaction_dict, private_key):
Delegate/Undelegate: Delegate/Undelegate:
delegatorAddress: :obj:`str`, Address of the delegator delegatorAddress: :obj:`str`, Address of the delegator
validatorAddress: :obj:`str`, Address of the validator validatorAddress: :obj:`str`, Address of the validator
amount: :obj:`int`, Amount to (un)delegate in ATTO amount: :obj:`int`, Amount to ( un )delegate in ATTO
CreateValidator: CreateValidator:
validatorAddress: :obj:`str`, Address of the validator validatorAddress: :obj:`str`, Address of the validator
name: ;obj:`str`, Name of the validator name: ;obj:`str`, Name of the validator
@ -468,16 +505,16 @@ def sign_staking_transaction(transaction_dict, private_key):
assert "chainId" in transaction_dict, "chainId missing" assert "chainId" in transaction_dict, "chainId missing"
assert "directive" in transaction_dict, "Staking transaction type not specified" assert "directive" in transaction_dict, "Staking transaction type not specified"
assert isinstance( assert isinstance(
transaction_dict["directive"], Directive transaction_dict[ "directive" ], Directive
), "Unknown staking transaction type" ), "Unknown staking transaction type"
if transaction_dict["directive"] == Directive.CollectRewards: if transaction_dict[ "directive" ] == Directive.CollectRewards:
return _sign_collect_rewards(transaction_dict, private_key) return _sign_collect_rewards( transaction_dict, private_key )
if transaction_dict["directive"] == Directive.Delegate: if transaction_dict[ "directive" ] == Directive.Delegate:
return _sign_delegate_or_undelegate(transaction_dict, private_key) return _sign_delegate_or_undelegate( transaction_dict, private_key )
if transaction_dict["directive"] == Directive.Undelegate: if transaction_dict[ "directive" ] == Directive.Undelegate:
return _sign_delegate_or_undelegate(transaction_dict, private_key) return _sign_delegate_or_undelegate( transaction_dict, private_key )
if transaction_dict["directive"] == Directive.CreateValidator: if transaction_dict[ "directive" ] == Directive.CreateValidator:
return _sign_create_validator(transaction_dict, private_key) return _sign_create_validator( transaction_dict, private_key )
if transaction_dict["directive"] == Directive.EditValidator: if transaction_dict[ "directive" ] == Directive.EditValidator:
return _sign_edit_validator(transaction_dict, private_key) return _sign_edit_validator( transaction_dict, private_key )
raise ValueError('Unknown staking transaction type') raise ValueError( 'Unknown staking transaction type' )

@ -10,16 +10,12 @@ from rlp.sedes import big_endian_int, Binary, CountableList, List, Text
from eth_rlp import HashableRLP from eth_rlp import HashableRLP
from eth_utils.curried import ( from eth_utils.curried import ( to_int, hexstr_if_str, )
to_int,
hexstr_if_str,
)
# https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L120 # https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L120
class Directive( class Directive( Enum ):
Enum def _generate_next_value_( name, start, count, last_values ): # pylint: disable=no-self-argument
):
def _generate_next_value_(name, start, count, last_values): # pylint: disable=no-self-argument
return count return count
CreateValidator = auto() CreateValidator = auto()
@ -39,24 +35,36 @@ FORMATTERS = {
"chainId": hexstr_if_str(to_int), "chainId": hexstr_if_str(to_int),
} }
class CollectRewards: class CollectRewards:
@staticmethod @staticmethod
def UnsignedChainId(): def UnsignedChainId():
class UnsignedChainId(HashableRLP): class UnsignedChainId( HashableRLP ):
fields = ( fields = (
("directive", big_endian_int), ( "directive",
("stakeMsg", CountableList(Binary.fixed_length(20, allow_empty=True))), big_endian_int ),
("nonce", big_endian_int), (
("gasPrice", big_endian_int), "stakeMsg",
("gasLimit", big_endian_int), CountableList(
("chainId", big_endian_int), Binary.fixed_length( 20,
allow_empty = True )
)
),
( "nonce",
big_endian_int ),
( "gasPrice",
big_endian_int ),
( "gasLimit",
big_endian_int ),
( "chainId",
big_endian_int ),
) )
return UnsignedChainId return UnsignedChainId
@staticmethod @staticmethod
def SignedChainId(): def SignedChainId():
class SignedChainId(HashableRLP): class SignedChainId( HashableRLP ):
fields = CollectRewards.UnsignedChainId()._meta.fields[ fields = CollectRewards.UnsignedChainId()._meta.fields[
:-1 :-1
] + ( # drop chainId ] + ( # drop chainId
@ -69,14 +77,15 @@ class CollectRewards:
@staticmethod @staticmethod
def Unsigned(): def Unsigned():
class Unsigned(HashableRLP): class Unsigned( HashableRLP ):
fields = CollectRewards.UnsignedChainId()._meta.fields[:-1] # drop chainId fields = CollectRewards.UnsignedChainId(
)._meta.fields[ :-1 ] # drop chainId
return Unsigned return Unsigned
@staticmethod @staticmethod
def Signed(): def Signed():
class Signed(HashableRLP): class Signed( HashableRLP ):
fields = CollectRewards.Unsigned()._meta.fields[ fields = CollectRewards.Unsigned()._meta.fields[
:-3 :-3
] + ( # drop last 3 for raw.pop() ] + ( # drop last 3 for raw.pop()
@ -91,31 +100,38 @@ class CollectRewards:
class DelegateOrUndelegate: class DelegateOrUndelegate:
@staticmethod @staticmethod
def UnsignedChainId(): def UnsignedChainId():
class UnsignedChainId(HashableRLP): class UnsignedChainId( HashableRLP ):
fields = ( fields = (
("directive", big_endian_int), ( "directive",
big_endian_int ),
( (
"stakeMsg", "stakeMsg",
List( List(
[ [
Binary.fixed_length(20, allow_empty=True), Binary.fixed_length( 20,
Binary.fixed_length(20, allow_empty=True), allow_empty = True ),
Binary.fixed_length( 20,
allow_empty = True ),
big_endian_int, big_endian_int,
], ],
True, True,
), ),
), ),
("nonce", big_endian_int), ( "nonce",
("gasPrice", big_endian_int), big_endian_int ),
("gasLimit", big_endian_int), ( "gasPrice",
("chainId", big_endian_int), big_endian_int ),
( "gasLimit",
big_endian_int ),
( "chainId",
big_endian_int ),
) )
return UnsignedChainId return UnsignedChainId
@staticmethod @staticmethod
def SignedChainId(): def SignedChainId():
class SignedChainId(HashableRLP): class SignedChainId( HashableRLP ):
fields = DelegateOrUndelegate.UnsignedChainId()._meta.fields[ fields = DelegateOrUndelegate.UnsignedChainId()._meta.fields[
:-1 :-1
] + ( # drop chainId ] + ( # drop chainId
@ -128,16 +144,15 @@ class DelegateOrUndelegate:
@staticmethod @staticmethod
def Unsigned(): def Unsigned():
class Unsigned(HashableRLP): class Unsigned( HashableRLP ):
fields = DelegateOrUndelegate.UnsignedChainId()._meta.fields[ fields = DelegateOrUndelegate.UnsignedChainId(
:-1 )._meta.fields[ :-1 ] # drop chainId
] # drop chainId
return Unsigned return Unsigned
@staticmethod @staticmethod
def Signed(): def Signed():
class Signed(HashableRLP): class Signed( HashableRLP ):
fields = DelegateOrUndelegate.Unsigned()._meta.fields[ fields = DelegateOrUndelegate.Unsigned()._meta.fields[
:-3 :-3
] + ( # drop last 3 for raw.pop() ] + ( # drop last 3 for raw.pop()
@ -152,7 +167,7 @@ class DelegateOrUndelegate:
class CreateValidator: class CreateValidator:
@staticmethod @staticmethod
def UnsignedChainId(): def UnsignedChainId():
class UnsignedChainId(HashableRLP): class UnsignedChainId( HashableRLP ):
fields = ( fields = (
("directive", big_endian_int), ("directive", big_endian_int),
( (
@ -196,7 +211,7 @@ class CreateValidator:
@staticmethod @staticmethod
def SignedChainId(): def SignedChainId():
class SignedChainId(HashableRLP): class SignedChainId( HashableRLP ):
fields = CreateValidator.UnsignedChainId()._meta.fields[ fields = CreateValidator.UnsignedChainId()._meta.fields[
:-1 :-1
] + ( # drop chainId ] + ( # drop chainId
@ -209,14 +224,15 @@ class CreateValidator:
@staticmethod @staticmethod
def Unsigned(): def Unsigned():
class Unsigned(HashableRLP): class Unsigned( HashableRLP ):
fields = CreateValidator.UnsignedChainId()._meta.fields[:-1] # drop chainId fields = CreateValidator.UnsignedChainId(
)._meta.fields[ :-1 ] # drop chainId
return Unsigned return Unsigned
@staticmethod @staticmethod
def Signed(): def Signed():
class Signed(HashableRLP): class Signed( HashableRLP ):
fields = CreateValidator.Unsigned()._meta.fields[ fields = CreateValidator.Unsigned()._meta.fields[
:-3 :-3
] + ( # drop last 3 for raw.pop() ] + ( # drop last 3 for raw.pop()
@ -231,7 +247,7 @@ class CreateValidator:
class EditValidator: class EditValidator:
@staticmethod @staticmethod
def UnsignedChainId(): def UnsignedChainId():
class UnsignedChainId(HashableRLP): class UnsignedChainId( HashableRLP ):
fields = ( fields = (
("directive", big_endian_int), ("directive", big_endian_int),
( (
@ -276,7 +292,7 @@ class EditValidator:
@staticmethod @staticmethod
def SignedChainId(): def SignedChainId():
class SignedChainId(HashableRLP): class SignedChainId( HashableRLP ):
fields = EditValidator.UnsignedChainId()._meta.fields[ fields = EditValidator.UnsignedChainId()._meta.fields[
:-1 :-1
] + ( # drop chainId ] + ( # drop chainId
@ -289,14 +305,15 @@ class EditValidator:
@staticmethod @staticmethod
def Unsigned(): def Unsigned():
class Unsigned(HashableRLP): class Unsigned( HashableRLP ):
fields = EditValidator.UnsignedChainId()._meta.fields[:-1] # drop chainId fields = EditValidator.UnsignedChainId(
)._meta.fields[ :-1 ] # drop chainId
return Unsigned return Unsigned
@staticmethod @staticmethod
def Signed(): def Signed():
class Signed(HashableRLP): class Signed( HashableRLP ):
fields = EditValidator.Unsigned()._meta.fields[ fields = EditValidator.Unsigned()._meta.fields[
:-3 :-3
] + ( # drop last 3 for raw.pop() ] + ( # drop last 3 for raw.pop()

@ -13,7 +13,8 @@ from .exceptions import TxConfirmationTimedoutError, InvalidRPCReplyError
# Transaction Pool RPCs # # Transaction Pool RPCs #
######################### #########################
def get_pending_transactions( def get_pending_transactions(
endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list: ) -> list:
"""Get list of pending transactions. """Get list of pending transactions.
@ -39,13 +40,16 @@ def get_pending_transactions(
""" """
method = "hmyv2_pendingTransactions" method = "hmyv2_pendingTransactions"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_transaction_error_sink( def get_transaction_error_sink(
endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list: ) -> list:
"""Get current transactions error sink. """Get current transactions error sink.
@ -74,13 +78,16 @@ def get_transaction_error_sink(
""" """
method = "hmyv2_getCurrentTransactionErrorSink" method = "hmyv2_getCurrentTransactionErrorSink"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_pending_staking_transactions( def get_pending_staking_transactions(
endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list: ) -> list:
"""Get list of pending staking transactions. """Get list of pending staking transactions.
@ -106,13 +113,16 @@ def get_pending_staking_transactions(
""" """
method = "hmyv2_pendingStakingTransactions" method = "hmyv2_pendingStakingTransactions"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_staking_transaction_error_sink( def get_staking_transaction_error_sink(
endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list: ) -> list:
"""Get current staking transactions error sink. """Get current staking transactions error sink.
@ -142,12 +152,17 @@ def get_staking_transaction_error_sink(
""" """
method = "hmyv2_getCurrentStakingErrorSink" method = "hmyv2_getCurrentStakingErrorSink"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_pool_stats(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict: def get_pool_stats(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict:
"""Get stats of the pool, that is, number of pending and queued (non- """Get stats of the pool, that is, number of pending and queued (non-
executable) transactions. executable) transactions.
@ -175,16 +190,20 @@ def get_pool_stats(endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT) -> dict:
""" """
method = "hmyv2_getPoolStats" method = "hmyv2_getPoolStats"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
#################### ####################
# Transaction RPCs # # Transaction RPCs #
#################### ####################
def get_transaction_by_hash( def get_transaction_by_hash(
tx_hash, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT tx_hash,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict: ) -> dict:
"""Get transaction by hash. """Get transaction by hash.
@ -231,17 +250,23 @@ def get_transaction_by_hash(
https://api.hmny.io/#117e84f6-a0ec-444e-abe0-455701310389 https://api.hmny.io/#117e84f6-a0ec-444e-abe0-455701310389
""" """
method = "hmyv2_getTransactionByHash" method = "hmyv2_getTransactionByHash"
params = [tx_hash] params = [ tx_hash ]
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_transaction_by_block_hash_and_index( def get_transaction_by_block_hash_and_index(
block_hash, tx_index, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT block_hash,
tx_index,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict: ) -> dict:
"""Get transaction based on index in list of transactions in a block by """Get transaction based on index in list of transactions in a block by
block hash. block hash.
@ -271,17 +296,23 @@ def get_transaction_by_block_hash_and_index(
https://api.hmny.io/#7c7e8d90-4984-4ebe-bb7e-d7adec167503 https://api.hmny.io/#7c7e8d90-4984-4ebe-bb7e-d7adec167503
""" """
method = "hmyv2_getTransactionByBlockHashAndIndex" method = "hmyv2_getTransactionByBlockHashAndIndex"
params = [block_hash, tx_index] params = [ block_hash, tx_index ]
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_transaction_by_block_number_and_index( def get_transaction_by_block_number_and_index(
block_num, tx_index, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT block_num,
tx_index,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict: ) -> dict:
"""Get transaction based on index in list of transactions in a block by """Get transaction based on index in list of transactions in a block by
block number. block number.
@ -311,17 +342,22 @@ def get_transaction_by_block_number_and_index(
https://api.hmny.io/#bcde8b1c-6ab9-4950-9835-3c7564e49c3e https://api.hmny.io/#bcde8b1c-6ab9-4950-9835-3c7564e49c3e
""" """
method = "hmyv2_getTransactionByBlockNumberAndIndex" method = "hmyv2_getTransactionByBlockNumberAndIndex"
params = [block_num, tx_index] params = [ block_num, tx_index ]
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_transaction_receipt( def get_transaction_receipt(
tx_hash, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT tx_hash,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict: ) -> dict:
"""Get transaction receipt corresponding to tx_hash. """Get transaction receipt corresponding to tx_hash.
@ -364,17 +400,22 @@ def get_transaction_receipt(
https://api.hmny.io/#0c2799f8-bcdc-41a4-b362-c3a6a763bb5e https://api.hmny.io/#0c2799f8-bcdc-41a4-b362-c3a6a763bb5e
""" """
method = "hmyv2_getTransactionReceipt" method = "hmyv2_getTransactionReceipt"
params = [tx_hash] params = [ tx_hash ]
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def send_raw_transaction( def send_raw_transaction(
signed_tx, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT signed_tx,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> str: ) -> str:
"""Send signed transaction. """Send signed transaction.
@ -403,18 +444,23 @@ def send_raw_transaction(
------------- -------------
https://api.hmny.io/#f40d124a-b897-4b7c-baf3-e0dedf8f40a0 https://api.hmny.io/#f40d124a-b897-4b7c-baf3-e0dedf8f40a0
""" """
params = [signed_tx] params = [ signed_tx ]
method = "hmyv2_sendRawTransaction" method = "hmyv2_sendRawTransaction"
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def send_and_confirm_raw_transaction( def send_and_confirm_raw_transaction(
signed_tx, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT signed_tx,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list: ) -> list:
"""Send signed transaction and wait for it to be confirmed. """Send signed transaction and wait for it to be confirmed.
@ -445,24 +491,27 @@ def send_and_confirm_raw_transaction(
------------- -------------
https://api.hmny.io/#f40d124a-b897-4b7c-baf3-e0dedf8f40a0 https://api.hmny.io/#f40d124a-b897-4b7c-baf3-e0dedf8f40a0
""" """
tx_hash = send_raw_transaction(signed_tx, endpoint=endpoint) tx_hash = send_raw_transaction( signed_tx, endpoint = endpoint )
start_time = time.time() start_time = time.time()
while (time.time() - start_time) <= timeout: while ( time.time() - start_time ) <= timeout:
tx_response = get_transaction_by_hash(tx_hash, endpoint=endpoint) tx_response = get_transaction_by_hash( tx_hash, endpoint = endpoint )
if tx_response is not None: if tx_response is not None:
block_hash = tx_response.get("blockHash", "0x00") block_hash = tx_response.get( "blockHash", "0x00" )
unique_chars = "".join(set(list(block_hash[2:]))) unique_chars = "".join( set( list( block_hash[ 2 : ] ) ) )
if unique_chars != "0": if unique_chars != "0":
return tx_response return tx_response
time.sleep(random.uniform(0.2, 0.5)) time.sleep( random.uniform( 0.2, 0.5 ) )
raise TxConfirmationTimedoutError("Could not confirm transaction on-chain.") raise TxConfirmationTimedoutError(
"Could not confirm transaction on-chain."
)
############################### ###############################
# CrossShard Transaction RPCs # # CrossShard Transaction RPCs #
############################### ###############################
def get_pending_cx_receipts( def get_pending_cx_receipts(
endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list: ) -> list:
"""Get list of pending cross shard transactions. """Get list of pending cross shard transactions.
@ -511,13 +560,17 @@ def get_pending_cx_receipts(
""" """
method = "hmyv2_getPendingCXReceipts" method = "hmyv2_getPendingCXReceipts"
try: try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)["result"] return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_cx_receipt_by_hash( def get_cx_receipt_by_hash(
cx_hash, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT cx_hash,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict: ) -> dict:
"""Get cross shard receipt by hash on the receiving shard end point. """Get cross shard receipt by hash on the receiving shard end point.
@ -552,18 +605,23 @@ def get_cx_receipt_by_hash(
------------- -------------
https://api.hmny.io/#3d6ad045-800d-4021-aeb5-30a0fbf724fe https://api.hmny.io/#3d6ad045-800d-4021-aeb5-30a0fbf724fe
""" """
params = [cx_hash] params = [ cx_hash ]
method = "hmyv2_getCXReceiptByHash" method = "hmyv2_getCXReceiptByHash"
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def resend_cx_receipt( def resend_cx_receipt(
cx_hash, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT cx_hash,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> bool: ) -> bool:
"""Resend the cross shard receipt to the receiving shard to re-process if """Resend the cross shard receipt to the receiving shard to re-process if
the transaction did not pay out. the transaction did not pay out.
@ -592,20 +650,25 @@ def resend_cx_receipt(
https://api.hmny.io/#c658b56b-d20b-480d-b71a-b0bc505d2164 https://api.hmny.io/#c658b56b-d20b-480d-b71a-b0bc505d2164
""" """
method = "hmyv2_resendCx" method = "hmyv2_resendCx"
params = [cx_hash] params = [ cx_hash ]
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
############################ ############################
# Staking Transaction RPCs # # Staking Transaction RPCs #
############################ ############################
def get_staking_transaction_by_hash( def get_staking_transaction_by_hash(
tx_hash, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT tx_hash,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict: ) -> dict:
"""Get staking transaction by hash. """Get staking transaction by hash.
@ -646,17 +709,23 @@ def get_staking_transaction_by_hash(
https://api.hmny.io/#296cb4d0-bce2-48e3-bab9-64c3734edd27 https://api.hmny.io/#296cb4d0-bce2-48e3-bab9-64c3734edd27
""" """
method = "hmyv2_getStakingTransactionByHash" method = "hmyv2_getStakingTransactionByHash"
params = [tx_hash] params = [ tx_hash ]
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_staking_transaction_by_block_hash_and_index( def get_staking_transaction_by_block_hash_and_index(
block_hash, tx_index, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT block_hash,
tx_index,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict: ) -> dict:
"""Get staking transaction by block hash and transaction index. """Get staking transaction by block hash and transaction index.
@ -685,17 +754,23 @@ def get_staking_transaction_by_block_hash_and_index(
https://api.hmny.io/#ba96cf61-61fe-464a-aa06-2803bb4b358f https://api.hmny.io/#ba96cf61-61fe-464a-aa06-2803bb4b358f
""" """
method = "hmyv2_getStakingTransactionByBlockHashAndIndex" method = "hmyv2_getStakingTransactionByBlockHashAndIndex"
params = [block_hash, tx_index] params = [ block_hash, tx_index ]
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def get_staking_transaction_by_block_number_and_index( def get_staking_transaction_by_block_number_and_index(
block_num, tx_index, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT block_num,
tx_index,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict: ) -> dict:
"""Get staking transaction by block number and transaction index. """Get staking transaction by block number and transaction index.
@ -724,17 +799,22 @@ def get_staking_transaction_by_block_number_and_index(
https://api.hmny.io/#fb41d717-1645-4d3e-8071-6ce8e1b65dd3 https://api.hmny.io/#fb41d717-1645-4d3e-8071-6ce8e1b65dd3
""" """
method = "hmyv2_getStakingTransactionByBlockNumberAndIndex" method = "hmyv2_getStakingTransactionByBlockNumberAndIndex"
params = [block_num, tx_index] params = [ block_num, tx_index ]
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def send_raw_staking_transaction( def send_raw_staking_transaction(
raw_tx, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT raw_tx,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> str: ) -> str:
"""Send signed staking transaction. """Send signed staking transaction.
@ -764,17 +844,22 @@ def send_raw_staking_transaction(
https://api.hmny.io/#e8c17fe9-e730-4c38-95b3-6f1a5b1b9401 https://api.hmny.io/#e8c17fe9-e730-4c38-95b3-6f1a5b1b9401
""" """
method = "hmyv2_sendRawStakingTransaction" method = "hmyv2_sendRawStakingTransaction"
params = [raw_tx] params = [ raw_tx ]
try: try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)[ return rpc_request(
"result" method,
] params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception: except KeyError as exception:
raise InvalidRPCReplyError(method, endpoint) from exception raise InvalidRPCReplyError( method, endpoint ) from exception
def send_and_confirm_raw_staking_transaction( def send_and_confirm_raw_staking_transaction(
signed_tx, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT signed_tx,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list: ) -> list:
"""Send signed staking transaction and wait for it to be confirmed. """Send signed staking transaction and wait for it to be confirmed.
@ -805,14 +890,19 @@ def send_and_confirm_raw_staking_transaction(
------------- -------------
https://api.hmny.io/#e8c17fe9-e730-4c38-95b3-6f1a5b1b9401 https://api.hmny.io/#e8c17fe9-e730-4c38-95b3-6f1a5b1b9401
""" """
tx_hash = send_raw_staking_transaction(signed_tx, endpoint=endpoint) tx_hash = send_raw_staking_transaction( signed_tx, endpoint = endpoint )
start_time = time.time() start_time = time.time()
while (time.time() - start_time) <= timeout: while ( time.time() - start_time ) <= timeout:
tx_response = get_staking_transaction_by_hash(tx_hash, endpoint=endpoint) tx_response = get_staking_transaction_by_hash(
tx_hash,
endpoint = endpoint
)
if tx_response is not None: if tx_response is not None:
block_hash = tx_response.get("blockHash", "0x00") block_hash = tx_response.get( "blockHash", "0x00" )
unique_chars = "".join(set(list(block_hash[2:]))) unique_chars = "".join( set( list( block_hash[ 2 : ] ) ) )
if unique_chars != "0": if unique_chars != "0":
return tx_response return tx_response
time.sleep(random.uniform(0.2, 0.5)) time.sleep( random.uniform( 0.2, 0.5 ) )
raise TxConfirmationTimedoutError("Could not confirm transaction on-chain.") raise TxConfirmationTimedoutError(
"Could not confirm transaction on-chain."
)

@ -13,17 +13,14 @@ from eth_utils import to_checksum_address
from .blockchain import get_latest_header from .blockchain import get_latest_header
from .rpc.exceptions import ( from .rpc.exceptions import ( RPCError, RequestsError, RequestsTimeoutError, )
RPCError,
RequestsError,
RequestsTimeoutError,
)
from .account import is_valid_address from .account import is_valid_address
from .bech32.bech32 import bech32_decode, bech32_encode, convertbits from .bech32.bech32 import bech32_decode, bech32_encode, convertbits
class Typgpy(str):
class Typgpy( str ):
"""Typography constants for pretty printing. """Typography constants for pretty printing.
Note that an ENDC is needed to mark the end of a 'highlighted' text Note that an ENDC is needed to mark the end of a 'highlighted' text
@ -40,7 +37,7 @@ class Typgpy(str):
UNDERLINE = "\033[4m" UNDERLINE = "\033[4m"
def chain_id_to_int(chain_id): def chain_id_to_int( chain_id ):
""" """
If chain_id is a string, converts it to int. If chain_id is a string, converts it to int.
If chain_id is an int, returns the int. If chain_id is an int, returns the int.
@ -48,72 +45,73 @@ def chain_id_to_int(chain_id):
Else raises TypeError Else raises TypeError
""" """
chain_ids = dict( chain_ids = dict(
Default=0, Default = 0,
EthMainnet=1, EthMainnet = 1,
Morden=2, Morden = 2,
Ropsten=3, Ropsten = 3,
Rinkeby=4, Rinkeby = 4,
RootstockMainnet=30, RootstockMainnet = 30,
RootstockTestnet=31, RootstockTestnet = 31,
Kovan=42, Kovan = 42,
EtcMainnet=61, EtcMainnet = 61,
EtcTestnet=62, EtcTestnet = 62,
Geth=1337, Geth = 1337,
Ganache=0, Ganache = 0,
HmyMainnet=1, HmyMainnet = 1,
HmyTestnet=2, HmyTestnet = 2,
HmyLocal=2, HmyLocal = 2,
HmyPangaea=3, HmyPangaea = 3,
) )
# do not validate integer chainids, only known strings # do not validate integer chainids, only known strings
if isinstance(chain_id, str): if isinstance( chain_id, str ):
assert ( assert (
chain_id in chain_ids chain_id in chain_ids
), f"Chain {chain_id} unknown, specify an integer chainId" ), f"Chain {chain_id} unknown, specify an integer chainId"
return chain_ids.get(chain_id) return chain_ids.get( chain_id )
if isinstance(chain_id, int): if isinstance( chain_id, int ):
return chain_id return chain_id
raise TypeError("chainId must be str or int") raise TypeError( "chainId must be str or int" )
def get_gopath(): def get_gopath():
""" """
:returns The go-path, assuming that go is installed. :returns The go-path, assuming that go is installed.
""" """
return subprocess.check_output(["go", "env", "GOPATH"]).decode().strip() return subprocess.check_output( [ "go", "env", "GOPATH" ] ).decode().strip()
def get_goversion(): def get_goversion():
""" """
:returns The go-version, assuming that go is installed. :returns The go-version, assuming that go is installed.
""" """
return subprocess.check_output(["go", "version"]).decode().strip() return subprocess.check_output( [ "go", "version" ] ).decode().strip()
def convert_one_to_hex(addr): def convert_one_to_hex( addr ):
"""Given a one address, convert it to hex checksum address.""" """Given a one address, convert it to hex checksum address."""
if not is_valid_address(addr): if not is_valid_address( addr ):
return to_checksum_address(addr) return to_checksum_address( addr )
_, data = bech32_decode(addr) _, data = bech32_decode( addr )
buf = convertbits(data, 5, 8, False) buf = convertbits( data, 5, 8, False )
address = "0x" + "".join(f"{x:02x}" for x in buf) address = "0x" + "".join( f"{x:02x}" for x in buf )
return str(to_checksum_address(address)) return str( to_checksum_address( address ) )
def convert_hex_to_one(addr): def convert_hex_to_one( addr ):
"""Given a hex address, convert it to a one address.""" """Given a hex address, convert it to a one address."""
if is_valid_address(addr): if is_valid_address( addr ):
return addr return addr
checksum_addr = str(to_checksum_address(addr)) checksum_addr = str( to_checksum_address( addr ) )
data = bytearray.fromhex( data = bytearray.fromhex(
checksum_addr[2:] if checksum_addr.startswith("0x") else checksum_addr checksum_addr[ 2 : ] if checksum_addr
.startswith( "0x" ) else checksum_addr
) )
buf = convertbits(data, 8, 5) buf = convertbits( data, 8, 5 )
return str(bech32_encode("one", buf)) return str( bech32_encode( "one", buf ) )
def is_active_shard(endpoint, delay_tolerance=60): def is_active_shard( endpoint, delay_tolerance = 60 ):
""" """
:param endpoint: The endpoint of the SHARD to check :param endpoint: The endpoint of the SHARD to check
:param delay_tolerance: The time (in seconds) that the shard timestamp can be behind :param delay_tolerance: The time (in seconds) that the shard timestamp can be behind
@ -121,14 +119,15 @@ def is_active_shard(endpoint, delay_tolerance=60):
""" """
try: try:
curr_time = datetime.datetime.utcnow() curr_time = datetime.datetime.utcnow()
latest_header = get_latest_header(endpoint=endpoint) latest_header = get_latest_header( endpoint = endpoint )
time_str = latest_header["timestamp"][:19] + ".0" # Fit time format time_str = latest_header[ "timestamp" ][ : 19 ] + ".0" # Fit time format
timestamp = datetime.datetime.strptime( timestamp = datetime.datetime.strptime(
time_str, "%Y-%m-%d %H:%M:%S.%f" time_str,
).replace(tzinfo=None) "%Y-%m-%d %H:%M:%S.%f"
).replace( tzinfo = None )
time_delta = curr_time - timestamp time_delta = curr_time - timestamp
return abs(time_delta.seconds) < delay_tolerance return abs( time_delta.seconds ) < delay_tolerance
except (RPCError, RequestsError, RequestsTimeoutError): except ( RPCError, RequestsError, RequestsTimeoutError ):
return False return False
@ -144,33 +143,36 @@ def get_bls_build_variables():
variables = {} variables = {}
try: try:
openssl_dir = ( openssl_dir = (
subprocess.check_output(["which", "openssl"]) subprocess.check_output(
.decode() [ "which",
.strip() "openssl" ]
.split("\n", maxsplit=1)[0] ).decode().strip().split( "\n",
maxsplit = 1 )[ 0 ]
) )
except (IndexError, subprocess.CalledProcessError) as exception: except ( IndexError, subprocess.CalledProcessError ) as exception:
raise RuntimeError("`openssl` not found") from exception raise RuntimeError( "`openssl` not found" ) from exception
hmy_path = f"{get_gopath()}/src/github.com/harmony-one" hmy_path = f"{get_gopath()}/src/github.com/harmony-one"
bls_dir = f"{hmy_path}/bls" bls_dir = f"{hmy_path}/bls"
mcl_dir = f"{hmy_path}/mcl" mcl_dir = f"{hmy_path}/mcl"
assert os.path.exists(bls_dir), f"Harmony BLS repo not found at {bls_dir}" assert os.path.exists( bls_dir ), f"Harmony BLS repo not found at {bls_dir}"
assert os.path.exists(mcl_dir), f"Harmony MCL repo not found at {mcl_dir}" assert os.path.exists( mcl_dir ), f"Harmony MCL repo not found at {mcl_dir}"
if sys.platform.startswith("darwin"): if sys.platform.startswith( "darwin" ):
variables[ variables[
"CGO_CFLAGS" "CGO_CFLAGS"
] = f"-I{bls_dir}/include -I{mcl_dir}/include -I{openssl_dir}/include" ] = f"-I{bls_dir}/include -I{mcl_dir}/include -I{openssl_dir}/include"
variables["CGO_LDFLAGS"] = f"-L{bls_dir}/lib -L{openssl_dir}/lib" variables[ "CGO_LDFLAGS" ] = f"-L{bls_dir}/lib -L{openssl_dir}/lib"
variables["LD_LIBRARY_PATH"] = f"{bls_dir}/lib:{mcl_dir}/lib:{openssl_dir}/lib" variables[ "LD_LIBRARY_PATH"
variables["DYLD_FALLBACK_LIBRARY_PATH"] = variables["LD_LIBRARY_PATH"] ] = f"{bls_dir}/lib:{mcl_dir}/lib:{openssl_dir}/lib"
variables[ "DYLD_FALLBACK_LIBRARY_PATH" ] = variables[ "LD_LIBRARY_PATH"
]
else: else:
variables["CGO_CFLAGS"] = f"-I{bls_dir}/include -I{mcl_dir}/include" variables[ "CGO_CFLAGS" ] = f"-I{bls_dir}/include -I{mcl_dir}/include"
variables["CGO_LDFLAGS"] = f"-L{bls_dir}/lib" variables[ "CGO_LDFLAGS" ] = f"-L{bls_dir}/lib"
variables["LD_LIBRARY_PATH"] = f"{bls_dir}/lib:{mcl_dir}/lib" variables[ "LD_LIBRARY_PATH" ] = f"{bls_dir}/lib:{mcl_dir}/lib"
return variables return variables
def json_load(string, **kwargs): def json_load( string, **kwargs ):
""" """
:param string: The JSON string to load :param string: The JSON string to load
:returns A dictionary loaded from a JSON string to a dictionary. :returns A dictionary loaded from a JSON string to a dictionary.
@ -179,7 +181,7 @@ def json_load(string, **kwargs):
Note that this prints the failed input should an error arise. Note that this prints the failed input should an error arise.
""" """
try: try:
return json.loads(string, **kwargs) return json.loads( string, **kwargs )
except Exception as exception: except Exception as exception:
print(f"{Typgpy.FAIL}Could not parse input: '{string}'{Typgpy.ENDC}") print( f"{Typgpy.FAIL}Could not parse input: '{string}'{Typgpy.ENDC}" )
raise exception raise exception

@ -22,11 +22,7 @@ from .constants import (
from .exceptions import InvalidValidatorError from .exceptions import InvalidValidatorError
from .rpc.exceptions import ( from .rpc.exceptions import ( RPCError, RequestsError, RequestsTimeoutError, )
RPCError,
RequestsError,
RequestsTimeoutError,
)
from .staking import get_all_validator_addresses, get_validator_information from .staking import get_all_validator_addresses, get_validator_information
@ -34,16 +30,22 @@ from .staking_structures import Directive
from .staking_signing import sign_staking_transaction from .staking_signing import sign_staking_transaction
class Validator: # pylint: disable=too-many-instance-attributes, too-many-public-methods
class Validator: # pylint: disable=too-many-instance-attributes, too-many-public-methods
""" """
Harmony validator Harmony validator
""" """
def __init__( self, address ):
def __init__(self, address): if not isinstance( address, str ):
if not isinstance(address, str): raise InvalidValidatorError(
raise InvalidValidatorError(1, "given ONE address was not a string") 1,
if not is_valid_address(address): "given ONE address was not a string"
raise InvalidValidatorError(1, f"{address} is not valid ONE address") )
if not is_valid_address( address ):
raise InvalidValidatorError(
1,
f"{address} is not valid ONE address"
)
self._address = address self._address = address
self._bls_keys = [] self._bls_keys = []
self._bls_key_sigs = [] self._bls_key_sigs = []
@ -62,7 +64,7 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
self._max_change_rate = None self._max_change_rate = None
self._max_rate = None self._max_rate = None
def _sanitize_input(self, data, check_str=False) -> str: def _sanitize_input( self, data, check_str = False ) -> str:
"""If data is None, return '' else return data. """If data is None, return '' else return data.
Raises Raises
@ -70,26 +72,26 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
InvalidValidatorError if check_str is True and str is not passed InvalidValidatorError if check_str is True and str is not passed
""" """
if check_str: if check_str:
if not isinstance(data, str): if not isinstance( data, str ):
raise InvalidValidatorError( raise InvalidValidatorError(
3, 3,
"Expected data to be string " "Expected data to be string "
f"to avoid floating point precision issues but got {data}", f"to avoid floating point precision issues but got {data}",
) )
return "" if not data else str(data) return "" if not data else str( data )
def __str__(self) -> str: def __str__( self ) -> str:
"""Returns JSON string representation of Validator fields.""" """Returns JSON string representation of Validator fields."""
info = self.export() info = self.export()
for key, value in info.items(): for key, value in info.items():
if isinstance(value, Decimal): if isinstance( value, Decimal ):
info[key] = str(value) info[ key ] = str( value )
return json.dumps(info) return json.dumps( info )
def __repr__(self) -> str: def __repr__( self ) -> str:
return f"<Validator: {hex(id(self))}>" return f"<Validator: {hex(id(self))}>"
def get_address(self) -> str: def get_address( self ) -> str:
"""Get validator address. """Get validator address.
Returns Returns
@ -99,7 +101,7 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
""" """
return self._address return self._address
def add_bls_key(self, key) -> bool: def add_bls_key( self, key ) -> bool:
"""Add BLS public key to validator BLS keys if not already in list. """Add BLS public key to validator BLS keys if not already in list.
Returns Returns
@ -107,13 +109,13 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
bool bool
If adding BLS key succeeded If adding BLS key succeeded
""" """
key = self._sanitize_input(key) key = self._sanitize_input( key )
if key not in self._bls_keys: if key not in self._bls_keys:
self._bls_keys.append(key) self._bls_keys.append( key )
return True return True
return False return False
def remove_bls_key(self, key) -> bool: def remove_bls_key( self, key ) -> bool:
"""Remove BLS public key from validator BLS keys if exists. """Remove BLS public key from validator BLS keys if exists.
Returns Returns
@ -121,13 +123,13 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
bool bool
If removing BLS key succeeded If removing BLS key succeeded
""" """
key = self._sanitize_input(key) key = self._sanitize_input( key )
if key in self._bls_keys: if key in self._bls_keys:
self._bls_keys.remove(key) self._bls_keys.remove( key )
return True return True
return False return False
def get_bls_keys(self) -> list: def get_bls_keys( self ) -> list:
"""Get list of validator BLS keys. """Get list of validator BLS keys.
Returns Returns
@ -137,7 +139,7 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
""" """
return self._bls_keys return self._bls_keys
def add_bls_key_sig(self, key) -> bool: def add_bls_key_sig( self, key ) -> bool:
"""Add BLS public key to validator BLS keys if not already in list. """Add BLS public key to validator BLS keys if not already in list.
Returns Returns
@ -145,13 +147,13 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
bool bool
If adding BLS key succeeded If adding BLS key succeeded
""" """
key = self._sanitize_input(key) key = self._sanitize_input( key )
if key not in self._bls_key_sigs: if key not in self._bls_key_sigs:
self._bls_key_sigs.append(key) self._bls_key_sigs.append( key )
return True return True
return False return False
def remove_bls_key_sig(self, key) -> bool: def remove_bls_key_sig( self, key ) -> bool:
"""Remove BLS public key from validator BLS keys if exists. """Remove BLS public key from validator BLS keys if exists.
Returns Returns
@ -159,13 +161,13 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
bool bool
If removing BLS key succeeded If removing BLS key succeeded
""" """
key = self._sanitize_input(key) key = self._sanitize_input( key )
if key in self._bls_key_sigs: if key in self._bls_key_sigs:
self._bls_key_sigs.remove(key) self._bls_key_sigs.remove( key )
return True return True
return False return False
def get_bls_key_sigs(self) -> list: def get_bls_key_sigs( self ) -> list:
"""Get list of validator BLS keys. """Get list of validator BLS keys.
Returns Returns
@ -175,7 +177,7 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
""" """
return self._bls_key_sigs return self._bls_key_sigs
def set_name(self, name): def set_name( self, name ):
"""Set validator name. """Set validator name.
Parameters Parameters
@ -188,14 +190,15 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
InvalidValidatorError InvalidValidatorError
If input is invalid If input is invalid
""" """
name = self._sanitize_input(name) name = self._sanitize_input( name )
if len(name) > NAME_CHAR_LIMIT: if len( name ) > NAME_CHAR_LIMIT:
raise InvalidValidatorError( raise InvalidValidatorError(
3, f"Name must be less than {NAME_CHAR_LIMIT} characters" 3,
f"Name must be less than {NAME_CHAR_LIMIT} characters"
) )
self._name = name self._name = name
def get_name(self) -> str: def get_name( self ) -> str:
"""Get validator name. """Get validator name.
Returns Returns
@ -205,7 +208,7 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
""" """
return self._name return self._name
def set_identity(self, identity): def set_identity( self, identity ):
"""Set validator identity. """Set validator identity.
Parameters Parameters
@ -218,14 +221,15 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
InvalidValidatorError InvalidValidatorError
If input is invalid If input is invalid
""" """
identity = self._sanitize_input(identity) identity = self._sanitize_input( identity )
if len(identity) > IDENTITY_CHAR_LIMIT: if len( identity ) > IDENTITY_CHAR_LIMIT:
raise InvalidValidatorError( raise InvalidValidatorError(
3, f"Identity must be less than {IDENTITY_CHAR_LIMIT} characters" 3,
f"Identity must be less than {IDENTITY_CHAR_LIMIT} characters"
) )
self._identity = identity self._identity = identity
def get_identity(self) -> str: def get_identity( self ) -> str:
"""Get validator identity. """Get validator identity.
Returns Returns
@ -235,7 +239,7 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
""" """
return self._identity return self._identity
def set_website(self, website): def set_website( self, website ):
"""Set validator website. """Set validator website.
Parameters Parameters
@ -248,14 +252,15 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
InvalidValidatorError InvalidValidatorError
If input is invalid If input is invalid
""" """
website = self._sanitize_input(website) website = self._sanitize_input( website )
if len(website) > WEBSITE_CHAR_LIMIT: if len( website ) > WEBSITE_CHAR_LIMIT:
raise InvalidValidatorError( raise InvalidValidatorError(
3, f"Website must be less than {WEBSITE_CHAR_LIMIT} characters" 3,
f"Website must be less than {WEBSITE_CHAR_LIMIT} characters"
) )
self._website = website self._website = website
def get_website(self) -> str: def get_website( self ) -> str:
"""Get validator website. """Get validator website.
Returns Returns
@ -265,7 +270,7 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
""" """
return self._website return self._website
def set_security_contact(self, contact): def set_security_contact( self, contact ):
"""Set validator security contact. """Set validator security contact.
Parameters Parameters
@ -278,15 +283,15 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
InvalidValidatorError InvalidValidatorError
If input is invalid If input is invalid
""" """
contact = self._sanitize_input(contact) contact = self._sanitize_input( contact )
if len(contact) > SECURITY_CONTACT_CHAR_LIMIT: if len( contact ) > SECURITY_CONTACT_CHAR_LIMIT:
raise InvalidValidatorError( raise InvalidValidatorError(
3, 3,
f"Security contact must be less than {SECURITY_CONTACT_CHAR_LIMIT} characters", f"Security contact must be less than {SECURITY_CONTACT_CHAR_LIMIT} characters",
) )
self._security_contact = contact self._security_contact = contact
def get_security_contact(self) -> str: def get_security_contact( self ) -> str:
"""Get validator security contact. """Get validator security contact.
Returns Returns
@ -296,7 +301,7 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
""" """
return self._security_contact return self._security_contact
def set_details(self, details): def set_details( self, details ):
"""Set validator details. """Set validator details.
Parameters Parameters
@ -309,14 +314,15 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
InvalidValidatorError InvalidValidatorError
If input is invalid If input is invalid
""" """
details = self._sanitize_input(details) details = self._sanitize_input( details )
if len(details) > DETAILS_CHAR_LIMIT: if len( details ) > DETAILS_CHAR_LIMIT:
raise InvalidValidatorError( raise InvalidValidatorError(
3, f"Details must be less than {DETAILS_CHAR_LIMIT} characters" 3,
f"Details must be less than {DETAILS_CHAR_LIMIT} characters"
) )
self._details = details self._details = details
def get_details(self) -> str: def get_details( self ) -> str:
"""Get validator details. """Get validator details.
Returns Returns
@ -326,7 +332,7 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
""" """
return self._details return self._details
def set_min_self_delegation(self, delegation): def set_min_self_delegation( self, delegation ):
"""Set validator min self delegation. """Set validator min self delegation.
Parameters Parameters
@ -339,12 +345,13 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
InvalidValidatorError InvalidValidatorError
If input is invalid If input is invalid
""" """
delegation = self._sanitize_input(delegation) delegation = self._sanitize_input( delegation )
try: try:
delegation = Decimal(delegation) delegation = Decimal( delegation )
except (TypeError, InvalidOperation) as exception: except ( TypeError, InvalidOperation ) as exception:
raise InvalidValidatorError( raise InvalidValidatorError(
3, "Min self delegation must be a number" 3,
"Min self delegation must be a number"
) from exception ) from exception
if delegation < MIN_REQUIRED_DELEGATION: if delegation < MIN_REQUIRED_DELEGATION:
raise InvalidValidatorError( raise InvalidValidatorError(
@ -353,7 +360,7 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
) )
self._min_self_delegation = delegation self._min_self_delegation = delegation
def get_min_self_delegation(self) -> Decimal: def get_min_self_delegation( self ) -> Decimal:
"""Get validator min self delegation. """Get validator min self delegation.
Returns Returns
@ -363,7 +370,7 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
""" """
return self._min_self_delegation return self._min_self_delegation
def set_max_total_delegation(self, max_delegation): def set_max_total_delegation( self, max_delegation ):
"""Set validator max total delegation. """Set validator max total delegation.
Parameters Parameters
@ -376,12 +383,13 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
InvalidValidatorError InvalidValidatorError
If input is invalid If input is invalid
""" """
max_delegation = self._sanitize_input(max_delegation) max_delegation = self._sanitize_input( max_delegation )
try: try:
max_delegation = Decimal(max_delegation) max_delegation = Decimal( max_delegation )
except (TypeError, InvalidOperation) as exception: except ( TypeError, InvalidOperation ) as exception:
raise InvalidValidatorError( raise InvalidValidatorError(
3, "Max total delegation must be a number" 3,
"Max total delegation must be a number"
) from exception ) from exception
if self._min_self_delegation: if self._min_self_delegation:
if max_delegation < self._min_self_delegation: if max_delegation < self._min_self_delegation:
@ -392,11 +400,12 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
) )
else: else:
raise InvalidValidatorError( raise InvalidValidatorError(
4, "Min self delegation must be set before max total delegation" 4,
"Min self delegation must be set before max total delegation"
) )
self._max_total_delegation = max_delegation self._max_total_delegation = max_delegation
def get_max_total_delegation(self) -> Decimal: def get_max_total_delegation( self ) -> Decimal:
"""Get validator max total delegation. """Get validator max total delegation.
Returns Returns
@ -406,7 +415,7 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
""" """
return self._max_total_delegation return self._max_total_delegation
def set_amount(self, amount): def set_amount( self, amount ):
"""Set validator initial delegation amount. """Set validator initial delegation amount.
Parameters Parameters
@ -419,11 +428,14 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
InvalidValidatorError InvalidValidatorError
If input is invalid If input is invalid
""" """
amount = self._sanitize_input(amount) amount = self._sanitize_input( amount )
try: try:
amount = Decimal(amount) amount = Decimal( amount )
except (TypeError, InvalidOperation) as exception: except ( TypeError, InvalidOperation ) as exception:
raise InvalidValidatorError(3, "Amount must be a number") from exception raise InvalidValidatorError(
3,
"Amount must be a number"
) from exception
if self._min_self_delegation: if self._min_self_delegation:
if amount < self._min_self_delegation: if amount < self._min_self_delegation:
raise InvalidValidatorError( raise InvalidValidatorError(
@ -433,7 +445,8 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
) )
else: else:
raise InvalidValidatorError( raise InvalidValidatorError(
4, "Min self delegation must be set before amount" 4,
"Min self delegation must be set before amount"
) )
if self._max_total_delegation: if self._max_total_delegation:
if amount > self._max_total_delegation: if amount > self._max_total_delegation:
@ -444,11 +457,12 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
) )
else: else:
raise InvalidValidatorError( raise InvalidValidatorError(
4, "Max total delegation must be set before amount" 4,
"Max total delegation must be set before amount"
) )
self._inital_delegation = amount self._inital_delegation = amount
def get_amount(self) -> Decimal: def get_amount( self ) -> Decimal:
"""Get validator initial delegation amount. """Get validator initial delegation amount.
Returns Returns
@ -458,7 +472,7 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
""" """
return self._inital_delegation return self._inital_delegation
def set_max_rate(self, rate): def set_max_rate( self, rate ):
"""Set validator max commission rate. """Set validator max commission rate.
Parameters Parameters
@ -471,16 +485,19 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
InvalidValidatorError InvalidValidatorError
If input is invalid If input is invalid
""" """
rate = self._sanitize_input(rate, True) rate = self._sanitize_input( rate, True )
try: try:
rate = Decimal(rate) rate = Decimal( rate )
except (TypeError, InvalidOperation) as exception: except ( TypeError, InvalidOperation ) as exception:
raise InvalidValidatorError(3, "Max rate must be a number") from exception raise InvalidValidatorError(
3,
"Max rate must be a number"
) from exception
if rate < 0 or rate > 1: if rate < 0 or rate > 1:
raise InvalidValidatorError(3, "Max rate must be between 0 and 1") raise InvalidValidatorError( 3, "Max rate must be between 0 and 1" )
self._max_rate = rate self._max_rate = rate
def get_max_rate(self) -> Decimal: def get_max_rate( self ) -> Decimal:
"""Get validator max commission rate. """Get validator max commission rate.
Returns Returns
@ -490,7 +507,7 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
""" """
return self._max_rate return self._max_rate
def set_max_change_rate(self, rate): def set_max_change_rate( self, rate ):
"""Set validator max commission change rate. """Set validator max commission change rate.
Parameters Parameters
@ -503,14 +520,18 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
InvalidValidatorError InvalidValidatorError
If input is invalid If input is invalid
""" """
rate = self._sanitize_input(rate, True) rate = self._sanitize_input( rate, True )
try: try:
rate = Decimal(rate) rate = Decimal( rate )
except (TypeError, InvalidOperation) as exception: except ( TypeError, InvalidOperation ) as exception:
raise InvalidValidatorError(3, "Max change rate must be a number") from exception raise InvalidValidatorError(
3,
"Max change rate must be a number"
) from exception
if rate < 0: if rate < 0:
raise InvalidValidatorError( raise InvalidValidatorError(
3, "Max change rate must be greater than or equal to 0" 3,
"Max change rate must be greater than or equal to 0"
) )
if self._max_rate: if self._max_rate:
if rate > self._max_rate: if rate > self._max_rate:
@ -520,11 +541,12 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
) )
else: else:
raise InvalidValidatorError( raise InvalidValidatorError(
4, "Max rate must be set before max change rate" 4,
"Max rate must be set before max change rate"
) )
self._max_change_rate = rate self._max_change_rate = rate
def get_max_change_rate(self) -> Decimal: def get_max_change_rate( self ) -> Decimal:
"""Get validator max commission change rate. """Get validator max commission change rate.
Returns Returns
@ -534,7 +556,7 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
""" """
return self._max_change_rate return self._max_change_rate
def set_rate(self, rate): def set_rate( self, rate ):
"""Set validator commission rate. """Set validator commission rate.
Parameters Parameters
@ -547,23 +569,30 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
InvalidValidatorError InvalidValidatorError
If input is invalid If input is invalid
""" """
rate = self._sanitize_input(rate, True) rate = self._sanitize_input( rate, True )
try: try:
rate = Decimal(rate) rate = Decimal( rate )
except (TypeError, InvalidOperation) as exception: except ( TypeError, InvalidOperation ) as exception:
raise InvalidValidatorError(3, "Rate must be a number") from exception raise InvalidValidatorError(
3,
"Rate must be a number"
) from exception
if rate < 0: if rate < 0:
raise InvalidValidatorError(3, "Rate must be greater than or equal to 0") raise InvalidValidatorError(
3,
"Rate must be greater than or equal to 0"
)
if self._max_rate: if self._max_rate:
if rate > self._max_rate: if rate > self._max_rate:
raise InvalidValidatorError( raise InvalidValidatorError(
3, f"Rate must be less than or equal to max rate: {self._max_rate}" 3,
f"Rate must be less than or equal to max rate: {self._max_rate}"
) )
else: else:
raise InvalidValidatorError(4, "Max rate must be set before rate") raise InvalidValidatorError( 4, "Max rate must be set before rate" )
self._rate = rate self._rate = rate
def get_rate(self) -> Decimal: def get_rate( self ) -> Decimal:
"""Get validator commission rate. """Get validator commission rate.
Returns Returns
@ -574,7 +603,9 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
return self._rate return self._rate
def does_validator_exist( def does_validator_exist(
self, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT self,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> bool: ) -> bool:
"""Check if validator exists on blockchain. """Check if validator exists on blockchain.
@ -595,12 +626,12 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
RPCError, RequestsError, RequestsTimeoutError RPCError, RequestsError, RequestsTimeoutError
If unable to get list of validators on chain If unable to get list of validators on chain
""" """
all_validators = get_all_validator_addresses(endpoint, timeout) all_validators = get_all_validator_addresses( endpoint, timeout )
if self._address in all_validators: if self._address in all_validators:
return True return True
return False return False
def load(self, info): def load( self, info ):
"""Import validator information. """Import validator information.
Parameters Parameters
@ -631,32 +662,37 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
If input value is invalid If input value is invalid
""" """
try: try:
self.set_name(info["name"]) self.set_name( info[ "name" ] )
self.set_identity(info["identity"]) self.set_identity( info[ "identity" ] )
self.set_website(info["website"]) self.set_website( info[ "website" ] )
self.set_details(info["details"]) self.set_details( info[ "details" ] )
self.set_security_contact(info["security-contact"]) self.set_security_contact( info[ "security-contact" ] )
self.set_min_self_delegation(info["min-self-delegation"]) self.set_min_self_delegation( info[ "min-self-delegation" ] )
self.set_max_total_delegation(info["max-total-delegation"]) self.set_max_total_delegation( info[ "max-total-delegation" ] )
self.set_amount(info["amount"]) self.set_amount( info[ "amount" ] )
self.set_max_rate(info["max-rate"]) self.set_max_rate( info[ "max-rate" ] )
self.set_max_change_rate(info["max-change-rate"]) self.set_max_change_rate( info[ "max-change-rate" ] )
self.set_rate(info["rate"]) self.set_rate( info[ "rate" ] )
self._bls_keys = [] self._bls_keys = []
for key in info["bls-public-keys"]: for key in info[ "bls-public-keys" ]:
self.add_bls_key(key) self.add_bls_key( key )
self._bls_key_sigs = [] self._bls_key_sigs = []
for key in info["bls-key-sigs"]: for key in info[ "bls-key-sigs" ]:
self.add_bls_key_sig(key) self.add_bls_key_sig( key )
except KeyError as exception: except KeyError as exception:
raise InvalidValidatorError(3, "Info has missing key") from exception raise InvalidValidatorError(
3,
"Info has missing key"
) from exception
def load_from_blockchain( def load_from_blockchain(
self, endpoint=DEFAULT_ENDPOINT, timeout=DEFAULT_TIMEOUT self,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
): ):
"""Import validator information from blockchain with given address At """Import validator information from blockchain with given address At
the moment, this is unable to fetch the BLS Signature, which is not the moment, this is unable to fetch the BLS Signature, which is not
@ -675,46 +711,54 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
If any error occur getting & importing validator information from the blockchain If any error occur getting & importing validator information from the blockchain
""" """
try: try:
if not self.does_validator_exist(endpoint, timeout): if not self.does_validator_exist( endpoint, timeout ):
raise InvalidValidatorError( raise InvalidValidatorError(
5, f"Validator does not exist on chain according to {endpoint}" 5,
f"Validator does not exist on chain according to {endpoint}"
) )
except (RPCError, RequestsError, RequestsTimeoutError) as exception: except ( RPCError, RequestsError, RequestsTimeoutError ) as exception:
raise InvalidValidatorError( raise InvalidValidatorError(
5, "Error requesting validator information" 5,
"Error requesting validator information"
) from exception ) from exception
try: try:
validator_info = get_validator_information(self._address, endpoint, timeout) validator_info = get_validator_information(
except (RPCError, RequestsError, RequestsTimeoutError) as exception: self._address,
endpoint,
timeout
)
except ( RPCError, RequestsError, RequestsTimeoutError ) as exception:
raise InvalidValidatorError( raise InvalidValidatorError(
5, "Error requesting validator information" 5,
"Error requesting validator information"
) from exception ) from exception
# Skip additional sanity checks when importing from chain # Skip additional sanity checks when importing from chain
try: try:
info = validator_info["validator"] info = validator_info[ "validator" ]
self._name = info["name"] self._name = info[ "name" ]
self._identity = info["identity"] self._identity = info[ "identity" ]
self._website = info["website"] self._website = info[ "website" ]
self._details = info["details"] self._details = info[ "details" ]
self._security_contact = info["security-contact"] self._security_contact = info[ "security-contact" ]
self._min_self_delegation = info["min-self-delegation"] self._min_self_delegation = info[ "min-self-delegation" ]
self._max_total_delegation = info["max-total-delegation"] self._max_total_delegation = info[ "max-total-delegation" ]
self._inital_delegation = ( self._inital_delegation = (
self._min_self_delegation self._min_self_delegation
) # Since validator exists, set initial delegation to 0 ) # Since validator exists, set initial delegation to 0
self._max_rate = Decimal(info["max-rate"]) self._max_rate = Decimal( info[ "max-rate" ] )
self._max_change_rate = Decimal(info["max-change-rate"]) self._max_change_rate = Decimal( info[ "max-change-rate" ] )
self._rate = Decimal(info["rate"]) self._rate = Decimal( info[ "rate" ] )
self._bls_keys = info["bls-public-keys"] self._bls_keys = info[ "bls-public-keys" ]
except KeyError as exception: except KeyError as exception:
raise InvalidValidatorError( raise InvalidValidatorError(
5, "Error importing validator information from RPC result" 5,
"Error importing validator information from RPC result"
) from exception ) from exception
def export(self) -> dict: def export( self ) -> dict:
"""Export validator information as dict. """Export validator information as dict.
Returns Returns
@ -760,14 +804,16 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L413 https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L413
""" """
info = self.export().copy() info = self.export().copy()
info["directive"] = Directive.CreateValidator info[ "directive" ] = Directive.CreateValidator
info["validatorAddress"] = info.pop("validator-addr") # change the key info[ "validatorAddress" ] = info.pop(
info["nonce"] = nonce "validator-addr"
info["gasPrice"] = gas_price ) # change the key
info["gasLimit"] = gas_limit info[ "nonce" ] = nonce
info[ "gasPrice" ] = gas_price
info[ "gasLimit" ] = gas_limit
if chain_id: if chain_id:
info["chainId"] = chain_id info[ "chainId" ] = chain_id
return sign_staking_transaction(info, private_key) return sign_staking_transaction( info, private_key )
def sign_edit_validator_transaction( # pylint: disable=too-many-arguments def sign_edit_validator_transaction( # pylint: disable=too-many-arguments
self, self,
@ -797,22 +843,24 @@ class Validator: # pylint: disable=too-many-instance-attributes, too-many-public
------------- -------------
https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L460 https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L460
""" """
self.set_rate(rate) self.set_rate( rate )
self.add_bls_key(bls_key_to_add) self.add_bls_key( bls_key_to_add )
self.remove_bls_key(bls_key_to_remove) self.remove_bls_key( bls_key_to_remove )
info = self.export().copy() info = self.export().copy()
info["directive"] = Directive.EditValidator info[ "directive" ] = Directive.EditValidator
info["validatorAddress"] = info.pop("validator-addr") # change the key info[ "validatorAddress" ] = info.pop(
info["nonce"] = nonce "validator-addr"
info["gasPrice"] = gas_price ) # change the key
info["gasLimit"] = gas_limit info[ "nonce" ] = nonce
_ = info.pop("max-rate") # not needed info[ "gasPrice" ] = gas_price
_ = info.pop("max-change-rate") # not needed info[ "gasLimit" ] = gas_limit
_ = info.pop("bls-public-keys") # remove this list _ = info.pop( "max-rate" ) # not needed
_ = info.pop("amount") # also unused _ = info.pop( "max-change-rate" ) # not needed
info["bls-key-to-remove"] = bls_key_to_remove _ = info.pop( "bls-public-keys" ) # remove this list
info["bls-key-to-add"] = bls_key_to_add _ = info.pop( "amount" ) # also unused
info["bls-key-to-add-sig"] = bls_key_to_add_sig info[ "bls-key-to-remove" ] = bls_key_to_remove
info[ "bls-key-to-add" ] = bls_key_to_add
info[ "bls-key-to-add-sig" ] = bls_key_to_add_sig
if chain_id: if chain_id:
info["chainId"] = chain_id info[ "chainId" ] = chain_id
return sign_staking_transaction(info, private_key) return sign_staking_transaction( info, private_key )

@ -2,8 +2,8 @@ from pyhmy.bech32 import bech32
def test_encode(): def test_encode():
bech32.encode("one", 5, [121, 161]) bech32.encode( "one", 5, [ 121, 161 ] )
def test_decode(): def test_decode():
bech32.decode("one", "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9") bech32.decode( "one", "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9" )

@ -10,57 +10,58 @@ TEMP_DIR = "/tmp/pyhmy-testing/test-cli"
BINARY_FILE_PATH = f"{TEMP_DIR}/bin/cli_test_binary" BINARY_FILE_PATH = f"{TEMP_DIR}/bin/cli_test_binary"
@pytest.fixture(scope="session", autouse=True) @pytest.fixture( scope = "session", autouse = True )
def setup(): def setup():
shutil.rmtree(TEMP_DIR, ignore_errors=True) shutil.rmtree( TEMP_DIR, ignore_errors = True )
os.makedirs(TEMP_DIR, exist_ok=True) os.makedirs( TEMP_DIR, exist_ok = True )
def test_download_cli(): def test_download_cli():
env = cli.download(BINARY_FILE_PATH, replace=False, verbose=False) env = cli.download( BINARY_FILE_PATH, replace = False, verbose = False )
cli.environment.update(env) cli.environment.update( env )
assert os.path.exists(BINARY_FILE_PATH) assert os.path.exists( BINARY_FILE_PATH )
def test_is_valid(): def test_is_valid():
bad_file_path = os.path.realpath(f"{TEMP_DIR}/test_is_valid/bad_hmy") bad_file_path = os.path.realpath( f"{TEMP_DIR}/test_is_valid/bad_hmy" )
shutil.rmtree(Path(bad_file_path).parent, ignore_errors=True) shutil.rmtree( Path( bad_file_path ).parent, ignore_errors = True )
os.makedirs(Path(bad_file_path).parent, exist_ok=True) os.makedirs( Path( bad_file_path ).parent, exist_ok = True )
Path(bad_file_path).touch() Path( bad_file_path ).touch()
assert os.path.exists(BINARY_FILE_PATH), "harmony cli did not download" assert os.path.exists( BINARY_FILE_PATH ), "harmony cli did not download"
assert os.path.exists(bad_file_path), "did not create bad binary" assert os.path.exists( bad_file_path ), "did not create bad binary"
assert cli.is_valid_binary(BINARY_FILE_PATH) assert cli.is_valid_binary( BINARY_FILE_PATH )
assert not cli.is_valid_binary(bad_file_path) assert not cli.is_valid_binary( bad_file_path )
def test_bad_bin_set(): def test_bad_bin_set():
bad_file_path = os.path.realpath(f"{TEMP_DIR}/test_bad_bin_set/hmy") bad_file_path = os.path.realpath( f"{TEMP_DIR}/test_bad_bin_set/hmy" )
shutil.rmtree(Path(bad_file_path).parent, ignore_errors=True) shutil.rmtree( Path( bad_file_path ).parent, ignore_errors = True )
os.makedirs(Path(bad_file_path).parent, exist_ok=True) os.makedirs( Path( bad_file_path ).parent, exist_ok = True )
Path(bad_file_path).touch() Path( bad_file_path ).touch()
is_set = cli.set_binary(bad_file_path) is_set = cli.set_binary( bad_file_path )
assert not is_set assert not is_set
assert cli.get_binary_path() != bad_file_path assert cli.get_binary_path() != bad_file_path
def test_bin_set(): def test_bin_set():
cli.set_binary(BINARY_FILE_PATH) cli.set_binary( BINARY_FILE_PATH )
cli_binary_path = cli.get_binary_path() cli_binary_path = cli.get_binary_path()
assert os.path.realpath(cli_binary_path) == os.path.realpath(BINARY_FILE_PATH) assert os.path.realpath( cli_binary_path
) == os.path.realpath( BINARY_FILE_PATH )
def test_update_keystore(): def test_update_keystore():
cli.single_call("hmy keys add test1") cli.single_call( "hmy keys add test1" )
addrs = cli.get_accounts_keystore() addrs = cli.get_accounts_keystore()
assert "test1" in addrs.keys() assert "test1" in addrs.keys()
check_addr = addrs["test1"] check_addr = addrs[ "test1" ]
accounts_list = cli.get_accounts(check_addr) accounts_list = cli.get_accounts( check_addr )
check_acc = accounts_list[0] check_acc = accounts_list[ 0 ]
assert check_acc == "test1" assert check_acc == "test1"
raw_cli_keys_list_print = cli.single_call("hmy keys list", timeout=2) raw_cli_keys_list_print = cli.single_call( "hmy keys list", timeout = 2 )
assert check_addr in raw_cli_keys_list_print assert check_addr in raw_cli_keys_list_print
assert check_acc in raw_cli_keys_list_print assert check_acc in raw_cli_keys_list_print
assert addrs[check_acc] == check_addr assert addrs[ check_acc ] == check_addr
cli.remove_address(check_addr) cli.remove_address( check_addr )
assert check_addr not in addrs.values() assert check_addr not in addrs.values()
assert "test1" not in addrs.keys() assert "test1" not in addrs.keys()

@ -6,19 +6,19 @@ from pyhmy import logging
def test_basic_logger(): def test_basic_logger():
if os.path.exists(f"{os.getcwd()}/logs/pytest.log"): if os.path.exists( f"{os.getcwd()}/logs/pytest.log" ):
os.remove(f"{os.getcwd()}/logs/pytest.log") os.remove( f"{os.getcwd()}/logs/pytest.log" )
logger = logging.ControlledLogger("pytest", "logs/") logger = logging.ControlledLogger( "pytest", "logs/" )
assert os.path.exists(f"{os.getcwd()}/logs/pytest.log") assert os.path.exists( f"{os.getcwd()}/logs/pytest.log" )
logger.info("test info") logger.info( "test info" )
logger.debug("test debug") logger.debug( "test debug" )
logger.error("test error") logger.error( "test error" )
logger.warning("test warning") logger.warning( "test warning" )
with open(f"{os.getcwd()}/logs/pytest.log", "r") as f: with open( f"{os.getcwd()}/logs/pytest.log", "r" ) as f:
log_file_contents = f.readlines() log_file_contents = f.readlines()
assert not log_file_contents assert not log_file_contents
logger.write() logger.write()
with open(f"{os.getcwd()}/logs/pytest.log", "r") as f: with open( f"{os.getcwd()}/logs/pytest.log", "r" ) as f:
log_file_contents = f.readlines() log_file_contents = f.readlines()
for line in log_file_contents: for line in log_file_contents:
if "INFO" in line: if "INFO" in line:

@ -4,28 +4,28 @@ from pyhmy import numbers
def test_convert_atto_to_one(): def test_convert_atto_to_one():
a = numbers.convert_atto_to_one(1e18) a = numbers.convert_atto_to_one( 1e18 )
assert Decimal(1) == a assert Decimal( 1 ) == a
b = numbers.convert_atto_to_one(1e18 + 0.6) b = numbers.convert_atto_to_one( 1e18 + 0.6 )
assert Decimal(1) == b assert Decimal( 1 ) == b
c = numbers.convert_atto_to_one("1" + ("0" * 18)) c = numbers.convert_atto_to_one( "1" + ( "0" * 18 ) )
assert Decimal(1) == c assert Decimal( 1 ) == c
d = numbers.convert_atto_to_one(Decimal(1e18)) d = numbers.convert_atto_to_one( Decimal( 1e18 ) )
assert Decimal(1) == d assert Decimal( 1 ) == d
def test_convert_one_to_atto(): def test_convert_one_to_atto():
a = numbers.convert_one_to_atto(1e-18) a = numbers.convert_one_to_atto( 1e-18 )
assert Decimal(1) == a assert Decimal( 1 ) == a
b = numbers.convert_one_to_atto(1.5) b = numbers.convert_one_to_atto( 1.5 )
assert Decimal(1.5e18) == b assert Decimal( 1.5e18 ) == b
c = numbers.convert_one_to_atto("1") c = numbers.convert_one_to_atto( "1" )
assert Decimal(1e18) == c assert Decimal( 1e18 ) == c
d = numbers.convert_one_to_atto(Decimal(1)) d = numbers.convert_one_to_atto( Decimal( 1 ) )
assert Decimal(1e18) == d assert Decimal( 1e18 ) == d

@ -7,56 +7,70 @@ import requests
from pyhmy.rpc import exceptions, request from pyhmy.rpc import exceptions, request
@pytest.fixture(scope="session", autouse=True) @pytest.fixture( scope = "session", autouse = True )
def setup(): def setup():
endpoint = "http://localhost:9500" endpoint = "http://localhost:9500"
timeout = 30 timeout = 30
method = "hmyv2_getNodeMetadata" method = "hmyv2_getNodeMetadata"
params = [] params = []
payload = {"id": "1", "jsonrpc": "2.0", "method": method, "params": params} payload = {
headers = {"Content-Type": "application/json"} "id": "1",
"jsonrpc": "2.0",
"method": method,
"params": params
}
headers = {
"Content-Type": "application/json"
}
try: try:
response = requests.request( response = requests.request(
"POST", "POST",
endpoint, endpoint,
headers=headers, headers = headers,
data=json.dumps(payload), data = json.dumps( payload ),
timeout=timeout, timeout = timeout,
allow_redirects=True, allow_redirects = True,
) )
except Exception as e: except Exception as e:
pytest.skip("can not connect to local blockchain", allow_module_level=True) pytest.skip(
"can not connect to local blockchain",
allow_module_level = True
)
def test_request_connection_error(): def test_request_connection_error():
# Find available port # Find available port
s = socket.socket() s = socket.socket()
s.bind(("localhost", 0)) s.bind( ( "localhost", 0 ) )
port = s.getsockname()[1] port = s.getsockname()[ 1 ]
s.close() s.close()
if port == 0: if port == 0:
pytest.skip("could not find available port") pytest.skip( "could not find available port" )
bad_endpoint = f"http://localhost:{port}" bad_endpoint = f"http://localhost:{port}"
bad_request = None bad_request = None
try: try:
bad_request = request.rpc_request( bad_request = request.rpc_request(
"hmyv2_getNodeMetadata", endpoint=bad_endpoint "hmyv2_getNodeMetadata",
endpoint = bad_endpoint
) )
except Exception as e: except Exception as e:
assert isinstance(e, exceptions.RequestsError) assert isinstance( e, exceptions.RequestsError )
assert bad_request is None assert bad_request is None
def test_request_rpc_error(): def test_request_rpc_error():
error_request = None error_request = None
try: try:
error_request = request.rpc_request("hmyv2_getBalance") error_request = request.rpc_request( "hmyv2_getBalance" )
except (exceptions.RequestsTimeoutError, exceptions.RequestsError) as err: except ( exceptions.RequestsTimeoutError, exceptions.RequestsError ) as err:
pytest.skip("can not connect to local blockchain", allow_module_level=True) pytest.skip(
"can not connect to local blockchain",
allow_module_level = True
)
except Exception as e: except Exception as e:
assert isinstance(e, exceptions.RPCError) assert isinstance( e, exceptions.RPCError )
assert error_request is None assert error_request is None
@ -65,33 +79,40 @@ def test_rpc_request():
timeout = 30 timeout = 30
method = "hmyv2_getNodeMetadata" method = "hmyv2_getNodeMetadata"
params = [] params = []
payload = {"id": "1", "jsonrpc": "2.0", "method": method, "params": params} payload = {
headers = {"Content-Type": "application/json"} "id": "1",
"jsonrpc": "2.0",
"method": method,
"params": params
}
headers = {
"Content-Type": "application/json"
}
response = None response = None
try: try:
response = requests.request( response = requests.request(
"POST", "POST",
endpoint, endpoint,
headers=headers, headers = headers,
data=json.dumps(payload), data = json.dumps( payload ),
timeout=timeout, timeout = timeout,
allow_redirects=True, allow_redirects = True,
) )
except: except:
pytest.skip("can not connect to local blockchain") pytest.skip( "can not connect to local blockchain" )
assert response is not None assert response is not None
resp = None resp = None
try: try:
resp = json.loads(response.content) resp = json.loads( response.content )
except json.decoder.JSONDecodeError as err: except json.decoder.JSONDecodeError as err:
pytest.skip("unable to decode response") pytest.skip( "unable to decode response" )
assert resp is not None assert resp is not None
rpc_response = None rpc_response = None
try: try:
rpc_response = request.rpc_request(method, params, endpoint, timeout) rpc_response = request.rpc_request( method, params, endpoint, timeout )
except exceptions.RPCError as e: except exceptions.RPCError as e:
assert "error" in resp assert "error" in resp

@ -11,7 +11,9 @@ import requests
endpoint = "http://localhost:9500" endpoint = "http://localhost:9500"
timeout = 30 timeout = 30
headers = {"Content-Type": "application/json"} headers = {
"Content-Type": "application/json"
}
txs = [ txs = [
# same shard 503 ONE transfer from one155jp2y76nazx8uw5sa94fr0m4s5aj8e5xm6fu3 to one1ru3p8ff0wsyl7ncsx3vwd5szuze64qz60upg37 (0 nonce) # same shard 503 ONE transfer from one155jp2y76nazx8uw5sa94fr0m4s5aj8e5xm6fu3 to one1ru3p8ff0wsyl7ncsx3vwd5szuze64qz60upg37 (0 nonce)
"0xf86f8085174876e8008252088080941f2213a52f7409ff4f103458e6d202e0b3aa805a891b4486fafde57c00008027a0d7c0b20207dcc9dde376822dc3f5625eac6f59a7526111695cdba3e29553ca17a05d4ca9a421ae16f89cbf6848186eaea7a800da732446dff9952e7c1e91d414e3", "0xf86f8085174876e8008252088080941f2213a52f7409ff4f103458e6d202e0b3aa805a891b4486fafde57c00008027a0d7c0b20207dcc9dde376822dc3f5625eac6f59a7526111695cdba3e29553ca17a05d4ca9a421ae16f89cbf6848186eaea7a800da732446dff9952e7c1e91d414e3",
@ -35,35 +37,37 @@ stx_hashes = [
"0x400e9831d358f5daccd153cad5bf53650a0d413bd8682ec0ffad55367d162968", "0x400e9831d358f5daccd153cad5bf53650a0d413bd8682ec0ffad55367d162968",
"0xc8177ace2049d9f4eb4a45fd6bd6b16f693573d036322c36774cc00d05a3e24f", "0xc8177ace2049d9f4eb4a45fd6bd6b16f693573d036322c36774cc00d05a3e24f",
] ]
assert len(txs) == len(tx_hashes), "Mismatch in tx and tx_hash count" assert len( txs ) == len( tx_hashes ), "Mismatch in tx and tx_hash count"
assert len(stxs) == len(stx_hashes), "Mismatch in stx and stx_hash count" assert len( stxs ) == len( stx_hashes ), "Mismatch in stx and stx_hash count"
@pytest.fixture(scope="session", autouse=True) @pytest.fixture( scope = "session", autouse = True )
def setup_blockchain(): def setup_blockchain():
# return # return
metadata = _check_connection() metadata = _check_connection()
_check_staking_epoch(metadata) _check_staking_epoch( metadata )
for i in range(len(txs)): for i in range( len( txs ) ):
tx = txs[i] tx = txs[ i ]
tx_hash = tx_hashes[i] tx_hash = tx_hashes[ i ]
_send_transaction(tx, endpoint) _send_transaction( tx, endpoint )
if not _wait_for_transaction_confirmed(tx_hash, endpoint): if not _wait_for_transaction_confirmed( tx_hash, endpoint ):
pytest.skip( pytest.skip(
"Could not confirm initial transaction #{} on chain".format(i), "Could not confirm initial transaction #{} on chain"
allow_module_level=True, .format( i ),
allow_module_level = True,
) )
for i in range(len(stxs)): for i in range( len( stxs ) ):
stx = stxs[i] stx = stxs[ i ]
stx_hash = stx_hashes[i] stx_hash = stx_hashes[ i ]
_send_staking_transaction(stx, endpoint) _send_staking_transaction( stx, endpoint )
if not _wait_for_staking_transaction_confirmed(stx_hash, endpoint): if not _wait_for_staking_transaction_confirmed( stx_hash, endpoint ):
pytest.skip( pytest.skip(
"Could not confirm initial staking transaction #{} on chain".format(i), "Could not confirm initial staking transaction #{} on chain"
allow_module_level=True, .format( i ),
allow_module_level = True,
) )
@ -78,31 +82,31 @@ def _check_connection():
response = requests.request( response = requests.request(
"POST", "POST",
endpoint, endpoint,
headers=headers, headers = headers,
data=json.dumps(payload), data = json.dumps( payload ),
timeout=timeout, timeout = timeout,
allow_redirects=True, allow_redirects = True,
) )
metadata = json.loads(response.content) metadata = json.loads( response.content )
if "error" in metadata: if "error" in metadata:
pytest.skip( pytest.skip(
f"Error in hmyv2_getNodeMetadata reply: {metadata['error']}", f"Error in hmyv2_getNodeMetadata reply: {metadata['error']}",
allow_module_level=True, allow_module_level = True,
) )
if "chain-config" not in metadata["result"]: if "chain-config" not in metadata[ "result" ]:
pytest.skip( pytest.skip(
"Chain config not found in hmyv2_getNodeMetadata reply", "Chain config not found in hmyv2_getNodeMetadata reply",
allow_module_level=True, allow_module_level = True,
) )
return metadata return metadata
except Exception as e: except Exception as e:
pytest.skip( pytest.skip(
"Can not connect to local blockchain or bad hmyv2_getNodeMetadata reply", "Can not connect to local blockchain or bad hmyv2_getNodeMetadata reply",
allow_module_level=True, allow_module_level = True,
) )
def _check_staking_epoch(metadata): def _check_staking_epoch( metadata ):
latest_header = None latest_header = None
try: try:
payload = { payload = {
@ -114,158 +118,164 @@ def _check_staking_epoch(metadata):
response = requests.request( response = requests.request(
"POST", "POST",
endpoint, endpoint,
headers=headers, headers = headers,
data=json.dumps(payload), data = json.dumps( payload ),
timeout=timeout, timeout = timeout,
allow_redirects=True, allow_redirects = True,
) )
latest_header = json.loads(response.content) latest_header = json.loads( response.content )
if "error" in latest_header: if "error" in latest_header:
pytest.skip( pytest.skip(
f"Error in hmyv2_latestHeader reply: {latest_header['error']}", f"Error in hmyv2_latestHeader reply: {latest_header['error']}",
allow_module_level=True, allow_module_level = True,
) )
except Exception as e: except Exception as e:
pytest.skip("Failed to get hmyv2_latestHeader reply", allow_module_level=True) pytest.skip(
"Failed to get hmyv2_latestHeader reply",
allow_module_level = True
)
if metadata and latest_header: if metadata and latest_header:
staking_epoch = metadata["result"]["chain-config"]["staking-epoch"] staking_epoch = metadata[ "result" ][ "chain-config" ][ "staking-epoch"
current_epoch = latest_header["result"]["epoch"] ]
current_epoch = latest_header[ "result" ][ "epoch" ]
if staking_epoch > current_epoch: if staking_epoch > current_epoch:
pytest.skip( pytest.skip(
f"Not staking epoch: current {current_epoch}, staking {staking_epoch}", f"Not staking epoch: current {current_epoch}, staking {staking_epoch}",
allow_module_level=True, allow_module_level = True,
) )
def _send_transaction(raw_tx, endpoint): def _send_transaction( raw_tx, endpoint ):
try: try:
payload = { payload = {
"id": "1", "id": "1",
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": "hmyv2_sendRawTransaction", "method": "hmyv2_sendRawTransaction",
"params": [raw_tx], "params": [ raw_tx ],
} }
response = requests.request( response = requests.request(
"POST", "POST",
endpoint, endpoint,
headers=headers, headers = headers,
data=json.dumps(payload), data = json.dumps( payload ),
timeout=timeout, timeout = timeout,
allow_redirects=True, allow_redirects = True,
) )
tx = json.loads(response.content) tx = json.loads( response.content )
if "error" in tx: if "error" in tx:
pytest.skip( pytest.skip(
f"Error in hmyv2_sendRawTransaction reply: {tx['error']}", f"Error in hmyv2_sendRawTransaction reply: {tx['error']}",
allow_module_level=True, allow_module_level = True,
) )
except Exception as e: except Exception as e:
pytest.skip( pytest.skip(
"Failed to get hmyv2_sendRawTransaction reply", allow_module_level=True "Failed to get hmyv2_sendRawTransaction reply",
allow_module_level = True
) )
def _check_transaction(tx_hash, endpoint): def _check_transaction( tx_hash, endpoint ):
try: try:
payload = { payload = {
"id": "1", "id": "1",
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": "hmyv2_getTransactionByHash", "method": "hmyv2_getTransactionByHash",
"params": [tx_hash], "params": [ tx_hash ],
} }
response = requests.request( response = requests.request(
"POST", "POST",
endpoint, endpoint,
headers=headers, headers = headers,
data=json.dumps(payload), data = json.dumps( payload ),
timeout=timeout, timeout = timeout,
allow_redirects=True, allow_redirects = True,
) )
tx_data = json.loads(response.content) tx_data = json.loads( response.content )
return tx_data return tx_data
except Exception as e: except Exception as e:
pytest.skip( pytest.skip(
"Failed to get hmyv2_getTransactionByHash reply", allow_module_level=True "Failed to get hmyv2_getTransactionByHash reply",
allow_module_level = True
) )
def _wait_for_transaction_confirmed(tx_hash, endpoint, timeout=30): def _wait_for_transaction_confirmed( tx_hash, endpoint, timeout = 30 ):
start_time = time.time() start_time = time.time()
while (time.time() - start_time) <= timeout: while ( time.time() - start_time ) <= timeout:
tx_data = _check_transaction(tx_hash, endpoint) tx_data = _check_transaction( tx_hash, endpoint )
if tx_data is not None: if tx_data is not None:
block_hash = tx_data["result"].get("blockHash", "0x00") block_hash = tx_data[ "result" ].get( "blockHash", "0x00" )
unique_chars = "".join(set(list(block_hash[2:]))) unique_chars = "".join( set( list( block_hash[ 2 : ] ) ) )
if unique_chars != "0": if unique_chars != "0":
return True return True
time.sleep(random.uniform(0.2, 0.5)) time.sleep( random.uniform( 0.2, 0.5 ) )
return False return False
def _send_staking_transaction(raw_tx, endpoint=endpoint): def _send_staking_transaction( raw_tx, endpoint = endpoint ):
try: try:
payload = { payload = {
"id": "1", "id": "1",
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": "hmyv2_sendRawStakingTransaction", "method": "hmyv2_sendRawStakingTransaction",
"params": [raw_tx], "params": [ raw_tx ],
} }
response = requests.request( response = requests.request(
"POST", "POST",
endpoint, endpoint,
headers=headers, headers = headers,
data=json.dumps(payload), data = json.dumps( payload ),
timeout=timeout, timeout = timeout,
allow_redirects=True, allow_redirects = True,
) )
staking_tx = json.loads(response.content) staking_tx = json.loads( response.content )
if "error" in staking_tx: if "error" in staking_tx:
pytest.skip( pytest.skip(
f"Error in hmyv2_sendRawStakingTransaction reply: {staking_tx['error']}", f"Error in hmyv2_sendRawStakingTransaction reply: {staking_tx['error']}",
allow_module_level=True, allow_module_level = True,
) )
except Exception as e: except Exception as e:
pytest.skip( pytest.skip(
"Failed to get hmyv2_sendRawStakingTransaction reply", "Failed to get hmyv2_sendRawStakingTransaction reply",
allow_module_level=True, allow_module_level = True,
) )
def _check_staking_transaction(stx_hash, endpoint=endpoint): def _check_staking_transaction( stx_hash, endpoint = endpoint ):
try: try:
payload = { payload = {
"id": "1", "id": "1",
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": "hmyv2_getStakingTransactionByHash", "method": "hmyv2_getStakingTransactionByHash",
"params": [stx_hash], "params": [ stx_hash ],
} }
response = requests.request( response = requests.request(
"POST", "POST",
endpoint, endpoint,
headers=headers, headers = headers,
data=json.dumps(payload), data = json.dumps( payload ),
timeout=timeout, timeout = timeout,
allow_redirects=True, allow_redirects = True,
) )
stx_data = json.loads(response.content) stx_data = json.loads( response.content )
return stx_data return stx_data
except Exception as e: except Exception as e:
pytest.skip( pytest.skip(
"Failed to get hmyv2_getStakingTransactionByHash reply", "Failed to get hmyv2_getStakingTransactionByHash reply",
allow_module_level=True, allow_module_level = True,
) )
def _wait_for_staking_transaction_confirmed(tx_hash, endpoint, timeout=30): def _wait_for_staking_transaction_confirmed( tx_hash, endpoint, timeout = 30 ):
answer = False answer = False
start_time = time.time() start_time = time.time()
while (time.time() - start_time) <= timeout: while ( time.time() - start_time ) <= timeout:
tx_data = _check_staking_transaction(tx_hash, endpoint) tx_data = _check_staking_transaction( tx_hash, endpoint )
if tx_data is not None: if tx_data is not None:
block_hash = tx_data["result"].get("blockHash", "0x00") block_hash = tx_data[ "result" ].get( "blockHash", "0x00" )
unique_chars = "".join(set(list(block_hash[2:]))) unique_chars = "".join( set( list( block_hash[ 2 : ] ) ) )
if unique_chars != "0": if unique_chars != "0":
answer = True answer = True
time.sleep(random.uniform(0.2, 0.5)) time.sleep( random.uniform( 0.2, 0.5 ) )
return answer return answer

@ -5,7 +5,6 @@ from pyhmy import account
from pyhmy.rpc import exceptions from pyhmy.rpc import exceptions
explorer_endpoint = "http://localhost:9700" explorer_endpoint = "http://localhost:9700"
endpoint_shard_one = "http://localhost:9502" endpoint_shard_one = "http://localhost:9502"
local_test_address = "one155jp2y76nazx8uw5sa94fr0m4s5aj8e5xm6fu3" local_test_address = "one155jp2y76nazx8uw5sa94fr0m4s5aj8e5xm6fu3"
@ -15,122 +14,142 @@ test_block_number = 1
fake_shard = "http://example.com" fake_shard = "http://example.com"
def _test_account_rpc(fn, *args, **kwargs): def _test_account_rpc( fn, *args, **kwargs ):
if not callable(fn): if not callable( fn ):
pytest.fail(f"Invalid function: {fn}") pytest.fail( f"Invalid function: {fn}" )
try: try:
response = fn(*args, **kwargs) response = fn( *args, **kwargs )
except Exception as e: except Exception as e:
if isinstance( if isinstance( e,
e, exceptions.RPCError exceptions.RPCError
) and "does not exist/is not available" in str(e): ) and "does not exist/is not available" in str( e ):
pytest.skip(f"{str(e)}") pytest.skip( f"{str(e)}" )
pytest.fail(f"Unexpected error: {e.__class__} {e}") pytest.fail( f"Unexpected error: {e.__class__} {e}" )
return response return response
def test_get_balance(setup_blockchain): def test_get_balance( setup_blockchain ):
balance = _test_account_rpc(account.get_balance, local_test_address) balance = _test_account_rpc( account.get_balance, local_test_address )
assert isinstance(balance, int) assert isinstance( balance, int )
assert balance > 0 assert balance > 0
def test_get_balance_by_block(setup_blockchain): def test_get_balance_by_block( setup_blockchain ):
balance = _test_account_rpc( balance = _test_account_rpc(
account.get_balance_by_block, local_test_address, genesis_block_number account.get_balance_by_block,
local_test_address,
genesis_block_number
) )
assert isinstance(balance, int) assert isinstance( balance, int )
assert balance > 0 assert balance > 0
def test_get_account_nonce(setup_blockchain): def test_get_account_nonce( setup_blockchain ):
true_nonce = _test_account_rpc( true_nonce = _test_account_rpc(
account.get_account_nonce, account.get_account_nonce,
local_test_address, local_test_address,
test_block_number, test_block_number,
endpoint=endpoint_shard_one, endpoint = endpoint_shard_one,
) )
assert isinstance(true_nonce, int) assert isinstance( true_nonce, int )
def test_get_transaction_history(setup_blockchain): def test_get_transaction_history( setup_blockchain ):
tx_history = _test_account_rpc( tx_history = _test_account_rpc(
account.get_transaction_history, local_test_address, endpoint=explorer_endpoint account.get_transaction_history,
local_test_address,
endpoint = explorer_endpoint
) )
assert isinstance(tx_history, list) assert isinstance( tx_history, list )
assert len(tx_history) >= 0 assert len( tx_history ) >= 0
def test_get_staking_transaction_history(setup_blockchain): def test_get_staking_transaction_history( setup_blockchain ):
staking_tx_history = _test_account_rpc( staking_tx_history = _test_account_rpc(
account.get_staking_transaction_history, account.get_staking_transaction_history,
test_validator_address, test_validator_address,
endpoint=explorer_endpoint, endpoint = explorer_endpoint,
) )
assert isinstance(staking_tx_history, list) assert isinstance( staking_tx_history, list )
assert len(staking_tx_history) > 0 assert len( staking_tx_history ) > 0
def test_get_balance_on_all_shards(setup_blockchain): def test_get_balance_on_all_shards( setup_blockchain ):
balances = _test_account_rpc(account.get_balance_on_all_shards, local_test_address) balances = _test_account_rpc(
assert isinstance(balances, list) account.get_balance_on_all_shards,
assert len(balances) == 2 local_test_address
)
assert isinstance( balances, list )
assert len( balances ) == 2
def test_get_total_balance(setup_blockchain): def test_get_total_balance( setup_blockchain ):
total_balance = _test_account_rpc(account.get_total_balance, local_test_address) total_balance = _test_account_rpc(
assert isinstance(total_balance, int) account.get_total_balance,
local_test_address
)
assert isinstance( total_balance, int )
assert total_balance > 0 assert total_balance > 0
def test_is_valid_address(): def test_is_valid_address():
assert account.is_valid_address("one1zksj3evekayy90xt4psrz8h6j2v3hla4qwz4ur") assert account.is_valid_address(
assert not account.is_valid_address("one1wje75aedczmj4dwjs0812xcg7vx0dy231cajk0") "one1zksj3evekayy90xt4psrz8h6j2v3hla4qwz4ur"
)
assert not account.is_valid_address(
"one1wje75aedczmj4dwjs0812xcg7vx0dy231cajk0"
)
def test_get_transaction_count(setup_blockchain): def test_get_transaction_count( setup_blockchain ):
tx_count = _test_account_rpc( tx_count = _test_account_rpc(
account.get_transaction_count, local_test_address, "latest", explorer_endpoint account.get_transaction_count,
local_test_address,
"latest",
explorer_endpoint
) )
assert isinstance(tx_count, int) assert isinstance( tx_count, int )
assert tx_count > 0 assert tx_count > 0
def test_get_transactions_count(setup_blockchain): def test_get_transactions_count( setup_blockchain ):
tx_count = _test_account_rpc( tx_count = _test_account_rpc(
account.get_transactions_count, local_test_address, "ALL", explorer_endpoint account.get_transactions_count,
local_test_address,
"ALL",
explorer_endpoint
) )
def test_get_staking_transactions_count(setup_blockchain): def test_get_staking_transactions_count( setup_blockchain ):
tx_count = _test_account_rpc( tx_count = _test_account_rpc(
account.get_staking_transactions_count, account.get_staking_transactions_count,
local_test_address, local_test_address,
"ALL", "ALL",
explorer_endpoint, explorer_endpoint,
) )
assert isinstance(tx_count, int) assert isinstance( tx_count, int )
def test_errors(): def test_errors():
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
account.get_balance("", fake_shard) account.get_balance( "", fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
account.get_balance_by_block("", 1, fake_shard) account.get_balance_by_block( "", 1, fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
account.get_account_nonce("", 1, fake_shard) account.get_account_nonce( "", 1, fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
account.get_transaction_count("", 1, fake_shard) account.get_transaction_count( "", 1, fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
account.get_transactions_count("", 1, fake_shard) account.get_transactions_count( "", 1, fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
account.get_transactions_count("", "ALL", fake_shard) account.get_transactions_count( "", "ALL", fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
account.get_transaction_history("", endpoint=fake_shard) account.get_transaction_history( "", endpoint = fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
account.get_staking_transaction_history("", endpoint=fake_shard) account.get_staking_transaction_history( "", endpoint = fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
account.get_balance_on_all_shards("", endpoint=fake_shard) account.get_balance_on_all_shards( "", endpoint = fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
account.get_total_balance("", endpoint=fake_shard) account.get_total_balance( "", endpoint = fake_shard )

@ -5,7 +5,6 @@ from pyhmy import blockchain
from pyhmy.rpc import exceptions from pyhmy.rpc import exceptions
test_epoch_number = 0 test_epoch_number = 0
genesis_block_number = 0 genesis_block_number = 0
test_block_number = 1 test_block_number = 1
@ -14,323 +13,357 @@ fake_shard = "http://example.com"
address = "one155jp2y76nazx8uw5sa94fr0m4s5aj8e5xm6fu3" address = "one155jp2y76nazx8uw5sa94fr0m4s5aj8e5xm6fu3"
def _test_blockchain_rpc(fn, *args, **kwargs): def _test_blockchain_rpc( fn, *args, **kwargs ):
if not callable(fn): if not callable( fn ):
pytest.fail(f"Invalid function: {fn}") pytest.fail( f"Invalid function: {fn}" )
try: try:
response = fn(*args, **kwargs) response = fn( *args, **kwargs )
except Exception as e: except Exception as e:
if isinstance( if isinstance( e,
e, exceptions.RPCError exceptions.RPCError
) and "does not exist/is not available" in str(e): ) and "does not exist/is not available" in str( e ):
pytest.skip(f"{str(e)}") pytest.skip( f"{str(e)}" )
pytest.fail(f"Unexpected error: {e.__class__} {e}") pytest.fail( f"Unexpected error: {e.__class__} {e}" )
return response return response
def test_get_node_metadata(setup_blockchain): def test_get_node_metadata( setup_blockchain ):
metadata = _test_blockchain_rpc(blockchain.get_node_metadata) metadata = _test_blockchain_rpc( blockchain.get_node_metadata )
assert isinstance(metadata, dict) assert isinstance( metadata, dict )
def test_get_sharding_structure(setup_blockchain): def test_get_sharding_structure( setup_blockchain ):
sharding_structure = _test_blockchain_rpc(blockchain.get_sharding_structure) sharding_structure = _test_blockchain_rpc(
assert isinstance(sharding_structure, list) blockchain.get_sharding_structure
assert len(sharding_structure) > 0 )
assert isinstance( sharding_structure, list )
assert len( sharding_structure ) > 0
def test_get_leader_address(setup_blockchain): def test_get_leader_address( setup_blockchain ):
leader = _test_blockchain_rpc(blockchain.get_leader_address) leader = _test_blockchain_rpc( blockchain.get_leader_address )
assert isinstance(leader, str) assert isinstance( leader, str )
assert "one1" in leader assert "one1" in leader
def test_get_block_number(setup_blockchain): def test_get_block_number( setup_blockchain ):
current_block_number = _test_blockchain_rpc(blockchain.get_block_number) current_block_number = _test_blockchain_rpc( blockchain.get_block_number )
assert isinstance(current_block_number, int) assert isinstance( current_block_number, int )
def test_get_current_epoch(setup_blockchain): def test_get_current_epoch( setup_blockchain ):
current_epoch = _test_blockchain_rpc(blockchain.get_current_epoch) current_epoch = _test_blockchain_rpc( blockchain.get_current_epoch )
assert isinstance(current_epoch, int) assert isinstance( current_epoch, int )
def tset_get_gas_price(setup_blockchain): def tset_get_gas_price( setup_blockchain ):
gas = _test_blockchain_rpc(blockchain.get_gas_price) gas = _test_blockchain_rpc( blockchain.get_gas_price )
assert isinstance(gas, int) assert isinstance( gas, int )
def test_get_num_peers(setup_blockchain): def test_get_num_peers( setup_blockchain ):
peers = _test_blockchain_rpc(blockchain.get_num_peers) peers = _test_blockchain_rpc( blockchain.get_num_peers )
assert isinstance(peers, int) assert isinstance( peers, int )
def test_get_latest_header(setup_blockchain): def test_get_latest_header( setup_blockchain ):
header = _test_blockchain_rpc(blockchain.get_latest_header) header = _test_blockchain_rpc( blockchain.get_latest_header )
assert isinstance(header, dict) assert isinstance( header, dict )
def test_get_latest_chain_headers(setup_blockchain): def test_get_latest_chain_headers( setup_blockchain ):
header_pair = _test_blockchain_rpc(blockchain.get_latest_chain_headers) header_pair = _test_blockchain_rpc( blockchain.get_latest_chain_headers )
assert isinstance(header_pair, dict) assert isinstance( header_pair, dict )
def test_get_block_by_number(setup_blockchain): def test_get_block_by_number( setup_blockchain ):
global test_block_hash global test_block_hash
block = _test_blockchain_rpc(blockchain.get_block_by_number, test_block_number) block = _test_blockchain_rpc(
assert isinstance(block, dict) blockchain.get_block_by_number,
test_block_number
)
assert isinstance( block, dict )
assert "hash" in block.keys() assert "hash" in block.keys()
test_block_hash = block["hash"] test_block_hash = block[ "hash" ]
def test_get_block_by_hash(setup_blockchain): def test_get_block_by_hash( setup_blockchain ):
if not test_block_hash: if not test_block_hash:
pytest.skip("Failed to get reference block hash") pytest.skip( "Failed to get reference block hash" )
block = _test_blockchain_rpc(blockchain.get_block_by_hash, test_block_hash) block = _test_blockchain_rpc(
assert isinstance(block, dict) blockchain.get_block_by_hash,
test_block_hash
)
assert isinstance( block, dict )
def test_get_block_transaction_count_by_number(setup_blockchain): def test_get_block_transaction_count_by_number( setup_blockchain ):
tx_count = _test_blockchain_rpc( tx_count = _test_blockchain_rpc(
blockchain.get_block_transaction_count_by_number, test_block_number blockchain.get_block_transaction_count_by_number,
test_block_number
) )
assert isinstance(tx_count, int) assert isinstance( tx_count, int )
def test_get_block_transaction_count_by_hash(setup_blockchain): def test_get_block_transaction_count_by_hash( setup_blockchain ):
if not test_block_hash: if not test_block_hash:
pytest.skip("Failed to get reference block hash") pytest.skip( "Failed to get reference block hash" )
tx_count = _test_blockchain_rpc( tx_count = _test_blockchain_rpc(
blockchain.get_block_transaction_count_by_hash, test_block_hash blockchain.get_block_transaction_count_by_hash,
test_block_hash
) )
assert isinstance(tx_count, int) assert isinstance( tx_count, int )
def test_get_blocks(setup_blockchain): def test_get_blocks( setup_blockchain ):
blocks = _test_blockchain_rpc( blocks = _test_blockchain_rpc(
blockchain.get_blocks, genesis_block_number, test_block_number blockchain.get_blocks,
genesis_block_number,
test_block_number
) )
assert isinstance(blocks, list) assert isinstance( blocks, list )
assert len(blocks) == (test_block_number - genesis_block_number + 1) assert len( blocks ) == ( test_block_number - genesis_block_number + 1 )
def test_get_block_signers(setup_blockchain): def test_get_block_signers( setup_blockchain ):
block_signers = _test_blockchain_rpc( block_signers = _test_blockchain_rpc(
blockchain.get_block_signers, test_block_number blockchain.get_block_signers,
test_block_number
) )
assert isinstance(block_signers, list) assert isinstance( block_signers, list )
assert len(block_signers) > 0 assert len( block_signers ) > 0
def test_get_validators(setup_blockchain): def test_get_validators( setup_blockchain ):
validators = _test_blockchain_rpc(blockchain.get_validators, test_epoch_number) validators = _test_blockchain_rpc(
assert isinstance(validators, dict) blockchain.get_validators,
test_epoch_number
)
assert isinstance( validators, dict )
assert "validators" in validators.keys() assert "validators" in validators.keys()
assert len(validators["validators"]) > 0 assert len( validators[ "validators" ] ) > 0
def test_get_shard(setup_blockchain): def test_get_shard( setup_blockchain ):
shard = _test_blockchain_rpc(blockchain.get_shard) shard = _test_blockchain_rpc( blockchain.get_shard )
assert isinstance(shard, int) assert isinstance( shard, int )
assert shard == 0 assert shard == 0
def test_get_staking_epoch(setup_blockchain): def test_get_staking_epoch( setup_blockchain ):
staking_epoch = _test_blockchain_rpc(blockchain.get_staking_epoch) staking_epoch = _test_blockchain_rpc( blockchain.get_staking_epoch )
assert isinstance(staking_epoch, int) assert isinstance( staking_epoch, int )
def test_get_prestaking_epoch(setup_blockchain): def test_get_prestaking_epoch( setup_blockchain ):
prestaking_epoch = _test_blockchain_rpc(blockchain.get_prestaking_epoch) prestaking_epoch = _test_blockchain_rpc( blockchain.get_prestaking_epoch )
assert isinstance(prestaking_epoch, int) assert isinstance( prestaking_epoch, int )
def test_get_bad_blocks(setup_blockchain): def test_get_bad_blocks( setup_blockchain ):
# TODO: Remove skip when RPC is fixed # TODO: Remove skip when RPC is fixed
pytest.skip("Known error with hmyv2_getCurrentBadBlocks") pytest.skip( "Known error with hmyv2_getCurrentBadBlocks" )
bad_blocks = _test_blockchain_rpc(blockchain.get_bad_blocks) bad_blocks = _test_blockchain_rpc( blockchain.get_bad_blocks )
assert isinstance(bad_blocks, list) assert isinstance( bad_blocks, list )
def test_get_validator_keys(setup_blockchain): def test_get_validator_keys( setup_blockchain ):
keys = _test_blockchain_rpc(blockchain.get_validator_keys, test_epoch_number) keys = _test_blockchain_rpc(
assert isinstance(keys, list) blockchain.get_validator_keys,
assert len(keys) > 0 test_epoch_number
)
assert isinstance( keys, list )
assert len( keys ) > 0
def test_get_block_signers_keys(setup_blockchain): def test_get_block_signers_keys( setup_blockchain ):
keys = _test_blockchain_rpc(blockchain.get_block_signers_keys, test_block_number) keys = _test_blockchain_rpc(
assert isinstance(keys, list) blockchain.get_block_signers_keys,
assert len(keys) > 0 test_block_number
)
assert isinstance( keys, list )
assert len( keys ) > 0
def test_chain_id(setup_blockchain): def test_chain_id( setup_blockchain ):
chain_id = _test_blockchain_rpc(blockchain.chain_id) chain_id = _test_blockchain_rpc( blockchain.chain_id )
assert isinstance(chain_id, int) assert isinstance( chain_id, int )
def test_get_peer_info(setup_blockchain): def test_get_peer_info( setup_blockchain ):
peer_info = _test_blockchain_rpc(blockchain.get_peer_info) peer_info = _test_blockchain_rpc( blockchain.get_peer_info )
assert isinstance(peer_info, dict) assert isinstance( peer_info, dict )
def test_protocol_version(setup_blockchain): def test_protocol_version( setup_blockchain ):
protocol_version = _test_blockchain_rpc(blockchain.protocol_version) protocol_version = _test_blockchain_rpc( blockchain.protocol_version )
assert isinstance(protocol_version, int) assert isinstance( protocol_version, int )
def test_is_last_block(setup_blockchain): def test_is_last_block( setup_blockchain ):
is_last_block = _test_blockchain_rpc(blockchain.is_last_block, 0) is_last_block = _test_blockchain_rpc( blockchain.is_last_block, 0 )
assert isinstance(is_last_block, bool) assert isinstance( is_last_block, bool )
assert not is_last_block assert not is_last_block
def test_epoch_last_block(setup_blockchain): def test_epoch_last_block( setup_blockchain ):
epoch_last_block = _test_blockchain_rpc(blockchain.epoch_last_block, 0) epoch_last_block = _test_blockchain_rpc( blockchain.epoch_last_block, 0 )
assert isinstance(epoch_last_block, int) assert isinstance( epoch_last_block, int )
def test_get_circulating_supply(setup_blockchain): def test_get_circulating_supply( setup_blockchain ):
circulating_supply = _test_blockchain_rpc(blockchain.get_circulating_supply) circulating_supply = _test_blockchain_rpc(
assert isinstance(circulating_supply, str) blockchain.get_circulating_supply
)
assert isinstance( circulating_supply, str )
def test_get_total_supply(setup_blockchain): def test_get_total_supply( setup_blockchain ):
total_supply = _test_blockchain_rpc(blockchain.get_total_supply) total_supply = _test_blockchain_rpc( blockchain.get_total_supply )
assert isinstance(total_supply, str) or total_supply == None assert isinstance( total_supply, str ) or total_supply == None
def test_get_last_cross_links(setup_blockchain): def test_get_last_cross_links( setup_blockchain ):
last_cross_links = _test_blockchain_rpc(blockchain.get_last_cross_links) last_cross_links = _test_blockchain_rpc( blockchain.get_last_cross_links )
assert isinstance(last_cross_links, list) assert isinstance( last_cross_links, list )
def test_get_gas_price(setup_blockchain): def test_get_gas_price( setup_blockchain ):
gas_price = _test_blockchain_rpc(blockchain.get_gas_price) gas_price = _test_blockchain_rpc( blockchain.get_gas_price )
assert isinstance(gas_price, int) assert isinstance( gas_price, int )
def test_get_version(setup_blockchain): def test_get_version( setup_blockchain ):
version = _test_blockchain_rpc(blockchain.get_version) version = _test_blockchain_rpc( blockchain.get_version )
assert isinstance(version, int) assert isinstance( version, int )
def test_get_header_by_number(setup_blockchain): def test_get_header_by_number( setup_blockchain ):
header_pair = _test_blockchain_rpc(blockchain.get_header_by_number, 0) header_pair = _test_blockchain_rpc( blockchain.get_header_by_number, 0 )
assert isinstance(header_pair, dict) assert isinstance( header_pair, dict )
def test_get_block_staking_transaction_count_by_number(setup_blockchain): def test_get_block_staking_transaction_count_by_number( setup_blockchain ):
tx_count = _test_blockchain_rpc( tx_count = _test_blockchain_rpc(
blockchain.get_block_staking_transaction_count_by_number, test_block_number blockchain.get_block_staking_transaction_count_by_number,
test_block_number
) )
assert isinstance(tx_count, int) assert isinstance( tx_count, int )
def test_get_block_staking_transaction_count_by_hash(setup_blockchain): def test_get_block_staking_transaction_count_by_hash( setup_blockchain ):
if not test_block_hash: if not test_block_hash:
pytest.skip("Failed to get reference block hash") pytest.skip( "Failed to get reference block hash" )
tx_count = _test_blockchain_rpc( tx_count = _test_blockchain_rpc(
blockchain.get_block_staking_transaction_count_by_hash, test_block_hash blockchain.get_block_staking_transaction_count_by_hash,
test_block_hash
) )
assert isinstance(tx_count, int) assert isinstance( tx_count, int )
def test_is_block_signer(setup_blockchain): def test_is_block_signer( setup_blockchain ):
is_signer = _test_blockchain_rpc( is_signer = _test_blockchain_rpc(
blockchain.is_block_signer, test_block_number, address blockchain.is_block_signer,
test_block_number,
address
) )
assert isinstance(is_signer, bool) assert isinstance( is_signer, bool )
def test_get_signed_blocks(setup_blockchain): def test_get_signed_blocks( setup_blockchain ):
signed_blocks = _test_blockchain_rpc(blockchain.get_signed_blocks, address) signed_blocks = _test_blockchain_rpc(
assert isinstance(signed_blocks, int) blockchain.get_signed_blocks,
address
)
assert isinstance( signed_blocks, int )
def test_in_sync(setup_blockchain): def test_in_sync( setup_blockchain ):
in_sync = _test_blockchain_rpc(blockchain.in_sync) in_sync = _test_blockchain_rpc( blockchain.in_sync )
assert isinstance(in_sync, bool) assert isinstance( in_sync, bool )
def test_beacon_in_sync(setup_blockchain): def test_beacon_in_sync( setup_blockchain ):
beacon_in_sync = _test_blockchain_rpc(blockchain.beacon_in_sync) beacon_in_sync = _test_blockchain_rpc( blockchain.beacon_in_sync )
assert isinstance(beacon_in_sync, bool) assert isinstance( beacon_in_sync, bool )
def test_errors(): def test_errors():
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.chain_id(fake_shard) blockchain.chain_id( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.get_node_metadata(fake_shard) blockchain.get_node_metadata( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.get_peer_info(fake_shard) blockchain.get_peer_info( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.protocol_version(fake_shard) blockchain.protocol_version( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.get_shard(fake_shard) blockchain.get_shard( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.get_staking_epoch(fake_shard) blockchain.get_staking_epoch( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.get_prestaking_epoch(fake_shard) blockchain.get_prestaking_epoch( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.get_sharding_structure(fake_shard) blockchain.get_sharding_structure( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.get_leader_address(fake_shard) blockchain.get_leader_address( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.is_last_block(0, fake_shard) blockchain.is_last_block( 0, fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.epoch_last_block(0, fake_shard) blockchain.epoch_last_block( 0, fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.get_circulating_supply(fake_shard) blockchain.get_circulating_supply( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.get_total_supply(fake_shard) blockchain.get_total_supply( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.get_block_number(fake_shard) blockchain.get_block_number( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.get_current_epoch(fake_shard) blockchain.get_current_epoch( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.get_last_cross_links(fake_shard) blockchain.get_last_cross_links( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.get_gas_price(fake_shard) blockchain.get_gas_price( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.get_num_peers(fake_shard) blockchain.get_num_peers( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.get_version(fake_shard) blockchain.get_version( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.get_latest_header(fake_shard) blockchain.get_latest_header( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.get_header_by_number(0, fake_shard) blockchain.get_header_by_number( 0, fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.get_latest_chain_headers(fake_shard) blockchain.get_latest_chain_headers( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.get_block_by_number(0, endpoint=fake_shard) blockchain.get_block_by_number( 0, endpoint = fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.get_block_by_hash("", endpoint=fake_shard) blockchain.get_block_by_hash( "", endpoint = fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.get_block_transaction_count_by_number(0, fake_shard) blockchain.get_block_transaction_count_by_number( 0, fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.get_block_transaction_count_by_hash("", fake_shard) blockchain.get_block_transaction_count_by_hash( "", fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
blockchain.get_block_staking_transaction_count_by_number(0, fake_shard) blockchain.get_block_staking_transaction_count_by_number(
with pytest.raises(exceptions.RPCError): 0,
blockchain.get_block_staking_transaction_count_by_hash("", fake_shard) fake_shard
with pytest.raises(exceptions.RPCError): )
blockchain.get_blocks(0, 1, endpoint=fake_shard) with pytest.raises( exceptions.RPCError ):
with pytest.raises(exceptions.RPCError): blockchain.get_block_staking_transaction_count_by_hash( "", fake_shard )
blockchain.get_block_signers(0, fake_shard) with pytest.raises( exceptions.RPCError ):
with pytest.raises(exceptions.RPCError): blockchain.get_blocks( 0, 1, endpoint = fake_shard )
blockchain.get_block_signers_keys(0, fake_shard) with pytest.raises( exceptions.RPCError ):
with pytest.raises(exceptions.RPCError): blockchain.get_block_signers( 0, fake_shard )
blockchain.is_block_signer(0, "", fake_shard) with pytest.raises( exceptions.RPCError ):
with pytest.raises(exceptions.RPCError): blockchain.get_block_signers_keys( 0, fake_shard )
blockchain.get_signed_blocks("", fake_shard) with pytest.raises( exceptions.RPCError ):
with pytest.raises(exceptions.RPCError): blockchain.is_block_signer( 0, "", fake_shard )
blockchain.get_validators(1, fake_shard) with pytest.raises( exceptions.RPCError ):
with pytest.raises(exceptions.RPCError): blockchain.get_signed_blocks( "", fake_shard )
blockchain.get_validator_keys(0, fake_shard) with pytest.raises( exceptions.RPCError ):
with pytest.raises(exceptions.RPCError): blockchain.get_validators( 1, fake_shard )
blockchain.in_sync(fake_shard) with pytest.raises( exceptions.RPCError ):
with pytest.raises(exceptions.RPCError): blockchain.get_validator_keys( 0, fake_shard )
blockchain.beacon_in_sync(fake_shard) with pytest.raises( exceptions.RPCError ):
blockchain.in_sync( fake_shard )
with pytest.raises( exceptions.RPCError ):
blockchain.beacon_in_sync( fake_shard )

@ -12,69 +12,75 @@ contract_address = None
fake_shard = "http://example.com" fake_shard = "http://example.com"
def _test_contract_rpc(fn, *args, **kwargs): def _test_contract_rpc( fn, *args, **kwargs ):
if not callable(fn): if not callable( fn ):
pytest.fail(f"Invalid function: {fn}") pytest.fail( f"Invalid function: {fn}" )
try: try:
response = fn(*args, **kwargs) response = fn( *args, **kwargs )
except Exception as e: except Exception as e:
if isinstance( if isinstance( e,
e, exceptions.RPCError exceptions.RPCError
) and "does not exist/is not available" in str(e): ) and "does not exist/is not available" in str( e ):
pytest.skip(f"{str(e)}") pytest.skip( f"{str(e)}" )
elif isinstance(e, exceptions.RPCError) and "estimateGas returned" in str(e): elif isinstance( e,
pytest.skip(f"{str(e)}") exceptions.RPCError
pytest.fail(f"Unexpected error: {e.__class__} {e}") ) and "estimateGas returned" in str( e ):
pytest.skip( f"{str(e)}" )
pytest.fail( f"Unexpected error: {e.__class__} {e}" )
return response return response
def test_get_contract_address_from_hash(setup_blockchain): def test_get_contract_address_from_hash( setup_blockchain ):
global contract_address global contract_address
contract_address = _test_contract_rpc( contract_address = _test_contract_rpc(
contract.get_contract_address_from_hash, contract_tx_hash contract.get_contract_address_from_hash,
contract_tx_hash
) )
assert isinstance(contract_address, str) assert isinstance( contract_address, str )
def test_call(setup_blockchain): def test_call( setup_blockchain ):
if not contract_address: if not contract_address:
pytest.skip("Contract address not loaded yet") pytest.skip( "Contract address not loaded yet" )
called = _test_contract_rpc(contract.call, contract_address, "latest") called = _test_contract_rpc( contract.call, contract_address, "latest" )
assert isinstance(called, str) and called.startswith("0x") assert isinstance( called, str ) and called.startswith( "0x" )
def test_estimate_gas(setup_blockchain): def test_estimate_gas( setup_blockchain ):
if not contract_address: if not contract_address:
pytest.skip("Contract address not loaded yet") pytest.skip( "Contract address not loaded yet" )
gas = _test_contract_rpc(contract.estimate_gas, contract_address) gas = _test_contract_rpc( contract.estimate_gas, contract_address )
assert isinstance(gas, int) assert isinstance( gas, int )
def test_get_code(setup_blockchain): def test_get_code( setup_blockchain ):
if not contract_address: if not contract_address:
pytest.skip("Contract address not loaded yet") pytest.skip( "Contract address not loaded yet" )
code = _test_contract_rpc(contract.get_code, contract_address, "latest") code = _test_contract_rpc( contract.get_code, contract_address, "latest" )
assert code == contract_code assert code == contract_code
def test_get_storage_at(setup_blockchain): def test_get_storage_at( setup_blockchain ):
if not contract_address: if not contract_address:
pytest.skip("Contract address not loaded yet") pytest.skip( "Contract address not loaded yet" )
storage = _test_contract_rpc( storage = _test_contract_rpc(
contract.get_storage_at, contract_address, "0x0", "latest" contract.get_storage_at,
contract_address,
"0x0",
"latest"
) )
assert isinstance(storage, str) and storage.startswith("0x") assert isinstance( storage, str ) and storage.startswith( "0x" )
def test_errors(): def test_errors():
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
contract.get_contract_address_from_hash("", fake_shard) contract.get_contract_address_from_hash( "", fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
contract.call("", "", endpoint=fake_shard) contract.call( "", "", endpoint = fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
contract.estimate_gas("", endpoint=fake_shard) contract.estimate_gas( "", endpoint = fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
contract.get_code("", "latest", endpoint=fake_shard) contract.get_code( "", "latest", endpoint = fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
contract.get_storage_at("", 1, "latest", endpoint=fake_shard) contract.get_storage_at( "", 1, "latest", endpoint = fake_shard )

@ -1,5 +1,4 @@
from pyhmy import signing from pyhmy import signing
""" """
Test signature source (node.js) Test signature source (node.js)
import { Transaction, RLPSign, TxStatus } from '@harmony-js/transaction'; import { Transaction, RLPSign, TxStatus } from '@harmony-js/transaction';
@ -43,8 +42,8 @@ def test_eth_transaction():
"4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48", "4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48",
) )
assert ( assert (
signed_tx.rawTransaction.hex() signed_tx.rawTransaction.hex() ==
== "0xf85d0201649414791697260e4c9a71f18484c9f997b308e5932505801ca0b364f4296bfd3231889d1b9ac94c68abbcb8ee6a6c7a5fa412ac82b5b7b0d5d1a02233864842ab28ee4f99c207940a867b0f8534ca362836190792816b48dde3b1" "0xf85d0201649414791697260e4c9a71f18484c9f997b308e5932505801ca0b364f4296bfd3231889d1b9ac94c68abbcb8ee6a6c7a5fa412ac82b5b7b0d5d1a02233864842ab28ee4f99c207940a867b0f8534ca362836190792816b48dde3b1"
) )
@ -96,6 +95,6 @@ def test_hmy_transaction():
"4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48", "4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48",
) )
assert ( assert (
signed_tx.rawTransaction.hex() signed_tx.rawTransaction.hex() ==
== "0xf85f02016480019414791697260e4c9a71f18484c9f997b308e59325058026a02a203357ca6d7cdec981ad3d3692ad2c9e24536a9b6e7b486ce2f94f28c7563ea010d38cd0312a153af0aa7d8cd986040c36118bba373cb94e3e86fd4aedce904d" "0xf85f02016480019414791697260e4c9a71f18484c9f997b308e59325058026a02a203357ca6d7cdec981ad3d3692ad2c9e24536a9b6e7b486ce2f94f28c7563ea010d38cd0312a153af0aa7d8cd986040c36118bba373cb94e3e86fd4aedce904d"
) )

@ -5,222 +5,244 @@ from pyhmy import staking
from pyhmy.rpc import exceptions from pyhmy.rpc import exceptions
explorer_endpoint = "http://localhost:9700" explorer_endpoint = "http://localhost:9700"
test_validator_address = "one155jp2y76nazx8uw5sa94fr0m4s5aj8e5xm6fu3" test_validator_address = "one155jp2y76nazx8uw5sa94fr0m4s5aj8e5xm6fu3"
fake_shard = "http://example.com" fake_shard = "http://example.com"
def _test_staking_rpc(fn, *args, **kwargs): def _test_staking_rpc( fn, *args, **kwargs ):
if not callable(fn): if not callable( fn ):
pytest.fail(f"Invalid function: {fn}") pytest.fail( f"Invalid function: {fn}" )
try: try:
response = fn(*args, **kwargs) response = fn( *args, **kwargs )
except Exception as e: except Exception as e:
if isinstance( if isinstance( e,
e, exceptions.RPCError exceptions.RPCError
) and "does not exist/is not available" in str(e): ) and "does not exist/is not available" in str( e ):
pytest.skip(f"{str(e)}") pytest.skip( f"{str(e)}" )
pytest.fail(f"Unexpected error: {e.__class__} {e}") pytest.fail( f"Unexpected error: {e.__class__} {e}" )
return response return response
def test_get_all_validator_addresses(setup_blockchain): def test_get_all_validator_addresses( setup_blockchain ):
validator_addresses = _test_staking_rpc(staking.get_all_validator_addresses) validator_addresses = _test_staking_rpc(
assert isinstance(validator_addresses, list) staking.get_all_validator_addresses
assert len(validator_addresses) > 0 )
assert isinstance( validator_addresses, list )
assert len( validator_addresses ) > 0
assert test_validator_address in validator_addresses assert test_validator_address in validator_addresses
def test_get_validator_information(setup_blockchain): def test_get_validator_information( setup_blockchain ):
info = _test_staking_rpc(staking.get_validator_information, test_validator_address) info = _test_staking_rpc(
assert isinstance(info, dict) staking.get_validator_information,
test_validator_address
)
assert isinstance( info, dict )
def test_get_all_validator_information(setup_blockchain): def test_get_all_validator_information( setup_blockchain ):
all_validator_information = _test_staking_rpc(staking.get_all_validator_information) all_validator_information = _test_staking_rpc(
assert isinstance(all_validator_information, list) staking.get_all_validator_information
assert len(all_validator_information) > 0 )
assert isinstance( all_validator_information, list )
assert len( all_validator_information ) > 0
def test_get_delegations_by_delegator(setup_blockchain): def test_get_delegations_by_delegator( setup_blockchain ):
delegations = _test_staking_rpc( delegations = _test_staking_rpc(
staking.get_delegations_by_delegator, test_validator_address staking.get_delegations_by_delegator,
test_validator_address
) )
assert isinstance(delegations, list) assert isinstance( delegations, list )
assert len(delegations) > 0 assert len( delegations ) > 0
def test_get_delegations_by_validator(setup_blockchain): def test_get_delegations_by_validator( setup_blockchain ):
delegations = _test_staking_rpc( delegations = _test_staking_rpc(
staking.get_delegations_by_validator, test_validator_address staking.get_delegations_by_validator,
test_validator_address
) )
assert isinstance(delegations, list) assert isinstance( delegations, list )
assert len(delegations) > 0 assert len( delegations ) > 0
def test_get_current_utility_metrics(setup_blockchain): def test_get_current_utility_metrics( setup_blockchain ):
metrics = _test_staking_rpc(staking.get_current_utility_metrics) metrics = _test_staking_rpc( staking.get_current_utility_metrics )
assert isinstance(metrics, dict) assert isinstance( metrics, dict )
def test_get_staking_network_info(setup_blockchain): def test_get_staking_network_info( setup_blockchain ):
info = _test_staking_rpc(staking.get_staking_network_info) info = _test_staking_rpc( staking.get_staking_network_info )
assert isinstance(info, dict) assert isinstance( info, dict )
def test_get_super_committees(setup_blockchain): def test_get_super_committees( setup_blockchain ):
committee = _test_staking_rpc(staking.get_super_committees) committee = _test_staking_rpc( staking.get_super_committees )
assert isinstance(committee, dict) assert isinstance( committee, dict )
def test_get_raw_median_stake_snapshot(setup_blockchain): def test_get_raw_median_stake_snapshot( setup_blockchain ):
median_stake = _test_staking_rpc(staking.get_raw_median_stake_snapshot) median_stake = _test_staking_rpc( staking.get_raw_median_stake_snapshot )
assert isinstance(median_stake, dict) assert isinstance( median_stake, dict )
def test_get_validator_information_by_block(setup_blockchain): def test_get_validator_information_by_block( setup_blockchain ):
# Apparently validator information not created until block after create-validator transaction is accepted, so +1 block # Apparently validator information not created until block after create-validator transaction is accepted, so +1 block
info = _test_staking_rpc( info = _test_staking_rpc(
staking.get_validator_information_by_block_number, staking.get_validator_information_by_block_number,
test_validator_address, test_validator_address,
"latest", "latest",
endpoint=explorer_endpoint, endpoint = explorer_endpoint,
) )
assert isinstance(info, dict) assert isinstance( info, dict )
def test_get_validator_information_by_block(setup_blockchain): def test_get_validator_information_by_block( setup_blockchain ):
# Apparently validator information not created until block after create-validator transaction is accepted, so +1 block # Apparently validator information not created until block after create-validator transaction is accepted, so +1 block
info = _test_staking_rpc( info = _test_staking_rpc(
staking.get_all_validator_information_by_block_number, staking.get_all_validator_information_by_block_number,
"latest", "latest",
endpoint=explorer_endpoint, endpoint = explorer_endpoint,
) )
assert isinstance(info, list) assert isinstance( info, list )
def test_get_delegations_by_delegator_by_block(setup_blockchain): def test_get_delegations_by_delegator_by_block( setup_blockchain ):
delegations = _test_staking_rpc( delegations = _test_staking_rpc(
staking.get_delegations_by_delegator_by_block_number, staking.get_delegations_by_delegator_by_block_number,
test_validator_address, test_validator_address,
"latest", "latest",
endpoint=explorer_endpoint, endpoint = explorer_endpoint,
) )
assert isinstance(delegations, list) assert isinstance( delegations, list )
def test_get_elected_validator_addresses(setup_blockchain): def test_get_elected_validator_addresses( setup_blockchain ):
validator_addresses = _test_staking_rpc(staking.get_elected_validator_addresses) validator_addresses = _test_staking_rpc(
assert isinstance(validator_addresses, list) staking.get_elected_validator_addresses
assert len(validator_addresses) > 0 )
assert isinstance( validator_addresses, list )
assert len( validator_addresses ) > 0
def test_get_validators(setup_blockchain): def test_get_validators( setup_blockchain ):
validators = _test_staking_rpc(staking.get_validators, 2) validators = _test_staking_rpc( staking.get_validators, 2 )
assert isinstance(validators, dict) assert isinstance( validators, dict )
assert len(validators["validators"]) > 0 assert len( validators[ "validators" ] ) > 0
def test_get_validator_keys(setup_blockchain): def test_get_validator_keys( setup_blockchain ):
validators = _test_staking_rpc(staking.get_validator_keys, 2) validators = _test_staking_rpc( staking.get_validator_keys, 2 )
assert isinstance(validators, list) assert isinstance( validators, list )
def test_get_validator_self_delegation(setup_blockchain): def test_get_validator_self_delegation( setup_blockchain ):
self_delegation = _test_staking_rpc( self_delegation = _test_staking_rpc(
staking.get_validator_self_delegation, test_validator_address staking.get_validator_self_delegation,
test_validator_address
) )
assert isinstance(self_delegation, int) assert isinstance( self_delegation, int )
assert self_delegation > 0 assert self_delegation > 0
def test_get_validator_total_delegation(setup_blockchain): def test_get_validator_total_delegation( setup_blockchain ):
total_delegation = _test_staking_rpc( total_delegation = _test_staking_rpc(
staking.get_validator_total_delegation, test_validator_address staking.get_validator_total_delegation,
test_validator_address
) )
assert isinstance(total_delegation, int) assert isinstance( total_delegation, int )
assert total_delegation > 0 assert total_delegation > 0
def test_get_all_delegation_information(setup_blockchain): def test_get_all_delegation_information( setup_blockchain ):
delegation_information = _test_staking_rpc( delegation_information = _test_staking_rpc(
staking.get_all_delegation_information, 0 staking.get_all_delegation_information,
0
) )
assert isinstance(delegation_information, list) assert isinstance( delegation_information, list )
assert len(delegation_information) > 0 assert len( delegation_information ) > 0
def test_get_delegation_by_delegator_and_validator(setup_blockchain): def test_get_delegation_by_delegator_and_validator( setup_blockchain ):
delegation_information = _test_staking_rpc( delegation_information = _test_staking_rpc(
staking.get_delegation_by_delegator_and_validator, staking.get_delegation_by_delegator_and_validator,
test_validator_address, test_validator_address,
test_validator_address, test_validator_address,
) )
assert isinstance(delegation_information, dict) assert isinstance( delegation_information, dict )
def test_get_available_redelegation_balance(setup_blockchain): def test_get_available_redelegation_balance( setup_blockchain ):
redelgation_balance = _test_staking_rpc( redelgation_balance = _test_staking_rpc(
staking.get_available_redelegation_balance, test_validator_address staking.get_available_redelegation_balance,
test_validator_address
) )
assert isinstance(redelgation_balance, int) assert isinstance( redelgation_balance, int )
assert redelgation_balance == 0 assert redelgation_balance == 0
def test_get_total_staking(setup_blockchain): def test_get_total_staking( setup_blockchain ):
total_staking = _test_staking_rpc(staking.get_total_staking) total_staking = _test_staking_rpc( staking.get_total_staking )
assert isinstance(total_staking, int) assert isinstance( total_staking, int )
if ( if (
staking.get_validator_information(test_validator_address, explorer_endpoint)[ staking.get_validator_information(
"active-status" test_validator_address,
] explorer_endpoint
== "active" )[ "active-status" ] == "active"
): ):
assert total_staking > 0 assert total_staking > 0
def test_errors(): def test_errors():
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
staking.get_all_validator_addresses(fake_shard) staking.get_all_validator_addresses( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
staking.get_validator_information("", fake_shard) staking.get_validator_information( "", fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
staking.get_elected_validator_addresses(fake_shard) staking.get_elected_validator_addresses( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
staking.get_validators(1, fake_shard) staking.get_validators( 1, fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
staking.get_validator_keys(1, fake_shard) staking.get_validator_keys( 1, fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
staking.get_validator_information_by_block_number("", 1, fake_shard) staking.get_validator_information_by_block_number( "", 1, fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
staking.get_all_validator_information(-1, fake_shard) staking.get_all_validator_information( -1, fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
staking.get_validator_self_delegation("", fake_shard) staking.get_validator_self_delegation( "", fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
staking.get_validator_total_delegation("", fake_shard) staking.get_validator_total_delegation( "", fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
staking.get_all_validator_information_by_block_number(1, 1, fake_shard) staking.get_all_validator_information_by_block_number(
with pytest.raises(exceptions.RPCError): 1,
staking.get_all_delegation_information(1, fake_shard) 1,
with pytest.raises(exceptions.RPCError): fake_shard
staking.get_delegations_by_delegator("", fake_shard) )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
staking.get_delegations_by_delegator_by_block_number("", 1, fake_shard) staking.get_all_delegation_information( 1, fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
staking.get_delegation_by_delegator_and_validator("", "", fake_shard) staking.get_delegations_by_delegator( "", fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
staking.get_available_redelegation_balance("", fake_shard) staking.get_delegations_by_delegator_by_block_number(
with pytest.raises(exceptions.RPCError): "",
staking.get_delegations_by_validator("", fake_shard) 1,
with pytest.raises(exceptions.RPCError): fake_shard
staking.get_current_utility_metrics(fake_shard) )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
staking.get_staking_network_info(fake_shard) staking.get_delegation_by_delegator_and_validator( "", "", fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
staking.get_super_committees(fake_shard) staking.get_available_redelegation_balance( "", fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
staking.get_total_staking(fake_shard) staking.get_delegations_by_validator( "", fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
staking.get_raw_median_stake_snapshot(fake_shard) staking.get_current_utility_metrics( fake_shard )
with pytest.raises( exceptions.RPCError ):
staking.get_staking_network_info( fake_shard )
with pytest.raises( exceptions.RPCError ):
staking.get_super_committees( fake_shard )
with pytest.raises( exceptions.RPCError ):
staking.get_total_staking( fake_shard )
with pytest.raises( exceptions.RPCError ):
staking.get_raw_median_stake_snapshot( fake_shard )

@ -5,7 +5,6 @@ from pyhmy.numbers import convert_one_to_atto
# other transactions (create/edit validator) are in test_validator.py # other transactions (create/edit validator) are in test_validator.py
# test_delegate is the same as test_undelegate (except the directive) so it has been omitted # test_delegate is the same as test_undelegate (except the directive) so it has been omitted
# staking transactions without a chain id have been omitted as well, since the node does not accept them anyway # staking transactions without a chain id have been omitted as well, since the node does not accept them anyway
""" """
let stakingTx let stakingTx
let stakeMsg3: CollectRewards = new CollectRewards( let stakeMsg3: CollectRewards = new CollectRewards(
@ -33,7 +32,6 @@ console.log(signed)
# } # }
# signed_tx = staking_signing.sign_staking_transaction(transaction_dict, '4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48') # signed_tx = staking_signing.sign_staking_transaction(transaction_dict, '4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48')
# assert signed_tx.rawTransaction.hex() == '0xf85a04d594ebcd16e8c1d8f493ba04e99a56474122d81a9c5823a0490e4ceb747563ba40da3e0db8a65133cf6f6ae4c48a24866cd6aa1f0d6c2414a06dbd51a67b35b5685e7b7420cba26e63b0e7d3c696fc6cb69d48e54fcad280e9' # assert signed_tx.rawTransaction.hex() == '0xf85a04d594ebcd16e8c1d8f493ba04e99a56474122d81a9c5823a0490e4ceb747563ba40da3e0db8a65133cf6f6ae4c48a24866cd6aa1f0d6c2414a06dbd51a67b35b5685e7b7420cba26e63b0e7d3c696fc6cb69d48e54fcad280e9'
""" """
let stakingTx let stakingTx
let stakeMsg3: CollectRewards = new CollectRewards( let stakeMsg3: CollectRewards = new CollectRewards(
@ -67,8 +65,8 @@ def test_collect_rewards_chain_id():
"4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48", "4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48",
) )
assert ( assert (
signed_tx.rawTransaction.hex() signed_tx.rawTransaction.hex() ==
== "0xf86504d594ebcd16e8c1d8f493ba04e99a56474122d81a9c5802880de0b6b3a76400006425a055d6c3c0d8e7a1e75152db361a2ed47f5ab54f6f19b0d8e549953dbdf13ba647a076e1367dfca38eae3bd0e8da296335acabbaeb87dc17e47ebe4942db29334099" "0xf86504d594ebcd16e8c1d8f493ba04e99a56474122d81a9c5802880de0b6b3a76400006425a055d6c3c0d8e7a1e75152db361a2ed47f5ab54f6f19b0d8e549953dbdf13ba647a076e1367dfca38eae3bd0e8da296335acabbaeb87dc17e47ebe4942db29334099"
) )
@ -100,7 +98,7 @@ def test_delegate():
"validatorAddress": "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9", "validatorAddress": "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9",
"amount": 5, "amount": 5,
"nonce": 2, "nonce": 2,
"gasPrice": int(convert_one_to_atto(1)), "gasPrice": int( convert_one_to_atto( 1 ) ),
"gasLimit": 100, "gasLimit": 100,
"chainId": 2, "chainId": 2,
} }
@ -109,6 +107,6 @@ def test_delegate():
"4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48", "4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48",
) )
assert ( assert (
signed_tx.rawTransaction.hex() signed_tx.rawTransaction.hex() ==
== "0xf87b02eb94ebcd16e8c1d8f493ba04e99a56474122d81a9c5894ebcd16e8c1d8f493ba04e99a56474122d81a9c580502880de0b6b3a76400006428a0c856fd483a989ca4db4b5257f6996729527828fb21ec13cc65f0bffe6c015ab1a05e9d3c92742e8cb7450bebdfb7ad277ccbfc9fa0719db0b12a715a0a173cadd6" "0xf87b02eb94ebcd16e8c1d8f493ba04e99a56474122d81a9c5894ebcd16e8c1d8f493ba04e99a56474122d81a9c580502880de0b6b3a76400006428a0c856fd483a989ca4db4b5257f6996729527828fb21ec13cc65f0bffe6c015ab1a05e9d3c92742e8cb7450bebdfb7ad277ccbfc9fa0719db0b12a715a0a173cadd6"
) )

@ -4,7 +4,6 @@ from pyhmy import transaction
from pyhmy.rpc import exceptions from pyhmy.rpc import exceptions
endpoint = "http://localhost:9500" endpoint = "http://localhost:9500"
endpoint_shard_one = "http://localhost:9502" endpoint_shard_one = "http://localhost:9502"
fake_shard = "http://example.com" fake_shard = "http://example.com"
@ -30,212 +29,238 @@ raw_stx = "0xf88302f494c9c6d47ee5f2e3e08d7367ad1a1373ba9dd1724194a5241513da9f446
raw_stx_hash = "0xe7d07ef6d9fca595a14ceb0ca917bece7bedb15efe662300e9334a32ac1da629" raw_stx_hash = "0xe7d07ef6d9fca595a14ceb0ca917bece7bedb15efe662300e9334a32ac1da629"
def _test_transaction_rpc(fn, *args, **kwargs): def _test_transaction_rpc( fn, *args, **kwargs ):
if not callable(fn): if not callable( fn ):
pytest.fail(f"Invalid function: {fn}") pytest.fail( f"Invalid function: {fn}" )
try: try:
response = fn(*args, **kwargs) response = fn( *args, **kwargs )
except Exception as e: except Exception as e:
if isinstance( if isinstance( e,
e, exceptions.RPCError exceptions.RPCError
) and "does not exist/is not available" in str(e): ) and "does not exist/is not available" in str( e ):
pytest.skip(f"{str(e)}") pytest.skip( f"{str(e)}" )
pytest.fail(f"Unexpected error: {e.__class__} {e}") pytest.fail( f"Unexpected error: {e.__class__} {e}" )
return response return response
def test_get_pending_transactions(setup_blockchain): def test_get_pending_transactions( setup_blockchain ):
pool = _test_transaction_rpc(transaction.get_pending_transactions) pool = _test_transaction_rpc( transaction.get_pending_transactions )
assert isinstance(pool, list) assert isinstance( pool, list )
def test_get_transaction_by_hash(setup_blockchain): def test_get_transaction_by_hash( setup_blockchain ):
tx = _test_transaction_rpc( tx = _test_transaction_rpc(
transaction.get_transaction_by_hash, tx_hash, endpoint=endpoint transaction.get_transaction_by_hash,
tx_hash,
endpoint = endpoint
) )
assert tx assert tx
assert isinstance(tx, dict) assert isinstance( tx, dict )
assert "blockNumber" in tx.keys() assert "blockNumber" in tx.keys()
assert "blockHash" in tx.keys() assert "blockHash" in tx.keys()
global tx_block_num global tx_block_num
tx_block_num = int(tx["blockNumber"]) tx_block_num = int( tx[ "blockNumber" ] )
global tx_block_hash global tx_block_hash
tx_block_hash = tx["blockHash"] tx_block_hash = tx[ "blockHash" ]
global tx_index global tx_index
tx_index = int(tx["transactionIndex"]) tx_index = int( tx[ "transactionIndex" ] )
def test_get_transaction_by_block_hash_and_index(setup_blockchain): def test_get_transaction_by_block_hash_and_index( setup_blockchain ):
if not tx_block_hash: if not tx_block_hash:
pytest.skip("Failed to get reference block hash") pytest.skip( "Failed to get reference block hash" )
tx = _test_transaction_rpc( tx = _test_transaction_rpc(
transaction.get_transaction_by_block_hash_and_index, transaction.get_transaction_by_block_hash_and_index,
tx_block_hash, tx_block_hash,
tx_index, tx_index,
endpoint=endpoint, endpoint = endpoint,
) )
assert tx assert tx
assert isinstance(tx, dict) assert isinstance( tx, dict )
def test_get_transaction_by_block_number_and_index(setup_blockchain): def test_get_transaction_by_block_number_and_index( setup_blockchain ):
if not tx_block_num: if not tx_block_num:
pytest.skip("Failed to get reference block num") pytest.skip( "Failed to get reference block num" )
tx = _test_transaction_rpc( tx = _test_transaction_rpc(
transaction.get_transaction_by_block_number_and_index, transaction.get_transaction_by_block_number_and_index,
tx_block_num, tx_block_num,
tx_index, tx_index,
endpoint=endpoint, endpoint = endpoint,
) )
assert tx assert tx
assert isinstance(tx, dict) assert isinstance( tx, dict )
def test_get_transaction_receipt(setup_blockchain): def test_get_transaction_receipt( setup_blockchain ):
tx_receipt = _test_transaction_rpc( tx_receipt = _test_transaction_rpc(
transaction.get_transaction_receipt, tx_hash, endpoint=endpoint transaction.get_transaction_receipt,
tx_hash,
endpoint = endpoint
) )
assert tx_receipt assert tx_receipt
assert isinstance(tx_receipt, dict) assert isinstance( tx_receipt, dict )
def test_get_transaction_error_sink(setup_blockchain): def test_get_transaction_error_sink( setup_blockchain ):
errors = _test_transaction_rpc(transaction.get_transaction_error_sink) errors = _test_transaction_rpc( transaction.get_transaction_error_sink )
assert isinstance(errors, list) assert isinstance( errors, list )
def test_send_and_confirm_raw_transaction(setup_blockchain): def test_send_and_confirm_raw_transaction( setup_blockchain ):
# Note: this test is not yet idempotent since the localnet will reject transactions which were previously finalized. # Note: this test is not yet idempotent since the localnet will reject transactions which were previously finalized.
test_tx = _test_transaction_rpc( test_tx = _test_transaction_rpc(
transaction.send_and_confirm_raw_transaction, raw_tx transaction.send_and_confirm_raw_transaction,
raw_tx
) )
assert isinstance(test_tx, dict) assert isinstance( test_tx, dict )
assert test_tx["hash"] == raw_tx_hash assert test_tx[ "hash" ] == raw_tx_hash
def test_get_pending_cx_receipts(setup_blockchain): def test_get_pending_cx_receipts( setup_blockchain ):
pending = _test_transaction_rpc(transaction.get_pending_cx_receipts) pending = _test_transaction_rpc( transaction.get_pending_cx_receipts )
assert isinstance(pending, list) assert isinstance( pending, list )
def test_get_cx_receipt_by_hash(setup_blockchain): def test_get_cx_receipt_by_hash( setup_blockchain ):
cx = _test_transaction_rpc( cx = _test_transaction_rpc(
transaction.get_cx_receipt_by_hash, cx_hash, endpoint_shard_one transaction.get_cx_receipt_by_hash,
cx_hash,
endpoint_shard_one
) )
assert cx assert cx
assert isinstance(cx, dict) assert isinstance( cx, dict )
def test_resend_cx_receipt(setup_blockchain): def test_resend_cx_receipt( setup_blockchain ):
sent = _test_transaction_rpc(transaction.resend_cx_receipt, cx_hash) sent = _test_transaction_rpc( transaction.resend_cx_receipt, cx_hash )
assert isinstance(sent, bool) assert isinstance( sent, bool )
assert sent assert sent
def test_get_staking_transaction_by_hash(setup_blockchain): def test_get_staking_transaction_by_hash( setup_blockchain ):
staking_tx = _test_transaction_rpc( staking_tx = _test_transaction_rpc(
transaction.get_staking_transaction_by_hash, stx_hash transaction.get_staking_transaction_by_hash,
stx_hash
) )
assert staking_tx assert staking_tx
assert isinstance(staking_tx, dict) assert isinstance( staking_tx, dict )
assert "blockNumber" in staking_tx.keys() assert "blockNumber" in staking_tx.keys()
assert "blockHash" in staking_tx.keys() assert "blockHash" in staking_tx.keys()
global stx_block_num global stx_block_num
stx_block_num = int(staking_tx["blockNumber"]) stx_block_num = int( staking_tx[ "blockNumber" ] )
global stx_block_hash global stx_block_hash
stx_block_hash = staking_tx["blockHash"] stx_block_hash = staking_tx[ "blockHash" ]
global stx_index global stx_index
stx_index = int(staking_tx["transactionIndex"]) stx_index = int( staking_tx[ "transactionIndex" ] )
def test_get_transaction_by_block_hash_and_index(setup_blockchain): def test_get_transaction_by_block_hash_and_index( setup_blockchain ):
if not stx_block_hash: if not stx_block_hash:
pytest.skip("Failed to get reference block hash") pytest.skip( "Failed to get reference block hash" )
stx = _test_transaction_rpc( stx = _test_transaction_rpc(
transaction.get_staking_transaction_by_block_hash_and_index, transaction.get_staking_transaction_by_block_hash_and_index,
stx_block_hash, stx_block_hash,
stx_index, stx_index,
) )
assert stx assert stx
assert isinstance(stx, dict) assert isinstance( stx, dict )
def test_get_transaction_by_block_number_and_index(setup_blockchain): def test_get_transaction_by_block_number_and_index( setup_blockchain ):
if not stx_block_num: if not stx_block_num:
pytest.skip("Failed to get reference block num") pytest.skip( "Failed to get reference block num" )
stx = _test_transaction_rpc( stx = _test_transaction_rpc(
transaction.get_staking_transaction_by_block_number_and_index, transaction.get_staking_transaction_by_block_number_and_index,
stx_block_num, stx_block_num,
stx_index, stx_index,
) )
assert stx assert stx
assert isinstance(stx, dict) assert isinstance( stx, dict )
def test_get_staking_transaction_error_sink(setup_blockchain): def test_get_staking_transaction_error_sink( setup_blockchain ):
errors = _test_transaction_rpc(transaction.get_staking_transaction_error_sink) errors = _test_transaction_rpc(
assert isinstance(errors, list) transaction.get_staking_transaction_error_sink
)
assert isinstance( errors, list )
def test_send_raw_staking_transaction(setup_blockchain): def test_send_raw_staking_transaction( setup_blockchain ):
test_stx = _test_transaction_rpc( test_stx = _test_transaction_rpc(
transaction.send_and_confirm_raw_staking_transaction, raw_stx, endpoint=endpoint transaction.send_and_confirm_raw_staking_transaction,
raw_stx,
endpoint = endpoint
) )
assert isinstance(test_stx, dict) assert isinstance( test_stx, dict )
assert test_stx["hash"] == raw_stx_hash assert test_stx[ "hash" ] == raw_stx_hash
def test_get_pool_stats(setup_blockchain): def test_get_pool_stats( setup_blockchain ):
test_pool_stats = _test_transaction_rpc( test_pool_stats = _test_transaction_rpc(
transaction.get_pool_stats, endpoint=endpoint transaction.get_pool_stats,
endpoint = endpoint
) )
assert isinstance(test_pool_stats, dict) assert isinstance( test_pool_stats, dict )
def test_get_pending_staking_transactions(setup_blockchain): def test_get_pending_staking_transactions( setup_blockchain ):
pending_staking_transactions = _test_transaction_rpc( pending_staking_transactions = _test_transaction_rpc(
transaction.get_pending_staking_transactions, endpoint=endpoint transaction.get_pending_staking_transactions,
endpoint = endpoint
) )
assert isinstance(pending_staking_transactions, list) assert isinstance( pending_staking_transactions, list )
def test_errors(): def test_errors():
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
transaction.get_pending_transactions(fake_shard) transaction.get_pending_transactions( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
transaction.get_transaction_error_sink(fake_shard) transaction.get_transaction_error_sink( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
transaction.get_pool_stats(fake_shard) transaction.get_pool_stats( fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
transaction.get_transaction_by_hash("", endpoint=fake_shard) transaction.get_transaction_by_hash( "", endpoint = fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
transaction.get_transaction_by_block_hash_and_index("", 1, endpoint=fake_shard) transaction.get_transaction_by_block_hash_and_index(
with pytest.raises(exceptions.RPCError): "",
transaction.get_transaction_by_block_number_and_index(1, 1, endpoint=fake_shard) 1,
with pytest.raises(exceptions.RPCError): endpoint = fake_shard
transaction.get_transaction_receipt("", endpoint=fake_shard) )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
transaction.send_raw_transaction("", endpoint=fake_shard) transaction.get_transaction_by_block_number_and_index(
with pytest.raises(exceptions.RPCError): 1,
transaction.get_pending_cx_receipts(fake_shard) 1,
with pytest.raises(exceptions.RPCError): endpoint = fake_shard
transaction.get_cx_receipt_by_hash("", endpoint=fake_shard) )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
transaction.resend_cx_receipt("", endpoint=fake_shard) transaction.get_transaction_receipt( "", endpoint = fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
transaction.get_staking_transaction_by_hash("", endpoint=fake_shard) transaction.send_raw_transaction( "", endpoint = fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
transaction.get_pending_cx_receipts( fake_shard )
with pytest.raises( exceptions.RPCError ):
transaction.get_cx_receipt_by_hash( "", endpoint = fake_shard )
with pytest.raises( exceptions.RPCError ):
transaction.resend_cx_receipt( "", endpoint = fake_shard )
with pytest.raises( exceptions.RPCError ):
transaction.get_staking_transaction_by_hash( "", endpoint = fake_shard )
with pytest.raises( exceptions.RPCError ):
transaction.get_staking_transaction_by_block_hash_and_index( transaction.get_staking_transaction_by_block_hash_and_index(
"", 1, endpoint=fake_shard "",
1,
endpoint = fake_shard
) )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
transaction.get_staking_transaction_by_block_number_and_index( transaction.get_staking_transaction_by_block_number_and_index(
1, 1, endpoint=fake_shard 1,
1,
endpoint = fake_shard
) )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
transaction.get_staking_transaction_error_sink(endpoint=fake_shard) transaction.get_staking_transaction_error_sink( endpoint = fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
transaction.send_raw_staking_transaction("", endpoint=fake_shard) transaction.send_raw_staking_transaction( "", endpoint = fake_shard )
with pytest.raises(exceptions.RPCError): with pytest.raises( exceptions.RPCError ):
transaction.get_pending_staking_transactions(endpoint=fake_shard) transaction.get_pending_staking_transactions( endpoint = fake_shard )

@ -14,25 +14,25 @@ test_validator_object = None
test_validator_loaded = False test_validator_loaded = False
def test_instantiate_validator(setup_blockchain): def test_instantiate_validator( setup_blockchain ):
global test_validator_object global test_validator_object
test_validator_object = validator.Validator( test_validator_object = validator.Validator(
"one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9" "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9"
) )
assert isinstance(test_validator_object, validator.Validator) assert isinstance( test_validator_object, validator.Validator )
def test_load_validator(setup_blockchain): def test_load_validator( setup_blockchain ):
if not test_validator_object: if not test_validator_object:
pytest.skip("Validator not instantiated yet") pytest.skip( "Validator not instantiated yet" )
info = { info = {
"name": "Alice", "name": "Alice",
"identity": "alice", "identity": "alice",
"website": "alice.harmony.one", "website": "alice.harmony.one",
"details": "Don't mess with me!!!", "details": "Don't mess with me!!!",
"security-contact": "Bob", "security-contact": "Bob",
"min-self-delegation": convert_one_to_atto(10000), "min-self-delegation": convert_one_to_atto( 10000 ),
"amount": convert_one_to_atto(10001), "amount": convert_one_to_atto( 10001 ),
"max-rate": "0.9", "max-rate": "0.9",
"max-change-rate": "0.05", "max-change-rate": "0.05",
"rate": "0.01", "rate": "0.01",
@ -42,9 +42,9 @@ def test_load_validator(setup_blockchain):
"bls-key-sigs": [ "bls-key-sigs": [
"0x68f800b6adf657b674903e04708060912b893b7c7b500788808247550ab3e186e56a44ebf3ca488f8ed1a42f6cef3a04bd5d2b2b7eb5a767848d3135b362e668ce6bba42c7b9d5666d8e3a83be707b5708e722c58939fe9b07c170f3b7062414" "0x68f800b6adf657b674903e04708060912b893b7c7b500788808247550ab3e186e56a44ebf3ca488f8ed1a42f6cef3a04bd5d2b2b7eb5a767848d3135b362e668ce6bba42c7b9d5666d8e3a83be707b5708e722c58939fe9b07c170f3b7062414"
], ],
"max-total-delegation": convert_one_to_atto(40000), "max-total-delegation": convert_one_to_atto( 40000 ),
} }
test_validator_object.load(info) test_validator_object.load( info )
global test_validator_loaded global test_validator_loaded
test_validator_loaded = True test_validator_loaded = True
@ -78,19 +78,19 @@ console.log(signed)
""" """
def test_create_validator_sign(setup_blockchain): def test_create_validator_sign( setup_blockchain ):
if not (test_validator_object or test_validator_loaded): if not ( test_validator_object or test_validator_loaded ):
pytest.skip("Validator not instantiated yet") pytest.skip( "Validator not instantiated yet" )
signed_hash = test_validator_object.sign_create_validator_transaction( signed_hash = test_validator_object.sign_create_validator_transaction(
2, 2,
int(convert_one_to_atto(1)), int( convert_one_to_atto( 1 ) ),
100, 100,
"4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48", "4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48",
2, 2,
).rawTransaction.hex() ).rawTransaction.hex()
assert ( assert (
signed_hash signed_hash ==
== "0xf9017580f9012394ebcd16e8c1d8f493ba04e99a56474122d81a9c58f83885416c69636585616c69636591616c6963652e6861726d6f6e792e6f6e6583426f6295446f6e2774206d6573732077697468206d65212121dcc8872386f26fc10000c9880c7d713b49da0000c887b1a2bc2ec500008a021e19e0c9bab24000008a0878678326eac9000000f1b030b2c38b1316da91e068ac3bd8751c0901ef6c02a1d58bc712104918302c6ed03d5894671d0c816dad2b4d303320f202f862b86068f800b6adf657b674903e04708060912b893b7c7b500788808247550ab3e186e56a44ebf3ca488f8ed1a42f6cef3a04bd5d2b2b7eb5a767848d3135b362e668ce6bba42c7b9d5666d8e3a83be707b5708e722c58939fe9b07c170f3b70624148a021e27c1806e59a4000002880de0b6b3a76400006428a0c6c7e62f02331df0afd4699ec514a2fc4548c920d77ad74d98caeec8c924c09aa02b27b999a724b1d341d6bbb0e877611d0047542cb7e380f9a6a272d204b450cd" "0xf9017580f9012394ebcd16e8c1d8f493ba04e99a56474122d81a9c58f83885416c69636585616c69636591616c6963652e6861726d6f6e792e6f6e6583426f6295446f6e2774206d6573732077697468206d65212121dcc8872386f26fc10000c9880c7d713b49da0000c887b1a2bc2ec500008a021e19e0c9bab24000008a0878678326eac9000000f1b030b2c38b1316da91e068ac3bd8751c0901ef6c02a1d58bc712104918302c6ed03d5894671d0c816dad2b4d303320f202f862b86068f800b6adf657b674903e04708060912b893b7c7b500788808247550ab3e186e56a44ebf3ca488f8ed1a42f6cef3a04bd5d2b2b7eb5a767848d3135b362e668ce6bba42c7b9d5666d8e3a83be707b5708e722c58939fe9b07c170f3b70624148a021e27c1806e59a4000002880de0b6b3a76400006428a0c6c7e62f02331df0afd4699ec514a2fc4548c920d77ad74d98caeec8c924c09aa02b27b999a724b1d341d6bbb0e877611d0047542cb7e380f9a6a272d204b450cd"
) )
@ -137,9 +137,9 @@ console.log(signed)
""" """
def test_edit_validator_sign(setup_blockchain): def test_edit_validator_sign( setup_blockchain ):
if not (test_validator_object or test_validator_loaded): if not ( test_validator_object or test_validator_loaded ):
pytest.skip("Validator not instantiated yet") pytest.skip( "Validator not instantiated yet" )
signed_hash = test_validator_object.sign_edit_validator_transaction( signed_hash = test_validator_object.sign_edit_validator_transaction(
2, 2,
int(convert_one_to_atto(1)), int(convert_one_to_atto(1)),
@ -152,67 +152,73 @@ def test_edit_validator_sign(setup_blockchain):
2, 2,
).rawTransaction.hex() ).rawTransaction.hex()
assert ( assert (
signed_hash signed_hash ==
== "0xf9012101f8d094ebcd16e8c1d8f493ba04e99a56474122d81a9c58f83885416c69636585616c69636591616c6963652e6861726d6f6e792e6f6e6583426f6295446f6e2774206d6573732077697468206d65212121c887d529ae9e8600008a021e19e0c9bab24000008a0878678326eac9000000b0b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608612b0b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b6224760861102880de0b6b3a76400006428a0782ed9f909b5c68eb050a917a274d9187a4c8f13799f21ba351bdcb11290c6c7a00a3b4ec8431290565acbbbb8231cafe32ed7c0b6211e7cf570b459cb733e0995" "0xf9012101f8d094ebcd16e8c1d8f493ba04e99a56474122d81a9c58f83885416c69636585616c69636591616c6963652e6861726d6f6e792e6f6e6583426f6295446f6e2774206d6573732077697468206d65212121c887d529ae9e8600008a021e19e0c9bab24000008a0878678326eac9000000b0b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608612b0b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b6224760861102880de0b6b3a76400006428a0782ed9f909b5c68eb050a917a274d9187a4c8f13799f21ba351bdcb11290c6c7a00a3b4ec8431290565acbbbb8231cafe32ed7c0b6211e7cf570b459cb733e0995"
) )
def test_invalid_validator(setup_blockchain): def test_invalid_validator( setup_blockchain ):
if not (test_validator_object or test_validator_loaded): if not ( test_validator_object or test_validator_loaded ):
pytest.skip("Validator not instantiated yet") pytest.skip( "Validator not instantiated yet" )
with pytest.raises(InvalidValidatorError): with pytest.raises( InvalidValidatorError ):
info = { info = {
"name": "Alice", "name": "Alice",
} }
test_validator_object.load(info) test_validator_object.load( info )
with pytest.raises(InvalidValidatorError): with pytest.raises( InvalidValidatorError ):
test_validator_object.set_name("a" * 141) test_validator_object.set_name( "a" * 141 )
with pytest.raises(InvalidValidatorError): with pytest.raises( InvalidValidatorError ):
test_validator_object.set_identity("a" * 141) test_validator_object.set_identity( "a" * 141 )
with pytest.raises(InvalidValidatorError): with pytest.raises( InvalidValidatorError ):
test_validator_object.set_website("a" * 141) test_validator_object.set_website( "a" * 141 )
with pytest.raises(InvalidValidatorError): with pytest.raises( InvalidValidatorError ):
test_validator_object.set_security_contact("a" * 141) test_validator_object.set_security_contact( "a" * 141 )
with pytest.raises(InvalidValidatorError): with pytest.raises( InvalidValidatorError ):
test_validator_object.set_details("a" * 281) test_validator_object.set_details( "a" * 281 )
with pytest.raises(InvalidValidatorError): with pytest.raises( InvalidValidatorError ):
test_validator_object.set_min_self_delegation(1) test_validator_object.set_min_self_delegation( 1 )
with pytest.raises(InvalidValidatorError): with pytest.raises( InvalidValidatorError ):
test_validator_object.set_max_total_delegation(1) test_validator_object.set_max_total_delegation( 1 )
with pytest.raises(InvalidValidatorError): with pytest.raises( InvalidValidatorError ):
test_validator_object.set_amount(1) test_validator_object.set_amount( 1 )
with pytest.raises(InvalidValidatorError): with pytest.raises( InvalidValidatorError ):
test_validator_object.set_max_rate("2.0") test_validator_object.set_max_rate( "2.0" )
with pytest.raises(InvalidValidatorError): with pytest.raises( InvalidValidatorError ):
test_validator_object.set_max_change_rate("-2.0") test_validator_object.set_max_change_rate( "-2.0" )
with pytest.raises(InvalidValidatorError): with pytest.raises( InvalidValidatorError ):
test_validator_object.set_rate("-2.0") test_validator_object.set_rate( "-2.0" )
def test_validator_getters(setup_blockchain): def test_validator_getters( setup_blockchain ):
if not (test_validator_object or test_validator_loaded): if not ( test_validator_object or test_validator_loaded ):
pytest.skip("Validator not instantiated yet") pytest.skip( "Validator not instantiated yet" )
assert ( assert (
test_validator_object.get_address() test_validator_object.get_address() ==
== "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9" "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9"
) )
assert test_validator_object.add_bls_key("5") assert test_validator_object.add_bls_key( "5" )
assert test_validator_object.remove_bls_key("5") assert test_validator_object.remove_bls_key( "5" )
assert test_validator_object.get_name() == "Alice" assert test_validator_object.get_name() == "Alice"
assert test_validator_object.get_identity() == "alice" assert test_validator_object.get_identity() == "alice"
assert test_validator_object.get_website() == "alice.harmony.one" assert test_validator_object.get_website() == "alice.harmony.one"
assert test_validator_object.get_security_contact() == "Bob" assert test_validator_object.get_security_contact() == "Bob"
assert test_validator_object.get_details() == "Don't mess with me!!!" assert test_validator_object.get_details() == "Don't mess with me!!!"
assert isinstance(test_validator_object.get_min_self_delegation(), Decimal) assert isinstance(
assert isinstance(test_validator_object.get_max_total_delegation(), Decimal) test_validator_object.get_min_self_delegation(),
assert isinstance(test_validator_object.get_amount(), Decimal) Decimal
assert isinstance(test_validator_object.get_max_rate(), Decimal) )
assert isinstance(test_validator_object.get_max_change_rate(), Decimal) assert isinstance(
assert isinstance(test_validator_object.get_rate(), Decimal) test_validator_object.get_max_total_delegation(),
assert len(test_validator_object.get_bls_keys()) > 0 Decimal
)
assert isinstance( test_validator_object.get_amount(), Decimal )
assert isinstance( test_validator_object.get_max_rate(), Decimal )
assert isinstance( test_validator_object.get_max_change_rate(), Decimal )
assert isinstance( test_validator_object.get_rate(), Decimal )
assert len( test_validator_object.get_bls_keys() ) > 0
def test_validator_load_from_blockchain(setup_blockchain): def test_validator_load_from_blockchain( setup_blockchain ):
test_validator_object2 = validator.Validator( test_validator_object2 = validator.Validator(
"one155jp2y76nazx8uw5sa94fr0m4s5aj8e5xm6fu3" "one155jp2y76nazx8uw5sa94fr0m4s5aj8e5xm6fu3"
) )

@ -12,59 +12,64 @@ from pyhmy import util
TEMP_DIR = "/tmp/pyhmy-testing/test-util" TEMP_DIR = "/tmp/pyhmy-testing/test-util"
@pytest.fixture(scope="session", autouse=True) @pytest.fixture( scope = "session", autouse = True )
def setup(): def setup():
shutil.rmtree(TEMP_DIR, ignore_errors=True) shutil.rmtree( TEMP_DIR, ignore_errors = True )
os.makedirs(TEMP_DIR, exist_ok=True) os.makedirs( TEMP_DIR, exist_ok = True )
def test_json_load(): def test_json_load():
dec = util.json_load("1.1", parse_float=decimal.Decimal) dec = util.json_load( "1.1", parse_float = decimal.Decimal )
assert isinstance(dec, decimal.Decimal) assert isinstance( dec, decimal.Decimal )
assert float(dec) == 1.1 assert float( dec ) == 1.1
ref_dict = {"test": "val", "arr": [1, 2, 3]} ref_dict = {
loaded_dict = util.json_load(json.dumps(ref_dict)) "test": "val",
assert str(ref_dict) == str(loaded_dict) "arr": [ 1,
2,
3 ]
}
loaded_dict = util.json_load( json.dumps( ref_dict ) )
assert str( ref_dict ) == str( loaded_dict )
def test_chain_id_to_int(): def test_chain_id_to_int():
assert util.chain_id_to_int(2) == 2 assert util.chain_id_to_int( 2 ) == 2
assert util.chain_id_to_int("HmyMainnet") == 1 assert util.chain_id_to_int( "HmyMainnet" ) == 1
def test_get_gopath(): def test_get_gopath():
assert isinstance(util.get_gopath(), str) assert isinstance( util.get_gopath(), str )
def test_get_goversion(): def test_get_goversion():
assert isinstance(util.get_goversion(), str) assert isinstance( util.get_goversion(), str )
def test_convert_one_to_hex(): def test_convert_one_to_hex():
assert ( assert (
util.convert_one_to_hex("0xebcd16e8c1d8f493ba04e99a56474122d81a9c58") util.convert_one_to_hex( "0xebcd16e8c1d8f493ba04e99a56474122d81a9c58" )
== "0xeBCD16e8c1D8f493bA04E99a56474122D81A9c58" == "0xeBCD16e8c1D8f493bA04E99a56474122D81A9c58"
) )
assert ( assert (
util.convert_one_to_hex("one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9") util.convert_one_to_hex( "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9" )
== "0xeBCD16e8c1D8f493bA04E99a56474122D81A9c58" == "0xeBCD16e8c1D8f493bA04E99a56474122D81A9c58"
) )
def test_convert_hex_to_one(): def test_convert_hex_to_one():
assert ( assert (
util.convert_hex_to_one("0xebcd16e8c1d8f493ba04e99a56474122d81a9c58") util.convert_hex_to_one( "0xebcd16e8c1d8f493ba04e99a56474122d81a9c58" )
== "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9" == "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9"
) )
assert ( assert (
util.convert_hex_to_one("one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9") util.convert_hex_to_one( "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9" )
== "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9" == "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9"
) )
def test_get_bls_build_variables(): def test_get_bls_build_variables():
assert isinstance(util.get_bls_build_variables(), dict) assert isinstance( util.get_bls_build_variables(), dict )
def test_is_active_shard(): def test_is_active_shard():
assert isinstance(util.is_active_shard(""), bool) assert isinstance( util.is_active_shard( "" ), bool )

Loading…
Cancel
Save