chainlib-eth

Ethereum implementation of the chainlib interface
Info | Log | Files | Refs | README | LICENSE

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')