diff --git a/.gitignore b/.gitignore index 99e493a..46d35bd 100644 --- a/.gitignore +++ b/.gitignore @@ -130,4 +130,7 @@ dmypy.json .pyre/ # IDE -.idea \ No newline at end of file +.idea + +# VIM +*.swp diff --git a/.style.yapf b/.style.yapf new file mode 100644 index 0000000..108d85c --- /dev/null +++ b/.style.yapf @@ -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 + diff --git a/Makefile b/Makefile index 2028746..1267c9f 100755 --- a/Makefile +++ b/Makefile @@ -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 . diff --git a/README.md b/README.md index 4f5882f..0771f93 100644 --- a/README.md +++ b/README.md @@ -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() ``` diff --git a/pyhmy/__init__.py b/pyhmy/__init__.py index 5aefb14..12ddecc 100644 --- a/pyhmy/__init__.py +++ b/pyhmy/__init__.py @@ -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() diff --git a/pyhmy/_version.py b/pyhmy/_version.py index 09a940e..0287d4b 100644 --- a/pyhmy/_version.py +++ b/pyhmy/_version.py @@ -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__" ] diff --git a/pyhmy/account.py b/pyhmy/account.py index d057fe7..95f2ca8 100644 --- a/pyhmy/account.py +++ b/pyhmy/account.py @@ -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 - -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 + 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. 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 diff --git a/pyhmy/bech32/__init__.py b/pyhmy/bech32/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyhmy/bech32/bech32.py b/pyhmy/bech32/bech32.py index d450080..0a2f196 100644 --- a/pyhmy/bech32/bech32.py +++ b/pyhmy/bech32/bech32.py @@ -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 diff --git a/pyhmy/blockchain.py b/pyhmy/blockchain.py index 50bb3b3..a989be1 100644 --- a/pyhmy/blockchain.py +++ b/pyhmy/blockchain.py @@ -1,21 +1,24 @@ -from .rpc.request import ( - rpc_request -) +""" +Interact with the Harmony blockchain to fetch +blocks, headers, transaction pool, node status, etc. +""" +# pylint: disable=too-many-lines +from .rpc.request import rpc_request -from .exceptions import ( - InvalidRPCReplyError -) +from .exceptions import InvalidRPCReplyError + +from .constants import DEFAULT_ENDPOINT, DEFAULT_TIMEOUT -_default_endpoint = 'http://localhost:9500' -_default_timeout = 30 ############################# # Node / network level RPCs # ############################# -def get_bad_blocks(endpoint=_default_endpoint, timeout=_default_timeout) -> list: - """ - [WIP] Get list of bad blocks in memory of specific node - Known issues with RPC not returning correctly +def get_bad_blocks( + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> list: + """[WIP] Get list of bad blocks in memory of specific node Known issues + with RPC not returning correctly. Parameters ---------- @@ -37,15 +40,17 @@ def get_bad_blocks(endpoint=_default_endpoint, timeout=_default_timeout) -> list ------------- https://api.hmny.io/#0ba3c7b6-6aa9-46b8-9c84-f8782e935951 """ - method = 'hmyv2_getCurrentBadBlocks' + method = "hmyv2_getCurrentBadBlocks" 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 chain_id(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: - """ - Chain id of the chain + +def chain_id( endpoint = DEFAULT_ENDPOINT, timeout = DEFAULT_TIMEOUT ) -> dict: + """Chain id of the chain. Parameters ---------- @@ -67,16 +72,19 @@ def chain_id(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: ------------- https://github.com/harmony-one/harmony/blob/343dbe89b3c105f8104ab877769070ba6fdd0133/rpc/blockchain.go#L44 """ - method = 'hmyv2_chainId' + method = "hmyv2_chainId" try: - data = rpc_request(method, endpoint=endpoint, timeout=timeout) - return data['result'] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + data = rpc_request( method, endpoint = endpoint, timeout = timeout ) + return data[ "result" ] + except KeyError as exception: + raise InvalidRPCReplyError( method, endpoint ) from exception -def get_node_metadata(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: - """ - Get config for the node + +def get_node_metadata( + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> dict: + """Get config for the node. Parameters ---------- @@ -91,25 +99,8 @@ def get_node_metadata(endpoint=_default_endpoint, timeout=_default_timeout) -> d blskey: :obj:`list` of BLS keys on the node version: :obj:`str` representing the Harmony binary version network: :obj:`str` the Network name that the node is on (Mainnet or Testnet) - chain-config: :obj:`dict` with the following keys (more are added over time): - chain-id: :obj:`int` Chain ID of the network - cross-tx-epoch: :obj:`int` Epoch at which cross shard transactions were enabled - cross-link-epoch: :obj:`int` Epoch at which cross links were enabled - staking-epoch: :obj:`int` Epoch at which staking was enabled - prestaking-epoch: :obj:`int` Epoch at which staking features without election were allowed - quick-unlock-epoch: :obj:`int` Epoch at which undelegations unlocked in one epoch - eip155-epoch: :obj:`int` Epoch at with EIP155 was enabled - s3-epoch: :obj:`int` Epoch at which Mainnet V0 was launched - receipt-log-epoch: :obj:`int` Epoch at which receipt logs were enabled - eth-compatible-chain-id: :obj:`int` EVM network compatible chain ID - eth-compatible-epoch: :obj:`int` Epoch at which EVM compatibility was launched - eth-compatible-shard-0-chain-id: :obj:`int` EVM network compatible chain ID on shard 0 - five-seconds-epoch: :obj:`int` Epoch at which five second finality was enabled and block rewards adjusted to 17.5 ONE/block - istanbul-epoch: :obj:`int` Epoch at which Ethereum's Istanbul upgrade was added to Harmony - no-early-unlock-epoch: :obj:`int` Epoch at which early unlock of tokens was disabled (https://github.com/harmony-one/harmony/pull/3605) - redelegation-epoch: :obj:`int` Epoch at which redelegation was enabled (staking) - sixty-percent-epoch: :obj:`int` Epoch when internal voting power reduced from 68% to 60% - two-seconds-epoch: :obj:`int` Epoch at which two second finality was enabled and block rewards adjusted to 7 ONE/block + chain-config: :obj:`dict` with the hard fork epochs list, and `chain-id` + both as :obj:`int` is-leader: :obj:`bool` Whether the node is currently leader or not shard-id: :obj:`int` Shard that the node is on current-epoch: :obj:`int` Current epoch @@ -140,19 +131,22 @@ def get_node_metadata(endpoint=_default_endpoint, timeout=_default_timeout) -> d API Reference ------------- https://api.hmny.io/#03c39b56-8dfc-48ce-bdad-f85776dd8aec - https://github.com/harmony-one/harmony/blob/v1.10.2/internal/params/config.go#L233 for chain-config dict - https://github.com/harmony-one/harmony/blob/9f320436ff30d9babd957bc5f2e15a1818c86584/node/api.go#L110 for consensus dict + https://github.com/harmony-one/harmony/blob/v1.10.2/internal/params/config.go#L233 + https://github.com/harmony-one/harmony/blob/9f320436ff30d9babd957bc5f2e15a1818c86584/node/api.go#L110 """ - method = 'hmyv2_getNodeMetadata' + method = "hmyv2_getNodeMetadata" try: - metadata = rpc_request(method, endpoint=endpoint, timeout=timeout) - return metadata['result'] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + metadata = rpc_request( method, endpoint = endpoint, timeout = timeout ) + return metadata[ "result" ] + except KeyError as exception: + raise InvalidRPCReplyError( method, endpoint ) from exception -def get_peer_info(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: - """ - Get peer info for the node + +def get_peer_info( + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> dict: + """Get peer info for the node. Parameters ---------- @@ -181,15 +175,20 @@ def get_peer_info(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: -------- get_node_metadata """ - method = 'hmyv2_getPeerInfo' + method = "hmyv2_getPeerInfo" 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 protocol_version(endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get the current Harmony protocol version this node supports + +def protocol_version( + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> int: + """Get the current Harmony protocol version this node supports. Parameters ---------- @@ -212,16 +211,19 @@ def protocol_version(endpoint=_default_endpoint, timeout=_default_timeout) -> in ------------- https://api.hmny.io/#cab9fcc2-e3cd-4bc9-b62a-13e4e046e2fd """ - method = 'hmyv2_protocolVersion' + method = "hmyv2_protocolVersion" try: - value = rpc_request(method, endpoint=endpoint, timeout=timeout) - return value['result'] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + value = rpc_request( method, endpoint = endpoint, timeout = timeout ) + return value[ "result" ] + except KeyError as exception: + raise InvalidRPCReplyError( method, endpoint ) from exception -def get_num_peers(endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get number of peers connected to the node + +def get_num_peers( + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> int: + """Get number of peers connected to the node. Parameters ---------- @@ -244,15 +246,23 @@ def get_num_peers(endpoint=_default_endpoint, timeout=_default_timeout) -> int: ------------- https://api.hmny.io/#09287e0b-5b61-4d18-a0f1-3afcfc3369c1 """ - method = 'net_peerCount' - try: # Number of peers represented as a hex string - return int(rpc_request(method, endpoint=endpoint, timeout=timeout)['result'], 16) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + method = "net_peerCount" + try: # Number of peers represented as a hex string + return int( + rpc_request( method, + endpoint = endpoint, + timeout = timeout )[ "result" ], + 16 + ) + except ( KeyError, TypeError ) as exception: + raise InvalidRPCReplyError( method, endpoint ) from exception -def get_version(endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get version of the EVM network (https://chainid.network/) + +def get_version( + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> int: + """Get version of the EVM network (https://chainid.network/) Parameters ---------- @@ -275,15 +285,20 @@ def get_version(endpoint=_default_endpoint, timeout=_default_timeout) -> int: ------------- https://api.hmny.io/#09287e0b-5b61-4d18-a0f1-3afcfc3369c1 """ - method = 'net_version' + method = "net_version" try: - return int(rpc_request(method, endpoint=endpoint, timeout=timeout)['result'], 16) # this is hexadecimal - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + return int( + rpc_request( method, + endpoint = endpoint, + timeout = timeout )[ "result" ], + 16 + ) # this is hexadecimal + except ( KeyError, TypeError ) as exception: + raise InvalidRPCReplyError( method, endpoint ) from exception -def in_sync(endpoint=_default_endpoint, timeout=_default_timeout) -> bool: - """ - Whether the shard chain is in sync or syncing (not out of sync) + +def in_sync( endpoint = DEFAULT_ENDPOINT, timeout = DEFAULT_TIMEOUT ) -> bool: + """Whether the shard chain is in sync or syncing (not out of sync) Parameters ---------- @@ -305,15 +320,22 @@ def in_sync(endpoint=_default_endpoint, timeout=_default_timeout) -> bool: ------------- https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/blockchain.go#L690 """ - method = 'hmyv2_inSync' + method = "hmyv2_inSync" try: - return bool(rpc_request(method, endpoint=endpoint, timeout=timeout)['result']) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + return bool( + rpc_request( method, + endpoint = endpoint, + timeout = timeout )[ "result" ] + ) + except ( KeyError, TypeError ) as exception: + raise InvalidRPCReplyError( method, endpoint ) from exception -def beacon_in_sync(endpoint=_default_endpoint, timeout=_default_timeout) -> bool: - """ - Whether the beacon chain is in sync or syncing (not out of sync) + +def beacon_in_sync( + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> bool: + """Whether the beacon chain is in sync or syncing (not out of sync) Parameters ---------- @@ -335,15 +357,22 @@ def beacon_in_sync(endpoint=_default_endpoint, timeout=_default_timeout) -> bool ------------- https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/blockchain.go#L695 """ - method = 'hmyv2_beaconInSync' + method = "hmyv2_beaconInSync" try: - return bool(rpc_request(method, endpoint=endpoint, timeout=timeout)['result']) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + return bool( + rpc_request( method, + endpoint = endpoint, + timeout = timeout )[ "result" ] + ) + except ( KeyError, TypeError ) as exception: + raise InvalidRPCReplyError( method, endpoint ) from exception -def get_staking_epoch(endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get epoch number when blockchain switches to EPoS election + +def get_staking_epoch( + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> int: + """Get epoch number when blockchain switches to EPoS election. Parameters ---------- @@ -370,16 +399,22 @@ def get_staking_epoch(endpoint=_default_endpoint, timeout=_default_timeout) -> i ------ get_node_metadata """ - method = 'hmyv2_getNodeMetadata' + method = "hmyv2_getNodeMetadata" try: - data = rpc_request(method, endpoint=endpoint, timeout=timeout)['result'] - return int(data['chain-config']['staking-epoch']) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + data = rpc_request( method, + endpoint = endpoint, + timeout = timeout )[ "result" ] + return int( data[ "chain-config" ][ "staking-epoch" ] ) + except ( KeyError, TypeError ) as exception: + raise InvalidRPCReplyError( method, endpoint ) from exception -def get_prestaking_epoch(endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get epoch number when blockchain switches to allow staking features without election + +def get_prestaking_epoch( + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> int: + """Get epoch number when blockchain switches to allow staking features + without election. Parameters ---------- @@ -406,19 +441,21 @@ def get_prestaking_epoch(endpoint=_default_endpoint, timeout=_default_timeout) - ------ get_node_metadata """ - method = 'hmyv2_getNodeMetadata' + method = "hmyv2_getNodeMetadata" try: - data = rpc_request(method, endpoint=endpoint, timeout=timeout)['result'] - return int(data['chain-config']['prestaking-epoch']) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + data = rpc_request( method, + endpoint = endpoint, + timeout = timeout )[ "result" ] + return int( data[ "chain-config" ][ "prestaking-epoch" ] ) + except ( KeyError, TypeError ) as exception: + raise InvalidRPCReplyError( method, endpoint ) from exception + ######################## # Sharding information # ######################## -def get_shard(endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get shard ID of the node +def get_shard( endpoint = DEFAULT_ENDPOINT, timeout = DEFAULT_TIMEOUT ) -> int: + """Get shard ID of the node. Parameters ---------- @@ -441,15 +478,20 @@ def get_shard(endpoint=_default_endpoint, timeout=_default_timeout) -> int: -------- get_node_metadata """ - method = 'hmyv2_getNodeMetadata' + method = "hmyv2_getNodeMetadata" try: - return rpc_request(method, endpoint=endpoint, timeout=timeout)['result']['shard-id'] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + return rpc_request( method, + endpoint = endpoint, + timeout = timeout )[ "result" ][ "shard-id" ] + except KeyError as exception: + raise InvalidRPCReplyError( method, endpoint ) from exception -def get_sharding_structure(endpoint=_default_endpoint, timeout=_default_timeout) -> list: - """ - Get network sharding structure + +def get_sharding_structure( + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> list: + """Get network sharding structure. Parameters ---------- @@ -475,18 +517,23 @@ def get_sharding_structure(endpoint=_default_endpoint, timeout=_default_timeout) ------------- https://api.hmny.io/#9669d49e-43c1-47d9-a3fd-e7786e5879df """ - method = 'hmyv2_getShardingStructure' + method = "hmyv2_getShardingStructure" 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 + ############################# # Current status of network # ############################# -def get_leader_address(endpoint=_default_endpoint, timeout=_default_timeout) -> str: - """ - Get current leader one address +def get_leader_address( + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> str: + """Get current leader one address. Parameters ---------- @@ -509,15 +556,21 @@ def get_leader_address(endpoint=_default_endpoint, timeout=_default_timeout) -> ------------- https://api.hmny.io/#8b08d18c-017b-4b44-a3c3-356f9c12dacd """ - method = 'hmyv2_getLeader' + method = "hmyv2_getLeader" 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 is_last_block(block_num, endpoint=_default_endpoint, timeout=_default_timeout) -> bool: - """ - If the block at block_num is the last block + +def is_last_block( + block_num, + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> bool: + """If the block at block_num is the last block. Parameters ---------- @@ -541,18 +594,27 @@ def is_last_block(block_num, endpoint=_default_endpoint, timeout=_default_timeou ------------- https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/blockchain.go#L286 """ - params = [ - block_num, - ] - method = 'hmyv2_isLastBlock' + params = [ block_num, ] + method = "hmyv2_isLastBlock" try: - return bool(rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result']) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e - -def epoch_last_block(epoch, endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Returns the number of the last block in the epoch + return bool( + rpc_request( + method, + params = params, + endpoint = endpoint, + timeout = timeout + )[ "result" ] + ) + except ( KeyError, TypeError ) as exception: + raise InvalidRPCReplyError( method, endpoint ) from exception + + +def epoch_last_block( + epoch, + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> int: + """Returns the number of the last block in the epoch. Parameters ---------- @@ -576,18 +638,26 @@ def epoch_last_block(epoch, endpoint=_default_endpoint, timeout=_default_timeout ------------- https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/blockchain.go#L294 """ - params = [ - epoch, - ] - method = 'hmyv2_epochLastBlock' + params = [ epoch, ] + method = "hmyv2_epochLastBlock" 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_circulating_supply(endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get current circulation supply of tokens in ONE + 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_circulating_supply( + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> int: + """Get current circulation supply of tokens in ONE. Parameters ---------- @@ -610,15 +680,20 @@ def get_circulating_supply(endpoint=_default_endpoint, timeout=_default_timeout) ------------- https://api.hmny.io/#8398e818-ac2d-4ad8-a3b4-a00927395044 """ - method = 'hmyv2_getCirculatingSupply' + method = "hmyv2_getCirculatingSupply" 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_supply(endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get total number of pre-mined tokens + +def get_total_supply( + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> int: + """Get total number of pre-mined tokens. Parameters ---------- @@ -641,15 +716,20 @@ def get_total_supply(endpoint=_default_endpoint, timeout=_default_timeout) -> in ------------- https://api.hmny.io/#3dcea518-9e9a-4a20-84f4-c7a0817b2196 """ - method = 'hmyv2_getTotalSupply' + method = "hmyv2_getTotalSupply" try: - rpc_request(method, endpoint=endpoint, timeout=timeout)['result'] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e + rpc_request( method, + endpoint = endpoint, + timeout = timeout )[ "result" ] + except KeyError as exception: + raise InvalidRPCReplyError( method, endpoint ) from exception -def get_block_number(endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get current block number + +def get_block_number( + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> int: + """Get current block number. Parameters ---------- @@ -672,15 +752,22 @@ def get_block_number(endpoint=_default_endpoint, timeout=_default_timeout) -> in ------------- https://api.hmny.io/#2602b6c4-a579-4b7c-bce8-85331e0db1a7 """ - method = 'hmyv2_blockNumber' + method = "hmyv2_blockNumber" try: - return int(rpc_request(method, endpoint=endpoint, timeout=timeout)['result']) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + return int( + rpc_request( method, + endpoint = endpoint, + timeout = timeout )[ "result" ] + ) + except ( KeyError, TypeError ) as exception: + raise InvalidRPCReplyError( method, endpoint ) from exception -def get_current_epoch(endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get current epoch number + +def get_current_epoch( + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> int: + """Get current epoch number. Parameters ---------- @@ -703,15 +790,22 @@ def get_current_epoch(endpoint=_default_endpoint, timeout=_default_timeout) -> i ------------- https://api.hmny.io/#9b8e98b0-46d1-4fa0-aaa6-317ff1ddba59 """ - method = 'hmyv2_getEpoch' + method = "hmyv2_getEpoch" try: - return int(rpc_request(method, endpoint=endpoint, timeout=timeout)['result']) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + return int( + rpc_request( method, + endpoint = endpoint, + timeout = timeout )[ "result" ] + ) + except ( KeyError, TypeError ) as exception: + raise InvalidRPCReplyError( method, endpoint ) from exception -def get_last_cross_links(endpoint=_default_endpoint, timeout=_default_timeout) -> list: - """ - Get last cross shard links + +def get_last_cross_links( + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> list: + """Get last cross shard links. Parameters ---------- @@ -741,15 +835,20 @@ def get_last_cross_links(endpoint=_default_endpoint, timeout=_default_timeout) - ------------- https://api.hmny.io/#4994cdf9-38c4-4b1d-90a8-290ddaa3040e """ - method = 'hmyv2_getLastCrossLinks' + method = "hmyv2_getLastCrossLinks" 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_gas_price(endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get network gas price + +def get_gas_price( + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> int: + """Get network gas price. Parameters ---------- @@ -772,18 +871,25 @@ def get_gas_price(endpoint=_default_endpoint, timeout=_default_timeout) -> int: ------------- https://api.hmny.io/#1d53fd59-a89f-436c-a171-aec9d9623f48 """ - method = 'hmyv2_gasPrice' + method = "hmyv2_gasPrice" try: - return int(rpc_request(method, endpoint=endpoint, timeout=timeout)['result']) - except (KeyError, TypeError) as e: - raise InvalidRPCReplyError(method, endpoint) from e + return int( + rpc_request( method, + endpoint = endpoint, + timeout = timeout )[ "result" ] + ) + except ( KeyError, TypeError ) as exception: + raise InvalidRPCReplyError( method, endpoint ) from exception + ############## # Block RPCs # ############## -def get_latest_header(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: - """ - Get block header of latest block +def get_latest_header( + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> dict: + """Get block header of latest block. Parameters ---------- @@ -798,14 +904,16 @@ def get_latest_header(endpoint=_default_endpoint, timeout=_default_timeout) -> d blockHash: :obj:`str` Block hash blockNumber: :obj:`int` Block number shardID: :obj:`int` Shard ID - leader: :obj:`str` Wallet address of leader that proposed this block if prestaking, otherwise sha256 hash of leader's public bls key + leader: :obj:`str` Wallet address of leader that proposed this block if prestaking + otherwise sha256 hash of leader's public bls key viewID: :obj:`int` View ID of the block epoch: :obj:`int` Epoch of block timestamp: :obj:`str` Timestamp that the block was finalized in human readable format unixtime: :obj:`int` Timestamp that the block was finalized in Unix time lastCommitSig: :obj:`str` Hex representation of aggregated signatures of the previous block - lastCommitBitmap: :obj:`str` Hex representation of aggregated signature bitmap of the previous block - crossLinks: list of dicts describing the cross shard links, each dict to have the following keys: + lastCommitBitmap: :obj:`str` + Hex representation of aggregated signature bitmap of the previous block + crossLinks:list of dicts describing the cross shard links: block-number: :obj:`int` Number of the cross link block epoch-number: :obj:`int` Epoch of the cross link block hash: :obj:`str` Hash of the cross link block @@ -822,15 +930,21 @@ def get_latest_header(endpoint=_default_endpoint, timeout=_default_timeout) -> d ------------- https://api.hmny.io/#73fc9b97-b048-4b85-8a93-4d2bf1da54a6 """ - method = 'hmyv2_latestHeader' + method = "hmyv2_latestHeader" 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_header_by_number(block_num, endpoint=_default_endpoint, timeout=_default_timeout) -> dict: - """ - Get block header of block at block_num + +def get_header_by_number( + block_num, + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> dict: + """Get block header of block at block_num. Parameters ---------- @@ -854,18 +968,24 @@ def get_header_by_number(block_num, endpoint=_default_endpoint, timeout=_default ------------- https://api.hmny.io/#01148e4f-72bb-426d-a123-718a161eaec0 """ - method = 'hmyv2_getHeaderByNumber' - params = [ - block_num - ] + method = "hmyv2_getHeaderByNumber" + params = [ 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_latest_chain_headers(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: - """ - Get block header of latest block for beacon chain & shard chain + return rpc_request( + method, + params = params, + endpoint = endpoint, + timeout = timeout + )[ "result" ] + except KeyError as exception: + raise InvalidRPCReplyError( method, endpoint ) from exception + + +def get_latest_chain_headers( + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> dict: + """Get block header of latest block for beacon chain & shard chain. Parameters ---------- @@ -877,7 +997,7 @@ def get_latest_chain_headers(endpoint=_default_endpoint, timeout=_default_timeou Returns ------- dict with two keys: - beacon-chain-header: :obj:`dict` with the following keys, applicable to the beacon chain (cross shard links) + beacon-chain-header: :obj:`dict` with the following keys, applicable to the beacon chain shard-chain-header: :obj:`dict` with the following keys, applicable to the shard chain difficulty: legacy epoch: :obj:`int` Epoch of the block @@ -908,16 +1028,25 @@ def get_latest_chain_headers(endpoint=_default_endpoint, timeout=_default_timeou ------------- https://api.hmny.io/#7625493d-16bf-4611-8009-9635d063b4c0 """ - method = 'hmyv2_getLatestChainHeaders' + method = "hmyv2_getLatestChainHeaders" try: - return rpc_request(method, endpoint=endpoint, timeout=timeout)['result'] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e - -def get_block_by_number(block_num, full_tx=False, include_tx=False, include_staking_tx=False, - include_signers=False, endpoint=_default_endpoint, timeout=_default_timeout) -> dict: - """ - Get block by number + return rpc_request( method, + endpoint = endpoint, + timeout = timeout )[ "result" ] + except KeyError as exception: + raise InvalidRPCReplyError( method, endpoint ) from exception + + +def get_block_by_number( # pylint: disable=too-many-arguments + block_num, + full_tx=False, + include_tx=False, + include_staking_tx=False, + include_signers=False, + endpoint=DEFAULT_ENDPOINT, + timeout=DEFAULT_TIMEOUT, +) -> dict: + """Get block by number. Parameters ---------- @@ -955,12 +1084,14 @@ def get_block_by_number(block_num, full_tx=False, include_tx=False, include_stak signers: :obj:`list` List of signers (only if include_signers is set to True) size: :obj:`int` Block size in bytes stakingTransactions: :obj:`list` - if full_tx is True: List of dictionaries, each containing a staking transaction (see account.get_staking_transaction_history) + if full_tx is True: List of dictionaries, + each containing a staking transaction (see account.get_staking_transaction_history) if full_tx is False: List of staking transaction hashes stateRoot: :obj:`str` Hash of state root timestamp: :obj:`int` Unix timestamp of the block transactions: :obj:`list` - if full_tx is True: List of dictionaries, each containing a transaction (see account.get_transaction_history) + if full_tx is True: List of dictionaries, + each containing a transaction (see account.get_transaction_history) if full_tx is False: List of transaction hashes transactionsRoot: :obj:`str` Hash of transactions root uncles: :obj:`str` legacy @@ -979,22 +1110,34 @@ def get_block_by_number(block_num, full_tx=False, include_tx=False, include_stak params = [ block_num, { - 'inclTx': include_tx, - 'fullTx': full_tx, - 'inclStaking': include_staking_tx, - 'withSigners': include_signers, + "inclTx": include_tx, + "fullTx": full_tx, + "inclStaking": include_staking_tx, + "withSigners": include_signers, }, ] - method = 'hmyv2_getBlockByNumber' + method = "hmyv2_getBlockByNumber" try: - return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result'] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e - -def get_block_by_hash(block_hash, full_tx=False, include_tx=False, include_staking_tx=False, - include_signers=False, endpoint=_default_endpoint, timeout=_default_timeout) -> dict: - """ - Get block by hash + return rpc_request( + method, + params = params, + endpoint = endpoint, + timeout = timeout + )[ "result" ] + except KeyError as exception: + raise InvalidRPCReplyError( method, endpoint ) from exception + + +def get_block_by_hash( # pylint: disable=too-many-arguments + block_hash, + full_tx=False, + include_tx=False, + include_staking_tx=False, + include_signers=False, + endpoint=DEFAULT_ENDPOINT, + timeout=DEFAULT_TIMEOUT, +) -> dict: + """Get block by hash. Parameters ---------- @@ -1027,21 +1170,30 @@ def get_block_by_hash(block_hash, full_tx=False, include_tx=False, include_staki params = [ block_hash, { - 'inclTx': include_tx, - 'fullTx': full_tx, - 'inclStaking': include_staking_tx, - 'withSigners': include_signers, + "inclTx": include_tx, + "fullTx": full_tx, + "inclStaking": include_staking_tx, + "withSigners": include_signers, }, ] - method = 'hmyv2_getBlockByHash' + method = "hmyv2_getBlockByHash" try: - return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result'] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e - -def get_block_transaction_count_by_number(block_num, endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get transaction count for specific block number + return rpc_request( + method, + params = params, + endpoint = endpoint, + timeout = timeout + )[ "result" ] + except KeyError as exception: + raise InvalidRPCReplyError( method, endpoint ) from exception + + +def get_block_transaction_count_by_number( + block_num, + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> int: + """Get transaction count for specific block number. Parameters ---------- @@ -1068,18 +1220,27 @@ def get_block_transaction_count_by_number(block_num, endpoint=_default_endpoint, ------------- https://api.hmny.io/#26c5adfb-d757-4595-9eb7-c6efef63df32 """ - params = [ - block_num - ] - method = 'hmyv2_getBlockTransactionCountByNumber' + params = [ block_num ] + method = "hmyv2_getBlockTransactionCountByNumber" 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_block_transaction_count_by_hash(block_hash, endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get transaction count for specific block hash + 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_block_transaction_count_by_hash( + block_hash, + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> int: + """Get transaction count for specific block hash. Parameters ---------- @@ -1106,18 +1267,27 @@ def get_block_transaction_count_by_hash(block_hash, endpoint=_default_endpoint, ------------- https://api.hmny.io/#66c68844-0208-49bb-a83b-08722bc113eb """ - params = [ - block_hash - ] - method = 'hmyv2_getBlockTransactionCountByHash' + params = [ block_hash ] + method = "hmyv2_getBlockTransactionCountByHash" 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_block_staking_transaction_count_by_number(block_num, endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get staking transaction count for specific block number + 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_block_staking_transaction_count_by_number( + block_num, + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> int: + """Get staking transaction count for specific block number. Parameters ---------- @@ -1144,18 +1314,27 @@ def get_block_staking_transaction_count_by_number(block_num, endpoint=_default_e ------------- https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/transaction.go#L494 """ - params = [ - block_num - ] - method = 'hmyv2_getBlockStakingTransactionCountByNumber' + params = [ block_num ] + method = "hmyv2_getBlockStakingTransactionCountByNumber" 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_block_staking_transaction_count_by_hash(block_hash, endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get staking transaction count for specific block hash + 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_block_staking_transaction_count_by_hash( + block_hash, + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> int: + """Get staking transaction count for specific block hash. Parameters ---------- @@ -1182,20 +1361,32 @@ def get_block_staking_transaction_count_by_hash(block_hash, endpoint=_default_en ------------- https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/transaction.go#L523 """ - params = [ - block_hash - ] - method = 'hmyv2_getBlockStakingTransactionCountByHash' + params = [ block_hash ] + method = "hmyv2_getBlockStakingTransactionCountByHash" 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_blocks(start_block, end_block, full_tx=False, include_tx=False, include_staking_tx=False, - include_signers=False, endpoint=_default_endpoint, timeout=_default_timeout - ) -> list: - """ - Get list of blocks from a range + 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_blocks( # pylint: disable=too-many-arguments + start_block, + end_block, + full_tx=False, + include_tx=False, + include_staking_tx=False, + include_signers=False, + endpoint=DEFAULT_ENDPOINT, + timeout=DEFAULT_TIMEOUT, +) -> list: + """Get list of blocks from a range. Parameters ---------- @@ -1233,21 +1424,30 @@ def get_blocks(start_block, end_block, full_tx=False, include_tx=False, include_ start_block, end_block, { - 'withSigners': include_signers, - 'fullTx': full_tx, - 'inclStaking': include_staking_tx, - 'inclTx': include_tx + "withSigners": include_signers, + "fullTx": full_tx, + "inclStaking": include_staking_tx, + "inclTx": include_tx, }, ] - method = 'hmyv2_getBlocks' + method = "hmyv2_getBlocks" try: - return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result'] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e - -def get_block_signers(block_num, endpoint=_default_endpoint, timeout=_default_timeout) -> list: - """ - Get list of block signers for specific block number + return rpc_request( + method, + params = params, + endpoint = endpoint, + timeout = timeout + )[ "result" ] + except KeyError as exception: + raise InvalidRPCReplyError( method, endpoint ) from exception + + +def get_block_signers( + block_num, + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> list: + """Get list of block signers for specific block number. Parameters ---------- @@ -1272,18 +1472,25 @@ def get_block_signers(block_num, endpoint=_default_endpoint, timeout=_default_ti ------------- https://api.hmny.io/#1e4b5f41-9db6-4dea-92fb-4408db78e622 """ - params = [ - block_num - ] - method = 'hmyv2_getBlockSigners' + params = [ block_num ] + method = "hmyv2_getBlockSigners" try: - return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result'] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e - -def get_block_signers_keys(block_num, endpoint=_default_endpoint, timeout=_default_timeout) -> list: - """ - Get list of block signer public bls keys for specific block number + return rpc_request( + method, + params = params, + endpoint = endpoint, + timeout = timeout + )[ "result" ] + except KeyError as exception: + raise InvalidRPCReplyError( method, endpoint ) from exception + + +def get_block_signers_keys( + block_num, + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> list: + """Get list of block signer public bls keys for specific block number. Parameters ---------- @@ -1308,18 +1515,27 @@ def get_block_signers_keys(block_num, endpoint=_default_endpoint, timeout=_defau ------------- https://api.hmny.io/#9f9c8298-1a4e-4901-beac-f34b59ed02f1 """ - params = [ - block_num - ] - method = 'hmyv2_getBlockSignerKeys' + params = [ block_num ] + method = "hmyv2_getBlockSignerKeys" try: - return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result'] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e - -def is_block_signer(block_num, address, endpoint=_default_endpoint, timeout=_default_timeout) -> bool: - """ - Determine if the account at address is a signer for the block at block_num + return rpc_request( + method, + params = params, + endpoint = endpoint, + timeout = timeout + )[ "result" ] + except KeyError as exception: + raise InvalidRPCReplyError( method, endpoint ) from exception + + +def is_block_signer( + block_num, + address, + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> bool: + """Determine if the account at address is a signer for the block at + block_num. Parameters ---------- @@ -1345,19 +1561,26 @@ def is_block_signer(block_num, address, endpoint=_default_endpoint, timeout=_def ------------- https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/blockchain.go#L368 """ - params = [ - block_num, - address - ] - method = 'hmyv2_isBlockSigner' + params = [ block_num, address ] + method = "hmyv2_isBlockSigner" try: - return rpc_request(method, params=params, endpoint=endpoint, timeout=timeout)['result'] - except KeyError as e: - raise InvalidRPCReplyError(method, endpoint) from e - -def get_signed_blocks(address, endpoint=_default_endpoint, timeout=_default_timeout) -> bool: - """ - The number of blocks a particular validator signed for last blocksPeriod (1 epoch) + return rpc_request( + method, + params = params, + endpoint = endpoint, + timeout = timeout + )[ "result" ] + except KeyError as exception: + raise InvalidRPCReplyError( method, endpoint ) from exception + + +def get_signed_blocks( + address, + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> bool: + """The number of blocks a particular validator signed for last blocksPeriod + (1 epoch) Parameters ---------- @@ -1381,18 +1604,27 @@ def get_signed_blocks(address, endpoint=_default_endpoint, timeout=_default_time ------------- https://github.com/harmony-one/harmony/blob/1a8494c069dc3f708fdf690456713a2411465199/rpc/blockchain.go#L406 """ - params = [ - address - ] - method = 'hmyv2_getSignedBlocks' + params = [ address ] + method = "hmyv2_getSignedBlocks" 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_validators(epoch, endpoint=_default_endpoint, timeout=_default_timeout) -> dict: - """ - Get list of validators for specific epoch number + 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_validators( + epoch, + endpoint = DEFAULT_ENDPOINT, + timeout = DEFAULT_TIMEOUT +) -> dict: + """Get list of validators for specific epoch number. Parameters ---------- @@ -1420,18 +1652,25 @@ def get_validators(epoch, endpoint=_default_endpoint, timeout=_default_timeout) ------------- https://api.hmny.io/#4dfe91ad-71fa-4c7d-83f3-d1c86a804da5 """ - params = [ - epoch - ] - method = 'hmyv2_getValidators' + params = [ epoch ] + method = "hmyv2_getValidators" 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 list of validator public bls keys for specific epoch number + 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 list of validator public bls keys for specific epoch number. Parameters ---------- @@ -1456,11 +1695,14 @@ def get_validator_keys(epoch, endpoint=_default_endpoint, timeout=_default_timeo ------------- https://api.hmny.io/#1439b580-fa3c-4d44-a79d-303390997a8c """ - params = [ - epoch - ] - method = 'hmyv2_getValidatorKeys' + params = [ epoch ] + method = "hmyv2_getValidatorKeys" 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 diff --git a/pyhmy/cli.py b/pyhmy/cli.py index 060eefa..79bda37 100644 --- a/pyhmy/cli.py +++ b/pyhmy/cli.py @@ -7,7 +7,8 @@ Example: Below is a demo of how to import, manage keys, and interact with the CLI:: >>> from pyhmy import cli >>> cli.single_call("hmy keys add test1") - '**Important** write this seed phrase in a safe place, it is the only way to recover your account if you ever forget your password + '**Important** write this seed phrase in a safe place, + it is the only way to recover your account if you ever forget your password craft ... tobacco' >>> cli.get_accounts_keystore() {'test1': 'one1aqfeed538xf7n0cfh60tjaeat7yw333pmj6sfu'} @@ -15,7 +16,7 @@ Example: >>> cli.get_accounts(check_addr) ['test1'] >>> cli.single_call("hmy keys list", timeout=2) - 'NAME \t\t ADDRESS\n\ntest1 \tone1aqfeed538xf7n0cfh60tjaeat7yw333pmj6sfu\n' + 'NAME \t\t ADDRESS\n\ntest1 \tone1aqfeed538xf7n0cfh60tjaeat7yw333pmj6sfu\n' >>> cli.get_accounts_keystore() {} @@ -40,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() - if not err: - return False - return "harmony" in err.decode().strip().lower() - except (OSError, subprocess.CalledProcessError, subprocess.SubprocessError): + 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 + ): 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,30 +264,37 @@ 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() - if not err: - raise RuntimeError(f"Could not get version.\n" - f"\tGot exit code {proc.returncode}. Expected non-empty error message.") - return err.decode().strip() + 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." + ) + return err.decode().strip() def get_account_keystore_path(): """ :return: The absolute path to the account keystore of the CLI binary. """ - return os.path.abspath(_account_keystore_path) + return os.path.abspath( account_keystore_path() ) -def check_address(address): +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 diff --git a/pyhmy/constants.py b/pyhmy/constants.py new file mode 100644 index 0000000..f4bc7ea --- /dev/null +++ b/pyhmy/constants.py @@ -0,0 +1,18 @@ +""" +Constants +""" + +# localnet constants +DEFAULT_ENDPOINT = 'http://localhost:9500' +DEFAULT_TIMEOUT = 30 + +# staking percentage constants +PRECISION = 18 +MAX_DECIMAL = 1000000000000000000 + +NAME_CHAR_LIMIT = 140 +IDENTITY_CHAR_LIMIT = 140 +WEBSITE_CHAR_LIMIT = 140 +SECURITY_CONTACT_CHAR_LIMIT = 140 +DETAILS_CHAR_LIMIT = 280 +MIN_REQUIRED_DELEGATION = int( 10000 * 1e18 ) diff --git a/pyhmy/contract.py b/pyhmy/contract.py index 90b41df..781c8b3 100644 --- a/pyhmy/contract.py +++ b/pyhmy/contract.py @@ -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 diff --git a/pyhmy/exceptions.py b/pyhmy/exceptions.py index 2238169..37edf55 100644 --- a/pyhmy/exceptions.py +++ b/pyhmy/exceptions.py @@ -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}" ) diff --git a/pyhmy/logging.py b/pyhmy/logging.py index 4b4787a..e9f29de 100644 --- a/pyhmy/logging.py +++ b/pyhmy/logging.py @@ -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"" - 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() - - 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_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 write(self): - """ - Flushes ALL of the log buffers to the log file via the logger. - - Note that directly after this method call, the respective prints will print - nothing since all log messages are flushed to file. - """ - self._lock.acquire() - self.logger.setLevel(logging.DEBUG) - for line in self.debug_buffer: - self.logger.debug(line) - self.logger.setLevel(logging.WARNING) - for line in self.warning_buffer: - self.logger.warning(line) - self.logger.setLevel(logging.ERROR) - for line in self.error_buffer: - self.logger.error(line) - self.logger.setLevel(logging.INFO) - for line in self.info_buffer: - self.logger.info(line) - self._clear() - self._lock.release() + 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_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_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. + + Note that directly after this method call, the respective prints + will print nothing since all log messages are flushed to file. + """ + with self._lock: + self.logger.setLevel( logging.DEBUG ) + for line in self.debug_buffer: + self.logger.debug( line ) + self.logger.setLevel( logging.WARNING ) + for line in self.warning_buffer: + self.logger.warning( line ) + self.logger.setLevel( logging.ERROR ) + for line in self.error_buffer: + self.logger.error( line ) + self.logger.setLevel( logging.INFO ) + for line in self.info_buffer: + self.logger.info( line ) + self._clear() diff --git a/pyhmy/numbers.py b/pyhmy/numbers.py index 9d62a11..d1577d7 100644 --- a/pyhmy/numbers.py +++ b/pyhmy/numbers.py @@ -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 diff --git a/pyhmy/rpc/exceptions.py b/pyhmy/rpc/exceptions.py index a14dfc6..a5b08a5 100644 --- a/pyhmy/rpc/exceptions.py +++ b/pyhmy/rpc/exceptions.py @@ -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}" ) diff --git a/pyhmy/rpc/request.py b/pyhmy/rpc/request.py index 81f32b0..a86de58 100644 --- a/pyhmy/rpc/request.py +++ b/pyhmy/rpc/request.py @@ -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 diff --git a/pyhmy/signing.py b/pyhmy/signing.py index c773936..693a494 100644 --- a/pyhmy/signing.py +++ b/pyhmy/signing.py @@ -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 hexbytes import HexBytes -from eth_account._utils.transactions import ( +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 eth_account._utils.signing import sign_transaction_hash -from cytoolz import ( - dissoc, - pipe, - merge, - partial -) - -from eth_account.datastructures import ( - SignedTransaction -) - -from .util import ( - chain_id_to_int, - convert_one_to_hex -) +from .util import chain_id_to_int, convert_one_to_hex 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 - ) + 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 + ("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, ) diff --git a/pyhmy/staking.py b/pyhmy/staking.py index 4186073..78be5cc 100644 --- a/pyhmy/staking.py +++ b/pyhmy/staking.py @@ -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 - -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 + 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. 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 - -def get_delegations_by_delegator(delegator_addr, endpoint=_default_endpoint, timeout=_default_timeout) -> list: - """ - Get list of delegations by a delegator + 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. 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 - -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 + 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. 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 - -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 + 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. 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 - -def get_available_redelegation_balance(delegator_addr, endpoint=_default_endpoint, timeout=_default_timeout) -> int: - """ - Get amount of locked undelegated tokens + 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. 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 diff --git a/pyhmy/staking_signing.py b/pyhmy/staking_signing.py index d1cd69a..bbf421f 100644 --- a/pyhmy/staking_signing.py +++ b/pyhmy/staking_signing.py @@ -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}), - chain_id_to_v, # will move chain id to v and add v/r/s - apply_formatters_to_dict(FORMATTERS) + partial( merge, { "chainId": None } ), + chain_id_to_v, # will move chain id to v and add v/r/s + 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'}) + 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" + }, + ) # 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'), - ] - ) - return _sign_transaction_generic(account, sanitized_transaction, DelegateOrUndelegate) + 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 + ) -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'), - ] - commission = apply_formatter_to_array( hexstr_if_str(to_int), # formatter + 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 [ - _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" ), ) - 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 - 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')), + 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 + 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 + identity, # bls public keys + 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')), - ] - ) - return _sign_transaction_generic(account, sanitized_transaction, CreateValidator) + bls_key_sigs, + math.floor( sanitized_transaction.pop( "amount" ) ), + ], + ) + 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['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')), + 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 + 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 + 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') - ] - ) - return _sign_transaction_generic(account, sanitized_transaction, EditValidator) + [ _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 + ) -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' ) diff --git a/pyhmy/staking_structures.py b/pyhmy/staking_structures.py index de64ed7..5cde3ed 100644 --- a/pyhmy/staking_structures.py +++ b/pyhmy/staking_structures.py @@ -1,218 +1,325 @@ -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, -) - -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): +""" +Helper module for signing Harmony staking transactions +""" +# disable most of the Lint here +# pylint: disable=protected-access,no-member,invalid-name,missing-class-docstring,missing-function-docstring + +from enum import Enum, auto + +from rlp.sedes import big_endian_int, Binary, CountableList, List, Text + +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] ] - 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 - 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), + ("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 + # 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), ) + 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 - 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), + ("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 + # 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 diff --git a/pyhmy/transaction.py b/pyhmy/transaction.py index 930adf6..79ef662 100644 --- a/pyhmy/transaction.py +++ b/pyhmy/transaction.py @@ -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 - -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 + 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. 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 - -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 + 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. 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 - -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 + 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. 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 - -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 + 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. 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." + ) diff --git a/pyhmy/util.py b/pyhmy/util.py index 6ef6fc5..77c3ffb 100644 --- a/pyhmy/util.py +++ b/pyhmy/util.py @@ -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 diff --git a/pyhmy/validator.py b/pyhmy/validator.py index 111644a..7104295 100644 --- a/pyhmy/validator.py +++ b/pyhmy/validator.py @@ -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'' + def __repr__( self ) -> str: + return f"" - 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 + """ + 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 """ - Set validator name + 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 ) diff --git a/pytest.ini b/pytest.ini index 02ae413..b816cb7 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,2 @@ [pytest] -addopts = -v --showlocals -python_paths = . +addopts = -v --showlocals \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 27a6ef2..6e8a7d9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,7 +27,7 @@ install_requires = requests incremental eth-rlp - eth-account == 0.5.4 + eth-account >= 0.5.5 eth-utils hexbytes cytoolz diff --git a/tests/GenerateRawTransactions.ipynb b/tests/GenerateRawTransactions.ipynb new file mode 100644 index 0000000..057053b --- /dev/null +++ b/tests/GenerateRawTransactions.ipynb @@ -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 +} diff --git a/tests/bech32-pyhmy/test_bech32.py b/tests/bech32-pyhmy/test_bech32.py index 325bc08..f9d3e77 100644 --- a/tests/bech32-pyhmy/test_bech32.py +++ b/tests/bech32-pyhmy/test_bech32.py @@ -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" ) diff --git a/tests/cli-pyhmy/test_cli.py b/tests/cli-pyhmy/test_cli.py index 1cb9802..844b979 100644 --- a/tests/cli-pyhmy/test_cli.py +++ b/tests/cli-pyhmy/test_cli.py @@ -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() diff --git a/tests/logging-pyhmy/test_logging.py b/tests/logging-pyhmy/test_logging.py index 018a66e..30d6063 100644 --- a/tests/logging-pyhmy/test_logging.py +++ b/tests/logging-pyhmy/test_logging.py @@ -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: diff --git a/tests/numbers-pyhmy/test_numbers.py b/tests/numbers-pyhmy/test_numbers.py index 569432f..6384fe2 100644 --- a/tests/numbers-pyhmy/test_numbers.py +++ b/tests/numbers-pyhmy/test_numbers.py @@ -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 diff --git a/tests/request-pyhmy/test_request.py b/tests/request-pyhmy/test_request.py index a7fcc97..74a2e7c 100644 --- a/tests/request-pyhmy/test_request.py +++ b/tests/request-pyhmy/test_request.py @@ -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 diff --git a/tests/sdk-pyhmy/conftest.py b/tests/sdk-pyhmy/conftest.py index ef421ec..b155b92 100644 --- a/tests/sdk-pyhmy/conftest.py +++ b/tests/sdk-pyhmy/conftest.py @@ -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 diff --git a/tests/sdk-pyhmy/test_account.py b/tests/sdk-pyhmy/test_account.py index 8bf0706..89a65e6 100644 --- a/tests/sdk-pyhmy/test_account.py +++ b/tests/sdk-pyhmy/test_account.py @@ -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 ) diff --git a/tests/sdk-pyhmy/test_blockchain.py b/tests/sdk-pyhmy/test_blockchain.py index edbfb10..8601ef8 100644 --- a/tests/sdk-pyhmy/test_blockchain.py +++ b/tests/sdk-pyhmy/test_blockchain.py @@ -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 ) diff --git a/tests/sdk-pyhmy/test_contract.py b/tests/sdk-pyhmy/test_contract.py index a5cbea8..36b38fa 100644 --- a/tests/sdk-pyhmy/test_contract.py +++ b/tests/sdk-pyhmy/test_contract.py @@ -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 ) diff --git a/tests/sdk-pyhmy/test_signing.py b/tests/sdk-pyhmy/test_signing.py index 34ac601..863fdd0 100644 --- a/tests/sdk-pyhmy/test_signing.py +++ b/tests/sdk-pyhmy/test_signing.py @@ -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" + ) diff --git a/tests/sdk-pyhmy/test_staking.py b/tests/sdk-pyhmy/test_staking.py index 115f834..ca1b11a 100644 --- a/tests/sdk-pyhmy/test_staking.py +++ b/tests/sdk-pyhmy/test_staking.py @@ -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) - assert total_staking > 0 -@pytest.mark.run(order=22) +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 + + 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 ) diff --git a/tests/sdk-pyhmy/test_staking_signing.py b/tests/sdk-pyhmy/test_staking_signing.py index 2b15c3d..ede7aa6 100644 --- a/tests/sdk-pyhmy/test_staking_signing.py +++ b/tests/sdk-pyhmy/test_staking_signing.py @@ -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" + ) diff --git a/tests/sdk-pyhmy/test_transaction.py b/tests/sdk-pyhmy/test_transaction.py index fde7c1b..d00bc66 100644 --- a/tests/sdk-pyhmy/test_transaction.py +++ b/tests/sdk-pyhmy/test_transaction.py @@ -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 ) diff --git a/tests/sdk-pyhmy/test_validator.py b/tests/sdk-pyhmy/test_validator.py index 34bc436..4dd3d1a 100644 --- a/tests/sdk-pyhmy/test_validator.py +++ b/tests/sdk-pyhmy/test_validator.py @@ -1,26 +1,11 @@ import pytest -import requests -from decimal import ( - Decimal -) +from decimal import Decimal -from pyhmy import ( - validator -) +from pyhmy import validator -from pyhmy.rpc import ( - exceptions -) +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)), - 100, - '4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48', - None).rawTransaction.hex() - assert signed_hash == '0xf9010580f8bf94ebcd16e8c1d8f493ba04e99a56474122d81a9c58f83885416c69636585616c69636591616c6963652e6861726d6f6e792e6f6e6583426f6295446f6e2774206d6573732077697468206d65212121dcc8872386f26fc10000c9880c7d713b49da0000c887b1a2bc2ec500008a021e19e0c9bab24000008a0878678326eac9000000f1b0b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b622476086118a021e27c1806e59a4000024a047c6d444971d4d3c48e8b255aa0e543ebb47b60f761582694e5af5330445aba5a04db1ffea9cca9f9e56e8f782c689db680992903acfd9c06f4593f7fd9a781bd7' + 2, + int( convert_one_to_atto( 1 ) ), + 100, + "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): + 2, + int(convert_one_to_atto(1)), + 100, + "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() diff --git a/tests/util-pyhmy/test_util.py b/tests/util-pyhmy/test_util.py index d502d11..adb7273 100644 --- a/tests/util-pyhmy/test_util.py +++ b/tests/util-pyhmy/test_util.py @@ -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, - 2, - 3 - ] + "test": "val", + "arr": [ 1, + 2, + 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 )