Browse Source

py3port: new HexBytes class

MMGen 6 years ago
parent
commit
af2bec1220
8 changed files with 49 additions and 25 deletions
  1. 1 1
      mmgen/addr.py
  2. 6 6
      mmgen/altcoins/eth/tx.py
  3. 29 11
      mmgen/obj.py
  4. 5 1
      mmgen/rpc.py
  5. 1 1
      mmgen/tw.py
  6. 4 4
      mmgen/tx.py
  7. 2 0
      mmgen/util.py
  8. 1 1
      test/objtest.py

+ 1 - 1
mmgen/addr.py

@@ -68,7 +68,7 @@ class AddrGeneratorSegwit(AddrGenerator):
 
 	def to_segwit_redeem_script(self,pubhex):
 		assert pubhex.compressed,'Uncompressed public keys incompatible with Segwit'
-		return HexStr(g.proto.pubhex2redeem_script(pubhex))
+		return HexBytes(g.proto.pubhex2redeem_script(pubhex))
 
 class AddrGeneratorBech32(AddrGenerator):
 	def to_addr(self,pubhex):

+ 6 - 6
mmgen/altcoins/eth/tx.py

@@ -46,7 +46,7 @@ class EthereumMMGenTX(MMGenTX):
 	usr_rel_fee = None # not in MMGenTX
 	disable_fee_check = False
 	txobj  = None # ""
-	data = HexStr('')
+	data = HexBytes('')
 
 	def __init__(self,*args,**kwargs):
 		super(EthereumMMGenTX,self).__init__(*args,**kwargs)
@@ -55,7 +55,7 @@ class EthereumMMGenTX(MMGenTX):
 		if hasattr(opt,'contract_data') and opt.contract_data:
 			m = "'--contract-data' option may not be used with token transaction"
 			assert not 'Token' in type(self).__name__, m
-			self.data = HexStr(open(opt.contract_data).read().strip())
+			self.data = HexBytes(open(opt.contract_data).read().strip())
 			self.disable_fee_check = True
 
 	@classmethod
@@ -84,7 +84,7 @@ class EthereumMMGenTX(MMGenTX):
 	def check_pubkey_scripts(self): pass
 
 	def check_sigs(self,deserial_tx=None):
-		if is_hex_str(self.hex):
+		if is_hex_bytes(self.hex):
 			self.mark_signed()
 			return True
 		return False
@@ -105,7 +105,7 @@ class EthereumMMGenTX(MMGenTX):
 					'gasPrice': ETHAmt(d['gasprice'],'wei'),
 					'startGas': ETHAmt(d['startgas'],'wei'),
 					'nonce':    ETHNonce(d['nonce']),
-					'data':     HexStr(d['data']) }
+					'data':     HexBytes(d['data']) }
 			if o['data'] and not o['to']:
 				self.token_addr = TokenAddr(hexlify(etx.creates).decode())
 			txid = CoinTxID(hexlify(etx.hash))
@@ -119,7 +119,7 @@ class EthereumMMGenTX(MMGenTX):
 					'startGas': ETHAmt(d['startGas']),
 					'nonce':    ETHNonce(d['nonce']),
 					'chainId':  Int(d['chainId']),
-					'data':     HexStr(d['data']) }
+					'data':     HexBytes(d['data']) }
 		self.tx_gas = o['startGas'] # approximate, but better than nothing
 		self.data = o['data']
 		if o['data'] and not o['to']: self.disable_fee_check = True
@@ -413,7 +413,7 @@ class EthereumTokenMMGenTX(EthereumMMGenTX):
 
 	def set_g_token(self):
 		g.dcoin = self.dcoin
-		if is_hex_str(self.hex): return # for txsend we can leave g.token uninitialized
+		if is_hex_bytes(self.hex): return # for txsend we can leave g.token uninitialized
 		d = json.loads(self.hex)
 		if g.token.upper() == self.dcoin:
 			g.token = d['token_addr']

+ 29 - 11
mmgen/obj.py

@@ -559,18 +559,20 @@ class TwLabel(str,InitErrors,MMGenObject):
 			m = "{}\n{!r}: value cannot be converted to TwLabel"
 			return cls.init_fail(m.format(e.args[0],s),on_fail)
 
-class HexStr(str,Hilite,InitErrors):
+class HexBytes(bytes,Hilite,InitErrors):
 	color = 'red'
 	trunc_ok = False
+	dtype = bytes
 	def __new__(cls,s,on_fail='die',case='lower'):
 		if type(s) == cls: return s
 		assert case in ('upper','lower')
 		cls.arg_chk(cls,on_fail)
+		if issubclass(type(s),bytes): s = s.decode()
 		try:
-			assert type(s) in (str,str,bytes),'not a string'
+			assert type(s) == str,'not a string'
 			assert set(s) <= set(getattr(hexdigits,case)()),'not {}case hexadecimal symbols'.format(case)
 			assert not len(s) % 2,'odd-length string'
-			return str.__new__(cls,s)
+			return cls.dtype.__new__(cls,s.encode() if cls.dtype == bytes else s)
 		except Exception as e:
 			m = "{!r}: value cannot be converted to {} (value is {})"
 			return cls.init_fail(m.format(s,cls.__name__,e.args[0]),on_fail)
@@ -578,25 +580,41 @@ class HexStr(str,Hilite,InitErrors):
 class Str(str,Hilite): pass
 class Int(int,Hilite): pass
 
-class HexStrWithWidth(HexStr):
+class HexBytesWithWidth(HexBytes):
 	color = 'nocolor'
-	trunc_ok = False
 	hexcase = 'lower'
 	width = None
+	parent_cls = HexBytes
 	def __new__(cls,s,on_fail='die'):
 		cls.arg_chk(cls,on_fail)
 		try:
-			ret = HexStr.__new__(cls,s,case=cls.hexcase,on_fail='raise')
+			ret = cls.parent_cls.__new__(cls,s,case=cls.hexcase,on_fail='raise')
 			assert len(s) == cls.width,'Value is not {} characters wide'.format(cls.width)
 			return ret
 		except Exception as e:
 			m = "{}\n{!r}: value cannot be converted to {}"
 			return cls.init_fail(m.format(e.args[0],s,cls.__name__),on_fail)
 
-class MMGenTxID(HexStrWithWidth):      color,width,hexcase = 'red',6,'upper'
-class MoneroViewKey(HexStrWithWidth):  color,width,hexcase = 'cyan',64,'lower'
+class CoinTxID(HexBytesWithWidth):       color,width,hexcase = 'purple',64,'lower'
+
+class HexStr(str,Hilite,InitErrors):
+	color = 'red'
+	trunc_ok = False
+	dtype = str
+	def __new__(cls,s,on_fail='die',case='lower'):
+		return HexBytes.__new__(cls,s,on_fail=on_fail,case=case)
+
+class HexStrWithWidth(HexStr):
+	color = 'nocolor'
+	hexcase = 'lower'
+	width = None
+	parent_cls = HexStr
+	def __new__(cls,s,on_fail='die'):
+		return HexBytesWithWidth.__new__(cls,s,on_fail=on_fail)
+
 class WalletPassword(HexStrWithWidth): color,width,hexcase = 'blue',32,'lower'
-class CoinTxID(HexStrWithWidth):       color,width,hexcase = 'purple',64,'lower'
+class MoneroViewKey(HexStrWithWidth):  color,width,hexcase = 'cyan',64,'lower'
+class MMGenTxID(HexStrWithWidth):      color,width,hexcase = 'red',6,'upper'
 
 class WifKey(str,Hilite,InitErrors):
 	width = 53
@@ -613,11 +631,11 @@ class WifKey(str,Hilite,InitErrors):
 			m = '{!r}: invalid value for WIF key ({})'.format(s,e.args[0])
 			return cls.init_fail(m,on_fail)
 
-class PubKey(HexStr,MMGenObject): # TODO: add some real checks
+class PubKey(HexBytes,MMGenObject): # TODO: add some real checks
 	def __new__(cls,s,compressed,on_fail='die'):
 		try:
 			assert type(compressed) == bool,"'compressed' must be of type bool"
-			me = HexStr.__new__(cls,s,case='lower',on_fail='raise')
+			me = HexBytes.__new__(cls,s,case='lower',on_fail='raise')
 			me.compressed = compressed
 			return me
 		except Exception as e:

+ 5 - 1
mmgen/rpc.py

@@ -110,11 +110,15 @@ class CoinDaemonRPCConnection(object):
 		dmsg_rpc('    RPC POST data ==> {}\n'.format(p))
 
 		ca_type = self.coin_amt_type if hasattr(self,'coin_amt_type') else str
+		from mmgen.obj import CoinTxID,HexBytes
 		class MyJSONEncoder(json.JSONEncoder):
 			def default(self,obj):
 				if isinstance(obj,g.proto.coin_amt):
 					return ca_type(obj)
-				return json.JSONEncoder.default(self,obj)
+				elif isinstance(obj,CoinTxID) or isinstance(obj,HexBytes):
+					return obj.decode()
+				else:
+					return json.JSONEncoder.default(self,obj)
 
 		http_hdr = { 'Content-Type': 'application/json' }
 		if self.auth:

+ 1 - 1
mmgen/tw.py

@@ -63,7 +63,7 @@ Actions: [q]uit view, [p]rint to file, pager [v]iew, [w]ide view, add [l]abel:
 		twmmid   = MMGenImmutableAttr('twmmid','TwMMGenID')
 		addr     = MMGenImmutableAttr('addr','CoinAddr')
 		confs    = MMGenImmutableAttr('confs',int,typeconv=False)
-		scriptPubKey = MMGenImmutableAttr('scriptPubKey','HexStr')
+		scriptPubKey = MMGenImmutableAttr('scriptPubKey','HexBytes')
 		days    = MMGenListItemAttr('days',int,typeconv=False)
 		skip    = MMGenListItemAttr('skip',str,typeconv=False,reassign_ok=True)
 

+ 4 - 4
mmgen/tx.py

@@ -238,7 +238,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
 
 	class MMGenTxInput(MMGenListItem):
 		for k in txio_attrs: locals()[k] = txio_attrs[k] # in lieu of inheritance
-		scriptPubKey = MMGenListItemAttr('scriptPubKey','HexStr')
+		scriptPubKey = MMGenListItemAttr('scriptPubKey','HexBytes')
 		sequence = MMGenListItemAttr('sequence',(int,int)[g.platform=='win'],typeconv=False)
 
 	class MMGenTxOutput(MMGenListItem):
@@ -368,7 +368,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
 		if self.inputs[0].sequence:
 			i[0]['sequence'] = self.inputs[0].sequence
 		o = dict([(e.addr,e.amt) for e in self.outputs])
-		self.hex = g.rpch.createrawtransaction(i,o)
+		self.hex = HexBytes(g.rpch.createrawtransaction(i,o))
 		self.update_txid()
 
 	# returns true if comment added or changed
@@ -729,7 +729,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
 			return False
 
 		try:
-			self.hex = ret['hex']
+			self.hex = HexBytes(ret['hex'])
 			self.compare_size_and_estimated_size()
 			dt = DeserializedTX(self.hex)
 			self.check_hex_tx_matches_mmgen_tx(dt)
@@ -1113,7 +1113,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
 		return out # TX label might contain non-ascii chars
 
 	def check_txfile_hex_data(self):
-		self.hex = HexStr(self.hex,on_fail='raise')
+		self.hex = HexBytes(self.hex,on_fail='raise')
 
 	def parse_tx_file(self,infile,metadata_only=False,silent_open=False):
 

+ 2 - 0
mmgen/util.py

@@ -241,6 +241,8 @@ def is_int(s):
 
 # https://en.wikipedia.org/wiki/Base32#RFC_4648_Base32_alphabet
 # https://tools.ietf.org/html/rfc4648
+def is_hex_bytes(s):  return set(list(s.lower())) <= set(list(hexdigits.lower().encode()))
+
 def is_hex_str(s):    return set(list(s.lower())) <= set(list(hexdigits.lower()))
 def is_hex_str_lc(s): return set(list(s))         <= set(list(hexdigits.lower()))
 def is_hex_str_uc(s): return set(list(s))         <= set(list(hexdigits.upper()))

+ 1 - 1
test/objtest.py

@@ -159,7 +159,7 @@ tests = OrderedDict([
 			'F00BAA12:S:9999999 comment',
 			tw_pfx+'x comment')
 	}),
-	('HexStr', {
+	('HexBytes', {
 		'bad':  (1,[],'\0','\1','я','g','gg','FF','f00'),
 		'good': ('deadbeef','f00baa12')
 	}),