Compare commits
31 Commits
lash/chain
...
lash/more-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68a01565c9
|
||
|
|
b5a01b28ac
|
||
|
|
926c954d6e
|
||
|
|
d24fea2d4f
|
||
|
|
d3f6d1138d
|
||
|
|
2c567cbded
|
||
|
|
28ee4f7197
|
||
|
|
eedb63212d
|
||
|
|
99cac32ced
|
||
| 7deeee2c84 | |||
|
|
6a94208c68
|
||
|
|
becd6744f6
|
||
|
|
06c6b2562a
|
||
|
|
1067c8494e
|
||
|
|
39478d9c3c
|
||
|
|
24d7c078e8
|
||
|
|
70d8ab6a33
|
||
|
|
d914f64797
|
||
|
|
5bfdb51676
|
||
|
|
7d7209dd31
|
||
|
|
1373cb10e6
|
||
|
|
64dbcfcddf
|
||
|
|
56f3cffbbf | ||
|
|
a71ec59fb2 | ||
|
|
095c0810f3 | ||
|
|
3e6d0c6912
|
||
|
|
52e748a02e
|
||
|
|
5518bf83a3
|
||
|
|
d2505f19d2
|
||
|
|
e073a3c57a | ||
|
|
f13b24798e |
22
CHANGELOG
22
CHANGELOG
@@ -1,14 +1,26 @@
|
||||
- 0.0.5-pending
|
||||
- 0.0.20
|
||||
* Add edit option for some long flag aliases of short flags: -a, -e, -s and -y
|
||||
- 0.0.19
|
||||
* Passphrase file option to unlock keyfile for CLI tooling
|
||||
- 0.0.18
|
||||
* Upgrade hexathon skipping buggy compact hex method
|
||||
- 0.0.17
|
||||
* Add loglevel environment variable
|
||||
- 0.0.16
|
||||
* Allow query string in query url
|
||||
- 0.0.14
|
||||
* Add option to skip ssl verification on rpc
|
||||
- 0.0.5
|
||||
* Move eth code to separate package
|
||||
- 0.0.4-unreleased
|
||||
- 0.0.4
|
||||
* Add pack tx from already signed tx struct
|
||||
* Add http auth handling for jsonrpc connections
|
||||
* Add customizable jsonrpc id generator (to allow for buggy server id handling)
|
||||
- 0.0.3-unreleased
|
||||
- 0.0.3
|
||||
* Remove erc20 module (to new external package)
|
||||
- 0.0.2-unreleased
|
||||
- 0.0.2
|
||||
*
|
||||
- 0.0.1-unreleased
|
||||
- 0.0.1
|
||||
* Add eth tx decode
|
||||
* Add eth balance query with erc20 option
|
||||
* Add eth checksum address
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# standard imports
|
||||
import copy
|
||||
import re
|
||||
|
||||
|
||||
def is_valid_label(v, alpha_only=False):
|
||||
re_m = None
|
||||
if alpha_only:
|
||||
re_m = r'^[a-zA-Z]+$'
|
||||
else:
|
||||
re_m = r'^[a-zA-Z0-9]+$'
|
||||
return re.match(re_m, v)
|
||||
|
||||
|
||||
class ChainSpec:
|
||||
@@ -16,13 +26,37 @@ class ChainSpec:
|
||||
:param tag: Descriptive tag
|
||||
:type tag: str
|
||||
"""
|
||||
def __init__(self, arch, fork, network_id, common_name=None):
|
||||
def __init__(self, arch, fork, network_id, common_name=None, custom=[], safe=True):
|
||||
if custom == None:
|
||||
custom = []
|
||||
elif not isinstance(custom, list):
|
||||
raise ValueError('custom value must be list')
|
||||
|
||||
self.o = {
|
||||
'arch': arch,
|
||||
'fork': fork,
|
||||
'network_id': network_id,
|
||||
'common_name': common_name,
|
||||
}
|
||||
'arch': arch,
|
||||
'fork': fork,
|
||||
'network_id': network_id,
|
||||
'common_name': common_name,
|
||||
'custom': custom,
|
||||
}
|
||||
|
||||
if safe:
|
||||
self.validate()
|
||||
|
||||
|
||||
def validate(self):
|
||||
self.o['network_id'] = int(self.o['network_id'])
|
||||
if not is_valid_label(self.o['arch'], alpha_only=True):
|
||||
raise ValueError('arch: ' + self.o['arch'])
|
||||
if not is_valid_label(self.o['fork'], alpha_only=True):
|
||||
raise ValueError('fork: ' + self.o['fork'])
|
||||
if self.o.get('common_name') and not is_valid_label(self.o['common_name']):
|
||||
raise ValueError('common_name: ' + self.o['common_name'])
|
||||
if self.o.get('custom'):
|
||||
for i, v in enumerate(self.o['custom']):
|
||||
if not is_valid_label(v):
|
||||
raise ValueError('common_name {}: {}'.format(i, v))
|
||||
|
||||
|
||||
def network_id(self):
|
||||
"""Returns the network id part of the spec.
|
||||
@@ -43,6 +77,12 @@ class ChainSpec:
|
||||
|
||||
|
||||
def engine(self):
|
||||
"""Alias of self.arch()
|
||||
"""
|
||||
return self.arch()
|
||||
|
||||
|
||||
def arch(self):
|
||||
"""Returns the chain architecture part of the spec
|
||||
|
||||
:rtype: str
|
||||
@@ -51,6 +91,15 @@ class ChainSpec:
|
||||
return self.o['arch']
|
||||
|
||||
|
||||
def fork(self):
|
||||
"""Returns the fork part of the spec
|
||||
|
||||
:rtype: str
|
||||
:returns: fork
|
||||
"""
|
||||
return self.o['fork']
|
||||
|
||||
|
||||
def common_name(self):
|
||||
"""Returns the common name part of the spec
|
||||
|
||||
@@ -60,6 +109,21 @@ class ChainSpec:
|
||||
return self.o['common_name']
|
||||
|
||||
|
||||
def is_same_as(self, chain_spec_cmp, use_common_name=False, use_custom=False):
|
||||
a = ['arch', 'fork', 'network_id']
|
||||
if use_common_name:
|
||||
a += ['common_name']
|
||||
if use_custom:
|
||||
a += ['custom']
|
||||
try:
|
||||
for k in a:
|
||||
assert(chain_spec_cmp.o[k] == self.o[k])
|
||||
except AssertionError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
def from_chain_str(chain_str):
|
||||
"""Create a new ChainSpec object from a colon-separated string, as output by the string representation of the ChainSpec object.
|
||||
@@ -79,9 +143,13 @@ class ChainSpec:
|
||||
if len(o) < 3:
|
||||
raise ValueError('Chain string must have three sections, got {}'.format(len(o)))
|
||||
common_name = None
|
||||
if len(o) == 4:
|
||||
if len(o) > 3:
|
||||
common_name = o[3]
|
||||
return ChainSpec(o[0], o[1], int(o[2]), common_name)
|
||||
custom = []
|
||||
if len(o) > 4:
|
||||
for i in range(4, len(o)):
|
||||
custom.append(o[i])
|
||||
return ChainSpec(o[0], o[1], int(o[2]), common_name=common_name, custom=custom)
|
||||
|
||||
|
||||
@staticmethod
|
||||
@@ -100,20 +168,35 @@ class ChainSpec:
|
||||
:rtype: chainlib.chain.ChainSpec
|
||||
:returns: Resulting chain spec
|
||||
"""
|
||||
return ChainSpec(o['arch'], o['fork'], o['network_id'], common_name=o['common_name'])
|
||||
return ChainSpec(o['arch'], o['fork'], o['network_id'], common_name=o.get('common_name'), custom=o.get('custom'))
|
||||
|
||||
|
||||
def asdict(self):
|
||||
def asdict(self, use_common_name=True, use_custom=True):
|
||||
"""Create a dictionary representation of the chain spec.
|
||||
|
||||
:rtype: dict
|
||||
:returns: Chain spec dictionary
|
||||
"""
|
||||
return copy.copy(self.o)
|
||||
r = copy.copy(self.o)
|
||||
if not use_common_name:
|
||||
del r['common_name']
|
||||
del r['custom']
|
||||
if not use_custom:
|
||||
del r['custom']
|
||||
return r
|
||||
|
||||
|
||||
def as_string(self, skip_optional=False):
|
||||
s = '{}:{}:{}'.format(self.o['arch'], self.o['fork'], self.o['network_id'])
|
||||
if skip_optional:
|
||||
return s
|
||||
|
||||
if self.o.get('common_name'):
|
||||
s += ':' + self.o['common_name']
|
||||
if self.o.get('custom'):
|
||||
s += ':' + ':'.join(self.o['custom'])
|
||||
return s
|
||||
|
||||
|
||||
def __str__(self):
|
||||
s = '{}:{}:{}'.format(self.o['arch'], self.o['fork'], self.o['network_id'])
|
||||
if self.o['common_name'] != None:
|
||||
s += ':' + self.o['common_name']
|
||||
return s
|
||||
return self.as_string()
|
||||
|
||||
@@ -5,6 +5,7 @@ import enum
|
||||
import os
|
||||
import select
|
||||
import sys
|
||||
import re
|
||||
|
||||
# local imports
|
||||
from .base import (
|
||||
@@ -30,16 +31,31 @@ def stdin_arg():
|
||||
return v.rstrip()
|
||||
return None
|
||||
|
||||
_default_long_args = {
|
||||
'-a': '--recipient',
|
||||
'-e': '--executable-address',
|
||||
'-s': '--send',
|
||||
'-y': '--key-file',
|
||||
}
|
||||
|
||||
_default_dest = {
|
||||
'-a': 'recipient',
|
||||
'-e': 'executable_address',
|
||||
}
|
||||
|
||||
class ArgumentParser(argparse.ArgumentParser):
|
||||
"""Extends the standard library argument parser to construct arguments based on configuration flags.
|
||||
|
||||
The extended class is set up to facilitate piping of single positional arguments via stdin. For this reason, positional arguments should be added using the locally defined add_positional method instead of add_argument.
|
||||
|
||||
Long flag aliases for short flags are editable using the arg_long argument. Editing a non-existent short flag will produce no error and have no effect. Adding a long flag for a short flag that does not have an alias will also not have effect.
|
||||
|
||||
Calls chainlib.cli.args.ArgumentParser.process_flags with arg_flags and env arguments, see the method's documentation for further details.
|
||||
|
||||
:param arg_flags: Argument flag bit vector to generate configuration values for.
|
||||
:type arg_flags: chainlib.cli.Flag
|
||||
:param arg_long: Change long flag alias for given short flags. Example value: {'-a': '--addr', '-e': '--contract'}
|
||||
:type arg_long: dict
|
||||
:param env: Environment variables
|
||||
:type env: dict
|
||||
:param usage: Usage string, passed to parent
|
||||
@@ -50,14 +66,26 @@ class ArgumentParser(argparse.ArgumentParser):
|
||||
:type epilog: str
|
||||
"""
|
||||
|
||||
def __init__(self, arg_flags=0x0f, env=os.environ, usage=None, description=None, epilog=None, *args, **kwargs):
|
||||
def __init__(self, arg_flags=0x0f, arg_long={}, env=os.environ, usage=None, description=None, epilog=None, *args, **kwargs):
|
||||
super(ArgumentParser, self).__init__(usage=usage, description=description, epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter, *args, **kwargs)
|
||||
|
||||
self.process_flags(arg_flags, env)
|
||||
self.pos_args = []
|
||||
self.long_args = _default_long_args
|
||||
self.arg_dest = _default_dest
|
||||
|
||||
re_long = r'^--[a-z\-]+$'
|
||||
for k in arg_long.keys():
|
||||
if re.match(re_long, arg_long[k]) == None:
|
||||
raise ValueError('invalid long arg {}'.format(arg_long[k]))
|
||||
self.long_args[k] = arg_long[k]
|
||||
dest = arg_long[k][2:]
|
||||
dest = dest.replace('-', '_')
|
||||
self.arg_dest[k] = dest
|
||||
|
||||
self.process_flags(arg_flags, env)
|
||||
|
||||
|
||||
def add_positional(self, name, type=str, help=None, required=True):
|
||||
def add_positional(self, name, type=str, help=None, append=False, required=True):
|
||||
"""Add a positional argument.
|
||||
|
||||
Stdin piping will only be possible in the event a single positional argument is defined.
|
||||
@@ -73,7 +101,7 @@ class ArgumentParser(argparse.ArgumentParser):
|
||||
:param required: If true, argument will be set to required
|
||||
:type required: bool
|
||||
"""
|
||||
self.pos_args.append((name, type, help, required,))
|
||||
self.pos_args.append((name, type, help, required, append,))
|
||||
|
||||
|
||||
def parse_args(self, argv=sys.argv[1:]):
|
||||
@@ -88,16 +116,25 @@ class ArgumentParser(argparse.ArgumentParser):
|
||||
"""
|
||||
if len(self.pos_args) == 1:
|
||||
arg = self.pos_args[0]
|
||||
self.add_argument(arg[0], nargs='?', type=arg[1], default=stdin_arg(), help=arg[2])
|
||||
if arg[4]:
|
||||
self.add_argument(arg[0], nargs='*', type=arg[1], default=stdin_arg(), help=arg[2])
|
||||
else:
|
||||
self.add_argument(arg[0], nargs='?', type=arg[1], default=stdin_arg(), help=arg[2])
|
||||
else:
|
||||
for arg in self.pos_args:
|
||||
if arg[3]:
|
||||
self.add_argument(arg[0], type=arg[1], help=arg[2])
|
||||
if arg[4]:
|
||||
self.add_argument(arg[0], nargs='+', type=arg[1], help=arg[2])
|
||||
else:
|
||||
self.add_argument(arg[0], type=arg[1], help=arg[2])
|
||||
else:
|
||||
self.add_argument(arg[0], nargs='?', type=arg[1], help=arg[2])
|
||||
if arg[4]:
|
||||
self.add_argument(arg[0], nargs='*', type=arg[1], help=arg[2])
|
||||
else:
|
||||
self.add_argument(arg[0], type=arg[1], help=arg[2])
|
||||
args = super(ArgumentParser, self).parse_args(args=argv)
|
||||
|
||||
if args.dumpconfig:
|
||||
if getattr(args, 'dumpconfig', None) != None:
|
||||
return args
|
||||
|
||||
if len(self.pos_args) == 1:
|
||||
@@ -129,12 +166,13 @@ class ArgumentParser(argparse.ArgumentParser):
|
||||
:type env: dict
|
||||
"""
|
||||
if arg_flags & Flag.VERBOSE:
|
||||
self.add_argument('--no-logs', dest='no_logs',action='store_true', help='Turn off all logging')
|
||||
self.add_argument('-v', action='store_true', help='Be verbose')
|
||||
self.add_argument('-vv', action='store_true', help='Be more verbose')
|
||||
if arg_flags & Flag.CONFIG:
|
||||
self.add_argument('-c', '--config', type=str, default=env.get('CONFINI_DIR'), help='Configuration directory')
|
||||
self.add_argument('-n', '--namespace', type=str, help='Configuration namespace')
|
||||
self.add_argument('--dumpconfig', action='store_true', help='Output configuration and quit. Use with --raw to omit values and output schema only.')
|
||||
self.add_argument('--dumpconfig', type=str, choices=['env', 'ini'], help='Output configuration and quit. Use with --raw to omit values and output schema only.')
|
||||
if arg_flags & Flag.WAIT:
|
||||
self.add_argument('-w', action='store_true', help='Wait for the last transaction to be confirmed')
|
||||
self.add_argument('-ww', action='store_true', help='Wait for every transaction to be confirmed')
|
||||
@@ -154,9 +192,10 @@ class ArgumentParser(argparse.ArgumentParser):
|
||||
if arg_flags & Flag.SEQ:
|
||||
self.add_argument('--seq', action='store_true', help='Use sequential rpc ids')
|
||||
if arg_flags & Flag.KEY_FILE:
|
||||
self.add_argument('-y', '--key-file', dest='y', type=str, help='Keystore file to use for signing or address')
|
||||
self.add_argument('-y', self.long_args['-y'], dest='y', type=str, help='Keystore file to use for signing or address')
|
||||
self.add_argument('--passphrase-file', dest='passphrase_file', type=str, help='File containing passphrase for keystore')
|
||||
if arg_flags & Flag.SEND:
|
||||
self.add_argument('-s', '--send', dest='s', action='store_true', help='Send to network')
|
||||
self.add_argument('-s', self.long_args['-s'], dest='s', action='store_true', help='Send to network')
|
||||
if arg_flags & Flag.RAW:
|
||||
self.add_argument('--raw', action='store_true', help='Do not decode output')
|
||||
if arg_flags & (Flag.SIGN | Flag.NONCE):
|
||||
@@ -167,6 +206,6 @@ class ArgumentParser(argparse.ArgumentParser):
|
||||
if arg_flags & argflag_std_target == 0:
|
||||
arg_flags |= Flag.WALLET
|
||||
if arg_flags & Flag.EXEC:
|
||||
self.add_argument('-e', '--exectuable-address', dest='executable_address', type=str, help='contract address')
|
||||
self.add_argument('-e', self.long_args['-e'], dest=self.arg_dest['-e'], type=str, help='contract address')
|
||||
if arg_flags & Flag.WALLET:
|
||||
self.add_argument('-a', '--recipient', dest='recipient', type=str, help='recipient address')
|
||||
self.add_argument('-a', self.long_args['-a'], dest=self.arg_dest['-a'], type=str, help='recipient address')
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import stat
|
||||
|
||||
# external imports
|
||||
import confini
|
||||
@@ -102,14 +103,51 @@ class Config(confini.Config):
|
||||
:rtype: confini.Config
|
||||
:return: Processed configuation
|
||||
"""
|
||||
env_prefix = getattr(args, 'env_prefix', None)
|
||||
env_prefix_str = env_prefix
|
||||
if env_prefix_str == None:
|
||||
env_prefix_str = ''
|
||||
else:
|
||||
env_prefix_str += '_'
|
||||
|
||||
env_loglevel_key_str = env_prefix_str + 'LOGLEVEL'
|
||||
env_loglevel = os.environ.get(env_loglevel_key_str)
|
||||
|
||||
if logger == None:
|
||||
logger = logging.getLogger()
|
||||
|
||||
if arg_flags & Flag.CONFIG:
|
||||
if env_loglevel != None:
|
||||
env_loglevel = env_loglevel.lower()
|
||||
if env_loglevel == '0' or env_loglevel == 'no' or env_loglevel == 'none' or env_loglevel == 'disable' or env_loglevel == 'disabled' or env_loglevel == 'off':
|
||||
logging.disable()
|
||||
elif env_loglevel == '1' or env_loglevel == 'err' or env_loglevel == 'error':
|
||||
logger.setLevel(logging.ERROR)
|
||||
elif env_loglevel == '2' or env_loglevel == 'warning' or env_loglevel == 'warn':
|
||||
logger.setLevel(logging.WARNING)
|
||||
elif env_loglevel == '3' or env_loglevel == 'info':
|
||||
logger.setLevel(logging.INFO)
|
||||
else:
|
||||
valid_level = False
|
||||
try:
|
||||
num_loglevel = int(env_loglevel)
|
||||
valid_level = True
|
||||
except:
|
||||
if env_loglevel == 'debug':
|
||||
valid_level = True
|
||||
|
||||
if not valid_level:
|
||||
raise ValueError('unknown loglevel {} set in environment variable {}'.format(env_loglevel, env_loglevel_key_str))
|
||||
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
if arg_flags & Flag.VERBOSE:
|
||||
if args.vv:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
elif args.v:
|
||||
logger.setLevel(logging.INFO)
|
||||
if args.no_logs:
|
||||
logging.disable()
|
||||
|
||||
override_config_dirs = []
|
||||
config_dir = [cls.default_base_config_dir]
|
||||
@@ -160,7 +198,6 @@ class Config(confini.Config):
|
||||
# default_config_dir = default_parent_config_dir
|
||||
# config_dir = default_config_dir
|
||||
# override_config_dirs = []
|
||||
env_prefix = getattr(args, 'env_prefix', None)
|
||||
|
||||
config = confini.Config(config_dir, env_prefix=env_prefix, override_dirs=override_config_dirs)
|
||||
config.process()
|
||||
@@ -170,14 +207,21 @@ class Config(confini.Config):
|
||||
args_override = {}
|
||||
|
||||
if arg_flags & Flag.PROVIDER:
|
||||
args_override['RPC_HTTP_PROVIDER'] = getattr(args, 'p')
|
||||
args_override['RPC_PROVIDER'] = getattr(args, 'p')
|
||||
args_override['RPC_DIALECT'] = getattr(args, 'rpc_dialect')
|
||||
if arg_flags & Flag.CHAIN_SPEC:
|
||||
args_override['CHAIN_SPEC'] = getattr(args, 'i')
|
||||
if arg_flags & Flag.KEY_FILE:
|
||||
args_override['WALLET_KEY_FILE'] = getattr(args, 'y')
|
||||
|
||||
fp = getattr(args, 'passphrase_file')
|
||||
if fp != None:
|
||||
st = os.stat(fp)
|
||||
if stat.S_IMODE(st.st_mode) & (stat.S_IRWXO | stat.S_IRWXG) > 0:
|
||||
logg.warning('others than owner have access on password file')
|
||||
f = open(fp, 'r')
|
||||
args_override['WALLET_PASSPHRASE'] = f.read()
|
||||
f.close()
|
||||
config.censor('PASSPHRASE', 'WALLET')
|
||||
config.dict_override(args_override, 'cli args')
|
||||
|
||||
if arg_flags & Flag.PROVIDER:
|
||||
@@ -225,6 +269,7 @@ class Config(confini.Config):
|
||||
config.add(getattr(args, 'rpc_credentials'), 'RPC_CREDENTIALS')
|
||||
|
||||
for k in extra_args.keys():
|
||||
logg.debug('extra_agrs {}'.format(k))
|
||||
v = extra_args[k]
|
||||
if v == None:
|
||||
v = '_' + k.upper()
|
||||
@@ -236,20 +281,29 @@ class Config(confini.Config):
|
||||
pass
|
||||
if existing_r == None or r != None:
|
||||
config.add(r, v, exists_ok=True)
|
||||
logg.debug('added {} to {}'.format(r, v))
|
||||
|
||||
if getattr(args, 'dumpconfig'):
|
||||
config_keys = config.all()
|
||||
with_values = not config.get('_RAW')
|
||||
for k in config_keys:
|
||||
if k[0] == '_':
|
||||
continue
|
||||
s = k + '='
|
||||
if with_values:
|
||||
v = config.get(k)
|
||||
if v != None:
|
||||
s += str(v)
|
||||
s += '\n'
|
||||
dump_writer.write(s)
|
||||
if getattr(args, 'dumpconfig', None):
|
||||
if args.dumpconfig == 'ini':
|
||||
from confini.export import ConfigExporter
|
||||
exporter = ConfigExporter(config, target=sys.stdout, doc=False)
|
||||
exporter.export(exclude_sections=['config'])
|
||||
elif args.dumpconfig == 'env':
|
||||
from confini.env import export_env
|
||||
export_env(config)
|
||||
|
||||
# config_keys = config.all()
|
||||
# with_values = not config.get('_RAW')
|
||||
# for k in config_keys:
|
||||
# if k[0] == '_':
|
||||
# continue
|
||||
# s = k + '='
|
||||
# if with_values:
|
||||
# v = config.get(k)
|
||||
# if v != None:
|
||||
# s += str(v)
|
||||
# s += '\n'
|
||||
# dump_writer.write(s)
|
||||
sys.exit(0)
|
||||
|
||||
if load_callback != None:
|
||||
|
||||
@@ -61,7 +61,7 @@ class Rpc:
|
||||
self.id_generator = IntSequenceGenerator()
|
||||
|
||||
self.chain_spec = config.get('CHAIN_SPEC')
|
||||
self.conn = self.constructor(url=config.get('RPC_PROVIDER'), chain_spec=self.chain_spec, auth=auth)
|
||||
self.conn = self.constructor(url=config.get('RPC_PROVIDER'), chain_spec=self.chain_spec, auth=auth, verify_identity=config.true('RPC_VERIFY'))
|
||||
|
||||
return self.conn
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
# standard imports
|
||||
import logging
|
||||
|
||||
# external imports
|
||||
from crypto_dev_signer.keystore.dict import DictKeystore
|
||||
|
||||
logg = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -19,7 +16,7 @@ class Wallet:
|
||||
:todo: sign_transaction_to_rlp from chainlib-eth must be renamed to sign_transaction_to_wire, and included as part of signer interface
|
||||
"""
|
||||
|
||||
def __init__(self, signer_cls, keystore=DictKeystore(), checksummer=None):
|
||||
def __init__(self, signer_cls, keystore=None, checksummer=None):
|
||||
self.signer_constructor = signer_cls
|
||||
self.keystore = keystore
|
||||
self.signer = None
|
||||
@@ -30,6 +27,10 @@ class Wallet:
|
||||
self.use_checksum = False
|
||||
|
||||
|
||||
def init(self):
|
||||
self.signer = self.signer_constructor(self.keystore)
|
||||
|
||||
|
||||
def from_config(self, config):
|
||||
"""Instantiates a signer from the registered signer class, using parameters from a processed configuration.
|
||||
|
||||
|
||||
@@ -23,7 +23,10 @@ from .jsonrpc import (
|
||||
ErrorParser,
|
||||
)
|
||||
from .http import PreemptiveBasicAuthHandler
|
||||
from .error import JSONRPCException
|
||||
from .error import (
|
||||
JSONRPCException,
|
||||
RPCException,
|
||||
)
|
||||
from .auth import Auth
|
||||
|
||||
logg = logging.getLogger(__name__)
|
||||
@@ -99,10 +102,13 @@ class RPCConnection:
|
||||
}
|
||||
__constructors_for_chains = {}
|
||||
|
||||
def __init__(self, url=None, chain_spec=None, auth=None):
|
||||
def __init__(self, url=None, chain_spec=None, auth=None, verify_identity=True):
|
||||
self.chain_spec = chain_spec
|
||||
self.location = None
|
||||
self.basic = None
|
||||
self.verify_identity = verify_identity
|
||||
if not self.verify_identity:
|
||||
logg.warning('RPC host identity verification is OFF. Beware, you will be easy to cheat')
|
||||
if url == None:
|
||||
return
|
||||
self.auth = auth
|
||||
@@ -127,6 +133,9 @@ class RPCConnection:
|
||||
self.location = os.path.join('{}://'.format(url_parsed.scheme), location)
|
||||
self.location = urljoin(self.location, url_parsed.path)
|
||||
|
||||
if url_parsed.query != '':
|
||||
self.location = urljoin(self.location, '?' + url_parsed.query)
|
||||
|
||||
logg.debug('parsed url {} to location {}'.format(url, self.location))
|
||||
|
||||
|
||||
@@ -284,6 +293,11 @@ class JSONRPCHTTPConnection(HTTPConnection):
|
||||
:returns: Result value part of JSON RPC response
|
||||
:todo: Invalid response exception from invalid json response
|
||||
"""
|
||||
ssl_ctx = None
|
||||
if not self.verify_identity:
|
||||
import ssl
|
||||
ssl_ctx = ssl.SSLContext()
|
||||
ssl_ctx.verify_mode = ssl.CERT_NONE
|
||||
req = Request(
|
||||
self.location,
|
||||
method='POST',
|
||||
@@ -308,8 +322,15 @@ class JSONRPCHTTPConnection(HTTPConnection):
|
||||
)
|
||||
ho = build_opener(handler)
|
||||
install_opener(ho)
|
||||
|
||||
r = urlopen(req, data=data.encode('utf-8'))
|
||||
|
||||
try:
|
||||
r = urlopen(
|
||||
req,
|
||||
data=data.encode('utf-8'),
|
||||
context=ssl_ctx,
|
||||
)
|
||||
except URLError as e:
|
||||
raise RPCException(e)
|
||||
|
||||
result = json.load(r)
|
||||
logg.debug('(HTTP) recv {}'.format(result))
|
||||
|
||||
@@ -4,6 +4,7 @@ auth =
|
||||
credentials =
|
||||
dialect = default
|
||||
scheme = http
|
||||
verify = 1
|
||||
|
||||
[chain]
|
||||
spec =
|
||||
|
||||
@@ -30,9 +30,7 @@ class TxHexNormalizer:
|
||||
|
||||
|
||||
def __hex_normalize(self, data, context):
|
||||
#r = add_0x(hex_uniform(strip_0x(data)))
|
||||
r = hex_uniform(strip_0x(data))
|
||||
logg.debug('normalize {} {} -> {}'.format(context, data, r))
|
||||
return r
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@node chainlib-cli
|
||||
|
||||
@section Command line interface provisions
|
||||
|
||||
The base CLI provisions of @code{chainlib} simplifies the generation of a some base object instances by command line arguments, environment variables and configuration schemas.
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@node chainlib-lib
|
||||
|
||||
@section Base library contents
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
@node chainlib-config
|
||||
|
||||
@anchor{chainlib-config}
|
||||
@section Rendering configurations
|
||||
|
||||
Configurations in @code{chainlib} are processed, rendered and interfaced using the @code{confini} python package.
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
@top chainlib
|
||||
|
||||
@node chainlib
|
||||
@chapter Chainlib
|
||||
|
||||
@include intro.texi
|
||||
@@ -1,4 +1,4 @@
|
||||
@node chainlib-intro
|
||||
@section Overview
|
||||
|
||||
Chainlib is an attempt at employing a universal interface to manipulate and access blockchains regardless of underlying architecture.
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
crypto-dev-signer>=0.4.15rc2,<=0.4.15
|
||||
funga~=0.5.1
|
||||
pysha3==1.0.2
|
||||
hexathon~=0.0.1a8
|
||||
hexathon~=0.1.3
|
||||
confini~=0.5.3
|
||||
|
||||
27
setup.cfg
27
setup.cfg
@@ -1,10 +1,13 @@
|
||||
[metadata]
|
||||
name = chainlib
|
||||
version = 0.0.10a1
|
||||
description = Generic blockchain access library and tooling
|
||||
author = Louis Holbrook
|
||||
author_email = dev@holbrook.no
|
||||
url = https://gitlab.com/chaintools/chainlib
|
||||
name=chainlib
|
||||
license=WTFPL2
|
||||
author_email=dev@holbrook.no
|
||||
description=Generic blockchain access library and tooling
|
||||
version=0.0.20
|
||||
url=https://gitlab.com/chaintools/chainlib
|
||||
author=Louis Holbrook
|
||||
|
||||
|
||||
keywords =
|
||||
dlt
|
||||
blockchain
|
||||
@@ -13,18 +16,8 @@ classifiers =
|
||||
Programming Language :: Python :: 3
|
||||
Operating System :: OS Independent
|
||||
Development Status :: 3 - Alpha
|
||||
Topic :: Software Development :: Libraries
|
||||
Environment :: Console
|
||||
Intended Audience :: Developers
|
||||
License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
||||
Topic :: Internet
|
||||
license = GPL3
|
||||
licence_files =
|
||||
LICENSE.txt
|
||||
|
||||
|
||||
[options]
|
||||
python_requires = >= 3.6
|
||||
include_package_data = True
|
||||
packages =
|
||||
chainlib
|
||||
chainlib.cli
|
||||
|
||||
9
setup.py
9
setup.py
@@ -16,5 +16,12 @@ setup(
|
||||
install_requires=requirements,
|
||||
extras_require={
|
||||
'xdg': "pyxdg~=0.27",
|
||||
}
|
||||
},
|
||||
license_files= ('LICENSE.txt',),
|
||||
python_requires = '>=3.8',
|
||||
include_package_data = True,
|
||||
packages = [
|
||||
'chainlib',
|
||||
'chainlib.cli',
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,40 +1,81 @@
|
||||
# standard imports
|
||||
import unittest
|
||||
import logging
|
||||
|
||||
# local imports
|
||||
from chainlib.chain import ChainSpec
|
||||
|
||||
# test imports
|
||||
from tests.base import TestBase
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger()
|
||||
logg.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
class TestChain(TestBase):
|
||||
|
||||
def test_chain_spec_str(self):
|
||||
s = ChainSpec('foo', 'bar', 3, 'baz')
|
||||
self.assertEqual('foo:bar:3:baz', str(s))
|
||||
|
||||
s = ChainSpec('foo', 'bar', 3)
|
||||
self.assertEqual('foo:bar:3', str(s))
|
||||
|
||||
def test_chain_spec(self):
|
||||
s = ChainSpec('foo', 'bar', 3, 'baz')
|
||||
self.assertEqual('foo:bar:3:baz', str(s))
|
||||
|
||||
|
||||
s = ChainSpec('foo', 'bar', 3, 'baz', ['inky', 'pinky', 'blinky'])
|
||||
self.assertEqual('foo:bar:3:baz:inky:pinky:blinky', str(s))
|
||||
|
||||
|
||||
def test_chain_spec(self):
|
||||
s = ChainSpec.from_chain_str('foo:bar:3')
|
||||
s = ChainSpec.from_chain_str('foo:bar:3:baz')
|
||||
s = ChainSpec.from_chain_str('foo:bar:3:baz:inky:pinky:blinky')
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
s = ChainSpec.from_chain_str('foo:bar:a')
|
||||
s = ChainSpec.from_chain_str('foo:bar')
|
||||
s = ChainSpec.from_chain_str('foo')
|
||||
s = ChainSpec.from_chain_str('foo1:bar:3')
|
||||
s = ChainSpec.from_chain_str('foo:bar2:3')
|
||||
|
||||
|
||||
def test_chain_spec_dict(self):
|
||||
s = 'foo:bar:3:baz'
|
||||
c = ChainSpec.from_chain_str('foo:bar:3:baz')
|
||||
ss = 'foo:bar:3:baz:inky:pinky:blinky'
|
||||
c = ChainSpec.from_chain_str(ss)
|
||||
d = c.asdict()
|
||||
self.assertEqual(d['arch'], 'foo')
|
||||
self.assertEqual(d['fork'], 'bar')
|
||||
self.assertEqual(d['network_id'], 3)
|
||||
self.assertEqual(d['common_name'], 'baz')
|
||||
self.assertEqual(d['custom'], ['inky', 'pinky', 'blinky'])
|
||||
cc = ChainSpec.from_dict(d)
|
||||
self.assertEqual(s, str(cc))
|
||||
self.assertEqual(ss, str(cc))
|
||||
|
||||
d = c.asdict(use_custom=False)
|
||||
cc = ChainSpec.from_dict(d)
|
||||
self.assertEqual(str(cc), 'foo:bar:3:baz')
|
||||
|
||||
d = c.asdict(use_common_name=False)
|
||||
cc = ChainSpec.from_dict(d)
|
||||
self.assertEqual(str(cc), 'foo:bar:3')
|
||||
|
||||
def test_chain_spec_compare(self):
|
||||
a = 'foo:bar:42:baz'
|
||||
b = 'foo:bar:42:barbar'
|
||||
c = 'foo:bar:42:baz:inky:pinky:blinky'
|
||||
|
||||
ca = ChainSpec.from_chain_str(a)
|
||||
cb = ChainSpec.from_chain_str(b)
|
||||
|
||||
self.assertTrue(ca.is_same_as(cb))
|
||||
self.assertFalse(ca.is_same_as(cb, use_common_name=True))
|
||||
|
||||
cc = ChainSpec.from_chain_str(c)
|
||||
logg.debug('chain_spec_cmp ' + str(cc.o))
|
||||
self.assertTrue(ca.is_same_as(cc))
|
||||
self.assertTrue(ca.is_same_as(cc, use_common_name=True))
|
||||
self.assertFalse(ca.is_same_as(cc, use_common_name=True, use_custom=True))
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# standard imports
|
||||
import unittest
|
||||
import os
|
||||
import logging
|
||||
|
||||
# local imports
|
||||
import chainlib.cli
|
||||
@@ -10,6 +11,8 @@ script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
data_dir = os.path.join(script_dir, 'testdata')
|
||||
config_dir = os.path.join(data_dir, 'config')
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
|
||||
class TestCli(unittest.TestCase):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user