def get_event_abi(self, contract_name: str, event_name: str) -> ABIEvent:
        """Returns the ABI for a given event."""
        # Import locally to avoid web3 dependency during installation via `compile_contracts`
        from web3._utils.contracts import find_matching_event_abi

        assert self.contracts, "ContractManager should have contracts compiled"
        contract_abi = self.get_contract_abi(contract_name)
        return find_matching_event_abi(abi=contract_abi, event_name=event_name)
Beispiel #2
0
def get_event_logs_from_tx_receipt(instance: Any, event_name: str,
                                   tx_receipt: Any) -> Iterator[Any]:
    """
    Query a transaction receipt for all events emitted by the given contract
    instance with a given event name. Yields an iterator of event-specific
    objects to be decoded by the caller. This function intentionally avoids
    connecting to a node, or creating host-side filters.
    """
    contract_address = instance.address
    contract_event = instance.events[event_name]()

    event_abi = find_matching_event_abi(instance.abi, event_name=event_name)
    log_topic = event_abi_to_log_topic(cast(Dict[str, Any], event_abi))
    for log in tx_receipt.logs:
        if log.address == contract_address and log_topic == log['topics'][0]:
            yield contract_event.processLog(log)
Beispiel #3
0
def get_event_logs(web3: Any, instance: Any, event_name: str, start_block: int,
                   end_block: int, batch_size: Optional[int]) -> Iterator[Any]:
    """
    Query the attached node for all events emitted by the given contract
    instance, with the given name. Yields an iterator of event-specific objects
    to be decoded by the caller.
    """

    # It is possible to achieve this via the contract interface, with code of
    # the form:
    #
    #   event = instance.events[event_name]
    #   filter = event.createFilter(fromBlock=start_block, toBlock=to_block)
    #   logs = web3.eth.getFilterLogs(filter)
    #
    # However, this creates filters on the host node, which may not be
    # permitted in all configurations. Hence, the code here iterates manually,
    # skpping events with other topics, from the same contract.

    contract_address = instance.address
    contract_event = instance.events[event_name]()

    event_abi = find_matching_event_abi(instance.abi, event_name=event_name)
    log_topic = event_abi_to_log_topic(cast(Dict[str, Any], event_abi))

    batch_size = batch_size or SYNC_BLOCKS_PER_BATCH
    while start_block <= end_block:
        # Filters are *inclusive* wrt "toBlock", hence the -1 here, and +1 to
        # set start_block before iterating.
        to_block = min(start_block + batch_size - 1, end_block)
        filter_params = {
            'fromBlock': start_block,
            'toBlock': to_block,
            'address': contract_address,
        }
        logs = web3.eth.getLogs(filter_params)
        for log in logs:
            if log_topic == log['topics'][0]:
                yield contract_event.processLog(log)
        start_block = to_block + 1
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:
            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 #5
0
    def get_logs(
        self,
        contract_address: ChecksumEthAddress,
        abi: List,
        event_name: str,
        argument_filters: Dict[str, Any],
        from_block: int,
        to_block: Union[int, str] = '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 self.connected:
            until_block = self.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 = self.web3.eth.getLogs(filter_args)
                start_block = end_block + 1
                events.extend(new_events)
        else:
            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'],
                    from_block=start_block,
                    to_block=end_block,
                )
                start_block = end_block + 1
                events.extend(new_events)

        return events
Beispiel #6
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
Beispiel #7
0
 def _get_event_abi(cls):
     return find_matching_event_abi(cls.contract_abi,
                                    event_name=cls.event_name)
Beispiel #8
0
 def _find_matching_event_abi(cls, event_name=None, argument_names=None):
     return find_matching_event_abi(abi=cls.abi,
                                    event_name=event_name,
                                    argument_names=argument_names)
Beispiel #9
0
 def _find_matching_event_abi(cls, event_name=None, argument_names=None):
     return find_matching_event_abi(
         abi=cls.abi,
         event_name=event_name,
         argument_names=argument_names)
Beispiel #10
0
 def _get_event_abi(cls):
     return find_matching_event_abi(
         cls.contract_abi,
         event_name=cls.event_name)