Repository: ethereum/web3.py
Pull request #3334 (2024-04-05 09:30:10)

feat(geth): Add debug_traceTransaction RPC method to Geth module
### What was wrong?

Related to Issue #3330 

### How was it fixed?
1. Add debug module in geth.
2. Implement `debug_traceTransaction` RPC Method first

### Todo:

- [ ] Clean up commit history
- [x] Add or update documentation related to these changes
- [x] Add entry to the [release notes](https://github.com/ethereum/<REPO_NAME>/blob/main/newsfragments/README.md)

#### Cute Animal Picture

![](https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSTXVu7hPQwKbfV6ayFqyppTo2OkIFPsyPYj7SgcWiHwQ&s)


kclowes: Thanks! This looks like the right approach. Let me know when this is ready for review!
wangereth: > Thanks! This looks like the right approach. Let me know when this is ready for review!

Hello, I implemented `debug_traceTransaction` with `callTracer` config, and tested it myself. Maybe you can help me review it now to check if is it ok.
kclowes: This looks good so far! Do you mind adding some tests? We'll probably want to add a new file to `web3/_utils/module_testing`, and then the new class will need to be added to the integration tests in `tests/integration/go_ethereum/test_go_ethereum_*.py`. Let me know if you need an example or if you don't have time and/or don't want to and I can take it from here. Thanks!
wangereth: Sure, I will try to add test, I will ask you for help if there is any questions.
wangereth: @kclowes I have add some tests, please help review if it's ok.
wangereth: hi, @kclowes , I have no idea why the py312-core check failed, please have a look.
<img width="1504" alt="image" src="https://github.com/ethereum/web3.py/assets/164353295/c0dd1d69-7277-45bc-8383-415169a7498a">
I don't think I has do a change related to the failed test.
wangereth: ` pytest  tests/core/caching-utils/test_generate_cache_key.py --hypothesis-seed=243990134122841475368898544757628869001` will faild, but with any other seed it will success.
wangereth: ![Uploading image.png…]()


kclowes: We're failing on main too, so it's not anything in here. I'm planning to take a look at this PR this week. Thanks for the patience!
kclowes: Thanks for getting this going @wangereth! I had to rebase to pull in the latest Geth version. I'll add a newsfragment tomorrow and get this in!

Patch:
diff --git a/docs/web3.geth.rst b/docs/web3.geth.rst
index 1423f786..8eaf29fa 100644
--- a/docs/web3.geth.rst
+++ b/docs/web3.geth.rst
@@ -367,3 +367,175 @@ The following methods are available on the ``web3.geth.txpool`` namespace.
             }
           }
         }
+
+.. py:module:: web3.geth.debug
+
+GethDebug API
+~~~~~~~~~~~~~~
+
+The ``web3.geth.debug`` object exposes methods to interact with the RPC APIs under the
+``debug_`` namespace. These methods are only exposed under the ``geth`` namespace.
+Full documentation around options can be found in the
+`Geth docs <https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers>`_.
+
+.. py:method:: Debug.trace_transaction(transaction_hash)
+
+    * Delegates to ``debug_traceTransaction`` RPC Method
+
+    Returns the trace of the transaction with the given ``transaction_hash`` and ``trace_config``.
+
+    .. code-block:: python
+
+        >>> web3.geth.debug.trace_transaction('0x96014f00980a25dc7275a5eb5ed25ce0dd79c9233628c421ae373601236949b3')
+            AttributeDict({
+              'gas': 21000,
+              'failed': False,
+              'returnValue': '',
+              'structLogs': []
+            })
+
+        >>> web3.geth.debug.trace_transaction('0x96014f00980a25dc7275a5eb5ed25ce0dd79c9233628c421ae373601236949b3', {
+                "tracer": "prestateTracer",
+            })
+            AttributeDict({
+              '0x0000000000000000000000000000000000000000': AttributeDict({'balance': 0}),
+              '0x7fE3e4C21bDE162214B715AabcE05391301e9F5B': AttributeDict({'balance': 115792089237316195423570985008687907853269984665640564039457584007913129639927}),
+              '0x91fe3039271d43d3f8479f60Cc5293Bc8A461b75': AttributeDict({'balance': 0})
+            })
+
+        >>> web3.geth.debug.trace_transaction('0x96014f00980a25dc7275a5eb5ed25ce0dd79c9233628c421ae373601236949b3', {
+                "tracer": "prestateTracer",
+                "traceConfig": {"diffMode": True}
+            })
+            AttributeDict({
+              'post': AttributeDict({
+                '0x0000000000000000000000000000000000000000': AttributeDict({'balance': 63000}),
+                '0xcF888Cc4FAe8a3D774e574Ef8C6a261958287d04': AttributeDict({
+                  'balance': 115792089237316195423570985008687907853269984665640564039457583959368602105927,
+                  'nonce': 3
+                }),
+                '0xD2593D3445a9F0f4c776715f5206FBf4CA6A0475': AttributeDict({'balance': 100000})}),
+              'pre': AttributeDict({
+                '0x0000000000000000000000000000000000000000': AttributeDict({'balance': 42000}),
+                '0xcF888Cc4FAe8a3D774e574Ef8C6a261958287d04': AttributeDict({
+                  'balance': 115792089237316195423570985008687907853269984665640564039457583973451623990927,
+                  'nonce': 2
+                }),
+                '0xD2593D3445a9F0f4c776715f5206FBf4CA6A0475': AttributeDict({'balance': 0})
+              })
+            })
+
+        >>> web3.geth.debug.trace_transaction('0x96014f00980a25dc7275a5eb5ed25ce0dd79c9233628c421ae373601236949b3', {
+                "tracer": "callTracer",
+                "tracerConfig": {"withLog": False},
+            })
+            AttributeDict({'calls': [AttributeDict({'from': '0xa0457775a08b175Cbb444eD923556Dc67Ec5Dc11',
+              'gas': 154003,
+              'gasUsed': 275,
+              'input': HexBytes('0xad5c4648'),
+              'output': HexBytes('0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'),
+              'to': '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',
+              'type': 'STATICCALL'}),
+              AttributeDict({'from': '0xa0457775a08b175Cbb444eD923556Dc67Ec5Dc11',
+              'gas': 150633,
+              'gasUsed': 24678,
+              'input': HexBytes('0x095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d000000000000000000000000000000000000007e37be2022c0914b2680000000'),
+              'output': HexBytes('0x0000000000000000000000000000000000000000000000000000000000000001'),
+              'to': '0xec4cF8dCB526080792bC98E1Ef41fB4775777b6B',
+              'type': 'CALL',
+              'value': 0}),
+              AttributeDict({'calls': [AttributeDict({'from': '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',
+                'gas': 121167,
+                'gasUsed': 27504,
+                'input': HexBytes('0x23b872dd000000000000000000000000a0457775a08b175cbb444ed923556dc67ec5dc1100000000000000000000000036f0548a77bfb1d5935483d25cc40633b46e2f4d000000000000000000000000000000000000007e37be2022c0914b2680000000'),
+                'output': HexBytes('0x0000000000000000000000000000000000000000000000000000000000000001'),
+                'to': '0xec4cF8dCB526080792bC98E1Ef41fB4775777b6B',
+                'type': 'CALL',
+                'value': 0}),
+                AttributeDict({'from': '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',
+                'gas': 89911,
+                'gasUsed': 2504,
+                'input': HexBytes('0x0902f1ac'),
+                'output': HexBytes('0x00000000000000000000000000000000000000000000000053f54ccde429ceb70000000000000000000000000000000000000000001635eb93ecdb339a7dc022000000000000000000000000000000000000000000000000000000006641f6cf'),
+                'to': '0x36f0548a77BFb1d5935483D25cc40633b46e2f4d',
+                'type': 'STATICCALL'}),
+                AttributeDict({'from': '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',
+                'gas': 86926,
+                'gasUsed': 621,
+                'input': HexBytes('0x70a0823100000000000000000000000036f0548a77bfb1d5935483d25cc40633b46e2f4d'),
+                'output': HexBytes('0x000000000000000000000000000000000000007e37d4560e547e265a1a7dc022'),
+                'to': '0xec4cF8dCB526080792bC98E1Ef41fB4775777b6B',
+                'type': 'STATICCALL'}),
+                AttributeDict({'calls': [AttributeDict({'from': '0x36f0548a77BFb1d5935483D25cc40633b46e2f4d',
+                  'gas': 70279,
+                  'gasUsed': 29962,
+                  'input': HexBytes('0xa9059cbb0000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d00000000000000000000000000000000000000000000000053f53dfc545d56a6'),
+                  'output': HexBytes('0x0000000000000000000000000000000000000000000000000000000000000001'),
+                  'to': '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
+                  'type': 'CALL',
+                  'value': 0}),
+                  AttributeDict({'from': '0x36f0548a77BFb1d5935483D25cc40633b46e2f4d',
+                  'gas': 40164,
+                  'gasUsed': 534,
+                  'input': HexBytes('0x70a0823100000000000000000000000036f0548a77bfb1d5935483d25cc40633b46e2f4d'),
+                  'output': HexBytes('0x00000000000000000000000000000000000000000000000000000ed18fcc7811'),
+                  'to': '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
+                  'type': 'STATICCALL'}),
+                  AttributeDict({'from': '0x36f0548a77BFb1d5935483D25cc40633b46e2f4d',
+                  'gas': 39233,
+                  'gasUsed': 621,
+                  'input': HexBytes('0x70a0823100000000000000000000000036f0548a77bfb1d5935483d25cc40633b46e2f4d'),
+                  'output': HexBytes('0x000000000000000000000000000000000000007e37d4560e547e265a1a7dc022'),
+                  'to': '0xec4cF8dCB526080792bC98E1Ef41fB4775777b6B',
+                  'type': 'STATICCALL'})],
+                'from': '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',
+                'gas': 84769,
+                'gasUsed': 64940,
+                'input': HexBytes('0x022c0d9f00000000000000000000000000000000000000000000000053f53dfc545d56a600000000000000000000000000000000000000000000000000000000000000000000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000'),
+                'to': '0x36f0548a77BFb1d5935483D25cc40633b46e2f4d',
+                'type': 'CALL',
+                'value': 0}),
+                AttributeDict({'from': '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',
+                'gas': 20382,
+                'gasUsed': 534,
+                'input': HexBytes('0x70a082310000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d'),
+                'output': HexBytes('0x00000000000000000000000000000000000000000000000053f53dfc545d56a6'),
+                'to': '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
+                'type': 'STATICCALL'}),
+                AttributeDict({'calls': [AttributeDict({'from': '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
+                  'gas': 2300,
+                  'gasUsed': 83,
+                  'input': HexBytes('0x'),
+                  'to': '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',
+                  'type': 'CALL',
+                  'value': 6049809828398585510})],
+                'from': '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',
+                'gas': 19432,
+                'gasUsed': 9223,
+                'input': HexBytes('0x2e1a7d4d00000000000000000000000000000000000000000000000053f53dfc545d56a6'),
+                'to': '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
+                'type': 'CALL',
+                'value': 0}),
+                AttributeDict({'from': '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',
+                'gas': 3353,
+                'gasUsed': 0,
+                'input': HexBytes('0x'),
+                'to': '0xE11418f9961248da36b1008b1090235f680AE8f5',
+                'type': 'CALL',
+                'value': 6049809828398585510})],
+              'from': '0xa0457775a08b175Cbb444eD923556Dc67Ec5Dc11',
+              'gas': 125270,
+              'gasUsed': 122013,
+              'input': HexBytes('0x791ac947000000000000000000000000000000000000007e37be2022c0914b2680000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000e11418f9961248da36b1008b1090235f680ae8f5000000000000000000000000000000000000000000000000000000006641fe7f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000ec4cf8dcb526080792bc98e1ef41fb4775777b6b000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'),
+              'to': '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',
+              'type': 'CALL',
+              'value': 0})],
+            'from': '0xE11418f9961248da36b1008b1090235f680AE8f5',
+            'gas': 185574,
+            'gasUsed': 144300,
+            'input': HexBytes('0x56feb11b000000000000000000000000ec4cf8dcb526080792bc98e1ef41fb4775777b6b000000000000000000000000000000000000007e37be2022c0914b2680000000'),
+            'to': '0xa0457775a08b175Cbb444eD923556Dc67Ec5Dc11',
+            'type': 'CALL',
+            'value': 0})
+
+            >>> w3.geth.debug.trace_transaction(tx_hash, {'tracer': '4byteTracer'})
diff --git a/newsfragments/3334.feature.rst b/newsfragments/3334.feature.rst
new file mode 100644
index 00000000..e948cb0d
--- /dev/null
+++ b/newsfragments/3334.feature.rst
@@ -0,0 +1,1 @@
+Add support for Geth Debug traceTransaction.
diff --git a/web3/_utils/method_formatters.py b/web3/_utils/method_formatters.py
index b1f2a944..5fef2426 100644
--- a/web3/_utils/method_formatters.py
+++ b/web3/_utils/method_formatters.py
@@ -9,6 +9,7 @@ from typing import (
     Iterable,
     NoReturn,
     Tuple,
+    TypeVar,
     Union,
     cast,
 )
@@ -111,6 +112,8 @@ if TYPE_CHECKING:
     from web3.eth import Eth  # noqa: F401
     from web3.module import Module  # noqa: F401
 
+TValue = TypeVar("TValue")
+
 
 def bytes_to_ascii(value: bytes) -> str:
     return codecs.decode(value, "ascii")
@@ -607,6 +610,102 @@ PYTHONIC_REQUEST_FORMATTERS: Dict[RPCEndpoint, Callable[..., Any]] = {
 }
 
 # --- Result Formatters --- #
+# -- debug -- #
+DEBUG_CALLTRACE_LOG_ENTRY_FORMATTERS = apply_formatter_if(
+    is_not_null,
+    type_aware_apply_formatters_to_dict(
+        {
+            "address": to_checksum_address,
+            "topics": apply_list_to_array_formatter(to_hexbytes(32)),
+            "data": HexBytes,
+            "position": to_integer_if_hex,
+        }
+    ),
+)
+
+
+debug_calltrace_log_list_result_formatter: Callable[
+    [Formatters], Any
+] = apply_formatter_to_array(DEBUG_CALLTRACE_LOG_ENTRY_FORMATTERS)
+
+
+PRETRACE_INNER_FORMATTERS = {
+    "balance": to_integer_if_hex,
+    "nonce": to_integer_if_hex,
+}
+
+
+def has_pretrace_keys(val: Any) -> bool:
+    if isinstance(val, dict) or isinstance(val, AttributeDict):
+        return (
+            val.get("balance")
+            or val.get("nonce")
+            or val.get("code")
+            or val.get("storage")
+        )
+    return False
+
+
+@curry
+def pretrace_formatter(
+    resp: Union[AttributeDict[str, Any], Dict[str, Any]],
+) -> Union[ReadableAttributeDict[str, Any], Dict[str, Any]]:
+    return type_aware_apply_formatters_to_dict_keys_and_values(
+        apply_formatter_if(is_address, to_checksum_address),
+        apply_formatter_if(
+            has_pretrace_keys,
+            type_aware_apply_formatters_to_dict(PRETRACE_INNER_FORMATTERS),
+        ),
+        resp,
+    )
+
+
+DEBUG_PRESTATE_DIFFMODE_FORMATTERS = {
+    "pre": pretrace_formatter,
+    "post": pretrace_formatter,
+}
+
+
+DEBUG_CALLTRACE_FORMATTERS = {
+    "from": to_checksum_address,
+    "to": to_checksum_address,
+    "value": to_integer_if_hex,
+    "gas": to_integer_if_hex,
+    "gasUsed": to_integer_if_hex,
+    "input": HexBytes,
+    "output": HexBytes,
+    "calls": lambda calls: debug_calltrace_list_result_formatter(calls),
+    "logs": debug_calltrace_log_list_result_formatter,
+}
+
+
+OPCODE_TRACE_FORMATTERS = {
+    "pc": to_integer_if_hex,
+    "gas": to_integer_if_hex,
+    "gasCost": to_integer_if_hex,
+    "refund": to_integer_if_hex,
+}
+
+
+DEBUG_TRACE_FORMATTERS = {
+    **DEBUG_CALLTRACE_FORMATTERS,
+    **OPCODE_TRACE_FORMATTERS,
+    **DEBUG_PRESTATE_DIFFMODE_FORMATTERS,
+}
+
+
+trace_result_formatters = type_aware_apply_formatters_to_dict(DEBUG_TRACE_FORMATTERS)
+
+
+debug_calltrace_result_formatter = type_aware_apply_formatters_to_dict(
+    DEBUG_CALLTRACE_FORMATTERS
+)
+
+
+debug_calltrace_list_result_formatter: Callable[
+    [Formatters], Any
+] = apply_formatter_to_array(debug_calltrace_result_formatter)
+
 
 # -- tracing -- #
 
@@ -646,7 +745,7 @@ TRACE_RESULT_FORMATTERS = apply_formatter_if(
 )
 
 # result formatters for the trace field
-TRACE_FORMATTERS = apply_formatter_if(
+TRACE_FORMATTERS: Callable[[TValue], Union[Any, TValue]] = apply_formatter_if(
     is_not_null,
     type_aware_apply_formatters_to_dict(
         {
@@ -795,6 +894,14 @@ PYTHONIC_RESULT_FORMATTERS: Dict[RPCEndpoint, Callable[..., Any]] = {
     RPC.evm_snapshot: hex_to_integer,
     # Net
     RPC.net_peerCount: to_integer_if_hex,
+    # Debug
+    RPC.debug_traceTransaction: apply_formatter_if(
+        is_not_null,
+        compose(
+            pretrace_formatter,
+            trace_result_formatters,
+        ),
+    ),
     # tracing
     RPC.trace_block: trace_list_result_formatter,
     RPC.trace_call: common_tracing_result_formatter,
diff --git a/web3/_utils/rpc_abi.py b/web3/_utils/rpc_abi.py
index f2e9dcbf..89dd8185 100644
--- a/web3/_utils/rpc_abi.py
+++ b/web3/_utils/rpc_abi.py
@@ -140,6 +140,9 @@ class RPC:
     # web3
     web3_clientVersion = RPCEndpoint("web3_clientVersion")
 
+    # debug
+    debug_traceTransaction = RPCEndpoint("debug_traceTransaction")
+
 
 TRANSACTION_PARAMS_ABIS = {
     "data": "bytes",
diff --git a/web3/geth.py b/web3/geth.py
index 35ba2fa3..dfa46434 100644
--- a/web3/geth.py
+++ b/web3/geth.py
@@ -5,6 +5,7 @@ from typing import (
     Optional,
     Protocol,
     Tuple,
+    Union,
 )
 
 from eth_typing.evm import (
@@ -22,12 +23,19 @@ from web3.module import (
     Module,
 )
 from web3.types import (
+    CallTrace,
+    DiffModeTrace,
     EnodeURI,
+    FourByteTrace,
     NodeInfo,
+    OpcodeTrace,
     Peer,
+    PrestateTrace,
+    TraceConfig,
     TxPoolContent,
     TxPoolInspect,
     TxPoolStatus,
+    _Hash32,
 )
 
 
@@ -133,9 +141,33 @@ class GethAdmin(Module):
     )
 
 
+class GethDebug(Module):
+    """
+    https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug
+    """
+
+    def trace_transaction_munger(
+        self,
+        transaction_hash: _Hash32,
+        trace_config: Optional[TraceConfig] = None,
+    ) -> Tuple[_Hash32, TraceConfig]:
+        return (transaction_hash, trace_config)
+
+    trace_transaction: Method[
+        Callable[
+            ...,
+            Union[CallTrace, PrestateTrace, OpcodeTrace, DiffModeTrace, FourByteTrace],
+        ]
+    ] = Method(
+        RPC.debug_traceTransaction,
+        mungers=[trace_transaction_munger],
+    )
+
+
 class Geth(Module):
     admin: GethAdmin
     txpool: GethTxPool
+    debug: GethDebug
 
 
 # --- async --- #
@@ -261,8 +293,35 @@ class AsyncGethAdmin(Module):
         return await self._stop_ws()
 
 
+class AsyncGethDebug(Module):
+    """
+    https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug
+    """
+
+    is_async = True
+
+    _trace_transaction: Method[
+        Callable[
+            ...,
+            Awaitable[
+                Union[
+                    CallTrace, PrestateTrace, OpcodeTrace, FourByteTrace, DiffModeTrace
+                ]
+            ],
+        ]
+    ] = Method(RPC.debug_traceTransaction)
+
+    async def trace_transaction(
+        self,
+        transaction_hash: _Hash32,
+        trace_config: Optional[TraceConfig] = None,
+    ) -> Union[CallTrace, PrestateTrace, OpcodeTrace, FourByteTrace, DiffModeTrace]:
+        return await self._trace_transaction(transaction_hash, trace_config)
+
+
 class AsyncGeth(Module):
     is_async = True
 
     admin: AsyncGethAdmin
     txpool: AsyncGethTxPool
+    debug: AsyncGethDebug
diff --git a/web3/main.py b/web3/main.py
index aea5f929..e74c4946 100644
--- a/web3/main.py
+++ b/web3/main.py
@@ -93,9 +93,11 @@ from web3.exceptions import (
 from web3.geth import (
     AsyncGeth,
     AsyncGethAdmin,
+    AsyncGethDebug,
     AsyncGethTxPool,
     Geth,
     GethAdmin,
+    GethDebug,
     GethTxPool,
 )
 from web3.manager import (
@@ -162,6 +164,7 @@ def get_async_default_modules() -> Dict[str, Union[Type[Module], Sequence[Any]]]
             {
                 "admin": AsyncGethAdmin,
                 "txpool": AsyncGethTxPool,
+                "debug": AsyncGethDebug,
             },
         ),
     }
@@ -176,6 +179,7 @@ def get_default_modules() -> Dict[str, Union[Type[Module], Sequence[Any]]]:
             {
                 "admin": GethAdmin,
                 "txpool": GethTxPool,
+                "debug": GethDebug,
             },
         ),
         "tracing": Tracing,
diff --git a/web3/manager.py b/web3/manager.py
index 888855e0..6fc217fe 100644
--- a/web3/manager.py
+++ b/web3/manager.py
@@ -39,6 +39,7 @@ from web3.exceptions import (
     ProviderConnectionError,
     RequestTimedOut,
     TaskNotRunning,
+    TransactionNotFound,
     Web3RPCError,
     Web3TypeError,
 )
@@ -150,6 +151,7 @@ def _validate_response(
     error_formatters: Optional[Callable[..., Any]],
     is_subscription_response: bool = False,
     logger: Optional[logging.Logger] = None,
+    params: Optional[Any] = None,
 ) -> None:
     if "jsonrpc" not in response or response["jsonrpc"] != "2.0":
         _raise_bad_response_format(
@@ -210,6 +212,13 @@ def _validate_response(
             _raise_bad_response_format(
                 response, 'error["message"] is required and must be a string value.'
             )
+        elif error_message == "transaction not found":
+            transaction_hash = params[0]
+            web3_rpc_error = TransactionNotFound(
+                repr(error),
+                rpc_response=response,
+                user_message=(f"Transaction with hash {transaction_hash!r} not found."),
+            )
 
         # errors must include an integer code
         code = error.get("code")
@@ -360,6 +369,7 @@ class RequestManager:
             error_formatters,
             is_subscription_response=is_subscription_response,
             logger=self.logger,
+            params=params,
         )
 
         # format results
diff --git a/web3/tools/benchmark/node.py b/web3/tools/benchmark/node.py
index 65d3a1e2..506e0c0a 100644
--- a/web3/tools/benchmark/node.py
+++ b/web3/tools/benchmark/node.py
@@ -87,7 +87,7 @@ class GethBenchmarkFixture:
             "--http.port",
             self.rpc_port,
             "--http.api",
-            "admin,eth,net,web3",
+            "admin,debug,eth,net,web3",
             "--ipcdisable",
             "--password",
             os.path.join(datadir, "keystore", "pw.txt"),
diff --git a/web3/types.py b/web3/types.py
index 34599542..7b2a227e 100644
--- a/web3/types.py
+++ b/web3/types.py
@@ -475,6 +475,84 @@ class TxPoolStatus(TypedDict, total=False):
     queued: int
 
 
+#
+# debug types
+#
+class TraceConfig(TypedDict, total=False):
+    disableStorage: bool
+    disableStack: bool
+    enableMemory: bool
+    enableReturnData: bool
+    tracer: str
+    tracerConfig: Dict[str, Any]
+    timeout: int
+
+
+class CallTraceLog(TypedDict):
+    address: ChecksumAddress
+    data: HexBytes
+    topics: Sequence[HexBytes]
+    position: int
+
+
+# syntax b/c "from" keyword not allowed w/ class construction
+CallTrace = TypedDict(
+    "CallTrace",
+    {
+        "type": str,
+        "from": ChecksumAddress,
+        "to": ChecksumAddress,
+        "value": Wei,
+        "gas": int,
+        "gasUsed": int,
+        "input": HexBytes,
+        "output": HexBytes,
+        "error": str,
+        "revertReason": str,
+        "calls": Sequence["CallTrace"],
+        "logs": Sequence[CallTraceLog],
+    },
+    total=False,
+)
+
+
+class TraceData(TypedDict, total=False):
+    balance: int
+    nonce: int
+    code: str
+    storage: Dict[str, str]
+
+
+class DiffModeTrace(TypedDict):
+    post: Dict[ChecksumAddress, TraceData]
+    pre: Dict[ChecksumAddress, TraceData]
+
+
+PrestateTrace = Dict[ChecksumAddress, TraceData]
+
+
+# 4byte tracer returns something like:
+# { '0x27dc297e-128' : 1 }
+# which is: { 4byte signature - calldata size : # of occurrences of key }
+FourByteTrace = Dict[str, int]
+
+
+class StructLog(TypedDict):
+    pc: int
+    op: str
+    gas: int
+    gasCost: int
+    depth: int
+    stack: List[HexStr]
+
+
+class OpcodeTrace(TypedDict, total=False):
+    gas: int
+    failed: bool
+    returnValue: str
+    structLogs: List[StructLog]
+
+
 #
 # web3.geth types
 #


Test patch:
diff --git a/tests/integration/go_ethereum/common.py b/tests/integration/go_ethereum/common.py
index 102f4720..faeba741 100644
--- a/tests/integration/go_ethereum/common.py
+++ b/tests/integration/go_ethereum/common.py
@@ -12,7 +12,9 @@ from web3._utils.module_testing import (  # noqa: F401
     AsyncNetModuleTest,
     EthModuleTest,
     GoEthereumAdminModuleTest,
+    GoEthereumAsyncDebugModuleTest,
     GoEthereumAsyncTxPoolModuleTest,
+    GoEthereumDebugModuleTest,
     GoEthereumTxPoolModuleTest,
     NetModuleTest,
     Web3ModuleTest,
diff --git a/tests/integration/go_ethereum/test_goethereum_http.py b/tests/integration/go_ethereum/test_goethereum_http.py
index 5ae82ad6..51e18c9b 100644
--- a/tests/integration/go_ethereum/test_goethereum_http.py
+++ b/tests/integration/go_ethereum/test_goethereum_http.py
@@ -18,10 +18,12 @@ from web3.providers.rpc import (
 
 from .common import (
     GoEthereumAdminModuleTest,
+    GoEthereumAsyncDebugModuleTest,
     GoEthereumAsyncEthModuleTest,
     GoEthereumAsyncNetModuleTest,
     GoEthereumAsyncTxPoolModuleTest,
     GoEthereumAsyncWeb3ModuleTest,
+    GoEthereumDebugModuleTest,
     GoEthereumEthModuleTest,
     GoEthereumNetModuleTest,
     GoEthereumTxPoolModuleTest,
@@ -51,7 +53,7 @@ def _geth_command_arguments(rpc_port, base_geth_command_arguments, geth_version)
             "--http.port",
             rpc_port,
             "--http.api",
-            "admin,eth,net,web3,txpool",
+            "admin,debug,eth,net,web3,txpool",
             "--ipcdisable",
         )
     else:
@@ -95,6 +97,10 @@ class TestGoEthereumAdminModuleTest(GoEthereumAdminModuleTest):
         super().test_admin_start_stop_ws(w3)
 
 
+class TestGoEthereumDebugModuleTest(GoEthereumDebugModuleTest):
+    pass
+
+
 class TestGoEthereumEthModuleTest(GoEthereumEthModuleTest):
     pass
 
@@ -144,6 +150,10 @@ class TestGoEthereumAsyncAdminModuleTest(GoEthereumAsyncAdminModuleTest):
         await super().test_admin_start_stop_ws(async_w3)
 
 
+class TestGoEthereumAsyncDebugModuleTest(GoEthereumAsyncDebugModuleTest):
+    pass
+
+
 class TestGoEthereumAsyncNetModuleTest(GoEthereumAsyncNetModuleTest):
     pass
 
diff --git a/tests/integration/go_ethereum/test_goethereum_ipc.py b/tests/integration/go_ethereum/test_goethereum_ipc.py
index d651e2a5..ec708d8b 100644
--- a/tests/integration/go_ethereum/test_goethereum_ipc.py
+++ b/tests/integration/go_ethereum/test_goethereum_ipc.py
@@ -15,9 +15,11 @@ from web3._utils.module_testing.persistent_connection_provider import (
 
 from .common import (
     GoEthereumAdminModuleTest,
+    GoEthereumAsyncDebugModuleTest,
     GoEthereumAsyncEthModuleTest,
     GoEthereumAsyncNetModuleTest,
     GoEthereumAsyncWeb3ModuleTest,
+    GoEthereumDebugModuleTest,
     GoEthereumEthModuleTest,
     GoEthereumNetModuleTest,
     GoEthereumWeb3ModuleTest,
@@ -61,6 +63,10 @@ class TestGoEthereumWeb3ModuleTest(GoEthereumWeb3ModuleTest):
     pass
 
 
+class TestGoEthereumDebugModuleTest(GoEthereumDebugModuleTest):
+    pass
+
+
 class TestGoEthereumEthModuleTest(GoEthereumEthModuleTest):
     pass
 
@@ -103,6 +109,10 @@ class TestGoEthereumAsyncWeb3ModuleTest(GoEthereumAsyncWeb3ModuleTest):
     pass
 
 
+class TestGoEthereumAsyncDebugModuleTest(GoEthereumAsyncDebugModuleTest):
+    pass
+
+
 class TestGoEthereumAsyncEthModuleTest(GoEthereumAsyncEthModuleTest):
     pass
 
diff --git a/tests/integration/go_ethereum/test_goethereum_legacy_ws.py b/tests/integration/go_ethereum/test_goethereum_legacy_ws.py
index cc1e66f1..05ec490c 100644
--- a/tests/integration/go_ethereum/test_goethereum_legacy_ws.py
+++ b/tests/integration/go_ethereum/test_goethereum_legacy_ws.py
@@ -15,6 +15,7 @@ from web3 import (
 
 from .common import (
     GoEthereumAdminModuleTest,
+    GoEthereumDebugModuleTest,
     GoEthereumEthModuleTest,
     GoEthereumNetModuleTest,
     GoEthereumWeb3ModuleTest,
@@ -41,7 +42,7 @@ def _geth_command_arguments(ws_port, base_geth_command_arguments, geth_version):
             "--ws.port",
             ws_port,
             "--ws.api",
-            "admin,eth,net,web3",
+            "admin,debug,eth,net,web3",
             "--ws.origins",
             "*",
             "--ipcdisable",
@@ -97,6 +98,10 @@ class TestGoEthereumAdminModuleTest(GoEthereumAdminModuleTest):
         super().test_admin_start_stop_ws(w3)
 
 
+class TestGoEthereumDebugModuleTest(GoEthereumDebugModuleTest):
+    pass
+
+
 class TestGoEthereumEthModuleTest(GoEthereumEthModuleTest):
     pass
 
diff --git a/tests/integration/go_ethereum/test_goethereum_ws/conftest.py b/tests/integration/go_ethereum/test_goethereum_ws/conftest.py
index e987ec45..fcd71d3a 100644
--- a/tests/integration/go_ethereum/test_goethereum_ws/conftest.py
+++ b/tests/integration/go_ethereum/test_goethereum_ws/conftest.py
@@ -28,7 +28,7 @@ def _geth_command_arguments(ws_port, base_geth_command_arguments, geth_version):
             "--ws.port",
             ws_port,
             "--ws.api",
-            "admin,eth,net,web3",
+            "admin,debug,eth,net,web3",
             "--ws.origins",
             "*",
             "--ipcdisable",
diff --git a/tests/integration/go_ethereum/test_goethereum_ws/test_async_await_w3.py b/tests/integration/go_ethereum/test_goethereum_ws/test_async_await_w3.py
index 40b8bfba..a04c56d1 100644
--- a/tests/integration/go_ethereum/test_goethereum_ws/test_async_await_w3.py
+++ b/tests/integration/go_ethereum/test_goethereum_ws/test_async_await_w3.py
@@ -14,6 +14,7 @@ from web3._utils.module_testing.persistent_connection_provider import (
 )
 
 from ..common import (
+    GoEthereumAsyncDebugModuleTest,
     GoEthereumAsyncEthModuleTest,
     GoEthereumAsyncNetModuleTest,
     GoEthereumAsyncWeb3ModuleTest,
@@ -71,3 +72,7 @@ class TestGoEthereumAsyncEthModuleTest(GoEthereumAsyncEthModuleTest):
 
 class TestGoEthereumAsyncNetModuleTest(GoEthereumAsyncNetModuleTest):
     pass
+
+
+class TestGoEthereumAsyncDebugModuleTest(GoEthereumAsyncDebugModuleTest):
+    pass
diff --git a/tests/integration/go_ethereum/test_goethereum_ws/test_async_ctx_manager_w3.py b/tests/integration/go_ethereum/test_goethereum_ws/test_async_ctx_manager_w3.py
index 594be644..127627fb 100644
--- a/tests/integration/go_ethereum/test_goethereum_ws/test_async_ctx_manager_w3.py
+++ b/tests/integration/go_ethereum/test_goethereum_ws/test_async_ctx_manager_w3.py
@@ -14,6 +14,7 @@ from web3._utils.module_testing.persistent_connection_provider import (
 )
 
 from ..common import (
+    GoEthereumAsyncDebugModuleTest,
     GoEthereumAsyncEthModuleTest,
     GoEthereumAsyncNetModuleTest,
     GoEthereumAsyncWeb3ModuleTest,
@@ -72,3 +73,7 @@ class TestGoEthereumAsyncEthModuleTest(GoEthereumAsyncEthModuleTest):
 
 class TestGoEthereumAsyncNetModuleTest(GoEthereumAsyncNetModuleTest):
     pass
+
+
+class TestGoEthereumAsyncDebugModuleTest(GoEthereumAsyncDebugModuleTest):
+    pass
diff --git a/tests/integration/go_ethereum/test_goethereum_ws/test_async_iterator_w3.py b/tests/integration/go_ethereum/test_goethereum_ws/test_async_iterator_w3.py
index c834d125..d10bad9c 100644
--- a/tests/integration/go_ethereum/test_goethereum_ws/test_async_iterator_w3.py
+++ b/tests/integration/go_ethereum/test_goethereum_ws/test_async_iterator_w3.py
@@ -14,6 +14,7 @@ from web3._utils.module_testing.persistent_connection_provider import (
 )
 
 from ..common import (
+    GoEthereumAsyncDebugModuleTest,
     GoEthereumAsyncEthModuleTest,
     GoEthereumAsyncNetModuleTest,
     GoEthereumAsyncWeb3ModuleTest,
@@ -72,3 +73,7 @@ class TestGoEthereumAsyncEthModuleTest(GoEthereumAsyncEthModuleTest):
 
 class TestGoEthereumAsyncNetModuleTest(GoEthereumAsyncNetModuleTest):
     pass
+
+
+class TestGoEthereumAsyncDebugModuleTest(GoEthereumAsyncDebugModuleTest):
+    pass
diff --git a/tests/integration/go_ethereum/utils.py b/tests/integration/go_ethereum/utils.py
index 1d94af60..a9842a81 100644
--- a/tests/integration/go_ethereum/utils.py
+++ b/tests/integration/go_ethereum/utils.py
@@ -9,15 +9,16 @@ import requests
 
 def wait_for_socket(ipc_path, timeout=30):
     start = time.time()
+    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
     while time.time() < start + timeout:
         try:
-            sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
             sock.connect(ipc_path)
             sock.settimeout(timeout)
         except OSError:
             time.sleep(0.01)
         else:
             break
+    sock.close()
 
 
 async def wait_for_async_socket(ipc_path, timeout=30):
diff --git a/web3/_utils/module_testing/__init__.py b/web3/_utils/module_testing/__init__.py
index 2bdb2675..f375ea9e 100644
--- a/web3/_utils/module_testing/__init__.py
+++ b/web3/_utils/module_testing/__init__.py
@@ -5,6 +5,10 @@ from .eth_module import (
 from .go_ethereum_admin_module import (
     GoEthereumAdminModuleTest,
 )
+from .go_ethereum_debug_module import (
+    GoEthereumAsyncDebugModuleTest,
+    GoEthereumDebugModuleTest,
+)
 from .go_ethereum_txpool_module import (
     GoEthereumAsyncTxPoolModuleTest,
     GoEthereumTxPoolModuleTest,
diff --git a/web3/_utils/module_testing/go_ethereum_debug_module.py b/web3/_utils/module_testing/go_ethereum_debug_module.py
new file mode 100644
index 00000000..eecc352f
--- /dev/null
+++ b/web3/_utils/module_testing/go_ethereum_debug_module.py
@@ -0,0 +1,128 @@
+import pytest
+from typing import (
+    cast,
+)
+
+from eth_typing import (
+    HexStr,
+)
+
+from web3 import (
+    AsyncWeb3,
+    Web3,
+)
+from web3.types import (
+    BlockData,
+    CallTrace,
+    ChecksumAddress,
+    DiffModeTrace,
+    OpcodeTrace,
+    PrestateTrace,
+)
+
+
+class GoEthereumAsyncDebugModuleTest:
+    @pytest.mark.asyncio
+    async def test_async_geth_debug_trace_transaction_opcode_logger(
+        self, async_w3: "AsyncWeb3", txn_hash_with_log: HexStr
+    ) -> None:
+        result = await async_w3.geth.debug.trace_transaction(txn_hash_with_log)
+        assert "structLogs" in dict(result).keys()
+        assert "gas" in dict(result).keys()
+        assert "failed" in dict(result).keys()
+
+    @pytest.mark.asyncio
+    async def test_async_geth_debug_trace_transaction_call_tracer(
+        self, async_w3: "AsyncWeb3", txn_hash_with_log: HexStr
+    ) -> None:
+        result = cast(
+            CallTrace,
+            await async_w3.geth.debug.trace_transaction(
+                txn_hash_with_log, {"tracer": "callTracer"}
+            ),
+        )
+        assert result.get("type") == "CALL"
+
+    @pytest.mark.asyncio
+    async def test_async_geth_debug_trace_transaction_prestate_tracer_diffMode(
+        self, async_w3: "AsyncWeb3", txn_hash_with_log: HexStr
+    ) -> None:
+        result = cast(
+            DiffModeTrace,
+            await async_w3.geth.debug.trace_transaction(
+                txn_hash_with_log,
+                {"tracer": "prestateTracer", "tracerConfig": {"diffMode": True}},
+            ),
+        )
+        assert "post" in dict(result).keys()
+        assert "pre" in dict(result).keys()
+
+    @pytest.mark.asyncio
+    async def test_async_geth_debug_trace_transaction_prestate_tracer(
+        self,
+        async_w3: "AsyncWeb3",
+        txn_hash_with_log: HexStr,
+        async_block_with_txn_with_log: BlockData,
+    ) -> None:
+        result = cast(
+            PrestateTrace,
+            await async_w3.geth.debug.trace_transaction(
+                txn_hash_with_log,
+                {"tracer": "prestateTracer"},
+            ),
+        )
+        tx = await async_w3.eth.get_transaction(txn_hash_with_log)
+        from_addr: ChecksumAddress = tx["from"]
+        assert isinstance(result[from_addr].get("balance"), int)
+        assert "post" not in dict(result).keys()
+        assert "pre" not in dict(result).keys()
+
+
+class GoEthereumDebugModuleTest:
+    def test_geth_debug_trace_transaction_opcode_logger(
+        self, w3: "Web3", txn_hash_with_log: HexStr
+    ) -> None:
+        result = cast(OpcodeTrace, w3.geth.debug.trace_transaction(txn_hash_with_log))
+        assert "structLogs" in dict(result).keys()
+        assert "gas" in dict(result).keys()
+        assert "failed" in dict(result).keys()
+
+    def test_geth_debug_trace_transaction_call_tracer(
+        self, w3: "Web3", txn_hash_with_log: HexStr
+    ) -> None:
+        result = cast(
+            CallTrace,
+            w3.geth.debug.trace_transaction(
+                txn_hash_with_log, {"tracer": "callTracer"}
+            ),
+        )
+        assert result.get("type") == "CALL"
+
+    def test_geth_debug_trace_transaction_prestate_tracer_diffmode(
+        self, w3: "Web3", txn_hash_with_log: HexStr
+    ) -> None:
+        result = cast(
+            DiffModeTrace,
+            w3.geth.debug.trace_transaction(
+                txn_hash_with_log,
+                {"tracer": "prestateTracer", "tracerConfig": {"diffMode": True}},
+            ),
+        )
+        assert "post" in dict(result).keys()
+        assert "pre" in dict(result).keys()
+
+    def test_geth_debug_trace_transaction_prestate_tracer(
+        self, w3: "Web3", txn_hash_with_log: HexStr
+    ) -> None:
+        result = cast(
+            PrestateTrace,
+            w3.geth.debug.trace_transaction(
+                txn_hash_with_log,
+                {"tracer": "prestateTracer"},
+            ),
+        )
+        tx = w3.eth.get_transaction(txn_hash_with_log)
+        from_addr: ChecksumAddress = tx["from"]
+        assert isinstance(result[from_addr].get("balance"), int)
+        assert "post" not in dict(result).keys()
+        assert "pre" not in dict(result).keys()