Merge pull request #35 from MaxMustermann2/validator-sigs

add `hex_to_one`, fix `bech32` installation, bug fix
pull/36/head
Max 2 years ago committed by GitHub
commit d12bde7328
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .gitignore
  2. 404
      .style.yapf
  3. 2
      Makefile
  4. 35
      README.md
  5. 21
      pyhmy/__init__.py
  6. 8
      pyhmy/_version.py
  7. 409
      pyhmy/account.py
  8. 0
      pyhmy/bech32/__init__.py
  9. 115
      pyhmy/bech32/bech32.py
  10. 988
      pyhmy/blockchain.py
  11. 414
      pyhmy/cli.py
  12. 18
      pyhmy/constants.py
  13. 209
      pyhmy/contract.py
  14. 64
      pyhmy/exceptions.py
  15. 169
      pyhmy/logging.py
  16. 30
      pyhmy/numbers.py
  17. 37
      pyhmy/rpc/exceptions.py
  18. 66
      pyhmy/rpc/request.py
  19. 262
      pyhmy/signing.py
  20. 575
      pyhmy/staking.py
  21. 530
      pyhmy/staking_signing.py
  22. 297
      pyhmy/staking_structures.py
  23. 539
      pyhmy/transaction.py
  24. 181
      pyhmy/util.py
  25. 694
      pyhmy/validator.py
  26. 1
      pytest.ini
  27. 2
      setup.cfg
  28. 578
      tests/GenerateRawTransactions.ipynb
  29. 10
      tests/bech32-pyhmy/test_bech32.py
  30. 61
      tests/cli-pyhmy/test_cli.py
  31. 20
      tests/logging-pyhmy/test_logging.py
  32. 41
      tests/numbers-pyhmy/test_numbers.py
  33. 83
      tests/request-pyhmy/test_request.py
  34. 348
      tests/sdk-pyhmy/conftest.py
  35. 219
      tests/sdk-pyhmy/test_account.py
  36. 628
      tests/sdk-pyhmy/test_blockchain.py
  37. 116
      tests/sdk-pyhmy/test_contract.py
  38. 56
      tests/sdk-pyhmy/test_signing.py
  39. 387
      tests/sdk-pyhmy/test_staking.py
  40. 86
      tests/sdk-pyhmy/test_staking_signing.py
  41. 394
      tests/sdk-pyhmy/test_transaction.py
  42. 244
      tests/sdk-pyhmy/test_validator.py
  43. 63
      tests/util-pyhmy/test_util.py

3
.gitignore vendored

@ -131,3 +131,6 @@ dmypy.json
# IDE
.idea
# VIM
*.swp

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

@ -29,7 +29,7 @@ dev:
python3 -m pip install pytest-ordering
test:
python3 -m py.test -r s -s tests
python3 -m pytest -r s -s tests
install:
python3 -m pip install -e .

@ -34,16 +34,16 @@ git clone https://github.com/harmony-one/mcl.git
git clone https://github.com/harmony-one/bls.git
git clone https://github.com/harmony-one/harmony.git
cd harmony
make test-rpc
make debug
```
Once the terminal displays `=== FINISHED RPC TESTS ===`, use another shell to run the following tests
Once the terminal displays a couple of `Started server` lines, use another shell to run the following tests
```bash
make test
```
Or directly with `pytest` (reference [here](https://docs.pytest.org/en/latest/index.html) for more info):
```bash
py.test tests
pytest tests
```
## Releasing
@ -61,6 +61,19 @@ test_address = 'one18t4yj4fuutj83uwqckkvxp9gfa0568uc48ggj7'
main_net = 'https://rpc.s0.t.hmny.io'
main_net_shard_1 = 'https://rpc.s1.t.hmny.io'
```
#### utilities
##### Address conversion
```py
from pyhmy import util
hex_addr = util.convert_one_to_hex('one155jp2y76nazx8uw5sa94fr0m4s5aj8e5xm6fu3')
one_addr = util.convert_hex_to_one('0xA5241513DA9F4463F1d4874b548dFBAC29D91f34')
```
##### Ether / Wei conversion
```py
from pyhmy import numbers
one_ether_in_wei = numbers.convert_one_to_atto(1) # as a decimal.Decimal
wei_to_ether = numbers.convert_atto_to_one(int(1e18))
```
#### accounts
```py
from pyhmy import account
@ -245,7 +258,7 @@ delegator_addr = 'one1y2624lg0mpkxkcttaj0c85pp8pfmh2tt5zhdte'
```py
all_validators = staking.get_all_validator_addresses(endpoint=test_net) # list of addresses
validator_information = staking.get_validator_information(validator_addr, endpoint=test_net) # dict with all info
validator_information_100 = staking.get_all_validator_information(page=0, endpoint=test_net) # for all use page=-1
validator_information_100 = staking.get_all_validator_information(page=0, endpoint=test_net)
elected_validators = staking.get_elected_validator_addresses(endpoint=test_net) # list of addresses
validators_for_epoch = staking.get_validators(epoch=73772, endpoint=test_net) # dict with list of validators and balance
validators_information_100_for_block = staking.get_all_validator_information_by_block_number(block_num=9017724, page=0, endpoint=test_net)
@ -256,7 +269,7 @@ total_delegation = staking.get_validator_total_delegation(validator_addr, endpoi
```
##### Delegation
```py
delegation_information = staking.get_all_delegation_information(page=-1, endpoint=test_net)
delegation_information = staking.get_all_delegation_information(page=0, endpoint=test_net)
delegations_by_delegator = staking.get_delegations_by_delegator(delegator_addr, test_net)
delegations_by_delegator_at_block = staking.get_delegations_by_delegator_by_block_number(delegator_addr, block_num=9017724, endpoint=test_net)
delegation_by_delegator_and_validator = staking.get_delegation_by_delegator_and_validator(delegator_addr, validator_addr, test_net)
@ -295,7 +308,10 @@ info = {
'max-rate': '0.9',
'max-change-rate': '0.05',
'rate': '0.01',
'bls-public-keys': ['0xb9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608611'],
'bls-public-keys': ['0xa20e70089664a874b00251c5e85d35a73871531306f3af43e02138339d294e6bb9c4eb82162199c6a852afeaa8d68712'],
"bls-key-sigs": [
"0xef2c49a2f31fbbd23c21bc176eaf05cd0bebe6832033075d81fea7cff6f9bc1ab42f3b6895c5493fe645d8379d2eaa1413de55a9d3ce412a4f747cb57d52cc4da4754bfb2583ec9a41fe5dd48287f964f276336699959a5fcef3391dc24df00d",
]
'max-total-delegation': convert_one_to_atto(40000)
}
validator.load(info)
@ -307,7 +323,7 @@ signed_create_tx_hash = validator.sign_create_validator_transaction(
gas_price = 1,
gas_limit = 100,
private_key = '4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48',
chain_id = None).rawTransaction.hex()
chain_id = 2).rawTransaction.hex()
```
To edit validator, change its parameters using the `setter` functions, for example, `validator.set_details`, except the `rate`, `bls_keys_to_add` and `bls_keys_to_remove` which can be passed to the below function:
```py
@ -316,8 +332,9 @@ signed_edit_tx_hash = validator.sign_edit_validator_transaction(
gas_price = 1,
gas_limit = 100,
rate = '0.06',
bls_keys_to_add = "0xb9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608611",
bls_keys_to_remove = '0xb9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608612',
bls_key_to_add = "0xb8c3b3a0f1966c169ca73c348f4b8aee333a407125ab5c67f1d6e1e18ab052ed5fff0f1f7d4a7f789528b5ccd9c47b04",
bls_key_to_add_sig = "0x3de4dff17451fb76a9690efce34bced97dd87eccd371fcd25335826cb879ca21281e82e5c2c76d4ef0ab0fc16e462312628834cbc1f29008b28e16a757367808be85180945b991be3103f98c14c7e3b3e54796d34aab4d8e812d440aa251c419",
bls_keys_to_remove = '0xa20e70089664a874b00251c5e85d35a73871531306f3af43e02138339d294e6bb9c4eb82162199c6a852afeaa8d68712',
private_key = '4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48',
chain_id = 2).rawTransaction.hex()
```

@ -1,18 +1,13 @@
"""
`pyhmy` for interacting with the Harmony blockchain
"""
import sys
import warnings
from ._version import __version__
from .util import (
Typgpy,
get_gopath,
get_goversion,
get_bls_build_variables,
json_load
)
if sys.version_info.major < 3:
warnings.simplefilter("always", DeprecationWarning)
warnings.simplefilter( "always", DeprecationWarning )
warnings.warn(
DeprecationWarning(
"`pyhmy` does not support Python 2. Please use Python 3."
@ -20,11 +15,9 @@ if sys.version_info.major < 3:
)
warnings.resetwarnings()
if sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
warnings.simplefilter("always", ImportWarning)
if sys.platform.startswith( "win32" ) or sys.platform.startswith( "cygwin" ):
warnings.simplefilter( "always", ImportWarning )
warnings.warn(
ImportWarning(
"`pyhmy` does not work on Windows or Cygwin."
)
ImportWarning( "`pyhmy` does not work on Windows or Cygwin." )
)
warnings.resetwarnings()

@ -1,11 +1,9 @@
"""
Provides pyhmy version information.
"""
"""Provides pyhmy version information."""
# This file is auto-generated! Do not edit!
# Use `python -m incremental.update pyhmy` to change this file.
from incremental import Version
__version__ = Version('pyhmy', 20, 5, 20)
__all__ = ["__version__"]
__version__ = Version( "pyhmy", 20, 5, 20 )
__all__ = [ "__version__" ]

@ -1,31 +1,20 @@
from .rpc.request import (
rpc_request
)
"""
Interact with accounts on the Harmony blockchain
"""
from .rpc.request import rpc_request
from .rpc.exceptions import (
RPCError,
RequestsError,
RequestsTimeoutError
)
from .rpc.exceptions import RPCError, RequestsError, RequestsTimeoutError
from .exceptions import (
InvalidRPCReplyError
)
from .exceptions import InvalidRPCReplyError
from .blockchain import (
get_sharding_structure
)
from .blockchain import get_sharding_structure
from .bech32.bech32 import (
bech32_decode
)
from .bech32.bech32 import bech32_decode
_default_endpoint = 'http://localhost:9500'
_default_timeout = 30
_address_length = 42
from .constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT
def is_valid_address(address) -> bool:
def is_valid_address( address ) -> bool:
"""
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.
@ -40,16 +29,20 @@ def is_valid_address(address) -> bool:
bool
Is valid address
"""
if not address.startswith('one1'):
if not address.startswith( "one1" ):
return False
hrp, _ = bech32_decode(address)
hrp, _ = bech32_decode( address )
if not hrp:
return False
return True
def get_balance(address, endpoint=_default_endpoint, timeout=_default_timeout) -> int:
"""
Get current account balance
def get_balance(
address,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""Get current account balance.
Parameters
----------
@ -74,19 +67,27 @@ def get_balance(address, endpoint=_default_endpoint, timeout=_default_timeout) -
-------------
https://api.hmny.io/#da8901d2-d237-4c3b-9d7d-10af9def05c4
"""
method = 'hmyv2_getBalance'
params = [
address
]
method = "hmyv2_getBalance"
params = [ address ]
try:
balance = rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
return int(balance) # v2 returns the result as it is
except TypeError as e: # check will work if rpc returns None
raise InvalidRPCReplyError(method, endpoint) from e
def get_balance_by_block(address, block_num, endpoint=_default_endpoint, timeout=_default_timeout) -> int:
"""
Get account balance for address at a given block number
balance = rpc_request(
method,
params = params,
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
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_balance_by_block(
address,
block_num,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""Get account balance for address at a given block number.
Parameters
----------
@ -114,20 +115,27 @@ def get_balance_by_block(address, block_num, endpoint=_default_endpoint, timeout
https://api.hmny.io/#9aeae4b8-1a09-4ed2-956b-d7c96266dd33
https://github.com/harmony-one/harmony/blob/9f320436ff30d9babd957bc5f2e15a1818c86584/rpc/blockchain.go#L92
"""
method = 'hmyv2_getBalanceByBlockNumber'
params = [
address,
block_num
]
method = "hmyv2_getBalanceByBlockNumber"
params = [ address, block_num ]
try:
balance = rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
return int(balance)
except TypeError as e:
raise InvalidRPCReplyError(method, endpoint) from e
def get_account_nonce(address, block_num, endpoint=_default_endpoint, timeout=_default_timeout) -> int:
"""
Get the account nonce
balance = rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
return int( balance )
except TypeError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_account_nonce(
address,
block_num = "latest",
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""Get the account nonce.
Parameters
----------
@ -154,22 +162,40 @@ def get_account_nonce(address, block_num, endpoint=_default_endpoint, timeout=_d
-------------
https://github.com/harmony-one/harmony/blob/9f320436ff30d9babd957bc5f2e15a1818c86584/rpc/transaction.go#L51
"""
method = 'hmyv2_getAccountNonce'
params = [
address,
block_num
]
method = "hmyv2_getAccountNonce"
params = [ address, block_num ]
try:
nonce = rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
return int(nonce)
except TypeError as e:
raise InvalidRPCReplyError(method, endpoint) from e
nonce = rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
return int( nonce )
except TypeError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_nonce(
address,
block_num = "latest",
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""See get_account_nonce."""
return get_account_nonce( address, block_num, endpoint, timeout )
def get_transaction_count(address, block_num, endpoint=_default_endpoint, timeout=_default_timeout) -> int:
"""
Get the number of transactions the given address has sent for the given block number
Legacy for apiv1. For apiv2, please use get_account_nonce/get_transactions_count/get_staking_transactions_count apis for
more granular transaction counts queries
def get_transaction_count(
address,
block_num,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""Get the number of transactions the given address has sent for the given
block number Legacy for apiv1. For apiv2, please use
get_account_nonce/get_transactions_count/get_staking_transactions_count
apis for more granular transaction counts queries.
Parameters
----------
@ -196,20 +222,27 @@ def get_transaction_count(address, block_num, endpoint=_default_endpoint, timeou
-------------
https://github.com/harmony-one/harmony/blob/9f320436ff30d9babd957bc5f2e15a1818c86584/rpc/transaction.go#L69
"""
method = 'hmyv2_getTransactionCount'
params = [
address,
block_num
]
method = "hmyv2_getTransactionCount"
params = [ address, block_num ]
try:
nonce = rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
return int(nonce)
except TypeError as e:
raise InvalidRPCReplyError(method, endpoint) from e
def get_transactions_count(address, tx_type, endpoint=_default_endpoint, timeout=_default_timeout) -> int:
"""
Get the number of regular transactions from genesis of input type
nonce = rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
return int( nonce )
except TypeError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_transactions_count(
address,
tx_type,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""Get the number of regular transactions from genesis of input type.
Parameters
----------
@ -238,20 +271,28 @@ def get_transactions_count(address, tx_type, endpoint=_default_endpoint, timeout
https://api.hmny.io/#fc97aed2-e65e-4cf4-bc01-8dadb76732c0
https://github.com/harmony-one/harmony/blob/9f320436ff30d9babd957bc5f2e15a1818c86584/rpc/transaction.go#L114
"""
method = 'hmyv2_getTransactionsCount'
params = [
address,
tx_type
]
method = "hmyv2_getTransactionsCount"
params = [ address, tx_type ]
try:
tx_count = rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
return int(tx_count)
except TypeError as e:
raise InvalidRPCReplyError(method, endpoint) from e
def get_staking_transactions_count(address, tx_type, endpoint=_default_endpoint, timeout=_default_timeout) -> int:
"""
Get the number of staking transactions from genesis of input type ("SENT", "RECEIVED", "ALL")
tx_count = rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
return int( tx_count )
except TypeError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_staking_transactions_count(
address,
tx_type,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""Get the number of staking transactions from genesis of input type
("SENT", "RECEIVED", "ALL")
Parameters
----------
@ -280,22 +321,31 @@ def get_staking_transactions_count(address, tx_type, endpoint=_default_endpoint,
https://api.hmny.io/#ddc1b029-f341-4c4d-ba19-74b528d6e5e5
https://github.com/harmony-one/harmony/blob/9f320436ff30d9babd957bc5f2e15a1818c86584/rpc/transaction.go#L134
"""
method = 'hmyv2_getStakingTransactionsCount'
params = [
address,
tx_type
]
method = "hmyv2_getStakingTransactionsCount"
params = [ address, tx_type ]
try:
tx_count = rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
return int(tx_count)
except (KeyError, TypeError) as e:
raise InvalidRPCReplyError(method, endpoint) from e
def get_transaction_history(address, page=0, page_size=1000, include_full_tx=False, tx_type='ALL',
order='ASC', endpoint=_default_endpoint, timeout=_default_timeout
) -> list:
"""
Get list of transactions sent and/or received by the account
tx_count = rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "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
address,
page=0,
page_size=1000,
include_full_tx=False,
tx_type="ALL",
order="ASC",
endpoint=DEFAULT_ENDPOINT,
timeout=DEFAULT_TIMEOUT,
) -> list:
"""Get list of transactions sent and/or received by the account.
Parameters
----------
@ -339,26 +389,38 @@ def get_transaction_history(address, page=0, page_size=1000, include_full_tx=Fal
"""
params = [
{
'address': address,
'pageIndex': page,
'pageSize': page_size,
'fullTx': include_full_tx,
'txType': tx_type,
'order': order
"address": address,
"pageIndex": page,
"pageSize": page_size,
"fullTx": include_full_tx,
"txType": tx_type,
"order": order,
}
]
method = 'hmyv2_getTransactionsHistory'
method = "hmyv2_getTransactionsHistory"
try:
tx_history = rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)
return tx_history['result']['transactions']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
def get_staking_transaction_history(address, page=0, page_size=1000, include_full_tx=False, tx_type='ALL',
order='ASC', endpoint=_default_endpoint, timeout=_default_timeout
) -> list:
"""
Get list of staking transactions sent by the account
tx_history = rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)
return tx_history[ "result" ][ "transactions" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_staking_transaction_history( # pylint: disable=too-many-arguments
address,
page=0,
page_size=1000,
include_full_tx=False,
tx_type="ALL",
order="ASC",
endpoint=DEFAULT_ENDPOINT,
timeout=DEFAULT_TIMEOUT,
) -> list:
"""Get list of staking transactions sent by the account.
Parameters
----------
@ -385,7 +447,8 @@ def get_staking_transaction_history(address, page=0, page_size=1000, include_ful
-------
list of transactions
if include_full_tx is True, each transaction is a dictionary with the following kets
blockHash: :obj:`str` Block hash that transaction was finalized; "0x0000000000000000000000000000000000000000000000000000000000000000" if tx is pending
blockHash: :obj:`str` Block hash that transaction was finalized or
"0x0000000000000000000000000000000000000000000000000000000000000000" if tx is pending
blockNumber: :obj:`int` Block number that transaction was finalized; None if tx is pending
from: :obj:`str` Wallet address
timestamp: :obj:`int` Timestamp in Unix time when transaction was finalized
@ -394,7 +457,8 @@ def get_staking_transaction_history(address, page=0, page_size=1000, include_ful
hash: :obj:`str` Transaction hash
nonce: :obj:`int` Wallet nonce for the transaction
transactionIndex: :obj:`int` Index of transaction in block; None if tx is pending
type: :obj:`str` Type of staking transaction, for example, "CollectRewards", "Delegate", "Undelegate"
type: :obj:`str` Type of staking transaction
for example, "CollectRewards", "Delegate", "Undelegate"
msg: :obj:`dict` Message attached to the staking transaction
r: :obj:`str` First 32 bytes of the transaction signature
s: :obj:`str` Next 32 bytes of the transaction signature
@ -413,25 +477,36 @@ def get_staking_transaction_history(address, page=0, page_size=1000, include_ful
"""
params = [
{
'address': address,
'pageIndex': page,
'pageSize': page_size,
'fullTx': include_full_tx,
'txType': tx_type,
'order': order
"address": address,
"pageIndex": page,
"pageSize": page_size,
"fullTx": include_full_tx,
"txType": tx_type,
"order": order,
}
]
# Using v2 API, because getStakingTransactionHistory not implemented in v1
method = 'hmyv2_getStakingTransactionsHistory'
method = "hmyv2_getStakingTransactionsHistory"
try:
stx_history = rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
return stx_history['staking_transactions']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
def get_balance_on_all_shards(address, skip_error=True, endpoint=_default_endpoint, timeout=_default_timeout) -> list:
"""
Get current account balance in all shards & optionally report errors getting account balance for a shard
stx_history = rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
return stx_history[ "staking_transactions" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_balance_on_all_shards(
address,
skip_error = True,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list:
"""Get current account balance in all shards & optionally report errors
getting account balance for a shard.
Parameters
----------
@ -458,24 +533,39 @@ def get_balance_on_all_shards(address, skip_error=True, endpoint=_default_endpoi
]
"""
balances = []
sharding_structure = get_sharding_structure(endpoint=endpoint, timeout=timeout)
sharding_structure = get_sharding_structure(
endpoint = endpoint,
timeout = timeout
)
for shard in sharding_structure:
try:
balances.append({
'shard': shard['shardID'],
'balance': get_balance(address, endpoint=shard['http'], timeout=timeout)
})
except (KeyError, RPCError, RequestsError, RequestsTimeoutError):
balances.append(
{
"shard": shard[ "shardID" ],
"balance": get_balance(
address,
endpoint = shard[ "http" ],
timeout = timeout
),
}
)
except ( KeyError, RPCError, RequestsError, RequestsTimeoutError ):
if not skip_error:
balances.append({
'shard': shard['shardID'],
'balance': None
})
balances.append(
{
"shard": shard[ "shardID" ],
"balance": None
}
)
return balances
def get_total_balance(address, endpoint=_default_endpoint, timeout=_default_timeout) -> int:
"""
Get total account balance on all shards
def get_total_balance(
address,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""Get total account balance on all shards.
Parameters
----------
@ -501,7 +591,12 @@ def get_total_balance(address, endpoint=_default_endpoint, timeout=_default_time
get_balance_on_all_shards
"""
try:
balances = get_balance_on_all_shards(address, skip_error=False, endpoint=endpoint, timeout=timeout)
return sum(b['balance'] for b in balances)
except TypeError as e:
raise RuntimeError from e
balances = get_balance_on_all_shards(
address,
skip_error = False,
endpoint = endpoint,
timeout = timeout
)
return sum( b[ "balance" ] for b in balances )
except TypeError as exception:
raise RuntimeError from exception

@ -17,107 +17,106 @@
# 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
# THE SOFTWARE.
"""Reference implementation for Bech32 and segwit addresses."""
CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
def bech32_polymod(values):
def bech32_polymod( values ):
"""Internal function that computes the Bech32 checksum."""
generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
generator = [ 0x3B6A57B2, 0x26508E6D, 0x1EA119FA, 0x3D4233DD, 0x2A1462B3 ]
chk = 1
for value in values:
top = chk >> 25
chk = (chk & 0x1ffffff) << 5 ^ value
for i in range(5):
chk ^= generator[i] if ((top >> i) & 1) else 0
chk = ( chk & 0x1FFFFFF ) << 5 ^ value
for i in range( 5 ):
chk ^= generator[ i ] if ( ( top >> i ) & 1 ) else 0
return chk
def bech32_hrp_expand(hrp):
def bech32_hrp_expand( hrp ):
"""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."""
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."""
values = bech32_hrp_expand(hrp) + data
polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ 1
return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)]
values = bech32_hrp_expand( hrp ) + data
polymod = bech32_polymod( values + [ 0, 0, 0, 0, 0, 0 ] ) ^ 1
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."""
combined = data + bech32_create_checksum(hrp, data)
return hrp + '1' + ''.join([CHARSET[d] for d in combined])
combined = data + bech32_create_checksum( hrp, data )
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."""
if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or
(bech.lower() != bech and bech.upper() != bech)):
return (None, None)
if ( any( ord( x ) < 33 or ord( x ) > 126 for x in bech
) ) or ( bech.lower() != bech and bech.upper() != bech ):
return ( None, None )
bech = bech.lower()
pos = bech.rfind('1')
if pos < 1 or pos + 7 > len(bech) or len(bech) > 90:
return (None, None)
if not all(x in CHARSET for x in bech[pos+1:]):
return (None, None)
hrp = bech[:pos]
data = [CHARSET.find(x) for x in bech[pos+1:]]
if not bech32_verify_checksum(hrp, data):
return (None, None)
return (hrp, data[:-6])
def convertbits(data, frombits, tobits, pad=True):
pos = bech.rfind( "1" )
if pos < 1 or pos + 7 > len( bech ) or len( bech ) > 90:
return ( None, None )
if not all( x in CHARSET for x in bech[ pos + 1 : ] ):
return ( None, None )
hrp = bech[ : pos ]
data = [ CHARSET.find( x ) for x in bech[ pos + 1 : ] ]
if not bech32_verify_checksum( hrp, data ):
return ( None, None )
return ( hrp, data[ :-6 ] )
def convertbits( data, frombits, tobits, pad = True ):
"""General power-of-2 base conversion."""
acc = 0
bits = 0
ret = []
maxv = (1 << tobits) - 1
max_acc = (1 << (frombits + tobits - 1)) - 1
maxv = ( 1 << tobits ) - 1
max_acc = ( 1 << ( frombits + tobits - 1 ) ) - 1
for value in data:
if value < 0 or (value >> frombits):
if value < 0 or ( value >> frombits ):
return None
acc = ((acc << frombits) | value) & max_acc
acc = ( ( acc << frombits ) | value ) & max_acc
bits += frombits
while bits >= tobits:
bits -= tobits
ret.append((acc >> bits) & maxv)
ret.append( ( acc >> bits ) & maxv )
if pad:
if bits:
ret.append((acc << (tobits - bits)) & maxv)
elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
ret.append( ( acc << ( tobits - bits ) ) & maxv )
elif bits >= frombits or ( ( acc << ( tobits - bits ) ) & maxv ):
return None
return ret
def decode(hrp, addr):
def decode( hrp, addr ):
"""Decode a segwit address."""
hrpgot, data = bech32_decode(addr)
hrpgot, data = bech32_decode( addr )
if hrpgot != hrp:
return (None, None)
decoded = convertbits(data[1:], 5, 8, False)
if decoded is None or len(decoded) < 2 or len(decoded) > 40:
return (None, None)
if data[0] > 16:
return (None, None)
if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32:
return (None, None)
return (data[0], decoded)
def encode(hrp, witver, witprog):
return ( None, None )
decoded = convertbits( data[ 1 : ], 5, 8, False )
if decoded is None or len( decoded ) < 2 or len( decoded ) > 40:
return ( None, None )
if data[ 0 ] > 16:
return ( None, None )
if data[ 0 ] == 0 and len( decoded ) != 20 and len( decoded ) != 32:
return ( None, None )
return ( data[ 0 ], decoded )
def encode( hrp, witver, witprog ):
"""Encode a segwit address."""
ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5))
if decode(hrp, ret) == (None, None):
ret = bech32_encode( hrp, [ witver ] + convertbits( witprog, 8, 5 ) )
if decode( hrp, ret ) == ( None, None ):
return None
return ret

File diff suppressed because it is too large Load Diff

@ -7,7 +7,8 @@ Example:
Below is a demo of how to import, manage keys, and interact with the CLI::
>>> from pyhmy import cli
>>> cli.single_call("hmy keys add test1")
'**Important** write this seed phrase in a safe place, it is the only way to recover your account if you ever forget your password
'**Important** write this seed phrase in a safe place,
it is the only way to recover your account if you ever forget your password
craft ... tobacco'
>>> cli.get_accounts_keystore()
{'test1': 'one1aqfeed538xf7n0cfh60tjaeat7yw333pmj6sfu'}
@ -40,128 +41,167 @@ For more details, reference the documentation here: TODO gitbook docs
"""
import subprocess
import pexpect
import os
import shutil
import re
import stat
import sys
from multiprocessing import Lock
from pathlib import Path
import pexpect
import requests
from .util import get_bls_build_variables, get_gopath
if sys.platform.startswith("linux"):
_libs = {"libbls384_256.so", "libcrypto.so.10", "libgmp.so.10", "libgmpxx.so.4", "libmcl.so"}
if sys.platform.startswith( "linux" ):
_libs = {
"libbls384_256.so",
"libcrypto.so.10",
"libgmp.so.10",
"libgmpxx.so.4",
"libmcl.so",
}
else:
_libs = {"libbls384_256.dylib", "libcrypto.1.0.0.dylib", "libgmp.10.dylib", "libgmpxx.4.dylib", "libmcl.dylib"}
_accounts = {} # Internal accounts keystore, make sure to sync when needed.
_account_keystore_path = "~/.hmy/account-keys" # Internal path to account keystore, will match the current binary.
_binary_path = "hmy" # Internal binary path.
_arg_prefix = "__PYHMY_ARG_PREFIX__"
_keystore_cache_lock = Lock()
_libs = {
"libbls384_256.dylib",
"libcrypto.1.0.0.dylib",
"libgmp.10.dylib",
"libgmpxx.4.dylib",
"libmcl.dylib",
}
# Internal accounts keystore, make sure to sync when needed.
_accounts = {}
# Internal path to account keystore, will match the current binary.
ARG_PREFIX = "__PYHMY_ARG_PREFIX__"
# _keystore_cache_lock = Lock()
environment = os.environ.copy() # The environment for the CLI to execute in.
# completely remove caching...
# we need to improve getting address better internally to REDUCE single calls....
# def _cache_and_lock_accounts_keystore(fn):
# """Internal decorator to cache the accounts keystore and prevent concurrent
# accesses with locks."""
# cached_accounts = {}
# last_mod = None
# def wrap(*args):
# nonlocal last_mod
# _keystore_cache_lock.acquire()
# files_in_dir = str(os.listdir(ACCOUNT_KEYSTORE_PATH))
# dir_mod_time = str(os.path.getmtime(ACCOUNT_KEYSTORE_PATH))
# curr_mod = hash(files_in_dir + dir_mod_time + BINARY_PATH)
# if curr_mod != last_mod:
# cached_accounts.clear()
# cached_accounts.update(fn(*args))
# last_mod = curr_mod
# accounts = cached_accounts.copy()
# _keystore_cache_lock.release()
# return accounts
# return wrap
# TODO: completely remove caching... we need to improve getting address better internally to REDUCE single calls....
def _cache_and_lock_accounts_keystore(fn):
def account_keystore_path( value = None ):
"""
Internal decorator to cache the accounts keystore and
prevent concurrent accesses with locks.
Gets or sets the ACCOUNT_KEYSTORE_PATH
"""
cached_accounts = {}
last_mod = None
if "value" not in account_keystore_path.__dict__:
account_keystore_path.value = "~/.hmy/account-keys"
if value:
account_keystore_path.value = value
return account_keystore_path.value
def wrap(*args):
nonlocal last_mod
_keystore_cache_lock.acquire()
files_in_dir = str(os.listdir(_account_keystore_path))
dir_mod_time = str(os.path.getmtime(_account_keystore_path))
curr_mod = hash(files_in_dir + dir_mod_time + _binary_path)
if curr_mod != last_mod:
cached_accounts.clear()
cached_accounts.update(fn(*args))
last_mod = curr_mod
accounts = cached_accounts.copy()
_keystore_cache_lock.release()
return accounts
return wrap
def binary_path( value = None ):
"""
Gets or sets the BINARY_PATH
"""
if "value" not in binary_path.__dict__:
binary_path.value = "hmy"
if value:
binary_path.value = value
return binary_path.value
def _get_current_accounts_keystore():
"""
Internal function that gets the current keystore from the CLI.
"""Internal function that gets the current keystore from the CLI.
:returns A dictionary where the keys are the account names/aliases and the
values are their 'one1...' addresses.
"""
curr_addresses = {}
response = single_call("hmy keys list")
lines = response.split("\n")
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")
if lines[1] != "":
raise ValueError("Unknown format: No blank line between label and data")
for line in lines[2:]:
columns = line.split("\t")
if len(columns) != 2:
response = single_call( "hmy keys list" )
lines = response.split( "\n" )
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"
)
if lines[ 1 ] != "":
raise ValueError(
"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.
name, address = columns
curr_addresses[name.strip()] = address
curr_addresses[ name.strip() ] = address
return curr_addresses
def _set_account_keystore_path():
"""
Internal function to set the account keystore path according to the binary.
"""
global _account_keystore_path
response = single_call("hmy keys location").strip()
if not os.path.exists(response):
os.mkdir(response)
_account_keystore_path = response
"""Internal function to set the account keystore path according to the
binary."""
response = single_call( "hmy keys location" ).strip()
if not os.path.exists( response ):
os.mkdir( response )
account_keystore_path( response )
def _sync_accounts():
"""
Internal function that UPDATES the accounts keystore with the CLI's keystore.
"""
"""Internal function that UPDATES the accounts keystore with the CLI's
keystore."""
new_keystore = _get_current_accounts_keystore()
for key in new_keystore.keys():
if key not in _accounts.keys():
_accounts[key] = new_keystore[key]
acc_keys_to_remove = [k for k in _accounts.keys() if k not in new_keystore.keys()]
for key, value in new_keystore.items():
if key not in _accounts:
_accounts[ key ] = value
acc_keys_to_remove = [ k for k in _accounts if k not in new_keystore ]
for key in acc_keys_to_remove:
del _accounts[key]
del _accounts[ key ]
def _make_call_command(command):
"""
Internal function that processes a command String or String Arg List for
def _make_call_command( command ):
"""Internal function that processes a command String or String Arg List for
underlying pexpect or subprocess call.
Note that single quote is not respected for strings.
"""
if isinstance(command, list):
if isinstance( command, list ):
command_toks = command
else:
all_strings = sorted(re.findall(r'"(.*?)"', command), key=lambda e: len(e), reverse=True)
for i, string in enumerate(all_strings):
command = command.replace(string, f"{_arg_prefix}_{i}")
command_toks_prefix = [el for el in command.split(" ") if el]
all_strings = sorted(
re.findall(r'"(.*?)"', command),
key=lambda e: len(e), # pylint: disable=unnecessary-lambda
reverse=True
)
for i, string in enumerate( all_strings ):
command = command.replace( string, f"{ARG_PREFIX}_{i}" )
command_toks_prefix = [ el for el in command.split( " " ) if el ]
command_toks = []
for el in command_toks_prefix:
if el.startswith(f'"{_arg_prefix}_') and el.endswith(f'"'):
index = int(el.replace(f'"{_arg_prefix}_', '').replace('"', ''))
command_toks.append(all_strings[index])
for element in command_toks_prefix:
if element.startswith( f'"{ARG_PREFIX}_'
) and element.endswith( '"' ):
index = int(
element.replace( f'"{ARG_PREFIX}_',
"" ).replace( '"',
"" )
)
command_toks.append( all_strings[ index ] )
else:
command_toks.append(el)
if re.match(".*hmy", command_toks[0]):
command_toks = command_toks[1:]
command_toks.append( element )
if re.match( ".*hmy", command_toks[ 0 ] ):
command_toks = command_toks[ 1 : ]
return command_toks
@ -175,38 +215,46 @@ def get_accounts_keystore():
return _accounts
def is_valid_binary(path):
def is_valid_binary( path ):
"""
:param path: Path to the Harmony CLI binary (absolute or relative).
:return: If the file at the path is a CLI binary.
"""
path = os.path.realpath(path)
os.chmod(path, os.stat(path).st_mode | stat.S_IEXEC)
path = os.path.realpath( path )
os.chmod( path, os.stat( path ).st_mode | stat.S_IEXEC )
try:
proc = subprocess.Popen([path, "version"], env=environment,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = proc.communicate()
with subprocess.Popen(
[ path,
"version" ],
env = environment,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
) as proc:
_, err = proc.communicate()
if not err:
return False
return "harmony" in err.decode().strip().lower()
except (OSError, subprocess.CalledProcessError, subprocess.SubprocessError):
except (
OSError,
subprocess.CalledProcessError,
subprocess.SubprocessError
):
return False
def set_binary(path):
def set_binary( path ):
"""
:param path: The path of the CLI binary to use.
:returns If the binary has been set.
Note that the exposed keystore will be updated accordingly.
"""
global _binary_path
path = os.path.realpath(path)
assert os.path.exists(path)
os.chmod(path, os.stat(path).st_mode | stat.S_IEXEC)
if not is_valid_binary(path):
path = os.path.realpath( path )
assert os.path.exists( path )
os.chmod( path, os.stat( path ).st_mode | stat.S_IEXEC )
if not is_valid_binary( path ):
return False
_binary_path = path
binary_path( path )
_set_account_keystore_path()
_sync_accounts()
return True
@ -216,19 +264,26 @@ def get_binary_path():
"""
:return: The absolute path of the CLI binary.
"""
return os.path.abspath(_binary_path)
return os.path.abspath( binary_path() )
def get_version():
"""
:return: The version string of the CLI binary.
"""
proc = subprocess.Popen([_binary_path, "version"], env=environment,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = proc.communicate()
with subprocess.Popen(
[ binary_path(),
"version" ],
env = environment,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
) as proc:
_, err = proc.communicate()
if not err:
raise RuntimeError(f"Could not get version.\n"
f"\tGot exit code {proc.returncode}. Expected non-empty error message.")
raise RuntimeError(
f"Could not get version.\n"
f"\tGot exit code {proc.returncode}. Expected non-empty error message."
)
return err.decode().strip()
@ -236,10 +291,10 @@ def get_account_keystore_path():
"""
:return: The absolute path to the account keystore of the CLI binary.
"""
return os.path.abspath(_account_keystore_path)
return os.path.abspath( account_keystore_path() )
def check_address(address):
def check_address( address ):
"""
:param address: A 'one1...' address.
:return: Boolean of if the address is in the CLI's keystore.
@ -247,15 +302,15 @@ def check_address(address):
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.
: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
:return: A list of account names associated with the param
@ -263,38 +318,42 @@ def get_accounts(address):
Note that a list of account names is needed because 1 address can
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):
"""
Note that this edits the keystore directly since there is currently no
def remove_account( name ):
"""Note that this edits the keystore directly since there is currently no
way to remove an address using the CLI.
:param name: The alias of a key used in the CLI's keystore.
:raises RuntimeError: If it failed to remove an account.
"""
if not get_address(name):
if not get_address( name ):
return
keystore_path = f"{get_account_keystore_path()}/{name}"
try:
shutil.rmtree(keystore_path)
except (shutil.Error, FileNotFoundError) as err:
raise RuntimeError(f"Failed to delete dir: {keystore_path}\n"
f"\tException: {err}") from err
shutil.rmtree( keystore_path )
except ( shutil.Error, FileNotFoundError ) as err:
raise RuntimeError(
f"Failed to delete dir: {keystore_path}\n"
f"\tException: {err}"
) from err
_sync_accounts()
def remove_address(address):
def remove_address( address ):
"""
:param address: The 'one1...' address to be removed.
"""
for name in get_accounts(address):
remove_account(name)
for name in get_accounts( address ):
remove_account( name )
_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 timeout: Optional timeout in seconds
@ -302,82 +361,129 @@ def single_call(command, timeout=60, error_ok=False):
:returns: Decoded string of response from hmy CLI call
:raises: RuntimeError if bad command
"""
command_toks = [_binary_path] + _make_call_command(command)
command_toks = [ binary_path() ] + _make_call_command( command )
try:
return subprocess.check_output(command_toks, env=environment, timeout=timeout).decode()
return subprocess.check_output(
command_toks,
env = environment,
timeout = timeout
).decode()
except subprocess.CalledProcessError as err:
if not error_ok:
raise RuntimeError(f"Bad CLI args: `{command}`\n "
f"\tException: {err}") from err
raise RuntimeError(
f"Bad CLI args: `{command}`\n "
f"\tException: {err}"
) from err
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 timeout: Optional timeout in seconds
:returns: A pexpect child program
:raises: RuntimeError if bad command
"""
command_toks = _make_call_command(command)
command_toks = _make_call_command( command )
try:
proc = pexpect.spawn(f"{_binary_path}", command_toks, env=environment, timeout=timeout)
proc = pexpect.spawn(
f"{binary_path()}",
command_toks,
env = environment,
timeout = timeout
)
proc.delaybeforesend = None
except pexpect.ExceptionPexpect as err:
raise RuntimeError(f"Bad CLI args: `{command}`\n "
f"\tException: {err}") from err
raise RuntimeError(
f"Bad CLI args: `{command}`\n "
f"\tException: {err}"
) from err
return proc
def download(path="./bin/hmy", replace=True, verbose=True):
"""
Download the CLI binary to the specified path.
Related files will be saved in the same directory.
def download( path = "./bin/hmy", replace = True, verbose = True ):
"""Download the CLI binary to the specified path. Related files will be
saved in the same directory.
:param path: The desired path (absolute or relative) of the saved binary.
:param replace: A flag to force a replacement of the binary/file.
:param verbose: A flag to enable a report message once the binary is downloaded.
:returns the environment to run the saved CLI binary.
"""
path = os.path.realpath(path)
parent_dir = Path(path).parent
assert not os.path.isdir(path), f"path `{path}` must specify a file, not a directory."
path = os.path.realpath( path )
parent_dir = Path( path ).parent
assert not os.path.isdir(
path
), 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()
os.makedirs(parent_dir, exist_ok=True)
os.chdir(parent_dir)
hmy_script_path = os.path.join(parent_dir, "hmy.sh")
with open(hmy_script_path, 'w') as f:
f.write(requests.get("https://raw.githubusercontent.com/harmony-one/go-sdk/master/scripts/hmy.sh")
.content.decode())
os.chmod(hmy_script_path, os.stat(hmy_script_path).st_mode | stat.S_IEXEC)
os.makedirs( parent_dir, exist_ok = True )
os.chdir( parent_dir )
hmy_script_path = os.path.join( parent_dir, "hmy.sh" )
with open( hmy_script_path, "w", encoding = 'utf8' ) as script_file:
script_file.write(
requests.get(
"https://raw.githubusercontent.com/harmony-one/go-sdk/master/scripts/hmy.sh"
).content.decode()
)
os.chmod(
hmy_script_path,
os.stat( hmy_script_path ).st_mode | stat.S_IEXEC
)
same_name_file = False
if os.path.exists(os.path.join(parent_dir, "hmy")) and Path(path).name != "hmy": # Save same name file.
if (
os.path.exists( os.path.join( parent_dir,
"hmy" ) ) and
Path( path ).name != "hmy"
): # Save same name file.
same_name_file = True
os.rename(os.path.join(parent_dir, "hmy"), os.path.join(parent_dir, ".hmy_tmp"))
os.rename(
os.path.join( parent_dir,
"hmy" ),
os.path.join( parent_dir,
".hmy_tmp" )
)
if verbose:
subprocess.call([hmy_script_path, '-d'])
subprocess.call( [ hmy_script_path, "-d" ] )
else:
subprocess.call([hmy_script_path, '-d'], stdout=open(os.devnull, 'w'), stderr=subprocess.STDOUT)
os.rename(os.path.join(parent_dir, "hmy"), path)
with open( os.devnull, "w", encoding = "UTF-8" ) as devnull:
subprocess.call(
[ hmy_script_path,
"-d" ],
stdout = devnull,
stderr = subprocess.STDOUT,
)
os.rename( os.path.join( parent_dir, "hmy" ), path )
if same_name_file:
os.rename(os.path.join(parent_dir, ".hmy_tmp"), os.path.join(parent_dir, "hmy"))
os.rename(
os.path.join( parent_dir,
".hmy_tmp" ),
os.path.join( parent_dir,
"hmy" )
)
if verbose:
print(f"Saved harmony binary to: `{path}`")
os.chdir(old_cwd)
print( f"Saved harmony binary to: `{path}`" )
os.chdir( old_cwd )
env = os.environ.copy()
if sys.platform.startswith("darwin"): # Dynamic linking for darwin
if sys.platform.startswith( "darwin" ): # Dynamic linking for darwin
try:
files_in_parent_dir = set(os.listdir(parent_dir))
if files_in_parent_dir.intersection(_libs) == _libs:
env["DYLD_FALLBACK_LIBRARY_PATH"] = parent_dir
elif os.path.exists(f"{get_gopath()}/src/github.com/harmony-one/bls") \
and os.path.exists(f"{get_gopath()}/src/github.com/harmony-one/mcl"):
env.update(get_bls_build_variables())
files_in_parent_dir = set( os.listdir( parent_dir ) )
if files_in_parent_dir.intersection( _libs ) == _libs:
env[ "DYLD_FALLBACK_LIBRARY_PATH" ] = parent_dir
elif os.path.exists(
f"{get_gopath()}/src/github.com/harmony-one/bls"
) and os.path.exists(
f"{get_gopath()}/src/github.com/harmony-one/mcl"
):
env.update( get_bls_build_variables() )
else:
raise RuntimeWarning(f"Could not get environment for downloaded hmy CLI at `{path}`")
except Exception as e:
raise RuntimeWarning(f"Could not get environment for downloaded hmy CLI at `{path}`") from e
raise RuntimeWarning(
f"Could not get environment for downloaded hmy CLI at `{path}`"
)
except Exception as exception:
raise RuntimeWarning(
f"Could not get environment for downloaded hmy CLI at `{path}`"
) from exception
return env

@ -0,0 +1,18 @@
"""
Constants
"""
# localnet constants
DEFAULT_ENDPOINT = 'http://localhost:9500'
DEFAULT_TIMEOUT = 30
# staking percentage constants
PRECISION = 18
MAX_DECIMAL = 1000000000000000000
NAME_CHAR_LIMIT = 140
IDENTITY_CHAR_LIMIT = 140
WEBSITE_CHAR_LIMIT = 140
SECURITY_CONTACT_CHAR_LIMIT = 140
DETAILS_CHAR_LIMIT = 280
MIN_REQUIRED_DELEGATION = int( 10000 * 1e18 )

@ -1,27 +1,36 @@
from .rpc.request import (
rpc_request
)
"""
Basic smart contract functions on Harmony
For full ABI driven interaction, use something like web3py or brownie
"""
from .transaction import (
get_transaction_receipt
)
from .rpc.request import rpc_request
from .transaction import get_transaction_receipt
_default_endpoint = 'http://localhost:9500'
_default_timeout = 30
from .exceptions import InvalidRPCReplyError
from .constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT
#########################
# Smart contract RPCs
#########################
def call(to, block_num, from_address=None, gas=None, gas_price=None, value=None, data=None,
endpoint=_default_endpoint, timeout=_default_timeout) -> str:
"""
Execute a smart contract without saving state
def call( # pylint: disable=too-many-arguments
to_address,
block_num,
from_address=None,
gas=None,
gas_price=None,
value=None,
data=None,
endpoint=DEFAULT_ENDPOINT,
timeout=DEFAULT_TIMEOUT,
) -> str:
"""Execute a smart contract without saving state.
Parameters
----------
to: :obj:`str`
to_address: :obj:`str`
Address of the smart contract
block_num: :obj:`int`
Block number to execute the contract for
@ -48,7 +57,7 @@ def call(to, block_num, from_address=None, gas=None, gas_price=None, value=None,
Raises
------
InvalidRPCReplyError
If received unknown result from endpoint, or
If received unknown result from exceptionndpoint, or
API Reference
-------------
@ -56,29 +65,42 @@ def call(to, block_num, from_address=None, gas=None, gas_price=None, value=None,
"""
params = [
{
'to': to,
'from': from_address,
'gas': gas,
'gasPrice': gas_price,
'value': value,
'data': data
"to": to_address,
"from": from_address,
"gas": gas,
"gasPrice": gas_price,
"value": value,
"data": data,
},
block_num
block_num,
]
method = 'hmyv2_call'
method = "hmyv2_call"
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def estimate_gas(to, from_address=None, gas=None, gas_price=None, value=None, data=None,
endpoint=_default_endpoint, timeout=_default_timeout) -> int:
"""
Estimate the gas price needed for a smart contract call
def estimate_gas( # pylint: disable=too-many-arguments
to_address,
from_address=None,
gas=None,
gas_price=None,
value=None,
data=None,
endpoint=DEFAULT_ENDPOINT,
timeout=DEFAULT_TIMEOUT,
) -> int:
"""Estimate the gas price needed for a smart contract call.
Parameters
----------
to: :obj:`str`
to_address: :obj:`str`
Address of the smart contract
from_address: :obj:`str`, optional
Wallet address
@ -103,29 +125,45 @@ def estimate_gas(to, from_address=None, gas=None, gas_price=None, value=None, da
Raises
------
InvalidRPCReplyError
If received unknown result from endpoint, or
If received unknown result from exceptionndpoint, or
API Reference
-------------
https://api.hmny.io/?version=latest#b9bbfe71-8127-4dda-b26c-ff95c4c22abd
"""
params = [ {
'to': to,
'from': from_address,
'gas': gas,
'gasPrice': gas_price,
'value': value,
'data': data
} ]
method = 'hmyv2_estimateGas'
params = [
{
"to": to_address,
"from": from_address,
"gas": gas,
"gasPrice": gas_price,
"value": value,
"data": data,
}
]
method = "hmyv2_estimateGas"
try:
return int(rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result'], 16)
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return int(
rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ],
16,
)
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_code(address, block_num, endpoint=_default_endpoint, timeout=_default_timeout) -> str:
"""
Get the code stored at the given address in the state for the given block number
def get_code(
address,
block_num,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> str:
"""Get the code stored at the given address in the state for the given
block number.
Parameters
----------
@ -146,26 +184,35 @@ def get_code(address, block_num, endpoint=_default_endpoint, timeout=_default_ti
Raises
------
InvalidRPCReplyError
If received unknown result from endpoint, or
If received unknown result from exceptionndpoint, or
API Reference
-------------
https://api.hmny.io/?version=latest#e13e9d78-9322-4dc8-8917-f2e721a8e556
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/contract.go#L59
"""
params = [
address,
block_num
]
method = 'hmyv2_getCode'
params = [ address, block_num ]
method = "hmyv2_getCode"
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_storage_at(address, key, block_num, endpoint=_default_endpoint, timeout=_default_timeout) -> str:
"""
Get the storage from the state at the given address, the key and the block number
def get_storage_at(
address,
key,
block_num,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> str:
"""Get the storage from the state at the given address, the key and the
block number.
Parameters
----------
@ -188,28 +235,33 @@ def get_storage_at(address, key, block_num, endpoint=_default_endpoint, timeout=
Raises
------
InvalidRPCReplyError
If received unknown result from endpoint, or
If received unknown result from exceptionndpoint, or
API Reference
-------------
https://api.hmny.io/?version=latest#fa8ac8bd-952d-4149-968c-857ca76da43f
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/contract.go#L84
"""
params = [
address,
key,
block_num
]
method = 'hmyv2_getStorageAt'
params = [ address, key, block_num ]
method = "hmyv2_getStorageAt"
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_contract_address_from_hash(tx_hash, endpoint=_default_endpoint, timeout=_default_timeout) -> str:
"""
Get address of the contract which was deployed in the transaction
represented by tx_hash
def get_contract_address_from_hash(
tx_hash,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> str:
"""Get address of the contract which was deployed in the transaction
represented by tx_hash.
Parameters
----------
@ -228,13 +280,18 @@ def get_contract_address_from_hash(tx_hash, endpoint=_default_endpoint, timeout=
Raises
------
InvalidRPCReplyError
If received unknown result from endpoint, or
If received unknown result from exceptionndpoint, or
API Reference
-------------
https://github.com/harmony-one/harmony-test/blob/master/localnet/rpc_tests/test_contract.py#L36
"""
try:
return get_transaction_receipt(tx_hash, endpoint, timeout)["contractAddress"]
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return get_transaction_receipt( tx_hash,
endpoint,
timeout )[ "contractAddress" ]
except KeyError as exception:
raise InvalidRPCReplyError(
"hmyv2_getTransactionReceipt",
endpoint
) from exception

@ -1,44 +1,38 @@
from .rpc.exceptions import (
RPCError,
RequestsError,
RequestsTimeoutError
)
class InvalidRPCReplyError(RuntimeError):
"""
Exception raised when RPC call returns unexpected result
Generally indicates Harmony API has been updated & pyhmy library needs to be updated as well
"""
def __init__(self, method, endpoint):
super().__init__(f'Unexpected reply for {method} from {endpoint}')
class InvalidValidatorError(ValueError):
"""
Exception raised Validator does not pass sanity checks
"""
"""
Exceptions used by pyhmy
"""
class InvalidRPCReplyError( RuntimeError ):
"""Exception raised when RPC call returns unexpected result Generally
indicates Harmony API has been updated & pyhmy library needs to be updated
as well."""
def __init__( self, method, endpoint ):
super().__init__( f"Unexpected reply for {method} from {endpoint}" )
class InvalidValidatorError( ValueError ):
"""Exception raised Validator does not pass sanity checks."""
errors = {
1: 'Invalid ONE address',
2: 'Field not initialized',
3: 'Invalid field input',
4: 'Error checking blockchain',
5: 'Unable to import validator information from blockchain'
1: "Invalid ONE address",
2: "Field not initialized",
3: "Invalid field input",
4: "Error checking 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.msg = msg
super().__init__(msg)
super().__init__( msg )
def __str__(self):
return f'[Errno {self.code}] {self.errors[self.code]}: {self.msg}'
def __str__( self ):
return f"[Errno {self.code}] {self.errors[self.code]}: {self.msg}"
class TxConfirmationTimedoutError(AssertionError):
"""
Exception raised when a transaction is sent to the chain
But not confirmed during the timeout period specified
"""
def __init__(self, msg):
super().__init__(f'{msg}')
class TxConfirmationTimedoutError( AssertionError ):
"""Exception raised when a transaction is sent to the chain But not
confirmed during the timeout period specified."""
def __init__( self, msg ):
super().__init__( f"{msg}" )

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

@ -1,12 +1,15 @@
from decimal import Decimal
"""
Handles conversion of ONE to ATTO and vice versa
For more granular conversions, see Web3.toWei
"""
from decimal import Decimal
_conversion_unit = Decimal(1e18)
_conversion_unit = Decimal( 1e18 )
def convert_atto_to_one(atto) -> Decimal:
"""
Convert ATTO to ONE
def convert_atto_to_one( atto ) -> Decimal:
"""Convert ATTO to ONE.
Parameters
----------
@ -19,14 +22,13 @@ def convert_atto_to_one(atto) -> Decimal:
decimal
Converted value in ONE
"""
if isinstance(atto, float):
atto = int(atto)
return Decimal(atto) / _conversion_unit
if isinstance( atto, float ):
atto = int( atto )
return Decimal( atto ) / _conversion_unit
def convert_one_to_atto(one) -> Decimal:
"""
Convert ONE to ATTO
def convert_one_to_atto( one ) -> Decimal:
"""Convert ONE to ATTO.
Parameters
----------
@ -38,6 +40,6 @@ def convert_one_to_atto(one) -> Decimal:
decimal
Converted value in ATTO
"""
if isinstance(one, float):
one = str(one)
return Decimal(one) * _conversion_unit
if isinstance( one, float ):
one = str( one )
return Decimal( one ) * _conversion_unit

@ -1,27 +1,26 @@
import requests
"""
RPC Specific Exceptions
"""
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
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):
"""
Wrapper for requests lib exceptions
"""
def __init__(self, endpoint):
super().__init__(f'Error connecting to {endpoint}')
class RequestsError( requests.exceptions.RequestException ):
"""Wrapper for requests lib exceptions."""
def __init__( self, endpoint ):
super().__init__( f"Error connecting to {endpoint}" )
class RequestsTimeoutError(requests.exceptions.Timeout):
"""
Wrapper for requests lib Timeout exceptions
"""
def __init__(self, endpoint):
super().__init__(f'Error connecting to {endpoint}')
class RequestsTimeoutError( requests.exceptions.Timeout ):
"""Wrapper for requests lib Timeout exceptions."""
def __init__( self, endpoint ):
super().__init__( f"Error connecting to {endpoint}" )

@ -1,21 +1,22 @@
"""
RPC wrapper around requests library
"""
import json
import requests
from .exceptions import (
RequestsError,
RequestsTimeoutError,
RPCError
)
from .exceptions import RequestsError, RequestsTimeoutError, RPCError
_default_endpoint = 'http://localhost:9500'
_default_timeout = 30
from ..constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT
def base_request(method, params=None, endpoint=_default_endpoint, timeout=_default_timeout) -> str:
"""
Basic RPC request
def base_request(
method,
params = None,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> str:
"""Basic RPC request.
Parameters
---------
@ -44,8 +45,8 @@ def base_request(method, params=None, endpoint=_default_endpoint, timeout=_defau
"""
if params is None:
params = []
elif not isinstance(params, list):
raise TypeError(f'invalid type {params.__class__}')
elif not isinstance( params, list ):
raise TypeError( f"invalid type {params.__class__}" )
try:
payload = {
@ -55,21 +56,31 @@ def base_request(method, params=None, endpoint=_default_endpoint, timeout=_defau
"params": params
}
headers = {
'Content-Type': 'application/json'
"Content-Type": "application/json"
}
resp = requests.request('POST', endpoint, headers=headers, data=json.dumps(payload),
timeout=timeout, allow_redirects=True)
resp = requests.request(
"POST",
endpoint,
headers = headers,
data = json.dumps( payload ),
timeout = timeout,
allow_redirects = True,
)
return resp.content
except requests.exceptions.Timeout as err:
raise RequestsTimeoutError(endpoint) from err
raise RequestsTimeoutError( endpoint ) from err
except requests.exceptions.RequestException as err:
raise RequestsError(endpoint) from err
raise RequestsError( endpoint ) from err
def rpc_request(method, params=None, endpoint=_default_endpoint, timeout=_default_timeout) -> dict:
"""
RPC request
def rpc_request(
method,
params = None,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict:
"""RPC request.
Parameters
---------
@ -102,15 +113,12 @@ def rpc_request(method, params=None, endpoint=_default_endpoint, timeout=_defaul
--------
base_request
"""
raw_resp = base_request(method, params, endpoint, timeout)
raw_resp = base_request( method, params, endpoint, timeout )
try:
resp = json.loads(raw_resp)
if 'error' in resp:
raise RPCError(method, endpoint, str(resp['error']))
resp = json.loads( raw_resp )
if "error" in resp:
raise RPCError( method, endpoint, str( resp[ "error" ] ) )
return resp
except json.decoder.JSONDecodeError as err:
raise RPCError(method, endpoint, raw_resp) from err
# TODO: Add GET requests
raise RPCError( method, endpoint, raw_resp ) from err

@ -1,133 +1,163 @@
import rlp
"""
Sign Harmony or Ethereum transactions
Harmony staking transaction signing is not covered by this module
"""
from eth_utils.curried import (
keccak,
to_int,
hexstr_if_str,
apply_formatters_to_dict
)
# pylint: disable=protected-access, no-member
from rlp.sedes import (
big_endian_int,
Binary,
binary
)
from functools import partial
from toolz import dissoc, pipe, merge
from eth_account import (
Account
)
import rlp
from eth_rlp import (
HashableRLP
)
from eth_utils.curried import keccak, to_int, hexstr_if_str, apply_formatters_to_dict
from hexbytes import (
HexBytes
)
from rlp.sedes import big_endian_int, Binary, binary
from eth_account._utils.signing import (
sign_transaction_hash
)
from eth_rlp import HashableRLP
from eth_account._utils.transactions import (
from hexbytes import HexBytes
from eth_account import Account
from eth_account.datastructures import SignedTransaction
from eth_account._utils.legacy_transactions import (
Transaction as SignedEthereumTxData,
UnsignedTransaction as UnsignedEthereumTxData,
TRANSACTION_FORMATTERS as ETHEREUM_FORMATTERS,
LEGACY_TRANSACTION_FORMATTERS as ETHEREUM_FORMATTERS,
TRANSACTION_DEFAULTS,
chain_id_to_v,
UNSIGNED_TRANSACTION_FIELDS
)
from cytoolz import (
dissoc,
pipe,
merge,
partial
)
from eth_account._utils.signing import sign_transaction_hash
from eth_account.datastructures import (
SignedTransaction
)
from .util import (
chain_id_to_int,
convert_one_to_hex
)
from .util import chain_id_to_int, convert_one_to_hex
HARMONY_FORMATTERS = dict(
ETHEREUM_FORMATTERS,
shardID=hexstr_if_str(to_int), # additional fields for Harmony transaction
toShardID=hexstr_if_str(to_int), # which may be cross shard
)
)
class UnsignedHarmonyTxData(HashableRLP):
class UnsignedHarmonyTxData( HashableRLP ):
"""
Unsigned Harmony transaction data
Includes `shardID` and `toShardID`
as the difference against Eth
"""
fields = (
('nonce', big_endian_int),
('gasPrice', big_endian_int),
('gas', big_endian_int),
('shardID', big_endian_int),
('toShardID', big_endian_int),
('to', Binary.fixed_length(20, allow_empty=True)),
('value', big_endian_int),
('data', binary),
( "nonce",
big_endian_int ),
( "gasPrice",
big_endian_int ),
( "gas",
big_endian_int ),
( "shardID",
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
Includes `shardID` and `toShardID`
as the difference against Eth
"""
fields = UnsignedHarmonyTxData._meta.fields + (
("v", big_endian_int), # Recovery value + 27
("r", big_endian_int), # First 32 bytes
("s", big_endian_int), # Next 32 bytes
)
def encode_transaction(unsigned_transaction, vrs): # https://github.com/ethereum/eth-account/blob/00e7b10005c5fa7090086fcef37a76296c524e17/eth_account/_utils/transactions.py#L55
'''serialize and encode an unsigned transaction with v,r,s'''
(v, r, s) = vrs
# https://github.com/ethereum/eth-account/blob/00e7b10005c5fa7090086fcef37a76296c524e17/eth_account/_utils/transactions.py#L55
def encode_transaction( unsigned_transaction, vrs ):
"""serialize and encode an unsigned transaction with v,r,s."""
( v, r, s ) = vrs # pylint: disable=invalid-name
chain_naive_transaction = dissoc(
unsigned_transaction.as_dict(), 'v', 'r', 's')
if isinstance(unsigned_transaction, (UnsignedHarmonyTxData,
SignedHarmonyTxData)):
unsigned_transaction.as_dict(),
"v",
"r",
"s"
)
if isinstance(
unsigned_transaction,
( UnsignedHarmonyTxData,
SignedHarmonyTxData )
):
serializer = SignedHarmonyTxData
else:
serializer = SignedEthereumTxData
signed_transaction = serializer(v=v, r=r, s=s, **chain_naive_transaction)
return rlp.encode(signed_transaction)
signed_transaction = serializer(
v = v,
r = r,
s = s,
**chain_naive_transaction
)
return rlp.encode( signed_transaction )
def serialize_transaction(filled_transaction):
'''serialize a signed/unsigned transaction'''
if 'v' in filled_transaction:
if 'shardID' in filled_transaction:
def serialize_transaction( filled_transaction ):
"""serialize a signed/unsigned transaction."""
if "v" in filled_transaction:
if "shardID" in filled_transaction:
serializer = SignedHarmonyTxData
else:
serializer = SignedEthereumTxData
else:
if 'shardID' in filled_transaction:
if "shardID" in filled_transaction:
serializer = UnsignedHarmonyTxData
else:
serializer = UnsignedEthereumTxData
for f, _ in serializer._meta.fields:
assert f in filled_transaction, f'Could not find {f} in transaction'
return serializer.from_dict({f: filled_transaction[f] for f, _ in serializer._meta.fields})
def sanitize_transaction(transaction_dict, private_key):
'''remove the originating address from the dict and convert chainId to int'''
account = Account.from_key(private_key) # get account, from which you can derive public + private key
transaction_dict = transaction_dict.copy() # do not alter the original dictionary
if 'from' in transaction_dict:
transaction_dict[ 'from' ] = convert_one_to_hex( transaction_dict[ 'from' ] )
if transaction_dict[ 'from' ] == account.address: # https://github.com/ethereum/eth-account/blob/00e7b10005c5fa7090086fcef37a76296c524e17/eth_account/account.py#L650
sanitized_transaction = dissoc(transaction_dict, 'from')
for field, _ in serializer._meta.fields:
assert field in filled_transaction, f"Could not find {field} in transaction"
return serializer.from_dict(
{
field: filled_transaction[ field ]
for field,
_ in serializer._meta.fields
}
)
# https://github.com/ethereum/eth-account/blob/00e7b10005c5fa7090086fcef37a76296c524e17/eth_account/account.py#L650
def sanitize_transaction( transaction_dict, private_key ):
"""remove the originating address from the dict and convert chainId to
int."""
account = Account.from_key( # pylint: disable=no-value-for-parameter
private_key
)
sanitized_transaction = transaction_dict.copy(
) # do not alter the original dictionary
if "from" in sanitized_transaction:
sanitized_transaction[ "from" ] = convert_one_to_hex(
transaction_dict[ "from" ]
)
if sanitized_transaction[ "from" ] == account.address:
sanitized_transaction = dissoc( sanitized_transaction, "from" )
else:
raise TypeError("from field must match key's %s, but it was %s" % (
account.address,
transaction_dict['from'],
))
if 'chainId' in transaction_dict:
transaction_dict[ 'chainId' ] = chain_id_to_int( transaction_dict[ 'chainId' ] )
return account, transaction_dict
def sign_transaction(transaction_dict, private_key) -> SignedTransaction:
"""
Sign a (non-staking) transaction dictionary with the specified private key
raise TypeError(
"from field must match key's {account.address}, "
"but it was {sanitized_transaction['from']}"
)
if "chainId" in sanitized_transaction:
sanitized_transaction[ "chainId" ] = chain_id_to_int(
sanitized_transaction[ "chainId" ]
)
return account, sanitized_transaction
def sign_transaction( transaction_dict, private_key ) -> SignedTransaction:
"""Sign a (non-staking) transaction dictionary with the specified private
key.
Parameters
----------
@ -171,30 +201,50 @@ def sign_transaction(transaction_dict, private_key) -> SignedTransaction:
https://readthedocs.org/projects/eth-account/downloads/pdf/stable/
"""
account, sanitized_transaction = sanitize_transaction(transaction_dict, private_key)
if 'to' in sanitized_transaction and sanitized_transaction[ 'to' ] is not None:
sanitized_transaction[ 'to' ] = convert_one_to_hex( sanitized_transaction[ 'to' ] )
filled_transaction = pipe( # https://github.com/ethereum/eth-account/blob/00e7b10005c5fa7090086fcef37a76296c524e17/eth_account/_utils/transactions.py#L39
if "to" in sanitized_transaction and 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
filled_transaction = pipe(
sanitized_transaction,
dict,
partial(merge, TRANSACTION_DEFAULTS),
partial( merge,
TRANSACTION_DEFAULTS ),
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()
if isinstance(unsigned_transaction, (UnsignedEthereumTxData, UnsignedHarmonyTxData)):
chain_id = None # 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(
unsigned_transaction,
( UnsignedEthereumTxData,
UnsignedHarmonyTxData )
):
chain_id = None
else:
chain_id = unsigned_transaction.v
(v, r, s) = sign_transaction_hash(
account._key_obj, transaction_hash, chain_id)
encoded_transaction = encode_transaction(unsigned_transaction, vrs=(v, r, s))
signed_transaction_hash = keccak(encoded_transaction)
( v, # pylint: disable=invalid-name
r, # pylint: disable=invalid-name
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(
rawTransaction=HexBytes(encoded_transaction),
hash=HexBytes(signed_transaction_hash),
r=r,
s=s,
v=v,
rawTransaction = HexBytes( encoded_transaction ),
hash = HexBytes( signed_transaction_hash ),
r = r,
s = s,
v = v,
)

@ -1,16 +1,22 @@
from .rpc.request import (
rpc_request
)
"""
Call Harmony's staking API
"""
from .rpc.request import rpc_request
from .exceptions import InvalidRPCReplyError
from .constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT
_default_endpoint = 'http://localhost:9500'
_default_timeout = 30
##################
# Validator RPCs #
##################
def get_all_validator_addresses(endpoint=_default_endpoint, timeout=_default_timeout) -> list:
"""
Get list of all created validator addresses on chain
def get_all_validator_addresses(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list:
"""Get list of all created validator addresses on chain.
Parameters
----------
@ -33,15 +39,21 @@ def get_all_validator_addresses(endpoint=_default_endpoint, timeout=_default_tim
-------------
https://api.hmny.io/#69b93657-8d3c-4d20-9c9f-e51f08c9b3f5
"""
method = 'hmyv2_getAllValidatorAddresses'
method = "hmyv2_getAllValidatorAddresses"
try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_validator_information(validator_addr, endpoint=_default_endpoint, timeout=_default_timeout) -> dict:
"""
Get validator information for validator address
def get_validator_information(
validator_addr,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict:
"""Get validator information for validator address.
Parameters
----------
@ -60,7 +72,8 @@ def get_validator_information(validator_addr, endpoint=_default_endpoint, timeou
bls-public-keys: :obj:`list` List of associated public BLS keys
last-epoch-in-committee: :obj:`int` Last epoch any key of the validator was elected
min-self-delegation: :obj:`int` Amount that validator must delegate to self in ATTO
max-total-delegation: :obj:`int` Total amount that validator will aceept delegations until, in ATTO
max-total-delegation: :obj:`int`
Total amount that validator will aceept delegations until, in ATTO
rate: :obj:`str` Current commission rate
max-rate: :obj:`str` Max commission rate a validator can charge
max-change-rate: :obj:`str` Maximum amount the commission rate can increase in one epoch
@ -71,8 +84,9 @@ def get_validator_information(validator_addr, endpoint=_default_endpoint, timeou
security-contact: :obj:`str` Method to contact the validators
details: :obj:`str` Validator details, displayed on the Staking Dashboard
creation-height: :obj:`int` Block number in which the validator was created
delegations: :obj:`list` List of delegations, see get_delegations_by_delegator for format
metrics: :obj:`dict` BLS key earning metrics for current epoch (or None if no earnings in the current epoch)
delegations: :obj:`list`
List of delegations, see get_delegations_by_delegator for format
metrics: :obj:`dict` BLS key earning metrics for current epoch (or None)
by-bls-key: :obj:`list` List of dictionaries, each with the following keys
key: :obj:`dict` Dictionary with the following keys
bls-public-key: :obj:`str` BLS public key
@ -85,7 +99,8 @@ def get_validator_information(validator_addr, endpoint=_default_endpoint, timeou
earned-reward: :obj:`int` Lifetime reward key has earned
total-delegation: :obj:`int` Total amount delegated to validator
currently-in-committee: :obj:`bool` if key is currently elected
epos-status: :obj:`str` Currently elected, eligible to be elected next epoch, or not eligible to be elected next epoch
epos-status: :obj:`str` Currently elected, eligible to be elected next epoch,
or not eligible to be elected next epoch
epos-winning-stake: :obj:`str` Total effective stake of the validator
booted-status: :obj:`str` Banned status
active-status: :obj:`str` Active or inactive
@ -108,18 +123,24 @@ def get_validator_information(validator_addr, endpoint=_default_endpoint, timeou
-------------
https://api.hmny.io/#659ad999-14ca-4498-8f74-08ed347cab49
"""
method = 'hmyv2_getValidatorInformation'
params = [
validator_addr
]
method = "hmyv2_getValidatorInformation"
params = [ validator_addr ]
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
def get_elected_validator_addresses(endpoint=_default_endpoint, timeout=_default_timeout) -> list:
"""
Get list of elected validator addresses
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_elected_validator_addresses(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list:
"""Get list of elected validator addresses.
Parameters
----------
@ -143,15 +164,21 @@ def get_elected_validator_addresses(endpoint=_default_endpoint, timeout=_default
-------------
https://api.hmny.io/#e90a6131-d67c-4110-96ef-b283d452632d
"""
method = 'hmyv2_getElectedValidatorAddresses'
method = "hmyv2_getElectedValidatorAddresses"
try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_validators(epoch, endpoint=_default_endpoint, timeout=_default_timeout) -> list:
"""
Get validators list for a particular epoch
def get_validators(
epoch,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list:
"""Get validators list for a particular epoch.
Parameters
----------
@ -179,18 +206,25 @@ def get_validators(epoch, endpoint=_default_endpoint, timeout=_default_timeout)
-------------
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L152
"""
method = 'hmyv2_getValidators'
params = [
epoch
]
method = "hmyv2_getValidators"
params = [ epoch ]
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
def get_validator_keys(epoch, endpoint=_default_endpoint, timeout=_default_timeout) -> list:
"""
Get validator BLS keys in the committee for a particular epoch
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_validator_keys(
epoch,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list:
"""Get validator BLS keys in the committee for a particular epoch.
Parameters
----------
@ -214,18 +248,26 @@ def get_validator_keys(epoch, endpoint=_default_endpoint, timeout=_default_timeo
-------------
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L152
"""
method = 'hmyv2_getValidatorKeys'
params = [
epoch
]
method = "hmyv2_getValidatorKeys"
params = [ epoch ]
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_validator_information_by_block_number(validator_addr, block_num, endpoint=_default_endpoint, timeout=_default_timeout):
"""
Get validator information for validator address at a block
def get_validator_information_by_block_number(
validator_addr,
block_num,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
):
"""Get validator information for validator address at a block.
Parameters
----------
@ -251,24 +293,30 @@ def get_validator_information_by_block_number(validator_addr, block_num, endpoin
-------------
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L319
"""
method = 'hmyv2_getValidatorInformationByBlockNumber'
params = [
validator_addr,
block_num
]
method = "hmyv2_getValidatorInformationByBlockNumber"
params = [ validator_addr, block_num ]
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
def get_all_validator_information(page=-1, endpoint=_default_endpoint, timeout=_default_timeout) -> list:
"""
Get validator information for all validators on chain
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_all_validator_information(
page = 0,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list:
"""Get validator information for all validators on chain.
Parameters
----------
page: :obj:`int`, optional
Page to request (-1 for all validators), page size is 100 otherwise
Page to request, page size is 100 otherwise
endpoint: :obj:`str`, optional
Endpoint to send request to
timeout: :obj:`int`, optional
@ -287,18 +335,25 @@ def get_all_validator_information(page=-1, endpoint=_default_endpoint, timeout=_
-------------
https://api.hmny.io/#df5f1631-7397-48e8-87b4-8dd873235b9c
"""
method = 'hmyv2_getAllValidatorInformation'
params = [
page
]
method = "hmyv2_getAllValidatorInformation"
params = [ page ]
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
def get_validator_self_delegation(address, endpoint=_default_endpoint, timeout=_default_timeout) -> int:
"""
Get the amount self delegated by validator
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_validator_self_delegation(
address,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""Get the amount self delegated by validator.
Parameters
----------
@ -322,18 +377,27 @@ def get_validator_self_delegation(address, endpoint=_default_endpoint, timeout=_
-------------
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L352
"""
method = 'hmyv2_getValidatorSelfDelegation'
params = [
address
]
method = "hmyv2_getValidatorSelfDelegation"
params = [ address ]
try:
return int(rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result'])
except (KeyError, TypeError) as e:
raise InvalidRPCReplyError(method, endpoint) from e
def get_validator_total_delegation(address, endpoint=_default_endpoint, timeout=_default_timeout) -> int:
"""
Get the total amount delegated t ovalidator (including self delegated)
return int(
rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
)
except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_validator_total_delegation(
address,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""Get the total amount delegated t ovalidator (including self delegated)
Parameters
----------
@ -357,25 +421,35 @@ def get_validator_total_delegation(address, endpoint=_default_endpoint, timeout=
-------------
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L379
"""
method = 'hmyv2_getValidatorTotalDelegation'
params = [
address
]
method = "hmyv2_getValidatorTotalDelegation"
params = [ address ]
try:
return int(rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result'])
except (KeyError, TypeError) as e:
raise InvalidRPCReplyError(method, endpoint) from e
def get_all_validator_information_by_block_number(block_num, page=-1, endpoint=_default_endpoint, timeout=_default_timeout) -> list:
"""
Get validator information at block number for all validators on chain
return int(
rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
)
except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_all_validator_information_by_block_number(
block_num,
page = 0,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list:
"""Get validator information at block number for all validators on chain.
Parameters
----------
block_num: int
Block number to get validator information for
page: :obj:`int`, optional
Page to request (-1 for all validators), page size is 100
Page to request, page size is 100
endpoint: :obj:`str`, optional
Endpoint to send request to
timeout: :obj:`int`, optional
@ -395,27 +469,33 @@ def get_all_validator_information_by_block_number(block_num, page=-1, endpoint=_
-------------
https://api.hmny.io/#a229253f-ca76-4b9d-88f5-9fd96e40d583
"""
method = 'hmyv2_getAllValidatorInformationByBlockNumber'
params = [
page,
block_num
]
method = "hmyv2_getAllValidatorInformationByBlockNumber"
params = [ page, block_num ]
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
###################
# Delegation RPCs #
###################
def get_all_delegation_information(page=-1, endpoint=_default_endpoint, timeout=_default_timeout) -> list:
"""
Get delegation information for all delegators on chain
def get_all_delegation_information(
page = 0,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list:
"""Get delegation information for all delegators on chain.
Parameters
----------
page: :obj:`int`, optional
Page to request (-1 for all validators), page size is 100
Page to request, page size is 100
endpoint: :obj:`str`, optional
Endpoint to send request to
timeout: :obj:`int`, optional
@ -436,18 +516,25 @@ def get_all_delegation_information(page=-1, endpoint=_default_endpoint, timeout=
-------------
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L413
"""
method = 'hmyv2_getAllDelegationInformation'
params = [
page,
]
method = "hmyv2_getAllDelegationInformation"
params = [ page, ]
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_delegations_by_delegator(delegator_addr, endpoint=_default_endpoint, timeout=_default_timeout) -> list:
"""
Get list of delegations by a delegator
def get_delegations_by_delegator(
delegator_addr,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list:
"""Get list of delegations by a delegator.
Parameters
----------
@ -478,18 +565,26 @@ def get_delegations_by_delegator(delegator_addr, endpoint=_default_endpoint, tim
-------------
https://api.hmny.io/#454b032c-6072-4ecb-bf24-38b3d6d2af69
"""
method = 'hmyv2_getDelegationsByDelegator'
params = [
delegator_addr
]
method = "hmyv2_getDelegationsByDelegator"
params = [ delegator_addr ]
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_delegations_by_delegator_by_block_number(delegator_addr, block_num, endpoint=_default_endpoint, timeout=_default_timeout) -> list:
"""
Get list of delegations by a delegator at a specific block
def get_delegations_by_delegator_by_block_number(
delegator_addr,
block_num,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list:
"""Get list of delegations by a delegator at a specific block.
Parameters
----------
@ -515,20 +610,26 @@ def get_delegations_by_delegator_by_block_number(delegator_addr, block_num, endp
-------------
https://api.hmny.io/#8ce13bda-e768-47b9-9dbe-193aba410b0a
"""
method = 'hmyv2_getDelegationsByDelegatorByBlockNumber'
params = [
delegator_addr,
block_num
]
method = "hmyv2_getDelegationsByDelegatorByBlockNumber"
params = [ delegator_addr, block_num ]
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_delegation_by_delegator_and_validator(delegator_addr, validator_address,
endpoint=_default_endpoint, timeout=_default_timeout) -> dict:
"""
Get list of delegations by a delegator at a specific block
def get_delegation_by_delegator_and_validator(
delegator_addr,
validator_address,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT,
) -> dict:
"""Get list of delegations by a delegator at a specific block.
Parameters
----------
@ -543,7 +644,8 @@ def get_delegation_by_delegator_and_validator(delegator_addr, validator_address,
Returns
-------
one delegation (or None if such delegation doesn't exist), see get_delegations_by_delegator for fields
one delegation (or None if such delegation doesn't exist)
see get_delegations_by_delegator for fields
Raises
------
@ -554,19 +656,25 @@ def get_delegation_by_delegator_and_validator(delegator_addr, validator_address,
-------------
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L605
"""
method = 'hmyv2_getDelegationByDelegatorAndValidator'
params = [
delegator_addr,
validator_address
]
method = "hmyv2_getDelegationByDelegatorAndValidator"
params = [ delegator_addr, validator_address ]
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_available_redelegation_balance(delegator_addr, endpoint=_default_endpoint, timeout=_default_timeout) -> int:
"""
Get amount of locked undelegated tokens
def get_available_redelegation_balance(
delegator_addr,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""Get amount of locked undelegated tokens.
Parameters
----------
@ -590,18 +698,27 @@ def get_available_redelegation_balance(delegator_addr, endpoint=_default_endpoin
-------------
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L653
"""
method = 'hmyv2_getAvailableRedelegationBalance'
params = [
delegator_addr
]
method = "hmyv2_getAvailableRedelegationBalance"
params = [ delegator_addr ]
try:
return int(rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result'])
except (KeyError, TypeError) as e:
raise InvalidRPCReplyError(method, endpoint) from e
def get_delegations_by_validator(validator_addr, endpoint=_default_endpoint, timeout=_default_timeout) -> list:
"""
Get list of delegations to a validator
return int(
rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
)
except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_delegations_by_validator(
validator_addr,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list:
"""Get list of delegations to a validator.
Parameters
----------
@ -625,21 +742,27 @@ def get_delegations_by_validator(validator_addr, endpoint=_default_endpoint, tim
-------------
https://api.hmny.io/#2e02d8db-8fec-41d9-a672-2c9862f63f39
"""
method = 'hmyv2_getDelegationsByValidator'
params = [
validator_addr
]
method = "hmyv2_getDelegationsByValidator"
params = [ validator_addr ]
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
########################
# Staking Network RPCs #
########################
def get_current_utility_metrics(endpoint=_default_endpoint, timeout=_default_timeout) -> dict:
"""
Get current utility metrics of network
def get_current_utility_metrics(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict:
"""Get current utility metrics of network.
Parameters
----------
@ -665,15 +788,20 @@ def get_current_utility_metrics(endpoint=_default_endpoint, timeout=_default_tim
-------------
https://api.hmny.io/#78dd2d94-9ff1-4e0c-bbac-b4eec1cdf10b
"""
method = 'hmyv2_getCurrentUtilityMetrics'
method = "hmyv2_getCurrentUtilityMetrics"
try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_staking_network_info(endpoint=_default_endpoint, timeout=_default_timeout) -> dict:
"""
Get staking network information
def get_staking_network_info(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict:
"""Get staking network information.
Parameters
----------
@ -700,15 +828,20 @@ def get_staking_network_info(endpoint=_default_endpoint, timeout=_default_timeou
-------------
https://api.hmny.io/#4a10fce0-2aa4-4583-bdcb-81ee0800993b
"""
method = 'hmyv2_getStakingNetworkInfo'
method = "hmyv2_getStakingNetworkInfo"
try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_super_committees(endpoint=_default_endpoint, timeout=_default_timeout) -> dict:
"""
Get voting committees for current & previous epoch
def get_super_committees(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict:
"""Get voting committees for current & previous epoch.
Parameters
----------
@ -753,15 +886,20 @@ def get_super_committees(endpoint=_default_endpoint, timeout=_default_timeout) -
-------------
https://api.hmny.io/#8eef2fc4-92db-4610-a9cd-f7b75cfbd080
"""
method = 'hmyv2_getSuperCommittees'
method = "hmyv2_getSuperCommittees"
try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_total_staking(endpoint=_default_endpoint, timeout=_default_timeout) -> int:
"""
Get total staking by validators, only meant to be called on beaconchain
def get_total_staking(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> int:
"""Get total staking by validators, only meant to be called on beaconchain.
Parameters
----------
@ -783,15 +921,22 @@ def get_total_staking(endpoint=_default_endpoint, timeout=_default_timeout) -> i
-------------
https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/staking.go#L102
"""
method = 'hmyv2_getTotalStaking'
method = "hmyv2_getTotalStaking"
try:
return int(rpc_request(method, endpoint=endpoint, timeout=timeout)['result'])
except (KeyError, TypeError) as e:
raise InvalidRPCReplyError(method, endpoint) from e
return int(
rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
)
except ( KeyError, TypeError ) as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_raw_median_stake_snapshot(endpoint=_default_endpoint, timeout=_default_timeout) -> dict:
"""
Get median stake & additional committee data of the current epoch
def get_raw_median_stake_snapshot(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict:
"""Get median stake & additional committee data of the current epoch.
Parameters
----------
@ -826,8 +971,10 @@ def get_raw_median_stake_snapshot(endpoint=_default_endpoint, timeout=_default_t
-------------
https://api.hmny.io/#bef93b3f-6763-4121-9c17-f0b0d9e5cc40
"""
method = 'hmyv2_getMedianRawStakeSnapshot'
method = "hmyv2_getMedianRawStakeSnapshot"
try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception

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

@ -1,218 +1,325 @@
from enum import (
Enum,
auto
)
"""
Helper module for signing Harmony staking transactions
"""
# disable most of the Lint here
# pylint: disable=protected-access,no-member,invalid-name,missing-class-docstring,missing-function-docstring
from rlp.sedes import (
big_endian_int,
Binary,
CountableList,
List,
Text
)
from eth_rlp import (
HashableRLP
)
from eth_utils.curried import (
to_int,
hexstr_if_str,
)
class StakingSettings:
PRECISION = 18
MAX_DECIMAL = 1000000000000000000
class Directive(Enum): # https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L120
def _generate_next_value_(name, start, count, last_values):
from enum import Enum, auto
from rlp.sedes import big_endian_int, Binary, CountableList, List, Text
from eth_rlp import HashableRLP
from eth_utils.curried import ( to_int, hexstr_if_str, )
# https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L120
class Directive( Enum ):
def _generate_next_value_( name, start, count, last_values ): # pylint: disable=no-self-argument
return count
CreateValidator = auto()
EditValidator = auto()
Delegate = auto()
Undelegate = auto()
CollectRewards = auto()
FORMATTERS = {
'directive': hexstr_if_str(to_int), # delegatorAddress is already formatted before the call
'nonce': hexstr_if_str(to_int),
'gasPrice': hexstr_if_str(to_int),
'gasLimit': hexstr_if_str(to_int),
'chainId': hexstr_if_str(to_int),
"directive": hexstr_if_str(
to_int
), # delegatorAddress is already formatted before the call
"nonce": hexstr_if_str(to_int),
"gasPrice": hexstr_if_str(to_int),
"gasLimit": hexstr_if_str(to_int),
"chainId": hexstr_if_str(to_int),
}
class CollectRewards:
@staticmethod
def UnsignedChainId():
class UnsignedChainId(HashableRLP):
class UnsignedChainId( HashableRLP ):
fields = (
('directive', big_endian_int),
('stakeMsg', CountableList(Binary.fixed_length(20, allow_empty=True))),
('nonce', big_endian_int),
('gasPrice', big_endian_int),
('gasLimit', big_endian_int),
('chainId', big_endian_int),
( "directive",
big_endian_int ),
(
"stakeMsg",
CountableList(
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
@staticmethod
def SignedChainId():
class SignedChainId(HashableRLP):
fields = CollectRewards.UnsignedChainId()._meta.fields[:-1] + ( # drop chainId
class SignedChainId( HashableRLP ):
fields = CollectRewards.UnsignedChainId()._meta.fields[
:-1
] + ( # drop chainId
("v", big_endian_int),
("r", big_endian_int),
("s", big_endian_int),
)
return SignedChainId
@staticmethod
def Unsigned():
class Unsigned(HashableRLP):
fields = CollectRewards.UnsignedChainId()._meta.fields[:-1] # drop chainId
class Unsigned( HashableRLP ):
fields = CollectRewards.UnsignedChainId(
)._meta.fields[ :-1 ] # drop chainId
return Unsigned
@staticmethod
def Signed():
class Signed(HashableRLP):
fields = CollectRewards.Unsigned()._meta.fields[:-3] + ( # drop last 3 for raw.pop()
class Signed( HashableRLP ):
fields = CollectRewards.Unsigned()._meta.fields[
:-3
] + ( # drop last 3 for raw.pop()
("v", big_endian_int),
("r", big_endian_int),
("s", big_endian_int),
)
return Signed
class DelegateOrUndelegate:
@staticmethod
def UnsignedChainId():
class UnsignedChainId(HashableRLP):
class UnsignedChainId( HashableRLP ):
fields = (
('directive', big_endian_int),
('stakeMsg', List([Binary.fixed_length(20, allow_empty=True),Binary.fixed_length(20, allow_empty=True),big_endian_int],True)),
('nonce', big_endian_int),
('gasPrice', big_endian_int),
('gasLimit', big_endian_int),
('chainId', big_endian_int),
( "directive",
big_endian_int ),
(
"stakeMsg",
List(
[
Binary.fixed_length( 20,
allow_empty = True ),
Binary.fixed_length( 20,
allow_empty = True ),
big_endian_int,
],
True,
),
),
( "nonce",
big_endian_int ),
( "gasPrice",
big_endian_int ),
( "gasLimit",
big_endian_int ),
( "chainId",
big_endian_int ),
)
return UnsignedChainId
@staticmethod
def SignedChainId():
class SignedChainId(HashableRLP):
fields = DelegateOrUndelegate.UnsignedChainId()._meta.fields[:-1] + ( # drop chainId
class SignedChainId( HashableRLP ):
fields = DelegateOrUndelegate.UnsignedChainId()._meta.fields[
:-1
] + ( # drop chainId
("v", big_endian_int),
("r", big_endian_int),
("s", big_endian_int),
)
return SignedChainId
@staticmethod
def Unsigned():
class Unsigned(HashableRLP):
fields = DelegateOrUndelegate.UnsignedChainId()._meta.fields[:-1] # drop chainId
class Unsigned( HashableRLP ):
fields = DelegateOrUndelegate.UnsignedChainId(
)._meta.fields[ :-1 ] # drop chainId
return Unsigned
@staticmethod
def Signed():
class Signed(HashableRLP):
fields = DelegateOrUndelegate.Unsigned()._meta.fields[:-3] + ( # drop last 3 for raw.pop()
class Signed( HashableRLP ):
fields = DelegateOrUndelegate.Unsigned()._meta.fields[
:-3
] + ( # drop last 3 for raw.pop()
("v", big_endian_int),
("r", big_endian_int),
("s", big_endian_int),
)
return Signed
class CreateValidator:
@staticmethod
def UnsignedChainId():
class UnsignedChainId(HashableRLP):
class UnsignedChainId( HashableRLP ):
fields = (
('directive', big_endian_int),
('stakeMsg', List([ # list with the following members
Binary.fixed_length(20, allow_empty=True), # validatorAddress
List([Text()]*5,True), # description is Text of 5 elements
List([List([big_endian_int],True)]*3,True), # commission rate is made up of 3 integers in an array [ [int1], [int2], [int3] ]
("directive", big_endian_int),
(
"stakeMsg",
List(
[ # list with the following members
# validatorAddress
Binary.fixed_length(
20, allow_empty=True
),
# description is Text of 5 elements
List(
[Text()] * 5, True
),
# commission rate is made up of 3 integers in an array
List(
[List([big_endian_int], True)] * 3, True
),
big_endian_int, # min self delegation
big_endian_int, # max total delegation
CountableList(Binary.fixed_length(48, allow_empty=True)), # bls-public-keys array of unspecified length, each key of 48
# bls-public-keys array of unspecified length, each key of 48
CountableList(
Binary.fixed_length(48, allow_empty=True)
),
# bls-key-sigs array of unspecified length, each sig of 96
CountableList(
Binary.fixed_length(96, allow_empty=True)
),
big_endian_int, # amount
], True)), # strictly these number of elements
('nonce', big_endian_int),
('gasPrice', big_endian_int),
('gasLimit', big_endian_int),
('chainId', big_endian_int),
],
True,
),
), # strictly these number of elements
("nonce", big_endian_int),
("gasPrice", big_endian_int),
("gasLimit", big_endian_int),
("chainId", big_endian_int),
)
return UnsignedChainId
@staticmethod
def SignedChainId():
class SignedChainId(HashableRLP):
fields = CreateValidator.UnsignedChainId()._meta.fields[:-1] + ( # drop chainId
class SignedChainId( HashableRLP ):
fields = CreateValidator.UnsignedChainId()._meta.fields[
:-1
] + ( # drop chainId
("v", big_endian_int),
("r", big_endian_int),
("s", big_endian_int),
)
return SignedChainId
@staticmethod
def Unsigned():
class Unsigned(HashableRLP):
fields = CreateValidator.UnsignedChainId()._meta.fields[:-1] # drop chainId
class Unsigned( HashableRLP ):
fields = CreateValidator.UnsignedChainId(
)._meta.fields[ :-1 ] # drop chainId
return Unsigned
@staticmethod
def Signed():
class Signed(HashableRLP):
fields = CreateValidator.Unsigned()._meta.fields[:-3] + ( # drop last 3 for raw.pop()
class Signed( HashableRLP ):
fields = CreateValidator.Unsigned()._meta.fields[
:-3
] + ( # drop last 3 for raw.pop()
("v", big_endian_int),
("r", big_endian_int),
("s", big_endian_int),
)
return Signed
class EditValidator:
@staticmethod
def UnsignedChainId():
class UnsignedChainId(HashableRLP):
class UnsignedChainId( HashableRLP ):
fields = (
('directive', big_endian_int),
('stakeMsg', List([ # list with the following members
Binary.fixed_length(20, allow_empty=True), # validatorAddress
List([Text()]*5,True), # description is Text of 5 elements
List([big_endian_int],True), # new rate is in a list
("directive", big_endian_int),
(
"stakeMsg",
List(
[ # list with the following members
# validatorAddress
Binary.fixed_length(
20, allow_empty=True
),
# description is Text of 5 elements
List(
[Text()] * 5, True
),
# new rate is in a list
List([big_endian_int], True),
big_endian_int, # min self delegation
big_endian_int, # max total delegation
Binary.fixed_length(48, allow_empty=True), # slot key to remove
Binary.fixed_length(48, allow_empty=True), # slot key to add
], True)), # strictly these number of elements
('nonce', big_endian_int),
('gasPrice', big_endian_int),
('gasLimit', big_endian_int),
('chainId', big_endian_int),
# slot key to remove
Binary.fixed_length(
48, allow_empty=True
),
# slot key to add
Binary.fixed_length(
48, allow_empty=True
),
# slot key to add sig
Binary.fixed_length(
96, allow_empty=True
),
],
True,
),
), # strictly these number of elements
("nonce", big_endian_int),
("gasPrice", big_endian_int),
("gasLimit", big_endian_int),
("chainId", big_endian_int),
)
return UnsignedChainId
@staticmethod
def SignedChainId():
class SignedChainId(HashableRLP):
fields = EditValidator.UnsignedChainId()._meta.fields[:-1] + ( # drop chainId
class SignedChainId( HashableRLP ):
fields = EditValidator.UnsignedChainId()._meta.fields[
:-1
] + ( # drop chainId
("v", big_endian_int),
("r", big_endian_int),
("s", big_endian_int),
)
return SignedChainId
@staticmethod
def Unsigned():
class Unsigned(HashableRLP):
fields = EditValidator.UnsignedChainId()._meta.fields[:-1] # drop chainId
class Unsigned( HashableRLP ):
fields = EditValidator.UnsignedChainId(
)._meta.fields[ :-1 ] # drop chainId
return Unsigned
@staticmethod
def Signed():
class Signed(HashableRLP):
fields = EditValidator.Unsigned()._meta.fields[:-3] + ( # drop last 3 for raw.pop()
class Signed( HashableRLP ):
fields = EditValidator.Unsigned()._meta.fields[
:-3
] + ( # drop last 3 for raw.pop()
("v", big_endian_int),
("r", big_endian_int),
("s", big_endian_int),
)
return Signed

@ -1,22 +1,22 @@
from .rpc.request import (
rpc_request
)
from .exceptions import (
TxConfirmationTimedoutError
)
"""
Interact with Harmony's transaction RPC API
"""
import time
import random
_default_endpoint = 'http://localhost:9500'
_default_timeout = 30
from .constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT
from .rpc.request import rpc_request
from .exceptions import TxConfirmationTimedoutError, InvalidRPCReplyError
#########################
# Transaction Pool RPCs #
#########################
def get_pending_transactions(endpoint=_default_endpoint, timeout=_default_timeout) -> list:
"""
Get list of pending transactions
def get_pending_transactions(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list:
"""Get list of pending transactions.
Parameters
----------
@ -38,15 +38,20 @@ def get_pending_transactions(endpoint=_default_endpoint, timeout=_default_timeou
-------------
https://api.hmny.io/#de6c4a12-fa42-44e8-972f-801bfde1dd18
"""
method = 'hmyv2_pendingTransactions'
method = "hmyv2_pendingTransactions"
try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_transaction_error_sink(endpoint=_default_endpoint, timeout=_default_timeout) -> list:
"""
Get current transactions error sink
def get_transaction_error_sink(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list:
"""Get current transactions error sink.
Parameters
----------
@ -71,15 +76,20 @@ def get_transaction_error_sink(endpoint=_default_endpoint, timeout=_default_time
-------------
https://api.hmny.io/#9aedbc22-6262-44b1-8276-cd8ae19fa600
"""
method = 'hmyv2_getCurrentTransactionErrorSink'
method = "hmyv2_getCurrentTransactionErrorSink"
try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_pending_staking_transactions(endpoint=_default_endpoint, timeout=_default_timeout) -> list:
"""
Get list of pending staking transactions
def get_pending_staking_transactions(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list:
"""Get list of pending staking transactions.
Parameters
----------
@ -101,16 +111,20 @@ def get_pending_staking_transactions(endpoint=_default_endpoint, timeout=_defaul
-------------
https://api.hmny.io/#de0235e4-f4c9-4a69-b6d2-b77dc1ba7b12
"""
method = 'hmyv2_pendingStakingTransactions'
method = "hmyv2_pendingStakingTransactions"
try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_staking_transaction_error_sink(endpoint=_default_endpoint, timeout=_default_timeout) -> list:
"""
Get current staking transactions error sink
def get_staking_transaction_error_sink(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list:
"""Get current staking transactions error sink.
Parameters
----------
@ -136,15 +150,21 @@ def get_staking_transaction_error_sink(endpoint=_default_endpoint, timeout=_defa
-------------
https://api.hmny.io/#bdd00e0f-2ba0-480e-b996-2ef13f10d75a
"""
method = 'hmyv2_getCurrentStakingErrorSink'
method = "hmyv2_getCurrentStakingErrorSink"
try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_pool_stats(endpoint=_default_endpoint, timeout=_default_timeout) -> dict:
"""
Get stats of the pool, that is, number of pending and queued (non-executable) transactions
def get_pool_stats(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict:
"""Get stats of the pool, that is, number of pending and queued (non-
executable) transactions.
Parameters
----------
@ -168,18 +188,24 @@ def get_pool_stats(endpoint=_default_endpoint, timeout=_default_timeout) -> dict
-------------
https://api.hmny.io/#7c2b9395-8f5e-4eb5-a687-2f1be683d83e
"""
method = 'hmyv2_getPoolStats'
method = "hmyv2_getPoolStats"
try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
####################
# Transaction RPCs #
####################
def get_transaction_by_hash(tx_hash, endpoint=_default_endpoint, timeout=_default_timeout) -> dict:
"""
Get transaction by hash
def get_transaction_by_hash(
tx_hash,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict:
"""Get transaction by hash.
Parameters
----------
@ -223,20 +249,27 @@ def get_transaction_by_hash(tx_hash, endpoint=_default_endpoint, timeout=_defaul
-------------
https://api.hmny.io/#117e84f6-a0ec-444e-abe0-455701310389
"""
method = 'hmyv2_getTransactionByHash'
params = [
tx_hash
]
method = "hmyv2_getTransactionByHash"
params = [ tx_hash ]
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_transaction_by_block_hash_and_index(block_hash, tx_index,
endpoint=_default_endpoint, timeout=_default_timeout
) -> dict:
"""
Get transaction based on index in list of transactions in a block by block hash
def get_transaction_by_block_hash_and_index(
block_hash,
tx_index,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict:
"""Get transaction based on index in list of transactions in a block by
block hash.
Parameters
----------
@ -262,21 +295,27 @@ def get_transaction_by_block_hash_and_index(block_hash, tx_index,
-------------
https://api.hmny.io/#7c7e8d90-4984-4ebe-bb7e-d7adec167503
"""
method = 'hmyv2_getTransactionByBlockHashAndIndex'
params = [
block_hash,
tx_index
]
method = "hmyv2_getTransactionByBlockHashAndIndex"
params = [ block_hash, tx_index ]
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_transaction_by_block_number_and_index(block_num, tx_index,
endpoint=_default_endpoint, timeout=_default_timeout
) -> dict:
"""
Get transaction based on index in list of transactions in a block by block number
def get_transaction_by_block_number_and_index(
block_num,
tx_index,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict:
"""Get transaction based on index in list of transactions in a block by
block number.
Parameters
----------
@ -302,19 +341,25 @@ def get_transaction_by_block_number_and_index(block_num, tx_index,
-------------
https://api.hmny.io/#bcde8b1c-6ab9-4950-9835-3c7564e49c3e
"""
method = 'hmyv2_getTransactionByBlockNumberAndIndex'
params = [
block_num,
tx_index
]
method = "hmyv2_getTransactionByBlockNumberAndIndex"
params = [ block_num, tx_index ]
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
def get_transaction_receipt(tx_hash, endpoint=_default_endpoint, timeout=_default_timeout) -> dict:
"""
Get transaction receipt corresponding to tx_hash
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_transaction_receipt(
tx_hash,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict:
"""Get transaction receipt corresponding to tx_hash.
Parameters
----------
@ -335,7 +380,9 @@ def get_transaction_receipt(tx_hash, endpoint=_default_endpoint, timeout=_defaul
from: :obj:`str` Sender wallet address
gasUsed: :obj:`int` Gas used for the transaction
logs: :obj:`list` List of logs, each being a dict with keys as follows:
address, blockHash, blockNumber, data, logIndex, removed, topics, transactionHash, transactionIndex
address, blockHash, blockNumber
data, logIndex, removed
topics, transactionHash, transactionIndex
logsBloom :obj:`str` Bloom logs
shardID :obj:`int` Shard ID
status :obj:`int` Status of transaction (0: pending, 1: success)
@ -352,18 +399,25 @@ def get_transaction_receipt(tx_hash, endpoint=_default_endpoint, timeout=_defaul
-------------
https://api.hmny.io/#0c2799f8-bcdc-41a4-b362-c3a6a763bb5e
"""
method = 'hmyv2_getTransactionReceipt'
params = [
tx_hash
]
method = "hmyv2_getTransactionReceipt"
params = [ tx_hash ]
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
def send_raw_transaction(signed_tx, endpoint=_default_endpoint, timeout=_default_timeout) -> str:
"""
Send signed transaction
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def send_raw_transaction(
signed_tx,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> str:
"""Send signed transaction.
Parameters
----------
@ -390,18 +444,25 @@ def send_raw_transaction(signed_tx, endpoint=_default_endpoint, timeout=_default
-------------
https://api.hmny.io/#f40d124a-b897-4b7c-baf3-e0dedf8f40a0
"""
params = [
signed_tx
]
method = 'hmyv2_sendRawTransaction'
params = [ signed_tx ]
method = "hmyv2_sendRawTransaction"
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
def send_and_confirm_raw_transaction(signed_tx, endpoint=_default_endpoint, timeout=_default_timeout) -> list:
"""
Send signed transaction and wait for it to be confirmed
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def send_and_confirm_raw_transaction(
signed_tx,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list:
"""Send signed transaction and wait for it to be confirmed.
Parameters
----------
@ -430,22 +491,29 @@ def send_and_confirm_raw_transaction(signed_tx, endpoint=_default_endpoint, time
-------------
https://api.hmny.io/#f40d124a-b897-4b7c-baf3-e0dedf8f40a0
"""
tx_hash = send_raw_transaction(signed_tx)
tx_hash = send_raw_transaction( signed_tx, endpoint = endpoint )
start_time = time.time()
while((time.time() - start_time) <= timeout):
tx_response = get_transaction_by_hash(tx_hash)
while ( time.time() - start_time ) <= timeout:
tx_response = get_transaction_by_hash( tx_hash, endpoint = endpoint )
if tx_response is not None:
if tx_response[ 'blockHash' ] != '0x0000000000000000000000000000000000000000000000000000000000000000':
block_hash = tx_response.get( "blockHash", "0x00" )
unique_chars = "".join( set( list( block_hash[ 2 : ] ) ) )
if unique_chars != "0":
return tx_response
time.sleep(random.uniform(0.2, 0.5))
raise TxConfirmationTimedoutError("Could not confirm transactions on-chain.")
time.sleep( random.uniform( 0.2, 0.5 ) )
raise TxConfirmationTimedoutError(
"Could not confirm transaction on-chain."
)
###############################
# CrossShard Transaction RPCs #
###############################
def get_pending_cx_receipts(endpoint=_default_endpoint, timeout=_default_timeout) -> list:
"""
Get list of pending cross shard transactions
def get_pending_cx_receipts(
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list:
"""Get list of pending cross shard transactions.
Parameters
----------
@ -459,7 +527,7 @@ def get_pending_cx_receipts(endpoint=_default_endpoint, timeout=_default_timeout
list of CX receipts, each a dict with the following keys
commitBitmap: :obj:`str` Hex represenation of aggregated signature bitmap
commitSig: :obj:`str` Hex representation of aggregated signature
receipts: :obj:`list` list of dictionaries, each representing a cross shard transaction receipt
receipts: :obj:`list` list of dictionaries, each a cross shard transaction receipt
amount: :obj:`int` Amount in ATTO
from: :obj:`str` From address
to: :obj:`str` From address
@ -490,15 +558,21 @@ def get_pending_cx_receipts(endpoint=_default_endpoint, timeout=_default_timeout
-------------
https://api.hmny.io/#fe60070d-97b4-458d-9365-490b44c18851
"""
method = 'hmyv2_getPendingCXReceipts'
method = "hmyv2_getPendingCXReceipts"
try:
return rpc_request(method, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request( method,
endpoint = endpoint,
timeout = timeout )[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_cx_receipt_by_hash(cx_hash, endpoint = _default_endpoint, timeout = _default_timeout) -> dict:
"""
Get cross shard receipt by hash on the receiving shard end point
def get_cx_receipt_by_hash(
cx_hash,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict:
"""Get cross shard receipt by hash on the receiving shard end point.
Parameters
----------
@ -531,18 +605,26 @@ def get_cx_receipt_by_hash(cx_hash, endpoint = _default_endpoint, timeout = _def
-------------
https://api.hmny.io/#3d6ad045-800d-4021-aeb5-30a0fbf724fe
"""
params = [
cx_hash
]
method = 'hmyv2_getCXReceiptByHash'
params = [ cx_hash ]
method = "hmyv2_getCXReceiptByHash"
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
def resend_cx_receipt(cx_hash, endpoint=_default_endpoint, timeout=_default_timeout) -> bool:
"""
Resend the cross shard receipt to the receiving shard to re-process if the transaction did not pay out
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def resend_cx_receipt(
cx_hash,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> bool:
"""Resend the cross shard receipt to the receiving shard to re-process if
the transaction did not pay out.
Parameters
----------
@ -567,21 +649,28 @@ def resend_cx_receipt(cx_hash, endpoint=_default_endpoint, timeout=_default_time
-------------
https://api.hmny.io/#c658b56b-d20b-480d-b71a-b0bc505d2164
"""
method = 'hmyv2_resendCx'
params = [
cx_hash
]
method = "hmyv2_resendCx"
params = [ cx_hash ]
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
############################
# Staking Transaction RPCs #
############################
def get_staking_transaction_by_hash(tx_hash, endpoint=_default_endpoint, timeout=_default_timeout) -> dict:
"""
Get staking transaction by hash
def get_staking_transaction_by_hash(
tx_hash,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict:
"""Get staking transaction by hash.
Parameters
----------
@ -619,20 +708,26 @@ def get_staking_transaction_by_hash(tx_hash, endpoint=_default_endpoint, timeout
-------------
https://api.hmny.io/#296cb4d0-bce2-48e3-bab9-64c3734edd27
"""
method = 'hmyv2_getStakingTransactionByHash'
params = [
tx_hash
]
method = "hmyv2_getStakingTransactionByHash"
params = [ tx_hash ]
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_staking_transaction_by_block_hash_and_index(block_hash, tx_index,
endpoint=_default_endpoint, timeout=_default_timeout
) -> dict:
"""
Get staking transaction by block hash and transaction index
def get_staking_transaction_by_block_hash_and_index(
block_hash,
tx_index,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict:
"""Get staking transaction by block hash and transaction index.
Parameters
----------
@ -658,21 +753,26 @@ def get_staking_transaction_by_block_hash_and_index(block_hash, tx_index,
-------------
https://api.hmny.io/#ba96cf61-61fe-464a-aa06-2803bb4b358f
"""
method = 'hmyv2_getStakingTransactionByBlockHashAndIndex'
params = [
block_hash,
tx_index
]
method = "hmyv2_getStakingTransactionByBlockHashAndIndex"
params = [ block_hash, tx_index ]
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def get_staking_transaction_by_block_number_and_index(block_num, tx_index,
endpoint=_default_endpoint, timeout=_default_timeout
) -> dict:
"""
Get staking transaction by block number and transaction index
def get_staking_transaction_by_block_number_and_index(
block_num,
tx_index,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> dict:
"""Get staking transaction by block number and transaction index.
Parameters
----------
@ -698,19 +798,25 @@ def get_staking_transaction_by_block_number_and_index(block_num, tx_index,
-------------
https://api.hmny.io/#fb41d717-1645-4d3e-8071-6ce8e1b65dd3
"""
method = 'hmyv2_getStakingTransactionByBlockNumberAndIndex'
params = [
block_num,
tx_index
]
method = "hmyv2_getStakingTransactionByBlockNumberAndIndex"
params = [ block_num, tx_index ]
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
def send_raw_staking_transaction(raw_tx, endpoint=_default_endpoint, timeout=_default_timeout) -> str:
"""
Send signed staking transaction
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def send_raw_staking_transaction(
raw_tx,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> str:
"""Send signed staking transaction.
Parameters
----------
@ -737,11 +843,66 @@ def send_raw_staking_transaction(raw_tx, endpoint=_default_endpoint, timeout=_de
-------------
https://api.hmny.io/#e8c17fe9-e730-4c38-95b3-6f1a5b1b9401
"""
method = 'hmyv2_sendRawStakingTransaction'
params = [
raw_tx
]
method = "hmyv2_sendRawStakingTransaction"
params = [ raw_tx ]
try:
return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']
except KeyError as e:
raise InvalidRPCReplyError(method, endpoint) from e
return rpc_request(
method,
params = params,
endpoint = endpoint,
timeout = timeout
)[ "result" ]
except KeyError as exception:
raise InvalidRPCReplyError( method, endpoint ) from exception
def send_and_confirm_raw_staking_transaction(
signed_tx,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> list:
"""Send signed staking transaction and wait for it to be confirmed.
Parameters
----------
signed_tx: str
Hex representation of signed staking transaction
endpoint: :obj:`str`, optional
Endpoint to send request to
timeout: :obj:`int`, optional
Timeout in seconds
Returns
-------
str
Transaction, see get_transaction_by_hash for structure
Raises
------
InvalidRPCReplyError
If received unknown result from endpoint, or
RPCError
If transaction failed to be added to the pool
TxConfirmationTimedoutError
If transaction could not be confirmed within the timeout period
API Reference
-------------
https://api.hmny.io/#e8c17fe9-e730-4c38-95b3-6f1a5b1b9401
"""
tx_hash = send_raw_staking_transaction( signed_tx, endpoint = endpoint )
start_time = time.time()
while ( time.time() - start_time ) <= timeout:
tx_response = get_staking_transaction_by_hash(
tx_hash,
endpoint = endpoint
)
if tx_response is not None:
block_hash = tx_response.get( "blockHash", "0x00" )
unique_chars = "".join( set( list( block_hash[ 2 : ] ) ) )
if unique_chars != "0":
return tx_response
time.sleep( random.uniform( 0.2, 0.5 ) )
raise TxConfirmationTimedoutError(
"Could not confirm transaction on-chain."
)

@ -1,51 +1,50 @@
"""
Basic pyhmy utils like is_shard_active
ONE address format conversion
Chain id (str) to int conversion
"""
import json
import subprocess
import os
import sys
import datetime
import requests
from eth_utils import to_checksum_address
from .blockchain import (
get_latest_header
)
from .blockchain import get_latest_header
from .rpc.exceptions import (
RPCError,
RequestsError,
RequestsTimeoutError,
)
from .rpc.exceptions import ( RPCError, RequestsError, RequestsTimeoutError, )
from .account import (
is_valid_address
)
from .account import is_valid_address
from .bech32.bech32 import (
bech32_decode,
convertbits
)
from .bech32.bech32 import bech32_decode, bech32_encode, convertbits
from eth_utils import to_checksum_address
datetime_format = "%Y-%m-%d %H:%M:%S.%f"
class Typgpy( str ):
"""Typography constants for pretty printing.
class Typgpy(str):
Note that an ENDC is needed to mark the end of a 'highlighted' text
segment.
"""
Typography constants for pretty printing.
Note that an ENDC is needed to mark the end of a 'highlighted' text segment.
HEADER = "\033[95m"
OKBLUE = "\033[94m"
OKGREEN = "\033[92m"
WARNING = "\033[93m"
FAIL = "\033[91m"
ENDC = "\033[0m"
BOLD = "\033[1m"
UNDERLINE = "\033[4m"
def chain_id_to_int( chain_id ):
"""
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
def chain_id_to_int(chainId):
chainIds = dict(
If chain_id is a string, converts it to int.
If chain_id is an int, returns the int.
Else raises TypeError
"""
chain_ids = dict(
Default = 0,
EthMainnet = 1,
Morden = 2,
@ -63,41 +62,56 @@ def chain_id_to_int(chainId):
HmyLocal = 2,
HmyPangaea = 3,
)
if isinstance(chainId, str):
assert chainId in chainIds, f'ChainId {chainId} is not valid'
return chainIds.get(chainId)
elif isinstance(chainId, int):
assert chainId in chainIds.values(), f'Unknown chain id {chainId}'
return chainId
else:
raise TypeError( 'chainId must be str or int' )
# do not validate integer chainids, only known strings
if isinstance( chain_id, str ):
assert (
chain_id in chain_ids
), f"Chain {chain_id} unknown, specify an integer chainId"
return chain_ids.get( chain_id )
if isinstance( chain_id, int ):
return chain_id
raise TypeError( "chainId must be str or int" )
def get_gopath():
"""
: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():
"""
:returns The go-version, assuming that go is installed.
"""
return subprocess.check_output(["go", "version"]).decode().strip()
def convert_one_to_hex(addr):
"""
Given a one address, convert it to hex checksum address
"""
if not is_valid_address(addr):
return to_checksum_address(addr)
hrp, data = bech32_decode(addr)
buf = convertbits(data, 5, 8, False)
address = '0x' + ''.join('{:02x}'.format(x) for x in buf)
return to_checksum_address(address)
return subprocess.check_output( [ "go", "version" ] ).decode().strip()
def convert_one_to_hex( addr ):
"""Given a one address, convert it to hex checksum address."""
if not is_valid_address( addr ):
return to_checksum_address( addr )
_, data = bech32_decode( addr )
buf = convertbits( data, 5, 8, False )
address = "0x" + "".join( f"{x:02x}" for x in buf )
return str( to_checksum_address( address ) )
def convert_hex_to_one( addr ):
"""Given a hex address, convert it to a one address."""
if is_valid_address( addr ):
return addr
checksum_addr = str( to_checksum_address( addr ) )
data = bytearray.fromhex(
checksum_addr[ 2 : ] if checksum_addr
.startswith( "0x" ) else checksum_addr
)
buf = convertbits( data, 8, 5 )
return 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 delay_tolerance: The time (in seconds) that the shard timestamp can be behind
@ -105,12 +119,15 @@ def is_active_shard(endpoint, delay_tolerance=60):
"""
try:
curr_time = datetime.datetime.utcnow()
latest_header = get_latest_header(endpoint=endpoint)
time_str = latest_header["timestamp"][:19] + '.0' # Fit time format
timestamp = datetime.datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S.%f").replace(tzinfo=None)
latest_header = get_latest_header( endpoint = endpoint )
time_str = latest_header[ "timestamp" ][ : 19 ] + ".0" # Fit time format
timestamp = datetime.datetime.strptime(
time_str,
"%Y-%m-%d %H:%M:%S.%f"
).replace( tzinfo = None )
time_delta = curr_time - timestamp
return abs(time_delta.seconds) < delay_tolerance
except (RPCError, RequestsError, RequestsTimeoutError):
return abs( time_delta.seconds ) < delay_tolerance
except ( RPCError, RequestsError, RequestsTimeoutError ):
return False
@ -125,27 +142,37 @@ def get_bls_build_variables():
"""
variables = {}
try:
openssl_dir = subprocess.check_output(["which", "openssl"]).decode().strip().split("\n")[0]
except (IndexError, subprocess.CalledProcessError) as e:
raise RuntimeError("`openssl` not found") from e
openssl_dir = (
subprocess.check_output(
[ "which",
"openssl" ]
).decode().strip().split( "\n",
maxsplit = 1 )[ 0 ]
)
except ( IndexError, subprocess.CalledProcessError ) as exception:
raise RuntimeError( "`openssl` not found" ) from exception
hmy_path = f"{get_gopath()}/src/github.com/harmony-one"
bls_dir = f"{hmy_path}/bls"
mcl_dir = f"{hmy_path}/mcl"
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}"
if sys.platform.startswith("darwin"):
variables["CGO_CFLAGS"] = 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["LD_LIBRARY_PATH"] = f"{bls_dir}/lib:{mcl_dir}/lib:{openssl_dir}/lib"
variables["DYLD_FALLBACK_LIBRARY_PATH"] = variables["LD_LIBRARY_PATH"]
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}"
if sys.platform.startswith( "darwin" ):
variables[
"CGO_CFLAGS"
] = 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[ "LD_LIBRARY_PATH"
] = f"{bls_dir}/lib:{mcl_dir}/lib:{openssl_dir}/lib"
variables[ "DYLD_FALLBACK_LIBRARY_PATH" ] = variables[ "LD_LIBRARY_PATH"
]
else:
variables["CGO_CFLAGS"] = f"-I{bls_dir}/include -I{mcl_dir}/include"
variables["CGO_LDFLAGS"] = f"-L{bls_dir}/lib"
variables["LD_LIBRARY_PATH"] = f"{bls_dir}/lib:{mcl_dir}/lib"
variables[ "CGO_CFLAGS" ] = f"-I{bls_dir}/include -I{mcl_dir}/include"
variables[ "CGO_LDFLAGS" ] = f"-L{bls_dir}/lib"
variables[ "LD_LIBRARY_PATH" ] = f"{bls_dir}/lib:{mcl_dir}/lib"
return variables
def json_load(string, **kwargs):
def json_load( string, **kwargs ):
"""
:param string: The JSON string to load
:returns A dictionary loaded from a JSON string to a dictionary.
@ -154,7 +181,7 @@ def json_load(string, **kwargs):
Note that this prints the failed input should an error arise.
"""
try:
return json.loads(string, **kwargs)
except Exception as e:
print(f"{Typgpy.FAIL}Could not parse input: '{string}'{Typgpy.ENDC}")
raise e from e
return json.loads( string, **kwargs )
except Exception as exception:
print( f"{Typgpy.FAIL}Could not parse input: '{string}'{Typgpy.ENDC}" )
raise exception

@ -1,63 +1,54 @@
"""
Load validator information from Harmony blockchain
Create and edit validators
"""
import json
from decimal import Decimal, InvalidOperation
from eth_account.datastructures import (
SignedTransaction
)
from decimal import (
Decimal,
InvalidOperation
)
from .account import (
get_balance,
is_valid_address
)
from eth_account.datastructures import SignedTransaction
from .numbers import (
convert_one_to_atto
)
from .account import is_valid_address
from .exceptions import (
InvalidValidatorError,
RPCError,
RequestsError,
RequestsTimeoutError
from .constants import (
DEFAULT_ENDPOINT,
DEFAULT_TIMEOUT,
NAME_CHAR_LIMIT,
IDENTITY_CHAR_LIMIT,
WEBSITE_CHAR_LIMIT,
SECURITY_CONTACT_CHAR_LIMIT,
DETAILS_CHAR_LIMIT,
MIN_REQUIRED_DELEGATION,
)
from .staking import (
get_all_validator_addresses,
get_validator_information
)
from .exceptions import InvalidValidatorError
from .staking_structures import (
Directive
)
from .rpc.exceptions import ( RPCError, RequestsError, RequestsTimeoutError, )
from .staking_signing import (
sign_staking_transaction
)
from .staking import get_all_validator_addresses, get_validator_information
_default_endpoint = 'http://localhost:9500'
_default_timeout = 30
from .staking_structures import Directive
# TODO: Add unit testing
class Validator:
from .staking_signing import sign_staking_transaction
name_char_limit = 140
identity_char_limit = 140
website_char_limit = 140
security_contact_char_limit = 140
details_char_limit = 280
min_required_delegation = convert_one_to_atto(10000) # in ATTO
def __init__(self, address):
if not isinstance(address, str):
raise InvalidValidatorError(1, 'given ONE address was not a string')
if not is_valid_address(address):
raise InvalidValidatorError(1, f'{address} is not valid ONE address')
class Validator: # pylint: disable=too-many-instance-attributes, too-many-public-methods
"""
Harmony validator
"""
def __init__( self, address ):
if not isinstance( address, str ):
raise InvalidValidatorError(
1,
"given ONE address was not a string"
)
if not is_valid_address( address ):
raise InvalidValidatorError(
1,
f"{address} is not valid ONE address"
)
self._address = address
self._bls_keys = []
self._bls_key_sigs = []
self._name = None
self._identity = None
@ -73,35 +64,35 @@ class Validator:
self._max_change_rate = None
self._max_rate = None
def _sanitize_input(self, data, check_str=False) -> str:
"""
If data is None, return '' else return data
def _sanitize_input( self, data, check_str = False ) -> str:
"""If data is None, return '' else return data.
Raises
------
InvalidValidatorError if check_str is True and str is not passed
"""
if check_str:
if not isinstance(data, str):
raise InvalidValidatorError(3, f'Expected data to be string to avoid floating point precision issues but got {data}')
return '' if not data else str(data)
def __str__(self) -> str:
"""
Returns JSON string representation of Validator fields
"""
if not isinstance( data, str ):
raise InvalidValidatorError(
3,
"Expected data to be string "
f"to avoid floating point precision issues but got {data}",
)
return "" if not data else str( data )
def __str__( self ) -> str:
"""Returns JSON string representation of Validator fields."""
info = self.export()
for key, value in info.items():
if isinstance(value, Decimal):
info[key] = str(value)
return json.dumps(info)
if isinstance( value, Decimal ):
info[ key ] = str( value )
return json.dumps( info )
def __repr__(self) -> str:
return f'<Validator: {hex(id(self))}>'
def __repr__( self ) -> str:
return f"<Validator: {hex(id(self))}>"
def get_address(self) -> str:
"""
Get validator address
def get_address( self ) -> str:
"""Get validator address.
Returns
-------
@ -110,39 +101,36 @@ class Validator:
"""
return self._address
def add_bls_key(self, key) -> bool:
"""
Add BLS public key to validator BLS keys if not already in list
def add_bls_key( self, key ) -> bool:
"""Add BLS public key to validator BLS keys if not already in list.
Returns
-------
bool
If adding BLS key succeeded
"""
key = self._sanitize_input(key)
key = self._sanitize_input( key )
if key not in self._bls_keys:
self._bls_keys.append(key)
self._bls_keys.append( key )
return True
return False
def remove_bls_key(self, key) -> bool:
"""
Remove BLS public key from validator BLS keys if exists
def remove_bls_key( self, key ) -> bool:
"""Remove BLS public key from validator BLS keys if exists.
Returns
-------
bool
If removing BLS key succeeded
"""
key = self._sanitize_input(key)
key = self._sanitize_input( key )
if key in self._bls_keys:
self._bls_keys.remove(key)
self._bls_keys.remove( key )
return True
return False
def get_bls_keys(self) -> list:
"""
Get list of validator BLS keys
def get_bls_keys( self ) -> list:
"""Get list of validator BLS keys.
Returns
-------
@ -151,9 +139,46 @@ class Validator:
"""
return self._bls_keys
def set_name(self, name):
def add_bls_key_sig( self, key ) -> bool:
"""Add BLS public key to validator BLS keys if not already in list.
Returns
-------
bool
If adding BLS key succeeded
"""
Set validator name
key = self._sanitize_input( key )
if key not in self._bls_key_sigs:
self._bls_key_sigs.append( key )
return True
return False
def remove_bls_key_sig( self, key ) -> bool:
"""Remove BLS public key from validator BLS keys if exists.
Returns
-------
bool
If removing BLS key succeeded
"""
key = self._sanitize_input( key )
if key in self._bls_key_sigs:
self._bls_key_sigs.remove( key )
return True
return False
def get_bls_key_sigs( self ) -> list:
"""Get list of validator BLS keys.
Returns
-------
list
List of validator BLS keys (strings)
"""
return self._bls_key_sigs
def set_name( self, name ):
"""Set validator name.
Parameters
----------
@ -165,14 +190,16 @@ class Validator:
InvalidValidatorError
If input is invalid
"""
name = self._sanitize_input(name)
if len(name) > self.name_char_limit:
raise InvalidValidatorError(3, f'Name must be less than {self.name_char_limit} characters')
name = self._sanitize_input( name )
if len( name ) > NAME_CHAR_LIMIT:
raise InvalidValidatorError(
3,
f"Name must be less than {NAME_CHAR_LIMIT} characters"
)
self._name = name
def get_name(self) -> str:
"""
Get validator name
def get_name( self ) -> str:
"""Get validator name.
Returns
-------
@ -181,9 +208,8 @@ class Validator:
"""
return self._name
def set_identity(self, identity):
"""
Set validator identity
def set_identity( self, identity ):
"""Set validator identity.
Parameters
----------
@ -195,14 +221,16 @@ class Validator:
InvalidValidatorError
If input is invalid
"""
identity = self._sanitize_input(identity)
if len(identity) > self.identity_char_limit:
raise InvalidValidatorError(3, f'Identity must be less than {self.identity_char_limit} characters')
identity = self._sanitize_input( identity )
if len( identity ) > IDENTITY_CHAR_LIMIT:
raise InvalidValidatorError(
3,
f"Identity must be less than {IDENTITY_CHAR_LIMIT} characters"
)
self._identity = identity
def get_identity(self) -> str:
"""
Get validator identity
def get_identity( self ) -> str:
"""Get validator identity.
Returns
-------
@ -211,9 +239,8 @@ class Validator:
"""
return self._identity
def set_website(self, website):
"""
Set validator website
def set_website( self, website ):
"""Set validator website.
Parameters
----------
@ -225,14 +252,16 @@ class Validator:
InvalidValidatorError
If input is invalid
"""
website = self._sanitize_input(website)
if len(website) > self.website_char_limit:
raise InvalidValidatorError(3, f'Website must be less than {self.website_char_limit} characters')
website = self._sanitize_input( website )
if len( website ) > WEBSITE_CHAR_LIMIT:
raise InvalidValidatorError(
3,
f"Website must be less than {WEBSITE_CHAR_LIMIT} characters"
)
self._website = website
def get_website(self) -> str:
"""
Get validator website
def get_website( self ) -> str:
"""Get validator website.
Returns
-------
@ -241,9 +270,8 @@ class Validator:
"""
return self._website
def set_security_contact(self, contact):
"""
Set validator security contact
def set_security_contact( self, contact ):
"""Set validator security contact.
Parameters
----------
@ -255,14 +283,16 @@ class Validator:
InvalidValidatorError
If input is invalid
"""
contact = self._sanitize_input(contact)
if len(contact) > self.security_contact_char_limit:
raise InvalidValidatorError(3, f'Security contact must be less than {self.security_contact_char_limit} characters')
contact = self._sanitize_input( contact )
if len( contact ) > SECURITY_CONTACT_CHAR_LIMIT:
raise InvalidValidatorError(
3,
f"Security contact must be less than {SECURITY_CONTACT_CHAR_LIMIT} characters",
)
self._security_contact = contact
def get_security_contact(self) -> str:
"""
Get validator security contact
def get_security_contact( self ) -> str:
"""Get validator security contact.
Returns
-------
@ -271,9 +301,8 @@ class Validator:
"""
return self._security_contact
def set_details(self, details):
"""
Set validator details
def set_details( self, details ):
"""Set validator details.
Parameters
----------
@ -285,14 +314,16 @@ class Validator:
InvalidValidatorError
If input is invalid
"""
details = self._sanitize_input(details)
if len(details) > self.details_char_limit:
raise InvalidValidatorError(3, f'Details must be less than {self.details_char_limit} characters')
details = self._sanitize_input( details )
if len( details ) > DETAILS_CHAR_LIMIT:
raise InvalidValidatorError(
3,
f"Details must be less than {DETAILS_CHAR_LIMIT} characters"
)
self._details = details
def get_details(self) -> str:
"""
Get validator details
def get_details( self ) -> str:
"""Get validator details.
Returns
-------
@ -301,9 +332,8 @@ class Validator:
"""
return self._details
def set_min_self_delegation(self, delegation):
"""
Set validator min self delegation
def set_min_self_delegation( self, delegation ):
"""Set validator min self delegation.
Parameters
----------
@ -315,18 +345,23 @@ class Validator:
InvalidValidatorError
If input is invalid
"""
delegation = self._sanitize_input(delegation)
delegation = self._sanitize_input( delegation )
try:
delegation = Decimal(delegation)
except (TypeError, InvalidOperation) as e:
raise InvalidValidatorError(3, 'Min self delegation must be a number') from e
if delegation < self.min_required_delegation:
raise InvalidValidatorError(3, f'Min self delegation must be greater than {self.min_required_delegation} ATTO')
delegation = Decimal( delegation )
except ( TypeError, InvalidOperation ) as exception:
raise InvalidValidatorError(
3,
"Min self delegation must be a number"
) from exception
if delegation < MIN_REQUIRED_DELEGATION:
raise InvalidValidatorError(
3,
f"Min self delegation must be greater than {MIN_REQUIRED_DELEGATION} ATTO",
)
self._min_self_delegation = delegation
def get_min_self_delegation(self) -> Decimal:
"""
Get validator min self delegation
def get_min_self_delegation( self ) -> Decimal:
"""Get validator min self delegation.
Returns
-------
@ -335,9 +370,8 @@ class Validator:
"""
return self._min_self_delegation
def set_max_total_delegation(self, max_delegation):
"""
Set validator max total delegation
def set_max_total_delegation( self, max_delegation ):
"""Set validator max total delegation.
Parameters
----------
@ -349,22 +383,30 @@ class Validator:
InvalidValidatorError
If input is invalid
"""
max_delegation = self._sanitize_input(max_delegation)
max_delegation = self._sanitize_input( max_delegation )
try:
max_delegation = Decimal(max_delegation)
except (TypeError, InvalidOperation) as e:
raise InvalidValidatorError(3, 'Max total delegation must be a number') from e
max_delegation = Decimal( max_delegation )
except ( TypeError, InvalidOperation ) as exception:
raise InvalidValidatorError(
3,
"Max total delegation must be a number"
) from exception
if self._min_self_delegation:
if max_delegation < self._min_self_delegation:
raise InvalidValidatorError(3, f'Max total delegation must be greater than min self delegation: '
'{self._min_self_delegation}')
raise InvalidValidatorError(
3,
"Max total delegation must be greater than min self delegation: "
f"{self._min_self_delegation}",
)
else:
raise InvalidValidatorError(4, 'Min self delegation must be set before max total delegation')
raise InvalidValidatorError(
4,
"Min self delegation must be set before max total delegation"
)
self._max_total_delegation = max_delegation
def get_max_total_delegation(self) -> Decimal:
"""
Get validator max total delegation
def get_max_total_delegation( self ) -> Decimal:
"""Get validator max total delegation.
Returns
-------
@ -373,9 +415,8 @@ class Validator:
"""
return self._max_total_delegation
def set_amount(self, amount):
"""
Set validator initial delegation amount
def set_amount( self, amount ):
"""Set validator initial delegation amount.
Parameters
----------
@ -387,28 +428,42 @@ class Validator:
InvalidValidatorError
If input is invalid
"""
amount = self._sanitize_input(amount)
amount = self._sanitize_input( amount )
try:
amount = Decimal(amount)
except (TypeError, InvalidOperation) as e:
raise InvalidValidatorError(3, 'Amount must be a number') from e
amount = Decimal( amount )
except ( TypeError, InvalidOperation ) as exception:
raise InvalidValidatorError(
3,
"Amount must be a number"
) from exception
if self._min_self_delegation:
if amount < self._min_self_delegation:
raise InvalidValidatorError(3, 'Amount must be greater than min self delegation: '
f'{self._min_self_delegation}')
raise InvalidValidatorError(
3,
"Amount must be greater than min self delegation: "
f"{self._min_self_delegation}",
)
else:
raise InvalidValidatorError(4, 'Min self delegation must be set before amount')
raise InvalidValidatorError(
4,
"Min self delegation must be set before amount"
)
if self._max_total_delegation:
if amount > self._max_total_delegation:
raise InvalidValidatorError(3, 'Amount must be less than max total delegation: '
f'{self._max_total_delegation}')
raise InvalidValidatorError(
3,
"Amount must be less than max total delegation: "
f"{self._max_total_delegation}",
)
else:
raise InvalidValidatorError(4, 'Max total delegation must be set before amount')
raise InvalidValidatorError(
4,
"Max total delegation must be set before amount"
)
self._inital_delegation = amount
def get_amount(self) -> Decimal:
"""
Get validator initial delegation amount
def get_amount( self ) -> Decimal:
"""Get validator initial delegation amount.
Returns
-------
@ -417,9 +472,8 @@ class Validator:
"""
return self._inital_delegation
def set_max_rate(self, rate):
"""
Set validator max commission rate
def set_max_rate( self, rate ):
"""Set validator max commission rate.
Parameters
----------
@ -431,18 +485,20 @@ class Validator:
InvalidValidatorError
If input is invalid
"""
rate = self._sanitize_input(rate, True)
rate = self._sanitize_input( rate, True )
try:
rate = Decimal(rate)
except (TypeError, InvalidOperation) as e:
raise InvalidValidatorError(3, 'Max rate must be a number') from e
rate = Decimal( rate )
except ( TypeError, InvalidOperation ) as exception:
raise InvalidValidatorError(
3,
"Max rate must be a number"
) from exception
if rate < 0 or rate > 1:
raise InvalidValidatorError(3, 'Max rate must be between 0 and 1')
raise InvalidValidatorError( 3, "Max rate must be between 0 and 1" )
self._max_rate = rate
def get_max_rate(self) -> Decimal:
"""
Get validator max commission rate
def get_max_rate( self ) -> Decimal:
"""Get validator max commission rate.
Returns
-------
@ -451,9 +507,8 @@ class Validator:
"""
return self._max_rate
def set_max_change_rate(self, rate):
"""
Set validator max commission change rate
def set_max_change_rate( self, rate ):
"""Set validator max commission change rate.
Parameters
----------
@ -465,23 +520,34 @@ class Validator:
InvalidValidatorError
If input is invalid
"""
rate = self._sanitize_input(rate, True)
rate = self._sanitize_input( rate, True )
try:
rate = Decimal(rate)
except (TypeError, InvalidOperation) as e:
raise InvalidValidatorError(3, 'Max change rate must be a number') from e
rate = Decimal( rate )
except ( TypeError, InvalidOperation ) as exception:
raise InvalidValidatorError(
3,
"Max change rate must be a number"
) from exception
if rate < 0:
raise InvalidValidatorError(3, 'Max change rate must be greater than or equal to 0')
raise InvalidValidatorError(
3,
"Max change rate must be greater than or equal to 0"
)
if self._max_rate:
if rate > self._max_rate:
raise InvalidValidatorError(3, f'Max change rate must be less than or equal to max rate: {self._max_rate}')
raise InvalidValidatorError(
3,
f"Max change rate must be less than or equal to max rate: {self._max_rate}",
)
else:
raise InvalidValidatorError(4, 'Max rate must be set before max change rate')
raise InvalidValidatorError(
4,
"Max rate must be set before max change rate"
)
self._max_change_rate = rate
def get_max_change_rate(self) -> Decimal:
"""
Get validator max commission change rate
def get_max_change_rate( self ) -> Decimal:
"""Get validator max commission change rate.
Returns
-------
@ -490,9 +556,8 @@ class Validator:
"""
return self._max_change_rate
def set_rate(self, rate):
"""
Set validator commission rate
def set_rate( self, rate ):
"""Set validator commission rate.
Parameters
----------
@ -504,23 +569,31 @@ class Validator:
InvalidValidatorError
If input is invalid
"""
rate = self._sanitize_input(rate, True)
rate = self._sanitize_input( rate, True )
try:
rate = Decimal(rate)
except (TypeError, InvalidOperation) as e:
raise InvalidValidatorError(3, 'Rate must be a number') from e
rate = Decimal( rate )
except ( TypeError, InvalidOperation ) as exception:
raise InvalidValidatorError(
3,
"Rate must be a number"
) from exception
if rate < 0:
raise InvalidValidatorError(3, 'Rate must be greater than or equal to 0')
raise InvalidValidatorError(
3,
"Rate must be greater than or equal to 0"
)
if self._max_rate:
if rate > self._max_rate:
raise InvalidValidatorError(3, f'Rate must be less than or equal to max rate: {self._max_rate}')
raise InvalidValidatorError(
3,
f"Rate must be less than or equal to max rate: {self._max_rate}"
)
else:
raise InvalidValidatorError(4, 'Max rate must be set before rate')
raise InvalidValidatorError( 4, "Max rate must be set before rate" )
self._rate = rate
def get_rate(self) -> Decimal:
"""
Get validator commission rate
def get_rate( self ) -> Decimal:
"""Get validator commission rate.
Returns
-------
@ -529,9 +602,12 @@ class Validator:
"""
return self._rate
def does_validator_exist(self, endpoint=_default_endpoint, timeout=_default_timeout) -> bool:
"""
Check if validator exists on blockchain
def does_validator_exist(
self,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
) -> bool:
"""Check if validator exists on blockchain.
Parameters
----------
@ -550,14 +626,13 @@ class Validator:
RPCError, RequestsError, RequestsTimeoutError
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:
return True
return False
def load(self, info):
"""
Import validator information
def load( self, info ):
"""Import validator information.
Parameters
----------
@ -578,6 +653,7 @@ class Validator:
"max-rate": '0',
"max-change-rate": '0',
"bls-public-keys": [ "" ]
"bls-key-sigs": [ "" ]
}
Raises
@ -586,29 +662,41 @@ class Validator:
If input value is invalid
"""
try:
self.set_name(info['name'])
self.set_identity(info['identity'])
self.set_website(info['website'])
self.set_details(info['details'])
self.set_security_contact(info['security-contact'])
self.set_name( info[ "name" ] )
self.set_identity( info[ "identity" ] )
self.set_website( info[ "website" ] )
self.set_details( info[ "details" ] )
self.set_security_contact( info[ "security-contact" ] )
self.set_min_self_delegation(info['min-self-delegation'])
self.set_max_total_delegation(info['max-total-delegation'])
self.set_amount(info['amount'])
self.set_min_self_delegation( info[ "min-self-delegation" ] )
self.set_max_total_delegation( info[ "max-total-delegation" ] )
self.set_amount( info[ "amount" ] )
self.set_max_rate(info['max-rate'])
self.set_max_change_rate(info['max-change-rate'])
self.set_rate(info['rate'])
self.set_max_rate( info[ "max-rate" ] )
self.set_max_change_rate( info[ "max-change-rate" ] )
self.set_rate( info[ "rate" ] )
self._bls_keys = []
for key in info['bls-public-keys']:
self.add_bls_key(key)
except KeyError as e:
raise InvalidValidatorError(3, 'Info has missing key') from e
def load_from_blockchain(self, endpoint=_default_endpoint, timeout=_default_timeout):
"""
Import validator information from blockchain with given address
for key in info[ "bls-public-keys" ]:
self.add_bls_key( key )
self._bls_key_sigs = []
for key in info[ "bls-key-sigs" ]:
self.add_bls_key_sig( key )
except KeyError as exception:
raise InvalidValidatorError(
3,
"Info has missing key"
) from exception
def load_from_blockchain(
self,
endpoint = DEFAULT_ENDPOINT,
timeout = DEFAULT_TIMEOUT
):
"""Import validator information from blockchain with given address At
the moment, this is unable to fetch the BLS Signature, which is not
implemented in the Node API.
Parameters
----------
@ -623,38 +711,55 @@ class Validator:
If any error occur getting & importing validator information from the blockchain
"""
try:
if not self.does_validator_exist(endpoint, timeout):
raise InvalidValidatorError(5, f'Validator does not exist on chain according to {endpoint}')
except (RPCError, RequestsError, RequestsTimeoutError) as e:
raise InvalidValidatorError(5, 'Error requesting validator information') from e
if not self.does_validator_exist( endpoint, timeout ):
raise InvalidValidatorError(
5,
f"Validator does not exist on chain according to {endpoint}"
)
except ( RPCError, RequestsError, RequestsTimeoutError ) as exception:
raise InvalidValidatorError(
5,
"Error requesting validator information"
) from exception
try:
validator_info = get_validator_information(self._address, endpoint, timeout)
except (RPCError, RequestsError, RequestsTimeoutError) as e:
raise InvalidValidatorError(5, 'Error requesting validator information') from e
validator_info = get_validator_information(
self._address,
endpoint,
timeout
)
except ( RPCError, RequestsError, RequestsTimeoutError ) as exception:
raise InvalidValidatorError(
5,
"Error requesting validator information"
) from exception
# Skip additional sanity checks when importing from chain
try:
info = validator_info['validator']
self._name = info['name']
self._identity = info['identity']
self._website = info['website']
self._details = info['details']
self._security_contact = info['security-contact']
self._min_self_delegation = info['min-self-delegation']
self._max_total_delegation = info['max-total-delegation']
self._inital_delegation = self._min_self_delegation # Since validator exists, set initial delegation to 0
self._max_rate = Decimal(info['max-rate'])
self._max_change_rate = Decimal(info['max-change-rate'])
self._rate = Decimal(info['rate'])
self._bls_keys = info[ 'bls-public-keys' ]
except KeyError as e:
raise InvalidValidatorError(5, 'Error importing validator information from RPC result') from e
def export(self) -> dict:
"""
Export validator information as dict
info = validator_info[ "validator" ]
self._name = info[ "name" ]
self._identity = info[ "identity" ]
self._website = info[ "website" ]
self._details = info[ "details" ]
self._security_contact = info[ "security-contact" ]
self._min_self_delegation = info[ "min-self-delegation" ]
self._max_total_delegation = info[ "max-total-delegation" ]
self._inital_delegation = (
self._min_self_delegation
) # Since validator exists, set initial delegation to 0
self._max_rate = Decimal( info[ "max-rate" ] )
self._max_change_rate = Decimal( info[ "max-change-rate" ] )
self._rate = Decimal( info[ "rate" ] )
self._bls_keys = info[ "bls-public-keys" ]
except KeyError as exception:
raise InvalidValidatorError(
5,
"Error importing validator information from RPC result"
) from exception
def export( self ) -> dict:
"""Export validator information as dict.
Returns
-------
@ -674,13 +779,16 @@ class Validator:
"rate": self._rate,
"max-rate": self._max_rate,
"max-change-rate": self._max_change_rate,
"bls-public-keys": self._bls_keys
"bls-public-keys": self._bls_keys,
"bls-key-sigs": self._bls_key_sigs,
}
return info
def sign_create_validator_transaction(self, nonce, gas_price, gas_limit, private_key, chain_id=None) -> SignedTransaction:
"""
Create but not post a transaction to Create the Validator using private_key
def sign_create_validator_transaction( # pylint: disable=too-many-arguments
self, nonce, gas_price, gas_limit, private_key, chain_id=None
) -> SignedTransaction:
"""Create but not post a transaction to Create the Validator using
private_key.
Returns
-------
@ -696,18 +804,31 @@ class Validator:
https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L413
"""
info = self.export().copy()
info['directive'] = Directive.CreateValidator
info['validatorAddress'] = info.pop('validator-addr') # change the key
info['nonce'] = nonce
info['gasPrice'] = gas_price
info['gasLimit'] = gas_limit
info[ "directive" ] = Directive.CreateValidator
info[ "validatorAddress" ] = info.pop(
"validator-addr"
) # change the key
info[ "nonce" ] = nonce
info[ "gasPrice" ] = gas_price
info[ "gasLimit" ] = gas_limit
if chain_id:
info['chainId'] = chain_id
return sign_staking_transaction(info, private_key)
def sign_edit_validator_transaction(self, nonce, gas_price, gas_limit, rate, bls_key_to_add, bls_key_to_remove, private_key, chain_id=None) -> SignedTransaction:
"""
Create but not post a transaction to Edit the Validator using private_key
info[ "chainId" ] = chain_id
return sign_staking_transaction( info, private_key )
def sign_edit_validator_transaction( # pylint: disable=too-many-arguments
self,
nonce,
gas_price,
gas_limit,
rate,
bls_key_to_remove,
bls_key_to_add,
bls_key_to_add_sig,
private_key,
chain_id=None,
) -> SignedTransaction:
"""Create but not post a transaction to Edit the Validator using
private_key.
Returns
-------
@ -722,21 +843,24 @@ class Validator:
-------------
https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L460
"""
self.set_rate(rate)
self.add_bls_key(bls_key_to_add)
self.remove_bls_key(bls_key_to_remove)
self.set_rate( rate )
self.add_bls_key( bls_key_to_add )
self.remove_bls_key( bls_key_to_remove )
info = self.export().copy()
info['directive'] = Directive.EditValidator
info['validatorAddress'] = info.pop('validator-addr') # change the key
info['nonce'] = nonce
info['gasPrice'] = gas_price
info['gasLimit'] = gas_limit
_ = info.pop('max-rate') # not needed
_ = info.pop('max-change-rate') # not needed
_ = info.pop('bls-public-keys') # remove this list
_ = info.pop('amount') # also unused
info['bls-key-to-remove'] = bls_key_to_remove
info['bls-key-to-add'] = bls_key_to_add
info[ "directive" ] = Directive.EditValidator
info[ "validatorAddress" ] = info.pop(
"validator-addr"
) # change the key
info[ "nonce" ] = nonce
info[ "gasPrice" ] = gas_price
info[ "gasLimit" ] = gas_limit
_ = info.pop( "max-rate" ) # not needed
_ = info.pop( "max-change-rate" ) # not needed
_ = info.pop( "bls-public-keys" ) # remove this list
_ = info.pop( "amount" ) # also unused
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:
info['chainId'] = chain_id
return sign_staking_transaction(info, private_key)
info[ "chainId" ] = chain_id
return sign_staking_transaction( info, private_key )

@ -1,3 +1,2 @@
[pytest]
addopts = -v --showlocals
python_paths = .

@ -27,7 +27,7 @@ install_requires =
requests
incremental
eth-rlp
eth-account == 0.5.4
eth-account >= 0.5.5
eth-utils
hexbytes
cytoolz

@ -0,0 +1,578 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 105,
"id": "feee22ef",
"metadata": {},
"outputs": [],
"source": [
"from pyhmy import signing, staking_signing, numbers, transaction, account, validator as validator_module, staking_structures, contract\n",
"from web3 import Web3"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "55b8db60",
"metadata": {},
"outputs": [],
"source": [
"# we need five transactions in conftest\n",
"# simple transfer (from localnet address)\n",
"# contract creation (from second address)\n",
"# cross shard transfer (from second address)\n",
"# validator creation (from localnet address)\n",
"# delegation (from second address)"
]
},
{
"cell_type": "markdown",
"id": "e104724c",
"metadata": {},
"source": [
"### Simple Transfer"
]
},
{
"cell_type": "code",
"execution_count": 144,
"id": "d7fa35f8",
"metadata": {},
"outputs": [],
"source": [
"pk = \"1f84c95ac16e6a50f08d44c7bde7aff8742212fda6e4321fde48bf83bef266dc\"\n",
"tx = {\n",
" 'from': 'one155jp2y76nazx8uw5sa94fr0m4s5aj8e5xm6fu3',\n",
" # 3c86ac59f6b038f584be1c08fced78d7c71bb55d5655f81714f3cddc82144c65\n",
" 'to': 'one1ru3p8ff0wsyl7ncsx3vwd5szuze64qz60upg37',\n",
" 'gasPrice': Web3.toWei( 100, 'gwei' ),\n",
" 'gas': 21000,\n",
" 'chainId': 2, # localnet\n",
" 'value': int( numbers.convert_one_to_atto( 503 ) ),\n",
" 'nonce': 0,\n",
" 'shardID': 0,\n",
" 'toShardID': 0,\n",
"}\n",
"raw_tx = signing.sign_transaction(tx, pk).rawTransaction.hex()\n",
"tx_hash = transaction.send_raw_transaction(raw_tx)"
]
},
{
"cell_type": "code",
"execution_count": 145,
"id": "ed907d4b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0xf86f8085174876e8008252088080941f2213a52f7409ff4f103458e6d202e0b3aa805a891b4486fafde57c00008027a0d7c0b20207dcc9dde376822dc3f5625eac6f59a7526111695cdba3e29553ca17a05d4ca9a421ae16f89cbf6848186eaea7a800da732446dff9952e7c1e91d414e3\n",
"0xc26be5776aa57438bccf196671a2d34f3f22c9c983c0f844c62b2fb90403aa43\n"
]
}
],
"source": [
"print( raw_tx )\n",
"print( tx_hash )"
]
},
{
"cell_type": "markdown",
"id": "1bbee37b",
"metadata": {},
"source": [
"### Contract Creation"
]
},
{
"cell_type": "code",
"execution_count": 147,
"id": "b143507b",
"metadata": {},
"outputs": [],
"source": [
"pk = '3c86ac59f6b038f584be1c08fced78d7c71bb55d5655f81714f3cddc82144c65'\n",
"data = \"0x6080604052348015600f57600080fd5b50607780601d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80634936cd3614602d575b600080fd5b604080516001815290519081900360200190f3fea2646970667358221220fa3fa0e8d0267831a59f4dd5edf39a513d07e98461cb06660ad28d4beda744cd64736f6c634300080f0033\"\n",
"tx = {\n",
" 'gasPrice': Web3.toWei( 100, 'gwei' ),\n",
" 'gas': 100000,\n",
" 'chainId': 2,\n",
" 'nonce': 0,\n",
" 'shardID': 0,\n",
" 'toShardID': 0,\n",
" 'data': data,\n",
"}\n",
"raw_tx = signing.sign_transaction(tx, pk).rawTransaction.hex()\n",
"tx_hash = transaction.send_raw_transaction(raw_tx)"
]
},
{
"cell_type": "code",
"execution_count": 148,
"id": "53dbcbff",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0xf8e88085174876e800830186a080808080b8946080604052348015600f57600080fd5b50607780601d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80634936cd3614602d575b600080fd5b604080516001815290519081900360200190f3fea2646970667358221220fa3fa0e8d0267831a59f4dd5edf39a513d07e98461cb06660ad28d4beda744cd64736f6c634300080f003327a08bf26ee0120c296b17af507f62606abdb5c5f09a65642c3d30b349b8bfbb3d69a03ec7be51c615bcbf2f1d63f6eaa56cf8d7be81671717f90239619830a81ebc9f\n",
"0xa605852dd2fa39ed42e101c17aaca9d344d352ba9b24b14b9af94ec9cb58b31f\n"
]
}
],
"source": [
"print( raw_tx )\n",
"print( tx_hash )"
]
},
{
"cell_type": "code",
"execution_count": 130,
"id": "6e66392b",
"metadata": {},
"outputs": [],
"source": [
"contract_address = transaction.get_transaction_receipt( tx_hash ).get( 'contractAddress' )\n",
"deployed = contract.get_code( contract_address, 'latest' )"
]
},
{
"cell_type": "code",
"execution_count": 131,
"id": "ead2f9d4",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0x6080604052348015600f57600080fd5b506004361060285760003560e01c80634936cd3614602d575b600080fd5b604080516001815290519081900360200190f3fea2646970667358221220fa3fa0e8d0267831a59f4dd5edf39a513d07e98461cb06660ad28d4beda744cd64736f6c634300080f0033\n",
"0x6080604052348015600f57600080fd5b50607780601d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80634936cd3614602d575b600080fd5b604080516001815290519081900360200190f3fea2646970667358221220fa3fa0e8d0267831a59f4dd5edf39a513d07e98461cb06660ad28d4beda744cd64736f6c634300080f0033\n"
]
}
],
"source": [
"print( deployed )\n",
"print( data )"
]
},
{
"cell_type": "code",
"execution_count": 132,
"id": "453a34d6",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3300f080003436c6f63746dc447adeb4d82da06660bc16489e70d315a93fde5dd4f95a1387620d8e0af3af0221228537660796462aef3f091002063009180915092518100615080406b5df080006b575d2064163dc63943608c10e065300067582060163400605b5df08000675f0065108432504060806x0\n",
"3300f080003436c6f63746dc447adeb4d82da06660bc16489e70d315a93fde5dd4f95a1387620d8e0af3af0221228537660796462aef3f091002063009180915092518100615080406b5df080006b575d2064163dc63943608c10e065300067582060163400605b5df08000675f0065108432504060806ef3f0006930006d10608770605b5df08000675f0065108432504060806x0\n"
]
}
],
"source": [
"print( \"\".join( [ deployed[ len( deployed ) - ( i + 1 ) ] for i in range( len( deployed ) ) ] ) )\n",
"print( \"\".join( [ data[ len( data ) - ( i + 1 ) ] for i in range( len( data ) ) ] ) )"
]
},
{
"cell_type": "code",
"execution_count": 133,
"id": "d251d1bf",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 133,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"\"0x6080604052348015600f57600080fd5b506004361060285760003560e01c80634936cd3614602d575b600080fd5b604080516001815290519081900360200190f3fea2646970667358221220fa3fa0e8d0267831a59f4dd5edf39a513d07e98461cb06660ad28d4beda744cd64736f6c634300080f0033\" == deployed"
]
},
{
"cell_type": "markdown",
"id": "812e033c",
"metadata": {},
"source": [
"### Cross Shard Transfer"
]
},
{
"cell_type": "code",
"execution_count": 149,
"id": "d7c70614",
"metadata": {},
"outputs": [],
"source": [
"pk = '3c86ac59f6b038f584be1c08fced78d7c71bb55d5655f81714f3cddc82144c65'\n",
"tx = {\n",
" 'from': 'one1ru3p8ff0wsyl7ncsx3vwd5szuze64qz60upg37',\n",
" 'gasPrice': Web3.toWei( 100, 'gwei' ),\n",
" 'gas': 21000,\n",
" 'chainId': 2,\n",
" 'nonce': 1,\n",
" 'shardID': 0,\n",
" 'toShardID': 1,\n",
" 'to': 'one1e8rdglh97t37prtnv7k35ymnh2wazujpzsmzes',\n",
" 'value': Web3.toWei( 100, 'gwei' ),\n",
"}\n",
"raw_tx = signing.sign_transaction(tx, pk).rawTransaction.hex()\n",
"tx_hash = transaction.send_raw_transaction(raw_tx)"
]
},
{
"cell_type": "code",
"execution_count": 150,
"id": "f20990f1",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0xf86b0185174876e800825208800194c9c6d47ee5f2e3e08d7367ad1a1373ba9dd1724185174876e8008027a02501c517220e9499f14e97c20b0a88cd3b7ba80637bba43ed295422e69a3f300a079b8e1213c9506184aed6ac2eb0b2cb00594c3f9fcdd6c088937ce17fe47107c\n",
"0xf73ba634cb96fc0e3e2c9d3b4c91379e223741be4a5aa56e6d6caf49c1ae75cf\n"
]
}
],
"source": [
"print( raw_tx )\n",
"print( tx_hash )"
]
},
{
"cell_type": "code",
"execution_count": 153,
"id": "66f024b9",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0"
]
},
"execution_count": 153,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"account.get_balance( 'one1e8rdglh97t37prtnv7k35ymnh2wazujpzsmzes', 'http://localhost:9502' )"
]
},
{
"cell_type": "markdown",
"id": "2b2446df",
"metadata": {},
"source": [
"### Validator Creation"
]
},
{
"cell_type": "code",
"execution_count": 154,
"id": "c3513c37",
"metadata": {},
"outputs": [],
"source": [
"pk = \"1f84c95ac16e6a50f08d44c7bde7aff8742212fda6e4321fde48bf83bef266dc\"\n",
"address = \"one155jp2y76nazx8uw5sa94fr0m4s5aj8e5xm6fu3\"\n",
"info = {\n",
" \"name\": \"Alice\",\n",
" \"identity\": \"alice\",\n",
" \"website\": \"alice.harmony.one\",\n",
" \"security-contact\": \"Bob\",\n",
" \"details\": \"Are you even reading this?\",\n",
" \"min-self-delegation\": int( numbers.convert_one_to_atto( 10000 ) ),\n",
" \"max-total-delegation\": int( numbers.convert_one_to_atto( 100000 ) ),\n",
" \"rate\": \"0.1\",\n",
" \"max-rate\": \"0.9\",\n",
" \"max-change-rate\": \"0.05\",\n",
" \"bls-public-keys\": [\n",
" # private key is b1f2a5029f5f43c8c933a61ce936ced030b2c9379f8e2478fc888fa670cdbc89b8cd1ebc29b5b00a81d3152bb3aaa3a337404f50bee5e434430ca3693a94a1c102a765cf3b0887b8b0bcf5317d33f4bec60a97feae2498a39ab7a1c2\n",
" # blspass.txt is empty\n",
" \"0xa20e70089664a874b00251c5e85d35a73871531306f3af43e02138339d294e6bb9c4eb82162199c6a852afeaa8d68712\",\n",
" ],\n",
" \"amount\": int( numbers.convert_one_to_atto( 10000 ) ),\n",
" \"bls-key-sigs\": [\n",
" \"0xef2c49a2f31fbbd23c21bc176eaf05cd0bebe6832033075d81fea7cff6f9bc1ab42f3b6895c5493fe645d8379d2eaa1413de55a9d3ce412a4f747cb57d52cc4da4754bfb2583ec9a41fe5dd48287f964f276336699959a5fcef3391dc24df00d\",\n",
" ]\n",
"}\n",
"validator = validator_module.Validator( address )\n",
"validator.load( info )\n",
"raw_tx = validator.sign_create_validator_transaction(\n",
" 1,\n",
" Web3.toWei( 100, 'gwei' ),\n",
" 55000000, # gas limit\n",
" pk,\n",
" 2 # chain id\n",
").rawTransaction.hex()\n",
"tx_hash = transaction.send_raw_staking_transaction(\n",
" raw_tx,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 155,
"id": "9b12f75f",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0xf9017c80f9012994a5241513da9f4463f1d4874b548dfbac29d91f34f83d85416c69636585616c69636591616c6963652e6861726d6f6e792e6f6e6583426f629a41726520796f75206576656e2072656164696e6720746869733fddc988016345785d8a0000c9880c7d713b49da0000c887b1a2bc2ec500008a021e19e0c9bab24000008a152d02c7e14af6800000f1b0a20e70089664a874b00251c5e85d35a73871531306f3af43e02138339d294e6bb9c4eb82162199c6a852afeaa8d68712f862b860ef2c49a2f31fbbd23c21bc176eaf05cd0bebe6832033075d81fea7cff6f9bc1ab42f3b6895c5493fe645d8379d2eaa1413de55a9d3ce412a4f747cb57d52cc4da4754bfb2583ec9a41fe5dd48287f964f276336699959a5fcef3391dc24df00d8a021e19e0c9bab24000000185174876e8008403473bc028a08c1146305eaef981aa24c2f17c8519664d10c99ee42acedbc258749930d31a7ca031dadf114ee6ab9bd09933208094c65037b66c796bcfc57a70158106b37357b0\n",
"0x400e9831d358f5daccd153cad5bf53650a0d413bd8682ec0ffad55367d162968\n"
]
}
],
"source": [
"print( raw_tx )\n",
"print( tx_hash )"
]
},
{
"cell_type": "markdown",
"id": "4c2ff645",
"metadata": {},
"source": [
"### Delegation"
]
},
{
"cell_type": "code",
"execution_count": 156,
"id": "458d81b8",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0xf88302f4941f2213a52f7409ff4f103458e6d202e0b3aa805a94a5241513da9f4463f1d4874b548dfbac29d91f3489056bc75e2d631000000285174876e80082c35028a02c5e953062dcdfa2de9723639b63bab45705eb6dfbfe7f44536ed266c3c7ca20a0742964e646338e7431874f70715565d99c01c762324355c69db34a9ed9de81d7\n",
"0xc8177ace2049d9f4eb4a45fd6bd6b16f693573d036322c36774cc00d05a3e24f\n"
]
}
],
"source": [
"pk = \"3c86ac59f6b038f584be1c08fced78d7c71bb55d5655f81714f3cddc82144c65\"\n",
"tx = {\n",
" 'directive': staking_structures.Directive.Delegate,\n",
" 'delegatorAddress': 'one1ru3p8ff0wsyl7ncsx3vwd5szuze64qz60upg37',\n",
" 'validatorAddress': 'one155jp2y76nazx8uw5sa94fr0m4s5aj8e5xm6fu3',\n",
" 'amount': Web3.toWei( 100, 'ether' ),\n",
" 'nonce': 2,\n",
" 'gasPrice': Web3.toWei( 100, 'gwei' ),\n",
" 'gasLimit': 50000,\n",
" 'chainId': 2,\n",
"}\n",
"raw_tx = staking_signing.sign_staking_transaction(\n",
" tx,\n",
" pk,\n",
").rawTransaction.hex()\n",
"tx_hash = transaction.send_raw_staking_transaction(\n",
" raw_tx,\n",
")\n",
"print( raw_tx )\n",
"print( tx_hash )"
]
},
{
"cell_type": "markdown",
"id": "8efa5536",
"metadata": {},
"source": [
"### test_transaction.py - transfer 105 ONE to another address"
]
},
{
"cell_type": "code",
"execution_count": 157,
"id": "c3295fee",
"metadata": {},
"outputs": [],
"source": [
"pk = '3c86ac59f6b038f584be1c08fced78d7c71bb55d5655f81714f3cddc82144c65'\n",
"tx = {\n",
" 'from': 'one1ru3p8ff0wsyl7ncsx3vwd5szuze64qz60upg37',\n",
" 'gasPrice': Web3.toWei( 100, 'gwei' ),\n",
" 'gas': 21000,\n",
" 'chainId': 2,\n",
" 'nonce': 3,\n",
" 'shardID': 0,\n",
" 'toShardID': 0,\n",
" 'to': 'one1e8rdglh97t37prtnv7k35ymnh2wazujpzsmzes',\n",
" 'value': Web3.toWei( 105, 'ether' ),\n",
"}\n",
"raw_tx = signing.sign_transaction(tx, pk).rawTransaction.hex()\n",
"tx_hash = transaction.send_raw_transaction(raw_tx)"
]
},
{
"cell_type": "code",
"execution_count": 158,
"id": "af515c7e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0xf86f0385174876e800825208808094c9c6d47ee5f2e3e08d7367ad1a1373ba9dd172418905b12aefafa80400008027a07a4952b90bf38723a9197179a8e6d2e9b3a86fd6da4e66a9cf09fdc59783f757a053910798b311245525bd77d6119332458c2855102e4fb9e564f6a3b710d18bb0\n",
"0x7ccd80f8513f76ec58b357c7a82a12a95e025d88f1444e953f90e3d86e222571\n"
]
}
],
"source": [
"print( raw_tx )\n",
"print( tx_hash )"
]
},
{
"cell_type": "markdown",
"id": "bb546b3e",
"metadata": {},
"source": [
"### test_transaction.py - staking transaction"
]
},
{
"cell_type": "code",
"execution_count": 168,
"id": "c14e2d6d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0xf88302f494c9c6d47ee5f2e3e08d7367ad1a1373ba9dd1724194a5241513da9f4463f1d4874b548dfbac29d91f3489056bc75e2d631000008085174876e80082c35027a0808ea7d27adf3b1f561e8da4676814084bb75ac541b616bece87c6446e6cc54ea02f19f0b14240354bd42ad60b0c7189873c0be87044e13072b0981a837ca76f64\n",
"0xe7d07ef6d9fca595a14ceb0ca917bece7bedb15efe662300e9334a32ac1da629\n"
]
}
],
"source": [
"pk = \"ff9ef6b00a61672b4b7bedd5ac653439b56ac8ee808c99a1bd871cf51b7d60eb\"\n",
"tx = {\n",
" 'directive': staking_structures.Directive.Delegate,\n",
" 'delegatorAddress': 'one1e8rdglh97t37prtnv7k35ymnh2wazujpzsmzes',\n",
" 'validatorAddress': 'one155jp2y76nazx8uw5sa94fr0m4s5aj8e5xm6fu3',\n",
" 'amount': Web3.toWei( 100, 'ether' ),\n",
" 'nonce': 0,\n",
" 'gasPrice': Web3.toWei( 100, 'gwei' ),\n",
" 'gasLimit': 50000,\n",
" 'chainId': 2,\n",
"}\n",
"raw_tx = staking_signing.sign_staking_transaction(\n",
" tx,\n",
" pk,\n",
").rawTransaction.hex()\n",
"tx_hash = transaction.send_raw_staking_transaction(\n",
" raw_tx,\n",
")\n",
"print( raw_tx )\n",
"print( tx_hash )"
]
},
{
"cell_type": "code",
"execution_count": 162,
"id": "ebf296aa",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'blockHash': '0xf55f1fb3c9be76fb74370e8a7d8580327797d2d6082040074783207a171e2de6',\n",
" 'blockNumber': 34,\n",
" 'from': 'one1ru3p8ff0wsyl7ncsx3vwd5szuze64qz60upg37',\n",
" 'hash': '0xf73ba634cb96fc0e3e2c9d3b4c91379e223741be4a5aa56e6d6caf49c1ae75cf',\n",
" 'shardID': 0,\n",
" 'to': 'one1e8rdglh97t37prtnv7k35ymnh2wazujpzsmzes',\n",
" 'toShardID': 1,\n",
" 'value': 100000000000}"
]
},
"execution_count": 162,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"transaction.get_cx_receipt_by_hash( '0xf73ba634cb96fc0e3e2c9d3b4c91379e223741be4a5aa56e6d6caf49c1ae75cf', 'http://localhost:9502' )"
]
},
{
"cell_type": "code",
"execution_count": 166,
"id": "ff0229ce",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'blockHash': '0x0000000000000000000000000000000000000000000000000000000000000000',\n",
" 'blockNumber': None,\n",
" 'from': 'one1e8rdglh97t37prtnv7k35ymnh2wazujpzsmzes',\n",
" 'gas': 50000,\n",
" 'gasPrice': 100000000000,\n",
" 'hash': '0x279935278d20d778cbe4fdfa5d51be9eb1eb184053dc9a7cb88ad3365df73060',\n",
" 'msg': {'amount': 100000000000000000000,\n",
" 'delegatorAddress': 'one1e8rdglh97t37prtnv7k35ymnh2wazujpzsmzes',\n",
" 'validatorAddress': 'one155jp2y76nazx8uw5sa94fr0m4s5aj8e5xm6fu3'},\n",
" 'nonce': 2,\n",
" 'r': '0x8660a63c10af06f2fb3f24b92cf61d4f319044a1f1931c4f4e54ce986ff563c',\n",
" 's': '0x597785559c4283d3ece2df37cbf37077966487a2a2dc0f4cdbbf75a8f20bc1a8',\n",
" 'timestamp': 0,\n",
" 'transactionIndex': 0,\n",
" 'type': 'Delegate',\n",
" 'v': '0x27'}"
]
},
"execution_count": 166,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"transaction.get_staking_transaction_by_hash( \"0x279935278d20d778cbe4fdfa5d51be9eb1eb184053dc9a7cb88ad3365df73060\" )"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.5"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

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

@ -10,61 +10,58 @@ TEMP_DIR = "/tmp/pyhmy-testing/test-cli"
BINARY_FILE_PATH = f"{TEMP_DIR}/bin/cli_test_binary"
@pytest.fixture(scope="session", autouse=True)
@pytest.fixture( scope = "session", autouse = True )
def setup():
shutil.rmtree(TEMP_DIR, ignore_errors=True)
os.makedirs(TEMP_DIR, exist_ok=True)
shutil.rmtree( TEMP_DIR, ignore_errors = True )
os.makedirs( TEMP_DIR, exist_ok = True )
@pytest.mark.run(order=0)
def test_download_cli():
env = cli.download(BINARY_FILE_PATH, replace=False, verbose=False)
cli.environment.update(env)
assert os.path.exists(BINARY_FILE_PATH)
env = cli.download( BINARY_FILE_PATH, replace = False, verbose = False )
cli.environment.update( env )
assert os.path.exists( BINARY_FILE_PATH )
@pytest.mark.run(order=1)
def test_is_valid():
bad_file_path = os.path.realpath(f"{TEMP_DIR}/test_is_valid/bad_hmy")
shutil.rmtree(Path(bad_file_path).parent, ignore_errors=True)
os.makedirs(Path(bad_file_path).parent, exist_ok=True)
Path(bad_file_path).touch()
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 cli.is_valid_binary(BINARY_FILE_PATH)
assert not cli.is_valid_binary(bad_file_path)
bad_file_path = os.path.realpath( f"{TEMP_DIR}/test_is_valid/bad_hmy" )
shutil.rmtree( Path( bad_file_path ).parent, ignore_errors = True )
os.makedirs( Path( bad_file_path ).parent, exist_ok = True )
Path( bad_file_path ).touch()
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 cli.is_valid_binary( BINARY_FILE_PATH )
assert not cli.is_valid_binary( bad_file_path )
@pytest.mark.run(order=2)
def test_bad_bin_set():
bad_file_path = os.path.realpath(f"{TEMP_DIR}/test_bad_bin_set/hmy")
shutil.rmtree(Path(bad_file_path).parent, ignore_errors=True)
os.makedirs(Path(bad_file_path).parent, exist_ok=True)
Path(bad_file_path).touch()
is_set = cli.set_binary(bad_file_path)
bad_file_path = os.path.realpath( f"{TEMP_DIR}/test_bad_bin_set/hmy" )
shutil.rmtree( Path( bad_file_path ).parent, ignore_errors = True )
os.makedirs( Path( bad_file_path ).parent, exist_ok = True )
Path( bad_file_path ).touch()
is_set = cli.set_binary( bad_file_path )
assert not is_set
assert cli.get_binary_path() != bad_file_path
@pytest.mark.run(order=3)
def test_bin_set():
cli.set_binary(BINARY_FILE_PATH)
cli.set_binary( BINARY_FILE_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():
cli.single_call("hmy keys add test1")
cli.single_call( "hmy keys add test1" )
addrs = cli.get_accounts_keystore()
assert "test1" in addrs.keys()
check_addr = addrs["test1"]
accounts_list = cli.get_accounts(check_addr)
check_acc = accounts_list[0]
check_addr = addrs[ "test1" ]
accounts_list = cli.get_accounts( check_addr )
check_acc = accounts_list[ 0 ]
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_acc in raw_cli_keys_list_print
assert addrs[check_acc] == check_addr
cli.remove_address(check_addr)
assert addrs[ check_acc ] == check_addr
cli.remove_address( check_addr )
assert check_addr not in addrs.values()
assert "test1" not in addrs.keys()

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

@ -1,36 +1,31 @@
from decimal import Decimal
import pytest
from pyhmy import numbers
from pyhmy import (
numbers
)
@pytest.mark.run(order=1)
def test_convert_atto_to_one():
a = numbers.convert_atto_to_one(1e18)
assert Decimal(1) == a
a = numbers.convert_atto_to_one( 1e18 )
assert Decimal( 1 ) == a
b = numbers.convert_atto_to_one( 1e18 + 0.6 )
assert Decimal( 1 ) == b
b = numbers.convert_atto_to_one(1e18 + 0.6)
assert Decimal(1) == b
c = numbers.convert_atto_to_one( "1" + ( "0" * 18 ) )
assert Decimal( 1 ) == c
c = numbers.convert_atto_to_one('1' + ('0' * 18))
assert Decimal(1) == c
d = numbers.convert_atto_to_one( Decimal( 1e18 ) )
assert Decimal( 1 ) == d
d = numbers.convert_atto_to_one(Decimal(1e18))
assert Decimal(1) == d
@pytest.mark.run(order=2)
def test_convert_one_to_atto():
a = numbers.convert_one_to_atto(1e-18)
assert Decimal(1) == a
a = numbers.convert_one_to_atto( 1e-18 )
assert Decimal( 1 ) == a
b = numbers.convert_one_to_atto(1.5)
assert Decimal(1.5e18) == b
b = numbers.convert_one_to_atto( 1.5 )
assert Decimal( 1.5e18 ) == b
c = numbers.convert_one_to_atto('1')
assert Decimal(1e18) == c
c = numbers.convert_one_to_atto( "1" )
assert Decimal( 1e18 ) == c
d = numbers.convert_one_to_atto(Decimal(1))
assert Decimal(1e18) == d
d = numbers.convert_one_to_atto( Decimal( 1 ) )
assert Decimal( 1e18 ) == d

@ -4,17 +4,14 @@ import socket
import pytest
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():
endpoint = 'http://localhost:9500'
endpoint = "http://localhost:9500"
timeout = 30
method = 'hmyv2_getNodeMetadata'
method = "hmyv2_getNodeMetadata"
params = []
payload = {
"id": "1",
@ -23,52 +20,64 @@ def setup():
"params": params
}
headers = {
'Content-Type': 'application/json'
"Content-Type": "application/json"
}
try:
response = requests.request('POST', endpoint, headers=headers,
data=json.dumps(payload), timeout=timeout, allow_redirects=True)
response = requests.request(
"POST",
endpoint,
headers = headers,
data = json.dumps( payload ),
timeout = timeout,
allow_redirects = True,
)
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
)
@pytest.mark.run(order=1)
def test_request_connection_error():
# Find available port
s = socket.socket()
s.bind(('localhost', 0))
port = s.getsockname()[1]
s.bind( ( "localhost", 0 ) )
port = s.getsockname()[ 1 ]
s.close()
if port == 0:
pytest.skip("could not find available port")
bad_endpoint = f'http://localhost:{port}'
pytest.skip( "could not find available port" )
bad_endpoint = f"http://localhost:{port}"
bad_request = None
try:
bad_request = request.rpc_request('hmyv2_getNodeMetadata', endpoint=bad_endpoint)
bad_request = request.rpc_request(
"hmyv2_getNodeMetadata",
endpoint = bad_endpoint
)
except Exception as e:
assert isinstance(e, exceptions.RequestsError)
assert isinstance( e, exceptions.RequestsError )
assert bad_request is None
@pytest.mark.run(order=2)
def test_request_rpc_error():
error_request = None
try:
error_request = request.rpc_request('hmyv2_getBalance')
except (exceptions.RequestsTimeoutError, exceptions.RequestsError) as err:
pytest.skip("can not connect to local blockchain", allow_module_level=True)
error_request = request.rpc_request( "hmyv2_getBalance" )
except ( exceptions.RequestsTimeoutError, exceptions.RequestsError ) as err:
pytest.skip(
"can not connect to local blockchain",
allow_module_level = True
)
except Exception as e:
assert isinstance(e, exceptions.RPCError)
assert isinstance( e, exceptions.RPCError )
assert error_request is None
@pytest.mark.run(order=3)
def test_rpc_request():
endpoint = 'http://localhost:9500'
endpoint = "http://localhost:9500"
timeout = 30
method = 'hmyv2_getNodeMetadata'
method = "hmyv2_getNodeMetadata"
params = []
payload = {
"id": "1",
@ -77,29 +86,35 @@ def test_rpc_request():
"params": params
}
headers = {
'Content-Type': 'application/json'
"Content-Type": "application/json"
}
response = None
try:
response = requests.request('POST', endpoint, headers=headers,
data=json.dumps(payload), timeout=timeout, allow_redirects=True)
response = requests.request(
"POST",
endpoint,
headers = headers,
data = json.dumps( payload ),
timeout = timeout,
allow_redirects = True,
)
except:
pytest.skip("can not connect to local blockchain")
pytest.skip( "can not connect to local blockchain" )
assert response is not None
resp = None
try:
resp = json.loads(response.content)
resp = json.loads( response.content )
except json.decoder.JSONDecodeError as err:
pytest.skip('unable to decode response')
pytest.skip( "unable to decode response" )
assert resp is not None
rpc_response = None
try:
rpc_response = request.rpc_request(method, params, endpoint, timeout)
rpc_response = request.rpc_request( method, params, endpoint, timeout )
except exceptions.RPCError as e:
assert 'error' in resp
assert "error" in resp
if rpc_response is not None:
assert rpc_response == resp

@ -1,72 +1,74 @@
import json
import time
import random
import pytest
import requests
test_validator_address = 'one18tvf56zqjkjnak686lwutcp5mqfnvee35xjnhc'
transfer_raw_transaction = '0xf86f80843b9aca008252080180943ad89a684095a53edb47d7ddc5e034d8133667318a152d02c7e14af68000008027a0ec6c8ad0f70b3c826fa77574c6815a8f73936fafb7b2701a7082ad7d278c95a9a0429f9f166b1c1d385a4ec8f8b86604c26e427c2b0a1c85d9cf4ec6bbd0719508'
tx_hash = '0x1fa20537ea97f162279743139197ecf0eac863278ac1c8ada9a6be5d1e31e633'
create_validator_raw_transaction = '0xf9015680f90105943ad89a684095a53edb47d7ddc5e034d813366731d984746573748474657374847465737484746573748474657374ddc988016345785d8a0000c9880c7d713b49da0000c887b1a2bc2ec500008a022385a827e8155000008b084595161401484a000000f1b0282554f2478661b4844a05a9deb1837aac83931029cb282872f0dcd7239297c499c02ea8da8746d2f08ca2b037e89891f862b86003557e18435c201ecc10b1664d1aea5b4ec59dbfe237233b953dbd9021b86bc9770e116ed3c413fe0334d89562568a10e133d828611f29fee8cdab9719919bbcc1f1bf812c73b9ccd0f89b4f0b9ca7e27e66d58bbb06fcf51c295b1d076cfc878a0228f16f86157860000080843b9aca008351220027a018385211a150ca032c3526cef0aba6a75f99a18cb73f547f67bab746be0c7a64a028be921002c6eb949b3932afd010dfe1de2459ec7fe84403b9d9d8892394a78c'
staking_tx_hash = '0x57ec011aabdeb078a4816502224022f291fa8b07c82bbae8476f514a1d71c730'
contract_tx_hash = '0xa13414dd152173395c69a11e79dea31bf029660f747a42a53744181d05571e70'
contract_raw_transaction = '0xf9025080843b9aca008366916c80808080b901fc608060405234801561001057600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555061019c806100606000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063445df0ac146100465780638da5cb5b14610064578063fdacd576146100ae575b600080fd5b61004e6100dc565b6040518082815260200191505060405180910390f35b61006c6100e2565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100da600480360360208110156100c457600080fd5b8101908080359060200190929190505050610107565b005b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561016457806001819055505b5056fea265627a7a723158209b80813a158b44af65aee232b44c0ac06472c48f4abbe298852a39f0ff34a9f264736f6c6343000510003227a03a3ad2b7c2934a8325fc04d04daad740d337bb1f589482bbb1d091e1be804d29a00c46772871866a34f254e6197a526bebc2067f75edc53c488b31d84e07c3c685'
# private keys
# 1f84c95ac16e6a50f08d44c7bde7aff8742212fda6e4321fde48bf83bef266dc / one155jp2y76nazx8uw5sa94fr0m4s5aj8e5xm6fu3 (genesis)
# 3c86ac59f6b038f584be1c08fced78d7c71bb55d5655f81714f3cddc82144c65 / one1ru3p8ff0wsyl7ncsx3vwd5szuze64qz60upg37 (transferred 503)
endpoint = 'http://localhost:9500'
endpoint_shard_one = 'http://localhost:9501'
endpoint = "http://localhost:9500"
timeout = 30
headers = {
'Content-Type': 'application/json'
"Content-Type": "application/json"
}
txs = [
# same shard 503 ONE transfer from one155jp2y76nazx8uw5sa94fr0m4s5aj8e5xm6fu3 to one1ru3p8ff0wsyl7ncsx3vwd5szuze64qz60upg37 (0 nonce)
"0xf86f8085174876e8008252088080941f2213a52f7409ff4f103458e6d202e0b3aa805a891b4486fafde57c00008027a0d7c0b20207dcc9dde376822dc3f5625eac6f59a7526111695cdba3e29553ca17a05d4ca9a421ae16f89cbf6848186eaea7a800da732446dff9952e7c1e91d414e3",
# contract creation by one1ru3p8ff0wsyl7ncsx3vwd5szuze64qz60upg37 (0 nonce)
"0xf8e88085174876e800830186a080808080b8946080604052348015600f57600080fd5b50607780601d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80634936cd3614602d575b600080fd5b604080516001815290519081900360200190f3fea2646970667358221220fa3fa0e8d0267831a59f4dd5edf39a513d07e98461cb06660ad28d4beda744cd64736f6c634300080f003327a08bf26ee0120c296b17af507f62606abdb5c5f09a65642c3d30b349b8bfbb3d69a03ec7be51c615bcbf2f1d63f6eaa56cf8d7be81671717f90239619830a81ebc9f",
# cross shard transfer by one1ru3p8ff0wsyl7ncsx3vwd5szuze64qz60upg37 (1 nonce)
"0xf86b0185174876e800825208800194c9c6d47ee5f2e3e08d7367ad1a1373ba9dd1724185174876e8008027a02501c517220e9499f14e97c20b0a88cd3b7ba80637bba43ed295422e69a3f300a079b8e1213c9506184aed6ac2eb0b2cb00594c3f9fcdd6c088937ce17fe47107c",
]
stxs = [
# creation of one155jp2y76nazx8uw5sa94fr0m4s5aj8e5xm6fu3 as validator (1 nonce)
"0xf9017c80f9012994a5241513da9f4463f1d4874b548dfbac29d91f34f83d85416c69636585616c69636591616c6963652e6861726d6f6e792e6f6e6583426f629a41726520796f75206576656e2072656164696e6720746869733fddc988016345785d8a0000c9880c7d713b49da0000c887b1a2bc2ec500008a021e19e0c9bab24000008a152d02c7e14af6800000f1b0a20e70089664a874b00251c5e85d35a73871531306f3af43e02138339d294e6bb9c4eb82162199c6a852afeaa8d68712f862b860ef2c49a2f31fbbd23c21bc176eaf05cd0bebe6832033075d81fea7cff6f9bc1ab42f3b6895c5493fe645d8379d2eaa1413de55a9d3ce412a4f747cb57d52cc4da4754bfb2583ec9a41fe5dd48287f964f276336699959a5fcef3391dc24df00d8a021e19e0c9bab24000000185174876e8008403473bc028a08c1146305eaef981aa24c2f17c8519664d10c99ee42acedbc258749930d31a7ca031dadf114ee6ab9bd09933208094c65037b66c796bcfc57a70158106b37357b0",
# delegation by one1ru3p8ff0wsyl7ncsx3vwd5szuze64qz60upg37 to one155jp2y76nazx8uw5sa94fr0m4s5aj8e5xm6fu3 (2 nonce)
"0xf88302f4941f2213a52f7409ff4f103458e6d202e0b3aa805a94a5241513da9f4463f1d4874b548dfbac29d91f3489056bc75e2d631000000285174876e80082c35028a02c5e953062dcdfa2de9723639b63bab45705eb6dfbfe7f44536ed266c3c7ca20a0742964e646338e7431874f70715565d99c01c762324355c69db34a9ed9de81d7",
]
tx_hashes = [
"0xc26be5776aa57438bccf196671a2d34f3f22c9c983c0f844c62b2fb90403aa43",
"0xa605852dd2fa39ed42e101c17aaca9d344d352ba9b24b14b9af94ec9cb58b31f",
"0xf73ba634cb96fc0e3e2c9d3b4c91379e223741be4a5aa56e6d6caf49c1ae75cf",
]
stx_hashes = [
"0x400e9831d358f5daccd153cad5bf53650a0d413bd8682ec0ffad55367d162968",
"0xc8177ace2049d9f4eb4a45fd6bd6b16f693573d036322c36774cc00d05a3e24f",
]
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"
@pytest.fixture(scope="session", autouse=True)
@pytest.fixture( scope = "session", autouse = True )
def setup_blockchain():
# return
metadata = _check_connection()
_check_staking_epoch(metadata)
tx_data = _check_funding_transaction()
if not tx_data['result']:
_send_funding_transaction()
time.sleep(20) # Sleep to let cross shard transaction finalize
tx_data = _check_funding_transaction()
if 'error' in tx_data:
pytest.skip(f"Error in hmyv2_getTransactionByHash reply: {tx_data['error']}", allow_module_level=True)
if not tx_data['result']:
pytest.skip(f"Funding transaction failed: {tx_hash}", allow_module_level=True)
stx_data = _check_staking_transaction()
if not stx_data['result']:
_send_staking_transaction()
time.sleep(30) # Sleep to let transaction finalize
stx_data = _check_staking_transaction()
if 'error' in stx_data:
pytest.skip(f"Error in hmyv2_getStakingTransactionByHash reply: {stx_data['error']}", allow_module_level=True)
if not stx_data['result']:
pytest.skip(f"Staking transaction failed: {staking_tx_hash}", allow_module_level=True)
_check_staking_epoch( metadata )
contract_data = _check_contract_transaction()
for i in range( len( txs ) ):
tx = txs[ i ]
tx_hash = tx_hashes[ i ]
_send_transaction( tx, endpoint )
if not _wait_for_transaction_confirmed( tx_hash, endpoint ):
pytest.skip(
"Could not confirm initial transaction #{} on chain"
.format( i ),
allow_module_level = True,
)
if not contract_data['result']:
_send_contract_transaction()
times.sleep(30)
contract_data = _check_contract_transaction()
if 'error' in contract_data:
pytest.skip(f"Error in hmyv2_getStakingTransactionByHash reply: {contract_data['error']}", allow_module_level=True)
if not contract_data['result']:
pytest.skip(f"Staking transaction failed: {contract_tx_hash}", allow_module_level=True)
# TODO: Build data object to return data instead of hard coded values in the test files
try:
return int(stx_data['result']['blockNumber'])
except (TypeError, KeyError) as e:
pytest.skip(f"Unexpected reply for hmyv2_getStakingTransactionByHash: {stx_data['result']}", allow_module_level=True)
for i in range( len( stxs ) ):
stx = stxs[ i ]
stx_hash = stx_hashes[ i ]
_send_staking_transaction( stx, endpoint )
if not _wait_for_staking_transaction_confirmed( stx_hash, endpoint ):
pytest.skip(
"Could not confirm initial staking transaction #{} on chain"
.format( i ),
allow_module_level = True,
)
def _check_connection():
@ -74,132 +76,206 @@ def _check_connection():
payload = {
"id": "1",
"jsonrpc": "2.0",
"method": 'hmyv2_getNodeMetadata',
"params": []
"method": "hmyv2_getNodeMetadata",
"params": [],
}
response = requests.request('POST', endpoint, headers=headers,
data=json.dumps(payload), timeout=timeout, allow_redirects=True)
metadata = json.loads(response.content)
if 'error' in metadata:
pytest.skip(f"Error in hmyv2_getNodeMetadata reply: {metadata['error']}", allow_module_level=True)
if 'chain-config' not in metadata['result']:
pytest.skip("Chain config not found in hmyv2_getNodeMetadata reply", allow_module_level=True)
response = requests.request(
"POST",
endpoint,
headers = headers,
data = json.dumps( payload ),
timeout = timeout,
allow_redirects = True,
)
metadata = json.loads( response.content )
if "error" in metadata:
pytest.skip(
f"Error in hmyv2_getNodeMetadata reply: {metadata['error']}",
allow_module_level = True,
)
if "chain-config" not in metadata[ "result" ]:
pytest.skip(
"Chain config not found in hmyv2_getNodeMetadata reply",
allow_module_level = True,
)
return metadata
except Exception as e:
pytest.skip('Can not connect to local blockchain or bad hmyv2_getNodeMetadata reply', allow_module_level=True)
pytest.skip(
"Can not connect to local blockchain or bad hmyv2_getNodeMetadata reply",
allow_module_level = True,
)
def _check_staking_epoch(metadata):
def _check_staking_epoch( metadata ):
latest_header = None
try:
payload = {
"id": "1",
"jsonrpc": "2.0",
"method": 'hmyv2_latestHeader',
"params": []
"method": "hmyv2_latestHeader",
"params": [],
}
response = requests.request('POST', endpoint, headers=headers,
data=json.dumps(payload), timeout=timeout, allow_redirects=True)
latest_header = json.loads(response.content)
if 'error' in latest_header:
pytest.skip(f"Error in hmyv2_latestHeader reply: {latest_header['error']}", allow_module_level=True)
response = requests.request(
"POST",
endpoint,
headers = headers,
data = json.dumps( payload ),
timeout = timeout,
allow_redirects = True,
)
latest_header = json.loads( response.content )
if "error" in latest_header:
pytest.skip(
f"Error in hmyv2_latestHeader reply: {latest_header['error']}",
allow_module_level = True,
)
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:
staking_epoch = metadata['result']['chain-config']['staking-epoch']
current_epoch = latest_header['result']['epoch']
staking_epoch = metadata[ "result" ][ "chain-config" ][ "staking-epoch"
]
current_epoch = latest_header[ "result" ][ "epoch" ]
if staking_epoch > current_epoch:
pytest.skip(f'Not staking epoch: current {current_epoch}, staking {staking_epoch}', allow_module_level=True)
pytest.skip(
f"Not staking epoch: current {current_epoch}, staking {staking_epoch}",
allow_module_level = True,
)
def _send_funding_transaction():
try:
payload = {
"id": "1",
"jsonrpc": "2.0",
"method": 'hmyv2_sendRawTransaction',
"params": [transfer_raw_transaction]
}
response = requests.request('POST', endpoint_shard_one, headers=headers,
data=json.dumps(payload), timeout=timeout, allow_redirects=True)
tx = json.loads(response.content)
if 'error' in tx:
pytest.skip(f"Error in hmyv2_sendRawTransaction reply: {tx['error']}", allow_module_level=True)
except Exception as e:
pytest.skip('Failed to get hmyv2_sendRawTransaction reply', allow_module_level=True)
def _check_funding_transaction():
def _send_transaction( raw_tx, endpoint ):
try:
payload = {
"id": "1",
"jsonrpc": "2.0",
"method": 'hmyv2_getTransactionByHash',
"params": [tx_hash]
"method": "hmyv2_sendRawTransaction",
"params": [ raw_tx ],
}
response = requests.request('POST', endpoint_shard_one, headers=headers,
data=json.dumps(payload), timeout=timeout, allow_redirects=True)
tx_data = json.loads(response.content)
return tx_data
response = requests.request(
"POST",
endpoint,
headers = headers,
data = json.dumps( payload ),
timeout = timeout,
allow_redirects = True,
)
tx = json.loads( response.content )
if "error" in tx:
pytest.skip(
f"Error in hmyv2_sendRawTransaction reply: {tx['error']}",
allow_module_level = True,
)
except Exception as e:
pytest.skip('Failed to get hmyv2_getTransactionByHash reply', allow_module_level=True)
pytest.skip(
"Failed to get hmyv2_sendRawTransaction reply",
allow_module_level = True
)
def _check_contract_transaction():
def _check_transaction( tx_hash, endpoint ):
try:
payload = {
"id": "1",
"jsonrpc": "2.0",
"method": 'hmyv2_getTransactionByHash',
"params": [contract_tx_hash]
"method": "hmyv2_getTransactionByHash",
"params": [ tx_hash ],
}
response = requests.request('POST', endpoint, headers=headers,
data=json.dumps(payload), timeout=timeout, allow_redirects=True)
tx_data = json.loads(response.content)
response = requests.request(
"POST",
endpoint,
headers = headers,
data = json.dumps( payload ),
timeout = timeout,
allow_redirects = True,
)
tx_data = json.loads( response.content )
return tx_data
except Exception as e:
pytest.skip('Failed to get hmyv2_getTransactionByHash reply', allow_module_level=True)
pytest.skip(
"Failed to get hmyv2_getTransactionByHash reply",
allow_module_level = True
)
def _wait_for_transaction_confirmed( tx_hash, endpoint, timeout = 30 ):
start_time = time.time()
while ( time.time() - start_time ) <= timeout:
tx_data = _check_transaction( tx_hash, endpoint )
if tx_data is not None:
block_hash = tx_data[ "result" ].get( "blockHash", "0x00" )
unique_chars = "".join( set( list( block_hash[ 2 : ] ) ) )
if unique_chars != "0":
return True
time.sleep( random.uniform( 0.2, 0.5 ) )
return False
def _send_contract_transaction():
try:
payload = {
"id": "1",
"jsonrpc": "2.0",
"method": 'hmyv2_sendRawTransaction',
"params": [contract_raw_transaction]
}
response = requests.request('POST', endpoint, headers=headers,
data=json.dumps(payload), timeout=timeout, allow_redirects=True)
tx_data = json.loads(response.content)
if 'error' in staking_tx:
pytest.skip(f"Error in hmyv2_sendRawTransaction reply: {tx_data['error']}", allow_module_level=True)
except Exception as e:
pytest.skip('Failed to get hmyv2_sendRawTransaction reply', allow_module_level=True)
def _send_staking_transaction():
def _send_staking_transaction( raw_tx, endpoint = endpoint ):
try:
payload = {
"id": "1",
"jsonrpc": "2.0",
"method": 'hmyv2_sendRawStakingTransaction',
"params": [create_validator_raw_transaction]
"method": "hmyv2_sendRawStakingTransaction",
"params": [ raw_tx ],
}
response = requests.request('POST', endpoint, headers=headers,
data=json.dumps(payload), timeout=timeout, allow_redirects=True)
staking_tx = json.loads(response.content)
if 'error' in staking_tx:
pytest.skip(f"Error in hmyv2_sendRawStakingTransaction reply: {staking_tx['error']}", allow_module_level=True)
response = requests.request(
"POST",
endpoint,
headers = headers,
data = json.dumps( payload ),
timeout = timeout,
allow_redirects = True,
)
staking_tx = json.loads( response.content )
if "error" in staking_tx:
pytest.skip(
f"Error in hmyv2_sendRawStakingTransaction reply: {staking_tx['error']}",
allow_module_level = True,
)
except Exception as e:
pytest.skip('Failed to get hmyv2_sendRawStakingTransaction reply', allow_module_level=True)
pytest.skip(
"Failed to get hmyv2_sendRawStakingTransaction reply",
allow_module_level = True,
)
def _check_staking_transaction():
def _check_staking_transaction( stx_hash, endpoint = endpoint ):
try:
payload = {
"id": "1",
"jsonrpc": "2.0",
"method": 'hmyv2_getStakingTransactionByHash',
"params": [staking_tx_hash]
"method": "hmyv2_getStakingTransactionByHash",
"params": [ stx_hash ],
}
response = requests.request('POST', endpoint, headers=headers,
data=json.dumps(payload), timeout=timeout, allow_redirects=True)
stx_data = json.loads(response.content)
response = requests.request(
"POST",
endpoint,
headers = headers,
data = json.dumps( payload ),
timeout = timeout,
allow_redirects = True,
)
stx_data = json.loads( response.content )
return stx_data
except Exception as e:
pytest.skip('Failed to get hmyv2_getStakingTransactionByHash reply', allow_module_level=True)
pytest.skip(
"Failed to get hmyv2_getStakingTransactionByHash reply",
allow_module_level = True,
)
def _wait_for_staking_transaction_confirmed( tx_hash, endpoint, timeout = 30 ):
answer = False
start_time = time.time()
while ( time.time() - start_time ) <= timeout:
tx_data = _check_staking_transaction( tx_hash, endpoint )
if tx_data is not None:
block_hash = tx_data[ "result" ].get( "blockHash", "0x00" )
unique_chars = "".join( set( list( block_hash[ 2 : ] ) ) )
if unique_chars != "0":
answer = True
time.sleep( random.uniform( 0.2, 0.5 ) )
return answer

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

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

@ -1,74 +1,86 @@
import pytest
from pyhmy import (
contract
)
from pyhmy import contract
from pyhmy.rpc import (
exceptions
)
from pyhmy.rpc import exceptions
explorer_endpoint = 'http://localhost:9599'
contract_tx_hash = '0xa13414dd152173395c69a11e79dea31bf029660f747a42a53744181d05571e70'
explorer_endpoint = "http://localhost:9599"
contract_tx_hash = "0xa605852dd2fa39ed42e101c17aaca9d344d352ba9b24b14b9af94ec9cb58b31f"
# deployedBytecode from json file
contract_code = "0x6080604052348015600f57600080fd5b506004361060285760003560e01c80634936cd3614602d575b600080fd5b604080516001815290519081900360200190f3fea2646970667358221220fa3fa0e8d0267831a59f4dd5edf39a513d07e98461cb06660ad28d4beda744cd64736f6c634300080f0033"
contract_address = None
fake_shard = 'http://example.com'
fake_shard = "http://example.com"
def _test_contract_rpc(fn, *args, **kwargs):
if not callable(fn):
pytest.fail(f'Invalid function: {fn}')
def _test_contract_rpc( fn, *args, **kwargs ):
if not callable( fn ):
pytest.fail( f"Invalid function: {fn}" )
try:
response = fn(*args, **kwargs)
response = fn( *args, **kwargs )
except Exception as e:
if isinstance(e, exceptions.RPCError) and 'does not exist/is not available' in str(e):
pytest.skip(f'{str(e)}')
elif isinstance(e, exceptions.RPCError) and 'estimateGas returned' in str(e):
pytest.skip(f'{str(e)}')
pytest.fail(f'Unexpected error: {e.__class__} {e}')
if isinstance( e,
exceptions.RPCError
) and "does not exist/is not available" in str( e ):
pytest.skip( f"{str(e)}" )
elif isinstance( e,
exceptions.RPCError
) and "estimateGas returned" in str( e ):
pytest.skip( f"{str(e)}" )
pytest.fail( f"Unexpected error: {e.__class__} {e}" )
return response
@pytest.mark.run(order=1)
def test_get_contract_address_from_hash(setup_blockchain):
def test_get_contract_address_from_hash( setup_blockchain ):
global contract_address
contract_address = _test_contract_rpc(contract.get_contract_address_from_hash, contract_tx_hash)
assert isinstance(contract_address, str)
contract_address = _test_contract_rpc(
contract.get_contract_address_from_hash,
contract_tx_hash
)
assert isinstance( contract_address, str )
@pytest.mark.run(order=2)
def test_call(setup_blockchain):
def test_call( setup_blockchain ):
if not contract_address:
pytest.skip('Contract address not loaded yet')
called = _test_contract_rpc(contract.call, contract_address, 'latest')
assert isinstance(called, str) and called.startswith('0x')
pytest.skip( "Contract address not loaded yet" )
called = _test_contract_rpc( contract.call, contract_address, "latest" )
assert isinstance( called, str ) and called.startswith( "0x" )
@pytest.mark.run(order=3)
def test_estimate_gas(setup_blockchain):
def test_estimate_gas( setup_blockchain ):
if not contract_address:
pytest.skip('Contract address not loaded yet')
gas = _test_contract_rpc(contract.estimate_gas, contract_address)
assert isinstance(gas, int)
pytest.skip( "Contract address not loaded yet" )
gas = _test_contract_rpc( contract.estimate_gas, contract_address )
assert isinstance( gas, int )
@pytest.mark.run(order=4)
def test_get_code(setup_blockchain):
def test_get_code( setup_blockchain ):
if not contract_address:
pytest.skip('Contract address not loaded yet')
code = _test_contract_rpc(contract.get_code, contract_address, 'latest')
assert code == '0x608060405234801561001057600080fd5b50600436106100415760003560e01c8063445df0ac146100465780638da5cb5b14610064578063fdacd576146100ae575b600080fd5b61004e6100dc565b6040518082815260200191505060405180910390f35b61006c6100e2565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100da600480360360208110156100c457600080fd5b8101908080359060200190929190505050610107565b005b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561016457806001819055505b5056fea265627a7a723158209b80813a158b44af65aee232b44c0ac06472c48f4abbe298852a39f0ff34a9f264736f6c63430005100032'
pytest.skip( "Contract address not loaded yet" )
code = _test_contract_rpc( contract.get_code, contract_address, "latest" )
assert code == contract_code
@pytest.mark.run(order=5)
def test_get_storage_at(setup_blockchain):
def test_get_storage_at( setup_blockchain ):
if not contract_address:
pytest.skip('Contract address not loaded yet')
storage = _test_contract_rpc(contract.get_storage_at, contract_address, '0x0', 'latest')
assert isinstance(storage, str) and storage.startswith('0x')
pytest.skip( "Contract address not loaded yet" )
storage = _test_contract_rpc(
contract.get_storage_at,
contract_address,
"0x0",
"latest"
)
assert isinstance( storage, str ) and storage.startswith( "0x" )
def test_errors():
with pytest.raises(exceptions.RPCError):
contract.get_contract_address_from_hash('', fake_shard)
with pytest.raises(exceptions.RPCError):
contract.call('', '', endpoint=fake_shard)
with pytest.raises(exceptions.RPCError):
contract.estimate_gas('', endpoint=fake_shard)
with pytest.raises(exceptions.RPCError):
contract.get_code('', 'latest', endpoint=fake_shard)
with pytest.raises(exceptions.RPCError):
contract.get_storage_at('', 1, 'latest', endpoint=fake_shard)
with pytest.raises( exceptions.RPCError ):
contract.get_contract_address_from_hash( "", fake_shard )
with pytest.raises( exceptions.RPCError ):
contract.call( "", "", endpoint = fake_shard )
with pytest.raises( exceptions.RPCError ):
contract.estimate_gas( "", endpoint = fake_shard )
with pytest.raises( exceptions.RPCError ):
contract.get_code( "", "latest", endpoint = fake_shard )
with pytest.raises( exceptions.RPCError ):
contract.get_storage_at( "", 1, "latest", endpoint = fake_shard )

@ -1,7 +1,4 @@
from pyhmy import (
signing
)
from pyhmy import signing
"""
Test signature source (node.js)
import { Transaction, RLPSign, TxStatus } from '@harmony-js/transaction';
@ -30,16 +27,25 @@ let signed = RLPSign(transaction, privateKey);
console.log( 'Signed transaction' )
console.log(signed)
"""
def test_eth_transaction():
transaction_dict = {
'nonce': 2,
'gasPrice': 1,
'gas': 100, # signing.py uses Ether, which by default calls it gas
'to': '0x14791697260e4c9a71f18484c9f997b308e59325',
'value': 5,
"nonce": 2,
"gasPrice": 1,
"gas": 100, # signing.py uses Ether, which by default calls it gas
"to": "0x14791697260e4c9a71f18484c9f997b308e59325",
"value": 5,
}
signed_tx = signing.sign_transaction(transaction_dict, '4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48')
assert signed_tx.rawTransaction.hex() == '0xf85d0201649414791697260e4c9a71f18484c9f997b308e5932505801ca0b364f4296bfd3231889d1b9ac94c68abbcb8ee6a6c7a5fa412ac82b5b7b0d5d1a02233864842ab28ee4f99c207940a867b0f8534ca362836190792816b48dde3b1'
signed_tx = signing.sign_transaction(
transaction_dict,
"4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48",
)
assert (
signed_tx.rawTransaction.hex() ==
"0xf85d0201649414791697260e4c9a71f18484c9f997b308e5932505801ca0b364f4296bfd3231889d1b9ac94c68abbcb8ee6a6c7a5fa412ac82b5b7b0d5d1a02233864842ab28ee4f99c207940a867b0f8534ca362836190792816b48dde3b1"
)
"""
Test signature source (node.js)
@ -71,16 +77,24 @@ let signed = RLPSign(transaction, privateKey);
console.log( 'Signed transaction' )
console.log(signed)
"""
def test_hmy_transaction():
transaction_dict = {
'nonce': 2,
'gasPrice': 1,
'gas': 100, # signing.py uses Ether, which by default calls it gas
'to': '0x14791697260e4c9a71f18484c9f997b308e59325',
'value': 5,
'shardID': 0,
'toShardID': 1,
'chainId': 'HmyMainnet'
"nonce": 2,
"gasPrice": 1,
"gas": 100, # signing.py uses Ether, which by default calls it gas
"to": "0x14791697260e4c9a71f18484c9f997b308e59325",
"value": 5,
"shardID": 0,
"toShardID": 1,
"chainId": "HmyMainnet",
}
signed_tx = signing.sign_transaction(transaction_dict, '4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48')
assert signed_tx.rawTransaction.hex() == '0xf85f02016480019414791697260e4c9a71f18484c9f997b308e59325058026a02a203357ca6d7cdec981ad3d3692ad2c9e24536a9b6e7b486ce2f94f28c7563ea010d38cd0312a153af0aa7d8cd986040c36118bba373cb94e3e86fd4aedce904d'
signed_tx = signing.sign_transaction(
transaction_dict,
"4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48",
)
assert (
signed_tx.rawTransaction.hex() ==
"0xf85f02016480019414791697260e4c9a71f18484c9f997b308e59325058026a02a203357ca6d7cdec981ad3d3692ad2c9e24536a9b6e7b486ce2f94f28c7563ea010d38cd0312a153af0aa7d8cd986040c36118bba373cb94e3e86fd4aedce904d"
)

@ -1,191 +1,248 @@
import pytest
import requests
from pyhmy import (
staking
)
from pyhmy import staking
from pyhmy.rpc import (
exceptions
)
from pyhmy.rpc import exceptions
explorer_endpoint = "http://localhost:9700"
test_validator_address = "one155jp2y76nazx8uw5sa94fr0m4s5aj8e5xm6fu3"
fake_shard = "http://example.com"
explorer_endpoint = 'http://localhost:9599'
test_validator_address = 'one18tvf56zqjkjnak686lwutcp5mqfnvee35xjnhc'
fake_shard = 'http://example.com'
def _test_staking_rpc(fn, *args, **kwargs):
if not callable(fn):
pytest.fail(f'Invalid function: {fn}')
def _test_staking_rpc( fn, *args, **kwargs ):
if not callable( fn ):
pytest.fail( f"Invalid function: {fn}" )
try:
response = fn(*args, **kwargs)
response = fn( *args, **kwargs )
except Exception as e:
if isinstance(e, exceptions.RPCError) and 'does not exist/is not available' in str(e):
pytest.skip(f'{str(e)}')
pytest.fail(f'Unexpected error: {e.__class__} {e}')
if isinstance( e,
exceptions.RPCError
) and "does not exist/is not available" in str( e ):
pytest.skip( f"{str(e)}" )
pytest.fail( f"Unexpected error: {e.__class__} {e}" )
return response
@pytest.mark.run(order=1)
def test_get_all_validator_addresses(setup_blockchain):
validator_addresses = _test_staking_rpc(staking.get_all_validator_addresses)
assert isinstance(validator_addresses, list)
assert len(validator_addresses) > 0
def test_get_all_validator_addresses( setup_blockchain ):
validator_addresses = _test_staking_rpc(
staking.get_all_validator_addresses
)
assert isinstance( validator_addresses, list )
assert len( validator_addresses ) > 0
assert test_validator_address in validator_addresses
@pytest.mark.run(order=2)
def test_get_validator_information(setup_blockchain):
info = _test_staking_rpc(staking.get_validator_information, test_validator_address)
assert isinstance(info, dict)
@pytest.mark.run(order=3)
def test_get_all_validator_information(setup_blockchain):
all_validator_information = _test_staking_rpc(staking.get_all_validator_information)
assert isinstance(all_validator_information, list)
assert len(all_validator_information) > 0
@pytest.mark.run(order=4)
def test_get_delegations_by_delegator(setup_blockchain):
delegations = _test_staking_rpc(staking.get_delegations_by_delegator, test_validator_address)
assert isinstance(delegations, list)
assert len(delegations) > 0
@pytest.mark.run(order=5)
def test_get_delegations_by_validator(setup_blockchain):
delegations = _test_staking_rpc(staking.get_delegations_by_validator, test_validator_address)
assert isinstance(delegations, list)
assert len(delegations) > 0
@pytest.mark.run(order=6)
def test_get_current_utility_metrics(setup_blockchain):
metrics = _test_staking_rpc(staking.get_current_utility_metrics)
assert isinstance(metrics, dict)
@pytest.mark.run(order=7)
def test_get_staking_network_info(setup_blockchain):
info = _test_staking_rpc(staking.get_staking_network_info)
assert isinstance(info, dict)
@pytest.mark.run(order=8)
def test_get_super_committees(setup_blockchain):
committee = _test_staking_rpc(staking.get_super_committees)
assert isinstance(committee, dict)
@pytest.mark.run(order=9)
def test_get_raw_median_stake_snapshot(setup_blockchain):
median_stake = _test_staking_rpc(staking.get_raw_median_stake_snapshot)
assert isinstance(median_stake, dict)
@pytest.mark.run(order=10)
def test_get_validator_information_by_block(setup_blockchain):
def test_get_validator_information( setup_blockchain ):
info = _test_staking_rpc(
staking.get_validator_information,
test_validator_address
)
assert isinstance( info, dict )
def test_get_all_validator_information( setup_blockchain ):
all_validator_information = _test_staking_rpc(
staking.get_all_validator_information
)
assert isinstance( all_validator_information, list )
assert len( all_validator_information ) > 0
def test_get_delegations_by_delegator( setup_blockchain ):
delegations = _test_staking_rpc(
staking.get_delegations_by_delegator,
test_validator_address
)
assert isinstance( delegations, list )
assert len( delegations ) > 0
def test_get_delegations_by_validator( setup_blockchain ):
delegations = _test_staking_rpc(
staking.get_delegations_by_validator,
test_validator_address
)
assert isinstance( delegations, list )
assert len( delegations ) > 0
def test_get_current_utility_metrics( setup_blockchain ):
metrics = _test_staking_rpc( staking.get_current_utility_metrics )
assert isinstance( metrics, dict )
def test_get_staking_network_info( setup_blockchain ):
info = _test_staking_rpc( staking.get_staking_network_info )
assert isinstance( info, dict )
def test_get_super_committees( setup_blockchain ):
committee = _test_staking_rpc( staking.get_super_committees )
assert isinstance( committee, dict )
def test_get_raw_median_stake_snapshot( setup_blockchain ):
median_stake = _test_staking_rpc( staking.get_raw_median_stake_snapshot )
assert isinstance( median_stake, dict )
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
info = _test_staking_rpc(staking.get_validator_information_by_block_number, test_validator_address, setup_blockchain + 1, endpoint=explorer_endpoint)
assert isinstance(info, dict)
info = _test_staking_rpc(
staking.get_validator_information_by_block_number,
test_validator_address,
"latest",
endpoint = explorer_endpoint,
)
assert isinstance( info, dict )
@pytest.mark.run(order=11)
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
info = _test_staking_rpc(staking.get_all_validator_information_by_block_number, setup_blockchain + 1, endpoint=explorer_endpoint)
assert isinstance(info, list)
@pytest.mark.run(order=12)
def test_get_delegations_by_delegator_by_block(setup_blockchain):
delegations = _test_staking_rpc(staking.get_delegations_by_delegator_by_block_number, test_validator_address, setup_blockchain + 1, endpoint=explorer_endpoint)
assert isinstance(delegations, list)
@pytest.mark.run(order=13)
def test_get_elected_validator_addresses(setup_blockchain):
validator_addresses = _test_staking_rpc(staking.get_elected_validator_addresses)
assert isinstance(validator_addresses, list)
assert len(validator_addresses) > 0
@pytest.mark.run(order=14)
def test_get_validators(setup_blockchain):
validators = _test_staking_rpc(staking.get_validators, 2)
assert isinstance(validators, dict)
assert len(validators['validators']) > 0
@pytest.mark.run(order=15)
def test_get_validator_keys(setup_blockchain):
validators = _test_staking_rpc(staking.get_validator_keys, 2)
assert isinstance(validators, list)
@pytest.mark.run(order=16)
def test_get_validator_self_delegation(setup_blockchain):
self_delegation = _test_staking_rpc(staking.get_validator_self_delegation, test_validator_address)
assert isinstance(self_delegation, int)
info = _test_staking_rpc(
staking.get_all_validator_information_by_block_number,
"latest",
endpoint = explorer_endpoint,
)
assert isinstance( info, list )
def test_get_delegations_by_delegator_by_block( setup_blockchain ):
delegations = _test_staking_rpc(
staking.get_delegations_by_delegator_by_block_number,
test_validator_address,
"latest",
endpoint = explorer_endpoint,
)
assert isinstance( delegations, list )
def test_get_elected_validator_addresses( setup_blockchain ):
validator_addresses = _test_staking_rpc(
staking.get_elected_validator_addresses
)
assert isinstance( validator_addresses, list )
assert len( validator_addresses ) > 0
def test_get_validators( setup_blockchain ):
validators = _test_staking_rpc( staking.get_validators, 2 )
assert isinstance( validators, dict )
assert len( validators[ "validators" ] ) > 0
def test_get_validator_keys( setup_blockchain ):
validators = _test_staking_rpc( staking.get_validator_keys, 2 )
assert isinstance( validators, list )
def test_get_validator_self_delegation( setup_blockchain ):
self_delegation = _test_staking_rpc(
staking.get_validator_self_delegation,
test_validator_address
)
assert isinstance( self_delegation, int )
assert self_delegation > 0
@pytest.mark.run(order=17)
def test_get_validator_total_delegation(setup_blockchain):
total_delegation = _test_staking_rpc(staking.get_validator_total_delegation, test_validator_address)
assert isinstance(total_delegation, int)
def test_get_validator_total_delegation( setup_blockchain ):
total_delegation = _test_staking_rpc(
staking.get_validator_total_delegation,
test_validator_address
)
assert isinstance( total_delegation, int )
assert total_delegation > 0
@pytest.mark.run(order=18)
def test_get_all_delegation_information(setup_blockchain):
delegation_information = _test_staking_rpc(staking.get_all_delegation_information, 0)
assert isinstance(delegation_information, list)
assert len(delegation_information) > 0
@pytest.mark.run(order=19)
def test_get_delegation_by_delegator_and_validator(setup_blockchain):
delegation_information = _test_staking_rpc(staking.get_delegation_by_delegator_and_validator, test_validator_address, test_validator_address)
assert isinstance(delegation_information, dict)
@pytest.mark.run(order=20)
def test_get_available_redelegation_balance(setup_blockchain):
redelgation_balance = _test_staking_rpc(staking.get_available_redelegation_balance, test_validator_address)
assert isinstance(redelgation_balance, int)
def test_get_all_delegation_information( setup_blockchain ):
delegation_information = _test_staking_rpc(
staking.get_all_delegation_information,
0
)
assert isinstance( delegation_information, list )
assert len( delegation_information ) > 0
def test_get_delegation_by_delegator_and_validator( setup_blockchain ):
delegation_information = _test_staking_rpc(
staking.get_delegation_by_delegator_and_validator,
test_validator_address,
test_validator_address,
)
assert isinstance( delegation_information, dict )
def test_get_available_redelegation_balance( setup_blockchain ):
redelgation_balance = _test_staking_rpc(
staking.get_available_redelegation_balance,
test_validator_address
)
assert isinstance( redelgation_balance, int )
assert redelgation_balance == 0
@pytest.mark.run(order=21)
def test_get_total_staking(setup_blockchain):
total_staking = _test_staking_rpc(staking.get_total_staking)
assert isinstance(total_staking, int)
def test_get_total_staking( setup_blockchain ):
total_staking = _test_staking_rpc( staking.get_total_staking )
assert isinstance( total_staking, int )
if (
staking.get_validator_information(
test_validator_address,
explorer_endpoint
)[ "active-status" ] == "active"
):
assert total_staking > 0
@pytest.mark.run(order=22)
def test_errors():
with pytest.raises(exceptions.RPCError):
staking.get_all_validator_addresses(fake_shard)
with pytest.raises(exceptions.RPCError):
staking.get_validator_information('', fake_shard)
with pytest.raises(exceptions.RPCError):
staking.get_elected_validator_addresses(fake_shard)
with pytest.raises(exceptions.RPCError):
staking.get_validators(1, fake_shard)
with pytest.raises(exceptions.RPCError):
staking.get_validator_keys(1, fake_shard)
with pytest.raises(exceptions.RPCError):
staking.get_validator_information_by_block_number('', 1, fake_shard)
with pytest.raises(exceptions.RPCError):
staking.get_all_validator_information(-1, fake_shard)
with pytest.raises(exceptions.RPCError):
staking.get_validator_self_delegation('', fake_shard)
with pytest.raises(exceptions.RPCError):
staking.get_validator_total_delegation('', fake_shard)
with pytest.raises(exceptions.RPCError):
staking.get_all_validator_information_by_block_number(1, 1, fake_shard)
with pytest.raises(exceptions.RPCError):
staking.get_all_delegation_information(1, fake_shard)
with pytest.raises(exceptions.RPCError):
staking.get_delegations_by_delegator('', fake_shard)
with pytest.raises(exceptions.RPCError):
staking.get_delegations_by_delegator_by_block_number('', 1, fake_shard)
with pytest.raises(exceptions.RPCError):
staking.get_delegation_by_delegator_and_validator('', '', fake_shard)
with pytest.raises(exceptions.RPCError):
staking.get_available_redelegation_balance('', fake_shard)
with pytest.raises(exceptions.RPCError):
staking.get_delegations_by_validator('', fake_shard)
with pytest.raises(exceptions.RPCError):
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)
with pytest.raises( exceptions.RPCError ):
staking.get_all_validator_addresses( fake_shard )
with pytest.raises( exceptions.RPCError ):
staking.get_validator_information( "", fake_shard )
with pytest.raises( exceptions.RPCError ):
staking.get_elected_validator_addresses( fake_shard )
with pytest.raises( exceptions.RPCError ):
staking.get_validators( 1, fake_shard )
with pytest.raises( exceptions.RPCError ):
staking.get_validator_keys( 1, fake_shard )
with pytest.raises( exceptions.RPCError ):
staking.get_validator_information_by_block_number( "", 1, fake_shard )
with pytest.raises( exceptions.RPCError ):
staking.get_all_validator_information( 0, fake_shard )
with pytest.raises( exceptions.RPCError ):
staking.get_validator_self_delegation( "", fake_shard )
with pytest.raises( exceptions.RPCError ):
staking.get_validator_total_delegation( "", fake_shard )
with pytest.raises( exceptions.RPCError ):
staking.get_all_validator_information_by_block_number(
1,
1,
fake_shard
)
with pytest.raises( exceptions.RPCError ):
staking.get_all_delegation_information( 1, fake_shard )
with pytest.raises( exceptions.RPCError ):
staking.get_delegations_by_delegator( "", fake_shard )
with pytest.raises( exceptions.RPCError ):
staking.get_delegations_by_delegator_by_block_number(
"",
1,
fake_shard
)
with pytest.raises( exceptions.RPCError ):
staking.get_delegation_by_delegator_and_validator( "", "", fake_shard )
with pytest.raises( exceptions.RPCError ):
staking.get_available_redelegation_balance( "", fake_shard )
with pytest.raises( exceptions.RPCError ):
staking.get_delegations_by_validator( "", fake_shard )
with pytest.raises( exceptions.RPCError ):
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 )

@ -1,15 +1,10 @@
from pyhmy import (
staking_signing,
staking_structures
)
from pyhmy import staking_signing, staking_structures
from pyhmy.numbers import (
convert_one_to_atto
)
from pyhmy.numbers import convert_one_to_atto
# 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
# staking transactions without a chain id have been omitted as well, since the node does not accept them anyway
"""
let stakingTx
let stakeMsg3: CollectRewards = new CollectRewards(
@ -27,17 +22,16 @@ const signed = stakingTx.rlpSign('4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd
console.log( 'Signed transaction' )
console.log(signed)
"""
def test_collect_rewards_no_chain_id():
transaction_dict = {
'directive': staking_structures.Directive.CollectRewards,
'delegatorAddress': 'one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9',
'nonce': 2,
'gasPrice': int(convert_one_to_atto(1)),
'gasLimit': 100,
}
signed_tx = staking_signing.sign_staking_transaction(transaction_dict, '4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48')
assert signed_tx.rawTransaction.hex() == '0xf85a04d594ebcd16e8c1d8f493ba04e99a56474122d81a9c5823a0490e4ceb747563ba40da3e0db8a65133cf6f6ae4c48a24866cd6aa1f0d6c2414a06dbd51a67b35b5685e7b7420cba26e63b0e7d3c696fc6cb69d48e54fcad280e9'
# def test_collect_rewards_no_chain_id():
# transaction_dict = {
# 'directive': staking_structures.Directive.CollectRewards,
# 'delegatorAddress': 'one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9',
# 'nonce': 2,
# 'gasPrice': int(convert_one_to_atto(1)),
# 'gasLimit': 100,
# }
# signed_tx = staking_signing.sign_staking_transaction(transaction_dict, '4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48')
# assert signed_tx.rawTransaction.hex() == '0xf85a04d594ebcd16e8c1d8f493ba04e99a56474122d81a9c5823a0490e4ceb747563ba40da3e0db8a65133cf6f6ae4c48a24866cd6aa1f0d6c2414a06dbd51a67b35b5685e7b7420cba26e63b0e7d3c696fc6cb69d48e54fcad280e9'
"""
let stakingTx
let stakeMsg3: CollectRewards = new CollectRewards(
@ -55,17 +49,26 @@ const signed = stakingTx.rlpSign('4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd
console.log( 'Signed transaction' )
console.log(signed)
"""
def test_collect_rewards_chain_id():
transaction_dict = {
'directive': staking_structures.Directive.CollectRewards,
'delegatorAddress': 'one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9',
'nonce': 2,
'gasPrice': int(convert_one_to_atto(1)),
'gasLimit': 100,
'chainId': 1, # with chainId for coverage
"directive": staking_structures.Directive.CollectRewards,
"delegatorAddress": "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9",
"nonce": 2,
"gasPrice": int(convert_one_to_atto(1)),
"gasLimit": 100,
"chainId": 1, # with chainId for coverage
}
signed_tx = staking_signing.sign_staking_transaction(transaction_dict, '4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48')
assert signed_tx.rawTransaction.hex() == '0xf86504d594ebcd16e8c1d8f493ba04e99a56474122d81a9c5802880de0b6b3a76400006425a055d6c3c0d8e7a1e75152db361a2ed47f5ab54f6f19b0d8e549953dbdf13ba647a076e1367dfca38eae3bd0e8da296335acabbaeb87dc17e47ebe4942db29334099'
signed_tx = staking_signing.sign_staking_transaction(
transaction_dict,
"4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48",
)
assert (
signed_tx.rawTransaction.hex() ==
"0xf86504d594ebcd16e8c1d8f493ba04e99a56474122d81a9c5802880de0b6b3a76400006425a055d6c3c0d8e7a1e75152db361a2ed47f5ab54f6f19b0d8e549953dbdf13ba647a076e1367dfca38eae3bd0e8da296335acabbaeb87dc17e47ebe4942db29334099"
)
"""
let stakingTx
@ -80,21 +83,30 @@ stakingTx = new StakingTransaction(
2, // nonce
numberToHex(new Unit('1').asOne().toWei()), // gasPrice
100, // gasLimit
null, // chainId
2, // chainId
);
const signed = stakingTx.rlpSign('4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48')
console.log( 'Signed transaction' )
console.log(signed)
"""
def test_delegate():
transaction_dict = {
'directive': staking_structures.Directive.Delegate,
'delegatorAddress': 'one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9',
'validatorAddress': 'one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9',
'amount': 5,
'nonce': 2,
'gasPrice': int(convert_one_to_atto(1)),
'gasLimit': 100,
"directive": staking_structures.Directive.Delegate,
"delegatorAddress": "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9",
"validatorAddress": "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9",
"amount": 5,
"nonce": 2,
"gasPrice": int( convert_one_to_atto( 1 ) ),
"gasLimit": 100,
"chainId": 2,
}
signed_tx = staking_signing.sign_staking_transaction(transaction_dict, '4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48')
assert signed_tx.rawTransaction.hex() == '0xf87002eb94ebcd16e8c1d8f493ba04e99a56474122d81a9c5894ebcd16e8c1d8f493ba04e99a56474122d81a9c580523a0aceff4166ec0ecd0cc664fed865270fe77b35e408138950f802129f1f3d06a74a06f9aca402fb6b4842bff8d65f430d82eefa95645e9046b102195d1044993f9fe'
signed_tx = staking_signing.sign_staking_transaction(
transaction_dict,
"4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48",
)
assert (
signed_tx.rawTransaction.hex() ==
"0xf87b02eb94ebcd16e8c1d8f493ba04e99a56474122d81a9c5894ebcd16e8c1d8f493ba04e99a56474122d81a9c580502880de0b6b3a76400006428a0c856fd483a989ca4db4b5257f6996729527828fb21ec13cc65f0bffe6c015ab1a05e9d3c92742e8cb7450bebdfb7ad277ccbfc9fa0719db0b12a715a0a173cadd6"
)

@ -1,218 +1,266 @@
import pytest
from pyhmy import (
transaction
)
from pyhmy import transaction
from pyhmy.rpc import (
exceptions
)
from pyhmy.rpc import exceptions
from pyhmy.exceptions import (
TxConfirmationTimedoutError
)
endpoint = "http://localhost:9500"
endpoint_shard_one = "http://localhost:9502"
fake_shard = "http://example.com"
localhost_shard_one = 'http://localhost:9501'
tx_hash = '0x1fa20537ea97f162279743139197ecf0eac863278ac1c8ada9a6be5d1e31e633'
# previously sent txs to get and check
tx_hash = "0xc26be5776aa57438bccf196671a2d34f3f22c9c983c0f844c62b2fb90403aa43"
tx_block_num = None
tx_block_hash = None
cx_hash = '0x1fa20537ea97f162279743139197ecf0eac863278ac1c8ada9a6be5d1e31e633'
stx_hash = '0x57ec011aabdeb078a4816502224022f291fa8b07c82bbae8476f514a1d71c730'
tx_index = None
cx_hash = "0xf73ba634cb96fc0e3e2c9d3b4c91379e223741be4a5aa56e6d6caf49c1ae75cf"
stx_hash = "0xc8177ace2049d9f4eb4a45fd6bd6b16f693573d036322c36774cc00d05a3e24f"
stx_block_num = None
stx_block_hash = None
test_index = 0
fake_shard = 'http://example.com'
stx_index = None
# new txs to send and check
raw_tx = "0xf86f0385174876e800825208808094c9c6d47ee5f2e3e08d7367ad1a1373ba9dd172418905b12aefafa80400008027a07a4952b90bf38723a9197179a8e6d2e9b3a86fd6da4e66a9cf09fdc59783f757a053910798b311245525bd77d6119332458c2855102e4fb9e564f6a3b710d18bb0"
raw_tx_hash = "0x7ccd80f8513f76ec58b357c7a82a12a95e025d88f1444e953f90e3d86e222571"
# raw_txt generated via:
# hmy transfer --from one12fuf7x9rgtdgqg7vgq0962c556m3p7afsxgvll --to one12fuf7x9rgtdgqg7vgq0962c556m3p7afsxgvll
# --from-shard 0 --to-shard 1 --amount 0.1 --dry-run
raw_tx = '0xf86d01843b9aca0082520880019452789f18a342da8023cc401e5d2b14a6b710fba988016345785d8a00008028a01095f775386e0e3203446179a7a62e5ce1e765c200b5d885f6bb5b141155bd4da0651350a31e1797035cbf878e4c26069e9895845071d01308573532512cca5820'
raw_tx_hash = '0x86bce2e7765937b776bdcf927339c85421b95c70ddf06ba8e4cc0441142b0f53'
raw_stx = "0xf88302f494c9c6d47ee5f2e3e08d7367ad1a1373ba9dd1724194a5241513da9f4463f1d4874b548dfbac29d91f3489056bc75e2d631000008085174876e80082c35027a0808ea7d27adf3b1f561e8da4676814084bb75ac541b616bece87c6446e6cc54ea02f19f0b14240354bd42ad60b0c7189873c0be87044e13072b0981a837ca76f64"
raw_stx_hash = "0xe7d07ef6d9fca595a14ceb0ca917bece7bedb15efe662300e9334a32ac1da629"
raw_stx = '0xf9015680f90105943ad89a684095a53edb47d7ddc5e034d813366731d984746573748474657374847465737484746573748474657374ddc988016345785d8a0000c9880c7d713b49da0000c887b1a2bc2ec500008a022385a827e8155000008b084595161401484a000000f1b0282554f2478661b4844a05a9deb1837aac83931029cb282872f0dcd7239297c499c02ea8da8746d2f08ca2b037e89891f862b86003557e18435c201ecc10b1664d1aea5b4ec59dbfe237233b953dbd9021b86bc9770e116ed3c413fe0334d89562568a10e133d828611f29fee8cdab9719919bbcc1f1bf812c73b9ccd0f89b4f0b9ca7e27e66d58bbb06fcf51c295b1d076cfc878a0228f16f86157860000080843b9aca008351220027a018385211a150ca032c3526cef0aba6a75f99a18cb73f547f67bab746be0c7a64a028be921002c6eb949b3932afd010dfe1de2459ec7fe84403b9d9d8892394a78c'
def _test_transaction_rpc(fn, *args, **kwargs):
if not callable(fn):
pytest.fail(f'Invalid function: {fn}')
def _test_transaction_rpc( fn, *args, **kwargs ):
if not callable( fn ):
pytest.fail( f"Invalid function: {fn}" )
try:
response = fn(*args, **kwargs)
response = fn( *args, **kwargs )
except Exception as e:
if isinstance(e, exceptions.RPCError) and 'does not exist/is not available' in str(e):
pytest.skip(f'{str(e)}')
if isinstance(e, TxConfirmationTimedoutError):
pytest.skip(f'{str(e)}')
pytest.fail(f'Unexpected error: {e.__class__} {e}')
if isinstance( e,
exceptions.RPCError
) and "does not exist/is not available" in str( e ):
pytest.skip( f"{str(e)}" )
pytest.fail( f"Unexpected error: {e.__class__} {e}" )
return response
@pytest.mark.run(order=1)
def test_get_pending_transactions(setup_blockchain):
pool = _test_transaction_rpc(transaction.get_pending_transactions)
assert isinstance(pool, list)
@pytest.mark.run(order=2)
def test_get_transaction_by_hash(setup_blockchain):
tx = _test_transaction_rpc(transaction.get_transaction_by_hash, tx_hash, endpoint=localhost_shard_one)
def test_get_pending_transactions( setup_blockchain ):
pool = _test_transaction_rpc( transaction.get_pending_transactions )
assert isinstance( pool, list )
def test_get_transaction_by_hash( setup_blockchain ):
tx = _test_transaction_rpc(
transaction.get_transaction_by_hash,
tx_hash,
endpoint = endpoint
)
assert tx
assert isinstance(tx, dict)
assert 'blockNumber' in tx.keys()
assert 'blockHash' in tx.keys()
assert isinstance( tx, dict )
assert "blockNumber" in tx.keys()
assert "blockHash" in tx.keys()
global tx_block_num
tx_block_num = int(tx['blockNumber'])
tx_block_num = int( tx[ "blockNumber" ] )
global tx_block_hash
tx_block_hash = tx['blockHash']
tx_block_hash = tx[ "blockHash" ]
global tx_index
tx_index = int( tx[ "transactionIndex" ] )
@pytest.mark.run(order=3)
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:
pytest.skip('Failed to get reference block hash')
tx = _test_transaction_rpc(transaction.get_transaction_by_block_hash_and_index,
tx_block_hash, test_index, endpoint=localhost_shard_one)
pytest.skip( "Failed to get reference block hash" )
tx = _test_transaction_rpc(
transaction.get_transaction_by_block_hash_and_index,
tx_block_hash,
tx_index,
endpoint = endpoint,
)
assert tx
assert isinstance(tx, dict)
assert isinstance( tx, dict )
@pytest.mark.run(order=4)
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:
pytest.skip('Failed to get reference block num')
tx = _test_transaction_rpc(transaction.get_transaction_by_block_number_and_index, tx_block_num, test_index,
endpoint=localhost_shard_one)
pytest.skip( "Failed to get reference block num" )
tx = _test_transaction_rpc(
transaction.get_transaction_by_block_number_and_index,
tx_block_num,
tx_index,
endpoint = endpoint,
)
assert tx
assert isinstance(tx, dict)
assert isinstance( tx, dict )
@pytest.mark.run(order=5)
def test_get_transaction_receipt(setup_blockchain):
tx_receipt = _test_transaction_rpc(transaction.get_transaction_receipt, tx_hash, endpoint=localhost_shard_one)
def test_get_transaction_receipt( setup_blockchain ):
tx_receipt = _test_transaction_rpc(
transaction.get_transaction_receipt,
tx_hash,
endpoint = endpoint
)
assert tx_receipt
assert isinstance(tx_receipt, dict)
assert isinstance( tx_receipt, dict )
@pytest.mark.run(order=6)
def test_get_transaction_error_sink(setup_blockchain):
errors = _test_transaction_rpc(transaction.get_transaction_error_sink)
assert isinstance(errors, list)
def test_get_transaction_error_sink( setup_blockchain ):
errors = _test_transaction_rpc( transaction.get_transaction_error_sink )
assert isinstance( errors, list )
@pytest.mark.run(order=7)
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.
# Secondly, this is a test that seems to return None values - for example the below curl call has the same null value
# curl --location --request POST 'http://localhost:9501' \
# --header 'Content-Type: application/json' \
# --data-raw '{
# "jsonrpc": "2.0",
# "id": 1,
# "method": "hmyv2_getTransactionByHash",
# "params": [
# "0x86bce2e7765937b776bdcf927339c85421b95c70ddf06ba8e4cc0441142b0f53"
# ]
# }'
# {"jsonrpc":"2.0","id":1,"result":null}
test_tx = _test_transaction_rpc(transaction.send_and_confirm_raw_transaction,
raw_tx) # mining stops by the time this transaction is submitted
# so it never confirms, which is why TxConfirmationTimedoutError
# is in the set up call
assert isinstance(test_tx, dict)
assert test_tx[ 'hash' ] == raw_tx_hash
@pytest.mark.run(order=8)
def test_get_pending_cx_receipts(setup_blockchain):
pending = _test_transaction_rpc(transaction.get_pending_cx_receipts)
assert isinstance(pending, list)
@pytest.mark.run(order=9)
def test_get_cx_receipt_by_hash(setup_blockchain):
cx = _test_transaction_rpc(transaction.get_cx_receipt_by_hash, cx_hash)
test_tx = _test_transaction_rpc(
transaction.send_and_confirm_raw_transaction,
raw_tx
)
assert isinstance( test_tx, dict )
assert test_tx[ "hash" ] == raw_tx_hash
def test_get_pending_cx_receipts( setup_blockchain ):
pending = _test_transaction_rpc( transaction.get_pending_cx_receipts )
assert isinstance( pending, list )
def test_get_cx_receipt_by_hash( setup_blockchain ):
cx = _test_transaction_rpc(
transaction.get_cx_receipt_by_hash,
cx_hash,
endpoint_shard_one
)
assert cx
assert isinstance(cx, dict)
assert isinstance( cx, dict )
@pytest.mark.run(order=10)
def test_resend_cx_receipt(setup_blockchain):
sent = _test_transaction_rpc(transaction.resend_cx_receipt, cx_hash)
assert isinstance(sent, bool)
assert not sent
@pytest.mark.run(order=11)
def test_get_staking_transaction_by_hash(setup_blockchain):
staking_tx = _test_transaction_rpc(transaction.get_staking_transaction_by_hash, stx_hash)
def test_resend_cx_receipt( setup_blockchain ):
sent = _test_transaction_rpc( transaction.resend_cx_receipt, cx_hash )
assert isinstance( sent, bool )
assert sent
def test_get_staking_transaction_by_hash( setup_blockchain ):
staking_tx = _test_transaction_rpc(
transaction.get_staking_transaction_by_hash,
stx_hash
)
assert staking_tx
assert isinstance(staking_tx, dict)
assert 'blockNumber' in staking_tx.keys()
assert 'blockHash' in staking_tx.keys()
assert isinstance( staking_tx, dict )
assert "blockNumber" in staking_tx.keys()
assert "blockHash" in staking_tx.keys()
global stx_block_num
stx_block_num = int(staking_tx['blockNumber'])
stx_block_num = int( staking_tx[ "blockNumber" ] )
global stx_block_hash
stx_block_hash = staking_tx['blockHash']
stx_block_hash = staking_tx[ "blockHash" ]
global stx_index
stx_index = int( staking_tx[ "transactionIndex" ] )
@pytest.mark.run(order=12)
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:
pytest.skip('Failed to get reference block hash')
stx = _test_transaction_rpc(transaction.get_staking_transaction_by_block_hash_and_index, stx_block_hash, test_index)
pytest.skip( "Failed to get reference block hash" )
stx = _test_transaction_rpc(
transaction.get_staking_transaction_by_block_hash_and_index,
stx_block_hash,
stx_index,
)
assert stx
assert isinstance(stx, dict)
assert isinstance( stx, dict )
@pytest.mark.run(order=13)
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:
pytest.skip('Failed to get reference block num')
stx = _test_transaction_rpc(transaction.get_staking_transaction_by_block_number_and_index, stx_block_num, test_index)
pytest.skip( "Failed to get reference block num" )
stx = _test_transaction_rpc(
transaction.get_staking_transaction_by_block_number_and_index,
stx_block_num,
stx_index,
)
assert stx
assert isinstance(stx, dict)
@pytest.mark.run(order=14)
def test_get_staking_transaction_error_sink(setup_blockchain):
errors = _test_transaction_rpc(transaction.get_staking_transaction_error_sink)
assert isinstance(errors, list)
@pytest.mark.run(order=15)
def test_send_raw_staking_transaction(setup_blockchain):
test_stx_hash = _test_transaction_rpc(transaction.send_raw_staking_transaction, raw_stx, endpoint=localhost_shard_one)
assert isinstance(test_stx_hash, str)
assert test_stx_hash == stx_hash
@pytest.mark.run(order=16)
def test_get_pool_stats(setup_blockchain):
test_pool_stats = _test_transaction_rpc(transaction.get_pool_stats, endpoint=localhost_shard_one)
assert isinstance(test_pool_stats, dict)
@pytest.mark.run(order=17)
def test_get_pending_staking_transactions(setup_blockchain):
pending_staking_transactions = _test_transaction_rpc(transaction.get_pending_staking_transactions, endpoint=localhost_shard_one)
assert isinstance(pending_staking_transactions, list)
@pytest.mark.run(order=18)
assert isinstance( stx, dict )
def test_get_staking_transaction_error_sink( setup_blockchain ):
errors = _test_transaction_rpc(
transaction.get_staking_transaction_error_sink
)
assert isinstance( errors, list )
def test_send_raw_staking_transaction( setup_blockchain ):
test_stx = _test_transaction_rpc(
transaction.send_and_confirm_raw_staking_transaction,
raw_stx,
endpoint = endpoint
)
assert isinstance( test_stx, dict )
assert test_stx[ "hash" ] == raw_stx_hash
def test_get_pool_stats( setup_blockchain ):
test_pool_stats = _test_transaction_rpc(
transaction.get_pool_stats,
endpoint = endpoint
)
assert isinstance( test_pool_stats, dict )
def test_get_pending_staking_transactions( setup_blockchain ):
pending_staking_transactions = _test_transaction_rpc(
transaction.get_pending_staking_transactions,
endpoint = endpoint
)
assert isinstance( pending_staking_transactions, list )
def test_errors():
with pytest.raises(exceptions.RPCError):
transaction.get_pending_transactions(fake_shard)
with pytest.raises(exceptions.RPCError):
transaction.get_transaction_error_sink(fake_shard)
with pytest.raises(exceptions.RPCError):
transaction.get_pool_stats(fake_shard)
with pytest.raises(exceptions.RPCError):
transaction.get_transaction_by_hash('', endpoint=fake_shard)
with pytest.raises(exceptions.RPCError):
transaction.get_transaction_by_block_hash_and_index('', 1, endpoint=fake_shard)
with pytest.raises(exceptions.RPCError):
transaction.get_transaction_by_block_number_and_index(1, 1, endpoint=fake_shard)
with pytest.raises(exceptions.RPCError):
transaction.get_transaction_receipt('', endpoint=fake_shard)
with pytest.raises(exceptions.RPCError):
transaction.send_raw_transaction('', endpoint=fake_shard)
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('', 1, endpoint=fake_shard)
with pytest.raises(exceptions.RPCError):
transaction.get_staking_transaction_by_block_number_and_index(1, 1, endpoint=fake_shard)
with pytest.raises(exceptions.RPCError):
transaction.get_staking_transaction_error_sink(endpoint=fake_shard)
with pytest.raises(exceptions.RPCError):
transaction.send_raw_staking_transaction('', endpoint=fake_shard)
with pytest.raises(exceptions.RPCError):
transaction.get_pending_staking_transactions(endpoint=fake_shard)
with pytest.raises( exceptions.RPCError ):
transaction.get_pending_transactions( fake_shard )
with pytest.raises( exceptions.RPCError ):
transaction.get_transaction_error_sink( fake_shard )
with pytest.raises( exceptions.RPCError ):
transaction.get_pool_stats( fake_shard )
with pytest.raises( exceptions.RPCError ):
transaction.get_transaction_by_hash( "", endpoint = fake_shard )
with pytest.raises( exceptions.RPCError ):
transaction.get_transaction_by_block_hash_and_index(
"",
1,
endpoint = fake_shard
)
with pytest.raises( exceptions.RPCError ):
transaction.get_transaction_by_block_number_and_index(
1,
1,
endpoint = fake_shard
)
with pytest.raises( exceptions.RPCError ):
transaction.get_transaction_receipt( "", endpoint = fake_shard )
with pytest.raises( exceptions.RPCError ):
transaction.send_raw_transaction( "", endpoint = fake_shard )
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(
"",
1,
endpoint = fake_shard
)
with pytest.raises( exceptions.RPCError ):
transaction.get_staking_transaction_by_block_number_and_index(
1,
1,
endpoint = fake_shard
)
with pytest.raises( exceptions.RPCError ):
transaction.get_staking_transaction_error_sink( endpoint = fake_shard )
with pytest.raises( exceptions.RPCError ):
transaction.send_raw_staking_transaction( "", endpoint = fake_shard )
with pytest.raises( exceptions.RPCError ):
transaction.get_pending_staking_transactions( endpoint = fake_shard )

@ -1,26 +1,11 @@
import pytest
import requests
from decimal import (
Decimal
)
from pyhmy import (
validator
)
from decimal import Decimal
from pyhmy.rpc import (
exceptions
)
from pyhmy import validator
from pyhmy.numbers import (
convert_one_to_atto
)
from pyhmy.numbers import convert_one_to_atto
from pyhmy.exceptions import (
InvalidValidatorError
)
import sys
from pyhmy.exceptions import InvalidValidatorError
test_epoch_number = 0
genesis_block_number = 0
@ -28,36 +13,46 @@ test_block_number = 1
test_validator_object = None
test_validator_loaded = False
@pytest.mark.run(order=0)
def test_instantiate_validator(setup_blockchain):
def test_instantiate_validator( setup_blockchain ):
global test_validator_object
test_validator_object = validator.Validator('one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9')
assert isinstance(test_validator_object, validator.Validator)
test_validator_object = validator.Validator(
"one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9"
)
assert isinstance( test_validator_object, validator.Validator )
@pytest.mark.run(order=1)
def test_load_validator(setup_blockchain):
def test_load_validator( setup_blockchain ):
if not test_validator_object:
pytest.skip('Validator not instantiated yet')
pytest.skip( "Validator not instantiated yet" )
info = {
'name': 'Alice',
'identity': 'alice',
'website': 'alice.harmony.one',
'details': "Don't mess with me!!!",
'security-contact': 'Bob',
'min-self-delegation': convert_one_to_atto(10000),
'amount': convert_one_to_atto(10001),
'max-rate': '0.9',
'max-change-rate': '0.05',
'rate': '0.01',
'bls-public-keys': ['0xb9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608611'],
'max-total-delegation': convert_one_to_atto(40000)
"name": "Alice",
"identity": "alice",
"website": "alice.harmony.one",
"details": "Don't mess with me!!!",
"security-contact": "Bob",
"min-self-delegation": convert_one_to_atto( 10000 ),
"amount": convert_one_to_atto( 10001 ),
"max-rate": "0.9",
"max-change-rate": "0.05",
"rate": "0.01",
"bls-public-keys": [
"0x30b2c38b1316da91e068ac3bd8751c0901ef6c02a1d58bc712104918302c6ed03d5894671d0c816dad2b4d303320f202"
],
"bls-key-sigs": [
"0x68f800b6adf657b674903e04708060912b893b7c7b500788808247550ab3e186e56a44ebf3ca488f8ed1a42f6cef3a04bd5d2b2b7eb5a767848d3135b362e668ce6bba42c7b9d5666d8e3a83be707b5708e722c58939fe9b07c170f3b7062414"
],
"max-total-delegation": convert_one_to_atto( 40000 ),
}
test_validator_object.load(info)
test_validator_object.load( info )
global test_validator_loaded
test_validator_loaded = True
"""
TypeScript signature source
TypeScript signature source (is outdated because the JS SDK has not been updated for SlotKeySigs)
For now I have checked that the below transaction to localnet works
---
const description: Description = new Description('Alice', 'alice', 'alice.harmony.one', 'Bob', "Don't mess with me!!!")
const commissionRates: CommissionRate = new CommissionRate(new Decimal('0.01'), new Decimal('0.9'), new Decimal('0.05'))
const stakeMsg: CreateValidator = new CreateValidator(
@ -81,20 +76,28 @@ const signed = stakingTx.rlpSign('4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd
console.log( 'Signed transaction' )
console.log(signed)
"""
@pytest.mark.run(order=2)
def test_create_validator_sign(setup_blockchain):
if not (test_validator_object or test_validator_loaded):
pytest.skip('Validator not ready yet')
def test_create_validator_sign( setup_blockchain ):
if not ( test_validator_object or test_validator_loaded ):
pytest.skip( "Validator not instantiated yet" )
signed_hash = test_validator_object.sign_create_validator_transaction(
2,
int(convert_one_to_atto(1)),
int( convert_one_to_atto( 1 ) ),
100,
'4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48',
None).rawTransaction.hex()
assert signed_hash == '0xf9010580f8bf94ebcd16e8c1d8f493ba04e99a56474122d81a9c58f83885416c69636585616c69636591616c6963652e6861726d6f6e792e6f6e6583426f6295446f6e2774206d6573732077697468206d65212121dcc8872386f26fc10000c9880c7d713b49da0000c887b1a2bc2ec500008a021e19e0c9bab24000008a0878678326eac9000000f1b0b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b622476086118a021e27c1806e59a4000024a047c6d444971d4d3c48e8b255aa0e543ebb47b60f761582694e5af5330445aba5a04db1ffea9cca9f9e56e8f782c689db680992903acfd9c06f4593f7fd9a781bd7'
"4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48",
2,
).rawTransaction.hex()
assert (
signed_hash ==
"0xf9017580f9012394ebcd16e8c1d8f493ba04e99a56474122d81a9c58f83885416c69636585616c69636591616c6963652e6861726d6f6e792e6f6e6583426f6295446f6e2774206d6573732077697468206d65212121dcc8872386f26fc10000c9880c7d713b49da0000c887b1a2bc2ec500008a021e19e0c9bab24000008a0878678326eac9000000f1b030b2c38b1316da91e068ac3bd8751c0901ef6c02a1d58bc712104918302c6ed03d5894671d0c816dad2b4d303320f202f862b86068f800b6adf657b674903e04708060912b893b7c7b500788808247550ab3e186e56a44ebf3ca488f8ed1a42f6cef3a04bd5d2b2b7eb5a767848d3135b362e668ce6bba42c7b9d5666d8e3a83be707b5708e722c58939fe9b07c170f3b70624148a021e27c1806e59a4000002880de0b6b3a76400006428a0c6c7e62f02331df0afd4699ec514a2fc4548c920d77ad74d98caeec8c924c09aa02b27b999a724b1d341d6bbb0e877611d0047542cb7e380f9a6a272d204b450cd"
)
"""
Signature matched from TypeScript
Signature matched from TypeScript (is outdated because the JS SDK has not been updated for SlotKeyToAddSig)
For now I have checked that the below transaction to localnet works
---
import {
CreateValidator,
EditValidator,
@ -132,74 +135,91 @@ const signed = stakingTx.rlpSign('4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd
console.log( 'Signed transaction' )
console.log(signed)
"""
@pytest.mark.run(order=3)
def test_edit_validator_sign(setup_blockchain):
if not (test_validator_object or test_validator_loaded):
pytest.skip('Validator not ready yet')
def test_edit_validator_sign( setup_blockchain ):
if not ( test_validator_object or test_validator_loaded ):
pytest.skip( "Validator not instantiated yet" )
signed_hash = test_validator_object.sign_edit_validator_transaction(
2,
int(convert_one_to_atto(1)),
100,
'0.06',
'0xb9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608612', # add key
"0xb9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608611", # remove key
'4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48',
2).rawTransaction.hex()
assert signed_hash == '0xf9012101f8d094ebcd16e8c1d8f493ba04e99a56474122d81a9c58f83885416c69636585616c69636591616c6963652e6861726d6f6e792e6f6e6583426f6295446f6e2774206d6573732077697468206d65212121c887d529ae9e8600008a021e19e0c9bab24000008a0878678326eac9000000b0b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608611b0b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b6224760861202880de0b6b3a76400006428a0656d6741687ec1e42d1699274584a1777964e939b0ef11f3ff0e161859da21a2a03fc51e067f9fb6c96bee5ceccad4104f5b4b334a86a36a2f53d10b9a8e4a268a'
@pytest.mark.run(order=4)
def test_invalid_validator(setup_blockchain):
if not (test_validator_object or test_validator_loaded):
pytest.skip('Validator not ready yet')
with pytest.raises(InvalidValidatorError):
"0.06",
"0xb9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608612", # remove key
"0xb9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608611", # add key
"0x68f800b6adf657b674903e04708060912b893b7c7b500788808247550ab3e186e56a44ebf3ca488f8ed1a42f6cef3a04bd5d2b2b7eb5a767848d3135b362e668ce6bba42c7b9d5666d8e3a83be707b5708e722c58939fe9b07c170f3b7062414", # add key sig
"4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48",
2,
).rawTransaction.hex()
assert (
signed_hash ==
"0xf9018401f9013294ebcd16e8c1d8f493ba04e99a56474122d81a9c58f83885416c69636585616c69636591616c6963652e6861726d6f6e792e6f6e6583426f6295446f6e2774206d6573732077697468206d65212121c887d529ae9e8600008a021e19e0c9bab24000008a0878678326eac9000000b0b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608612b0b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608611b86068f800b6adf657b674903e04708060912b893b7c7b500788808247550ab3e186e56a44ebf3ca488f8ed1a42f6cef3a04bd5d2b2b7eb5a767848d3135b362e668ce6bba42c7b9d5666d8e3a83be707b5708e722c58939fe9b07c170f3b706241402880de0b6b3a76400006427a0ecdae4a29d051f4f83dd54004858fbf0f7820e169b8e1846245835ceb686ee12a04b2336eb5830e30720137b2de539518fd5655467fef140ab31fde881a19f256a"
)
def test_invalid_validator( setup_blockchain ):
if not ( test_validator_object or test_validator_loaded ):
pytest.skip( "Validator not instantiated yet" )
with pytest.raises( InvalidValidatorError ):
info = {
'name': 'Alice',
"name": "Alice",
}
test_validator_object.load(info)
with pytest.raises(InvalidValidatorError):
test_validator_object.set_name('a'*141)
with pytest.raises(InvalidValidatorError):
test_validator_object.set_identity('a'*141)
with pytest.raises(InvalidValidatorError):
test_validator_object.set_website('a'*141)
with pytest.raises(InvalidValidatorError):
test_validator_object.set_security_contact('a'*141)
with pytest.raises(InvalidValidatorError):
test_validator_object.set_details('a'*281)
with pytest.raises(InvalidValidatorError):
test_validator_object.set_min_self_delegation(1)
with pytest.raises(InvalidValidatorError):
test_validator_object.set_max_total_delegation(1)
with pytest.raises(InvalidValidatorError):
test_validator_object.set_amount(1)
with pytest.raises(InvalidValidatorError):
test_validator_object.set_max_rate('2.0')
with pytest.raises(InvalidValidatorError):
test_validator_object.set_max_change_rate('-2.0')
with pytest.raises(InvalidValidatorError):
test_validator_object.set_rate('-2.0')
@pytest.mark.run(order=5)
def test_validator_getters(setup_blockchain):
if not (test_validator_object or test_validator_loaded):
pytest.skip('Validator not ready yet')
assert test_validator_object.get_address() == 'one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9'
assert test_validator_object.add_bls_key('5')
assert test_validator_object.remove_bls_key('5')
assert test_validator_object.get_name() == 'Alice'
assert test_validator_object.get_identity() == 'alice'
assert test_validator_object.get_website() == 'alice.harmony.one'
assert test_validator_object.get_security_contact() == 'Bob'
test_validator_object.load( info )
with pytest.raises( InvalidValidatorError ):
test_validator_object.set_name( "a" * 141 )
with pytest.raises( InvalidValidatorError ):
test_validator_object.set_identity( "a" * 141 )
with pytest.raises( InvalidValidatorError ):
test_validator_object.set_website( "a" * 141 )
with pytest.raises( InvalidValidatorError ):
test_validator_object.set_security_contact( "a" * 141 )
with pytest.raises( InvalidValidatorError ):
test_validator_object.set_details( "a" * 281 )
with pytest.raises( InvalidValidatorError ):
test_validator_object.set_min_self_delegation( 1 )
with pytest.raises( InvalidValidatorError ):
test_validator_object.set_max_total_delegation( 1 )
with pytest.raises( InvalidValidatorError ):
test_validator_object.set_amount( 1 )
with pytest.raises( InvalidValidatorError ):
test_validator_object.set_max_rate( "2.0" )
with pytest.raises( InvalidValidatorError ):
test_validator_object.set_max_change_rate( "-2.0" )
with pytest.raises( InvalidValidatorError ):
test_validator_object.set_rate( "-2.0" )
def test_validator_getters( setup_blockchain ):
if not ( test_validator_object or test_validator_loaded ):
pytest.skip( "Validator not instantiated yet" )
assert (
test_validator_object.get_address() ==
"one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9"
)
assert test_validator_object.add_bls_key( "5" )
assert test_validator_object.remove_bls_key( "5" )
assert test_validator_object.get_name() == "Alice"
assert test_validator_object.get_identity() == "alice"
assert test_validator_object.get_website() == "alice.harmony.one"
assert test_validator_object.get_security_contact() == "Bob"
assert test_validator_object.get_details() == "Don't mess with me!!!"
assert isinstance(test_validator_object.get_min_self_delegation(), Decimal)
assert isinstance(test_validator_object.get_max_total_delegation(), 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
@pytest.mark.run(order=6)
def test_validator_load_from_blockchain(setup_blockchain):
test_validator_object2 = validator.Validator('one109r0tns7av5sjew7a7fkekg4fs3pw0h76pp45e')
assert isinstance(
test_validator_object.get_min_self_delegation(),
Decimal
)
assert isinstance(
test_validator_object.get_max_total_delegation(),
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 ):
test_validator_object2 = validator.Validator(
"one155jp2y76nazx8uw5sa94fr0m4s5aj8e5xm6fu3"
)
test_validator_object2.load_from_blockchain()

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

Loading…
Cancel
Save