Skip to content
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

### Bug fixes
- Fix promotion to prioritized layer for gas price fee markets [#9635](https://github.com/hyperledger/besu/pull/9635)
- Fix callTracer to properly capture nested calls and populate revertReason field when transactions revert [#9651](https://github.com/hyperledger/besu/pull/9651)

## 25.12.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ public Builder error(final String error) {
/**
* Sets the revert reason if the call was reverted.
*
* @param revertReason the revert reason
* @param revertReason the revert reason as raw bytes (will be decoded)
* @return this builder instance for method chaining
*/
public Builder revertReason(final Bytes revertReason) {
Expand All @@ -426,6 +426,21 @@ public Builder revertReason(final Bytes revertReason) {
return this;
}

/**
* Sets the revert reason directly from a decoded string.
*
* <p>This method should be used when you have already decoded the revert reason and want to set
* it directly without any fallback to hex encoding. This is useful for Geth compatibility where
* revertReason should only be set if it's a valid decoded Error(string).
*
* @param revertReason the decoded revert reason string
* @return this builder instance for method chaining
*/
public Builder revertReasonDecoded(final String revertReason) {
this.revertReason = revertReason;
return this;
}

/**
* Sets the list of nested calls made during this call's execution.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ public static CallTracerResult convert(final TransactionTrace transactionTrace)
private static CallTracerResult buildCallHierarchyFromFrames(final TransactionTrace trace) {
final CallTracerResult.Builder rootBuilder = initializeRootBuilder(trace);

if (!trace.getResult().isSuccessful()) {
// Only process frames if transaction succeeded OR reverted (not exceptional halt)
// Exceptional halts (stack underflow, out of gas, etc.) happen before execution really begins
if (!trace.getResult().isSuccessful()
&& trace.getResult().getExceptionalHaltReason().isPresent()) {
return rootBuilder.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.calltrace.OpcodeCategory.isCreateOp;

import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.CallTracerResult;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
Expand Down Expand Up @@ -170,6 +171,9 @@ public static void handleRootError(
&& result.getRevertReason().isPresent()) {
// For regular calls that reverted, set the output to the revert reason
builder.output(result.getRevertReason().get().toHexString());
// Decode and set revertReason only if it's a valid Error(string) for Geth compatibility
JsonRpcErrorResponse.decodeRevertReason(result.getRevertReason().get())
.ifPresent(builder::revertReasonDecoded);
}
}

Expand All @@ -193,7 +197,18 @@ public static void setOutputAndErrorStatus(
handleExceptionalHalt(builder, frame);
} else if (OpcodeCategory.isRevertOp(opcode)) {
builder.error(EXECUTION_REVERTED);
frame.getRevertReason().ifPresent(builder::revertReason);

// Try to decode revert reason from output data for Geth compatibility
// First check if frame has revert reason set
if (frame.getRevertReason().isPresent()) {
JsonRpcErrorResponse.decodeRevertReason(frame.getRevertReason().get())
.ifPresent(builder::revertReasonDecoded);
}
// Otherwise, decode from output data if present
else if (frame.getOutputData() != null && !frame.getOutputData().isEmpty()) {
JsonRpcErrorResponse.decodeRevertReason(frame.getOutputData())
.ifPresent(builder::revertReasonDecoded);
}
}
}

Expand Down