Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved handling of trace queries / exceptions #853

Merged
merged 4 commits into from
Nov 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion brownie/network/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ def trace_property(fn: Callable) -> Any:
def wrapper(self: "TransactionReceipt") -> Any:
if self.status < 0:
return None
if not web3.supports_traces:
raise RPCRequestError(
f"`TransactionReceipt.{fn.__name__}` requires the `debug_traceTransaction` RPC"
" endpoint, but the node client does not support it or has not made it available."
)
if self._trace_exc is not None:
raise self._trace_exc
return fn(self)
Expand Down Expand Up @@ -323,6 +328,10 @@ def wait(self, required_confs: int) -> None:
def _raise_if_reverted(self, exc: Any) -> None:
if self.status or CONFIG.mode == "console":
return
if not web3.supports_traces:
# if traces are not available, do not attempt to determine the revert reason
raise exc

if self._revert_msg is None:
# no revert message and unable to check dev string - have to get trace
self._expand_trace()
Expand Down Expand Up @@ -479,7 +488,8 @@ def _set_from_receipt(self, receipt: Dict) -> None:
def _confirm_output(self) -> str:
status = ""
if not self.status:
status = f"({color('bright red')}{self.revert_msg or 'reverted'}{color}) "
revert_msg = self.revert_msg if web3.supports_traces else None
status = f"({color('bright red')}{revert_msg or 'reverted'}{color}) "
result = (
f" {self._full_name()} confirmed {status}- "
f"Block: {color('bright blue')}{self.block_number}{color} "
Expand Down
16 changes: 16 additions & 0 deletions brownie/network/web3.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def __init__(self) -> None:
self._genesis_hash: Optional[str] = None
self._chain_uri: Optional[str] = None
self._custom_middleware: Set = set()
self._supports_traces = None

def connect(self, uri: str, timeout: int = 30) -> None:
"""Connects to a provider"""
Expand Down Expand Up @@ -89,12 +90,27 @@ def disconnect(self) -> None:
self.provider = None
self._genesis_hash = None
self._chain_uri = None
self._supports_traces = None

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
Expand Down
10 changes: 10 additions & 0 deletions docs/api-network.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2568,6 +2568,16 @@ Web3 Attributes
>>> web3.genesis_hash
'41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d'
.. py:classmethod:: Web3.supports_traces
Boolean indicating if the currently connected node client supports the `debug_traceTransaction <https://github.com/ethereum/go-ethereum/wiki/Management-APIs#user-content-debug_tracetransaction>`_ RPC endpoint.
.. code-block:: python
>>> web3.supports_traces
True
Web3 Internals
**************
Expand Down
23 changes: 23 additions & 0 deletions tests/network/test_web3.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,26 @@ def test_rinkeby(web3, network):

# this should work because we automatically add the POA middleware
web3.eth.getBlock("latest")


def test_supports_traces_development(web3, devnetwork):
# development should return true
assert web3.supports_traces


def test_supports_traces_not_connected(web3, network):
# should return false when disconnected
assert not web3.supports_traces


def test_supports_traces_infura(web3, network):
# ropsten should return false (infura, geth)
network.connect("ropsten")
assert not web3.supports_traces


def test_supports_traces_kovan(web3, network):
# kovan should return false (infura, parity)
network.connect("kovan")

assert not web3.supports_traces
14 changes: 14 additions & 0 deletions tests/network/transaction/test_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import pytest

from brownie import Contract
from brownie.exceptions import RPCRequestError
from brownie.network.transaction import TransactionReceipt
from brownie.project import build

Expand Down Expand Up @@ -248,3 +249,16 @@ def test_contractabi(ExternalCallTester, accounts, tester, ext_tester):
del ExternalCallTester[0]
ext_tester = Contract.from_abi("ExternalTesterABI", ext_tester.address, ext_tester.abi)
tx.call_trace()


def test_traces_not_supported(network, chain):
network.connect("ropsten")

tx = chain.get_transaction("0xfd9f98a245d3cff68dd67546fa7e89009a291101f045f81eb9afd14abcbdc6aa")

# the confirmation output should work even without traces
tx._confirm_output()

# querying the revert message should raise
with pytest.raises(RPCRequestError):
tx.revert_msg