def _get_transaction_receipt( self, web3: Optional[Web3], tx_hash: str, ) -> Dict[str, Any]: if web3 is None: tx_receipt = self.etherscan.get_transaction_receipt(tx_hash) try: # Turn hex numbers to int block_number = int(tx_receipt['blockNumber'], 16) tx_receipt['blockNumber'] = block_number tx_receipt['cumulativeGasUsed'] = int( tx_receipt['cumulativeGasUsed'], 16) tx_receipt['gasUsed'] = int(tx_receipt['gasUsed'], 16) tx_receipt['status'] = int(tx_receipt['status'], 16) tx_index = int(tx_receipt['transactionIndex'], 16) tx_receipt['transactionIndex'] = tx_index for receipt_log in tx_receipt['logs']: receipt_log['blockNumber'] = block_number receipt_log['logIndex'] = deserialize_int_from_hex( symbol=receipt_log['logIndex'], location='etherscan tx receipt', ) receipt_log['transactionIndex'] = tx_index except (DeserializationError, ValueError) as e: raise RemoteError( f'Couldnt deserialize transaction receipt data from etherscan {tx_receipt}', ) from e return tx_receipt tx_receipt = web3.eth.get_transaction_receipt(tx_hash) # type: ignore return process_result(tx_receipt)
def _get_transaction_receipt( self, web3: Optional[Web3], tx_hash: EVMTxHash, ) -> Dict[str, Any]: if web3 is None: tx_receipt = self.etherscan.get_transaction_receipt(tx_hash) try: # Turn hex numbers to int block_number = int(tx_receipt['blockNumber'], 16) tx_receipt['blockNumber'] = block_number tx_receipt['cumulativeGasUsed'] = int(tx_receipt['cumulativeGasUsed'], 16) tx_receipt['gasUsed'] = int(tx_receipt['gasUsed'], 16) tx_receipt['status'] = int(tx_receipt.get('status', '0x1'), 16) tx_index = int(tx_receipt['transactionIndex'], 16) tx_receipt['transactionIndex'] = tx_index for receipt_log in tx_receipt['logs']: receipt_log['blockNumber'] = block_number receipt_log['logIndex'] = deserialize_int_from_hex( symbol=receipt_log['logIndex'], location='etherscan tx receipt', ) receipt_log['transactionIndex'] = tx_index except (DeserializationError, ValueError, KeyError) as e: msg = str(e) if isinstance(e, KeyError): msg = f'missing key {msg}' log.error( f'Couldnt deserialize transaction receipt {tx_receipt} data from ' f'etherscan due to {msg}', ) raise RemoteError( f'Couldnt deserialize transaction receipt data from etherscan ' f'due to {msg}. Check logs for details', ) from e return tx_receipt # Can raise TransactionNotFound if the user's node is pruned and transaction is old tx_receipt = web3.eth.get_transaction_receipt(tx_hash) # type: ignore return process_result(tx_receipt)
def _get_logs( self, web3: Optional[Web3], contract_address: ChecksumEthAddress, abi: List, event_name: str, argument_filters: Dict[str, Any], from_block: int, to_block: Union[int, Literal['latest']] = 'latest', ) -> List[Dict[str, Any]]: """Queries logs of an ethereum contract May raise: - RemoteError if etherscan is used and there is a problem with reaching it or with the returned result """ event_abi = find_matching_event_abi(abi=abi, event_name=event_name) _, filter_args = construct_event_filter_params( event_abi=event_abi, abi_codec=Web3().codec, contract_address=contract_address, argument_filters=argument_filters, fromBlock=from_block, toBlock=to_block, ) if event_abi['anonymous']: # web3.py does not handle the anonymous events correctly and adds the first topic filter_args['topics'] = filter_args['topics'][1:] events: List[Dict[str, Any]] = [] start_block = from_block if web3 is not None: events = _query_web3_get_logs( web3=web3, filter_args=filter_args, from_block=from_block, to_block=to_block, contract_address=contract_address, event_name=event_name, argument_filters=argument_filters, ) else: # etherscan until_block = (self.etherscan.get_latest_block_number() if to_block == 'latest' else to_block) blocks_step = 300000 while start_block <= until_block: while True: # loop to continuously reduce block range if need b end_block = min(start_block + blocks_step, until_block) try: new_events = self.etherscan.get_logs( contract_address=contract_address, topics=filter_args['topics'], # type: ignore from_block=start_block, to_block=end_block, ) except RemoteError as e: if 'Please select a smaller result dataset' in str(e): blocks_step = blocks_step // 2 if blocks_step < 100: raise # stop trying # else try with the smaller step continue # else some other error raise break # we must have a result # Turn all Hex ints to ints for e_idx, event in enumerate(new_events): try: block_number = deserialize_int_from_hex( symbol=event['blockNumber'], location='etherscan log query', ) log_index = deserialize_int_from_hex( symbol=event['logIndex'], location='etherscan log query', ) # Try to see if the event is a duplicate that got returned # in the previous iteration for previous_event in reversed(events): if previous_event['blockNumber'] < block_number: break same_event = (previous_event['logIndex'] == log_index and previous_event['transactionHash'] == event['transactionHash']) if same_event: events.pop() new_events[e_idx][ 'address'] = deserialize_ethereum_address( event['address'], ) new_events[e_idx]['blockNumber'] = block_number new_events[e_idx][ 'timeStamp'] = deserialize_int_from_hex( symbol=event['timeStamp'], location='etherscan log query', ) new_events[e_idx][ 'gasPrice'] = deserialize_int_from_hex( symbol=event['gasPrice'], location='etherscan log query', ) new_events[e_idx][ 'gasUsed'] = deserialize_int_from_hex( symbol=event['gasUsed'], location='etherscan log query', ) new_events[e_idx]['logIndex'] = log_index new_events[e_idx][ 'transactionIndex'] = deserialize_int_from_hex( symbol=event['transactionIndex'], location='etherscan log query', ) except DeserializationError as e: raise RemoteError( 'Couldnt decode an etherscan event due to {str(e)}}', ) from e # etherscan will only return 1000 events in one go. If more than 1000 # are returned such as when no filter args are provided then continue # the query from the last block if len(new_events) == 1000: start_block = new_events[-1]['blockNumber'] else: start_block = end_block + 1 events.extend(new_events) return events
def _get_logs( self, web3: Optional[Web3], contract_address: ChecksumEthAddress, abi: List, event_name: str, argument_filters: Dict[str, Any], from_block: int, to_block: Union[int, Literal['latest']] = 'latest', ) -> List[Dict[str, Any]]: """Queries logs of an ethereum contract May raise: - RemoteError if etherscan is used and there is a problem with reaching it or with the returned result """ event_abi = find_matching_event_abi(abi=abi, event_name=event_name) _, filter_args = construct_event_filter_params( event_abi=event_abi, abi_codec=Web3().codec, contract_address=contract_address, argument_filters=argument_filters, fromBlock=from_block, toBlock=to_block, ) if event_abi['anonymous']: # web3.py does not handle the anonymous events correctly and adds the first topic filter_args['topics'] = filter_args['topics'][1:] events: List[Dict[str, Any]] = [] start_block = from_block if web3 is not None: until_block = web3.eth.blockNumber if to_block == 'latest' else to_block while start_block <= until_block: filter_args['fromBlock'] = start_block end_block = min(start_block + 250000, until_block) filter_args['toBlock'] = end_block log.debug( 'Querying node for contract event', contract_address=contract_address, event_name=event_name, argument_filters=argument_filters, from_block=filter_args['fromBlock'], to_block=filter_args['toBlock'], ) # WTF: for some reason the first time we get in here the loop resets # to the start without querying eth_getLogs and ends up with double logging new_events_web3 = cast(List[Dict[str, Any]], web3.eth.getLogs(filter_args)) # Turn all HexBytes into hex strings for e_idx, event in enumerate(new_events_web3): new_events_web3[e_idx]['blockHash'] = event[ 'blockHash'].hex() new_topics = [] for topic in (event['topics']): new_topics.append(topic.hex()) new_events_web3[e_idx]['topics'] = new_topics new_events_web3[e_idx]['transactionHash'] = event[ 'transactionHash'].hex() start_block = end_block + 1 events.extend(new_events_web3) else: # etherscan until_block = (self.etherscan.get_latest_block_number() if to_block == 'latest' else to_block) while start_block <= until_block: end_block = min(start_block + 300000, until_block) new_events = self.etherscan.get_logs( contract_address=contract_address, topics=filter_args['topics'], # type: ignore from_block=start_block, to_block=end_block, ) # Turn all Hex ints to ints for e_idx, event in enumerate(new_events): try: new_events[e_idx]['address'] = to_checksum_address( event['address']) new_events[e_idx][ 'blockNumber'] = deserialize_int_from_hex( symbol=event['blockNumber'], location='etherscan log query', ) new_events[e_idx][ 'timeStamp'] = deserialize_int_from_hex( symbol=event['timeStamp'], location='etherscan log query', ) new_events[e_idx][ 'gasPrice'] = deserialize_int_from_hex( symbol=event['gasPrice'], location='etherscan log query', ) new_events[e_idx][ 'gasUsed'] = deserialize_int_from_hex( symbol=event['gasUsed'], location='etherscan log query', ) new_events[e_idx][ 'logIndex'] = deserialize_int_from_hex( symbol=event['logIndex'], location='etherscan log query', ) new_events[e_idx][ 'transactionIndex'] = deserialize_int_from_hex( symbol=event['transactionIndex'], location='etherscan log query', ) except DeserializationError as e: raise RemoteError( 'Couldnt decode an etherscan event due to {str(e)}}', ) from e start_block = end_block + 1 events.extend(new_events) return events