chainlib-eth

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

gas.py (9041B)


      1 # standard imports
      2 import logging
      3 
      4 # external imports
      5 from hexathon import (
      6         add_0x,
      7         strip_0x,
      8         )
      9 from funga.eth.transaction import EIP155Transaction
     10 
     11 # local imports
     12 from chainlib.fee import FeeOracle
     13 from chainlib.hash import keccak256_hex_to_hex
     14 from chainlib.jsonrpc import JSONRPCRequest
     15 from chainlib.eth.tx import (
     16         TxFactory,
     17         TxFormat,
     18         raw,
     19         )
     20 from chainlib.eth.jsonrpc import to_blockheight_param
     21 from chainlib.block import BlockSpec
     22 from chainlib.eth.constant import (
     23         MINIMUM_FEE_UNITS,
     24     )
     25 
     26 logg = logging.getLogger(__name__)
     27 
     28 
     29 def price(id_generator=None):
     30     """Generate json-rpc query to retrieve current network gas price guess from node.
     31 
     32     :param id_generator: json-rpc id generator 
     33     :type id_generator: chainlib.connection.JSONRPCIdGenerator
     34     :rtype: dict
     35     :returns: rpc query object
     36     """
     37     j = JSONRPCRequest(id_generator)
     38     o = j.template()
     39     o['method'] = 'eth_gasPrice'
     40     return j.finalize(o)
     41 
     42 
     43 def balance(address, id_generator=None, height=BlockSpec.LATEST):
     44     """Generate json-rpc query to retrieve gas balance of address.
     45 
     46     :param address: Address to query balance for, in hex
     47     :type address: str
     48     :param id_generator: json-rpc id generator 
     49     :type id_generator: chainlib.connection.JSONRPCIdGenerator
     50     :param height: Block height specifier
     51     :type height: chainlib.block.BlockSpec
     52     :rtype: dict
     53     :returns: rpc query object
     54     """
     55     j = JSONRPCRequest(id_generator)
     56     o = j.template()
     57     o['method'] = 'eth_getBalance'
     58     o['params'].append(add_0x(address))
     59     height = to_blockheight_param(height)
     60     o['params'].append(height)
     61     return j.finalize(o)
     62 
     63 
     64 def parse_balance(balance):
     65     """Parse result of chainlib.eth.gas.balance rpc query
     66 
     67     :param balance: rpc result value, in hex or int
     68     :type balance: any
     69     :rtype: int
     70     :returns: Balance integer value
     71     """
     72     try:
     73         r = int(balance, 10)
     74     except ValueError:
     75         r = int(balance, 16)
     76     return r
     77 
     78 
     79 class Gas(TxFactory):
     80     """Gas transaction helper.
     81     """
     82 
     83     def create(self, sender_address, recipient_address, value, data=None, tx_format=TxFormat.JSONRPC, id_generator=None):
     84         """Generate json-rpc query to execute gas transaction.
     85 
     86         See parent class TxFactory for details on output format and general usage.
     87 
     88         :param sender_address: Sender address, in hex
     89         :type sender_address: str
     90         :param recipient_address: Recipient address, in hex
     91         :type recipient_address: str
     92         :param value: Value of transaction, integer decimal value (wei)
     93         :type value: int
     94         :param data: Arbitrary input data, in hex. None means no data (vanilla gas transaction).
     95         :type data: str
     96         :param tx_format: Output format
     97         :type tx_format: chainlib.eth.tx.TxFormat
     98         """
     99         tx = self.template(sender_address, recipient_address, use_nonce=True)
    100         tx['value'] = value
    101         if data != None:
    102             tx['data'] = data
    103         txe = EIP155Transaction(tx, tx['nonce'], tx['chainId'])
    104         tx_raw = self.signer.sign_transaction_to_wire(txe)
    105         tx_raw_hex = add_0x(tx_raw.hex())
    106         tx_hash_hex = add_0x(keccak256_hex_to_hex(tx_raw_hex))
    107 
    108         o = None
    109         if tx_format == TxFormat.JSONRPC:
    110             o = raw(tx_raw_hex, id_generator=id_generator)
    111         elif tx_format == TxFormat.RLP_SIGNED:
    112             o = tx_raw_hex
    113 
    114         return (tx_hash_hex, o)
    115 
    116 
    117 
    118 class RPCGasOracle(FeeOracle):
    119     """JSON-RPC only gas parameter helper.
    120 
    121     :param conn: RPC connection
    122     :type conn: chainlib.connection.RPCConnection
    123     :param code_callback: Callback method to evaluate gas usage for method and inputs.
    124     :type code_callback: method taking abi encoded input data as single argument
    125     :param min_price: Override gas price if less than given value
    126     :type min_price: int
    127     :param id_generator: json-rpc id generator 
    128     :type id_generator: chainlib.connection.JSONRPCIdGenerator
    129     """
    130 
    131     def __init__(self, conn, code_callback=None, min_price=1, id_generator=None):
    132         super(RPCGasOracle, self).__init__(code_callback=code_callback)
    133         self.conn = conn
    134         self.min_price = min_price
    135         self.id_generator = id_generator
    136 
    137 
    138     def get_fee(self, code=None, input_data=None):
    139         """Retrieve gas parameters from node.
    140 
    141         If code is given, the set code callback will be used to estimate gas usage.
    142 
    143         If code is not given or code callback is not set, the chainlib.eth.constant.MINIMUM_FEE_UNITS constant will be used. This gas limit will only be enough gas for a gas transaction without input data.
    144 
    145         :param code: EVM execution code to evaluate against, in hex
    146         :type code: str
    147         :param input_data: Contract input data, in hex
    148         :type input_data: str
    149         :rtype: tuple
    150         :returns: Gas price in wei, and gas limit in gas units
    151         """
    152         gas_price = 0
    153         if self.conn != None:
    154             o = price(id_generator=self.id_generator)
    155             r = self.conn.do(o)
    156             n = strip_0x(r)
    157             gas_price = int(n, 16)
    158         fee_units = MINIMUM_FEE_UNITS
    159         if self.code_callback != None:
    160             fee_units = self.code_callback(code)
    161         if gas_price < self.min_price:
    162             logg.debug('adjusting price {} to set minimum {}'.format(gas_price, self.min_price))
    163             gas_price = self.min_price
    164         return (gas_price, fee_units)
    165 
    166     
    167     def get_gas(self, code=None, input_data=None):
    168         return self.get_fee(code=code, input_data=input_data)
    169 
    170 
    171 class RPCPureGasOracle(RPCGasOracle):
    172     """Convenience constructor for rpc gas oracle without minimum price.
    173 
    174     :param conn: RPC connection
    175     :type conn: chainlib.connection.RPCConnection
    176     :param code_callback: Callback method to evaluate gas usage for method and inputs.
    177     :type code_callback: method taking abi encoded input data as single argument
    178     :param id_generator: json-rpc id generator 
    179     :type id_generator: chainlib.connection.JSONRPCIdGenerator
    180     """
    181     def __init__(self, conn, code_callback=None, id_generator=None):
    182         super(RPCPureGasOracle, self).__init__(conn, code_callback=code_callback, min_price=0, id_generator=id_generator)
    183 
    184 
    185 class OverrideGasOracle(RPCGasOracle):
    186     """Gas parameter helper that can be selectively overridden.
    187 
    188     If both price and limit are set, the conn parameter will not be used.
    189 
    190     If either price or limit is set to None, the rpc in the conn value will be used to query the missing value.
    191 
    192     If both are None, behaves the same as chainlib.eth.gas.RPCGasOracle. 
    193 
    194     :param price: Set exact gas price
    195     :type price: int
    196     :param limit: Set exact gas limit
    197     :type limit: int
    198     :param conn: RPC connection for fallback query
    199     :type conn: chainlib.connection.RPCConnection
    200     :param code_callback: Callback method to evaluate gas usage for method and inputs.
    201     :type code_callback: method taking abi encoded input data as single argument
    202     :param id_generator: json-rpc id generator 
    203     :type id_generator: chainlib.connection.JSONRPCIdGenerator
    204     """
    205     def __init__(self, price=None, limit=None, conn=None, code_callback=None, id_generator=None):
    206         self.conn = None
    207         self.code_callback = None
    208         self.limit = limit
    209         self.price = price
    210 
    211         price_conn = None
    212 
    213         if self.limit == None or self.price == None:
    214             if self.price == None:
    215                 price_conn = conn
    216             logg.debug('override gas oracle with rpc fallback; price {} limit {}'.format(self.price, self.limit))
    217 
    218         super(OverrideGasOracle, self).__init__(price_conn, code_callback, id_generator=id_generator)
    219         
    220 
    221     def get_fee(self, code=None, input_data=None):
    222         r = None
    223         fee_units = None
    224         fee_price = None
    225 
    226         rpc_results = super(OverrideGasOracle, self).get_fee(code)
    227  
    228         if self.limit != None:
    229             fee_units = self.limit
    230         if self.price != None:
    231             fee_price = self.price
    232 
    233         if fee_price == None:
    234             if rpc_results != None:
    235                 fee_price = rpc_results[0]
    236                 logg.debug('override gas oracle without explicit price, setting from rpc {}'.format(fee_price))
    237             else:
    238                 fee_price = MINIMUM_FEE_PRICE
    239                 logg.debug('override gas oracle without explicit price, setting default {}'.format(fee_price))
    240         if fee_units == None:
    241             if rpc_results != None:
    242                 fee_units = rpc_results[1]
    243                 logg.debug('override gas oracle without explicit limit, setting from rpc {}'.format(fee_units))
    244             else:
    245                 fee_units = MINIMUM_FEE_UNITS
    246                 logg.debug('override gas oracle without explicit limit, setting default {}'.format(fee_units))
    247 
    248         return (fee_price, fee_units)
    249 
    250 
    251     def get_gas(self, code=None, input_data=None):
    252         return self.get_fee(code=code, input_data=input_data)
    253 
    254 
    255 DefaultGasOracle = RPCGasOracle