Basic Python SDK (#1)
* [project] Update README with correct Python version requirement * [request] Add rpc request lib [exceptions] Add exceptions wrapper lib [common] Define defaults for endpoint & timeout * [blockchain] Added blockchain related RPCs [staking] Added staking related RPCs * [blockchain] Added more RPCs, organized file based on RPC type (network or block) * [account] Add account related RPCs (balance, transaction history) * [transaction] Added regular & staking transaction RPCs * [rpc] Move RPCs to separate lib [common] Remove common & add defaults to request * [rpc] Add __init__ file for future use * [util] Update utils to use rpc lib * [rpc] Add return type annotations * [make] Fix import errors * [test] Add tests for rpc_request using local blockchain [make] Update make tests to print reason to skip test * [test] Add proper testing infra for RPC calls * [numbers] Add utils for converting between atto and one * [test] Use cross shard as the inital funding transaction & enable cross shard RPC tests * [test] Refactor test setup to not wait if setup on chain is already done * [pyhmy] Remove dev version & update major version * [init] Update imports * [account] Add get_balance_on_all_shards * [rpc] Fix documentation errors * [util] Fix is_active_shard error * [rpc] Address PR commentspull/2/head
parent
b50b8214ae
commit
3cf778876e
@ -0,0 +1,42 @@ |
|||||||
|
from decimal import Decimal |
||||||
|
|
||||||
|
|
||||||
|
_conversion_unit = Decimal(1e18) |
||||||
|
|
||||||
|
def convert_atto_to_one(atto): |
||||||
|
""" |
||||||
|
Convert ATTO to ONE |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
atto: str, int, float, decimal |
||||||
|
Value in ATTO to convert to ONE |
||||||
|
Float input will be truncated, since ATTO is the lowest possible denomination of ONE |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
decimal |
||||||
|
Converted value in ONE |
||||||
|
""" |
||||||
|
if isinstance(atto, float): |
||||||
|
atto = int(atto) |
||||||
|
return Decimal(atto) / _conversion_unit |
||||||
|
|
||||||
|
|
||||||
|
def convert_one_to_atto(one): |
||||||
|
""" |
||||||
|
Convert ONE to ATTO |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
one: str, int, float, decimal |
||||||
|
Value in ONE to convert to ATTO |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
decimal |
||||||
|
Converted value in ATTO |
||||||
|
""" |
||||||
|
if isinstance(one, float): |
||||||
|
one = str(one) |
||||||
|
return Decimal(one) * _conversion_unit |
@ -0,0 +1,207 @@ |
|||||||
|
from .request import ( |
||||||
|
rpc_request |
||||||
|
) |
||||||
|
|
||||||
|
from .blockchain import ( |
||||||
|
get_sharding_structure |
||||||
|
) |
||||||
|
|
||||||
|
_default_endpoint = 'http://localhost:9500' |
||||||
|
_default_timeout = 30 |
||||||
|
|
||||||
|
|
||||||
|
def get_balance(address, endpoint=_default_endpoint, timeout=_default_timeout) -> int: |
||||||
|
""" |
||||||
|
Get current account balance |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
address: str |
||||||
|
Address to get balance for |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
int |
||||||
|
Account balance in ATTO |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
address, |
||||||
|
'latest' |
||||||
|
] |
||||||
|
return int(rpc_request('hmy_getBalance', params=params, endpoint=endpoint, timeout=timeout)['result'], 16) |
||||||
|
|
||||||
|
|
||||||
|
def get_balance_by_block(address, block_num, endpoint=_default_endpoint, timeout=_default_timeout) -> int: |
||||||
|
""" |
||||||
|
Get account balance at time of given block |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
address: str |
||||||
|
Address to get balance for |
||||||
|
block_num: int |
||||||
|
Block number to req |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
int |
||||||
|
Account balance in ATTO at given block |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
address, |
||||||
|
str(hex(block_num)) |
||||||
|
] |
||||||
|
return int(rpc_request('hmy_getBalanceByBlockNumber', params=params, endpoint=endpoint, timeout=timeout)['result'], 16) |
||||||
|
|
||||||
|
|
||||||
|
def get_transaction_count(address, endpoint=_default_endpoint, timeout=_default_timeout) -> int: |
||||||
|
""" |
||||||
|
Get number of transactions & staking transactions sent by an account |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
address: str |
||||||
|
Address to get transaction count for |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
int |
||||||
|
Number of transactions sent by the account (account nonce) |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
address, |
||||||
|
'latest' |
||||||
|
] |
||||||
|
return int(rpc_request('hmy_getTransactionCount', params=params, endpoint=endpoint, timeout=timeout)['result'], 16) |
||||||
|
|
||||||
|
|
||||||
|
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 |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
address: str |
||||||
|
Address to get transaction history for |
||||||
|
page: :obj:`int`, optional |
||||||
|
Page to request for pagination |
||||||
|
page_size: :obj:`int`, optional |
||||||
|
Size of page for pagination |
||||||
|
include_full_tx: :obj:`bool`, optional |
||||||
|
True to include full transaction data |
||||||
|
False to just get the transaction hash |
||||||
|
tx_type: :obj:`str`, optional |
||||||
|
'ALL' to get all transactions send & received by the address |
||||||
|
'SENT' to get all transactions sent by the address |
||||||
|
'RECEIVED' to get all transactions received by the address |
||||||
|
order: :obj:`str`, optional |
||||||
|
'ASC' to sort transactions in ascending order based on timestamp |
||||||
|
'DESC' to sort transactions in descending order based on timestamp |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
list |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
{ |
||||||
|
'address': address, |
||||||
|
'pageIndex': page, |
||||||
|
'pageSize': page_size, |
||||||
|
'fullTx': include_full_tx, |
||||||
|
'txType': tx_type, |
||||||
|
'order': order |
||||||
|
} |
||||||
|
] |
||||||
|
tx_history = rpc_request('hmy_getTransactionsHistory', params=params, endpoint=endpoint, timeout=timeout) |
||||||
|
return tx_history['result']['transactions'] |
||||||
|
|
||||||
|
|
||||||
|
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 |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
address: str |
||||||
|
Address to get staking transaction history for |
||||||
|
page: :obj:`int`, optional |
||||||
|
Page to request for pagination |
||||||
|
page-size: :obj:`int`, optional |
||||||
|
Size of page for pagination |
||||||
|
include_full_tx: :obj:`bool`, optional |
||||||
|
True to include full staking transaction data |
||||||
|
False to just get the staking transaction hash |
||||||
|
tx_type: :obj:`str`, optional |
||||||
|
'ALL' to get all staking transactions |
||||||
|
order: :obj:`str`, optional |
||||||
|
'ASC' to sort transactions in ascending order based on timestamp |
||||||
|
'DESC' to sort transactions in descending order based on timestamp |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
list |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
{ |
||||||
|
'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 |
||||||
|
stx_history = rpc_request('hmyv2_getStakingTransactionsHistory', params=params, endpoint=endpoint, timeout=timeout) |
||||||
|
return stx_history['result']['staking_transactions'] |
||||||
|
|
||||||
|
|
||||||
|
def get_balance_on_all_shards(address, endpoint=_default_endpoint, timeout=_default_timeout): |
||||||
|
""" |
||||||
|
Get current account balance in all shards |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
address: str |
||||||
|
Address to get balance for |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds per request |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
dict |
||||||
|
Account balance per shard in ATTO |
||||||
|
""" |
||||||
|
balances = {} |
||||||
|
sharding_structure = get_sharding_structure(endpoint=endpoint, timeout=timeout) |
||||||
|
for shard in sharding_structure: |
||||||
|
balances[shard['shardID']] = get_balance(address, endpoint=shard['http'], timeout=timeout) |
||||||
|
return balances |
@ -0,0 +1,378 @@ |
|||||||
|
from .request import ( |
||||||
|
rpc_request |
||||||
|
) |
||||||
|
|
||||||
|
_default_endpoint = 'http://localhost:9500' |
||||||
|
_default_timeout = 30 |
||||||
|
|
||||||
|
|
||||||
|
################ |
||||||
|
# Network RPCs # |
||||||
|
################ |
||||||
|
def get_node_metadata(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: |
||||||
|
""" |
||||||
|
Get config for the node |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
dict |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
return rpc_request('hmy_getNodeMetadata', endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def get_sharding_structure(endpoint=_default_endpoint, timeout=_default_timeout) -> list: |
||||||
|
""" |
||||||
|
Get network sharding structure |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
list |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
return rpc_request('hmy_getShardingStructure', endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def get_leader_address(endpoint=_default_endpoint, timeout=_default_timeout) -> str: |
||||||
|
""" |
||||||
|
Get current leader one address |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
str |
||||||
|
One address of current leader |
||||||
|
""" |
||||||
|
return rpc_request('hmy_getLeader', endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def get_block_number(endpoint=_default_endpoint, timeout=_default_timeout) -> int: |
||||||
|
""" |
||||||
|
Get current block number |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
int |
||||||
|
Current block number |
||||||
|
""" |
||||||
|
return int(rpc_request('hmy_blockNumber', endpoint=endpoint, timeout=timeout)['result'], 16) |
||||||
|
|
||||||
|
|
||||||
|
def get_current_epoch(endpoint=_default_endpoint, timeout=_default_timeout) -> int: |
||||||
|
""" |
||||||
|
Get current epoch number |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
int |
||||||
|
Current epoch number |
||||||
|
""" |
||||||
|
return int(rpc_request('hmy_getEpoch', endpoint=endpoint, timeout=timeout)['result'], 16) |
||||||
|
|
||||||
|
|
||||||
|
def get_gas_price(endpoint=_default_endpoint, timeout=_default_timeout) -> int: |
||||||
|
""" |
||||||
|
Get network gas price |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
int |
||||||
|
Network gas price |
||||||
|
""" |
||||||
|
return int(rpc_request('hmy_gasPrice', endpoint=endpoint, timeout=timeout)['result'], 16) |
||||||
|
|
||||||
|
|
||||||
|
def get_num_peers(endpoint=_default_endpoint, timeout=_default_timeout) -> int: |
||||||
|
""" |
||||||
|
Get number of peers connected to the node |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
int |
||||||
|
Number of connected peers |
||||||
|
""" |
||||||
|
return int(rpc_request('net_peerCount', endpoint=endpoint, timeout=timeout)['result'], 16) |
||||||
|
|
||||||
|
|
||||||
|
############## |
||||||
|
# Block RPCs # |
||||||
|
############## |
||||||
|
def get_latest_header(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: |
||||||
|
""" |
||||||
|
Get block header of latest block |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
dict |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
return rpc_request('hmy_latestHeader', endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def get_latest_headers(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: |
||||||
|
""" |
||||||
|
Get block header of latest block for beacon chain & shard chain |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
dict |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
return rpc_request('hmy_getLatestChainHeaders', endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def get_block_by_number(block_num, endpoint=_default_endpoint, include_full_tx=False, timeout=_default_timeout) -> dict: |
||||||
|
""" |
||||||
|
Get block by number |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
block_num: int |
||||||
|
Block number to fetch |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
include_full_tx: :obj:`bool`, optional |
||||||
|
Include list of full transactions data for each block |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
dict |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
str(hex(block_num)), |
||||||
|
include_full_tx |
||||||
|
] |
||||||
|
return rpc_request('hmy_getBlockByNumber', params=params, endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def get_block_by_hash(block_hash, endpoint=_default_endpoint, include_full_tx=False, timeout=_default_timeout) -> dict: |
||||||
|
""" |
||||||
|
Get block by hash |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
block_hash: str |
||||||
|
Block hash to fetch |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
include_full_tx: :obj:`bool`, optional |
||||||
|
Include list of full transactions data for each block |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
dict |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
block_hash, |
||||||
|
include_full_tx |
||||||
|
] |
||||||
|
return rpc_request('hmy_getBlockByHash', params=params, endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def get_block_transaction_count_by_number(block_num, endpoint=_default_endpoint, timeout=_default_timeout) -> int: |
||||||
|
""" |
||||||
|
Get transaction count for specific block number |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
block_num: int |
||||||
|
Block number to get transaction count for |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
include_full_tx: :obj:`bool`, optional |
||||||
|
Include list of full transactions data for each block |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
int |
||||||
|
Number of transactions in the block |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
str(hex(block_num)) |
||||||
|
] |
||||||
|
return int(rpc_request('hmy_getBlockTransactionCountByNumber', params=params, |
||||||
|
endpoint=endpoint, timeout=timeout)['result'], 16 |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
def get_block_transaction_count_by_hash(block_hash, endpoint=_default_endpoint, timeout=_default_timeout) -> int: |
||||||
|
""" |
||||||
|
Get transaction count for specific block hash for |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
block_hash: str |
||||||
|
Block hash to get transaction count |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
include_full_tx: :obj:`bool`, optional |
||||||
|
Include list of full transactions data for each block |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
int |
||||||
|
Number of transactions in the block |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
block_hash |
||||||
|
] |
||||||
|
return int(rpc_request('hmy_getBlockTransactionCountByHash', params=params, |
||||||
|
endpoint=endpoint, timeout=timeout)['result'], 16 |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
def get_blocks(start_block, end_block, endpoint=_default_endpoint, include_full_tx=False, |
||||||
|
include_signers=False, timeout=_default_timeout |
||||||
|
) -> list: |
||||||
|
""" |
||||||
|
Get list of blocks from a range |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
start_block: int |
||||||
|
First block to fetch (inclusive) |
||||||
|
end_block: int |
||||||
|
Last block to fetch (inclusive) |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
include_full_tx: :obj:`bool`, optional |
||||||
|
Include list of full transactions data for each block |
||||||
|
include_signers: :obj:`bool`, optional |
||||||
|
Include list of signers for each block |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
list |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
str(hex(start_block)), |
||||||
|
str(hex(end_block)), |
||||||
|
{ |
||||||
|
'withSigners': include_signers, |
||||||
|
'fullTx': include_full_tx, |
||||||
|
}, |
||||||
|
] |
||||||
|
return rpc_request('hmy_getBlocks', params=params, endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def get_block_signers(block_num, endpoint=_default_endpoint, timeout=_default_timeout) -> list: |
||||||
|
""" |
||||||
|
Get list of block signers for specific block number |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
block_num: int |
||||||
|
Block number to get signers for |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
list |
||||||
|
List of one addresses that signed the block |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
str(hex(block_num)) |
||||||
|
] |
||||||
|
return rpc_request('hmy_getBlockSigners', params=params, endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def get_validators(epoch, endpoint=_default_endpoint, timeout=_default_timeout) -> dict: |
||||||
|
""" |
||||||
|
Get list of validators for specific epoch number |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
epoch: int |
||||||
|
Epoch to get list of validators for |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
dict |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
epoch |
||||||
|
] |
||||||
|
return rpc_request('hmy_getValidators', params=params, endpoint=endpoint, timeout=timeout)['result'] |
@ -0,0 +1,23 @@ |
|||||||
|
import json |
||||||
|
import requests |
||||||
|
|
||||||
|
|
||||||
|
class RPCError(RuntimeError): |
||||||
|
""" |
||||||
|
Exception raised when RPC call returns an error |
||||||
|
""" |
||||||
|
|
||||||
|
class JSONDecodeError(json.decoder.JSONDecodeError): |
||||||
|
""" |
||||||
|
Wrapper for json lib DecodeError exception |
||||||
|
""" |
||||||
|
|
||||||
|
class RequestsError(requests.exceptions.RequestException): |
||||||
|
""" |
||||||
|
Wrapper for requests lib exceptions |
||||||
|
""" |
||||||
|
|
||||||
|
class RequestsTimeoutError(requests.exceptions.Timeout): |
||||||
|
""" |
||||||
|
Wrapper for requests lib Timeout exceptions |
||||||
|
""" |
@ -0,0 +1,119 @@ |
|||||||
|
import json |
||||||
|
|
||||||
|
import requests |
||||||
|
|
||||||
|
from .exceptions import ( |
||||||
|
JSONDecodeError, |
||||||
|
RequestsError, |
||||||
|
RequestsTimeoutError, |
||||||
|
RPCError |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
_default_endpoint = 'http://localhost:9500' |
||||||
|
_default_timeout = 30 |
||||||
|
|
||||||
|
|
||||||
|
def base_request(method, params=None, endpoint=_default_endpoint, timeout=_default_timeout) -> str: |
||||||
|
""" |
||||||
|
Basic RPC request |
||||||
|
|
||||||
|
Parameters |
||||||
|
--------- |
||||||
|
method: str |
||||||
|
RPC Method to call |
||||||
|
params: :obj:`list`, optional |
||||||
|
Parameters for the RPC method |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
str |
||||||
|
Raw output from the request |
||||||
|
|
||||||
|
Raises |
||||||
|
------ |
||||||
|
TypeError |
||||||
|
If params is not a list or None |
||||||
|
RequestsTimeoutError |
||||||
|
If request timed out |
||||||
|
RequestsError |
||||||
|
If other request error occured |
||||||
|
""" |
||||||
|
if params is None: |
||||||
|
params = [] |
||||||
|
elif not isinstance(params, list): |
||||||
|
raise TypeError(f'invalid type {params.__class__}') |
||||||
|
|
||||||
|
try: |
||||||
|
payload = { |
||||||
|
"id": "1", |
||||||
|
"jsonrpc": "2.0", |
||||||
|
"method": method, |
||||||
|
"params": params |
||||||
|
} |
||||||
|
headers = { |
||||||
|
'Content-Type': 'application/json' |
||||||
|
} |
||||||
|
|
||||||
|
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() from err |
||||||
|
except requests.exceptions.RequestException as err: |
||||||
|
raise RequestsError() from err |
||||||
|
|
||||||
|
|
||||||
|
def rpc_request(method, params=None, endpoint=_default_endpoint, timeout=_default_timeout) -> dict: |
||||||
|
""" |
||||||
|
RPC request |
||||||
|
|
||||||
|
Parameters |
||||||
|
--------- |
||||||
|
method: str |
||||||
|
RPC Method to call |
||||||
|
params: :obj:`list`, optional |
||||||
|
Parameters for the RPC method |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
dict |
||||||
|
Returns dictionary representation of RPC response |
||||||
|
Example format: |
||||||
|
{ |
||||||
|
"jsonrpc": "2.0", |
||||||
|
"id": 1, |
||||||
|
"result": ... |
||||||
|
} |
||||||
|
|
||||||
|
Raises |
||||||
|
------ |
||||||
|
RPCError |
||||||
|
If RPC response returned a blockchain error |
||||||
|
JSONDecodeError |
||||||
|
If RPC response format is not valid JSON object |
||||||
|
|
||||||
|
See Also |
||||||
|
-------- |
||||||
|
base_request |
||||||
|
""" |
||||||
|
raw_resp = base_request(method, params, endpoint, timeout) |
||||||
|
|
||||||
|
try: |
||||||
|
resp = json.loads(raw_resp) |
||||||
|
if 'error' in resp: |
||||||
|
raise RPCError(str(resp['error'])) |
||||||
|
return resp |
||||||
|
except json.decoder.JSONDecodeError as err: |
||||||
|
raise JSONDecodeError() from err |
||||||
|
|
||||||
|
|
||||||
|
# TODO: Add GET requests |
@ -0,0 +1,207 @@ |
|||||||
|
from .request import ( |
||||||
|
rpc_request |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
_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 |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
list |
||||||
|
List of one addresses for all validators on chain |
||||||
|
""" |
||||||
|
return rpc_request('hmy_getAllValidatorAddresses', endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def get_validator_information(validator_addr, endpoint=_default_endpoint, timeout=_default_timeout) -> dict: |
||||||
|
""" |
||||||
|
Get validator information for validator address |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
validator_addr: str |
||||||
|
One address of the validator to get information for |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
dict |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
validator_addr |
||||||
|
] |
||||||
|
return rpc_request('hmy_getValidatorInformation', params=params, endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def get_all_validator_information(page=-1, 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) |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
list |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
page |
||||||
|
] |
||||||
|
return rpc_request('hmy_getAllValidatorInformation', params=params, endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
################### |
||||||
|
# Delegation RPCs # |
||||||
|
################### |
||||||
|
def get_delegations_by_delegator(delegator_addr, endpoint=_default_endpoint, timeout=_default_timeout) -> list: |
||||||
|
""" |
||||||
|
Get list of delegations by a delegator |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
delegator_addr: str |
||||||
|
Delegator address to get list of delegations for |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
list |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
delegator_addr |
||||||
|
] |
||||||
|
return rpc_request('hmy_getDelegationsByDelegator', params=params, endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def get_delegations_by_validator(validator_addr, endpoint=_default_endpoint, timeout=_default_timeout) -> list: |
||||||
|
""" |
||||||
|
Get list of delegations to a validator |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
validator_addr: str |
||||||
|
Validator address to get list of delegations for |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
list |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
validator_addr |
||||||
|
] |
||||||
|
return rpc_request('hmy_getDelegationsByValidator', params=params, endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
######################## |
||||||
|
# Staking Network RPCs # |
||||||
|
######################## |
||||||
|
def get_current_utility_metrics(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: |
||||||
|
""" |
||||||
|
Get current utility metrics of network |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
dict |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
return rpc_request('hmy_getCurrentUtilityMetrics', endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def get_staking_network_info(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: |
||||||
|
""" |
||||||
|
Get staking network information |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
dict |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
return rpc_request('hmy_getStakingNetworkInfo', endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def get_super_committees(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: |
||||||
|
""" |
||||||
|
Get voting committees for current & previous epoch |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
dict |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
return rpc_request('hmy_getSuperCommittees', endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def get_raw_median_stake_snapshot(endpoint=_default_endpoint, timeout=_default_timeout) -> dict: |
||||||
|
""" |
||||||
|
Get median stake & additional committee data of the current epoch |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
dict |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
return rpc_request('hmy_getMedianRawStakeSnapshot', endpoint=endpoint, timeout=timeout)['result'] |
@ -0,0 +1,379 @@ |
|||||||
|
from .request import ( |
||||||
|
rpc_request |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
_default_endpoint = 'http://localhost:9500' |
||||||
|
_default_timeout = 30 |
||||||
|
|
||||||
|
|
||||||
|
######################### |
||||||
|
# Transaction Pool RPCs # |
||||||
|
######################### |
||||||
|
def get_pending_transactions(endpoint=_default_endpoint, timeout=_default_timeout) -> list: |
||||||
|
""" |
||||||
|
Get list of pending transactions |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
list |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
return rpc_request('hmy_pendingTransactions', endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
#################### |
||||||
|
# Transaction RPCs # |
||||||
|
#################### |
||||||
|
def get_transaction_by_hash(tx_hash, endpoint=_default_endpoint, timeout=_default_timeout) -> dict: |
||||||
|
""" |
||||||
|
Get transaction by hash |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
tx_hash: str |
||||||
|
Transaction hash to fetch |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
dict |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
tx_hash |
||||||
|
] |
||||||
|
return rpc_request('hmy_getTransactionByHash', params=params, endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
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 |
||||||
|
---------- |
||||||
|
block_hash: str |
||||||
|
Block hash for transaction |
||||||
|
tx_index: int |
||||||
|
Transaction index to fetch |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
dict |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
block_hash, |
||||||
|
str(hex(tx_index)) |
||||||
|
] |
||||||
|
return rpc_request('hmy_getTransactionByBlockHashAndIndex', params=params, endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
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 |
||||||
|
---------- |
||||||
|
block_num: int |
||||||
|
Block number for transaction |
||||||
|
tx_index: int |
||||||
|
Transaction index to fetch |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
dict |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
str(hex(block_num)), |
||||||
|
str(hex(tx_index)) |
||||||
|
] |
||||||
|
return rpc_request('hmy_getTransactionByBlockNumberAndIndex', params=params, endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def get_transaction_receipt(tx_receipt, endpoint=_default_endpoint, timeout=_default_timeout) -> dict: |
||||||
|
""" |
||||||
|
Get transaction receipt |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
tx_receipt: str |
||||||
|
Transaction receipt to fetch |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
dict |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
tx_receipt |
||||||
|
] |
||||||
|
return rpc_request('hmy_getTransactionReceipt', params=params, endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def get_transaction_error_sink(endpoint=_default_endpoint, timeout=_default_timeout) -> list: |
||||||
|
""" |
||||||
|
Get transaction error sink |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
list |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
return rpc_request('hmy_getCurrentTransactionErrorSink', endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def send_raw_transaction(raw_tx, endpoint=_default_endpoint, timeout=_default_timeout) -> str: |
||||||
|
""" |
||||||
|
Send signed transaction |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
raw_tx: str |
||||||
|
Hex representation of signed transaction |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
str |
||||||
|
Transaction hash |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
raw_tx |
||||||
|
] |
||||||
|
return rpc_request('hmy_sendRawTransaction', params=params, endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
############################### |
||||||
|
# CrossShard Transaction RPCs # |
||||||
|
############################### |
||||||
|
def get_pending_cx_receipts(endpoint=_default_endpoint, timeout=_default_timeout) -> list: |
||||||
|
""" |
||||||
|
Get list of pending cross shard transactions |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
list |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
return rpc_request('hmy_getPendingCXReceipts', endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def get_cx_receipt_by_hash(cx_hash, endpoint = _default_endpoint, timeout = _default_timeout) -> dict: |
||||||
|
""" |
||||||
|
Get cross shard receipt by hash |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
cx_hash: str |
||||||
|
Hash of cross shard transaction receipt |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
dict |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
cx_hash |
||||||
|
] |
||||||
|
return rpc_request('hmy_getCXReceiptByHash', params=params, endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def resend_cx_receipt(cx_receipt, endpoint=_default_endpoint, timeout=_default_timeout) -> bool: |
||||||
|
""" |
||||||
|
Send cross shard receipt |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
cx_hash: str |
||||||
|
Hash of cross shard transaction receipt |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
bool |
||||||
|
If the receipt transactions was succesfully resent |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
cx_receipt |
||||||
|
] |
||||||
|
return rpc_request('hmy_resendCx', params=params, endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
############################ |
||||||
|
# Staking Transaction RPCs # |
||||||
|
############################ |
||||||
|
def get_staking_transaction_by_hash(tx_hash, endpoint=_default_endpoint, timeout=_default_timeout) -> dict: |
||||||
|
""" |
||||||
|
Get staking transaction by hash |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
tx_hash: str |
||||||
|
Hash of staking transaction to fetch |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
dict |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
tx_hash |
||||||
|
] |
||||||
|
return rpc_request('hmy_getStakingTransactionByHash', params=params, endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def get_staking_transaction_by_block_hash_and_index(block_hash, tx_index, |
||||||
|
endpoint=_default_endpoint, timeout=_default_timeout |
||||||
|
) -> dict: |
||||||
|
""" |
||||||
|
Get staking transaction based on index in list of staking transactions for a block by block hash |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
block_hash: str |
||||||
|
Block hash for staking transaction |
||||||
|
tx_index: int |
||||||
|
Staking transaction index to fetch |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
dict |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
block_hash, |
||||||
|
str(hex(tx_index)) |
||||||
|
] |
||||||
|
return rpc_request('hmy_getStakingTransactionByBlockHashAndIndex', params=params, endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def get_staking_transaction_by_block_number_and_index(block_num, tx_index, |
||||||
|
endpoint=_default_endpoint, timeout=_default_timeout |
||||||
|
) -> dict: |
||||||
|
""" |
||||||
|
Get staking transaction based on index in list of staking transactions for a block by block number |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
block_num: int |
||||||
|
Block number for staking transaction |
||||||
|
tx_index: int |
||||||
|
Staking transaction index to fetch |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
dict |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
str(hex(block_num)), |
||||||
|
str(hex(tx_index)) |
||||||
|
] |
||||||
|
return rpc_request('hmy_getStakingTransactionByBlockNumberAndIndex', params=params, endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def get_staking_transaction_error_sink(endpoint=_default_endpoint, timeout=_default_timeout) -> list: |
||||||
|
""" |
||||||
|
Get staking transaction error sink |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
endpoint: :obj:`str`, optional |
||||||
|
Endpoint to send request to |
||||||
|
timeout: :obj:`int`, optional |
||||||
|
Timeout in seconds |
||||||
|
|
||||||
|
Returns |
||||||
|
------- |
||||||
|
list |
||||||
|
# TODO: Add link to reference RPC documentation |
||||||
|
""" |
||||||
|
return rpc_request('hmy_getCurrentStakingErrorSink', endpoint=endpoint, timeout=timeout)['result'] |
||||||
|
|
||||||
|
|
||||||
|
def send_raw_staking_transaction(raw_tx, endpoint=_default_endpoint, timeout=_default_timeout) -> str: |
||||||
|
""" |
||||||
|
Send signed staking transaction |
||||||
|
|
||||||
|
Parameters |
||||||
|
---------- |
||||||
|
raw_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 |
||||||
|
Staking transaction hash |
||||||
|
""" |
||||||
|
params = [ |
||||||
|
raw_tx |
||||||
|
] |
||||||
|
return rpc_request('hmy_sendRawStakingTransaction', params=params, endpoint=endpoint, timeout=timeout)['result'] |
@ -0,0 +1,36 @@ |
|||||||
|
from decimal import Decimal |
||||||
|
|
||||||
|
import pytest |
||||||
|
|
||||||
|
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 |
||||||
|
|
||||||
|
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 |
||||||
|
|
||||||
|
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 |
||||||
|
|
||||||
|
b = numbers.convert_one_to_atto(1.5) |
||||||
|
assert Decimal(1.5e18) == b |
||||||
|
|
||||||
|
c = numbers.convert_one_to_atto('1') |
||||||
|
assert Decimal(1e18) == c |
||||||
|
|
||||||
|
d = numbers.convert_one_to_atto(Decimal(1)) |
||||||
|
assert Decimal(1e18) == d |
@ -0,0 +1,105 @@ |
|||||||
|
import json |
||||||
|
import socket |
||||||
|
|
||||||
|
import pytest |
||||||
|
import requests |
||||||
|
|
||||||
|
from pyhmy.rpc import ( |
||||||
|
exceptions, |
||||||
|
request |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session", autouse=True) |
||||||
|
def setup(): |
||||||
|
endpoint = 'http://localhost:9500' |
||||||
|
timeout = 30 |
||||||
|
method = 'hmy_getNodeMetadata' |
||||||
|
params = [] |
||||||
|
payload = { |
||||||
|
"id": "1", |
||||||
|
"jsonrpc": "2.0", |
||||||
|
"method": method, |
||||||
|
"params": params |
||||||
|
} |
||||||
|
headers = { |
||||||
|
'Content-Type': 'application/json' |
||||||
|
} |
||||||
|
|
||||||
|
try: |
||||||
|
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.mark.run(order=1) |
||||||
|
def test_request_connection_error(): |
||||||
|
# Find available port |
||||||
|
s = socket.socket() |
||||||
|
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}' |
||||||
|
bad_request = None |
||||||
|
try: |
||||||
|
bad_request = request.rpc_request('hmy_getNodeMetadata', endpoint=bad_endpoint) |
||||||
|
except Exception as e: |
||||||
|
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('hmy_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 error_request is None |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.run(order=3) |
||||||
|
def test_rpc_request(): |
||||||
|
endpoint = 'http://localhost:9500' |
||||||
|
timeout = 30 |
||||||
|
method = 'hmy_getNodeMetadata' |
||||||
|
params = [] |
||||||
|
payload = { |
||||||
|
"id": "1", |
||||||
|
"jsonrpc": "2.0", |
||||||
|
"method": method, |
||||||
|
"params": params |
||||||
|
} |
||||||
|
headers = { |
||||||
|
'Content-Type': 'application/json' |
||||||
|
} |
||||||
|
|
||||||
|
response = None |
||||||
|
try: |
||||||
|
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") |
||||||
|
assert response is not None |
||||||
|
|
||||||
|
resp = None |
||||||
|
try: |
||||||
|
resp = json.loads(response.content) |
||||||
|
except json.decoder.JSONDecodeError as err: |
||||||
|
pytest.skip('unable to decode response') |
||||||
|
assert resp is not None |
||||||
|
|
||||||
|
rpc_response = None |
||||||
|
try: |
||||||
|
rpc_response = request.rpc_request(method, params, endpoint, timeout) |
||||||
|
except exceptions.RPCError as e: |
||||||
|
assert 'error' in resp |
||||||
|
|
||||||
|
if rpc_response is not None: |
||||||
|
assert rpc_response == resp |
@ -0,0 +1,154 @@ |
|||||||
|
import json |
||||||
|
import time |
||||||
|
|
||||||
|
import pytest |
||||||
|
import requests |
||||||
|
|
||||||
|
test_validator_address = 'one18tvf56zqjkjnak686lwutcp5mqfnvee35xjnhc' |
||||||
|
transfer_raw_transaction = '0xf86f80843b9aca008252080180943ad89a684095a53edb47d7ddc5e034d8133667318a152d02c7e14af68000008027a0ec6c8ad0f70b3c826fa77574c6815a8f73936fafb7b2701a7082ad7d278c95a9a0429f9f166b1c1d385a4ec8f8b86604c26e427c2b0a1c85d9cf4ec6bbd0719508' |
||||||
|
tx_hash = '0x1fa20537ea97f162279743139197ecf0eac863278ac1c8ada9a6be5d1e31e633' |
||||||
|
create_validator_raw_transaction = '0xf9015680f90105943ad89a684095a53edb47d7ddc5e034d813366731d984746573748474657374847465737484746573748474657374ddc988016345785d8a0000c9880c7d713b49da0000c887b1a2bc2ec500008a022385a827e8155000008b084595161401484a000000f1b0282554f2478661b4844a05a9deb1837aac83931029cb282872f0dcd7239297c499c02ea8da8746d2f08ca2b037e89891f862b86003557e18435c201ecc10b1664d1aea5b4ec59dbfe237233b953dbd9021b86bc9770e116ed3c413fe0334d89562568a10e133d828611f29fee8cdab9719919bbcc1f1bf812c73b9ccd0f89b4f0b9ca7e27e66d58bbb06fcf51c295b1d076cfc878a0228f16f86157860000080843b9aca008351220027a018385211a150ca032c3526cef0aba6a75f99a18cb73f547f67bab746be0c7a64a028be921002c6eb949b3932afd010dfe1de2459ec7fe84403b9d9d8892394a78c' |
||||||
|
staking_tx_hash = '0x57ec011aabdeb078a4816502224022f291fa8b07c82bbae8476f514a1d71c730' |
||||||
|
|
||||||
|
endpoint = 'http://localhost:9500' |
||||||
|
endpoint_shard_one = 'http://localhost:9501' |
||||||
|
timeout = 30 |
||||||
|
headers = { |
||||||
|
'Content-Type': 'application/json' |
||||||
|
} |
||||||
|
|
||||||
|
@pytest.fixture(scope="session", autouse=True) |
||||||
|
def setup_blockchain(): |
||||||
|
|
||||||
|
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 hmy_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 hmy_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) |
||||||
|
|
||||||
|
|
||||||
|
def _check_connection(): |
||||||
|
try: |
||||||
|
payload = { |
||||||
|
"id": "1", |
||||||
|
"jsonrpc": "2.0", |
||||||
|
"method": 'hmy_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 hmy_getNodeMetadata reply: {metadata['error']}", allow_module_level=True) |
||||||
|
if 'chain-config' not in metadata['result']: |
||||||
|
pytest.skip("Chain config not found in hmy_getNodeMetadata reply", allow_module_level=True) |
||||||
|
return metadata |
||||||
|
except Exception as e: |
||||||
|
pytest.skip('Can not connect to local blockchain or bad hmy_getNodeMetadata reply', allow_module_level=True) |
||||||
|
|
||||||
|
def _check_staking_epoch(metadata): |
||||||
|
latest_header = None |
||||||
|
try: |
||||||
|
payload = { |
||||||
|
"id": "1", |
||||||
|
"jsonrpc": "2.0", |
||||||
|
"method": 'hmy_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 hmy_latestHeader reply: {latest_header['error']}", allow_module_level=True) |
||||||
|
except Exception as e: |
||||||
|
pytest.skip('Failed to get hmy_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'] |
||||||
|
if staking_epoch > current_epoch: |
||||||
|
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": 'hmy_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 hmy_sendRawTransaction reply: {tx['error']}", allow_module_level=True) |
||||||
|
except Exception as e: |
||||||
|
pytest.skip('Failed to get hmy_sendRawTransaction reply', allow_module_level=True) |
||||||
|
|
||||||
|
def _check_funding_transaction(): |
||||||
|
try: |
||||||
|
payload = { |
||||||
|
"id": "1", |
||||||
|
"jsonrpc": "2.0", |
||||||
|
"method": 'hmy_getTransactionByHash', |
||||||
|
"params": [tx_hash] |
||||||
|
} |
||||||
|
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 |
||||||
|
except Exception as e: |
||||||
|
pytest.skip('Failed to get hmy_getTransactionByHash reply', allow_module_level=True) |
||||||
|
|
||||||
|
def _send_staking_transaction(): |
||||||
|
try: |
||||||
|
payload = { |
||||||
|
"id": "1", |
||||||
|
"jsonrpc": "2.0", |
||||||
|
"method": 'hmy_sendRawStakingTransaction', |
||||||
|
"params": [create_validator_raw_transaction] |
||||||
|
} |
||||||
|
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 hmy_sendRawStakingTransaction reply: {staking_tx['error']}", allow_module_level=True) |
||||||
|
except Exception as e: |
||||||
|
pytest.skip('Failed to get hmy_sendRawStakingTransaction reply', allow_module_level=True) |
||||||
|
|
||||||
|
def _check_staking_transaction(): |
||||||
|
try: |
||||||
|
payload = { |
||||||
|
"id": "1", |
||||||
|
"jsonrpc": "2.0", |
||||||
|
"method": 'hmy_getStakingTransactionByHash', |
||||||
|
"params": [staking_tx_hash] |
||||||
|
} |
||||||
|
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 hmy_getStakingTransactionByHash reply', allow_module_level=True) |
@ -0,0 +1,56 @@ |
|||||||
|
import pytest |
||||||
|
import requests |
||||||
|
|
||||||
|
from pyhmy import ( |
||||||
|
account |
||||||
|
) |
||||||
|
|
||||||
|
from pyhmy.rpc import ( |
||||||
|
exceptions |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
explorer_endpoint = 'http://localhost:9599' |
||||||
|
endpoint_shard_one = 'http://localhost:9501' |
||||||
|
local_test_address = 'one1zksj3evekayy90xt4psrz8h6j2v3hla4qwz4ur' |
||||||
|
test_validator_address = 'one18tvf56zqjkjnak686lwutcp5mqfnvee35xjnhc' |
||||||
|
genesis_block_number = 0 |
||||||
|
test_block_number = 1 |
||||||
|
|
||||||
|
def _test_account_rpc(fn, *args, **kwargs): |
||||||
|
if not callable(fn): |
||||||
|
pytest.fail(f'Invalid function: {fn}') |
||||||
|
|
||||||
|
try: |
||||||
|
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}') |
||||||
|
return response |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.run(order=1) |
||||||
|
def test_get_balance(setup_blockchain): |
||||||
|
balance = _test_account_rpc(account.get_balance, local_test_address) |
||||||
|
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 balance > 0 |
||||||
|
|
||||||
|
@pytest.mark.run(order=3) |
||||||
|
def test_get_transaction_count(setup_blockchain): |
||||||
|
transactions = _test_account_rpc(account.get_transaction_count, local_test_address, endpoint=endpoint_shard_one) |
||||||
|
assert transactions > 0 |
||||||
|
|
||||||
|
@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 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 len(staking_tx_history) > 0 |
@ -0,0 +1,98 @@ |
|||||||
|
import pytest |
||||||
|
import requests |
||||||
|
|
||||||
|
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 |
||||||
|
|
||||||
|
def _test_blockchain_rpc(fn, *args, **kwargs): |
||||||
|
if not callable(fn): |
||||||
|
pytest.fail(f'Invalid function: {fn}') |
||||||
|
|
||||||
|
try: |
||||||
|
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}') |
||||||
|
return response |
||||||
|
|
||||||
|
@pytest.mark.run(order=1) |
||||||
|
def test_get_node_metadata(setup_blockchain): |
||||||
|
_test_blockchain_rpc(blockchain.get_node_metadata) |
||||||
|
|
||||||
|
@pytest.mark.run(order=2) |
||||||
|
def test_get_sharding_structure(setup_blockchain): |
||||||
|
_test_blockchain_rpc(blockchain.get_sharding_structure) |
||||||
|
|
||||||
|
@pytest.mark.run(order=3) |
||||||
|
def test_get_leader_address(setup_blockchain): |
||||||
|
_test_blockchain_rpc(blockchain.get_leader_address) |
||||||
|
|
||||||
|
@pytest.mark.run(order=4) |
||||||
|
def test_get_block_number(setup_blockchain): |
||||||
|
_test_blockchain_rpc(blockchain.get_block_number) |
||||||
|
|
||||||
|
@pytest.mark.run(order=5) |
||||||
|
def test_get_current_epoch(setup_blockchain): |
||||||
|
_test_blockchain_rpc(blockchain.get_current_epoch) |
||||||
|
|
||||||
|
@pytest.mark.run(order=6) |
||||||
|
def tset_get_gas_price(setup_blockchain): |
||||||
|
_test_blockchain_rpc(blockchain.get_gas_price) |
||||||
|
|
||||||
|
@pytest.mark.run(order=7) |
||||||
|
def test_get_num_peers(setup_blockchain): |
||||||
|
_test_blockchain_rpc(blockchain.get_num_peers) |
||||||
|
|
||||||
|
@pytest.mark.run(order=8) |
||||||
|
def test_get_latest_header(setup_blockchain): |
||||||
|
_test_blockchain_rpc(blockchain.get_latest_header) |
||||||
|
|
||||||
|
@pytest.mark.run(order=9) |
||||||
|
def test_get_latest_headers(setup_blockchain): |
||||||
|
_test_blockchain_rpc(blockchain.get_latest_headers) |
||||||
|
|
||||||
|
@pytest.mark.run(order=10) |
||||||
|
def test_get_block_by_number(setup_blockchain): |
||||||
|
global test_block_hash |
||||||
|
block = _test_blockchain_rpc(blockchain.get_block_by_number, test_block_number) |
||||||
|
test_block_hash = block['hash'] |
||||||
|
|
||||||
|
@pytest.mark.run(order=11) |
||||||
|
def test_get_block_by_hash(setup_blockchain): |
||||||
|
if not test_block_hash: |
||||||
|
pytest.skip('Failed to get reference block hash') |
||||||
|
_test_blockchain_rpc(blockchain.get_block_by_hash, test_block_hash) |
||||||
|
|
||||||
|
@pytest.mark.run(order=12) |
||||||
|
def test_get_block_transaction_count_by_number(setup_blockchain): |
||||||
|
_test_blockchain_rpc(blockchain.get_block_transaction_count_by_number, test_block_number) |
||||||
|
|
||||||
|
@pytest.mark.run(order=13) |
||||||
|
def test_get_block_transaction_count_by_hash(setup_blockchain): |
||||||
|
if not test_block_hash: |
||||||
|
pytest.skip('Failed to get reference block hash') |
||||||
|
_test_blockchain_rpc(blockchain.get_block_transaction_count_by_hash, test_block_hash) |
||||||
|
|
||||||
|
@pytest.mark.run(order=14) |
||||||
|
def test_get_blocks(setup_blockchain): |
||||||
|
_test_blockchain_rpc(blockchain.get_blocks, genesis_block_number, test_block_number) |
||||||
|
|
||||||
|
@pytest.mark.run(order=15) |
||||||
|
def test_get_block_signers(setup_blockchain): |
||||||
|
_test_blockchain_rpc(blockchain.get_block_signers, test_block_number) |
||||||
|
|
||||||
|
@pytest.mark.run(order=16) |
||||||
|
def test_get_validators(setup_blockchain): |
||||||
|
_test_blockchain_rpc(blockchain.get_validators, test_epoch_number) |
@ -0,0 +1,66 @@ |
|||||||
|
import pytest |
||||||
|
import requests |
||||||
|
|
||||||
|
from pyhmy import ( |
||||||
|
staking |
||||||
|
) |
||||||
|
|
||||||
|
from pyhmy.rpc import ( |
||||||
|
exceptions |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
test_validator_address = 'one18tvf56zqjkjnak686lwutcp5mqfnvee35xjnhc' |
||||||
|
|
||||||
|
def _test_staking_rpc(fn, *args, **kwargs): |
||||||
|
if not callable(fn): |
||||||
|
pytest.fail(f'Invalid function: {fn}') |
||||||
|
|
||||||
|
try: |
||||||
|
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}') |
||||||
|
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 len(validator_addresses) > 0 |
||||||
|
assert test_validator_address in validator_addresses |
||||||
|
|
||||||
|
@pytest.mark.run(order=2) |
||||||
|
def test_get_validator_information(setup_blockchain): |
||||||
|
_test_staking_rpc(staking.get_validator_information, test_validator_address) |
||||||
|
|
||||||
|
@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 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 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 len(delegations) > 0 |
||||||
|
|
||||||
|
@pytest.mark.run(order=6) |
||||||
|
def test_get_current_utility_metrics(setup_blockchain): |
||||||
|
_test_staking_rpc(staking.get_current_utility_metrics) |
||||||
|
|
||||||
|
@pytest.mark.run(order=7) |
||||||
|
def test_get_staking_network_info(setup_blockchain): |
||||||
|
_test_staking_rpc(staking.get_staking_network_info) |
||||||
|
|
||||||
|
@pytest.mark.run(order=8) |
||||||
|
def test_get_super_committees(setup_blockchain): |
||||||
|
_test_staking_rpc(staking.get_super_committees) |
||||||
|
|
||||||
|
@pytest.mark.run(order=9) |
||||||
|
def test_get_raw_median_stake_snapshot(setup_blockchain): |
||||||
|
_test_staking_rpc(staking.get_raw_median_stake_snapshot) |
@ -0,0 +1,125 @@ |
|||||||
|
import pytest |
||||||
|
import requests |
||||||
|
|
||||||
|
from pyhmy import ( |
||||||
|
transaction |
||||||
|
) |
||||||
|
|
||||||
|
from pyhmy.rpc import ( |
||||||
|
exceptions |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
localhost_shard_one = 'http://localhost:9501' |
||||||
|
tx_hash = '0x1fa20537ea97f162279743139197ecf0eac863278ac1c8ada9a6be5d1e31e633' |
||||||
|
tx_block_num = None |
||||||
|
tx_block_hash = None |
||||||
|
cx_hash = '0x1fa20537ea97f162279743139197ecf0eac863278ac1c8ada9a6be5d1e31e633' |
||||||
|
stx_hash = '0x57ec011aabdeb078a4816502224022f291fa8b07c82bbae8476f514a1d71c730' |
||||||
|
stx_block_num = None |
||||||
|
stx_block_hash = None |
||||||
|
test_index = 0 |
||||||
|
|
||||||
|
raw_tx = '0xf86f80843b9aca008252080180943ad89a684095a53edb47d7ddc5e034d8133667318a152d02c7e14af68000008027a0ec6c8ad0f70b3c826fa77574c6815a8f73936fafb7b2701a7082ad7d278c95a9a0429f9f166b1c1d385a4ec8f8b86604c26e427c2b0a1c85d9cf4ec6bbd0719508' |
||||||
|
raw_stx = '0xf9015680f90105943ad89a684095a53edb47d7ddc5e034d813366731d984746573748474657374847465737484746573748474657374ddc988016345785d8a0000c9880c7d713b49da0000c887b1a2bc2ec500008a022385a827e8155000008b084595161401484a000000f1b0282554f2478661b4844a05a9deb1837aac83931029cb282872f0dcd7239297c499c02ea8da8746d2f08ca2b037e89891f862b86003557e18435c201ecc10b1664d1aea5b4ec59dbfe237233b953dbd9021b86bc9770e116ed3c413fe0334d89562568a10e133d828611f29fee8cdab9719919bbcc1f1bf812c73b9ccd0f89b4f0b9ca7e27e66d58bbb06fcf51c295b1d076cfc878a0228f16f86157860000080843b9aca008351220027a018385211a150ca032c3526cef0aba6a75f99a18cb73f547f67bab746be0c7a64a028be921002c6eb949b3932afd010dfe1de2459ec7fe84403b9d9d8892394a78c' |
||||||
|
|
||||||
|
def _test_transaction_rpc(fn, *args, **kwargs): |
||||||
|
if not callable(fn): |
||||||
|
pytest.fail(f'Invalid function: {fn}') |
||||||
|
|
||||||
|
try: |
||||||
|
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}') |
||||||
|
return response |
||||||
|
|
||||||
|
@pytest.mark.run(order=1) |
||||||
|
def test_get_pending_transactions(setup_blockchain): |
||||||
|
_test_transaction_rpc(transaction.get_pending_transactions) |
||||||
|
|
||||||
|
@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) |
||||||
|
assert tx is not None |
||||||
|
global tx_block_num |
||||||
|
tx_block_num = int(tx['blockNumber'], 0) |
||||||
|
global tx_block_hash |
||||||
|
tx_block_hash = tx['blockHash'] |
||||||
|
|
||||||
|
@pytest.mark.run(order=3) |
||||||
|
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) |
||||||
|
assert tx is not None |
||||||
|
|
||||||
|
@pytest.mark.run(order=4) |
||||||
|
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) |
||||||
|
assert tx is not None |
||||||
|
|
||||||
|
@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) |
||||||
|
assert tx_receipt is not None |
||||||
|
|
||||||
|
@pytest.mark.run(order=6) |
||||||
|
def test_get_transaction_error_sink(setup_blockchain): |
||||||
|
_test_transaction_rpc(transaction.get_transaction_error_sink) |
||||||
|
|
||||||
|
@pytest.mark.run(order=7) |
||||||
|
def test_send_raw_transaction(setup_blockchain): |
||||||
|
test_tx_hash = _test_transaction_rpc(transaction.send_raw_transaction, raw_tx) |
||||||
|
assert test_tx_hash == tx_hash |
||||||
|
|
||||||
|
@pytest.mark.run(order=8) |
||||||
|
def test_get_pending_cx_receipts(setup_blockchain): |
||||||
|
_test_transaction_rpc(transaction.get_pending_cx_receipts) |
||||||
|
|
||||||
|
@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) |
||||||
|
assert cx is not None |
||||||
|
|
||||||
|
@pytest.mark.run(order=10) |
||||||
|
def test_resend_cx_receipt(setup_blockchain): |
||||||
|
sent = _test_transaction_rpc(transaction.resend_cx_receipt, cx_hash) |
||||||
|
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) |
||||||
|
assert staking_tx is not None |
||||||
|
global stx_block_num |
||||||
|
stx_block_num = int(staking_tx['blockNumber'], 0) |
||||||
|
global stx_block_hash |
||||||
|
stx_block_hash = staking_tx['blockHash'] |
||||||
|
|
||||||
|
@pytest.mark.run(order=12) |
||||||
|
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) |
||||||
|
assert stx is not None |
||||||
|
|
||||||
|
@pytest.mark.run(order=13) |
||||||
|
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) |
||||||
|
assert stx is not None |
||||||
|
|
||||||
|
@pytest.mark.run(order=14) |
||||||
|
def test_get_staking_transaction_error_sink(setup_blockchain): |
||||||
|
_test_transaction_rpc(transaction.get_staking_transaction_error_sink) |
||||||
|
|
||||||
|
@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) |
||||||
|
assert test_stx_hash == stx_hash |
Loading…
Reference in new issue