def etherscan_query_with_retries(url, sleep, retries=3): for _ in range(retries - 1): try: etherscan_block = quantity_decoder(requests.get(url).json()['result']) except (RequestException, ValueError, KeyError): gevent.sleep(sleep) else: return etherscan_block etherscan_block = quantity_decoder(requests.get(url).json()['result']) return etherscan_block
def nonce(self, address): if len(address) == 40: address = unhexlify(address) with self.nonce_lock: initialized = self.nonce_current_value is not None query_time = now() if self.nonce_last_update > query_time: # Python's 2.7 time is not monotonic and it's affected by clock # resets, force an update. self.nonce_update_interval = query_time - self.nonce_update_interval needs_update = True else: last_update_interval = query_time - self.nonce_last_update needs_update = last_update_interval > self.nonce_update_interval if initialized and not needs_update: self.nonce_current_value += 1 return self.nonce_current_value pending_transactions_hex = self.call( 'eth_getTransactionCount', address_encoder(address), 'pending', ) pending_transactions = quantity_decoder(pending_transactions_hex) nonce = pending_transactions + self.nonce_offset # we may have hammered the server and not all tx are # registered as `pending` yet if initialized: while nonce < self.nonce_current_value: log.debug( 'nonce on server too low; retrying', server=nonce, local=self.nonce_current_value, ) query_time = now() pending_transactions_hex = self.call( 'eth_getTransactionCount', address_encoder(address), 'pending', ) pending_transactions = quantity_decoder( pending_transactions_hex) nonce = pending_transactions + self.nonce_offset self.nonce_current_value = nonce self.nonce_last_update = query_time return self.nonce_current_value
def nonce(self, address): if len(address) == 40: address = unhexlify(address) with self.nonce_lock: initialized = self.nonce_current_value is not None query_time = now() if self.nonce_last_update > query_time: # Python's 2.7 time is not monotonic and it's affected by clock # resets, force an update. self.nonce_update_interval = query_time - self.nonce_update_interval needs_update = True else: last_update_interval = query_time - self.nonce_last_update needs_update = last_update_interval > self.nonce_update_interval if initialized and not needs_update: self.nonce_current_value += 1 return self.nonce_current_value pending_transactions_hex = self.call( 'eth_getTransactionCount', address_encoder(address), 'pending', ) pending_transactions = quantity_decoder(pending_transactions_hex) nonce = pending_transactions + self.nonce_offset # we may have hammered the server and not all tx are # registered as `pending` yet if initialized: while nonce < self.nonce_current_value: log.debug( 'nonce on server too low; retrying', server=nonce, local=self.nonce_current_value, ) query_time = now() pending_transactions_hex = self.call( 'eth_getTransactionCount', address_encoder(address), 'pending', ) pending_transactions = quantity_decoder(pending_transactions_hex) nonce = pending_transactions + self.nonce_offset self.nonce_current_value = nonce self.nonce_last_update = query_time return self.nonce_current_value
def eth_estimateGas(self, sender: address = b'', to: address = b'', value: int = 0, data: bytes = b'', startgas: int = None) -> Optional[int]: """ Makes a call or transaction, which won't be added to the blockchain and returns the used gas, which can be used for estimating the used gas. Args: sender: The address the transaction is sent from. to: The address the transaction is directed to. gas: Gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions. value: Integer of the value sent with this transaction. data: Hash of the method signature and encoded parameters. For details see Ethereum Contract ABI. block_number: Determines the state of ethereum used in the call. """ startgas = self.check_startgas(startgas) json_data = format_data_for_call(sender, to, value, data, startgas) try: res = self.call('eth_estimateGas', json_data) except EthNodeCommunicationError as e: tx_would_fail = e.error_code and e.error_code in (-32015, -32000) if tx_would_fail: # -32015 is parity and -32000 is geth return None else: raise e return quantity_decoder(res)
def new_filter(self, fromBlock=None, toBlock=None, address=None, topics=None): """ Creates a filter object, based on filter options, to notify when the state changes (logs). To check if the state has changed, call eth_getFilterChanges. """ json_data = { 'fromBlock': block_tag_encoder(fromBlock or ''), 'toBlock': block_tag_encoder(toBlock or ''), } if address is not None: json_data['address'] = address_encoder(address) if topics is not None: if not isinstance(topics, list): raise ValueError('topics must be a list') json_data['topics'] = [topic_encoder(topic) for topic in topics] filter_id = self.call('eth_newFilter', json_data) return quantity_decoder(filter_id)
def is_synced(self) -> bool: result = self.client.call('eth_syncing') # the node is synchronized if result is False: return True current_block = self.block_number() highest_block = quantity_decoder(result['highestBlock']) if highest_block - current_block > 2: return False return True
def tester_state(deploy_key, private_keys, tester_blockgas_limit): tester_state = tester.state() # special addresses 1 to 5 alloc = { int_to_addr(i): {'wei': 1} for i in range(1, 5) } for privkey in [deploy_key] + private_keys: address = privatekey_to_address(privkey) alloc[address] = { 'balance': DEFAULT_BALANCE, } for account in tester.accounts: alloc[account] = { 'balance': DEFAULT_BALANCE, } db = ethereum.db.EphemDB() env = ethereum.config.Env( db, ethereum.config.default_config, ) genesis_overwrite = { 'nonce': zpad(data_decoder('0x00006d6f7264656e'), 8), 'difficulty': quantity_decoder('0x20000'), 'mixhash': zpad(b'\x00', 32), 'coinbase': address_decoder('0x0000000000000000000000000000000000000000'), 'timestamp': 0, 'extra_data': b'', 'gas_limit': tester_blockgas_limit, 'start_alloc': alloc, } genesis_block = ethereum.blocks.genesis( env, **genesis_overwrite ) # enable DELEGATECALL opcode genesis_block.number = genesis_block.config['HOMESTEAD_FORK_BLKNUM'] + 1 tester_state.db = db tester_state.env = env tester_state.block = genesis_block tester_state.blocks = [genesis_block] return tester_state
def eth_estimateGas( self, sender: address = b'', to: address = b'', value: int = 0, data: bytes = b'', startgas: int = GAS_PRICE, gasprice: int = GAS_PRICE) -> int: """ Makes a call or transaction, which won't be added to the blockchain and returns the used gas, which can be used for estimating the used gas. Args: sender: The address the transaction is sent from. to: The address the transaction is directed to. gas: Gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions. gasPrice: gasPrice used for unit of gas paid. value: Integer of the value sent with this transaction. data: Hash of the method signature and encoded parameters. For details see Ethereum Contract ABI. block_number: Determines the state of ethereum used in the call. """ json_data = format_data_for_call( sender, to, value, data, startgas, gasprice, ) try: res = self.call('eth_estimateGas', json_data) except EthNodeCommunicationError as e: tx_would_fail = e.error_code and e.error_code in (-32015, -32000) if tx_would_fail: # -32015 is parity and -32000 is geth return None else: raise e return quantity_decoder(res)
def nonce_update_from_node(self): nonce = -2 nonce_available_value = self.nonce_available_value or -1 # Wait until all tx are registered as pending while nonce < nonce_available_value: pending_transactions_hex = self.rpccall_with_retry( 'eth_getTransactionCount', address_encoder(self.sender), 'pending', ) pending_transactions = quantity_decoder(pending_transactions_hex) nonce = pending_transactions + self.nonce_offset log.debug( 'updated nonce from server', server=nonce, local=nonce_available_value, ) self.nonce_last_update = time.time() self.nonce_available_value = nonce
def eth_estimateGas( self, sender='', to='', value=0, data='', startgas=GAS_PRICE, gasprice=GAS_PRICE): """ Makes a call or transaction, which won't be added to the blockchain and returns the used gas, which can be used for estimating the used gas. Args: sender: The address the transaction is sent from. to: The address the transaction is directed to. gas (int): Gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions. gasPrice (int): gasPrice used for unit of gas paid. value (int): Integer of the value sent with this transaction. data (bin): Hash of the method signature and encoded parameters. For details see Ethereum Contract ABI. block_number: Determines the state of ethereum used in the call. """ json_data = format_data_for_call( sender, to, value, data, startgas, gasprice, ) res = self.call('eth_estimateGas', json_data) return quantity_decoder(res)
def gaslimit(self): last_block = self.call('eth_getBlockByNumber', 'latest', True) gas_limit = quantity_decoder(last_block['gasLimit']) return gas_limit
def balance(self, account): """ Return the balance of the account of given address. """ res = self.call('eth_getBalance', address_encoder(account), 'pending') return quantity_decoder(res)
def blocknumber(self): """ Return the most recent block. """ return quantity_decoder(self.call('eth_blockNumber'))
def block_number(self): """ Return the most recent block. """ return quantity_decoder(self.call('eth_blockNumber'))
def _gasprice(self) -> int: if self.given_gas_price: return self.given_gas_price gas_price = self.call('eth_gasPrice') return quantity_decoder(gas_price)
def _gaslimit(self, location='pending') -> int: last_block = self.call('eth_getBlockByNumber', location, True) gas_limit = quantity_decoder(last_block['gasLimit']) # The gas limit can fluctuate from the actual pending limit by a maximum # of up to a 1/1024th of the previous gas limit return gas_limit - int(gas_limit / 1024)
def poll( self, transaction_hash: bytes, confirmations: int = None, timeout: float = None, ): """ Wait until the `transaction_hash` is applied or rejected. If timeout is None, this could wait indefinitely! Args: transaction_hash: Transaction hash that we are waiting for. confirmations: Number of block confirmations that we will wait for. timeout: Timeout in seconds, raise an Excpetion on timeout. """ if transaction_hash.startswith(b'0x'): warnings.warn( 'transaction_hash seems to be already encoded, this will' ' result in unexpected behavior') if len(transaction_hash) != 32: raise ValueError( 'transaction_hash length must be 32 (it might be hex encoded)') transaction_hash = data_encoder(transaction_hash) deadline = None if timeout: deadline = gevent.Timeout(timeout) deadline.start() try: # used to check if the transaction was removed, this could happen # if gas price is too low: # # > Transaction (acbca3d6) below gas price (tx=1 Wei ask=18 # > Shannon). All sequential txs from this address(7d0eae79) # > will be ignored # last_result = None while True: # Could return None for a short period of time, until the # transaction is added to the pool transaction = self.web3.eth.getTransaction(transaction_hash) # if the transaction was added to the pool and then removed if transaction is None and last_result is not None: raise Exception('invalid transaction, check gas price') # the transaction was added to the pool and mined if transaction and transaction['blockNumber'] is not None: break last_result = transaction gevent.sleep(.5) if confirmations: # this will wait for both APPLIED and REVERTED transactions transaction_block = quantity_decoder( transaction['blockNumber']) confirmation_block = transaction_block + confirmations block_number = self.block_number() while block_number < confirmation_block: gevent.sleep(.5) block_number = self.block_number() except gevent.Timeout: raise Exception('timeout when polling for transaction') finally: if deadline: deadline.cancel()
def poll( self, transaction_hash: bytes, confirmations: Optional[int] = None, timeout: Optional[float] = None): """ Wait until the `transaction_hash` is applied or rejected. If timeout is None, this could wait indefinitely! Args: transaction_hash: Transaction hash that we are waiting for. confirmations: Number of block confirmations that we will wait for. timeout: Timeout in seconds, raise an Excpetion on timeout. """ if transaction_hash.startswith(b'0x'): warnings.warn( 'transaction_hash seems to be already encoded, this will' ' result in unexpected behavior' ) if len(transaction_hash) != 32: raise ValueError( 'transaction_hash length must be 32 (it might be hex encoded)' ) transaction_hash = data_encoder(transaction_hash) deadline = None if timeout: deadline = gevent.Timeout(timeout) deadline.start() try: # used to check if the transaction was removed, this could happen # if gas price is too low: # # > Transaction (acbca3d6) below gas price (tx=1 Wei ask=18 # > Shannon). All sequential txs from this address(7d0eae79) # > will be ignored # last_result = None while True: # Could return None for a short period of time, until the # transaction is added to the pool transaction = self.call('eth_getTransactionByHash', transaction_hash) # if the transaction was added to the pool and then removed if transaction is None and last_result is not None: raise Exception('invalid transaction, check gas price') # the transaction was added to the pool and mined if transaction and transaction['blockNumber'] is not None: break last_result = transaction gevent.sleep(.5) if confirmations: # this will wait for both APPLIED and REVERTED transactions transaction_block = quantity_decoder(transaction['blockNumber']) confirmation_block = transaction_block + confirmations block_number = self.block_number() while block_number < confirmation_block: gevent.sleep(.5) block_number = self.block_number() except gevent.Timeout: raise Exception('timeout when polling for transaction') finally: if deadline: deadline.cancel()
def gaslimit(self) -> int: last_block = self.call('eth_getBlockByNumber', 'latest', True) gas_limit = quantity_decoder(last_block['gasLimit']) return gas_limit
def balance(self, account: address): """ Return the balance of the account of given address. """ res = self.call('eth_getBalance', address_encoder(account), 'pending') return quantity_decoder(res)
def _gaslimit(self, location='pending') -> int: last_block = self.call('eth_getBlockByNumber', location, True) gas_limit = quantity_decoder(last_block['gasLimit']) return gas_limit * 8 // 10
def block_number(self): """ Return the most recent block. """ return quantity_decoder(self.rpccall_with_retry('eth_blockNumber'))