From f08cd32e33ceda7ba58dbcfd3c848e89ef5cd543 Mon Sep 17 00:00:00 2001 From: MMGen Date: Tue, 19 Mar 2019 11:55:49 +0000 Subject: [PATCH] MMGenListItem, MMGenTxInput, MMGenTxOutput: allow inheritance of attrs --- mmgen/obj.py | 9 ++++- mmgen/tx.py | 101 ++++++++++++++++++++++++--------------------------- 2 files changed, 54 insertions(+), 56 deletions(-) diff --git a/mmgen/obj.py b/mmgen/obj.py index 462add19..6fed1360 100755 --- a/mmgen/obj.py +++ b/mmgen/obj.py @@ -249,16 +249,21 @@ class MMGenListItemAttr(MMGenImmutableAttr): # Descriptor class MMGenListItem(MMGenObject): + valid_attrs = None + def __init__(self,*args,**kwargs): + if self.valid_attrs == None: + type(self).valid_attrs = ( + {e for e in dir(self) if e[:2] != '__'} - {'pformat','pmsg','pdie','valid_attrs'} ) if args: raise ValueError('Non-keyword args not allowed') for k in kwargs: if kwargs[k] != None: setattr(self,k,kwargs[k]) - # prevent setting random attributes + # allow only valid attributes to be set def __setattr__(self,name,value): - if name not in type(self).__dict__: + if name not in self.valid_attrs: m = "'{}': no such attribute in class {}" raise AttributeError(m.format(name,type(self))) return object.__setattr__(self,name,value) diff --git a/mmgen/tx.py b/mmgen/tx.py index d0e43a52..1498c40a 100755 --- a/mmgen/tx.py +++ b/mmgen/tx.py @@ -207,16 +207,44 @@ class DeserializedTX(dict,MMGenObject): dict.__init__(self,d) -txio_attrs = { - 'vout': MMGenListItemAttr('vout',int,typeconv=False), - 'amt': MMGenImmutableAttr('amt',g.proto.coin_amt,typeconv=False), # require amt to be of proper type - 'label': MMGenListItemAttr('label','TwComment',reassign_ok=True), - 'mmid': MMGenListItemAttr('mmid','MMGenID'), - 'addr': MMGenImmutableAttr('addr','CoinAddr'), - 'confs': MMGenListItemAttr('confs',int,typeconv=True), # long confs exist in the wild, so convert - 'txid': MMGenListItemAttr('txid','CoinTxID'), - 'have_wif': MMGenListItemAttr('have_wif',bool,typeconv=False,delete_ok=True) -} +class MMGenTxIO(MMGenListItem): + vout = MMGenListItemAttr('vout',int,typeconv=False) + amt = MMGenImmutableAttr('amt',g.proto.coin_amt,typeconv=False) # require amt to be of proper type + label = MMGenListItemAttr('label','TwComment',reassign_ok=True) + mmid = MMGenListItemAttr('mmid','MMGenID') + addr = MMGenImmutableAttr('addr','CoinAddr') + confs = MMGenListItemAttr('confs',int,typeconv=True) # confs of type long exist in the wild, so convert + txid = MMGenListItemAttr('txid','CoinTxID') + have_wif = MMGenListItemAttr('have_wif',bool,typeconv=False,delete_ok=True) + +class MMGenTxInput(MMGenTxIO): + scriptPubKey = MMGenListItemAttr('scriptPubKey','HexStr') + sequence = MMGenListItemAttr('sequence',int,typeconv=False) + +class MMGenTxOutput(MMGenTxIO): + is_chg = MMGenListItemAttr('is_chg',bool,typeconv=False) + +class MMGenTxInputList(list,MMGenObject): + + desc = 'transaction inputs' + member_type = 'MMGenTxInput' + + def convert_coin(self,verbose=False): + if verbose: + msg('{}:'.format(self.desc.capitalize())) + for i in self: + d = i.__dict__ + d['amt'] = g.proto.coin_amt(d['amt']) + + def check_coin_mismatch(self): + for i in self: + if type(i.amt) != g.proto.coin_amt: + die(2,'Coin mismatch in transaction: amount {} not of type {}!'.format(i.amt,g.proto.coin_amt)) + +class MMGenTxOutputList(MMGenTxInputList): + + desc = 'transaction outputs' + member_type = 'MMGenTxOutput' class MMGenTX(MMGenObject): @@ -252,45 +280,9 @@ inputs must be supplied to '{pnl}-txsign' in a file with the '--keys-from-file' option. Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_name.lower()) - class MMGenTxInput(MMGenListItem): - for k in txio_attrs: locals()[k] = txio_attrs[k] # in lieu of inheritance - scriptPubKey = MMGenListItemAttr('scriptPubKey','HexStr') - sequence = MMGenListItemAttr('sequence',int,typeconv=False) - - class MMGenTxOutput(MMGenListItem): - for k in txio_attrs: locals()[k] = txio_attrs[k] - is_chg = MMGenListItemAttr('is_chg',bool,typeconv=False) - - class MMGenTxInputList(list,MMGenObject): - - desc = 'transaction inputs' - member_type = 'MMGenTxInput' - - def convert_coin(self,verbose=False): - from mmgen.protocol import CoinProtocol - io = getattr(MMGenTX,self.member_type) - if verbose: - msg('{}:'.format(self.desc.capitalize())) - for i in self: - d = i.__dict__ - d['amt'] = g.proto.coin_amt(d['amt']) - i = io(**d) - if verbose: - pmsg(i.__dict__) - - def check_coin_mismatch(self): - for i in self: - if type(i.amt) != g.proto.coin_amt: - die(2,'Coin mismatch in transaction: amount {} not of type {}!'.format(i.amt,g.proto.coin_amt)) - - class MMGenTxOutputList(MMGenTxInputList): - - desc = 'transaction outputs' - member_type = 'MMGenTxOutput' - def __init__(self,filename=None,metadata_only=False,caller=None,silent_open=False): - self.inputs = self.MMGenTxInputList() - self.outputs = self.MMGenTxOutputList() + self.inputs = MMGenTxInputList() + self.outputs = MMGenTxOutputList() self.send_amt = g.proto.coin_amt('0') # total amt minus change self.fee = g.proto.coin_amt('0') self.hex = '' # raw serialized hex transaction @@ -328,7 +320,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam return not bad def add_output(self,coinaddr,amt,is_chg=None): - self.outputs.append(MMGenTX.MMGenTxOutput(addr=coinaddr,amt=amt,is_chg=is_chg)) + self.outputs.append(MMGenTxOutput(addr=coinaddr,amt=amt,is_chg=is_chg)) def get_chg_output_idx(self): ch_ops = [x.is_chg for x in self.outputs] @@ -340,7 +332,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam def update_output_amt(self,idx,amt): o = self.outputs[idx].__dict__ o['amt'] = amt - self.outputs[idx] = MMGenTX.MMGenTxOutput(**o) + self.outputs[idx] = MMGenTxOutput(**o) def update_change_output(self,change_amt): chg_idx = self.get_chg_output_idx() @@ -620,9 +612,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): - txi,self.inputs = self.MMGenTxInput,self.MMGenTxInputList() + self.inputs = MMGenTxInputList() + MMGenTxInput() # throwaway instance to initialize cls.valid_attrs for d in tw_unspent_data: - t = txi(**{attr:getattr(d,attr) for attr in d.__dict__ if attr in txi.__dict__}) + 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 self.inputs.append(t) @@ -1140,8 +1133,8 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam assert len(d),'no {}!'.format(desc) for e in d: e['amt'] = g.proto.coin_amt(e['amt']) io,io_list = ( - (MMGenTX.MMGenTxOutput,MMGenTX.MMGenTxOutputList), - (MMGenTX.MMGenTxInput,MMGenTX.MMGenTxInputList) + (MMGenTxOutput,MMGenTxOutputList), + (MMGenTxInput,MMGenTxInputList) )[desc=='inputs'] return io_list([io(**e) for e in d])