Skip to content

Commit

Permalink
Merge pull request #853 from eth-brownie/feat-trace-check
Browse files Browse the repository at this point in the history
Improved handling of trace queries / exceptions
  • Loading branch information
iamdefinitelyahuman committed Nov 21, 2020
2 parents 4beaefa + b9f146f commit e137d77
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 1 deletion.
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

0 comments on commit e137d77

Please sign in to comment.