connection.py (7796B)
1 # standard imports 2 import copy 3 import logging 4 import json 5 import datetime 6 import time 7 import socket 8 from urllib.request import ( 9 Request, 10 urlopen, 11 ) 12 13 # third-party imports 14 from hexathon import ( 15 add_0x, 16 strip_0x, 17 ) 18 19 # local imports 20 from .error import RevertEthException 21 from chainlib.eth.dialect import DefaultErrorParser 22 from .sign import ( 23 sign_transaction, 24 ) 25 from chainlib.connection import ( 26 ConnType, 27 RPCConnection, 28 JSONRPCHTTPConnection, 29 JSONRPCUnixConnection, 30 error_parser, 31 ) 32 from chainlib.jsonrpc import ( 33 JSONRPCRequest, 34 jsonrpc_result, 35 ) 36 from chainlib.eth.tx import ( 37 unpack, 38 ) 39 from potaahto.symbols import snake_and_camel 40 41 logg = logging.getLogger(__name__) 42 43 44 class EthHTTPConnection(JSONRPCHTTPConnection): 45 """HTTP Interface for Ethereum node JSON-RPC 46 47 :todo: support https 48 """ 49 50 def wait(self, tx_hash_hex, delay=0.5, timeout=0.0, error_parser=error_parser, id_generator=None): 51 """Poll for confirmation of a transaction on network. 52 53 Returns the result of the transaction if it was successfully executed on the network, and raises RevertEthException if execution fails. 54 55 This is a blocking call. 56 57 :param tx_hash_hex: Transaction hash to wait for, hex 58 :type tx_hash_hex: str 59 :param delay: Polling interval 60 :type delay: float 61 :param timeout: Max time to wait for confirmation (0 = no timeout) 62 :type timeout: float 63 :param error_parser: json-rpc response error parser 64 :type error_parser: chainlib.jsonrpc.ErrorParser 65 :param id_generator: json-rpc id generator 66 :type id_generator: chainlib.jsonrpc.JSONRPCIdGenerator 67 :raises TimeoutError: Timeout reached 68 :raises chainlib.eth.error.RevertEthException: Transaction confirmed but failed 69 :rtype: dict 70 :returns: Transaction receipt 71 """ 72 t = datetime.datetime.utcnow() 73 i = 0 74 while True: 75 j = JSONRPCRequest(id_generator) 76 o = j.template() 77 o['method'] ='eth_getTransactionReceipt' 78 o['params'].append(add_0x(tx_hash_hex)) 79 o = j.finalize(o) 80 req = Request( 81 self.location, 82 method='POST', 83 ) 84 req.add_header('Content-Type', 'application/json') 85 data = json.dumps(o) 86 logg.debug('({}) poll receipt attempt {} {}'.format(str(self), i, data)) 87 res = urlopen(req, data=data.encode('utf-8')) 88 r = json.load(res) 89 90 e = jsonrpc_result(r, error_parser) 91 if e != None: 92 e = snake_and_camel(e) 93 # In openethereum we encounter receipts that have NONE block hashes and numbers. WTF... 94 logg.debug('({}) poll receipt received {}'.format(str(self), r)) 95 if e['block_hash'] == None: 96 logg.warning('poll receipt attempt {} returned receipt but with a null block hash value!'.format(i)) 97 else: 98 logg.debug('e {}'.format(strip_0x(e['status']))) 99 if strip_0x(e['status']) == '00': 100 raise RevertEthException(tx_hash_hex) 101 return e 102 103 if timeout > 0.0: 104 delta = (datetime.datetime.utcnow() - t) + datetime.timedelta(seconds=delay) 105 if delta.total_seconds() >= timeout: 106 raise TimeoutError(tx_hash) 107 108 time.sleep(delay) 109 i += 1 110 111 112 def __str__(self): 113 return 'ETH HTTP JSONRPC' 114 115 116 def check_rpc(self, id_generator=None): 117 """Execute Ethereum specific json-rpc query to (superficially) check whether node is sane. 118 119 :param id_generator: json-rpc id generator 120 :type id_generator: chainlib.jsonrpc.JSONRPCIdGenerator 121 :raises Exception: Any exception indicates an invalid node 122 """ 123 j = JSONRPCRequest(id_generator) 124 req = j.template() 125 req['method'] = 'net_version' 126 req = j.finalize(req) 127 r = self.do(req) 128 129 130 class EthUnixConnection(JSONRPCUnixConnection): 131 """Unix socket implementation of Ethereum JSON-RPC 132 """ 133 134 def wait(self, tx_hash_hex, delay=0.5, timeout=0.0, error_parser=error_parser): 135 """See EthHTTPConnection. Not yet implemented for unix socket. 136 """ 137 raise NotImplementedError('Not yet implemented for unix socket') 138 139 140 def sign_transaction_to_rlp(chain_spec, doer, tx): 141 """Generate a signature query and execute it against a json-rpc signer backend. 142 143 Uses the `eth_signTransaction` json-rpc method, generated by chainlib.eth.sign.sign_transaction. 144 145 :param chain_spec: Chain spec to use for EIP155 signature. 146 :type chain_spec: chainlib.chain.ChainSpec 147 :param doer: Signer rpc backend 148 :type doer: chainlib.connection.RPCConnection implementing json-rpc 149 :param tx: Transaction object 150 :type tx: dict 151 :rtype: bytes 152 :returns: Ethereum signature 153 """ 154 txs = tx.serialize() 155 logg.debug('serializing {}'.format(txs)) 156 # TODO: because some rpc servers may fail when chainId is included, we are forced to spend cpu here on this 157 chain_id = txs.get('chainId') or 1 158 if chain_spec != None: 159 chain_id = chain_spec.chain_id() 160 txs['chainId'] = add_0x(chain_id.to_bytes(2, 'big').hex()) 161 txs['from'] = add_0x(tx.sender) 162 o = sign_transaction(txs) 163 r = doer(o) 164 logg.debug('sig got {}'.format(r)) 165 return bytes.fromhex(strip_0x(r)) 166 167 168 def sign_message(doer, msg): 169 """Sign arbitrary data using the Ethereum message signer protocol. 170 171 :param doer: Signer rpc backend 172 :type doer: chainlib.connection.RPCConnection with json-rpc 173 :param msg: Message to sign, in hex 174 :type msg: str 175 :rtype: str 176 :returns: Signature, hex 177 """ 178 o = sign_message(msg) 179 return doer(o) 180 181 182 class EthUnixSignerConnection(EthUnixConnection): 183 """Connects rpc signer methods to Unix socket connection interface 184 """ 185 186 def sign_transaction_to_wire(self, tx): 187 """Sign transaction using unix socket rpc. 188 189 :param tx: Transaction object 190 :type tx: dict 191 :rtype: See chainlib.eth.connection.sign_transaction_to_rlp 192 :returns: Serialized signature 193 """ 194 return sign_transaction_to_rlp(self.chain_spec, self.do, tx) 195 196 197 def sign_message(self, msg): 198 """Sign message using unix socket json-rpc. 199 200 :param msg: Message to sign, in hex 201 :type msg: str 202 :rtype: See chainlin.eth.connection.sign_message 203 :returns: See chainlin.eth.connection.sign_message 204 """ 205 return sign_message(self.do, msg) 206 207 208 class EthHTTPSignerConnection(EthHTTPConnection): 209 210 def sign_transaction_to_wire(self, tx): 211 """Sign transaction using http json-rpc. 212 213 :param tx: Transaction object 214 :type tx: dict 215 :rtype: See chainlin.eth.connection.sign_transaction_to_rlp 216 :returns: Serialized signature 217 """ 218 return sign_transaction_to_rlp(self.chain_spec, self.do, tx) 219 220 221 def sign_message(self, tx): 222 """Sign message using http json-rpc. 223 224 :param msg: Message to sign, in hex 225 :type msg: str 226 :rtype: See chainlin.eth.connection.sign_message 227 :returns: See chainlin.eth.connection.sign_message 228 """ 229 return sign_message(self.do, tx) 230 231 232 233 RPCConnection.register_constructor(ConnType.HTTP, EthHTTPConnection, tag='eth_default') 234 RPCConnection.register_constructor(ConnType.HTTP_SSL, EthHTTPConnection, tag='eth_default') 235 RPCConnection.register_constructor(ConnType.UNIX, EthUnixConnection, tag='eth_default')