coin-specific and protocol-specific configuration options
Rationale: to enable communication with multiple coin daemons on multiple hosts
in a single program invocation, making possible the implementation of asset
swap functionality, for instance
Coin-specific options are prefixed with a coin symbol, proto-specific options
with a coin symbol plus a network name.
Coin- and protocol-specific options override their non-prefixed counterparts.
They are available via the command line, configuration file and Config API.
Currently available options:
Option Supported Prefixes
tw_name btc ltc bch
rpc_user btc ltc bch
rpc_password btc ltc bch
rpc_host btc ltc bch eth etc
rpc_port btc ltc bch eth etc xmr
ignore_daemon_version btc ltc bch eth etc xmr
max_tx_fee btc ltc bch eth etc
chain_names eth_mainnet eth_testnet etc_mainnet etc_testnet
Example:
$ mmgen-tool --coin=ltc --ltc-tw-name=ltc2 --ltc-ignore-daemon-version twview
Help:
$ mmgen-tool --longhelp
$ view mmgen/data/mmgen.cfg
Testing:
$ test/daemontest.py rpc.btc rpc.geth
$ test/cmdtest.py help opts cfgfile
This commit is contained in:
parent
6b7548f84b
commit
f8a312e407
20 changed files with 377 additions and 118 deletions
78
mmgen/cfg.py
78
mmgen/cfg.py
|
|
@ -280,6 +280,8 @@ class Config(Lockable):
|
|||
('autosign', 'outdir'),
|
||||
)
|
||||
|
||||
# proto-specific only: eth_mainnet_chain_names eth_testnet_chain_names
|
||||
# coin-specific only: bch_cashaddr (alias of cashaddr)
|
||||
_cfg_file_opts = (
|
||||
'autochg_ignore_labels',
|
||||
'color',
|
||||
|
|
@ -289,6 +291,7 @@ class Config(Lockable):
|
|||
'force_256_color',
|
||||
'hash_preset',
|
||||
'http_timeout',
|
||||
'ignore_daemon_version', # also coin-specific
|
||||
'macos_autosign_ramdisk_size',
|
||||
'max_input_size',
|
||||
'max_tx_file_size',
|
||||
|
|
@ -298,27 +301,15 @@ class Config(Lockable):
|
|||
'no_license',
|
||||
'quiet',
|
||||
'regtest',
|
||||
'rpc_host',
|
||||
'rpc_password',
|
||||
'rpc_port',
|
||||
'rpc_user',
|
||||
'rpc_host', # also coin-specific
|
||||
'rpc_password', # also coin-specific
|
||||
'rpc_port', # also coin-specific
|
||||
'rpc_user', # also coin-specific
|
||||
'scroll',
|
||||
'subseeds',
|
||||
'testnet',
|
||||
'usr_randchars',
|
||||
'bch_cashaddr',
|
||||
'bch_max_tx_fee',
|
||||
'btc_max_tx_fee',
|
||||
'eth_max_tx_fee',
|
||||
'ltc_max_tx_fee',
|
||||
'bch_ignore_daemon_version',
|
||||
'btc_ignore_daemon_version',
|
||||
'etc_ignore_daemon_version',
|
||||
'eth_ignore_daemon_version',
|
||||
'ltc_ignore_daemon_version',
|
||||
'xmr_ignore_daemon_version',
|
||||
'eth_mainnet_chain_names',
|
||||
'eth_testnet_chain_names')
|
||||
'tw_name', # also coin-specific
|
||||
'usr_randchars')
|
||||
|
||||
# Supported environmental vars
|
||||
# The corresponding attributes (lowercase, without 'mmgen_') must exist in the class.
|
||||
|
|
@ -563,6 +554,9 @@ class Config(Lockable):
|
|||
|
||||
del self._cloned
|
||||
|
||||
if hasattr(self, 'bch_cashaddr') and not hasattr(self, 'cashaddr'):
|
||||
self.cashaddr = self.bch_cashaddr
|
||||
|
||||
self._lock()
|
||||
|
||||
if need_proto:
|
||||
|
|
@ -634,34 +628,29 @@ class Config(Lockable):
|
|||
non_auto_opts = []
|
||||
already_set = tuple(self._uopts) + env_cfg
|
||||
|
||||
def set_opt(d, obj, name, refval):
|
||||
val = ucfg.parse_value(d.value, refval)
|
||||
if not val:
|
||||
die('CfgFileParseError', f'Parse error in file {ucfg.fn!r}, line {d.lineno}')
|
||||
val_conv = conv_type(name, val, refval, src=ucfg.fn)
|
||||
setattr(obj, name, val_conv)
|
||||
non_auto_opts.append(name)
|
||||
|
||||
for d in ucfg.get_lines():
|
||||
if d.name in self._cfg_file_opts:
|
||||
ns = d.name.split('_')
|
||||
if ns[0] in gc.core_coins:
|
||||
if not need_proto:
|
||||
continue
|
||||
nse, tn = (
|
||||
(ns[2:], ns[1]=='testnet') if len(ns) > 2 and ns[1] in ('mainnet', 'testnet') else
|
||||
(ns[1:], False)
|
||||
)
|
||||
# no instance yet, so override _class_ attr:
|
||||
cls = init_proto(self, ns[0], tn, need_amt=True, return_cls=True)
|
||||
attr = '_'.join(nse)
|
||||
else:
|
||||
cls = self
|
||||
attr = d.name
|
||||
refval = getattr(cls, attr)
|
||||
val = ucfg.parse_value(d.value, refval)
|
||||
if not val:
|
||||
die('CfgFileParseError', f'Parse error in file {ucfg.fn!r}, line {d.lineno}')
|
||||
val_conv = conv_type(attr, val, refval, src=ucfg.fn)
|
||||
if not attr in already_set:
|
||||
setattr(cls, attr, val_conv)
|
||||
non_auto_opts.append(attr)
|
||||
if not d.name in already_set:
|
||||
set_opt(d, self, d.name, getattr(self, d.name))
|
||||
elif d.name in self._autoset_opts:
|
||||
autoset_opts[d.name] = d.value
|
||||
elif d.name in self._auto_typeset_opts:
|
||||
auto_typeset_opts[d.name] = d.value
|
||||
elif any(d.name.startswith(coin + '_') for coin in gc.rpc_coins):
|
||||
if need_proto and not d.name in already_set:
|
||||
try:
|
||||
refval = init_proto(self, d.name.split('_', 1)[0]).get_opt_clsval(self, d.name)
|
||||
except AttributeError:
|
||||
die('CfgFileParseError', f'{d.name!r}: unrecognized option in {ucfg.fn!r}, line {d.lineno}')
|
||||
set_opt(d, self, d.name, refval)
|
||||
else:
|
||||
die('CfgFileParseError', f'{d.name!r}: unrecognized option in {ucfg.fn!r}, line {d.lineno}')
|
||||
|
||||
|
|
@ -946,7 +935,8 @@ def conv_type(name, val, refval, src, invert_bool=False):
|
|||
d = '' if src in ('cmdline', 'cfg', 'env') else f' in {src!r}',
|
||||
e = type(refval).__name__))
|
||||
|
||||
if type(refval) is bool:
|
||||
# refval is None = boolean opt with no cmdline parameter
|
||||
if type(refval) is bool or refval is None:
|
||||
v = str(val).lower()
|
||||
ret = (
|
||||
True if v in ('true', 'yes', '1', 'on') else
|
||||
|
|
@ -954,6 +944,12 @@ def conv_type(name, val, refval, src, invert_bool=False):
|
|||
None
|
||||
)
|
||||
return do_fail() if ret is None else (not ret) if invert_bool else ret
|
||||
elif isinstance(refval, (list, tuple)):
|
||||
if src == 'cmdline':
|
||||
return type(refval)(val.split(','))
|
||||
else:
|
||||
assert isinstance(val, (list, tuple)), f'{val}: not a list or tuple'
|
||||
return type(refval)(val)
|
||||
else:
|
||||
try:
|
||||
return type(refval)(not val if invert_bool else val)
|
||||
|
|
|
|||
|
|
@ -254,15 +254,17 @@ class CfgFileSampleUsr(cfg_file_sample):
|
|||
def show_changes(self, diff):
|
||||
ymsg('Warning: configuration file options have changed!\n')
|
||||
for desc in ('added', 'removed'):
|
||||
data = diff[desc]
|
||||
if data:
|
||||
opts = fmt_list([i.name for i in data], fmt='bare')
|
||||
msg(f' The following option{suf(data, verb="has")} been {desc}:\n {opts}\n')
|
||||
if desc == 'removed' and data:
|
||||
changed_opts = [i.name for i in diff[desc]
|
||||
# workaround for coin-specific opts previously listed in sample file:
|
||||
if not (i.name.endswith('_ignore_daemon_version') and desc == 'removed')
|
||||
]
|
||||
if changed_opts:
|
||||
msg(f' The following option{suf(changed_opts, verb="has")} been {desc}:')
|
||||
msg(f' {fmt_list(changed_opts, fmt="bare")}\n')
|
||||
if desc == 'removed':
|
||||
uc = mmgen_cfg_file(self.cfg, 'usr')
|
||||
usr_names = [i.name for i in uc.get_lines()]
|
||||
rm_names = [i.name for i in data]
|
||||
bad = sorted(set(usr_names).intersection(rm_names))
|
||||
bad = sorted(set(usr_names).intersection(changed_opts))
|
||||
if bad:
|
||||
m = f"""
|
||||
The following removed option{suf(bad, verb='is')} set in {uc.fn!r}
|
||||
|
|
|
|||
|
|
@ -420,7 +420,8 @@ class CoinDaemon(Daemon):
|
|||
ps_adj = (port_shift or 0) + (self.test_suite_port_shift if test_suite else 0)
|
||||
|
||||
# user-set values take precedence
|
||||
self.rpc_port = (cfg.rpc_port or 0) + (port_shift or 0) if cfg.rpc_port else ps_adj + self.get_rpc_port()
|
||||
usr_rpc_port = self.proto.rpc_port or cfg.rpc_port
|
||||
self.rpc_port = usr_rpc_port + (port_shift or 0) if usr_rpc_port else ps_adj + self.get_rpc_port()
|
||||
self.p2p_port = (
|
||||
p2p_port or (
|
||||
self.get_p2p_port() + ps_adj if self.get_p2p_port() and (test_suite or ps_adj) else None
|
||||
|
|
|
|||
|
|
@ -26,18 +26,6 @@
|
|||
# Uncomment to use testnet instead of mainnet:
|
||||
# testnet true
|
||||
|
||||
# Set the RPC host (the host the coin daemon is running on):
|
||||
# rpc_host localhost
|
||||
|
||||
# Set the RPC host's port number:
|
||||
# rpc_port 8332
|
||||
|
||||
# Uncomment to override 'rpcuser' from coin daemon config file:
|
||||
# rpc_user myusername
|
||||
|
||||
# Uncomment to override 'rpcpassword' from coin daemon config file:
|
||||
# rpc_password mypassword
|
||||
|
||||
# Choose the backend to use for JSON-RPC connections. Valid choices:
|
||||
# 'auto' (defaults to 'httplib'), 'httplib', 'requests', 'curl', 'aiohttp':
|
||||
# rpc_backend auto
|
||||
|
|
@ -89,27 +77,44 @@
|
|||
# setups with unusually large Monero wallets:
|
||||
# macos_autosign_ramdisk_size 10
|
||||
|
||||
############################
|
||||
## Ignore daemon versions ##
|
||||
############################
|
||||
# Ignore coin daemon version. This option also has coin-specific variants
|
||||
# (see below):
|
||||
# ignore_daemon_version false
|
||||
|
||||
# Ignore Bitcoin Core version:
|
||||
# btc_ignore_daemon_version false
|
||||
# Specify the tracking wallet name. This option also has coin-specific
|
||||
# variants (see below):
|
||||
# tw_name my-other-tracking-wallet
|
||||
|
||||
# Ignore Litecoin Core version:
|
||||
# ltc_ignore_daemon_version false
|
||||
#####################################################################
|
||||
## RPC options. These also have coin-specific variants (see below) ##
|
||||
#####################################################################
|
||||
|
||||
# Ignore Bitcoin Cash Node version:
|
||||
# bch_ignore_daemon_version false
|
||||
# Set the RPC host (the host the coin daemon is running on):
|
||||
# rpc_host localhost
|
||||
|
||||
# Ignore OpenEthereum version for ETH:
|
||||
# eth_ignore_daemon_version false
|
||||
# Set the RPC host's port number:
|
||||
# rpc_port 8332
|
||||
|
||||
# Ignore OpenEthereum version for ETC:
|
||||
# etc_ignore_daemon_version false
|
||||
# Uncomment to override 'rpcuser' from coin daemon config file:
|
||||
# rpc_user myusername
|
||||
|
||||
# Ignore daemon version for Monero:
|
||||
# xmr_ignore_daemon_version false
|
||||
# Uncomment to override 'rpcpassword' from coin daemon config file:
|
||||
# rpc_password mypassword
|
||||
|
||||
#######################################################################
|
||||
####################### COIN-SPECIFIC OPTIONS #######################
|
||||
#######################################################################
|
||||
## OPTION SUPPORTED PREFIXES ##
|
||||
## tw_name btc ltc bch ##
|
||||
## rpc_user btc ltc bch ##
|
||||
## rpc_password btc ltc bch ##
|
||||
## rpc_host btc ltc bch eth etc ##
|
||||
## rpc_port btc ltc bch eth etc xmr ##
|
||||
## ignore_daemon_version btc ltc bch eth etc xmr ##
|
||||
## max_tx_fee btc ltc bch eth etc ##
|
||||
## Note: prefix is followed by an underscore, e.g. ‘xmr_rpc_port’ ##
|
||||
#######################################################################
|
||||
#######################################################################
|
||||
|
||||
#####################
|
||||
## Altcoin options ##
|
||||
|
|
@ -139,7 +144,6 @@
|
|||
# Set the Monero wallet RPC password to something secure:
|
||||
# monero_wallet_rpc_password passw0rd
|
||||
|
||||
|
||||
#######################################################################
|
||||
## The following options are probably of interest only to developers ##
|
||||
#######################################################################
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
15.1.dev11
|
||||
15.1.dev12
|
||||
|
|
|
|||
|
|
@ -142,17 +142,18 @@ class GlobalHelp(Help):
|
|||
data_desc = 'global_opts_data'
|
||||
|
||||
def gen_text(self, opts):
|
||||
from ..opts import global_opts_pat
|
||||
from ..opts import global_opts_help_pat
|
||||
skipping = False
|
||||
for line in opts.global_opts_data['text']['options'][1:-3].splitlines():
|
||||
if m := global_opts_pat.match(line):
|
||||
if m[1] in opts.global_opts_filter.coin and m[2] in opts.global_opts_filter.cmd:
|
||||
yield ' --{} {}'.format(m[3], m[5])
|
||||
skipping = False
|
||||
else:
|
||||
skipping = True
|
||||
elif not skipping:
|
||||
yield line[4:]
|
||||
m = global_opts_help_pat.match(line)
|
||||
if m[1] == '+':
|
||||
if not skipping:
|
||||
yield line[4:]
|
||||
elif m[1] in opts.global_opts_filter.coin and m[2] in opts.global_opts_filter.cmd:
|
||||
yield ' --{} {}'.format(m[3], m[5]) if m[3] else m[5]
|
||||
skipping = False
|
||||
else:
|
||||
skipping = True
|
||||
|
||||
def print_help(cfg, opts):
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ def get_opt_by_substring(opt, opts):
|
|||
from .util import die
|
||||
die('CmdlineOptError', f'--{opt}: ambiguous option (not unique substring)')
|
||||
|
||||
def process_uopts(opts_data, opts):
|
||||
def process_uopts(cfg, opts_data, opts, need_proto):
|
||||
|
||||
from .util import die
|
||||
|
||||
|
|
@ -83,6 +83,32 @@ def process_uopts(opts_data, opts):
|
|||
if parm:
|
||||
die('CmdlineOptError', f'option --{_opt} requires no parameter')
|
||||
yield (negated_opts(opts)[_opt].name, False)
|
||||
elif (
|
||||
need_proto
|
||||
and (not gc.cmd_caps or gc.cmd_caps.rpc)
|
||||
and any(opt.startswith(coin + '-') for coin in gc.rpc_coins)):
|
||||
opt_name = opt.replace('-', '_')
|
||||
from .protocol import init_proto
|
||||
try:
|
||||
refval = init_proto(cfg, opt.split('-', 1)[0], return_cls=True).get_opt_clsval(cfg, opt_name)
|
||||
except AttributeError:
|
||||
die('CmdlineOptError', f'--{opt}: unrecognized option')
|
||||
else:
|
||||
if refval is None: # None == no parm
|
||||
if parm:
|
||||
die('CmdlineOptError', f'option --{opt} requires no parameter')
|
||||
yield (opt_name, True)
|
||||
else:
|
||||
from .cfg import conv_type
|
||||
if parm:
|
||||
yield (opt_name,
|
||||
conv_type(opt_name, parm, refval, src='cmdline'))
|
||||
else:
|
||||
idx += 1
|
||||
if idx == argv_len or (parm := sys.argv[idx]).startswith('-'):
|
||||
die('CmdlineOptError', f'missing parameter for option --{opt}')
|
||||
yield (opt_name,
|
||||
conv_type(opt_name, parm, refval, src='cmdline'))
|
||||
else:
|
||||
die('CmdlineOptError', f'--{opt}: unrecognized option')
|
||||
elif arg[0] == '-' and len(arg) > 1:
|
||||
|
|
@ -125,10 +151,11 @@ def process_uopts(opts_data, opts):
|
|||
return uopts, uargs
|
||||
|
||||
cmd_opts_pat = re.compile(r'^-([a-zA-Z0-9-]), --([a-zA-Z0-9-]{2,64})(=| )(.+)')
|
||||
global_opts_pat = re.compile(r'^\t\t\t(.)(.) --([a-zA-Z0-9-]{2,64})(=| )(.+)')
|
||||
global_opts_pat = re.compile(r'^\t\t\t(.)(.) --([a-z0-9-]{2,64})(=| )(.+)')
|
||||
global_opts_help_pat = re.compile(r'^\t\t\t(.)(.) (?:--([{}a-zA-Z0-9-]{2,64})(=| ))?(.+)')
|
||||
opt_tuple = namedtuple('cmdline_option', ['name', 'has_parm'])
|
||||
|
||||
def parse_opts(opts_data, opt_filter, global_opts_data, global_opts_filter):
|
||||
def parse_opts(cfg, opts_data, opt_filter, global_opts_data, global_opts_filter, need_proto):
|
||||
|
||||
def parse_cmd_opts_text():
|
||||
for line in opts_data['text']['options'].strip().splitlines():
|
||||
|
|
@ -146,7 +173,7 @@ def parse_opts(opts_data, opt_filter, global_opts_data, global_opts_filter):
|
|||
|
||||
opts = tuple(parse_cmd_opts_text()) + tuple(parse_global_opts_text())
|
||||
|
||||
uopts, uargs = process_uopts(opts_data, dict(opts))
|
||||
uopts, uargs = process_uopts(cfg, opts_data, dict(opts), need_proto)
|
||||
|
||||
return namedtuple('parsed_cmd_opts', ['user_opts', 'cmd_args', 'opts'])(
|
||||
uopts, # dict
|
||||
|
|
@ -214,10 +241,12 @@ class Opts:
|
|||
self.opts_data = opts_data
|
||||
|
||||
po = parsed_opts or parse_opts(
|
||||
cfg,
|
||||
opts_data,
|
||||
opt_filter,
|
||||
self.global_opts_data,
|
||||
self.global_opts_filter)
|
||||
self.global_opts_filter,
|
||||
need_proto)
|
||||
|
||||
cfg._args = po.cmd_args
|
||||
cfg._uopts = uopts = po.user_opts
|
||||
|
|
@ -283,6 +312,20 @@ class UserOpts(Opts):
|
|||
b- --bob Specify user ‘Bob’ in MMGen regtest mode
|
||||
b- --alice Specify user ‘Alice’ in MMGen regtest mode
|
||||
b- --carol Specify user ‘Carol’ in MMGen regtest mode
|
||||
rr COIN-SPECIFIC OPTIONS:
|
||||
rr For descriptions, refer to the non-prefixed versions of these options above
|
||||
rr Prefixed options override their non-prefixed counterparts
|
||||
rr OPTION SUPPORTED PREFIXES
|
||||
rr --PREFIX-ignore-daemon-version btc ltc bch eth etc xmr
|
||||
br --PREFIX-tw-name btc ltc bch
|
||||
Rr --PREFIX-rpc-host btc ltc bch eth etc
|
||||
rr --PREFIX-rpc-port btc ltc bch eth etc xmr
|
||||
br --PREFIX-rpc-user btc ltc bch
|
||||
br --PREFIX-rpc-password btc ltc bch
|
||||
Rr --PREFIX-max-tx-fee btc ltc bch eth etc
|
||||
Rr PROTO-SPECIFIC OPTIONS:
|
||||
Rr Option Supported Prefixes
|
||||
Rr --PREFIX-chain-names eth-mainnet eth-testnet etc-mainnet etc-testnet
|
||||
""",
|
||||
},
|
||||
'code': {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ class mainnet(mainnet):
|
|||
caps = ()
|
||||
coin_amt = 'BCHAmt'
|
||||
max_tx_fee = 0.1
|
||||
ignore_daemon_version = False
|
||||
cashaddr_pfx = 'bitcoincash'
|
||||
cashaddr = True
|
||||
|
||||
|
|
|
|||
|
|
@ -50,9 +50,19 @@ class mainnet(CoinProtocol.Secp256k1): # chainparams.cpp
|
|||
diff_adjust_interval = 2016
|
||||
max_halvings = 64
|
||||
start_subsidy = 50
|
||||
ignore_daemon_version = False
|
||||
max_int = 0xffffffff
|
||||
|
||||
coin_cfg_opts = (
|
||||
'ignore_daemon_version',
|
||||
'rpc_host',
|
||||
'rpc_port',
|
||||
'rpc_user',
|
||||
'rpc_password',
|
||||
'tw_name',
|
||||
'max_tx_fee',
|
||||
'cashaddr',
|
||||
)
|
||||
|
||||
def encode_wif(self, privbytes, pubkey_type, compressed): # input is preprocessed hex
|
||||
assert len(privbytes) == self.privkey_len, f'{len(privbytes)} bytes: incorrect private key length!'
|
||||
assert pubkey_type in self.wif_ver_bytes, f'{pubkey_type!r}: invalid pubkey_type'
|
||||
|
|
|
|||
|
|
@ -124,11 +124,13 @@ class BitcoinRPCClient(RPCClient, metaclass=AsyncInit):
|
|||
self.proto = proto
|
||||
self.daemon = daemon
|
||||
self.call_sigs = getattr(CallSigs, daemon.id)(cfg)
|
||||
self.twname = TrackingWalletName(cfg.regtest_user or cfg.tw_name or self.dfl_twname)
|
||||
self.twname = TrackingWalletName(cfg.regtest_user or proto.tw_name or cfg.tw_name or self.dfl_twname)
|
||||
|
||||
super().__init__(
|
||||
cfg = cfg,
|
||||
host = 'localhost' if cfg.test_suite or cfg.network == 'regtest' else (cfg.rpc_host or 'localhost'),
|
||||
host = (
|
||||
'localhost' if cfg.test_suite or cfg.network == 'regtest'
|
||||
else (proto.rpc_host or cfg.rpc_host or 'localhost')),
|
||||
port = daemon.rpc_port)
|
||||
|
||||
self.set_auth()
|
||||
|
|
@ -210,14 +212,15 @@ class BitcoinRPCClient(RPCClient, metaclass=AsyncInit):
|
|||
"""
|
||||
if self.cfg.network == 'regtest':
|
||||
from .regtest import MMGenRegtest
|
||||
user, passwd = (MMGenRegtest.rpc_user, MMGenRegtest.rpc_password)
|
||||
elif self.cfg.rpc_user:
|
||||
user, passwd = (self.cfg.rpc_user, self.cfg.rpc_password)
|
||||
user = MMGenRegtest.rpc_user
|
||||
passwd = MMGenRegtest.rpc_password
|
||||
else:
|
||||
user, passwd = self.get_daemon_cfg_options(('rpcuser', 'rpcpassword')).values()
|
||||
|
||||
if not (user and passwd):
|
||||
user, passwd = (self.daemon.rpc_user, self.daemon.rpc_password)
|
||||
user = (
|
||||
self.proto.rpc_user or self.cfg.rpc_user or self.get_daemon_cfg_option('rpcuser')
|
||||
or self.daemon.rpc_user)
|
||||
passwd = (
|
||||
self.proto.rpc_password or self.cfg.rpc_password or self.get_daemon_cfg_option('rpcpassword')
|
||||
or self.daemon.rpc_password)
|
||||
|
||||
if user and passwd:
|
||||
self.auth = auth_data(user, passwd)
|
||||
|
|
@ -260,6 +263,9 @@ class BitcoinRPCClient(RPCClient, metaclass=AsyncInit):
|
|||
(os.path.dirname(self.cfg.data_dir) if self.proto.regtest else self.daemon.datadir),
|
||||
self.daemon.cfg_file)
|
||||
|
||||
def get_daemon_cfg_option(self, req_key):
|
||||
return list(self.get_daemon_cfg_options([req_key]).values())[0]
|
||||
|
||||
def get_daemon_cfg_options(self, req_keys):
|
||||
|
||||
fn = self.get_daemon_cfg_fn()
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ class mainnet(mainnet):
|
|||
chain_names = ['classic', 'ethereum_classic']
|
||||
max_tx_fee = 0.005
|
||||
coin_amt = 'ETCAmt'
|
||||
ignore_daemon_version = False
|
||||
|
||||
class testnet(mainnet):
|
||||
chain_names = ['morden', 'morden_testnet', 'classic-testnet']
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ class mainnet(CoinProtocol.DummyWIF, CoinProtocol.Secp256k1):
|
|||
base_proto_coin = 'ETH'
|
||||
base_coin = 'ETH'
|
||||
avg_bdi = 15
|
||||
ignore_daemon_version = False
|
||||
decimal_prec = 36
|
||||
|
||||
chain_ids = {
|
||||
|
|
@ -52,6 +51,17 @@ class mainnet(CoinProtocol.DummyWIF, CoinProtocol.Secp256k1):
|
|||
711: 'ethereum', # geth mainnet (empty chain)
|
||||
}
|
||||
|
||||
coin_cfg_opts = (
|
||||
'ignore_daemon_version',
|
||||
'rpc_host',
|
||||
'rpc_port',
|
||||
'max_tx_fee',
|
||||
)
|
||||
|
||||
proto_cfg_opts = (
|
||||
'chain_names',
|
||||
)
|
||||
|
||||
@property
|
||||
def dcoin(self):
|
||||
return self.tokensym or self.coin
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class EthereumRPCClient(RPCClient, metaclass=AsyncInit):
|
|||
|
||||
super().__init__(
|
||||
cfg = cfg,
|
||||
host = 'localhost' if cfg.test_suite else (cfg.rpc_host or 'localhost'),
|
||||
host = 'localhost' if cfg.test_suite else (proto.rpc_host or cfg.rpc_host or 'localhost'),
|
||||
port = daemon.rpc_port)
|
||||
|
||||
await self.set_backend_async(backend)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ class mainnet(mainnet):
|
|||
bech32_hrp = 'ltc'
|
||||
avg_bdi = 150
|
||||
halving_interval = 840000
|
||||
ignore_daemon_version = False
|
||||
|
||||
class testnet(mainnet):
|
||||
# addr ver nums same as Bitcoin testnet, except for 'p2sh'
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class MoneroViewKey(HexStr):
|
|||
color, width, hexcase = 'cyan', 64, 'lower' # FIXME - no checking performed
|
||||
|
||||
# https://github.com/monero-project/monero/blob/master/src/cryptonote_config.h
|
||||
class mainnet(CoinProtocol.DummyWIF, CoinProtocol.Base):
|
||||
class mainnet(CoinProtocol.RPC, CoinProtocol.DummyWIF, CoinProtocol.Base):
|
||||
|
||||
network_names = _nw('mainnet', 'stagenet', None)
|
||||
base_proto = 'Monero'
|
||||
|
|
@ -37,10 +37,14 @@ class mainnet(CoinProtocol.DummyWIF, CoinProtocol.Base):
|
|||
avg_bdi = 120
|
||||
privkey_len = 32
|
||||
mmcaps = ('rpc',)
|
||||
ignore_daemon_version = False
|
||||
coin_amt = 'XMRAmt'
|
||||
sign_mode = 'standalone'
|
||||
|
||||
coin_cfg_opts = (
|
||||
'ignore_daemon_version',
|
||||
'rpc_port',
|
||||
)
|
||||
|
||||
def get_addr_len(self, addr_fmt):
|
||||
return (64, 72)[addr_fmt == 'monero_integrated']
|
||||
|
||||
|
|
|
|||
|
|
@ -116,6 +116,11 @@ class CoinProtocol(MMGenObject):
|
|||
self.coin_amt = None
|
||||
self.max_tx_fee = None
|
||||
|
||||
self.set_cfg_opts()
|
||||
|
||||
def set_cfg_opts(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def dcoin(self):
|
||||
return self.coin
|
||||
|
|
@ -192,8 +197,43 @@ class CoinProtocol(MMGenObject):
|
|||
else:
|
||||
return getattr(importlib.import_module(modpath), clsname)
|
||||
|
||||
class RPC:
|
||||
|
||||
class Secp256k1(Base):
|
||||
# prefixed with coin, e.g. ‘ltc_rpc_host’: refvals taken from proto class
|
||||
coin_cfg_opts = ()
|
||||
|
||||
# prefixed with coin + network, e.g. ‘eth_mainnet_chain_names’: refvals taken from proto class
|
||||
proto_cfg_opts = ()
|
||||
|
||||
# default vals (refvals): bool(val) must be False (val = None -> option takes no parameter)
|
||||
ignore_daemon_version = None
|
||||
rpc_host = ''
|
||||
rpc_port = 0
|
||||
rpc_user = ''
|
||||
rpc_password = ''
|
||||
tw_name = ''
|
||||
|
||||
@classmethod
|
||||
def get_opt_clsval(cls, cfg, opt):
|
||||
coin, *rem = opt.split('_', 2)
|
||||
network = rem[0] if rem[0] in init_proto(cfg, coin, return_cls=True).network_names else None
|
||||
opt_name = '_'.join(rem[bool(network):])
|
||||
if ((network is None and opt_name in cls.coin_cfg_opts) or
|
||||
(network and opt_name in cls.proto_cfg_opts)):
|
||||
# raises AttributeError on failure:
|
||||
return getattr(init_proto(cfg, coin, network=network, return_cls=True), opt_name)
|
||||
else:
|
||||
raise AttributeError(f'{opt_name}: unrecognized attribute')
|
||||
|
||||
def set_cfg_opts(self):
|
||||
for opt in self.cfg.__dict__:
|
||||
if opt.startswith(self.coin.lower() + '_'):
|
||||
res = opt.split('_', 2)[1:]
|
||||
network = res[0] if res[0] in self.network_names else None
|
||||
if network is None or network == self.network:
|
||||
setattr(self, '_'.join(res[bool(network):]), getattr(self.cfg, opt))
|
||||
|
||||
class Secp256k1(RPC, Base):
|
||||
"""
|
||||
Bitcoin and Ethereum protocols inherit from this class
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -203,10 +203,10 @@ class CmdTestCfgFile(CmdTestBase):
|
|||
|
||||
for coin, res1_chk, res2_chk, res2_chk_eq in (
|
||||
('BTC', 'True', '1.2345', True),
|
||||
('LTC', 'False', '1.2345', False),
|
||||
('BCH', 'False', '1.2345', False),
|
||||
('LTC', 'None', '1.2345', False),
|
||||
('BCH', 'None', '1.2345', False),
|
||||
('ETH', 'True', '5.4321', True),
|
||||
('ETC', 'False', '5.4321', False)
|
||||
('ETC', 'None', '5.4321', False)
|
||||
):
|
||||
if cfg.no_altcoin and coin != 'BTC':
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -46,6 +46,13 @@ class CmdTestOpts(CmdTestBase):
|
|||
('opt_good22', (41, 'good cmdline opt (opt + negated opt [substring])', [])),
|
||||
('opt_good23', (41, 'good cmdline opt (negated negative opt [substring])', [])),
|
||||
('opt_good24', (41, 'good cmdline opt (negated opt + opt [substring])', [])),
|
||||
('opt_good25', (41, 'good cmdline opt (--btc-rpc-host)', [])),
|
||||
('opt_good26', (41, 'good cmdline opt (--btc-rpc-port)', [])),
|
||||
('opt_good27', (41, 'good cmdline opt (--btc-ignore-daemon-version)', [])),
|
||||
('opt_good28', (41, 'good cmdline opt (--bch-cashaddr)', [])),
|
||||
('opt_good29', (41, 'good cmdline opt (--etc-max-tx-fee=0.1)', [])),
|
||||
('opt_good30', (41, 'good cmdline opt (--eth-chain-names=foo,bar)', [])),
|
||||
('opt_good31', (41, 'good cmdline opt (--xmr-rpc-port=28081)', [])),
|
||||
('opt_bad_param', (41, 'bad global opt (--pager=1)', [])),
|
||||
('opt_bad_infile', (41, 'bad infile parameter', [])),
|
||||
('opt_bad_outdir', (41, 'bad outdir parameter', [])),
|
||||
|
|
@ -65,6 +72,23 @@ class CmdTestOpts(CmdTestBase):
|
|||
('opt_invalid_14', (41, 'invalid cmdline opt (long opt substring too short)', [])),
|
||||
('opt_invalid_15', (41, 'invalid cmdline (too many args)', [])),
|
||||
('opt_invalid_16', (41, 'invalid cmdline (overlong arg)', [])),
|
||||
('opt_invalid_17', (41, 'invalid cmdline opt (--btc-rpc-host without ‘need_proto’)', [])),
|
||||
('opt_invalid_18', (41, 'invalid cmdline opt (--btc-rpc-port without ‘need_proto’)', [])),
|
||||
('opt_invalid_19', (41, 'invalid cmdline opt (--btc-rpc-port with non-integer param)', [])),
|
||||
('opt_invalid_21', (41, 'invalid cmdline opt (--btc-foo)', [])),
|
||||
('opt_invalid_22', (41, 'invalid cmdline opt (--btc-rpc-host with missing param)', [])),
|
||||
('opt_invalid_23', (41, 'invalid cmdline opt (--btc-ignore-daemon-version with param)', [])),
|
||||
('opt_invalid_24', (41, 'invalid cmdline opt (--bch-cashaddr without ‘need_proto’)', [])),
|
||||
('opt_invalid_25', (41, 'invalid cmdline opt (--bch-cashaddr without parameter)', [])),
|
||||
('opt_invalid_26', (41, 'invalid cmdline opt (--bch-cashaddr with non-bool parameter)', [])),
|
||||
('opt_invalid_27', (41, 'invalid cmdline opt (--ltc-cashaddr)', [])),
|
||||
('opt_invalid_28', (41, 'invalid cmdline opt (--xmr-max-tx-fee)', [])),
|
||||
('opt_invalid_29', (41, 'invalid cmdline opt (--eth-max-tx-fee without parameter)', [])),
|
||||
('opt_invalid_30', (41, 'invalid cmdline opt (--eth-max-tx-fee with non-numeric parameter)', [])),
|
||||
('opt_invalid_31', (41, 'invalid cmdline opt (--bch-cashaddr without --coin=bch)', [])),
|
||||
('opt_invalid_32', (41, 'invalid cmdline opt (--eth-chain-names without --coin=eth)', [])),
|
||||
('opt_invalid_33', (41, 'invalid cmdline opt (--xmr-rpc-host)', [])),
|
||||
('opt_invalid_34', (41, 'invalid cmdline opt (--eth-rpc-user)', [])),
|
||||
)
|
||||
|
||||
def spawn_prog(self, args, opts=[], exit_val=None, need_proto=False):
|
||||
|
|
@ -242,6 +266,45 @@ class CmdTestOpts(CmdTestBase):
|
|||
def opt_good24(self):
|
||||
return self.check_vals(['--no-pag', '--pag'], (('cfg.pager', 'True'),))
|
||||
|
||||
def opt_good25(self):
|
||||
return self.check_vals(
|
||||
['--btc-rpc-host=pi5'],
|
||||
(('cfg.btc_rpc_host', 'pi5'), ('proto.rpc_host', 'pi5')),
|
||||
need_proto=True)
|
||||
|
||||
def opt_good26(self):
|
||||
return self.check_vals(
|
||||
['--btc-rpc-port=7272'],
|
||||
(('cfg.btc_rpc_port', '7272'), ('proto.rpc_port', '7272')),
|
||||
need_proto=True)
|
||||
|
||||
def opt_good27(self):
|
||||
return self.check_vals(
|
||||
['--btc-ignore-daemon-version'],
|
||||
(('cfg.btc_ignore_daemon_version', 'True'), ('proto.ignore_daemon_version', 'True'),),
|
||||
need_proto = True)
|
||||
|
||||
def opt_good28(self):
|
||||
return self.check_vals(
|
||||
['--coin=bch', '--bch-cashaddr=yes'],
|
||||
(('cfg.bch_cashaddr', 'True'), ('proto.cashaddr', 'True'),),
|
||||
need_proto = True)
|
||||
|
||||
def opt_good29(self):
|
||||
return self.check_vals(['--etc-max-tx-fee=0.1'], (('cfg.etc_max_tx_fee', '0.1'),), need_proto=True)
|
||||
|
||||
def opt_good30(self):
|
||||
return self.check_vals(
|
||||
['--coin=eth', '--eth-mainnet-chain-names=foo,bar'],
|
||||
(('cfg.eth_mainnet_chain_names', r"\['foo', 'bar'\]"), ('proto.chain_names', r"\['foo', 'bar'\]")),
|
||||
need_proto = True)
|
||||
|
||||
def opt_good31(self):
|
||||
return self.check_vals(
|
||||
['--coin=xmr', '--xmr-rpc-port=28081'],
|
||||
(('cfg.xmr_rpc_port', '28081'),('proto.rpc_port', '28081'),),
|
||||
need_proto = True)
|
||||
|
||||
def opt_bad_param(self):
|
||||
return self.do_run(['--pager=1'], 'no parameter', 1)
|
||||
|
||||
|
|
@ -259,8 +322,8 @@ class CmdTestOpts(CmdTestBase):
|
|||
def opt_bad_autoset(self):
|
||||
return self.do_run(['--fee-estimate-mode=Fubar'], 'not unique substring', 1)
|
||||
|
||||
def opt_invalid(self, args, expect, exit_val=1):
|
||||
t = self.spawn_prog(args, exit_val=exit_val)
|
||||
def opt_invalid(self, args, expect, opts=[], need_proto=False, exit_val=1):
|
||||
t = self.spawn_prog(args, opts=opts, exit_val=exit_val, need_proto=need_proto)
|
||||
t.expect(expect)
|
||||
return t
|
||||
|
||||
|
|
@ -305,3 +368,54 @@ class CmdTestOpts(CmdTestBase):
|
|||
|
||||
def opt_invalid_16(self):
|
||||
return self.opt_invalid(['e' * 4097], 'too long')
|
||||
|
||||
def opt_invalid_17(self):
|
||||
return self.opt_invalid(['--btc-rpc-host'], 'unrecognized option')
|
||||
|
||||
def opt_invalid_18(self):
|
||||
return self.opt_invalid(['--btc-rpc-port'], 'unrecognized option')
|
||||
|
||||
def opt_invalid_19(self):
|
||||
return self.opt_invalid(['--btc-rpc-port=foo'], "must be of type 'int'", need_proto=True)
|
||||
|
||||
def opt_invalid_21(self):
|
||||
return self.opt_invalid(['--btc-foo'], 'unrecognized option')
|
||||
|
||||
def opt_invalid_22(self):
|
||||
return self.opt_invalid(['--btc-rpc-host'], 'missing parameter', need_proto=True)
|
||||
|
||||
def opt_invalid_23(self):
|
||||
return self.opt_invalid(['--btc-ignore-daemon-version=1'], 'requires no parameter', need_proto=True)
|
||||
|
||||
def opt_invalid_24(self):
|
||||
return self.opt_invalid(['--bch-cashaddr'], 'unrecognized option')
|
||||
|
||||
def opt_invalid_25(self):
|
||||
return self.opt_invalid(['--bch-cashaddr'], 'missing parameter', need_proto=True)
|
||||
|
||||
def opt_invalid_26(self):
|
||||
return self.opt_invalid(['--bch-cashaddr=foo'], "must be of type 'bool'", need_proto=True)
|
||||
|
||||
def opt_invalid_27(self):
|
||||
return self.opt_invalid(['--ltc-cashaddr'], 'unrecognized option', need_proto=True)
|
||||
|
||||
def opt_invalid_28(self):
|
||||
return self.opt_invalid(['--xmr-max-tx-fee=0.1'], 'unrecognized option', need_proto=True)
|
||||
|
||||
def opt_invalid_29(self):
|
||||
return self.opt_invalid(['--eth-max-tx-fee'], 'missing parameter', need_proto=True)
|
||||
|
||||
def opt_invalid_30(self):
|
||||
return self.opt_invalid(['--eth-max-tx-fee=true'], 'must be of type', need_proto=True)
|
||||
|
||||
def opt_invalid_31(self):
|
||||
return self.opt_invalid(['--bch-cashaddr=true'], 'has no attribute', opts=['--show-opts=bch_cashaddr'], need_proto=True)
|
||||
|
||||
def opt_invalid_32(self):
|
||||
return self.opt_invalid(['--eth-chain-names=foo,bar'], 'unrecognized option', need_proto=True)
|
||||
|
||||
def opt_invalid_33(self):
|
||||
return self.opt_invalid(['--xmr-rpc-host=solaris'], 'unrecognized option', need_proto=True)
|
||||
|
||||
def opt_invalid_34(self):
|
||||
return self.opt_invalid(['--eth-rpc-user=bob'], 'unrecognized option', need_proto=True)
|
||||
|
|
|
|||
|
|
@ -78,6 +78,9 @@ async def print_daemon_info(rpc):
|
|||
WALLETINFO: {fmt_dict(await rpc.walletinfo)}
|
||||
""".rstrip())
|
||||
|
||||
if rpc.proto.base_proto == 'Ethereum':
|
||||
msg(f' CHAIN_NAMES: {" ".join(rpc.daemon.proto.chain_names)}')
|
||||
|
||||
msg('')
|
||||
|
||||
def do_msg(rpc, backend):
|
||||
|
|
@ -92,7 +95,9 @@ class init_test:
|
|||
do_msg(rpc, backend)
|
||||
|
||||
wi = await rpc.walletinfo
|
||||
assert wi['walletname'] == cfg_override['tw_name']
|
||||
assert wi['walletname'] == cfg_override['btc_tw_name']
|
||||
assert wi['walletname'] == rpc.cfg._proto.tw_name, f'{wi["walletname"]!r} != {rpc.cfg._proto.tw_name!r}'
|
||||
assert daemon.bind_port == cfg_override['btc_rpc_port']
|
||||
|
||||
bh = (await rpc.call('getblockchaininfo', timeout=300))['bestblockhash']
|
||||
await rpc.gathered_call('getblock', ((bh,), (bh, 1)), timeout=300)
|
||||
|
|
@ -112,6 +117,9 @@ class init_test:
|
|||
rpc = await rpc_init(cfg, daemon.proto, backend, daemon)
|
||||
do_msg(rpc, backend)
|
||||
await rpc.call('eth_blockNumber', timeout=300)
|
||||
if rpc.proto.network == 'testnet':
|
||||
assert daemon.proto.chain_names == cfg_override['eth_testnet_chain_names']
|
||||
assert daemon.bind_port == cfg_override['eth_rpc_port']
|
||||
return rpc
|
||||
|
||||
etc = eth
|
||||
|
|
@ -166,7 +174,15 @@ class unit_tests:
|
|||
return await run_test(
|
||||
['btc', 'btc_tn'],
|
||||
test_cf_auth = True,
|
||||
cfg_override = {'_clone': cfg, 'tw_name': 'alternate-tracking-wallet'})
|
||||
cfg_override = {
|
||||
'_clone': cfg,
|
||||
'btc_rpc_port': 19777,
|
||||
'rpc_port': 32323, # ignored
|
||||
'btc_tw_name': 'alternate-tracking-wallet',
|
||||
'tw_name': 'this-is-overridden',
|
||||
'ltc_tw_name': 'this-is-ignored',
|
||||
'eth_mainnet_chain_names': ['also', 'ignored'],
|
||||
})
|
||||
|
||||
async def ltc(self, name, ut):
|
||||
return await run_test(['ltc', 'ltc_tn'], test_cf_auth=True)
|
||||
|
|
@ -176,7 +192,17 @@ class unit_tests:
|
|||
|
||||
async def geth(self, name, ut):
|
||||
# mainnet returns EIP-155 error on empty blockchain:
|
||||
return await run_test(['eth_tn', 'eth_rt'], daemon_ids=['geth'])
|
||||
return await run_test(
|
||||
['eth_tn', 'eth_rt'],
|
||||
daemon_ids = ['geth'],
|
||||
cfg_override = {
|
||||
'_clone': cfg,
|
||||
'eth_rpc_port': 19777,
|
||||
'rpc_port': 32323, # ignored
|
||||
'btc_tw_name': 'ignored',
|
||||
'tw_name': 'also-ignored',
|
||||
'eth_testnet_chain_names': ['goerli', 'foo', 'bar', 'baz'],
|
||||
})
|
||||
|
||||
async def erigon(self, name, ut):
|
||||
return await run_test(['eth', 'eth_tn', 'eth_rt'], daemon_ids=['erigon'])
|
||||
|
|
|
|||
|
|
@ -60,6 +60,11 @@ if cfg.show_opts:
|
|||
col1_w = max(len(s) for s in opts) + 5
|
||||
for opt in opts:
|
||||
msg('{:{w}} {}'.format(f'cfg.{opt}:', getattr(cfg, opt), w=col1_w))
|
||||
if cfg._proto:
|
||||
coin, *rem = opt.split('_')
|
||||
network = rem[0] if rem[0] in cfg._proto.network_names else None
|
||||
opt_name = '_'.join(rem[bool(network):])
|
||||
msg('{:{w}} {}'.format(f'proto.{opt_name}:', getattr(cfg._proto, opt_name), w=col1_w))
|
||||
|
||||
msg('')
|
||||
for n, arg in enumerate(cfg._args, 1):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue