Skip to content

Commit

Permalink
Config API, Part I
Browse files Browse the repository at this point in the history
This patch eliminates the global configuration variables `opt` and `g`, making
all functions and class instances locally configurable.  Configuration data is
passed to functions and constructors via the `cfg` parameter and made available
to methods in `self.cfg`.

Local configuration free from dependence on the command line will enable the
creation of multiple, independently configured instances of MMGen’s data
objects within a single process.

Potential applications include testing (tracking wallets configured to interact
with spawned processes, for example) and the use of MMGen as a library for
other projects.

This patch completes most of the work required to enable the API.  The full
implementation will appear in a forthcoming commit.
  • Loading branch information
mmgen committed Mar 28, 2023
1 parent 6cd8856 commit c7adb56
Show file tree
Hide file tree
Showing 197 changed files with 2,218 additions and 2,078 deletions.
10 changes: 4 additions & 6 deletions examples/halving-calculator.py
Expand Up @@ -15,10 +15,9 @@
import time

import mmgen.opts as opts
from mmgen.opts import opt
from mmgen.util import async_run

opts.init({
cfg = opts.init({
'text': {
'desc': 'Estimate date of next block subsidy halving',
'usage':'[opts]',
Expand Down Expand Up @@ -55,16 +54,15 @@ def time_diff_warning(t_diff):

async def main():

from mmgen.protocol import init_proto_from_opts
proto = init_proto_from_opts(need_amt=True)
proto = cfg._proto

from mmgen.rpc import rpc_init
c = await rpc_init(proto)
c = await rpc_init(cfg,proto)

tip = await c.call('getblockcount')
assert tip > 1, 'block tip must be > 1'
remaining = proto.halving_interval - tip % proto.halving_interval
sample_size = int(opt.sample_size) if opt.sample_size else min(tip-1,max(remaining,144))
sample_size = int(cfg.sample_size) if cfg.sample_size else min(tip-1,max(remaining,144))

# aiohttp backend will perform these two calls concurrently:
cur,old = await c.gathered_call('getblockstats',((tip,),(tip - sample_size,)))
Expand Down
14 changes: 8 additions & 6 deletions mmgen/addrdata.py
Expand Up @@ -20,7 +20,8 @@
addrdata: MMGen AddrData and related classes
"""

from .util import vmsg,fmt,die
from .globalvars import gc
from .util import fmt,die
from .base_obj import AsyncInit
from .obj import MMGenObject,MMGenDict,get_obj
from .addr import MMGenID,AddrListID
Expand Down Expand Up @@ -68,16 +69,16 @@ def make_reverse_dict(self,coinaddrs):

class TwAddrData(AddrData,metaclass=AsyncInit):

def __new__(cls,proto,*args,**kwargs):
def __new__(cls,cfg,proto,*args,**kwargs):
return MMGenObject.__new__(proto.base_proto_subclass(cls,'addrdata'))

async def __init__(self,proto,twctl=None):
async def __init__(self,cfg,proto,twctl=None):
from .rpc import rpc_init
from .tw.shared import TwLabel
from .globalvars import gc
from .seed import SeedID
self.cfg = cfg
self.proto = proto
self.rpc = await rpc_init(proto)
self.rpc = await rpc_init(cfg,proto)
self.al_ids = {}
twd = await self.get_tw_data(twctl)
out,i = {},0
Expand All @@ -96,10 +97,11 @@ async def __init__(self,proto,twctl=None):
out[al_id].append(AddrListEntry(self.proto,idx=obj.idx,addr=addr_array[0],comment=l.comment))
i += 1

vmsg(f'{i} {gc.proj_name} addresses found, {len(twd)} accounts total')
self.cfg._util.vmsg(f'{i} {gc.proj_name} addresses found, {len(twd)} accounts total')

for al_id in out:
self.add(AddrList(
self.cfg,
self.proto,
al_id = al_id,
adata = AddrListData(sorted( out[al_id], key=lambda a: a.idx ))
Expand Down
39 changes: 19 additions & 20 deletions mmgen/addrfile.py
Expand Up @@ -20,8 +20,8 @@
addrfile: Address and password file classes for the MMGen suite
"""

from .globalvars import g
from .util import msg,qmsg,qmsg_r,die,capfirst
from .globalvars import gc
from .util import msg,die,capfirst
from .protocol import init_proto
from .obj import MMGenObject,TwComment,WalletPassword,MMGenPWIDString
from .seed import SeedID,is_seed_id
Expand All @@ -44,13 +44,13 @@ class AddrFile(MMGenObject):
"""

def __init__(self,parent):

self.parent = parent
self.cfg = parent.cfg
self.infile = None

def encrypt(self):
from .crypto import Crypto
self.fmt_data = Crypto().mmgen_encrypt(
self.fmt_data = Crypto(self.cfg).mmgen_encrypt(
data = self.fmt_data.encode(),
desc = f'new {self.parent.desc} list' )
self.ext += f'.{Crypto.mmenc_ext}'
Expand All @@ -63,13 +63,13 @@ def filename(self):
self.ext )

def write(self,fn=None,ask_tty=True,ask_write_default_yes=False,binary=False,desc=None):
from .opts import opt
from .fileutil import write_data_to_file
write_data_to_file(
self.cfg,
fn or self.filename,
self.fmt_data,
desc or self.desc,
ask_tty = self.parent.has_keys and not opt.quiet,
ask_tty = self.parent.has_keys and not self.cfg.quiet,
binary = binary )

def make_label(self):
Expand All @@ -87,8 +87,7 @@ def format(self,add_comments=False):
self.file_header_mn.format(p.pw_fmt.upper())
if p.gen_passwds and p.pw_fmt in ('bip39','xmrseed') else
self.file_header ).strip()
from .globalvars import gc
out = [fh.format(pnm=gc.proj_name,n=TwComment.max_screen_width) + '\n']
out = [fh.format( pnm=gc.proj_name, n=TwComment.max_screen_width ) + '\n']

if p.chksum:
out.append(f'# {capfirst(p.desc)} data checksum for {p.id_str}: {p.chksum}')
Expand All @@ -108,8 +107,7 @@ def format(self,add_comments=False):
else: # First line with idx
out.append(fs.format(e.idx,e.addr,c))
if p.has_keys:
from .opts import opt
if opt.b16:
if self.cfg.b16:
out.append(fs.format( '', f'orig_hex: {e.sec.orig_bytes.hex()}', c ))
out.append(fs.format( '', f'{p.al_id.mmtype.wif_label}: {e.sec.wif}', c ))
for k in ('viewkey','wallet_passwd'):
Expand Down Expand Up @@ -160,21 +158,21 @@ def parse_file_body(self,lines):

def verify_keys():
from .addrgen import KeyGenerator,AddrGenerator
kg = KeyGenerator(p.proto,p.al_id.mmtype.pubkey_type)
ag = AddrGenerator(p.proto,p.al_id.mmtype)
kg = KeyGenerator( self.cfg, p.proto, p.al_id.mmtype.pubkey_type )
ag = AddrGenerator( self.cfg, p.proto, p.al_id.mmtype )
llen = len(ret)
qmsg_r = p.cfg._util.qmsg_r
for n,e in enumerate(ret):
qmsg_r(f'\rVerifying keys {n+1}/{llen}')
assert e.addr == ag.to_addr(kg.gen_data(e.sec)),(
f'Key doesn’t match address!\n {e.sec.wif}\n {e.addr}')
qmsg(' - done')
p.cfg._util.qmsg(' - done')

from .opts import opt
if opt.yes or p.ka_validity_chk == True:
if self.cfg.yes or p.ka_validity_chk == True:
verify_keys()
else:
from .ui import keypress_confirm
if keypress_confirm('Check key-to-address validity?'):
if keypress_confirm( p.cfg, 'Check key-to-address validity?' ):
verify_keys()

return ret
Expand Down Expand Up @@ -216,16 +214,17 @@ def parse_addrfile_label(lbl):
else: # only component is coin
coin,mmtype_key = ( lbl, None )

proto = init_proto(coin=coin,network=network)
proto = init_proto( p.cfg, coin=coin, network=network )

if mmtype_key == None:
mmtype_key = proto.mmtypes[0]

return ( proto, proto.addr_type(mmtype_key) )

p = self.parent

from .fileutil import get_lines_from_file
lines = get_lines_from_file(fn,p.desc+' data',trim_comments=True)
lines = get_lines_from_file( p.cfg, fn, p.desc+' data', trim_comments=True )

try:
assert len(lines) >= 3, f'Too few lines in address file ({len(lines)})'
Expand All @@ -246,12 +245,12 @@ def parse_addrfile_label(lbl):
modname,funcname = p.pw_info[p.pw_fmt].chk_func.split('.')
import importlib
p.chk_func = getattr(importlib.import_module('mmgen.'+modname),funcname)
proto = init_proto('btc') # FIXME: dummy protocol
proto = init_proto( p.cfg, 'btc' ) # FIXME: dummy protocol
mmtype = MMGenPasswordType(proto,'P')
elif len(ls) == 1:
proto,mmtype = parse_addrfile_label(ls[0])
elif len(ls) == 0:
proto = init_proto('btc')
proto = init_proto( p.cfg, 'btc' )
mmtype = proto.addr_type('L')
else:
raise ValueError(f'{lines[0]}: Invalid first line for {p.gen_desc} file {fn!r}')
Expand Down
12 changes: 6 additions & 6 deletions mmgen/addrgen.py
Expand Up @@ -36,20 +36,20 @@ class addr_generator:

class base:

def __init__(self,proto,addr_type):
def __init__(self,cfg,proto,addr_type):
self.proto = proto
self.pubkey_type = addr_type.pubkey_type
self.compressed = addr_type.compressed
desc = f'AddrGenerator {type(self).__name__!r}'

class keccak(base):

def __init__(self,proto,addr_type):
super().__init__(proto,addr_type)
def __init__(self,cfg,proto,addr_type):
super().__init__(cfg,proto,addr_type)
from .util2 import get_keccak
self.keccak_256 = get_keccak()
self.keccak_256 = get_keccak(cfg)

def AddrGenerator(proto,addr_type):
def AddrGenerator(cfg,proto,addr_type):
"""
factory function returning an address generator for the specified address type
"""
Expand All @@ -76,4 +76,4 @@ def AddrGenerator(proto,addr_type):
import importlib
return getattr(
importlib.import_module(f'mmgen.proto.{package_map[addr_type.name]}.addrgen'),
addr_type.name )(proto,addr_type)
addr_type.name )(cfg,proto,addr_type)
31 changes: 16 additions & 15 deletions mmgen/addrlist.py
Expand Up @@ -20,8 +20,7 @@
addrlist: Address list classes for the MMGen suite
"""

from .globalvars import g
from .util import qmsg,qmsg_r,suf,make_chksum_N,Msg,die
from .util import suf,make_chksum_N,Msg,die
from .objmethods import MMGenObject,Hilite,InitErrors
from .obj import MMGenListItem,ListItemAttr,MMGenDict,TwComment,WalletPassword
from .key import PrivKey
Expand Down Expand Up @@ -158,6 +157,7 @@ def noop(self,desc,data):

def __init__(
self,
cfg,
proto,
addrfile = '',
al_id = '',
Expand All @@ -173,12 +173,13 @@ def __init__(
skip_chksum_msg = False,
add_p2pkh = False ):

self.cfg = cfg
self.ka_validity_chk = key_address_validity_check
self.add_p2pkh = add_p2pkh
self.proto = proto
do_chksum = False

if not g.debug_addrlist:
if not cfg.debug_addrlist:
self.dmsg_sc = self.noop

if seed and addr_idxs: # data from seed + idxs
Expand Down Expand Up @@ -230,7 +231,7 @@ def __init__(
def do_chksum_msg(self,record):
chk = 'Check this value against your records'
rec = f'Record this checksum: it will be used to verify the {self.desc} file in the future'
qmsg(
self.cfg._util.qmsg(
f'Checksum for {self.desc} data {self.id_str.hl()}: {self.chksum.hl()}\n' +
(chk,rec)[record] )

Expand All @@ -246,23 +247,23 @@ def generate(self,seed,addr_idxs):

if self.gen_addrs:
from .addrgen import KeyGenerator,AddrGenerator
kg = KeyGenerator( self.proto, mmtype.pubkey_type )
ag = AddrGenerator( self.proto, mmtype )
kg = KeyGenerator( self.cfg, self.proto, mmtype.pubkey_type )
ag = AddrGenerator( self.cfg, self.proto, mmtype )
if self.add_p2pkh:
ag2 = AddrGenerator( self.proto, 'compressed' )
ag2 = AddrGenerator( self.cfg, self.proto, 'compressed' )

from .globalvars import g
from .derive import derive_coin_privkey_bytes

t_addrs = len(addr_idxs)
le = self.entry_type
out = AddrListData()
CR = '\n' if g.debug_addrlist else '\r'
CR = '\n' if self.cfg.debug_addrlist else '\r'

for pk_bytes in derive_coin_privkey_bytes(seed,addr_idxs):

if not g.debug:
qmsg_r(f'{CR}Generating {self.gen_desc} #{pk_bytes.idx} ({pk_bytes.pos} of {t_addrs})')
if not self.cfg.debug:
self.cfg._util.qmsg_r(
f'{CR}Generating {self.gen_desc} #{pk_bytes.idx} ({pk_bytes.pos} of {t_addrs})' )

e = le( proto=self.proto, idx=pk_bytes.idx )

Expand All @@ -286,7 +287,7 @@ def generate(self,seed,addr_idxs):

out.append(e)

qmsg('{}{}: {} {}{} generated{}'.format(
self.cfg._util.qmsg('{}{}: {} {}{} generated{}'.format(
CR,
self.al_id.hl(),
t_addrs,
Expand Down Expand Up @@ -316,7 +317,7 @@ def scramble_seed(self,seed):
if self.proto.testnet:
scramble_key += ':' + self.proto.network
self.dmsg_sc('str',scramble_key)
return Crypto().scramble_seed(seed,scramble_key.encode())
return Crypto(self.cfg).scramble_seed(seed,scramble_key.encode())

def idxs(self):
return [e.idx for e in self.data]
Expand Down Expand Up @@ -371,8 +372,8 @@ def add_wifs(self,key_list):
def gen_addr(pk,t):
at = self.proto.addr_type(t)
from .addrgen import KeyGenerator,AddrGenerator
kg = KeyGenerator(self.proto,at.pubkey_type)
ag = AddrGenerator(self.proto,at)
kg = KeyGenerator( self.cfg, self.proto, at.pubkey_type )
ag = AddrGenerator( self.cfg, self.proto, at )
return ag.to_addr(kg.gen_data(pk))

compressed_types = set(self.proto.mmtypes) - {'L','E'}
Expand Down
12 changes: 6 additions & 6 deletions mmgen/altcoin.py
Expand Up @@ -434,14 +434,14 @@ def verify_leading_symbols(cls,quiet=False,verbose=False):
test_equal('P2SH leading symbol',vn_info[1],ret,*cdata)

@classmethod
def verify_core_coin_data(cls,quiet=False,verbose=False):
def verify_core_coin_data(cls,cfg,quiet=False,verbose=False):
from .protocol import CoinProtocol,init_proto

for network in ('mainnet','testnet'):
for coin in gc.core_coins:
e = cls.get_entry(coin,network)
if e:
proto = init_proto(coin,testnet=network=='testnet')
proto = init_proto( cfg, coin, network=network )
cdata = (network,coin,e,type(proto).__name__,verbose)
if not quiet:
msg(f'Verifying {coin.upper()} {network}')
Expand Down Expand Up @@ -791,11 +791,11 @@ def num2hexstr(n):
}
}

from mmgen.opts import init,opt
init( opts_data )
from mmgen.opts import init
cfg = init( opts_data, need_amt=False )

msg('Checking CoinInfo WIF/P2PKH/P2SH version numbers and trust levels against protocol.py')
CoinInfo.verify_core_coin_data( quiet=opt.quiet, verbose=opt.verbose )
CoinInfo.verify_core_coin_data( cfg, cfg.quiet, cfg.verbose )

msg('Checking CoinInfo address leading symbols')
CoinInfo.verify_leading_symbols( quiet=opt.quiet, verbose=opt.verbose )
CoinInfo.verify_leading_symbols( cfg.quiet, cfg.verbose )
5 changes: 2 additions & 3 deletions mmgen/baseconv.py
Expand Up @@ -100,14 +100,13 @@ def get_wordlist_chksum(self):
from hashlib import sha256
return sha256( ' '.join(self.digits).encode() ).hexdigest()[:8]

def check_wordlist(self):
def check_wordlist(self,cfg):

wl = self.digits
from .util import qmsg,compare_chksums
ret = f'Wordlist: {self.wl_id}\nLength: {len(wl)} words'
new_chksum = self.get_wordlist_chksum()

compare_chksums( new_chksum, 'generated', self.wl_chksum, 'saved', die_on_fail=True )
cfg._util.compare_chksums( new_chksum, 'generated', self.wl_chksum, 'saved', die_on_fail=True )

if tuple(sorted(wl)) == wl:
return ret + '\nList is sorted'
Expand Down

0 comments on commit c7adb56

Please sign in to comment.