From 6346c1d11a383efa45714a931eb834d81680c94d Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Fri, 18 Oct 2024 10:32:09 +0000 Subject: [PATCH] whitespace: proto.eth (plus cleanup) --- mmgen/proto/eth/addrdata.py | 6 +-- mmgen/proto/eth/contract.py | 72 ++++++++++++++--------------- mmgen/proto/eth/daemon.py | 62 ++++++++++++------------- mmgen/proto/eth/misc.py | 21 +++++---- mmgen/proto/eth/msg.py | 12 ++--- mmgen/proto/eth/params.py | 16 +++---- mmgen/proto/eth/rpc.py | 38 +++++++-------- mmgen/proto/eth/tw/addresses.py | 8 ++-- mmgen/proto/eth/tw/bal.py | 8 ++-- mmgen/proto/eth/tw/ctl.py | 82 ++++++++++++++++----------------- mmgen/proto/eth/tw/json.py | 56 +++++++++++----------- mmgen/proto/eth/tw/unspent.py | 30 ++++++------ mmgen/proto/eth/tw/view.py | 10 ++-- mmgen/proto/eth/tx/base.py | 19 ++++---- mmgen/proto/eth/tx/bump.py | 10 ++-- mmgen/proto/eth/tx/completed.py | 10 ++-- mmgen/proto/eth/tx/info.py | 20 ++++---- mmgen/proto/eth/tx/new.py | 63 ++++++++++++------------- mmgen/proto/eth/tx/online.py | 22 ++++----- mmgen/proto/eth/tx/signed.py | 20 ++++---- mmgen/proto/eth/tx/status.py | 22 ++++----- mmgen/proto/eth/tx/unsigned.py | 32 ++++++------- 22 files changed, 321 insertions(+), 318 deletions(-) diff --git a/mmgen/proto/eth/addrdata.py b/mmgen/proto/eth/addrdata.py index 4013ee7a..e093854c 100755 --- a/mmgen/proto/eth/addrdata.py +++ b/mmgen/proto/eth/addrdata.py @@ -31,12 +31,12 @@ class EthereumTwAddrData(TwAddrData): """ } - async def get_tw_data(self,twctl=None): + async def get_tw_data(self, twctl=None): from ...tw.ctl import TwCtl self.cfg._util.vmsg('Getting address data from tracking wallet') - twctl = (twctl or await TwCtl(self.cfg,self.proto)).mmid_ordered_dict + twctl = (twctl or await TwCtl(self.cfg, self.proto)).mmid_ordered_dict # emulate the output of RPC 'listaccounts' and 'getaddressesbyaccount' - return [(mmid+' '+d['comment'],[d['addr']]) for mmid,d in list(twctl.items())] + return [(mmid+' '+d['comment'], [d['addr']]) for mmid, d in list(twctl.items())] class EthereumTokenTwAddrData(EthereumTwAddrData): pass diff --git a/mmgen/proto/eth/contract.py b/mmgen/proto/eth/contract.py index 972f249c..73e363ac 100755 --- a/mmgen/proto/eth/contract.py +++ b/mmgen/proto/eth/contract.py @@ -24,46 +24,46 @@ from decimal import Decimal from . import rlp from . import erigon_sleep -from ...util import msg,pp_msg,die +from ...util import msg, pp_msg, die from ...base_obj import AsyncInit -from ...obj import MMGenObject,CoinTxID -from ...addr import CoinAddr,TokenAddr +from ...obj import MMGenObject, CoinTxID +from ...addr import CoinAddr, TokenAddr def parse_abi(s): return [s[:8]] + [s[8+x*64:8+(x+1)*64] for x in range(len(s[8:])//64)] class TokenCommon(MMGenObject): - def create_method_id(self,sig): + def create_method_id(self, sig): return self.keccak_256(sig.encode()).hexdigest()[:8] - def transferdata2sendaddr(self,data): # online - return CoinAddr(self.proto,parse_abi(data)[1][-40:]) + def transferdata2sendaddr(self, data): # online + return CoinAddr(self.proto, parse_abi(data)[1][-40:]) - def transferdata2amt(self,data): # online + def transferdata2amt(self, data): # online return self.proto.coin_amt( int(parse_abi(data)[-1], 16) * self.base_unit, from_decimal = True) - async def do_call(self,method_sig,method_args='',toUnit=False): + async def do_call(self, method_sig, method_args='', toUnit=False): data = self.create_method_id(method_sig) + method_args if self.cfg.debug: msg('ETH_CALL {}: {}'.format( method_sig, - '\n '.join(parse_abi(data)) )) - ret = await self.rpc.call('eth_call',{ 'to': '0x'+self.addr, 'data': '0x'+data },'pending') + '\n '.join(parse_abi(data)))) + ret = await self.rpc.call('eth_call', {'to': '0x'+self.addr, 'data': '0x'+data}, 'pending') await erigon_sleep(self) if toUnit: - return int(ret,16) * self.base_unit + return int(ret, 16) * self.base_unit else: return ret - async def get_balance(self,acct_addr): + async def get_balance(self, acct_addr): return self.proto.coin_amt( await self.do_call('balanceOf(address)', acct_addr.rjust(64, '0'), toUnit=True), from_decimal = True) - def strip(self,s): + def strip(self, s): return ''.join([chr(b) for b in s if 32 <= b <= 127]).strip() async def get_name(self): @@ -76,13 +76,13 @@ class TokenCommon(MMGenObject): ret = await self.do_call('decimals()') try: assert ret[:2] == '0x' - return int(ret,16) + return int(ret, 16) except: msg(f'RPC call to decimals() failed (returned {ret!r})') return None async def get_total_supply(self): - return await self.do_call('totalSupply()',toUnit=True) + return await self.do_call('totalSupply()', toUnit=True) async def info(self): return ('{:15}{}\n' * 5).format( @@ -90,10 +90,10 @@ class TokenCommon(MMGenObject): 'token symbol:', await self.get_symbol(), 'token name:', await self.get_name(), 'decimals:', self.decimals, - 'total supply:', await self.get_total_supply() ) + 'total supply:', await self.get_total_supply()) async def code(self): - return (await self.rpc.call('eth_getCode','0x'+self.addr))[2:] + return (await self.rpc.call('eth_getCode', '0x'+self.addr))[2:] def create_data( self, @@ -101,8 +101,8 @@ class TokenCommon(MMGenObject): amt, method_sig = 'transfer(address,uint256)'): from_arg = '' - to_arg = to_addr.rjust(64,'0') - amt_arg = '{:064x}'.format( int(amt / self.base_unit) ) + to_arg = to_addr.rjust(64, '0') + amt_arg = '{:064x}'.format(int(amt / self.base_unit)) return self.create_method_id(method_sig) + from_arg + to_arg + amt_arg def make_tx_in( @@ -125,24 +125,24 @@ class TokenCommon(MMGenObject): 'nonce': nonce, 'data': bytes.fromhex(data)} - async def txsign(self,tx_in,key,from_addr,chain_id=None): + async def txsign(self, tx_in, key, from_addr, chain_id=None): from .pyethereum.transactions import Transaction if chain_id is None: res = await self.rpc.call('eth_chainId') - chain_id = None if res is None else int(res,16) + chain_id = None if res is None else int(res, 16) - tx = Transaction(**tx_in).sign(key,chain_id) + tx = Transaction(**tx_in).sign(key, chain_id) if tx.sender.hex() != from_addr: - die(3,f'Sender address {from_addr!r} does not match address of key {tx.sender.hex()!r}!') + die(3, f'Sender address {from_addr!r} does not match address of key {tx.sender.hex()!r}!') if self.cfg.debug: msg('TOKEN DATA:') pp_msg(tx.to_dict()) msg('PARSED ABI DATA:\n {}'.format( - '\n '.join(parse_abi(tx.data.hex())) )) + '\n '.join(parse_abi(tx.data.hex())))) return ( rlp.encode(tx).hex(), @@ -151,8 +151,8 @@ class TokenCommon(MMGenObject): # The following are used for token deployment only: - async def txsend(self,txhex): - return (await self.rpc.call('eth_sendRawTransaction','0x'+txhex)).replace('0x','',1) + async def txsend(self, txhex): + return (await self.rpc.call('eth_sendRawTransaction', '0x'+txhex)).replace('0x', '', 1) async def transfer( self, @@ -168,35 +168,35 @@ class TokenCommon(MMGenObject): amt, start_gas, gasPrice, - nonce = int(await self.rpc.call('eth_getTransactionCount','0x'+from_addr,'pending'),16), + nonce = int(await self.rpc.call('eth_getTransactionCount', '0x'+from_addr, 'pending'), 16), method_sig = method_sig) - txhex,_ = await self.txsign(tx_in,key,from_addr) + txhex, _ = await self.txsign(tx_in, key, from_addr) return await self.txsend(txhex) class Token(TokenCommon): - def __init__(self,cfg,proto,addr,decimals,rpc=None): + def __init__(self, cfg, proto, addr, decimals, rpc=None): if type(self).__name__ == 'Token': from ...util2 import get_keccak self.keccak_256 = get_keccak(cfg) self.cfg = cfg self.proto = proto - self.addr = TokenAddr(proto,addr) - assert isinstance(decimals,int),f'decimals param must be int instance, not {type(decimals)}' + self.addr = TokenAddr(proto, addr) + assert isinstance(decimals, int), f'decimals param must be int instance, not {type(decimals)}' self.decimals = decimals self.base_unit = Decimal('10') ** -self.decimals self.rpc = rpc -class ResolvedToken(TokenCommon,metaclass=AsyncInit): +class ResolvedToken(TokenCommon, metaclass=AsyncInit): - async def __init__(self,cfg,proto,rpc,addr): + async def __init__(self, cfg, proto, rpc, addr): from ...util2 import get_keccak self.keccak_256 = get_keccak(cfg) self.cfg = cfg self.proto = proto self.rpc = rpc - self.addr = TokenAddr(proto,addr) + self.addr = TokenAddr(proto, addr) decimals = await self.get_decimals() # requires self.addr! if not decimals: - die( 'TokenNotInBlockchain', f'Token {addr!r} not in blockchain' ) - Token.__init__(self,cfg,proto,addr,decimals,rpc) + die('TokenNotInBlockchain', f'Token {addr!r} not in blockchain') + Token.__init__(self, cfg, proto, addr, decimals, rpc) diff --git a/mmgen/proto/eth/daemon.py b/mmgen/proto/eth/daemon.py index ee371b69..8d9e4471 100755 --- a/mmgen/proto/eth/daemon.py +++ b/mmgen/proto/eth/daemon.py @@ -15,29 +15,29 @@ proto.eth.daemon: Ethereum base protocol daemon classes import os from ...cfg import gc -from ...util import list_gen,get_subclasses -from ...daemon import CoinDaemon,RPCDaemon,_nw,_dd +from ...util import list_gen, get_subclasses +from ...daemon import CoinDaemon, RPCDaemon, _nw, _dd class ethereum_daemon(CoinDaemon): - chain_subdirs = _nw('ethereum','goerli','DevelopmentChain') + chain_subdirs = _nw('ethereum', 'goerli', 'DevelopmentChain') base_rpc_port = 8545 # same for all networks! base_authrpc_port = 8551 # same for all networks! base_p2p_port = 30303 # same for all networks! daemon_port_offset = 100 - network_port_offsets = _nw(0,10,20) + network_port_offsets = _nw(0, 10, 20) - def __init__(self,*args,test_suite=False,**kwargs): + def __init__(self, *args, test_suite=False, **kwargs): - if not hasattr(self,'all_daemons'): - ethereum_daemon.all_daemons = get_subclasses(ethereum_daemon,names=True) + if not hasattr(self, 'all_daemons'): + ethereum_daemon.all_daemons = get_subclasses(ethereum_daemon, names=True) daemon_idx_offset = ( self.all_daemons.index(self.id+'_daemon') * self.daemon_port_offset - if test_suite else 0 ) + if test_suite else 0) - self.port_offset = daemon_idx_offset + getattr(self.network_port_offsets,self.network) + self.port_offset = daemon_idx_offset + getattr(self.network_port_offsets, self.network) - super().__init__( *args, test_suite=test_suite, **kwargs ) + super().__init__(*args, test_suite=test_suite, **kwargs) def get_rpc_port(self): return self.base_rpc_port + self.port_offset @@ -54,7 +54,7 @@ class ethereum_daemon(CoinDaemon): return os.path.join( self.logdir, self.id, - getattr(self.chain_subdirs,self.network) ) + getattr(self.chain_subdirs, self.network)) class openethereum_daemon(ethereum_daemon): daemon_data = _dd('OpenEthereum', 3003005, '3.3.5') @@ -62,9 +62,9 @@ class openethereum_daemon(ethereum_daemon): exec_fn = 'openethereum' cfg_file = 'parity.conf' datadirs = { - 'linux': [gc.home_dir,'.local','share','io.parity.ethereum'], + 'linux': [gc.home_dir, '.local', 'share', 'io.parity.ethereum'], 'darwin': [gc.home_dir, 'Library', 'Application Support', 'io.parity.ethereum'], - 'win32': [os.getenv('LOCALAPPDATA'),'Parity','Ethereum'] + 'win32': [os.getenv('LOCALAPPDATA'), 'Parity', 'Ethereum'] } def init_subclass(self): @@ -102,20 +102,20 @@ class geth_daemon(ethereum_daemon): exec_fn = 'geth' use_pidfile = False use_threads = True - avail_opts = ('no_daemonize','online') + avail_opts = ('no_daemonize', 'online') version_info_arg = 'version' datadirs = { - 'linux': [gc.home_dir,'.ethereum','geth'], + 'linux': [gc.home_dir, '.ethereum', 'geth'], 'darwin': [gc.home_dir, 'Library', 'Ethereum', 'geth'], - 'win32': [os.getenv('LOCALAPPDATA'),'Geth'] # FIXME + 'win32': [os.getenv('LOCALAPPDATA'), 'Geth'] # FIXME } def init_subclass(self): def have_authrpc(): - from subprocess import run,PIPE + from subprocess import run, PIPE try: - return b'authrpc' in run(['geth','help'],check=True,stdout=PIPE).stdout + return b'authrpc' in run(['geth', 'help'], check=True, stdout=PIPE).stdout except: return False @@ -138,12 +138,12 @@ class erigon_daemon(geth_daemon): daemon_data = _dd('Erigon', 2022099099, '2022.99.99') version_pat = r'erigon/(\d+)\.(\d+)\.(\d+)' exec_fn = 'erigon' - private_ports = _nw(9090,9091,9092) # testnet and regtest are non-standard - torrent_ports = _nw(42069,42070,None) # testnet is non-standard + private_ports = _nw(9090, 9091, 9092) # testnet and regtest are non-standard + torrent_ports = _nw(42069, 42070, None) # testnet is non-standard version_info_arg = '--version' datadirs = { - 'linux': [gc.home_dir,'.local','share','erigon'], - 'win32': [os.getenv('LOCALAPPDATA'),'Erigon'] # FIXME + 'linux': [gc.home_dir, '.local', 'share', 'erigon'], + 'win32': [os.getenv('LOCALAPPDATA'), 'Erigon'] # FIXME } def init_subclass(self): @@ -169,21 +169,21 @@ class erigon_daemon(geth_daemon): rpc_port = self.rpc_port, private_port = self.private_port, test_suite = self.test_suite, - datadir = self.datadir ) + datadir = self.datadir) - def start(self,quiet=False,silent=False): - super().start(quiet=quiet,silent=silent) + def start(self, quiet=False, silent=False): + super().start(quiet=quiet, silent=silent) self.rpc_d.debug = self.debug - return self.rpc_d.start(quiet=quiet,silent=silent) + return self.rpc_d.start(quiet=quiet, silent=silent) - def stop(self,quiet=False,silent=False): + def stop(self, quiet=False, silent=False): self.rpc_d.debug = self.debug - self.rpc_d.stop(quiet=quiet,silent=silent) - return super().stop(quiet=quiet,silent=silent) + self.rpc_d.stop(quiet=quiet, silent=silent) + return super().stop(quiet=quiet, silent=silent) @property def start_cmds(self): - return [self.start_cmd,self.rpc_d.start_cmd] + return [self.start_cmd, self.rpc_d.start_cmd] class erigon_rpcdaemon(RPCDaemon): @@ -193,7 +193,7 @@ class erigon_rpcdaemon(RPCDaemon): use_pidfile = False use_threads = True - def __init__(self,cfg,proto,rpc_port,private_port,test_suite,datadir): + def __init__(self, cfg, proto, rpc_port, private_port, test_suite, datadir): self.proto = proto self.test_suite = test_suite diff --git a/mmgen/proto/eth/misc.py b/mmgen/proto/eth/misc.py index d3e0b330..4fe6f08f 100755 --- a/mmgen/proto/eth/misc.py +++ b/mmgen/proto/eth/misc.py @@ -14,7 +14,7 @@ proto.eth.misc: miscellaneous utilities for Ethereum base protocol from ...util2 import get_keccak -def decrypt_geth_keystore(cfg,wallet_fn,passwd,check_addr=True): +def decrypt_geth_keystore(cfg, wallet_fn, passwd, check_addr=True): """ Decrypt the encrypted private key in a Geth keystore wallet, returning the decrypted key """ @@ -33,32 +33,32 @@ def decrypt_geth_keystore(cfg,wallet_fn,passwd,check_addr=True): if check_addr: from ...tool.coin import tool_cmd from ...protocol import init_proto - t = tool_cmd( cfg=cfg, proto=init_proto(cfg,'eth') ) + t = tool_cmd(cfg=cfg, proto=init_proto(cfg, 'eth')) addr = t.wif2addr(key.hex()) addr_chk = wallet_data['address'] assert addr == addr_chk, f'incorrect address: ({addr} != {addr_chk})' return key -def hash_message(cfg,message,msghash_type): +def hash_message(cfg, message, msghash_type): return get_keccak(cfg)( { 'raw': message, - 'eth_sign': '\x19Ethereum Signed Message:\n{}{}'.format( len(message), message ), + 'eth_sign': '\x19Ethereum Signed Message:\n{}{}'.format(len(message), message), }[msghash_type].encode() ).digest() -def ec_sign_message_with_privkey(cfg,message,key,msghash_type): +def ec_sign_message_with_privkey(cfg, message, key, msghash_type): """ Sign an arbitrary string with an Ethereum private key, returning the signature Conforms to the standard defined by the Geth `eth_sign` JSON-RPC call """ from py_ecc.secp256k1 import ecdsa_raw_sign - v,r,s = ecdsa_raw_sign( hash_message(cfg,message,msghash_type), key ) - return '{:064x}{:064x}{:02x}'.format(r,s,v) + v, r, s = ecdsa_raw_sign(hash_message(cfg, message, msghash_type), key) + return '{:064x}{:064x}{:02x}'.format(r, s, v) -def ec_recover_pubkey(cfg,message,sig,msghash_type): +def ec_recover_pubkey(cfg, message, sig, msghash_type): """ Given a message and signature, recover the public key associated with the private key used to make the signature @@ -66,7 +66,8 @@ def ec_recover_pubkey(cfg,message,sig,msghash_type): Conforms to the standard defined by the Geth `eth_sign` JSON-RPC call """ from py_ecc.secp256k1 import ecdsa_raw_recover - r,s,v = ( sig[:64], sig[64:128], sig[128:] ) + r, s, v = (sig[:64], sig[64:128], sig[128:]) return '{:064x}{:064x}'.format( - *ecdsa_raw_recover( hash_message(cfg,message,msghash_type), tuple(int(hexstr,16) for hexstr in (v,r,s)) ) + *ecdsa_raw_recover( + hash_message(cfg, message, msghash_type), tuple(int(hexstr, 16) for hexstr in (v, r, s))) ) diff --git a/mmgen/proto/eth/msg.py b/mmgen/proto/eth/msg.py index 3a101aae..414bfd3a 100755 --- a/mmgen/proto/eth/msg.py +++ b/mmgen/proto/eth/msg.py @@ -18,23 +18,23 @@ class coin_msg(coin_msg): include_pubhash = False sigdata_pfx = '0x' - msghash_types = ('eth_sign','raw') # first-listed is the default + msghash_types = ('eth_sign', 'raw') # first-listed is the default class unsigned(coin_msg.unsigned): - async def do_sign(self,wif,message,msghash_type): + async def do_sign(self, wif, message, msghash_type): from .misc import ec_sign_message_with_privkey - return ec_sign_message_with_privkey( self.cfg, message, bytes.fromhex(wif), msghash_type ) + return ec_sign_message_with_privkey(self.cfg, message, bytes.fromhex(wif), msghash_type) class signed_online(coin_msg.signed_online): - async def do_verify(self,addr,sig,message,msghash_type): + async def do_verify(self, addr, sig, message, msghash_type): from ...tool.coin import tool_cmd from .misc import ec_recover_pubkey return tool_cmd( self.cfg, proto = self.proto).pubhex2addr( - ec_recover_pubkey( self.cfg, message, sig, msghash_type )) == addr + ec_recover_pubkey(self.cfg, message, sig, msghash_type)) == addr - class exported_sigs(coin_msg.exported_sigs,signed_online): + class exported_sigs(coin_msg.exported_sigs, signed_online): pass diff --git a/mmgen/proto/eth/params.py b/mmgen/proto/eth/params.py index 22ad0e14..e4d27fd5 100755 --- a/mmgen/proto/eth/params.py +++ b/mmgen/proto/eth/params.py @@ -12,13 +12,13 @@ proto.eth.params: Ethereum protocol """ -from ...protocol import CoinProtocol,_nw,decoded_addr +from ...protocol import CoinProtocol, _nw, decoded_addr from ...addr import CoinAddr -from ...util import is_hex_str_lc,Msg +from ...util import is_hex_str_lc, Msg -class mainnet(CoinProtocol.DummyWIF,CoinProtocol.Secp256k1): +class mainnet(CoinProtocol.DummyWIF, CoinProtocol.Secp256k1): - network_names = _nw('mainnet','testnet','devnet') + network_names = _nw('mainnet', 'testnet', 'devnet') addr_len = 20 mmtypes = ('E',) dfl_mmtype = 'E' @@ -27,7 +27,7 @@ class mainnet(CoinProtocol.DummyWIF,CoinProtocol.Secp256k1): coin_amt = 'ETHAmt' max_tx_fee = '0.005' - chain_names = ['ethereum','foundation'] + chain_names = ['ethereum', 'foundation'] sign_mode = 'standalone' caps = ('token',) mmcaps = ('rpc', 'rpc_init', 'tw', 'msg') @@ -63,9 +63,9 @@ class mainnet(CoinProtocol.DummyWIF,CoinProtocol.Secp256k1): Msg(f'Invalid address: {addr}') return False - def checksummed_addr(self,addr): + def checksummed_addr(self, addr): h = self.keccak_256(addr.encode()).digest().hex() - return ''.join(addr[i].upper() if int(h[i],16) > 7 else addr[i] for i in range(len(addr))) + return ''.join(addr[i].upper() if int(h[i], 16) > 7 else addr[i] for i in range(len(addr))) def pubhash2addr(self, pubhash, addr_type): assert len(pubhash) == 20, f'{len(pubhash)}: invalid length for {self.name} pubkey hash' @@ -74,7 +74,7 @@ class mainnet(CoinProtocol.DummyWIF,CoinProtocol.Secp256k1): return CoinAddr(self, pubhash.hex()) class testnet(mainnet): - chain_names = ['kovan','goerli','rinkeby'] + chain_names = ['kovan', 'goerli', 'rinkeby'] class regtest(testnet): chain_names = ['developmentchain'] diff --git a/mmgen/proto/eth/rpc.py b/mmgen/proto/eth/rpc.py index 294e6624..e76d2f87 100755 --- a/mmgen/proto/eth/rpc.py +++ b/mmgen/proto/eth/rpc.py @@ -16,7 +16,7 @@ import re from ...base_obj import AsyncInit from ...obj import Int -from ...util import die,fmt,oneshot_warning_group +from ...util import die, fmt, oneshot_warning_group from ...rpc import RPCClient class daemon_warning(oneshot_warning_group): @@ -32,7 +32,7 @@ class daemon_warning(oneshot_warning_group): class CallSigs: pass -class EthereumRPCClient(RPCClient,metaclass=AsyncInit): +class EthereumRPCClient(RPCClient, metaclass=AsyncInit): async def __init__( self, @@ -44,50 +44,50 @@ class EthereumRPCClient(RPCClient,metaclass=AsyncInit): self.proto = proto self.daemon = daemon - self.call_sigs = getattr(CallSigs,daemon.id,None) + self.call_sigs = getattr(CallSigs, daemon.id, None) super().__init__( cfg = cfg, host = 'localhost' if cfg.test_suite else (cfg.rpc_host or 'localhost'), - port = daemon.rpc_port ) + port = daemon.rpc_port) await self.set_backend_async(backend) - vi,bh,ci = await self.gathered_call(None, ( - ('web3_clientVersion',()), - ('eth_getBlockByNumber',('latest',False)), - ('eth_chainId',()), + vi, bh, ci = await self.gathered_call(None, ( + ('web3_clientVersion', ()), + ('eth_getBlockByNumber', ('latest', False)), + ('eth_chainId', ()), )) - vip = re.match(self.daemon.version_pat,vi,re.ASCII) + vip = re.match(self.daemon.version_pat, vi, re.ASCII) if not vip: - die(2,fmt(f""" + die(2, fmt(f""" Aborting on daemon mismatch: Requested daemon: {self.daemon.id} Running daemon: {vi} - """,strip_char='\t').rstrip()) + """, strip_char='\t').rstrip()) self.daemon_version = int('{:d}{:03d}{:03d}'.format(*[int(e) for e in vip.groups()])) self.daemon_version_str = '{}.{}.{}'.format(*vip.groups()) self.daemon_version_info = vi - self.blockcount = int(bh['number'],16) - self.cur_date = int(bh['timestamp'],16) + self.blockcount = int(bh['number'], 16) + self.cur_date = int(bh['timestamp'], 16) self.caps = () - if self.daemon.id in ('parity','openethereum'): + if self.daemon.id in ('parity', 'openethereum'): if (await self.call('parity_nodeKind'))['capability'] == 'full': self.caps += ('full_node',) - self.chainID = None if ci is None else Int(ci,16) # parity/oe return chainID only for dev chain - self.chain = (await self.call('parity_chain')).replace(' ','_').replace('_testnet','') - elif self.daemon.id in ('geth','erigon'): + self.chainID = None if ci is None else Int(ci, 16) # parity/oe return chainID only for dev chain + self.chain = (await self.call('parity_chain')).replace(' ', '_').replace('_testnet', '') + elif self.daemon.id in ('geth', 'erigon'): if self.daemon.network == 'mainnet': daemon_warning(self.daemon.id) self.caps += ('full_node',) - self.chainID = Int(ci,16) + self.chainID = Int(ci, 16) self.chain = self.proto.chain_ids[self.chainID] - def make_host_path(self,wallet): + def make_host_path(self, wallet): return '' rpcmethods = ( diff --git a/mmgen/proto/eth/tw/addresses.py b/mmgen/proto/eth/tw/addresses.py index daf80c63..bac56dbb 100755 --- a/mmgen/proto/eth/tw/addresses.py +++ b/mmgen/proto/eth/tw/addresses.py @@ -16,7 +16,7 @@ from ....tw.addresses import TwAddresses from .view import EthereumTwView from .rpc import EthereumTwRPC -class EthereumTwAddresses(TwAddresses,EthereumTwView,EthereumTwRPC): +class EthereumTwAddresses(TwAddresses, EthereumTwView, EthereumTwRPC): has_age = False prompt_fs_in = [ @@ -35,13 +35,13 @@ class EthereumTwAddresses(TwAddresses,EthereumTwView,EthereumTwRPC): 'D':'i_addr_delete', 'v':'a_view', 'w':'a_view_detail', - 'p':'a_print_detail' } + 'p':'a_print_detail'} - def get_column_widths(self,data,wide,interactive): + def get_column_widths(self, data, wide, interactive): return self.compute_column_widths( widths = { # fixed cols - 'num': max(2,len(str(len(data)))+1), + 'num': max(2, len(str(len(data)))+1), 'mmid': max(len(d.twmmid.disp) for d in data), 'used': 0, 'amt': self.amt_widths['amt'], diff --git a/mmgen/proto/eth/tw/bal.py b/mmgen/proto/eth/tw/bal.py index f9f33fce..79963638 100755 --- a/mmgen/proto/eth/tw/bal.py +++ b/mmgen/proto/eth/tw/bal.py @@ -25,14 +25,14 @@ from ....tw.bal import TwGetBalance class EthereumTwGetBalance(TwGetBalance): - start_labels = ('TOTAL','Non-MMGen') + start_labels = ('TOTAL', 'Non-MMGen') conf_cols = { 'ge_minconf': 'Balance', } - async def __init__(self,cfg,proto,*args,**kwargs): - self.twctl = await TwCtl(cfg,proto,mode='w') - await super().__init__(cfg,proto,*args,**kwargs) + async def __init__(self, cfg, proto, *args, **kwargs): + self.twctl = await TwCtl(cfg, proto, mode='w') + await super().__init__(cfg, proto, *args, **kwargs) async def create_data(self): in_data = self.twctl.mmid_ordered_dict diff --git a/mmgen/proto/eth/tw/ctl.py b/mmgen/proto/eth/tw/ctl.py index 13292cef..f2b2781f 100755 --- a/mmgen/proto/eth/tw/ctl.py +++ b/mmgen/proto/eth/tw/ctl.py @@ -20,11 +20,11 @@ proto.eth.tw.ctl: Ethereum tracking wallet control class """ -from ....util import msg,ymsg,die +from ....util import msg, ymsg, die from ....tw.ctl import TwCtl, write_mode, label_addr_pair from ....tw.shared import TwLabel -from ....addr import is_coin_addr,is_mmgen_id,CoinAddr -from ..contract import Token,ResolvedToken +from ....addr import is_coin_addr, is_mmgen_id, CoinAddr +from ..contract import Token, ResolvedToken class EthereumTwCtl(TwCtl): @@ -81,43 +81,43 @@ class EthereumTwCtl(TwCtl): self.force_write() msg(f'{self.desc} upgraded successfully!') - async def rpc_get_balance(self,addr): + async def rpc_get_balance(self, addr): return self.proto.coin_amt( int(await self.rpc.call('eth_getBalance', '0x' + addr, 'latest'), 16), from_unit = 'wei') @write_mode - async def batch_import_address(self,args_list): + async def batch_import_address(self, args_list): return [await self.import_address(*a) for a in args_list] - async def rescan_addresses(self,coin_addrs): + async def rescan_addresses(self, coin_addrs): pass @write_mode - async def import_address(self,addr,label,rescan=False): + async def import_address(self, addr, label, rescan=False): r = self.data_root if addr in r: if not r[addr]['mmid'] and label.mmid: msg(f'Warning: MMGen ID {label.mmid!r} was missing in tracking wallet!') elif r[addr]['mmid'] != label.mmid: - die(3,'MMGen ID {label.mmid!r} does not match tracking wallet!') - r[addr] = { 'mmid': label.mmid, 'comment': label.comment } + die(3, 'MMGen ID {label.mmid!r} does not match tracking wallet!') + r[addr] = {'mmid': label.mmid, 'comment': label.comment} @write_mode - async def remove_address(self,addr): + async def remove_address(self, addr): r = self.data_root - if is_coin_addr(self.proto,addr): + if is_coin_addr(self.proto, addr): have_match = lambda k: k == addr - elif is_mmgen_id(self.proto,addr): + elif is_mmgen_id(self.proto, addr): have_match = lambda k: r[k]['mmid'] == addr else: - die(1,f'{addr!r} is not an Ethereum address or MMGen ID') + die(1, f'{addr!r} is not an Ethereum address or MMGen ID') for k in r: if have_match(k): # return the addr resolved to mmid if possible - ret = r[k]['mmid'] if is_mmgen_id(self.proto,r[k]['mmid']) else addr + ret = r[k]['mmid'] if is_mmgen_id(self.proto, r[k]['mmid']) else addr del r[k] self.write() return ret @@ -125,8 +125,8 @@ class EthereumTwCtl(TwCtl): return None @write_mode - async def set_label(self,coinaddr,lbl): - for addr,d in list(self.data_root.items()): + async def set_label(self, coinaddr, lbl): + for addr, d in list(self.data_root.items()): if addr == coinaddr: d['comment'] = lbl.comment self.write() @@ -134,32 +134,32 @@ class EthereumTwCtl(TwCtl): msg(f'Address {coinaddr!r} not found in {self.data_root_desc!r} section of tracking wallet') return False - async def addr2sym(self,req_addr): + async def addr2sym(self, req_addr): for addr in self.data['tokens']: if addr == req_addr: return self.data['tokens'][addr]['params']['symbol'] - async def sym2addr(self,sym): + async def sym2addr(self, sym): for addr in self.data['tokens']: if self.data['tokens'][addr]['params']['symbol'] == sym.upper(): return addr - def get_token_param(self,token,param): + def get_token_param(self, token, param): if token in self.data['tokens']: return self.data['tokens'][token]['params'].get(param) @property def sorted_list(self): - return sorted( - [ { 'addr':x[0], - 'mmid':x[1]['mmid'], - 'comment':x[1]['comment'] } - for x in self.data_root.items() if x[0] not in ('params','coin') ], - key=lambda x: x['mmid'].sort_key+x['addr'] ) + return sorted([{ + 'addr': x[0], + 'mmid': x[1]['mmid'], + 'comment': x[1]['comment'] + } for x in self.data_root.items() if x[0] not in ('params', 'coin')], + key = lambda x: x['mmid'].sort_key + x['addr']) @property def mmid_ordered_dict(self): - return dict((x['mmid'],{'addr':x['addr'],'comment':x['comment']}) for x in self.sorted_list) + return dict((x['mmid'], {'addr': x['addr'], 'comment': x['comment']}) for x in self.sorted_list) async def get_label_addr_pairs(self): return [label_addr_pair( @@ -182,22 +182,22 @@ class EthereumTokenTwCtl(EthereumTwCtl): self.conv_types(v) if self.importing and token_addr: - if not is_coin_addr(proto,token_addr): - die( 'InvalidTokenAddress', f'{token_addr!r}: invalid token address' ) + if not is_coin_addr(proto, token_addr): + die('InvalidTokenAddress', f'{token_addr!r}: invalid token address') else: - assert token_addr is None,'EthereumTokenTwCtl_chk1' + assert token_addr is None, 'EthereumTokenTwCtl_chk1' token_addr = await self.sym2addr(proto.tokensym) # returns None on failure - if not is_coin_addr(proto,token_addr): - die( 'UnrecognizedTokenSymbol', f'Specified token {proto.tokensym!r} could not be resolved!' ) + if not is_coin_addr(proto, token_addr): + die('UnrecognizedTokenSymbol', f'Specified token {proto.tokensym!r} could not be resolved!') from ....addr import TokenAddr - self.token = TokenAddr(proto,token_addr) + self.token = TokenAddr(proto, token_addr) if self.token not in self.data['tokens']: if self.importing: await self.import_token(self.token) else: - die( 'TokenNotInWallet', f'Specified token {self.token!r} not in wallet!' ) + die('TokenNotInWallet', f'Specified token {self.token!r} not in wallet!') self.decimals = self.get_param('decimals') self.symbol = self.get_param('symbol') @@ -212,29 +212,29 @@ class EthereumTokenTwCtl(EthereumTwCtl): def data_root_desc(self): return 'token ' + self.get_param('symbol') - async def rpc_get_balance(self,addr): - return await Token(self.cfg,self.proto,self.token,self.decimals,self.rpc).get_balance(addr) + async def rpc_get_balance(self, addr): + return await Token(self.cfg, self.proto, self.token, self.decimals, self.rpc).get_balance(addr) - async def get_eth_balance(self,addr,force_rpc=False): + async def get_eth_balance(self, addr, force_rpc=False): cache = self.cur_eth_balances r = self.data['accounts'] - ret = None if force_rpc else self.get_cached_balance(addr,cache,r) + ret = None if force_rpc else self.get_cached_balance(addr, cache, r) if ret is None: ret = await super().rpc_get_balance(addr) - self.cache_balance(addr,ret,cache,r) + self.cache_balance(addr, ret, cache, r) return ret - def get_param(self,param): + def get_param(self, param): return self.data['tokens'][self.token]['params'][param] @write_mode - async def import_token(self,tokenaddr): + async def import_token(self, tokenaddr): """ Token 'symbol' and 'decimals' values are resolved from the network by the system just once, upon token import. Thereafter, token address, symbol and decimals are resolved either from the tracking wallet (online operations) or transaction file (when signing). """ - t = await ResolvedToken(self.cfg,self.proto,self.rpc,tokenaddr) + t = await ResolvedToken(self.cfg, self.proto, self.rpc, tokenaddr) self.data['tokens'][tokenaddr] = { 'params': { 'symbol': await t.get_symbol(), diff --git a/mmgen/proto/eth/tw/json.py b/mmgen/proto/eth/tw/json.py index 2dd6e582..abc845cb 100755 --- a/mmgen/proto/eth/tw/json.py +++ b/mmgen/proto/eth/tw/json.py @@ -20,30 +20,30 @@ class EthereumTwJSON(TwJSON): class Base(TwJSON.Base): - def __init__(self,proto,*args,**kwargs): + def __init__(self, proto, *args, **kwargs): - self.params_keys = ['symbol','decimals'] - self.params_tuple = namedtuple('params_tuple',self.params_keys) + self.params_keys = ['symbol', 'decimals'] + self.params_tuple = namedtuple('params_tuple', self.params_keys) - super().__init__(proto,*args,**kwargs) + super().__init__(proto, *args, **kwargs) @property def mappings_json(self): def gen_mappings(data): for d in data: - yield (d.mmgen_id,d.address) if hasattr(d,'mmgen_id') else d + yield (d.mmgen_id, d.address) if hasattr(d, 'mmgen_id') else d return self.json_dump({ 'accounts': list(gen_mappings(self.entries['accounts'])), - 'tokens': {k:list(gen_mappings(v)) for k,v in self.entries['tokens'].items()} + 'tokens': {k:list(gen_mappings(v)) for k, v in self.entries['tokens'].items()} }) @property def num_entries(self): return len(self.entries['accounts']) + len(self.entries['tokens']) - class Import(TwJSON.Import,Base): + class Import(TwJSON.Import, Base): info_msg = """ This utility will recreate a new tracking wallet from the supplied JSON dump. @@ -68,72 +68,72 @@ class EthereumTwJSON(TwJSON): else: e = self.entry_tuple_in(*d) yield self.entry_tuple( - TwMMGenID(self.proto,e.mmgen_id), + TwMMGenID(self.proto, e.mmgen_id), e.address, - getattr(e,'amount','0'), - e.comment ) + getattr(e, 'amount', '0'), + e.comment) def gen_token_entries(): - for token_addr,token_data in edata['tokens'].items(): + for token_addr, token_data in edata['tokens'].items(): yield ( token_addr, list(gen_entries(token_data)), ) return { - 'accounts': list(gen_entries( edata['accounts'] )), + 'accounts': list(gen_entries(edata['accounts'])), 'tokens': dict(list(gen_token_entries())) } - async def do_import(self,batch): + async def do_import(self, batch): from ....obj import TwComment def gen_data(data): for d in data: - if hasattr(d,'address'): - if d.amount is None: - yield (d.address, {'mmid':d.mmgen_id,'comment':TwComment(d.comment)}) - else: - yield (d.address, {'mmid':d.mmgen_id,'comment':TwComment(d.comment),'balance':d.amount}) + if hasattr(d, 'address'): + yield ( + d.address, + {'mmid': d.mmgen_id, 'comment': TwComment(d.comment)} + | ({} if d.amount is None else {'balance': d.amount})) else: - yield ('params', {'symbol':d.symbol,'decimals':d.decimals}) + yield ('params', {'symbol': d.symbol, 'decimals': d.decimals}) self.twctl.data = { # keys must be in correct order 'coin': self.coin.upper(), 'network': self.network.upper(), 'accounts': dict(gen_data(self.entries['accounts'])), - 'tokens': {k:dict(gen_data(v)) for k,v in self.entries['tokens'].items()}, + 'tokens': {k:dict(gen_data(v)) for k, v in self.entries['tokens'].items()}, } self.twctl.write(quiet=False) - class Export(TwJSON.Export,Base): + class Export(TwJSON.Export, Base): - async def get_entries(self,include_amts=True): + async def get_entries(self, include_amts=True): def gen_data(data): - for k,v in data.items(): + for k, v in data.items(): if k == 'params': yield self.params_tuple(**v) elif include_amts: - yield self.entry_tuple(TwMMGenID(self.proto,v['mmid']), k, v.get('balance'), v['comment']) + yield self.entry_tuple(TwMMGenID(self.proto, v['mmid']), k, v.get('balance'), v['comment']) else: - yield self.entry_tuple_in(TwMMGenID(self.proto,v['mmid']), k, v['comment']) + yield self.entry_tuple_in(TwMMGenID(self.proto, v['mmid']), k, v['comment']) def gen_token_data(): - for token_addr,token_data in self.twctl.data['tokens'].items(): + for token_addr, token_data in self.twctl.data['tokens'].items(): yield ( token_addr, sorted( gen_data(token_data), - key = lambda x: x.mmgen_id.sort_key if hasattr(x,'mmgen_id') else '+' + key = lambda x: x.mmgen_id.sort_key if hasattr(x, 'mmgen_id') else '+' ) ) return { 'accounts': sorted( gen_data(self.twctl.data['accounts']), - key = lambda x: x.mmgen_id.sort_key ), + key = lambda x: x.mmgen_id.sort_key), 'tokens': dict(sorted(gen_token_data())) } diff --git a/mmgen/proto/eth/tw/unspent.py b/mmgen/proto/eth/tw/unspent.py index c9c771b2..1a817a4e 100755 --- a/mmgen/proto/eth/tw/unspent.py +++ b/mmgen/proto/eth/tw/unspent.py @@ -25,18 +25,18 @@ from ....tw.unspent import TwUnspentOutputs from .view import EthereumTwView # No unspent outputs with Ethereum, but naming must be consistent -class EthereumTwUnspentOutputs(EthereumTwView,TwUnspentOutputs): +class EthereumTwUnspentOutputs(EthereumTwView, TwUnspentOutputs): class display_type(TwUnspentOutputs.display_type): class squeezed(TwUnspentOutputs.display_type.squeezed): - cols = ('num','addr','mmid','comment','amt','amt2') + cols = ('num', 'addr', 'mmid', 'comment', 'amt', 'amt2') class detail(TwUnspentOutputs.display_type.detail): - cols = ('num','addr','mmid','amt','amt2','comment') + cols = ('num', 'addr', 'mmid', 'amt', 'amt2', 'comment') class MMGenTwUnspentOutput(TwUnspentOutputs.MMGenTwUnspentOutput): - valid_attrs = {'txid','vout','amt','amt2','comment','twmmid','addr','confs','skip'} + valid_attrs = {'txid', 'vout', 'amt', 'amt2', 'comment', 'twmmid', 'addr', 'confs', 'skip'} invalid_attrs = {'proto'} has_age = False @@ -62,19 +62,19 @@ class EthereumTwUnspentOutputs(EthereumTwView,TwUnspentOutputs): 'w':'a_view_detail', 'l':'i_comment_add', 'D':'i_addr_delete', - 'R':'i_balance_refresh' } + 'R':'i_balance_refresh'} no_data_errmsg = 'No accounts in tracking wallet!' - def get_column_widths(self,data,wide,interactive): + def get_column_widths(self, data, wide, interactive): # min screen width: 80 cols # num addr [mmid] [comment] amt [amt2] return self.compute_column_widths( widths = { # fixed cols - 'num': max(2,len(str(len(data)))+1), + 'num': max(2, len(str(len(data)))+1), 'mmid': max(len(d.twmmid.disp) for d in data) if self.show_mmid else 0, 'amt': self.amt_widths['amt'], - 'amt2': self.amt_widths.get('amt2',0), + 'amt2': self.amt_widths.get('amt2', 0), 'spc': (5 if self.show_mmid else 3) + self.has_amt2, # 5(3) spaces in fs 'txid': 0, 'vout': 0, @@ -95,17 +95,17 @@ class EthereumTwUnspentOutputs(EthereumTwView,TwUnspentOutputs): interactive = interactive, ) - def do_sort(self,key=None,reverse=False): + def do_sort(self, key=None, reverse=False): if key == 'txid': return - super().do_sort(key=key,reverse=reverse) + super().do_sort(key=key, reverse=reverse) async def get_rpc_data(self): wl = self.twctl.sorted_list if self.addrs: wl = [d for d in wl if d['addr'] in self.addrs] return [{ - 'account': TwLabel(self.proto,d['mmid']+' '+d['comment']), + 'account': TwLabel(self.proto, d['mmid']+' '+d['comment']), 'address': d['addr'], 'amt': await self.twctl.get_balance(d['addr']), 'confirmations': 0, # TODO @@ -115,11 +115,11 @@ class EthereumTokenTwUnspentOutputs(EthereumTwUnspentOutputs): has_amt2 = True - async def __init__(self,proto,*args,**kwargs): - await super().__init__(proto,*args,**kwargs) + async def __init__(self, proto, *args, **kwargs): + await super().__init__(proto, *args, **kwargs) self.proto.tokensym = self.twctl.symbol - async def get_data(self,*args,**kwargs): - await super().get_data(*args,**kwargs) + async def get_data(self, *args, **kwargs): + await super().get_data(*args, **kwargs) for e in self.data: e.amt2 = await self.twctl.get_eth_balance(e.addr) diff --git a/mmgen/proto/eth/tw/view.py b/mmgen/proto/eth/tw/view.py index 44570734..a5669f8c 100755 --- a/mmgen/proto/eth/tw/view.py +++ b/mmgen/proto/eth/tw/view.py @@ -24,15 +24,15 @@ class EthereumTwView(TwView): 'twmmid': lambda i: i.twmmid.sort_key } - def age_disp(self,o,age_fmt): # TODO + def age_disp(self, o, age_fmt): # TODO pass - def get_disp_prec(self,wide): + def get_disp_prec(self, wide): return self.proto.coin_amt.max_prec if wide else 8 - def gen_subheader(self,cw,color): + def gen_subheader(self, cw, color): if self.disp_prec == 8: yield 'Balances truncated to 8 decimal points' if self.cfg.cached_balances: - from ....color import nocolor,yellow - yield (nocolor,yellow)[color]('WARNING: Using cached balances. These may be out of date!') + from ....color import nocolor, yellow + yield (nocolor, yellow)[color]('WARNING: Using cached balances. These may be out of date!') diff --git a/mmgen/proto/eth/tx/base.py b/mmgen/proto/eth/tx/base.py index 6fde11cd..a2581ef1 100755 --- a/mmgen/proto/eth/tx/base.py +++ b/mmgen/proto/eth/tx/base.py @@ -15,7 +15,7 @@ proto.eth.tx.base: Ethereum base transaction class from collections import namedtuple from ....tx import base as TxBase -from ....obj import HexStr,Int +from ....obj import HexStr, Int class Base(TxBase.Base): @@ -52,16 +52,17 @@ class Base(TxBase.Base): def is_replaceable(self): return True - async def get_receipt(self,txid): - rx = await self.rpc.call('eth_getTransactionReceipt','0x'+txid) # -> null if pending + async def get_receipt(self, txid): + rx = await self.rpc.call('eth_getTransactionReceipt', '0x'+txid) # -> null if pending if not rx: return None - tx = await self.rpc.call('eth_getTransactionByHash','0x'+txid) - return namedtuple('exec_status',['status','gas_sent','gas_used','gas_price','contract_addr','tx','rx'])( - status = Int(rx['status'],16), # zero is failure, non-zero success - gas_sent = Int(tx['gas'],16), - gas_used = Int(rx['gasUsed'],16), - gas_price = self.proto.coin_amt(int(tx['gasPrice'],16),from_unit='wei'), + tx = await self.rpc.call('eth_getTransactionByHash', '0x'+txid) + return namedtuple('exec_status', + ['status', 'gas_sent', 'gas_used', 'gas_price', 'contract_addr', 'tx', 'rx'])( + status = Int(rx['status'], 16), # zero is failure, non-zero success + gas_sent = Int(tx['gas'], 16), + gas_used = Int(rx['gasUsed'], 16), + gas_price = self.proto.coin_amt(int(tx['gasPrice'], 16), from_unit='wei'), contract_addr = self.proto.coin_addr(rx['contractAddress'][2:]) if rx['contractAddress'] else None, tx = tx, rx = rx, diff --git a/mmgen/proto/eth/tx/bump.py b/mmgen/proto/eth/tx/bump.py index 45272779..6e4a42cc 100755 --- a/mmgen/proto/eth/tx/bump.py +++ b/mmgen/proto/eth/tx/bump.py @@ -15,23 +15,23 @@ proto.eth.tx.bump: Ethereum transaction bump class from decimal import Decimal from ....tx import bump as TxBase -from .completed import Completed,TokenCompleted -from .new import New,TokenNew +from .completed import Completed, TokenCompleted +from .new import New, TokenNew -class Bump(Completed,New,TxBase.Bump): +class Bump(Completed, New, TxBase.Bump): desc = 'fee-bumped transaction' @property def min_fee(self): return self.fee * Decimal('1.101') - def bump_fee(self,idx,fee): + def bump_fee(self, idx, fee): self.txobj['gasPrice'] = self.fee_abs2gas(fee) async def get_nonce(self): return self.txobj['nonce'] -class TokenBump(TokenCompleted,TokenNew,Bump): +class TokenBump(TokenCompleted, TokenNew, Bump): desc = 'fee-bumped transaction' class AutomountBump(Bump): diff --git a/mmgen/proto/eth/tx/completed.py b/mmgen/proto/eth/tx/completed.py index f4f9ed9d..05422c16 100755 --- a/mmgen/proto/eth/tx/completed.py +++ b/mmgen/proto/eth/tx/completed.py @@ -13,16 +13,16 @@ proto.eth.tx.completed: Ethereum completed transaction class """ from ....tx import completed as TxBase -from .base import Base,TokenBase +from .base import Base, TokenBase -class Completed(Base,TxBase.Completed): +class Completed(Base, TxBase.Completed): fn_fee_unit = 'Mwei' - def __init__(self,*args,**kwargs): + def __init__(self, *args, **kwargs): self.txobj = {} - super().__init__(*args,**kwargs) + super().__init__(*args, **kwargs) self.gas = self.proto.coin_amt(self.dfl_gas, from_unit='wei') self.start_gas = self.proto.coin_amt(self.dfl_start_gas, from_unit='wei') @@ -54,7 +54,7 @@ class Completed(Base,TxBase.Completed): def get_serialized_locktime(self): return None # TODO -class TokenCompleted(TokenBase,Completed): +class TokenCompleted(TokenBase, Completed): @property def change(self): diff --git a/mmgen/proto/eth/tx/info.py b/mmgen/proto/eth/tx/info.py index decbe5fd..99abb8f8 100755 --- a/mmgen/proto/eth/tx/info.py +++ b/mmgen/proto/eth/tx/info.py @@ -31,9 +31,9 @@ class TxInfo(TxInfo): def format_body(self, blockcount, nonmm_str, max_mmwid, enl, terse, sort): tx = self.tx m = {} - for k in ('inputs','outputs'): - if len(getattr(tx,k)): - m[k] = getattr(tx,k)[0].mmid if len(getattr(tx,k)) else '' + for k in ('inputs', 'outputs'): + if len(getattr(tx, k)): + m[k] = getattr(tx, k)[0].mmid if len(getattr(tx, k)) else '' m[k] = ' ' + m[k].hl() if m[k] else ' ' + MMGenID.hlc(nonmm_str) fs = """ From: {f}{f_mmid} @@ -43,7 +43,7 @@ class TxInfo(TxInfo): Start gas: {G} Kwei Nonce: {n} Data: {d} - """.strip().replace('\t','') + """.strip().replace('\t', '') t = tx.txobj td = t['data'] to_addr = t[self.to_addr_key] @@ -52,19 +52,19 @@ class TxInfo(TxInfo): t = to_addr.hl(0) if to_addr else blue('None'), a = t['amt'].hl(), n = t['nonce'].hl(), - d = '{}... ({} bytes)'.format(td[:40],len(td)//2) if len(td) else blue('None'), + d = '{}... ({} bytes)'.format(td[:40], len(td)//2) if len(td) else blue('None'), c = tx.proto.dcoin if len(tx.outputs) else '', g = yellow(tx.pretty_fmt_fee(t['gasPrice'].to_unit('Gwei'))), G = yellow(tx.pretty_fmt_fee(t['startGas'].to_unit('Kwei'))), t_mmid = m['outputs'] if len(tx.outputs) else '', f_mmid = m['inputs']) + '\n\n' - def format_abs_fee(self,color,iwidth): - return self.tx.fee.fmt(color=color,iwidth=iwidth) + (' (max)' if self.tx.txobj['data'] else '') + def format_abs_fee(self, color, iwidth): + return self.tx.fee.fmt(color=color, iwidth=iwidth) + (' (max)' if self.tx.txobj['data'] else '') def format_rel_fee(self): return ' ({} of spend amount)'.format( - pink('{:0.6f}%'.format( self.tx.fee / self.tx.send_amt * 100 )) + pink('{:0.6f}%'.format(self.tx.fee / self.tx.send_amt * 100)) ) def format_verbose_footer(self): @@ -80,8 +80,8 @@ class TokenTxInfo(TxInfo): def format_rel_fee(self): return '' - def format_body(self,*args,**kwargs): + def format_body(self, *args, **kwargs): return 'Token: {d} {c}\n{r}'.format( d = self.tx.txobj['token_addr'].hl(0), c = blue('(' + self.tx.proto.dcoin + ')'), - r = super().format_body(*args,**kwargs )) + r = super().format_body(*args, **kwargs)) diff --git a/mmgen/proto/eth/tx/new.py b/mmgen/proto/eth/tx/new.py index 8bd076f0..35a43bf9 100755 --- a/mmgen/proto/eth/tx/new.py +++ b/mmgen/proto/eth/tx/new.py @@ -22,16 +22,16 @@ from ....addr import is_mmgen_id, is_coin_addr from ..contract import Token from .base import Base, TokenBase -class New(Base,TxBase.New): +class New(Base, TxBase.New): desc = 'transaction' fee_fail_fs = 'Network fee estimation failed' no_chg_msg = 'Warning: Transaction leaves account with zero balance' usr_fee_prompt = 'Enter transaction fee or gas price: ' msg_insufficient_funds = 'Account balance insufficient to fund this transaction ({} {} needed)' - def __init__(self,*args,**kwargs): + def __init__(self, *args, **kwargs): - super().__init__(*args,**kwargs) + super().__init__(*args, **kwargs) if self.cfg.gas: self.gas = self.start_gas = self.proto.coin_amt(int(self.cfg.gas), from_unit='wei') @@ -47,7 +47,8 @@ class New(Base,TxBase.New): self.disable_fee_check = True async def get_nonce(self): - return ETHNonce(int(await self.rpc.call('eth_getTransactionCount','0x'+self.inputs[0].addr,'pending'),16)) + return ETHNonce(int( + await self.rpc.call('eth_getTransactionCount', '0x'+self.inputs[0].addr, 'pending'), 16)) async def make_txobj(self): # called by create_serialized() self.txobj = { @@ -64,22 +65,22 @@ class New(Base,TxBase.New): # Instead of serializing tx data as with BTC, just create a JSON dump. # This complicates things but means we avoid using the rlp library to deserialize the data, # thus removing an attack vector - async def create_serialized(self,locktime=None,bump=None): - assert len(self.inputs) == 1,'Transaction has more than one input!' + async def create_serialized(self, locktime=None, bump=None): + assert len(self.inputs) == 1, 'Transaction has more than one input!' o_num = len(self.outputs) o_ok = 0 if self.usr_contract_data else 1 assert o_num == o_ok, f'Transaction has {o_num} output{suf(o_num)} (should have {o_ok})' await self.make_txobj() - odict = {k:v if v is None else str(v) for k,v in self.txobj.items() if k != 'token_to'} + odict = {k:v if v is None else str(v) for k, v in self.txobj.items() if k != 'token_to'} self.serialized = json.dumps(odict) self.update_txid() def update_txid(self): assert not is_hex_str(self.serialized), ( - 'update_txid() must be called only when self.serialized is not hex data' ) + 'update_txid() must be called only when self.serialized is not hex data') self.txid = MMGenTxID(make_chksum_6(self.serialized).upper()) - async def process_cmd_args(self,cmd_args,ad_f,ad_w): + async def process_cmd_args(self, cmd_args, ad_f, ad_w): lc = len(cmd_args) @@ -96,10 +97,10 @@ class New(Base,TxBase.New): amt = self.proto.coin_amt(arg.amt or '0'), is_chg = not arg.amt) - def select_unspent(self,unspent): + def select_unspent(self, unspent): from ....ui import line_input while True: - reply = line_input( self.cfg, 'Enter an account to spend from: ' ).strip() + reply = line_input(self.cfg, 'Enter an account to spend from: ').strip() if reply: if not is_int(reply): msg('Account number must be an integer') @@ -116,7 +117,7 @@ class New(Base,TxBase.New): # get rel_fee (gas price) from network, return in native wei async def get_rel_fee_from_network(self): - return Int(await self.rpc.call('eth_gasPrice'),16), 'eth_gasPrice' + return Int(await self.rpc.call('eth_gasPrice'), 16), 'eth_gasPrice' def check_fee(self): if not self.disable_fee_check: @@ -127,14 +128,14 @@ class New(Base,TxBase.New): return self.proto.coin_amt(amt_in_units, from_unit=units[unit]) * self.gas.toWei() # given fee estimate (gas price) in wei, return absolute fee, adjusting by self.cfg.fee_adjust - def fee_est2abs(self,rel_fee,fe_type=None): + def fee_est2abs(self, rel_fee, fe_type=None): ret = self.fee_gasPrice2abs(rel_fee) * self.cfg.fee_adjust if self.cfg.verbose: msg(f'Estimated fee: {ret} ETH') return ret - def convert_and_check_fee(self,fee,desc): - abs_fee = self.feespec2abs(fee,None) + def convert_and_check_fee(self, fee, desc): + abs_fee = self.feespec2abs(fee, None) if abs_fee is False: return False elif not self.disable_fee_check and (abs_fee > self.proto.max_tx_fee): @@ -142,71 +143,71 @@ class New(Base,TxBase.New): abs_fee.hl(), desc, self.proto.max_tx_fee.hl(), - c = self.proto.coin )) + c = self.proto.coin)) return False else: return abs_fee - def update_change_output(self,funds_left): + def update_change_output(self, funds_left): if self.outputs and self.outputs[0].is_chg: self.update_output_amt(0, funds_left) async def get_input_addrs_from_cmdline(self): ret = [] if self.cfg.inputs: - data_root = (await TwCtl(self.cfg,self.proto)).data_root # must create new instance here + data_root = (await TwCtl(self.cfg, self.proto)).data_root # must create new instance here errmsg = 'Address {!r} not in tracking wallet' for addr in self.cfg.inputs.split(','): - if is_mmgen_id(self.proto,addr): + if is_mmgen_id(self.proto, addr): for waddr in data_root: if data_root[waddr]['mmid'] == addr: ret.append(waddr) break else: - die( 'UserAddressNotInWallet', errmsg.format(addr) ) - elif is_coin_addr(self.proto,addr): + die('UserAddressNotInWallet', errmsg.format(addr)) + elif is_coin_addr(self.proto, addr): if not addr in data_root: - die( 'UserAddressNotInWallet', errmsg.format(addr) ) + die('UserAddressNotInWallet', errmsg.format(addr)) ret.append(addr) else: - die(1,f'{addr!r}: not an MMGen ID or coin address') + die(1, f'{addr!r}: not an MMGen ID or coin address') return ret def final_inputs_ok_msg(self, funds_left): chg = self.proto.coin_amt('0') if (self.outputs and self.outputs[0].is_chg) else funds_left return 'Transaction leaves {} {} in the sender’s account'.format(chg.hl(), self.proto.coin) -class TokenNew(TokenBase,New): +class TokenNew(TokenBase, New): desc = 'transaction' fee_is_approximate = True async def make_txobj(self): # called by create_serialized() await super().make_txobj() - t = Token(self.cfg,self.proto,self.twctl.token,self.twctl.decimals) + t = Token(self.cfg, self.proto, self.twctl.token, self.twctl.decimals) o = self.txobj o['token_addr'] = t.addr o['decimals'] = t.decimals o['token_to'] = o['to'] - o['data'] = t.create_data(o['token_to'],o['amt']) + o['data'] = t.create_data(o['token_to'], o['amt']) - def update_change_output(self,funds_left): + def update_change_output(self, funds_left): if self.outputs[0].is_chg: - self.update_output_amt(0,self.inputs[0].amt) + self.update_output_amt(0, self.inputs[0].amt) # token transaction, so check both eth and token balances # TODO: add test with insufficient funds - async def precheck_sufficient_funds(self,inputs_sum,sel_unspent,outputs_sum): + async def precheck_sufficient_funds(self, inputs_sum, sel_unspent, outputs_sum): eth_bal = await self.twctl.get_eth_balance(sel_unspent[0].addr) if eth_bal == 0: # we don't know the fee yet msg('This account has no ether to pay for the transaction fee!') return False - return await super().precheck_sufficient_funds(inputs_sum,sel_unspent,outputs_sum) + return await super().precheck_sufficient_funds(inputs_sum, sel_unspent, outputs_sum) async def get_funds_available(self, fee, outputs_sum): bal = await self.twctl.get_eth_balance(self.inputs[0].addr) return self._funds_available(bal >= fee, bal - fee if bal >= fee else fee - bal) - def final_inputs_ok_msg(self,funds_left): + def final_inputs_ok_msg(self, funds_left): token_bal = ( self.proto.coin_amt('0') if self.outputs[0].is_chg else self.inputs[0].amt - self.outputs[0].amt diff --git a/mmgen/proto/eth/tx/online.py b/mmgen/proto/eth/tx/online.py index a4856e54..9c79fd68 100755 --- a/mmgen/proto/eth/tx/online.py +++ b/mmgen/proto/eth/tx/online.py @@ -12,24 +12,24 @@ proto.eth.tx.online: Ethereum online signed transaction class """ -from ....util import msg,die +from ....util import msg, die from ....color import orange from ....tx import online as TxBase from .. import erigon_sleep -from .signed import Signed,TokenSigned +from .signed import Signed, TokenSigned -class OnlineSigned(Signed,TxBase.OnlineSigned): +class OnlineSigned(Signed, TxBase.OnlineSigned): - async def send(self,prompt_user=True): + async def send(self, prompt_user=True): self.check_correct_chain() if not self.disable_fee_check and (self.fee > self.proto.max_tx_fee): - die(2,'Transaction fee ({}) greater than {} max_tx_fee ({} {})!'.format( + die(2, 'Transaction fee ({}) greater than {} max_tx_fee ({} {})!'.format( self.fee, self.proto.name, self.proto.max_tx_fee, - self.proto.coin )) + self.proto.coin)) await self.status.display() @@ -40,12 +40,12 @@ class OnlineSigned(Signed,TxBase.OnlineSigned): m = 'BOGUS transaction NOT sent: {}' else: try: - ret = await self.rpc.call('eth_sendRawTransaction','0x'+self.serialized) + ret = await self.rpc.call('eth_sendRawTransaction', '0x'+self.serialized) except Exception as e: msg(orange('\n'+str(e))) die(2, f'Send of MMGen transaction {self.txid} failed') m = 'Transaction sent: {}' - assert ret == '0x'+self.coin_txid,'txid mismatch (after sending)' + assert ret == '0x'+self.coin_txid, 'txid mismatch (after sending)' await erigon_sleep(self) msg(m.format(self.coin_txid.hl())) @@ -58,7 +58,7 @@ class OnlineSigned(Signed,TxBase.OnlineSigned): if 'token_addr' in self.txobj: msg('Contract address: {}'.format(self.txobj['token_addr'].hl(0))) -class TokenOnlineSigned(TokenSigned,OnlineSigned): +class TokenOnlineSigned(TokenSigned, OnlineSigned): def parse_txfile_serialized_data(self): from ....addr import TokenAddr @@ -66,9 +66,9 @@ class TokenOnlineSigned(TokenSigned,OnlineSigned): OnlineSigned.parse_txfile_serialized_data(self) o = self.txobj assert self.twctl.token == o['to'] - o['token_addr'] = TokenAddr(self.proto,o['to']) + o['token_addr'] = TokenAddr(self.proto, o['to']) o['decimals'] = self.twctl.decimals - t = Token(self.cfg,self.proto,o['token_addr'],o['decimals']) + t = Token(self.cfg, self.proto, o['token_addr'], o['decimals']) o['amt'] = t.transferdata2amt(o['data']) o['token_to'] = t.transferdata2sendaddr(o['data']) diff --git a/mmgen/proto/eth/tx/signed.py b/mmgen/proto/eth/tx/signed.py index bdf313d0..5770b592 100755 --- a/mmgen/proto/eth/tx/signed.py +++ b/mmgen/proto/eth/tx/signed.py @@ -17,22 +17,22 @@ from ....obj import CoinTxID, ETHNonce, HexStr from ....addr import CoinAddr, TokenAddr from .completed import Completed, TokenCompleted -class Signed(Completed,TxBase.Signed): +class Signed(Completed, TxBase.Signed): desc = 'signed transaction' def parse_txfile_serialized_data(self): from ..pyethereum.transactions import Transaction from .. import rlp - etx = rlp.decode(bytes.fromhex(self.serialized),Transaction) + etx = rlp.decode(bytes.fromhex(self.serialized), Transaction) d = etx.to_dict() # ==> hex values have '0x' prefix, 0 is '0x' - for k in ('sender','to','data'): + for k in ('sender', 'to', 'data'): if k in d: - d[k] = d[k].replace('0x','',1) + d[k] = d[k].replace('0x', '', 1) o = { - 'from': CoinAddr(self.proto,d['sender']), + 'from': CoinAddr(self.proto, d['sender']), # NB: for token, 'to' is token address - 'to': CoinAddr(self.proto,d['to']) if d['to'] else None, + 'to': CoinAddr(self.proto, d['to']) if d['to'] else None, 'amt': self.proto.coin_amt(d['value'], from_unit='wei'), 'gasPrice': self.proto.coin_amt(d['gasprice'], from_unit='wei'), 'startGas': self.proto.coin_amt(d['startgas'], from_unit='wei'), @@ -40,15 +40,15 @@ class Signed(Completed,TxBase.Signed): 'data': HexStr(d['data']) } if o['data'] and not o['to']: # token- or contract-creating transaction # NB: could be a non-token contract address: - o['token_addr'] = TokenAddr(self.proto,etx.creates.hex()) + o['token_addr'] = TokenAddr(self.proto, etx.creates.hex()) self.disable_fee_check = True txid = CoinTxID(etx.hash.hex()) - assert txid == self.coin_txid,"txid in tx.serialized doesn't match value in MMGen transaction file" + assert txid == self.coin_txid, "txid in tx.serialized doesn't match value in MMGen transaction file" self.gas = o['startGas'] # approximate, but better than nothing self.txobj = o - return d # 'token_addr','decimals' required by Token subclass + return d # 'token_addr', 'decimals' required by Token subclass -class TokenSigned(TokenCompleted,Signed): +class TokenSigned(TokenCompleted, Signed): desc = 'signed transaction' def parse_txfile_serialized_data(self): diff --git a/mmgen/proto/eth/tx/status.py b/mmgen/proto/eth/tx/status.py index fce36c75..63d8ec4f 100755 --- a/mmgen/proto/eth/tx/status.py +++ b/mmgen/proto/eth/tx/status.py @@ -13,38 +13,38 @@ proto.eth.tx.status: Ethereum transaction status class """ from ....tx import status as TxBase -from ....util import msg,die,suf,capfirst +from ....util import msg, die, suf, capfirst class Status(TxBase.Status): - async def display(self,usr_req=False): + async def display(self, usr_req=False): tx = self.tx async def is_in_mempool(): if not 'full_node' in tx.rpc.caps: return False - if tx.rpc.daemon.id in ('parity','openethereum'): + if tx.rpc.daemon.id in ('parity', 'openethereum'): pool = [x['hash'] for x in await tx.rpc.call('parity_pendingTransactions')] - elif tx.rpc.daemon.id in ('geth','erigon'): + elif tx.rpc.daemon.id in ('geth', 'erigon'): res = await tx.rpc.call('txpool_content') pool = list(res['pending']) + list(res['queued']) return '0x'+tx.coin_txid in pool async def is_in_wallet(): - d = await tx.rpc.call('eth_getTransactionReceipt','0x'+tx.coin_txid) + d = await tx.rpc.call('eth_getTransactionReceipt', '0x'+tx.coin_txid) if d and 'blockNumber' in d and d['blockNumber'] is not None: from collections import namedtuple - receipt_info = namedtuple('receipt_info',['confs','exec_status']) + receipt_info = namedtuple('receipt_info', ['confs', 'exec_status']) return receipt_info( - confs = 1 + int(await tx.rpc.call('eth_blockNumber'),16) - int(d['blockNumber'],16), - exec_status = int(d['status'],16) + confs = 1 + int(await tx.rpc.call('eth_blockNumber'), 16) - int(d['blockNumber'], 16), + exec_status = int(d['status'], 16) ) if await is_in_mempool(): msg( 'Transaction is in mempool' if usr_req else - 'Warning: transaction is in mempool!' ) + 'Warning: transaction is in mempool!') return if usr_req: @@ -56,8 +56,8 @@ class Status(TxBase.Status): msg(f'{cd} failed to execute!') else: msg(f'{cd} successfully executed with status {ret.exec_status}') - die(0,f'Transaction has {ret.confs} confirmation{suf(ret.confs)}') - die(1,'Transaction is neither in mempool nor blockchain!') + die(0, f'Transaction has {ret.confs} confirmation{suf(ret.confs)}') + die(1, 'Transaction is neither in mempool nor blockchain!') class TokenStatus(Status): pass diff --git a/mmgen/proto/eth/tx/unsigned.py b/mmgen/proto/eth/tx/unsigned.py index 8e68656f..096489de 100755 --- a/mmgen/proto/eth/tx/unsigned.py +++ b/mmgen/proto/eth/tx/unsigned.py @@ -21,26 +21,26 @@ from ....addr import CoinAddr, TokenAddr from ..contract import Token from .completed import Completed, TokenCompleted -class Unsigned(Completed,TxBase.Unsigned): +class Unsigned(Completed, TxBase.Unsigned): desc = 'unsigned transaction' def parse_txfile_serialized_data(self): d = json.loads(self.serialized) o = { - 'from': CoinAddr(self.proto,d['from']), + 'from': CoinAddr(self.proto, d['from']), # NB: for token, 'to' is sendto address - 'to': CoinAddr(self.proto,d['to']) if d['to'] else None, + 'to': CoinAddr(self.proto, d['to']) if d['to'] else None, 'amt': self.proto.coin_amt(d['amt']), 'gasPrice': self.proto.coin_amt(d['gasPrice']), 'startGas': self.proto.coin_amt(d['startGas']), 'nonce': ETHNonce(d['nonce']), 'chainId': None if d['chainId'] == 'None' else Int(d['chainId']), - 'data': HexStr(d['data']) } + 'data': HexStr(d['data'])} self.gas = o['startGas'] # approximate, but better than nothing self.txobj = o - return d # 'token_addr','decimals' required by Token subclass + return d # 'token_addr', 'decimals' required by Token subclass - async def do_sign(self,wif): + async def do_sign(self, wif): o = self.txobj o_conv = { 'to': bytes.fromhex(o['to'] or ''), @@ -61,11 +61,11 @@ class Unsigned(Completed,TxBase.Unsigned): if o['data']: if o['to']: - assert self.txobj['token_addr'] == TokenAddr(self.proto,etx.creates.hex()),'Token address mismatch' + assert self.txobj['token_addr'] == TokenAddr(self.proto, etx.creates.hex()), 'Token address mismatch' else: # token- or contract-creating transaction - self.txobj['token_addr'] = TokenAddr(self.proto,etx.creates.hex()) + self.txobj['token_addr'] = TokenAddr(self.proto, etx.creates.hex()) - async def sign(self,tx_num_str,keys): # return TX object or False; don't exit or raise exception + async def sign(self, tx_num_str, keys): # return TX object or False; don't exit or raise exception from ....exception import TransactionChainMismatch try: @@ -84,28 +84,28 @@ class Unsigned(Completed,TxBase.Unsigned): msg(f'{e}: transaction signing failed!') return False -class TokenUnsigned(TokenCompleted,Unsigned): +class TokenUnsigned(TokenCompleted, Unsigned): desc = 'unsigned transaction' def parse_txfile_serialized_data(self): d = Unsigned.parse_txfile_serialized_data(self) o = self.txobj - o['token_addr'] = TokenAddr(self.proto,d['token_addr']) + o['token_addr'] = TokenAddr(self.proto, d['token_addr']) o['decimals'] = Int(d['decimals']) - t = Token(self.cfg,self.proto,o['token_addr'],o['decimals']) - o['data'] = t.create_data(o['to'],o['amt']) + t = Token(self.cfg, self.proto, o['token_addr'], o['decimals']) + o['data'] = t.create_data(o['to'], o['amt']) o['token_to'] = t.transferdata2sendaddr(o['data']) - async def do_sign(self,wif): + async def do_sign(self, wif): o = self.txobj - t = Token(self.cfg,self.proto,o['token_addr'],o['decimals']) + t = Token(self.cfg, self.proto, o['token_addr'], o['decimals']) tx_in = t.make_tx_in( to_addr = o['to'], amt = o['amt'], start_gas = self.start_gas, gasPrice = o['gasPrice'], nonce = o['nonce']) - (self.serialized,self.coin_txid) = await t.txsign(tx_in,wif,o['from'],chain_id=o['chainId']) + (self.serialized, self.coin_txid) = await t.txsign(tx_in, wif, o['from'], chain_id=o['chainId']) class AutomountUnsigned(TxBase.AutomountUnsigned, Unsigned): pass