Browse Source

Basic Ethereum RPC support (Parity)

MMGen 6 years ago
parent
commit
7ad2fdf66c
3 changed files with 77 additions and 34 deletions
  1. 7 2
      mmgen/protocol.py
  2. 42 11
      mmgen/rpc.py
  3. 28 21
      mmgen/util.py

+ 7 - 2
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)

+ 42 - 11
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'
 

+ 28 - 21
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 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 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)
 
 	g.rpch = conn
 	return conn