Beispiel #1
0
    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)
Beispiel #2
0
    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)
Beispiel #3
0
    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
Beispiel #4
0
    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