Compare commits

..

1 Commits

Author SHA1 Message Date
lash b8ee1ed97d
Bump vesrion 2022-04-28 15:42:14 +00:00
14 changed files with 541 additions and 429 deletions

View File

@ -1,20 +1,3 @@
- 0.3.1
* Make pending flag for nonce at rpc bootstrap settable
- 0.3.0
* Implement arg handling from aiee
* Implement value argument handling from aiee
- 0.2.1
* Fix bug in wire format generation for tx
- 0.2.0
* Consolidate genertic blcok, tx and tx result objects
- 0.1.3
* Remove superfluous arguments for chain settings instantiation
- 0.1.2
* Upgrade hexathon dep
- 0.1.1
* Add settings object
- 0.1.0
* Upgrade deps
- 0.0.23 - 0.0.23
* Configuration variable descriptions * Configuration variable descriptions
* Arg flags to names listing method * Arg flags to names listing method

View File

@ -2,8 +2,7 @@
import enum import enum
# local imports # local imports
from .tx import Tx from chainlib.tx import Tx
from .src import Src
class BlockSpec(enum.IntEnum): class BlockSpec(enum.IntEnum):
@ -13,28 +12,23 @@ class BlockSpec(enum.IntEnum):
LATEST = 0 LATEST = 0
class Block(Src): class Block:
"""Base class to extend for implementation specific block object. """Base class to extend for implementation specific block object.
""" """
tx_generator = Tx tx_generator = Tx
def __init__(self, src=None):
self.number = None
self.txs = []
self.author = None
self.get_tx = self.tx_index_by_hash def src(self):
self.tx = self.tx_by_index """Return implementation specific block representation.
self.fee_limit = 0 :rtype: dict
self.fee_cost = 0 :returns: Block representation
self.parent_hash = None """
return self.block_src
super(Block, self).__init__(src=src)
def tx_by_index(self, idx): def tx(self, idx):
"""Return transaction object for transaction data at given index. """Return transaction object for transaction data at given index.
:param idx: Transaction index :param idx: Transaction index
@ -45,12 +39,28 @@ class Block(Src):
return self.tx_generator(self.txs[idx], self) return self.tx_generator(self.txs[idx], self)
def tx_index_by_hash(self, hsh): def tx_src(self, idx):
for tx in self.tx: """Return implementation specific transaction representation for transaction data at given index
if tx == hsh:
return tx :param idx: Transaction index
return -1 :type idx: int
:rtype: chainlib.tx.Tx
:returns: Transaction representation
"""
return self.txs[idx]
def __str__(self): def __str__(self):
return 'block {} {} ({} txs)'.format(self.number, self.hash, len(self.txs)) return 'block {} {} ({} txs)'.format(self.number, self.hash, len(self.txs))
@classmethod
def from_src(cls, src):
"""Instantiate an implementation specific block object from the given block representation.
:param src: Block representation
:type src: dict
:rtype: chainlib.block.Block
:returns: Block object
"""
return cls(src)

View File

@ -1,143 +1,227 @@
# standard imports # standard imports
import logging import logging
import argparse import argparse
#import enum import enum
#import os import os
#import select import select
import sys import sys
#import re import re
# external imports # local imports
from aiee.arg import ( from .base import (
ArgFlag as BaseArgFlag, default_config_dir,
Arg as BaseArg, Flag,
process_args, argflag_std_target,
) )
logg = logging.getLogger(__name__) logg = logging.getLogger(__name__)
#def stdin_arg(): def stdin_arg():
# """Retreive input arguments from stdin if they exist. """Retreive input arguments from stdin if they exist.
#
# Method does not block, and expects arguments to be ready on stdin before being called. Method does not block, and expects arguments to be ready on stdin before being called.
#
# :rtype: str :rtype: str
# :returns: Input arguments string :returns: Input arguments string
# """ """
# h = select.select([sys.stdin], [], [], 0) h = select.select([sys.stdin], [], [], 0)
# if len(h[0]) > 0: if len(h[0]) > 0:
# v = h[0][0].read() v = h[0][0].read()
# return v.rstrip() return v.rstrip()
# return None return None
_default_long_args = {
'-a': '--recipient',
'-e': '--executable-address',
'-s': '--send',
'-y': '--key-file',
}
_default_dest = {
'-a': 'recipient',
'-e': 'executable_address',
}
_default_fmt = 'human'
class ArgumentParser(argparse.ArgumentParser): 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
:type usage: str
:param description: Description string, passed to parent
:type description: str
:param epilog: Epilog string, passed to parent
:type epilog: str
"""
def __init__(self, arg_flags=0x0f, arg_long={}, env=os.environ, usage=None, description=None, epilog=None, default_format=_default_fmt, *args, **kwargs):
super(ArgumentParser, self).__init__(usage=usage, description=description, epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter, *args, **kwargs)
self.pos_args = []
self.long_args = _default_long_args
self.arg_dest = _default_dest
self.default_format = default_format
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, append=False, required=True):
"""Add a positional argument.
Stdin piping will only be possible in the event a single positional argument is defined.
If the "required" is set, the resulting parsed arguments must have provided a value either from stdin or excplicitly on the command line.
:param name: Attribute name of argument
:type name: str
:param type: Argument type
:type type: str
:param help: Help string
:type help: str
:param required: If true, argument will be set to required
:type required: bool
"""
self.pos_args.append((name, type, help, required, append,))
def parse_args(self, argv=sys.argv[1:]): def parse_args(self, argv=sys.argv[1:]):
if '--dumpconfig' in argv: """Overrides the argparse.ArgumentParser.parse_args method.
argv = [argv[0], '--dumpconfig']
return super(ArgumentParser, self).parse_args(args=argv) Implements reading arguments from stdin if a single positional argument is defined (and not set to required).
If the "required" was set for the single positional argument, the resulting parsed arguments must have provided a value either from stdin or excplicitly on the command line.
:param argv: Argument vector to process
:type argv: list
"""
if len(self.pos_args) == 1:
arg = self.pos_args[0]
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]:
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:
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 getattr(args, 'dumpconfig', None) != None:
return args
if len(self.pos_args) == 1:
arg = self.pos_args[0]
argname = arg[0]
required = arg[3]
if getattr(args, arg[0], None) == None:
argp = stdin_arg()
if argp == None and required:
self.error('need first positional argument or value from stdin')
setattr(args, arg[0], argp)
return args
class ArgFlag(BaseArgFlag): def process_flags(self, arg_flags, env):
"""Configures the arguments of the parser using the provided flags.
def __init__(self): Environment variables are used for default values for:
super(ArgFlag, self).__init__()
self.add('verbose') CONFINI_DIR: -c, --config
self.add('config') CONFINI_ENV_PREFIX: --env-prefix
self.add('raw')
self.add('env')
self.add('provider')
self.add('chain_spec')
self.add('target')
self.add('unsafe')
self.add('seq')
self.add('key_file')
self.add('fee')
self.add('nonce')
self.add('no_target')
self.add('exec')
self.add('wallet')
self.add('wait')
self.add('wait_all')
self.add('send')
self.add('rpc_auth')
self.add('fmt_human')
self.add('fmt_wire')
self.add('fmt_rpc')
self.add('veryverbose')
self.add('path')
self.add('backend')
self.alias('sign', 'key_file', 'send')
self.alias('std_base', 'verbose', 'config', 'raw', 'env', 'target')
self.alias('std_base_read', 'verbose', 'config', 'raw', 'env', 'provider', 'chain_spec', 'seq')
self.alias('std_read', 'std_base', 'provider', 'chain_spec', 'unsafe', 'seq', 'sign', 'fee', 'target')
self.alias('std_write', 'verbose', 'config', 'raw', 'env', 'provider', 'chain_spec', 'unsafe', 'seq', 'key_file', 'sign', 'target', 'wait', 'wait_all', 'send', 'rpc_auth', 'nonce', 'fee')
self.alias('std_target', 'no_target', 'exec', 'wallet')
self.alias('state', 'backend', 'path')
class Arg(BaseArg):
def __init__(self, flags):
super(Arg, self).__init__(flags)
self.add_long('no-logs', 'verbose', typ=bool, help='Turn off all logging') This method is called by the constructor, and is not intended to be called directly.
self.add('v', 'verbose', typ=bool, help='Be verbose')
self.add('vv', 'verbose', check=False, typ=bool, help='Be more verbose')
self.add('vvv', 'veryverbose', check=False, typ=bool, help='Be morse verbose with custom tracing')
self.add('n', 'config', help='Configuration namespace') :param arg_flags: Argument flag bit vector to generate configuration values for.
self.set_long('n', 'namespace', dest='namespace') :type arg_flags: chainlib.cli.Flag
self.add('c', 'config', dest='config', help='Configuration directory') :param env: Environment variables
self.set_long('c', 'config') :type env: dict
self.add_long('dumpconfig', 'config', help='Output configuration and quit. Use with --raw to omit values and output schema only.') """
if arg_flags & Flag.VERBOSE:
self.add('a', 'wallet', dest='recipient', help='Recipient address') self.add_argument('--no-logs', dest='no_logs',action='store_true', help='Turn off all logging')
self.set_long('a', 'recipient') self.add_argument('-v', action='store_true', help='Be verbose')
self.add_argument('-vv', action='store_true', help='Be more verbose')
self.add('e', 'exec', dest='executable_address', help='Recipient address') if arg_flags & Flag.CONFIG:
self.set_long('e', 'executable') 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('w', 'wait', typ=bool, help='Wait for the last transaction to be confirmed') self.add_argument('--dumpconfig', type=str, choices=['env', 'ini'], help='Output configuration and quit. Use with --raw to omit values and output schema only.')
self.add('ww', 'wait', check=False, typ=bool, help='Wait for every transaction to be confirmed') if arg_flags & Flag.WAIT:
self.add_argument('-w', action='store_true', help='Wait for the last transaction to be confirmed')
self.add_long('env-prefix', 'env', help='environment prefix for variables to overwrite configuration') self.add_argument('-ww', action='store_true', help='Wait for every transaction to be confirmed')
if arg_flags & Flag.ENV_PREFIX:
self.add('p', 'provider', help='RPC HTTP(S) provider url') self.add_argument('--env-prefix', default=env.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
self.set_long('p', 'provider') if arg_flags & Flag.PROVIDER:
self.add_long('rpc-dialect', 'provider', help='RPC HTTP(S) backend dialect') self.add_argument('-p', '--rpc-provider', dest='p', type=str, help='RPC HTTP(S) provider url')
self.add_long('rpc-timeout', 'provider', help='RPC autentication credential values') self.add_argument('--rpc-dialect', dest='rpc_dialect', type=str, help='RPC HTTP(S) backend dialect')
self.add_long('rpc-proxy', 'provider', help='RPC autentication credential values') if arg_flags & Flag.NO_TARGET == 0:
self.add_argument('--height', default='latest', help='Block height to execute against')
self.add_long('height', 'target', default='latest', help='Block height to execute against') if arg_flags & Flag.RPC_AUTH:
self.add_argument('--rpc-auth', dest='rpc_auth', type=str, help='RPC autentication scheme')
self.add_long('rpc-auth', 'rpc_auth', help='RPC autentication scheme') self.add_argument('--rpc-credentials', dest='rpc_credentials', type=str, help='RPC autentication credential values')
self.add_long('rpc-credentials', 'rpc_auth', help='RPC autentication credential values') if arg_flags & Flag.CHAIN_SPEC:
self.add_argument('-i', '--chain-spec', dest='i', type=str, help='Chain specification string')
self.add('i', 'chain_spec', help='Chain specification string') if arg_flags & Flag.UNSAFE:
self.set_long('i', 'chain-spec') self.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Do not verify address checksums')
if arg_flags & Flag.SEQ:
self.add('u', 'unsafe', typ=bool, help='Do not verify address checksums') self.add_argument('--seq', action='store_true', help='Use sequential rpc ids')
self.set_long('u', 'unsafe') if arg_flags & Flag.KEY_FILE:
self.add_argument('-y', self.long_args['-y'], dest='y', type=str, help='Keystore file to use for signing or address')
self.add_long('seq', 'seq', typ=bool, help='Use sequential rpc ids') self.add_argument('--passphrase-file', dest='passphrase_file', type=str, help='File containing passphrase for keystore')
if arg_flags & Flag.SEND:
self.add('y', 'key_file', help='Keystore file to use for signing or address') self.add_argument('-s', self.long_args['-s'], dest='s', action='store_true', help='Send to network')
self.set_long('y', 'key_file') if arg_flags & Flag.RAW:
self.add_long('passphrase-file', 'key_file', help='Keystore file to use for signing or address') self.add_argument('--raw', action='store_true', help='Do not decode output')
if arg_flags & (Flag.SIGN | Flag.NONCE):
self.add('s', 'send', typ=bool, help='Send to network') self.add_argument('--nonce', type=int, help='override nonce')
self.set_long('s', 'send') if arg_flags & (Flag.SIGN | Flag.FEE):
self.add_argument('--fee-price', dest='fee_price', type=int, help='override fee price')
self.add_long('raw', 'raw', typ=bool, help='Do not decode output') self.add_argument('--fee-limit', dest='fee_limit', type=int, help='override fee limit')
self.add('0', 'raw', typ=bool, help='Omit newline to output') # wtf?
#if arg_flags & argflag_std_target == 0:
self.add_long('nonce', 'nonce', typ=int, help='override nonce') # arg_flags |= Flag.WALLET
self.add_long('fee-price', 'fee', typ=int, help='override fee price') if arg_flags & Flag.EXEC:
self.add_long('fee-limit', 'fee', typ=int, help='override fee limit') 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_long('state-path', 'path', help='Path to store state data under') self.add_argument('-a', self.long_args['-a'], dest=self.arg_dest['-a'], type=str, help='recipient address')
self.add_long('runtime-path', 'path', help='Path to store volatile data under') if arg_flags & (Flag.FMT_HUMAN | Flag.FMT_WIRE | Flag.FMT_RPC):
self.add_long('backend', 'backend', help='Backend to use for data storage') format_choices = []
if arg_flags & Flag.FMT_HUMAN:
format_choices.append('human')
if arg_flags & Flag.FMT_WIRE:
format_choices.append('bin')
if arg_flags & Flag.FMT_RPC:
format_choices.append('rpc')
self.add_argument('-f', '--format', type=str, choices=format_choices, help='output formatting (default: {})'.format(self.default_format))

View File

@ -36,102 +36,277 @@ class Config(confini.Config):
default_base_config_dir = default_parent_config_dir default_base_config_dir = default_parent_config_dir
default_fee_limit = 0 default_fee_limit = 0
def __init__(self, config_dir=None, namespace=None):
self.namespace = namespace @staticmethod
if config_dir == None: def override_defaults(base_dir=None, default_fee_limit=None):
config_dir = self.default_base_config_dir if base_dir != None:
if self.namespace != None: Config.default_base_config_dir = os.path.realpath(base_dir)
config_dir = os.path.join(config_dir, namespace) if default_fee_limit != None:
super(Config, self).__init__(config_dir) Config.default_fee_limit = int(default_fee_limit)
def add_user_dir(self, v): @classmethod
if self.namespace != None: def from_args(cls, args, arg_flags=0x0f, env=os.environ, extra_args={}, base_config_dir=None, default_config_dir=None, user_config_dir=None, default_fee_limit=None, logger=None, load_callback=None, dump_writer=sys.stdout):
v = os.path.join(v, self.namespace) """Parses arguments in argparse.ArgumentParser instance, then match and override configuration values that match them.
return super(Config, self).add_override_dir(v)
The method processes all known argument flags from chainlib.cli.Flag passed in the "args" argument.
def process_config(config, arg, args, flags): All entries in extra_args may be used to associate arguments not defined in the argument flags with configuration variables, in the following manner:
if arg.match('env', flags): - The value of argparser.ArgumentParser instance attribute with the dictionary key string is looked up.
config.set_env_prefix(getattr(args, 'env_prefix')) - If the value is None (defined but empty), any existing value for the configuration directive will be kept.
- If the value of the extra_args dictionary entry is None, then the value will be stored in the configuration under the upper-case value of the key string, prefixed with "_" ("foo_bar" becomes "_FOO_BAR")
config.process() - If the value of the extra_args dictionary entries is a string, then the value will be stored in the configuration under that literal string.
args_override = {}
if arg.match('raw', flags):
config.add(getattr(args, 'raw', None), '_RAW')
if arg.match('provider', flags): Missing attributes defined by both the "args" and "extra_args" arguments will both raise an AttributeError.
args_override['RPC_PROVIDER'] = getattr(args, 'p')
args_override['RPC_DIALECT'] = getattr(args, 'rpc_dialect')
if arg.match('chain_spec', flags): The python package "confini" is used to process and render the configuration.
args_override['CHAIN_SPEC'] = getattr(args, 'i')
if arg.match('config', flags): The confini config schema is determined in the following manner:
config.add(getattr(args, 'namespace', None), 'CONFIG_USER_NAMESPACE')
if arg.match('key_file', flags): - If nothing is set, only the config folder in chainlib.data.config will be used as schema.
args_override['WALLET_KEY_FILE'] = getattr(args, 'y') - If base_config_dir is a string or list, the config directives from the path(s) will be added to the schema.
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')
if arg.match('backend', flags): The global override config directories are determined in the following manner:
args_override['STATE_BACKEND'] = getattr(args, 'backend')
if arg.match('path', flags): - If no default_config_dir is defined, the environment variable CONFINI_DIR will be used.
args_override['STATE_PATH'] = getattr(args, 'state_path') - If default_config_dir is a string or list, values from the config directives from the path(s) will override those defined in the schema(s).
config.dict_override(args_override, 'cli args', allow_empty=True) The user override config directories work the same way as the global ones, but the namespace - if defined - are dependent on them. They are only applied if the CONFIG arg flag is set. User override config directories are determined in the following manner:
if arg.match('provider', flags): - If --config argument is not defined and the pyxdg module is present, the first available xdg basedir is used.
if arg.match('target', flags): - If --config argument is defined, the directory defined by its value will be used.
The namespace, if defined, will be stored under the CONFIG_USER_NAMESPACE configuration key.
:param args: Argument parser object
:type args: argparse.ArgumentParser
:param arg_flags: Argument flags defining which arguments to process into configuration.
:type arg_flags: confini.cli.args.ArgumentParser
:param env: Environment variables selection
:type env: dict
:param extra_args: Extra arguments to process and override.
:type extra_args: dict
:param base_config_dir: Path(s) to one or more directories extending the base chainlib config schema.
:type base_config_dir: list or str
:param default_config_dir: Path(s) to one or more directories overriding the defaults defined in the schema config directories.
:type default_config_dir: list or str
:param user_config_dir: User xdg config basedir, with namespace
:type user_config_dir: str
:param default_fee_limit: Default value for fee limit argument
:type default_fee_limit: int
:param logger: Logger instance to use during argument processing (will use package namespace logger if None)
:type logger: logging.Logger
:param load_callback: Callback receiving config instance as argument after config processing and load completes.
:type load_callback: function
:raises AttributeError: Attribute defined in flag not found in parsed arguments
: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 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]
if user_config_dir == None:
try:
import xdg.BaseDirectory
user_config_dir = xdg.BaseDirectory.load_first_config('chainlib/eth')
except ModuleNotFoundError:
pass
# if one or more additional base dirs are defined, add these after the default base dir
# the consecutive dirs cannot include duplicate sections
if base_config_dir != None:
logg.debug('have explicit base config addition {}'.format(base_config_dir))
if isinstance(base_config_dir, str):
base_config_dir = [base_config_dir]
for d in base_config_dir:
config_dir.append(d)
logg.debug('processing config dir {}'.format(config_dir))
# confini dir env var will be used for override configs only in this case
if default_config_dir == None:
default_config_dir = env.get('CONFINI_DIR')
if default_config_dir != None:
if isinstance(default_config_dir, str):
default_config_dir = [default_config_dir]
for d in default_config_dir:
override_config_dirs.append(d)
# process config command line arguments
if arg_flags & Flag.CONFIG:
effective_user_config_dir = getattr(args, 'config', None)
if effective_user_config_dir == None:
effective_user_config_dir = user_config_dir
if effective_user_config_dir != None:
if getattr(args, 'namespace', None) != None:
effective_user_config_dir = os.path.join(effective_user_config_dir, args.namespace)
#if config_dir == None:
# config_dir = [cls.default_base_config_dir, effective_user_config_dir]
# logg.debug('using config arg as base config addition {}'.format(effective_user_config_dir))
#else:
override_config_dirs.append(effective_user_config_dir)
logg.debug('using config arg as config override {}'.format(effective_user_config_dir))
#if config_dir == None:
# if default_config_dir == None:
# default_config_dir = default_parent_config_dir
# config_dir = default_config_dir
# override_config_dirs = []
config = confini.Config(config_dir, env_prefix=env_prefix, override_dirs=override_config_dirs)
config.process()
config.add(getattr(args, 'raw'), '_RAW')
args_override = {}
if arg_flags & Flag.PROVIDER:
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', allow_empty=True)
if arg_flags & (Flag.PROVIDER | Flag.NO_TARGET) == Flag.PROVIDER:
config.add(getattr(args, 'height'), '_HEIGHT') config.add(getattr(args, 'height'), '_HEIGHT')
if arg_flags & Flag.UNSAFE:
if arg.match('unsafe', flags): config.add(getattr(args, 'u'), '_UNSAFE')
config.add(getattr(args, 'u'), '_UNSAFE') if arg_flags & (Flag.SIGN | Flag.FEE):
if arg.match('sign', flags):
config.add(getattr(args, 's'), '_RPC_SEND')
if arg.match('fee', flags):
config.add(getattr(args, 'fee_price'), '_FEE_PRICE') config.add(getattr(args, 'fee_price'), '_FEE_PRICE')
fee_limit = getattr(args, 'fee_limit') fee_limit = getattr(args, 'fee_limit')
if fee_limit == None: if fee_limit == None:
fee_limit = int(config.get('CHAIN_MIN_FEE')) fee_limit = default_fee_limit
if fee_limit == None:
fee_limit = cls.default_fee_limit
config.add(fee_limit, '_FEE_LIMIT') config.add(fee_limit, '_FEE_LIMIT')
if arg_flags & (Flag.SIGN | Flag.NONCE):
if arg.match('nonce', flags):
config.add(getattr(args, 'nonce'), '_NONCE') config.add(getattr(args, 'nonce'), '_NONCE')
if arg.match('wait', flags): if arg_flags & Flag.SIGN:
config.add(getattr(args, 's'), '_RPC_SEND')
# handle wait
wait = 0
if args.w:
wait |= Flag.WAIT
if args.ww: if args.ww:
config.add(True, '_WAIT_ALL') wait |= Flag.WAIT_ALL
config.add(True, '_WAIT') wait_last = wait & (Flag.WAIT | Flag.WAIT_ALL)
elif args.w: config.add(bool(wait_last), '_WAIT')
config.add(True, '_WAIT') wait_all = wait & Flag.WAIT_ALL
config.add(bool(wait_all), '_WAIT_ALL')
if arg.match('seq', flags):
config.add(getattr(args, 'seq'), '_SEQ')
if arg.match('wallet', flags):
config.add(getattr(args, 'recipient'), '_RECIPIENT')
if arg.match('exec', flags):
config.add(getattr(args, 'executable_address'), '_EXEC_ADDRESS')
if arg.match('rpc_auth', flags):
config.add(getattr(args, 'rpc_auth'), 'RPC_AUTH')
config.add(getattr(args, 'rpc_credentials'), 'RPC_CREDENTIALS')
return config if arg_flags & Flag.SEQ:
config.add(getattr(args, 'seq'), '_SEQ')
if arg_flags & Flag.WALLET:
config.add(getattr(args, 'recipient'), '_RECIPIENT')
if arg_flags & Flag.EXEC:
config.add(getattr(args, 'executable_address'), '_EXEC_ADDRESS')
if arg_flags & Flag.CONFIG:
config.add(getattr(args, 'namespace'), 'CONFIG_USER_NAMESPACE')
if arg_flags & Flag.RPC_AUTH:
config.add(getattr(args, 'rpc_auth'), 'RPC_AUTH')
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()
r = getattr(args, k)
existing_r = None
try:
existing_r = config.get(v)
except KeyError:
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', 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:
load_callback(config)
return config

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, verify_identity=config.true('RPC_VERIFY'), timeout=float(config.get('RPC_TIMEOUT'))) 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

@ -5,19 +5,10 @@ credentials =
dialect = default dialect = default
scheme = http scheme = http
verify = 1 verify = 1
timeout = 10.0
proxy =
[chain] [chain]
spec = spec =
min_fee = 0
max_fee = 0
[wallet] [wallet]
key_file = key_file =
passphrase = passphrase =
[state]
path =
runtime_path =
backend =

View File

@ -11,18 +11,13 @@ class JSONRPCException(RPCException):
pass pass
class InitializationError(Exception):
"""Base error for errors occurring while processing settings
"""
pass
class ExecutionError(Exception): class ExecutionError(Exception):
"""Base error for transaction execution failures """Base error for transaction execution failures
""" """
pass pass
class SignerMissingException(InitializationError): class SignerMissingException(Exception):
"""Raised when attempting to retrieve a signer when none has been added """Raised when attempting to retrieve a signer when none has been added
""" """

View File

@ -1,8 +1,8 @@
class NonceOracle: class NonceOracle:
def __init__(self, address, id_generator=None, confirmed=False): def __init__(self, address):
self.address = address self.address = address
self.nonce = self.get_nonce(confirmed=confirmed) self.nonce = self.get_nonce()
def get_nonce(self): def get_nonce(self):

View File

@ -1,19 +1,20 @@
# external imports
from aiee.numbers import postfix_to_int
# local imports # local imports
from .chain import ChainSpec from .chain import ChainSpec
class ChainSettings: class ChainSettings:
def __init__(self): def __init__(self, include_sync=False, include_queue=False):
self.o = {} self.o = {}
self.get = self.o.get self.get = self.o.get
def set(self, k, v): def process_common(self, config):
self.o[k] = v self.o['CHAIN_SPEC'] = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
def process(self, config):
self.process_common(config)
def __str__(self): def __str__(self):
@ -23,27 +24,3 @@ class ChainSettings:
for k in ks: for k in ks:
s += '{}: {}\n'.format(k, self.o.get(k)) s += '{}: {}\n'.format(k, self.o.get(k))
return s return s
def process_settings_common(settings, config):
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
settings.set('CHAIN_SPEC', chain_spec)
return settings
def process_settings_value(settings, config):
value = None
try:
value = config.get('_VALUE')
except KeyError:
return settings
value = postfix_to_int(config.get('_VALUE'))
settings.set('VALUE', value)
return settings
def process_settings(settings, config):
settings = process_settings_common(settings, config)
settings = process_settings_value(settings, config)
return settings

View File

@ -4,7 +4,6 @@ import enum
class Status(enum.Enum): class Status(enum.Enum):
"""Representation of transaction status in network. """Representation of transaction status in network.
""" """
UNKNOWN = -1
PENDING = 0 PENDING = 0
SUCCESS = 1 SUCCESS = 1
ERROR = 2 ERROR = 2

View File

@ -1,9 +1,4 @@
# local imports class Tx:
from .status import Status
from .src import Src
class Tx(Src):
"""Base class to extend for implementation specific transaction objects. """Base class to extend for implementation specific transaction objects.
:param src: Transaction representation source :param src: Transaction representation source
@ -12,73 +7,8 @@ class Tx(Src):
:type block: chainlib.block.Block :type block: chainlib.block.Block
""" """
def __init__(self, src=None, block=None, result=None, strict=False): def __init__(self, src, block=None):
self.src = src
self.block = block self.block = block
self.index = -1 self.block_src = None
self.index = None
self.fee_limit = None
self.fee_price = None
self.nonce = None
self.value = 0
self.outputs = []
self.inputs = []
self.payload = None
self.result = None
super(Tx, self).__init__(src)
if block != None:
self.apply_block(block)
if result != None:
self.apply_result(result)
def apply_result(self, result):
self.result = result
def apply_block(self, block):
self.block = block
@property
def status(self):
if self.result == None:
return None
return self.result.status
@property
def status_name(self):
if self.result == None:
return None
return self.result.status.name
def generate_wire(self, chain_spec):
pass
def as_dict(self):
raise NotImplementedError()
def __str__(self):
if self.block != None:
return 'tx {} status {} block {} index {}'.format(self.display_hash(), self.status_name(), self.block.number, self.index)
else:
return 'tx {} status {}'.format(self.display_hash(), self.hash, self.status_name())
class TxResult(Src):
def __init__(self, src=None):
self.status = Status.UNKNOWN
self.tx_index = None
self.block_hash = None
self.fee_cost = 0
super(TxResult, self).__init__(src=src)

View File

@ -1,5 +1,4 @@
funga~=0.5.2 funga~=0.5.2
pysha3==1.0.2 pysha3==1.0.2
hexathon~=0.1.7 hexathon~=0.1.5
confini~=0.6.1 confini~=0.6.0
aiee~=0.3.1

View File

@ -3,7 +3,7 @@ name=chainlib
license=WTFPL2 license=WTFPL2
author_email=dev@holbrook.no author_email=dev@holbrook.no
description=Generic blockchain access library and tooling description=Generic blockchain access library and tooling
version=0.3.1 version=0.1.1
url=https://gitlab.com/chaintools/chainlib url=https://gitlab.com/chaintools/chainlib
author=Louis Holbrook author=Louis Holbrook

View File

@ -3,20 +3,10 @@ import unittest
import os import os
import logging import logging
# external imports
from aiee.arg import process_args
# local imports # local imports
#from chainlib.cli.base import argflag_std_base import chainlib.cli
from chainlib.cli.arg import ( from chainlib.cli.base import argflag_std_base
ArgFlag,
Arg,
ArgumentParser,
)
from chainlib.cli.config import (
Config,
process_config,
)
script_dir = os.path.dirname(os.path.realpath(__file__)) 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')
@ -25,45 +15,28 @@ logging.basicConfig(level=logging.DEBUG)
class TestCli(unittest.TestCase): class TestCli(unittest.TestCase):
def setUp(self):
self.flags = ArgFlag()
self.arg = Arg(self.flags)
def test_args_process_single(self): def test_args_process_single(self):
ap = ArgumentParser() ap = chainlib.cli.arg.ArgumentParser()
flags = self.flags.VERBOSE | self.flags.CONFIG
process_args(ap, self.arg, flags)
argv = [ argv = [
'-vv', '-vv',
'-n', '-n',
'foo', 'foo',
] ]
args = ap.parse_args(argv) args = ap.parse_args(argv)
config = Config(config_dir) config = chainlib.cli.config.Config.from_args(args)
config = process_config(config, self.arg, args, flags)
self.assertEqual(config.get('CONFIG_USER_NAMESPACE'), 'foo') self.assertEqual(config.get('CONFIG_USER_NAMESPACE'), 'foo')
def test_args_process_schema_override(self): def test_args_process_schema_override(self):
ap = ArgumentParser() ap = chainlib.cli.arg.ArgumentParser()
flags = self.flags.VERBOSE | self.flags.CONFIG
process_args(ap, self.arg, flags)
args = ap.parse_args([]) args = ap.parse_args([])
config = chainlib.cli.config.Config.from_args(args, base_config_dir=config_dir)
config = Config(config_dir)
config = process_config(config, self.arg, args, flags)
self.assertEqual(config.get('FOO_BAR'), 'baz') self.assertEqual(config.get('FOO_BAR'), 'baz')
def test_args_process_arg_override(self): def test_args_process_arg_override(self):
ap = ArgumentParser() ap = chainlib.cli.arg.ArgumentParser()
flags = self.flags.VERBOSE | self.flags.CONFIG
process_args(ap, self.arg, flags)
argv = [ argv = [
'-c', '-c',
config_dir, config_dir,
@ -71,57 +44,53 @@ class TestCli(unittest.TestCase):
'foo', 'foo',
] ]
args = ap.parse_args(argv) args = ap.parse_args(argv)
config = Config(config_dir, namespace=args.namespace) config = chainlib.cli.config.Config.from_args(args, base_config_dir=config_dir)
config = process_config(config, self.arg, args, flags)
self.assertEqual(config.get('FOO_BAR'), 'bazbazbaz') self.assertEqual(config.get('FOO_BAR'), 'bazbazbaz')
def test_args_process_internal_override(self): def test_args_process_internal_override(self):
ap = ArgumentParser() ap = chainlib.cli.arg.ArgumentParser()
flags = self.flags.VERBOSE | self.flags.CONFIG | self.flags.CHAIN_SPEC
process_args(ap, self.arg, flags)
args = ap.parse_args() args = ap.parse_args()
default_config_dir = os.path.join(config_dir, 'default') default_config_dir = os.path.join(config_dir, 'default')
config = chainlib.cli.config.Config.from_args(args, default_config_dir=default_config_dir)
config = Config(default_config_dir)
config = process_config(config, self.arg, args, flags)
self.assertEqual(config.get('CHAIN_SPEC'), 'baz:bar:13:foo') self.assertEqual(config.get('CHAIN_SPEC'), 'baz:bar:13:foo')
user_config_dir = os.path.join(default_config_dir, 'user') user_config_dir = os.path.join(default_config_dir, 'user')
config = Config(default_config_dir) config = chainlib.cli.config.Config.from_args(args, default_config_dir=default_config_dir, user_config_dir=user_config_dir)
config.add_override_dir(user_config_dir)
config = process_config(config, self.arg, args, flags)
self.assertEqual(config.get('CHAIN_SPEC'), 'foo:foo:666:foo') self.assertEqual(config.get('CHAIN_SPEC'), 'foo:foo:666:foo')
config = Config(default_config_dir) config = chainlib.cli.config.Config.from_args(args, default_config_dir=default_config_dir, user_config_dir=default_config_dir)
config = process_config(config, self.arg, args, flags)
self.assertEqual(config.get('CHAIN_SPEC'), 'baz:bar:13:foo') self.assertEqual(config.get('CHAIN_SPEC'), 'baz:bar:13:foo')
ap = ArgumentParser() ap = chainlib.cli.arg.ArgumentParser()
process_args(ap, self.arg, flags)
argv = [ argv = [
'-n', '-n',
'user', 'user',
] ]
args = ap.parse_args(argv) args = ap.parse_args(argv)
config = Config(default_config_dir, namespace=args.namespace) config = chainlib.cli.config.Config.from_args(args, default_config_dir=default_config_dir, user_config_dir=default_config_dir)
config = process_config(config, self.arg, args, flags)
self.assertEqual(config.get('CHAIN_SPEC'), 'foo:foo:666:foo') self.assertEqual(config.get('CHAIN_SPEC'), 'foo:foo:666:foo')
def test_all_args(self): def test_args_process_extra(self):
ap = ArgumentParser() ap = chainlib.cli.arg.ArgumentParser()
flags = self.flags.all ap.add_argument('--foo', type=str)
process_args(ap, self.arg, flags) argv = [
'--foo',
'bar',
]
args = ap.parse_args(argv)
extra_args = {
'foo': None,
}
config = chainlib.cli.config.Config.from_args(args, extra_args=extra_args)
self.assertEqual(config.get('_FOO'), 'bar')
args = ap.parse_args([ extra_args = {
'-y', 'foo', 'foo': 'FOOFOO',
'-i', 'foo:bar:42:baz', }
]) config = chainlib.cli.config.Config.from_args(args, extra_args=extra_args)
self.assertEqual(config.get('FOOFOO'), 'bar')
config = Config()
config = process_config(config, self.arg, args, flags)
if __name__ == '__main__': if __name__ == '__main__':