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