|
|
|
"""
|
|
|
|
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
|
|
|
|
|
|
|
|
from eth_utils import to_checksum_address
|
|
|
|
|
|
|
|
from .blockchain import get_latest_header
|
|
|
|
|
|
|
|
from .rpc.exceptions import ( RPCError, RequestsError, RequestsTimeoutError, )
|
|
|
|
|
|
|
|
from .account import is_valid_address
|
|
|
|
|
|
|
|
from .bech32.bech32 import bech32_decode, bech32_encode, convertbits
|
|
|
|
|
|
|
|
|
|
|
|
class Typgpy( str ):
|
|
|
|
"""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 ):
|
|
|
|
"""
|
|
|
|
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,
|
|
|
|
Ropsten = 3,
|
|
|
|
Rinkeby = 4,
|
|
|
|
RootstockMainnet = 30,
|
|
|
|
RootstockTestnet = 31,
|
|
|
|
Kovan = 42,
|
|
|
|
EtcMainnet = 61,
|
|
|
|
EtcTestnet = 62,
|
|
|
|
Geth = 1337,
|
|
|
|
Ganache = 0,
|
|
|
|
HmyMainnet = 1,
|
|
|
|
HmyTestnet = 2,
|
|
|
|
HmyLocal = 2,
|
|
|
|
HmyPangaea = 3,
|
|
|
|
)
|
|
|
|
|
|
|
|
# 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()
|
|
|
|
|
|
|
|
|
|
|
|
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 )
|
|
|
|
_, 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 ):
|
|
|
|
"""
|
|
|
|
:param endpoint: The endpoint of the SHARD to check
|
|
|
|
:param delay_tolerance: The time (in seconds) that the shard timestamp can be behind
|
|
|
|
:return: If shard is active or not
|
|
|
|
"""
|
|
|
|
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 )
|
|
|
|
time_delta = curr_time - timestamp
|
|
|
|
return abs( time_delta.seconds ) < delay_tolerance
|
|
|
|
except ( RPCError, RequestsError, RequestsTimeoutError ):
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def get_bls_build_variables():
|
|
|
|
"""
|
|
|
|
:returns The environment variables needed to build and/or run programs that
|
|
|
|
use the Harmony BLS & MCL repo.
|
|
|
|
:raises RuntimeError if openssl is not found.
|
|
|
|
|
|
|
|
Note that this assumes that the BLS & MCL repo are in the appropriate directory
|
|
|
|
as stated here: https://github.com/harmony-one/harmony/blob/master/README.md
|
|
|
|
"""
|
|
|
|
variables = {}
|
|
|
|
try:
|
|
|
|
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"
|
|
|
|
]
|
|
|
|
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"
|
|
|
|
return variables
|
|
|
|
|
|
|
|
|
|
|
|
def json_load( string, **kwargs ):
|
|
|
|
"""
|
|
|
|
:param string: The JSON string to load
|
|
|
|
:returns A dictionary loaded from a JSON string to a dictionary.
|
|
|
|
:raises The exception caused by the load (if present).
|
|
|
|
|
|
|
|
Note that this prints the failed input should an error arise.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
return json.loads( string, **kwargs )
|
|
|
|
except Exception as exception:
|
|
|
|
print( f"{Typgpy.FAIL}Could not parse input: '{string}'{Typgpy.ENDC}" )
|
|
|
|
raise exception
|