31 Commits

Author SHA1 Message Date
lash
68a01565c9 Make certain long flag aliases editable 2022-02-21 08:12:24 +00:00
lash
b5a01b28ac Add confini dep 2022-02-20 19:00:15 +00:00
lash
926c954d6e Add wallet passphrase to censor list 2022-02-20 18:42:30 +00:00
lash
d24fea2d4f Passphrase file in wallet key file 2022-02-20 18:27:00 +00:00
lash
d3f6d1138d Skip buggy hexathon 2022-02-14 13:56:41 +00:00
lash
2c567cbded Remove unneeded change in -vv comment 2022-01-21 10:21:17 +00:00
lash
28ee4f7197 Add log disable and env var loglevel 2022-01-21 00:13:07 +00:00
nolash
eedb63212d Bump version 2022-01-04 17:14:24 +00:00
nolash
99cac32ced Allow for query string in rpc url 2022-01-04 17:10:19 +00:00
7deeee2c84 Merge pull request 'bug: Fix documentation compile and config dumps' (#2) from lash/docs-and-dumps into master
Reviewed-on: #2
2021-12-21 15:00:02 +00:00
nolash
6a94208c68 Remove bluto 2021-12-21 14:58:09 +00:00
nolash
becd6744f6 Add option to skip ssl validation on rpc 2021-12-06 18:55:36 +01:00
nolash
06c6b2562a Replace string with object in config export suppress 2021-11-10 11:14:02 +01:00
nolash
1067c8494e Add omit sections for config exporter 2021-11-10 09:56:51 +01:00
nolash
39478d9c3c Add bluto package info renderer 2021-11-08 07:49:02 +01:00
nolash
24d7c078e8 Remove prerelease tag 2021-11-06 15:30:03 +01:00
nolash
70d8ab6a33 Add signer initializer 2021-10-30 14:10:19 +02:00
nolash
d914f64797 Remove logline span 2021-10-28 13:27:30 +02:00
nolash
5bfdb51676 Add custom fields to chain spec, harden custom field imports, improve dumpconfig handle 2021-10-28 12:16:23 +02:00
nolash
7d7209dd31 Handle missing dumpconfig arg 2021-10-27 20:53:07 +02:00
nolash
1373cb10e6 Remove unnecessary nodes 2021-10-26 19:44:54 +02:00
nolash
64dbcfcddf WIP explicit node links in texinfo docs 2021-10-26 18:20:36 +02:00
nolash
56f3cffbbf Merge remote-tracking branch 'origin/master' into lash/fix-docs-compile 2021-10-26 18:08:40 +02:00
Louis Holbrook
a71ec59fb2 Merge branch 'lash/fix-docs-compile' into 'master'
feat: chainspec, positional args, docfix

See merge request chaintool/chainlib!8
2021-10-26 14:16:00 +00:00
Louis Holbrook
095c0810f3 feat: chainspec, positional args, docfix 2021-10-26 14:15:59 +00:00
nolash
3e6d0c6912 Rename index texi file 2021-10-26 15:37:08 +02:00
nolash
52e748a02e Fix urllib error reference 2021-10-21 07:19:42 +02:00
nolash
5518bf83a3 Convert urlerror in rpc do, allow appends for pos args in cli module 2021-10-19 19:39:38 +02:00
nolash
d2505f19d2 Remove crypto_dev_signer, add funga 2021-10-18 14:28:15 +02:00
Louis Holbrook
e073a3c57a Merge branch 'lash/chain-spec-rename' into '0.0.10-dev'
refactor: Rename chain spec members

See merge request chaintool/chainlib!7
2021-10-18 11:25:00 +00:00
Louis Holbrook
f13b24798e refactor: Rename chain spec members 2021-10-18 11:25:00 +00:00
19 changed files with 345 additions and 97 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,6 +4,7 @@ auth =
credentials =
dialect = default
scheme = http
verify = 1
[chain]
spec =

View File

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

View File

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

View File

@@ -1,5 +1,3 @@
@node chainlib-lib
@section Base library contents

View File

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

View File

@@ -1,5 +1,4 @@
@top chainlib
@node chainlib
@chapter Chainlib
@include intro.texi

View File

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

View File

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

View File

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

View File

@@ -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',
],
)

View File

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

View File

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