Skip to content

Conversation

@usmansaleem
Copy link
Member

PR description

This PR fixes two critical issues in Besu's callTracer implementation that caused incompatibility with Geth when tracing reverted transactions.

Issue 1: Missing revertReason field for reverted transactions

Problem:
When a transaction reverted with an Error(string) message, Besu's callTracer output included the ABI-encoded revert data in the output field but was
missing the decoded revertReason field that Geth provides.

Root Cause:
CallTracerErrorHandler was not decoding revert reasons from the output data.

Fix:

  • Added revertReasonDecoded(String) method to CallTracerResult.Builder for setting revertReason without hex fallback
  • Updated CallTracerErrorHandler.handleRootError() and CallTracerErrorHandler.setOutputAndErrorStatus() to use
    JsonRpcErrorResponse.decodeRevertReason()
  • Only sets revertReason field when the revert data is a valid Error(string) format (starts with 0x08c379a0), ensuring Geth compatibility

Files Changed:

  • CallTracerResult.java - Added revertReasonDecoded() method
  • CallTracerErrorHandler.java - Added revert reason decoding logic

Issue 2: Missing nested calls array when transaction reverts

Problem:
When a transaction reverted, Besu's callTracer was not capturing nested calls at all. The calls array was completely missing from the output, even
though Geth includes all nested calls up to the point of revert.

Root Cause:
CallTracerResultConverter.buildCallHierarchyFromFrames() had an early return that prevented frame processing for ALL unsuccessful transactions,
including reverts.

Fix:

  • Modified the condition to only skip frame processing for exceptional halts (stack underflow, out of gas, etc.)
  • Reverted transactions now process frames normally and populate the nested calls array
  • This ensures proper call tree reconstruction even when transactions revert

Files Changed:

  • CallTracerResultConverter.java - Updated condition in buildCallHierarchyFromFrames()

Fixed Issue(s)

Thanks for sending a pull request! Have you done the following?

  • Checked out our contribution guidelines?
  • Considered documentation and added the doc-change-required label to this PR if updates are required.
  • Considered the changelog and included an update if required.
  • For database changes (e.g. KeyValueSegmentIdentifier) considered compatibility and performed forwards and backwards compatibility tests

Locally, you can run these tests to catch failures early:

  • spotless: ./gradlew spotlessApply
  • unit tests: ./gradlew build
  • acceptance tests: ./gradlew acceptanceTest
  • integration tests: ./gradlew integrationTest
  • reference tests: ./gradlew ethereum:referenceTests:referenceTests
  • hive tests: Engine or other RPCs modified?

This commit fixes two related issues where Besu's callTracer was not
decoding and populating the revertReason field when transactions reverted
with a message, making output incompatible with Geth.

Issues fixed:
1. Root-level calls that reverted were setting output but not revertReason
2. Nested calls that reverted were not decoding revertReason from output

Changes:
- CallTracerErrorHandler.handleRootError(): Added revertReason decoding
  for non-creation calls that revert
- CallTracerErrorHandler.setOutputAndErrorStatus(): Added revertReason
  decoding from output data when REVERT opcode is executed

The fix uses the existing JsonRpcErrorResponse.decodeRevertReason() method
via CallTracerResult.Builder.revertReason() to decode ABI-encoded Error(string)
data from the output field.

Before fix:
  {
    "output": "0x08c379a0...",
    "error": "execution reverted"
  }

After fix:
  {
    "output": "0x08c379a0...",
    "error": "execution reverted",
    "revertReason": "This function always reverts"
  }

Signed-off-by: Usman Saleem <usman@usmans.info>
Process trace frames for reverted transactions to capture nested calls that
occurred before the revert. Previously, Besu skipped frame processing for
all unsuccessful transactions, causing nested calls to be completely omitted.

The fix distinguishes between:
- Transactions that reverted (REVERT opcode): Process frames to capture calls
- Transactions with exceptional halt (stack underflow, etc.): Skip frames

This matches Geth behavior where the full execution history is captured
even when the transaction ultimately reverts.

Before fix:
  {
    "error": "execution reverted",
    "revertReason": "...",
    // Missing calls array
  }

After fix:
  {
    "error": "execution reverted",
    "revertReason": "...",
    "calls": [
      {
        "type": "STATICCALL",
        "error": "execution reverted",
        "revertReason": "..."
      }
    ]
  }

Signed-off-by: Usman Saleem <usman@usmans.info>
Copy link
Contributor

@macfarla macfarla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changelog?

…rt-issues

Signed-off-by: Usman Saleem <usman@usmans.info>
Signed-off-by: Usman Saleem <usman@usmans.info>
…rt-issues

Signed-off-by: Usman Saleem <usman@usmans.info>
@usmansaleem usmansaleem enabled auto-merge (squash) January 21, 2026 05:11
…rt-issues

Signed-off-by: Usman Saleem <usman@usmans.info>
@usmansaleem usmansaleem merged commit ea3cd4c into hyperledger:main Jan 22, 2026
46 checks passed
@usmansaleem usmansaleem deleted the fix-calltracer-revert-issues branch January 22, 2026 00:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants