Skip to content

Add support for 4byteTracer in debug_trace* methods#9642

Merged
usmansaleem merged 29 commits intobesu-eth:mainfrom
usmansaleem:4byte_tracer
Jan 30, 2026
Merged

Add support for 4byteTracer in debug_trace* methods#9642
usmansaleem merged 29 commits intobesu-eth:mainfrom
usmansaleem:4byte_tracer

Conversation

@usmansaleem
Copy link
Copy Markdown
Contributor

@usmansaleem usmansaleem commented Jan 16, 2026

PR description

Implements Geth's 4byteTracer for debug_traceTransaction, debug_traceBlock, and related debug trace methods. The 4byteTracer collects function selectors (the first 4 bytes of call data) from all internal calls made during transaction execution, along with the size of the remaining call data (excluding the 4-byte selector).

Changes

  • Add FOUR_BYTE_TRACER to TracerType enum
  • Create FourByteTracerResult class for JSON output formatting
  • Create FourByteTracerResultConverter to process transaction traces
  • Wire 4ByteTracer into DebugTraceTransactionStepFactory
  • Add comprehensive unit tests

Implementation Details

The implementation matches Geth's 4byteTracer behavior:

  • Only processes CALL-type operations (CALL, CALLCODE, DELEGATECALL, STATICCALL)
  • Excludes CREATE/CREATE2 operations and precompiled contracts
  • Only counts calls that successfully enter (depth increases in next frame)
  • Calculates size as input_length - 4 (excluding the selector)
  • Correctly handles nested sub-calls at any depth with proper occurrence counting
  • Uses ProtocolSpec's precompile registry for accurate precompile detection across different protocol specifications
  • Injects ProtocolSpec into DebugTraceTransactionStepFactory for protocol-aware tracing

Output Format

Returns a map where keys are in the format "0x[4-byte-selector]-[remaining-size]" and values are occurrence counts:

{
  "0x27dc297e-128": 1,
  "0x38cc4831-0": 2,
  "0x524f3889-96": 1
}

Testing

  • Added FourByteTracerResultConverterTest with comprehensive test cases covering:
    • Null/empty input handling
    • Function selector extraction from CALL operations
    • Filtering (precompiles, non-entered calls, CREATE ops, short input)
    • Multiple calls with same/different selectors
    • All CALL-type operations (CALL, CALLCODE, DELEGATECALL, STATICCALL)
    • Nested sub-calls with multiple occurrences
    • Edge cases (exactly 4 bytes of input)
  • Updated DebugTraceTransactionStepFactoryTest to include 4ByteTracer
  • All existing tests pass

References

Acknowledgements

Based on original contribution PR: #9202 by @JukLee0ira

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?

Add support for the 4byteTracer which collects function selectors
(first 4 bytes of call data) from all internal calls during transaction
execution, along with the size of the remaining call data.

Implementation includes:
- Add FOUR_BYTE_TRACER to TracerType enum
- Create FourByteTracerResult class for JSON output
- Create FourByteTracerResultConverter to process transaction traces
- Wire 4ByteTracer into DebugTraceTransactionStepFactory
- Add comprehensive unit tests

The implementation matches Geth's behavior by:
- Only processing CALL-type operations (CALL, CALLCODE, DELEGATECALL, STATICCALL)
- Excluding CREATE/CREATE2 operations and precompiled contracts
- Only counting calls that successfully enter (depth increases)
- Calculating size as input length minus 4 bytes (the selector)
- Excluding the initial transaction call (only internal calls)

Signed-off-by: Usman Saleem <usman@usmans.info>
Signed-off-by: Usman Saleem <usman@usmans.info>
Signed-off-by: Usman Saleem <usman@usmans.info>
@usmansaleem usmansaleem requested a review from Copilot January 16, 2026 01:55
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Signed-off-by: Usman Saleem <usman@usmans.info>
Signed-off-by: Usman Saleem <usman@usmans.info>
Signed-off-by: Usman Saleem <usman@usmans.info>
Signed-off-by: Usman Saleem <usman@usmans.info>
@usmansaleem usmansaleem marked this pull request as ready for review January 19, 2026 23:35
Signed-off-by: Usman Saleem <usman@usmans.info>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Signed-off-by: Usman Saleem <usman@usmans.info>
Signed-off-by: Usman Saleem <usman@usmans.info>
Signed-off-by: Usman Saleem <usman@usmans.info>
Signed-off-by: Usman Saleem <usman@usmans.info>
Signed-off-by: Usman Saleem <usman@usmans.info>
Signed-off-by: Usman Saleem <usman@usmans.info>
Signed-off-by: Usman Saleem <usman@usmans.info>
Signed-off-by: Usman Saleem <usman@usmans.info>
@macfarla macfarla moved this to Open PRs in 26.1.0 Release Jan 22, 2026
Signed-off-by: Usman Saleem <usman@usmans.info>
Signed-off-by: Usman Saleem <usman@usmans.info>
Signed-off-by: Usman Saleem <usman@usmans.info>
Update FourByteTracerResultConverter to use ProtocolSpec's
precompile contract registry instead of hardcoded address
checks when detecting precompiled contracts. This ensures
correct precompile detection across different protocol specs.

- Add ProtocolSpec parameter to convert() method
- Use protocolSpec.getPrecompileContractRegistry().get() for
  precompile detection
- Update all tests to pass mock ProtocolSpec
- Add test coverage for null ProtocolSpec parameter

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

@lu-pinto lu-pinto left a comment

Choose a reason for hiding this comment

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

I see that you are using a post processing approach where you are collecting all traces and then filtering out/picking up the information you are interested on afterwards. I would have done an in-EVM approach to build this data while tracing. The current approach could easily blow up memory usage for transactions with large enough executions.

I have already bumped into a case on hoodi where tracing timed out with one of the blocks. To debug that block I added an in-EVM tracing option to only capture specific selected opcodes and ignore all others that I didn't need. Idea: maybe these tracer types could be implemented with that feature.

Having said that, I understand that what I ask above should not be part of this PR since it's a lot of work. Pretty much all TracerTypes are already implemented with post processing so makes sense to be consistent too. Just letting you know my thoughts about this code.


@Test
@DisplayName("should handle nested sub-calls and count multiple occurrences")
void shouldHandleNestedSubCallsAndCountMultipleOccurrences() {
Copy link
Copy Markdown
Contributor

@lu-pinto lu-pinto Jan 29, 2026

Choose a reason for hiding this comment

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

tests named with ...And... generally mean you need 2 separate tests. This test is more than 100 lines long!
I'm even for duplicating setup code that asserts different things, this way unit tests are cleaner.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

👍🏼 Refactored it into two methods.

Copy link
Copy Markdown
Contributor

@lu-pinto lu-pinto left a comment

Choose a reason for hiding this comment

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

Would have liked to see at least a test end to end showing the new tracer type working.

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

@lu-pinto I've also added spec tests generated against Geth for 4ByteTracer.

@usmansaleem usmansaleem requested a review from lu-pinto January 30, 2026 00:49
Signed-off-by: Usman Saleem <usman@usmans.info>
Copy link
Copy Markdown
Contributor

@lu-pinto lu-pinto left a comment

Choose a reason for hiding this comment

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

LGTM

@usmansaleem usmansaleem merged commit bdf5cf6 into besu-eth:main Jan 30, 2026
46 checks passed
@github-project-automation github-project-automation bot moved this from Open PRs to Done in 26.1.0 Release Jan 30, 2026
@usmansaleem usmansaleem deleted the 4byte_tracer branch January 30, 2026 11:25
macfarla pushed a commit to CPerezz/besu that referenced this pull request Feb 6, 2026
Implement 4ByteTracer in Besu


---------

Signed-off-by: Usman Saleem <usman@usmans.info>
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.

5 participants