From 514278d61620f7f7dd5d4cf667cb9b94909082a6 Mon Sep 17 00:00:00 2001 From: MMGen Date: Sat, 19 Oct 2019 14:28:46 +0000 Subject: [PATCH] MMGenListItem: require all immutable attrs to be set in constructor --- mmgen/addr.py | 2 +- mmgen/altcoins/eth/tw.py | 14 +++++++++++++- mmgen/devtools.py | 19 +++++++++++++++++++ mmgen/obj.py | 19 ++++++++++++++++--- mmgen/tx.py | 8 +++++--- 5 files changed, 54 insertions(+), 8 deletions(-) diff --git a/mmgen/addr.py b/mmgen/addr.py index 5df86c1b..db8034b7 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -300,7 +300,7 @@ class AddrListEntry(MMGenListItem): wallet_passwd = MMGenListItemAttr('wallet_passwd','WalletPassword') class PasswordListEntry(MMGenListItem): - passwd = MMGenImmutableAttr('passwd',str,typeconv=False) # TODO: create Password type + passwd = MMGenListItemAttr('passwd',str,typeconv=False) # TODO: create Password type idx = MMGenImmutableAttr('idx','AddrIdx') label = MMGenListItemAttr('label','TwComment',reassign_ok=True) sec = MMGenListItemAttr('sec',PrivKey,typeconv=False) diff --git a/mmgen/altcoins/eth/tw.py b/mmgen/altcoins/eth/tw.py index 818a58c9..3937911c 100755 --- a/mmgen/altcoins/eth/tw.py +++ b/mmgen/altcoins/eth/tw.py @@ -21,7 +21,7 @@ altcoins.eth.tw: Ethereum tracking wallet and related classes for the MMGen suit """ from mmgen.common import * -from mmgen.obj import ETHAmt,TwLabel,is_coin_addr,is_mmgen_id +from mmgen.obj import ETHAmt,TwLabel,is_coin_addr,is_mmgen_id,MMGenListItem,MMGenListItemAttr,MMGenImmutableAttr from mmgen.tw import TrackingWallet,TwAddrList,TwUnspentOutputs from mmgen.addr import AddrData from .contract import Token @@ -295,6 +295,18 @@ Actions: [q]uit view, [p]rint to file, pager [v]iew, [w]ide view, 'confirmations': 0, # TODO } for d in wl] + class MMGenTwUnspentOutput(MMGenListItem): + txid = MMGenListItemAttr('txid','CoinTxID') + vout = MMGenListItemAttr('vout',int,typeconv=False) + amt = MMGenImmutableAttr('amt',g.proto.coin_amt.__name__) + amt2 = MMGenListItemAttr('amt2',g.proto.coin_amt.__name__) + label = MMGenListItemAttr('label','TwComment',reassign_ok=True) + twmmid = MMGenImmutableAttr('twmmid','TwMMGenID') + addr = MMGenImmutableAttr('addr','CoinAddr') + confs = MMGenImmutableAttr('confs',int,typeconv=False) + days = MMGenListItemAttr('days',int,typeconv=False) + skip = MMGenListItemAttr('skip',str,typeconv=False,reassign_ok=True) + class EthereumTokenTwUnspentOutputs(EthereumTwUnspentOutputs): disp_type = 'token' diff --git a/mmgen/devtools.py b/mmgen/devtools.py index c3f88085..bd8d7e9c 100755 --- a/mmgen/devtools.py +++ b/mmgen/devtools.py @@ -2,6 +2,7 @@ class MMGenObject(object): 'placeholder - overridden when testing' + def immutable_attr_init_check(self): pass import os if os.getenv('MMGEN_DEBUG') or os.getenv('MMGEN_TEST_SUITE') or os.getenv('MMGEN_TRACEBACK'): @@ -107,6 +108,24 @@ if os.getenv('MMGEN_DEBUG') or os.getenv('MMGEN_TEST_SUITE') or os.getenv('MMGEN import re return re.sub('\n+','\n',''.join(out)) + # Check that all immutables have been initialized. Expensive, so do only when testing. + def immutable_attr_init_check(self): + from mmgen.globalvars import g + if g.test_suite: + from mmgen.util import rdie + cls = type(self) + for attrname in sorted({a for a in self.valid_attrs if a[0] != '_'}): + for o in (cls,cls.__bases__[0]): # assume there's only one base class + if attrname in o.__dict__: + attr = o.__dict__[attrname] + break + else: + rdie(3,'unable to find descriptor {}.{}'.format(cls.__name__,attrname)) + if type(attr).__name__ == 'MMGenImmutableAttr': + if attrname not in self.__dict__: + fs = 'attribute {!r} of {} has not been initialized in constructor!' + rdie(3,fs.format(attrname,cls.__name__)) + def print_diff(a,b,from_json=True): if from_json: a = json.dumps(json.loads(a),indent=4).split('\n') if a else [] diff --git a/mmgen/obj.py b/mmgen/obj.py index f0b47027..8bd3597d 100755 --- a/mmgen/obj.py +++ b/mmgen/obj.py @@ -251,18 +251,31 @@ class MMGenListItem(MMGenObject): valid_attrs = None valid_attrs_extra = set() + invalid_attrs = { + 'pfmt', + 'pmsg', + 'pdie', + 'valid_attrs', + 'valid_attrs_extra', + 'invalid_attrs', + 'immutable_attr_init_check', + } def __init__(self,*args,**kwargs): if self.valid_attrs == None: type(self).valid_attrs = ( - ( {e for e in dir(self) if e[:2] != '__'} | self.valid_attrs_extra ) - - {'pfmt','pmsg','pdie','valid_attrs','valid_attrs_extra'} ) + ( {e for e in dir(self) if e[:2] != '__'} | self.valid_attrs_extra ) - self.invalid_attrs ) + if args: - raise ValueError('Non-keyword args not allowed') + raise ValueError('Non-keyword args not allowed in {!r} constructor'.format(type(self).__name__)) + for k in kwargs: if kwargs[k] != None: setattr(self,k,kwargs[k]) + # Require all immutables to be initialized. Check performed only when testing. + self.immutable_attr_init_check() + # allow only valid attributes to be set def __setattr__(self,name,value): if name not in self.valid_attrs: diff --git a/mmgen/tx.py b/mmgen/tx.py index bb6f5937..c37b829e 100755 --- a/mmgen/tx.py +++ b/mmgen/tx.py @@ -247,6 +247,8 @@ class MMGenTxIO(MMGenListItem): class MMGenTxInput(MMGenTxIO): scriptPubKey = MMGenListItemAttr('scriptPubKey','HexStr') sequence = MMGenListItemAttr('sequence',int,typeconv=False) + # required by copy_inputs_from_tw() + copy_attrs = { 'scriptPubKey','vout','amt','label','mmid','addr','confs','txid' } class MMGenTxOutput(MMGenTxIO): is_chg = MMGenListItemAttr('is_chg',bool,typeconv=False) @@ -660,10 +662,10 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam # inputs methods def copy_inputs_from_tw(self,tw_unspent_data): self.inputs = MMGenTxInputList() - MMGenTxInput() # throwaway instance to initialize cls.valid_attrs for d in tw_unspent_data: - t = MMGenTxInput(**{attr:getattr(d,attr) for attr in d.__dict__ if attr in MMGenTxInput.valid_attrs}) - if d.twmmid.type == 'mmgen': t.mmid = d.twmmid # twmmid -> mmid + t = MMGenTxInput(**{attr:getattr(d,attr) for attr in d.__dict__ if attr in MMGenTxInput.copy_attrs}) + if d.twmmid.type == 'mmgen': + t.mmid = d.twmmid # twmmid -> mmid self.inputs.append(t) def get_input_sids(self):