From 7ad2fdf66c4c44fd3727f2dd26c1622368d1ff96 Mon Sep 17 00:00:00 2001 From: MMGen Date: Thu, 24 May 2018 09:08:42 +0000 Subject: [PATCH] Basic Ethereum RPC support (Parity) --- mmgen/protocol.py | 9 ++++++-- mmgen/rpc.py | 53 +++++++++++++++++++++++++++++++++++++---------- mmgen/util.py | 47 +++++++++++++++++++++++------------------ 3 files changed, 76 insertions(+), 33 deletions(-) diff --git a/mmgen/protocol.py b/mmgen/protocol.py index 66ab09c8..77bbe608 100755 --- a/mmgen/protocol.py +++ b/mmgen/protocol.py @@ -294,6 +294,10 @@ class EthereumProtocol(DummyWIF,BitcoinProtocolAddrgen): base_coin = 'ETH' pubkey_type = 'std' # required by DummyWIF + daemon_name = 'parity' + rpc_port = 8545 + mmcaps = ('key','addr','rpc') + @classmethod def verify_addr(cls,addr,hex_width,return_dict=False): from mmgen.util import is_hex_str_lc @@ -310,7 +314,8 @@ class EthereumProtocol(DummyWIF,BitcoinProtocolAddrgen): class EthereumTestnetProtocol(EthereumProtocol): pass class EthereumClassicProtocol(EthereumProtocol): - name = 'ethereum_classic' + name = 'ethereum_classic' + mmcaps = ('key','addr') class EthereumClassicTestnetProtocol(EthereumClassicProtocol): pass class ZcashProtocol(BitcoinProtocolAddrgen): @@ -400,7 +405,7 @@ class CoinProtocol(MMGenObject): 'btc': (BitcoinProtocol,BitcoinTestnetProtocol,None), 'bch': (BitcoinCashProtocol,BitcoinCashTestnetProtocol,None), 'ltc': (LitecoinProtocol,LitecoinTestnetProtocol,None), - 'eth': (EthereumProtocol,EthereumTestnetProtocol,2), + 'eth': (EthereumProtocol,EthereumTestnetProtocol,None), 'etc': (EthereumClassicProtocol,EthereumClassicTestnetProtocol,2), 'zec': (ZcashProtocol,ZcashTestnetProtocol,2), 'xmr': (MoneroProtocol,MoneroTestnetProtocol,None) diff --git a/mmgen/rpc.py b/mmgen/rpc.py index d944cc44..81d02e85 100755 --- a/mmgen/rpc.py +++ b/mmgen/rpc.py @@ -32,9 +32,9 @@ class RPCFailure(Exception): pass class CoinDaemonRPCConnection(object): - def __init__(self,host=None,port=None,user=None,passwd=None,auth_cookie=None): + def __init__(self,host=None,port=None,user=None,passwd=None,auth_cookie=None,auth=True): - dmsg_rpc('=== CoinDaemonRPCConnection.__init__() debug ===') + dmsg_rpc('=== {}.__init__() debug ==='.format(type(self).__name__)) dmsg_rpc(' host [{}] port [{}] user [{}] passwd [{}] auth_cookie [{}]\n'.format( host,port,user,passwd,auth_cookie)) @@ -44,7 +44,9 @@ class CoinDaemonRPCConnection(object): except: die(1,'Unable to connect to {}:{}'.format(host,port)) - if user and passwd: + if not auth: + self.auth_str = '' + elif user and passwd: self.auth_str = '{}:{}'.format(user,passwd) elif auth_cookie: self.auth_str = auth_cookie @@ -85,9 +87,9 @@ class CoinDaemonRPCConnection(object): hc = httplib.HTTPConnection(self.host, self.port, False, cf['timeout']) if cf['batch']: - p = [{'method':cmd,'params':r,'id':n} for n,r in enumerate(args[0],1)] + p = [{'method':cmd,'params':r,'id':n,'jsonrpc':'2.0'} for n,r in enumerate(args[0],1)] else: - p = {'method':cmd,'params':args,'id':1} + p = {'method':cmd,'params':args,'id':1,'jsonrpc':'2.0'} def do_fail(*args): if cf['on_fail'] in ('return','silent'): @@ -110,14 +112,16 @@ class CoinDaemonRPCConnection(object): return g.proto.get_rpc_coin_amt_type()(obj) return json.JSONEncoder.default(self, obj) - dmsg_rpc(' RPC AUTHORIZATION data ==> raw: [{}]\n{}enc: [Basic {}]\n'.format( - self.auth_str,' '*31,base64.b64encode(self.auth_str))) + http_hdr = { 'Content-Type': 'application/json' } + if self.auth_str: + dmsg_rpc(' RPC AUTHORIZATION data ==> raw: [{}]\n{}enc: [Basic {}]\n'.format( + self.auth_str,' '*31,base64.b64encode(self.auth_str))) + http_hdr.update({ + 'Host': self.host, + 'Authorization': 'Basic {}'.format(base64.b64encode(self.auth_str)) }) try: - hc.request('POST', '/', json.dumps(p,cls=MyJSONEncoder), { - 'Host': self.host, - 'Authorization': 'Basic {}'.format(base64.b64encode(self.auth_str)) - }) + hc.request('POST','/',json.dumps(p,cls=MyJSONEncoder),http_hdr) except Exception as e: m = '{}\nUnable to connect to {} at {}:{}' return do_fail(None,2,m.format(e,g.proto.daemon_name,self.host,self.port)) @@ -197,6 +201,33 @@ class CoinDaemonRPCConnection(object): for name in rpcmethods: exec "def {n}(self,*a,**k):return self.request('{n}',*a,**k)\n".format(n=name) +class EthereumRPCConnection(CoinDaemonRPCConnection): + + rpcmethods = ( + 'eth_accounts', + 'eth_blockNumber', + 'eth_gasPrice', + 'eth_getBalance', + 'eth_getBlockByHash', + 'eth_getBlockByNumber', + 'eth_getTransactionByHash', + 'eth_protocolVersion', + 'eth_sendRawTransaction', + 'eth_signTransaction', + 'eth_syncing', + 'parity_accountsInfo', + 'parity_chainStatus', + 'parity_gasCeilTarget', + 'parity_gasFloorTarget', + 'parity_minGasPrice', + 'parity_netPeers', + 'parity_versionInfo', + ) + + for name in rpcmethods: + exec "def {n}(self,*a,**k):return self.request('{n}',*a,**k)\n".format(n=name) + + def rpc_error(ret): return type(ret) is tuple and ret and ret[0] == 'rpcfail' diff --git a/mmgen/util.py b/mmgen/util.py index e1d3b292..4b92221e 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -825,8 +825,7 @@ def rpc_init(reinit=False): assert block0 == g.proto.block0,'Incorrect Genesis block for {}'.format(g.proto.__name__) for fork in g.proto.forks: if fork[0] == None or latest < fork[0]: break - bhash = conn.getblockhash(fork[0]) - assert bhash == fork[1], ( + assert conn.getblockhash(fork[0]) == fork[1], ( 'Bad block hash at fork block {}. Is this the {} chain?'.format(fork[0],fork[2].upper())) except Exception as e: die(2,"{}\n'{c}' requested, but this is not the {c} chain!".format(e,c=g.coin)) @@ -839,26 +838,34 @@ def rpc_init(reinit=False): except Exception as e: die(1,'{}\nChain is {}!'.format(e,g.chain)) - cfg = get_daemon_cfg_options(('rpcuser','rpcpassword')) import mmgen.rpc - conn = mmgen.rpc.CoinDaemonRPCConnection( - g.rpc_host or 'localhost', - g.rpc_port or g.proto.rpc_port, - g.rpc_user or cfg['rpcuser'], # MMGen's rpcuser,rpcpassword override coin daemon's - g.rpc_password or cfg['rpcpassword'], - auth_cookie=get_coin_daemon_auth_cookie()) + if g.coin == 'ETH': + conn = mmgen.rpc.EthereumRPCConnection( + g.rpc_host or 'localhost', + g.rpc_port or g.proto.rpc_port, + auth=False) + if not g.daemon_version: # First call + g.daemon_version = conn.parity_versionInfo()['version'] # fail immediately if daemon is geth + else: + cfg = get_daemon_cfg_options(('rpcuser','rpcpassword')) + conn = mmgen.rpc.CoinDaemonRPCConnection( + g.rpc_host or 'localhost', + g.rpc_port or g.proto.rpc_port, + g.rpc_user or cfg['rpcuser'], # MMGen's rpcuser,rpcpassword override coin daemon's + g.rpc_password or cfg['rpcpassword'], + auth_cookie=get_coin_daemon_auth_cookie()) - if not g.daemon_version: # First call - if g.bob or g.alice: - import regtest as rt - rt.user(('alice','bob')[g.bob],quiet=True) - g.daemon_version = int(conn.getnetworkinfo()['version']) - g.chain = conn.getblockchaininfo()['chain'] - if g.chain != 'regtest': g.chain += 'net' - assert g.chain in g.chains - check_chaintype_mismatch() - if g.chain == 'mainnet': # skip this for testnet, as Genesis block may change - check_chainfork_mismatch(conn) + if not g.daemon_version: # First call + if g.bob or g.alice: + import regtest as rt + rt.user(('alice','bob')[g.bob],quiet=True) + g.daemon_version = int(conn.getnetworkinfo()['version']) + g.chain = conn.getblockchaininfo()['chain'] + if g.chain != 'regtest': g.chain += 'net' + assert g.chain in g.chains + check_chaintype_mismatch() + if g.chain == 'mainnet': # skip this for testnet, as Genesis block may change + check_chainfork_mismatch(conn) g.rpch = conn return conn