18 Commits

Author SHA1 Message Date
nolash
f38ece7c2b Revert "Remove bluto"
This reverts commit 6a94208c68.
2021-12-21 15:00:45 +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
23 changed files with 275 additions and 73 deletions

17
.bluto/bluto.deb.ini Normal file
View File

@@ -0,0 +1,17 @@
[main:deb]
standards_version=3.8.5
priority=optional
engine=9
[dep:deb-build]
debhelper=>=9
python3=>=3.8
python3-setuptools=0
dh-python=0
[dep:deb-install]
dpkg=0
python3=>=3.8
[dep:deb-exec]
make=0

18
.bluto/bluto.ini Normal file
View File

@@ -0,0 +1,18 @@
[main]
name = chainlib
version = 0.0.12
summary = Generic blockchain access library and tooling
[author:lash]
name = Louis Holbrook
email = dev@holbrook.no
pgp=0826EDA1702D1E87C6E2875121D2E7BB88C2A746
[locate:git]
url = https://gitlab.com/chaintools/chainlib
[locate:lash]
url=https://holbrook.no
[license]
GPL=3+

6
.bluto/bluto.py.ini Normal file
View File

@@ -0,0 +1,6 @@
[main:py]
include_data = 1
packages = chainlib,chainlib.cli
[dep:py-exec]
python=>=3.6

11
.bluto/bluto.tag Normal file
View File

@@ -0,0 +1,11 @@
dlt
blockchain
cryptocurrency
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

View File

@@ -1,4 +1,6 @@
- 0.0.5-pending - 0.0.14
* Add option to skip ssl verification on rpc
- 0.0.5
* Move eth code to separate package * Move eth code to separate package
- 0.0.4-unreleased - 0.0.4-unreleased
* Add pack tx from already signed tx struct * Add pack tx from already signed tx struct

View File

@@ -1,5 +1,15 @@
# standard imports # standard imports
import copy 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: class ChainSpec:
@@ -16,13 +26,37 @@ class ChainSpec:
:param tag: Descriptive tag :param tag: Descriptive tag
:type tag: str :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 = { self.o = {
'arch': arch, 'arch': arch,
'fork': fork, 'fork': fork,
'network_id': network_id, 'network_id': network_id,
'common_name': common_name, '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): def network_id(self):
"""Returns the network id part of the spec. """Returns the network id part of the spec.
@@ -43,6 +77,12 @@ class ChainSpec:
def engine(self): def engine(self):
"""Alias of self.arch()
"""
return self.arch()
def arch(self):
"""Returns the chain architecture part of the spec """Returns the chain architecture part of the spec
:rtype: str :rtype: str
@@ -51,6 +91,15 @@ class ChainSpec:
return self.o['arch'] 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): def common_name(self):
"""Returns the common name part of the spec """Returns the common name part of the spec
@@ -60,6 +109,21 @@ class ChainSpec:
return self.o['common_name'] 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 @staticmethod
def from_chain_str(chain_str): 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. """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: if len(o) < 3:
raise ValueError('Chain string must have three sections, got {}'.format(len(o))) raise ValueError('Chain string must have three sections, got {}'.format(len(o)))
common_name = None common_name = None
if len(o) == 4: if len(o) > 3:
common_name = 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 @staticmethod
@@ -100,20 +168,35 @@ class ChainSpec:
:rtype: chainlib.chain.ChainSpec :rtype: chainlib.chain.ChainSpec
:returns: Resulting chain spec :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. """Create a dictionary representation of the chain spec.
:rtype: dict :rtype: dict
:returns: Chain spec dictionary :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): def __str__(self):
s = '{}:{}:{}'.format(self.o['arch'], self.o['fork'], self.o['network_id']) return self.as_string()
if self.o['common_name'] != None:
s += ':' + self.o['common_name']
return s

View File

@@ -107,7 +107,7 @@ class ArgumentParser(argparse.ArgumentParser):
self.add_argument(arg[0], type=arg[1], help=arg[2]) self.add_argument(arg[0], type=arg[1], help=arg[2])
args = super(ArgumentParser, self).parse_args(args=argv) args = super(ArgumentParser, self).parse_args(args=argv)
if args.dumpconfig: if getattr(args, 'dumpconfig', None) != None:
return args return args
if len(self.pos_args) == 1: if len(self.pos_args) == 1:
@@ -144,7 +144,7 @@ class ArgumentParser(argparse.ArgumentParser):
if arg_flags & Flag.CONFIG: if arg_flags & Flag.CONFIG:
self.add_argument('-c', '--config', type=str, default=env.get('CONFINI_DIR'), help='Configuration directory') 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('-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: if arg_flags & Flag.WAIT:
self.add_argument('-w', action='store_true', help='Wait for the last transaction to be confirmed') 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') self.add_argument('-ww', action='store_true', help='Wait for every transaction to be confirmed')

View File

@@ -170,7 +170,6 @@ class Config(confini.Config):
args_override = {} args_override = {}
if arg_flags & Flag.PROVIDER: if arg_flags & Flag.PROVIDER:
args_override['RPC_HTTP_PROVIDER'] = getattr(args, 'p')
args_override['RPC_PROVIDER'] = getattr(args, 'p') args_override['RPC_PROVIDER'] = getattr(args, 'p')
args_override['RPC_DIALECT'] = getattr(args, 'rpc_dialect') args_override['RPC_DIALECT'] = getattr(args, 'rpc_dialect')
if arg_flags & Flag.CHAIN_SPEC: if arg_flags & Flag.CHAIN_SPEC:
@@ -225,6 +224,7 @@ class Config(confini.Config):
config.add(getattr(args, 'rpc_credentials'), 'RPC_CREDENTIALS') config.add(getattr(args, 'rpc_credentials'), 'RPC_CREDENTIALS')
for k in extra_args.keys(): for k in extra_args.keys():
logg.debug('extra_agrs {}'.format(k))
v = extra_args[k] v = extra_args[k]
if v == None: if v == None:
v = '_' + k.upper() v = '_' + k.upper()
@@ -236,20 +236,29 @@ class Config(confini.Config):
pass pass
if existing_r == None or r != None: if existing_r == None or r != None:
config.add(r, v, exists_ok=True) config.add(r, v, exists_ok=True)
logg.debug('added {} to {}'.format(r, v))
if getattr(args, 'dumpconfig'): if getattr(args, 'dumpconfig', None):
config_keys = config.all() if args.dumpconfig == 'ini':
with_values = not config.get('_RAW') from confini.export import ConfigExporter
for k in config_keys: exporter = ConfigExporter(config, target=sys.stdout, doc=False)
if k[0] == '_': exporter.export(exclude_sections=['config'])
continue elif args.dumpconfig == 'env':
s = k + '=' from confini.env import export_env
if with_values: export_env(config)
v = config.get(k)
if v != None: # config_keys = config.all()
s += str(v) # with_values = not config.get('_RAW')
s += '\n' # for k in config_keys:
dump_writer.write(s) # 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) sys.exit(0)
if load_callback != None: if load_callback != None:

View File

@@ -61,7 +61,7 @@ class Rpc:
self.id_generator = IntSequenceGenerator() self.id_generator = IntSequenceGenerator()
self.chain_spec = config.get('CHAIN_SPEC') 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 return self.conn

View File

@@ -27,6 +27,10 @@ class Wallet:
self.use_checksum = False self.use_checksum = False
def init(self):
self.signer = self.signer_constructor(self.keystore)
def from_config(self, config): def from_config(self, config):
"""Instantiates a signer from the registered signer class, using parameters from a processed configuration. """Instantiates a signer from the registered signer class, using parameters from a processed configuration.

View File

@@ -102,10 +102,13 @@ class RPCConnection:
} }
__constructors_for_chains = {} __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.chain_spec = chain_spec
self.location = None self.location = None
self.basic = 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: if url == None:
return return
self.auth = auth self.auth = auth
@@ -287,6 +290,11 @@ class JSONRPCHTTPConnection(HTTPConnection):
:returns: Result value part of JSON RPC response :returns: Result value part of JSON RPC response
:todo: Invalid response exception from invalid json 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( req = Request(
self.location, self.location,
method='POST', method='POST',
@@ -313,7 +321,11 @@ class JSONRPCHTTPConnection(HTTPConnection):
install_opener(ho) install_opener(ho)
try: try:
r = urlopen(req, data=data.encode('utf-8')) r = urlopen(
req,
data=data.encode('utf-8'),
context=ssl_ctx,
)
except URLError as e: except URLError as e:
raise RPCException(e) raise RPCException(e)

View File

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

View File

@@ -30,9 +30,7 @@ class TxHexNormalizer:
def __hex_normalize(self, data, context): def __hex_normalize(self, data, context):
#r = add_0x(hex_uniform(strip_0x(data)))
r = hex_uniform(strip_0x(data)) r = hex_uniform(strip_0x(data))
logg.debug('normalize {} {} -> {}'.format(context, data, r))
return r return r

View File

@@ -1,5 +1,3 @@
@node chainlib-cli
@section Command line interface provisions @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. 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 @section Base library contents

View File

@@ -1,5 +1,4 @@
@node chainlib-config @anchor{chainlib-config}
@section Rendering configurations @section Rendering configurations
Configurations in @code{chainlib} are processed, rendered and interfaced using the @code{confini} python package. 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 @chapter Chainlib
@include intro.texi @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. Chainlib is an attempt at employing a universal interface to manipulate and access blockchains regardless of underlying architecture.

View File

@@ -1,3 +1,3 @@
funga>=0.5.1a1,<0.6.0 funga~=0.5.1
pysha3==1.0.2 pysha3==1.0.2
hexathon~=0.0.1a8 hexathon~=0.1.0

View File

@@ -1,10 +1,16 @@
; Config::Simple 4.59
; Mon Nov 8 05:19:17 2021
[metadata] [metadata]
name = chainlib name=chainlib
version = 0.0.10a6 license=WTFPL2
description = Generic blockchain access library and tooling author_email=dev@holbrook.no
author = Louis Holbrook description=Generic blockchain access library and tooling
author_email = dev@holbrook.no version=0.0.14
url = https://gitlab.com/chaintools/chainlib url=https://gitlab.com/chaintools/chainlib
author=Louis Holbrook
keywords = keywords =
dlt dlt
blockchain blockchain
@@ -13,18 +19,8 @@ classifiers =
Programming Language :: Python :: 3 Programming Language :: Python :: 3
Operating System :: OS Independent Operating System :: OS Independent
Development Status :: 3 - Alpha Development Status :: 3 - Alpha
Topic :: Software Development :: Libraries
Environment :: Console Environment :: Console
Intended Audience :: Developers Intended Audience :: Developers
License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
Topic :: Internet 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, install_requires=requirements,
extras_require={ extras_require={
'xdg': "pyxdg~=0.27", '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 unittest
import logging
# local imports
from chainlib.chain import ChainSpec from chainlib.chain import ChainSpec
# test imports
from tests.base import TestBase from tests.base import TestBase
logging.basicConfig(level=logging.DEBUG)
logg = logging.getLogger()
logg.setLevel(logging.DEBUG)
class TestChain(TestBase): class TestChain(TestBase):
def test_chain_spec_str(self): 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) s = ChainSpec('foo', 'bar', 3)
self.assertEqual('foo:bar:3', str(s)) 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')
s = ChainSpec.from_chain_str('foo:bar:3:baz') 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): with self.assertRaises(ValueError):
s = ChainSpec.from_chain_str('foo:bar:a') s = ChainSpec.from_chain_str('foo:bar:a')
s = ChainSpec.from_chain_str('foo:bar') s = ChainSpec.from_chain_str('foo:bar')
s = ChainSpec.from_chain_str('foo') 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): def test_chain_spec_dict(self):
s = 'foo:bar:3:baz' ss = 'foo:bar:3:baz:inky:pinky:blinky'
c = ChainSpec.from_chain_str('foo:bar:3:baz') c = ChainSpec.from_chain_str(ss)
d = c.asdict() d = c.asdict()
self.assertEqual(d['arch'], 'foo') self.assertEqual(d['arch'], 'foo')
self.assertEqual(d['fork'], 'bar') self.assertEqual(d['fork'], 'bar')
self.assertEqual(d['network_id'], 3) self.assertEqual(d['network_id'], 3)
self.assertEqual(d['common_name'], 'baz') self.assertEqual(d['common_name'], 'baz')
self.assertEqual(d['custom'], ['inky', 'pinky', 'blinky'])
cc = ChainSpec.from_dict(d) 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 # standard imports
import unittest import unittest
import os import os
import logging
# local imports # local imports
import chainlib.cli import chainlib.cli
@@ -10,6 +11,8 @@ script_dir = os.path.dirname(os.path.realpath(__file__))
data_dir = os.path.join(script_dir, 'testdata') data_dir = os.path.join(script_dir, 'testdata')
config_dir = os.path.join(data_dir, 'config') config_dir = os.path.join(data_dir, 'config')
logging.basicConfig(level=logging.DEBUG)
class TestCli(unittest.TestCase): class TestCli(unittest.TestCase):