Exemple #1
0
class Web3(_Web3):
    """Brownie Web3 subclass"""
    def __init__(self) -> None:
        super().__init__(HTTPProvider("null"))
        self.enable_unstable_package_management_api()
        self.provider = None
        self._mainnet_w3: Optional[_Web3] = None
        self._genesis_hash: Optional[str] = None
        self._chain_uri: Optional[str] = None
        self._custom_middleware: Set = set()
        self._supports_traces = None
        self._chain_id: Optional[int] = None

    def _remove_middlewares(self) -> None:
        for middleware in self._custom_middleware:
            try:
                self.middleware_onion.remove(middleware)
            except ValueError:
                pass
            middleware.uninstall()
        self._custom_middleware.clear()

    def connect(self, uri: str, timeout: int = 30) -> None:
        """Connects to a provider"""
        self._remove_middlewares()
        self.provider = None

        uri = _expand_environment_vars(uri)
        try:
            if Path(uri).exists():
                self.provider = IPCProvider(uri, timeout=timeout)
        except OSError:
            pass

        if self.provider is None:
            if uri.startswith("ws"):
                self.provider = WebsocketProvider(uri,
                                                  {"close_timeout": timeout})
            elif uri.startswith("http"):

                self.provider = HTTPProvider(uri, {"timeout": timeout})
            else:
                raise ValueError(
                    "Unknown URI - must be a path to an IPC socket, a websocket "
                    "beginning with 'ws' or a URL beginning with 'http'")

        try:
            if self.isConnected():
                self.reset_middlewares()
        except Exception:
            # checking an invalid connection sometimes raises on windows systems
            pass

    def reset_middlewares(self) -> None:
        """
        Uninstall and reinject all custom middlewares.
        """
        if self.provider is None:
            raise ConnectionError("web3 is not currently connected")
        self._remove_middlewares()

        middleware_layers = get_middlewares(self, CONFIG.network_type)

        # middlewares with a layer below zero are injected
        to_inject = sorted((i for i in middleware_layers if i < 0),
                           reverse=True)
        for layer, obj in [(k, x) for k in to_inject
                           for x in middleware_layers[k]]:
            middleware = obj(self)
            self.middleware_onion.inject(middleware, layer=0)
            self._custom_middleware.add(middleware)

        # middlewares with a layer of zero or greater are added
        to_add = sorted(i for i in middleware_layers if i >= 0)
        for layer, obj in [(k, x) for k in to_add
                           for x in middleware_layers[k]]:
            middleware = obj(self)
            self.middleware_onion.add(middleware)
            self._custom_middleware.add(middleware)

    def disconnect(self) -> None:
        """Disconnects from a provider"""
        if self.provider:
            self.provider = None
            self._genesis_hash = None
            self._chain_uri = None
            self._supports_traces = None
            self._chain_id = None
            self._remove_middlewares()

    def isConnected(self) -> bool:
        if not self.provider:
            return False
        return super().isConnected()

    @property
    def supports_traces(self) -> bool:
        if not self.provider:
            return False

        # Send a malformed request to `debug_traceTransaction`. If the error code
        # returned is -32601 "endpoint does not exist/is not available" we know
        # traces are not possible. Any other error code means the endpoint is open.
        if self._supports_traces is None:
            response = self.provider.make_request("debug_traceTransaction", [])
            self._supports_traces = bool(response["error"]["code"] != -32601)

        return self._supports_traces

    @property
    def _mainnet(self) -> _Web3:
        # a web3 instance connected to the mainnet
        if self.isConnected() and CONFIG.active_network["id"] == "mainnet":
            return self
        try:
            mainnet = CONFIG.networks["mainnet"]
        except KeyError:
            raise MainnetUndefined("No 'mainnet' network defined") from None
        if not self._mainnet_w3:
            uri = _expand_environment_vars(mainnet["host"])
            self._mainnet_w3 = _Web3(HTTPProvider(uri))
            self._mainnet_w3.enable_unstable_package_management_api()
        return self._mainnet_w3

    @property
    def genesis_hash(self) -> str:
        """The genesis hash of the currently active network."""
        if self.provider is None:
            raise ConnectionError("web3 is not currently connected")
        if self._genesis_hash is None:
            self._genesis_hash = self.eth.get_block(0)["hash"].hex()[2:]
        return self._genesis_hash

    @property
    def chain_uri(self) -> str:
        if self.provider is None:
            raise ConnectionError("web3 is not currently connected")
        if self.genesis_hash not in _chain_uri_cache:
            block_number = max(self.eth.block_number - 16, 0)
            block_hash = self.eth.get_block(block_number)["hash"].hex()[2:]
            chain_uri = f"blockchain://{self.genesis_hash}/block/{block_hash}"
            _chain_uri_cache[self.genesis_hash] = chain_uri
        return _chain_uri_cache[self.genesis_hash]

    @property
    def chain_id(self) -> int:
        # chain ID is needed each time we a sign a transaction, however we
        # cache it after the first request to avoid redundant RPC calls
        if self.provider is None:
            raise ConnectionError("web3 is not currently connected")
        if self._chain_id is None:
            self._chain_id = self.eth.chain_id
        return self._chain_id