diff --git a/CHANGELOG.md b/CHANGELOG.md index 300ba53a350..1aebc644c68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ - Fast Sync ### Additions and Improvements +- Add support for `4byteTracer` in `debug_trace*` methods to collect function selectors from internal calls via PR [#9462][9462]. Thanks to [@JukLee0ira](https://github.com/JukLee0ira). - Performance: Optimise EIP-196 AltBn128: EcAdd 33-128% faster, EcMul 8% faster [#9570](https://github.com/hyperledger/besu/pull/9570) - Performance: Improved `getBlobsV2` by disabling HTTP response compression for engine API, with up to 10× throughput improvement observed for large numbers of blobs. [#9667](https://github.com/hyperledger/besu/pull/9667) - Performance: Replace BytesTrieSet with HashSet, improves CREATE, CREATE2, SELFDESTRUCT and jumpdest analysis by up to 48% [#9641](https://github.com/hyperledger/besu/pull/9641) @@ -56,6 +57,8 @@ - Enhance payload selection with tx count and creation time tiebreakers [#9657](https://github.com/hyperledger/besu/pull/9657) - Fix mining beneficiary for BFT networks when set to zero address [#9679](https://github.com/hyperledger/besu/pull/9679) +[PR_9462]: https://github.com/hyperledger/besu/pull/9642 + ## 25.12.0 ### Breaking Changes diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceTransactionStepFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceTransactionStepFactory.java index c37e6175bcc..d04d418fd63 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceTransactionStepFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceTransactionStepFactory.java @@ -17,6 +17,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTrace; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.CallTracerResultConverter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.DebugTraceTransactionResult; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.FourByteTracerResultConverter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.OpCodeLoggerTracerResult; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.diff.StateDiffTrace; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.diff.StateTraceGenerator; @@ -85,6 +86,11 @@ public static Function create( return new DebugTraceTransactionResult( transactionTrace, new StateTraceResult(trace, diffMode)); }; + case FOUR_BYTE_TRACER -> + transactionTrace -> { + var result = FourByteTracerResultConverter.convert(transactionTrace, protocolSpec); + return new DebugTraceTransactionResult(transactionTrace, result); + }; }; } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/FourByteTracerResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/FourByteTracerResult.java new file mode 100644 index 00000000000..ba9f0a012dd --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/FourByteTracerResult.java @@ -0,0 +1,74 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results; + +import java.util.Collections; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Represents the result format for Ethereum's 4byteTracer as specified in the Geth documentation. + * + *

The 4byteTracer collects function selectors (the first 4 bytes of call data) from all calls + * made during transaction execution, along with the size of the supplied call data. This is useful + * for analyzing which contract functions were invoked during a transaction. + * + *

The JSON output is a simple map where keys are in the format + * "0x[4-byte-selector]-[calldata-size]" and values are the number of times that combination was + * called. For example: + * + *

{@code
+ * {
+ *   "0x27dc297e-128": 1,
+ *   "0x38cc4831-0": 2,
+ *   "0x524f3889-96": 1
+ * }
+ * }
+ * + * @see + * Geth 4byteTracer Documentation + */ +public class FourByteTracerResult { + + /** + * Map of function selector + call data size to occurrence count. Key format: + * "0x[4-byte-selector]-[calldata-size]" + */ + private final Map selectorCounts; + + /** + * Constructs a FourByteTracerResult with the given selector counts. + * + * @param selectorCounts map of selector-size keys to occurrence counts + */ + public FourByteTracerResult(final Map selectorCounts) { + this.selectorCounts = selectorCounts != null ? selectorCounts : Collections.emptyMap(); + } + + /** + * Gets the map of function selectors and their occurrence counts. + * + *

The {@link JsonValue} annotation causes Jackson to serialize this object directly as the + * map, without wrapping it in an object. This matches Geth's output format. + * + * @return the map of selector-size keys to occurrence counts + */ + @JsonValue + public Map getSelectorCounts() { + return selectorCounts; + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/FourByteTracerResultConverter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/FourByteTracerResultConverter.java new file mode 100644 index 00000000000..714cabd7fa3 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/FourByteTracerResultConverter.java @@ -0,0 +1,218 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results; + +import static com.google.common.base.Preconditions.checkNotNull; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTrace; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.calltrace.OpcodeCategory; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.evm.tracing.TraceFrame; + +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import org.apache.tuweni.bytes.Bytes; + +/** + * Converts Ethereum transaction traces into 4byte tracer format. + * + *

This class transforms transaction traces to collect 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). This matches Geth's 4byteTracer output + * format. + * + *

The converter processes only CALL-type operations that successfully enter: + * + *

    + *
  • CALL + *
  • CALLCODE + *
  • DELEGATECALL + *
  • STATICCALL + *
+ * + *

CREATE and CREATE2 operations are excluded, as are calls to precompiled contracts and calls + * that fail to enter. + * + *

For each qualifying call operation, it extracts the first 4 bytes of the input data (the + * function selector) and combines it with the size of the remaining call data (size - 4) to create + * a key in the format "0x[4-byte-selector]-[remaining-size]". The result is a map of these keys to + * their occurrence counts. + * + * @see + * Geth 4byteTracer Implementation + */ +public final class FourByteTracerResultConverter { + + private static final int FUNCTION_SELECTOR_LENGTH = 4; + + private FourByteTracerResultConverter() { + // Utility class - prevent instantiation + } + + /** + * Converts a transaction trace to a 4byte tracer result. + * + * @param transactionTrace The transaction trace to convert + * @param protocolSpec Protocol Spec instance related to the transaction's block + * @return A 4byte tracer result containing function selector counts + * @throws NullPointerException if transactionTrace is null + */ + public static FourByteTracerResult convert( + final TransactionTrace transactionTrace, final ProtocolSpec protocolSpec) { + checkNotNull( + transactionTrace, "FourByteTracerResultConverter requires a non-null TransactionTrace"); + checkNotNull(protocolSpec, "FourByteTracerResultConverter requires a non-null ProtocolSpec"); + + // Sort keys alphabetically to match Geth's JSON encoding behavior + final Map selectorCounts = new TreeMap<>(); + + processInitialTransaction(transactionTrace, protocolSpec, selectorCounts); + + // Process all trace frames for internal calls only (not the initial transaction) + if (transactionTrace.getTraceFrames() != null) { + processTraceFrames(transactionTrace.getTraceFrames(), selectorCounts); + } + + return new FourByteTracerResult(selectorCounts); + } + + /** + * Processes the initial transaction to extract its function selector. + * + *

This matches Geth's behavior where OnEnter fires for the transaction's entry into the + * contract, not just for internal calls. + * + * @param transactionTrace the transaction trace + * @param protocolSpec Protocol Spec instance to obtain precompile registry + * @param selectorCounts the map to update with selector counts + */ + private static void processInitialTransaction( + final TransactionTrace transactionTrace, + final ProtocolSpec protocolSpec, + final Map selectorCounts) { + + final Transaction tx = transactionTrace.getTransaction(); + + // Skip scenarios + if (tx.isContractCreation() + || tx.getTo().isEmpty() + || isTargetPrecompiledContract(protocolSpec, tx.getTo().get())) { + return; + } + + final Bytes inputData = tx.getPayload(); + + // Only process if we have at least 4 bytes for the function selector + if (inputData != null && inputData.size() >= FUNCTION_SELECTOR_LENGTH) { + final String key = createKey(inputData); + selectorCounts.merge(key, 1, Integer::sum); + } + } + + private static boolean isTargetPrecompiledContract( + final ProtocolSpec protocolSpec, final Address toAddress) { + return protocolSpec.getPrecompileContractRegistry().get(toAddress) != null; + } + + /** + * Processes all trace frames to extract function selectors from CALL operations. + * + *

This method only processes CALL, CALLCODE, DELEGATECALL, and STATICCALL operations that + * successfully enter a new execution scope. CREATE and CREATE2 operations are ignored to match + * Geth's behavior. + * + * @param frames the list of trace frames to process + * @param selectorCounts the map to update with selector counts + */ + private static void processTraceFrames( + final List frames, final Map selectorCounts) { + + for (int i = 0; i < frames.size(); i++) { + final TraceFrame frame = frames.get(i); + final String opcode = frame.getOpcode(); + + // Only process CALL-type operations (not CREATE/CREATE2) + if (OpcodeCategory.isCallOp(opcode)) { + final TraceFrame nextTrace = (i < frames.size() - 1) ? frames.get(i + 1) : null; + processCall(frame, nextTrace, selectorCounts); + } + } + } + + /** + * Processes a single CALL operation to extract its function selector. + * + *

This method: + * + *

    + *
  • Checks if the call actually entered (depth increased in next frame) + *
  • Skips calls to precompiled contracts + *
  • Extracts the input data from the entered call + *
  • Skips calls with less than 4 bytes of input (no function selector) + *
  • Creates a key in the format "0x[selector]-[size-4]" and increments its count + *
+ * + *

This matches Geth's OnEnter hook behavior which only fires when a call successfully enters a + * new execution scope. + * + * @param frame the current trace frame (the CALL instruction) + * @param nextTrace the next trace frame (first frame inside the call, may be null) + * @param selectorCounts the map to update with selector counts + */ + private static void processCall( + final TraceFrame frame, + final TraceFrame nextTrace, + final Map selectorCounts) { + + // Skip precompiled contracts + if (frame.isPrecompile()) { + return; + } + + // Check if call entered (depth increased) - matches Geth's OnEnter behavior + final boolean calleeEntered = nextTrace != null && nextTrace.getDepth() > frame.getDepth(); + if (!calleeEntered) { + return; + } + + // Get the input data from the entered call (nextTrace has the actual input data) + final Bytes inputData = nextTrace.getInputData(); + + // Only process if we have at least 4 bytes for the function selector + if (inputData != null && inputData.size() >= FUNCTION_SELECTOR_LENGTH) { + final String key = createKey(inputData); + selectorCounts.merge(key, 1, Integer::sum); + } + } + + /** + * Creates a key in the format "0x[4-byte-selector]-[size-4]" from the input data. + * + *

The size portion represents the length of the input data minus the 4-byte selector, matching + * Geth's implementation where the size is calculated as `len(input) - 4`. + * + * @param inputData the input data containing the function selector + * @return the formatted key (e.g., "0x27dc297e-128") + */ + private static String createKey(final Bytes inputData) { + final Bytes selector = inputData.slice(0, FUNCTION_SELECTOR_LENGTH); + final int remainingSize = inputData.size() - FUNCTION_SELECTOR_LENGTH; + return selector.toHexString() + "-" + remainingSize; + } +} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/DebugGethTraceJsonRpcHttpBySpecTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/DebugGethTraceJsonRpcHttpBySpecTest.java index d1ada18fb4b..22c8b94b471 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/DebugGethTraceJsonRpcHttpBySpecTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/DebugGethTraceJsonRpcHttpBySpecTest.java @@ -47,7 +47,8 @@ public static Object[][] specs() { new String[] { "debug-geth/specs/prestate-tracer/diff-mode-true", "debug-geth/specs/prestate-tracer/diff-mode-false", - "debug-geth/specs/call-tracer" + "debug-geth/specs/call-tracer", + "debug-geth/specs/4byte-tracer" }); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceTransactionStepFactoryTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceTransactionStepFactoryTest.java index 200c8f0bb10..a84eadf40af 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceTransactionStepFactoryTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceTransactionStepFactoryTest.java @@ -23,12 +23,14 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTrace; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.DebugTraceTransactionResult; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.FourByteTracerResult; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.OpCodeLoggerTracerResult; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.debug.TraceOptions; import org.hyperledger.besu.ethereum.debug.TracerType; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; +import org.hyperledger.besu.evm.precompile.PrecompileContractRegistry; import java.util.Collections; import java.util.concurrent.CompletableFuture; @@ -62,6 +64,11 @@ void setUp() { mockResult = mock(TransactionProcessingResult.class); mockProtocolSpec = mock(ProtocolSpec.class); + // Setup PrecompileContractRegistry for FourByteTracer + PrecompileContractRegistry mockRegistry = mock(PrecompileContractRegistry.class); + when(mockProtocolSpec.getPrecompileContractRegistry()).thenReturn(mockRegistry); + when(mockRegistry.get(org.mockito.ArgumentMatchers.any(Address.class))).thenReturn(null); + // Set up transaction hash chain when(mockTransactionTrace.getTransaction()).thenReturn(mockTransaction); when(mockTransaction.getHash()).thenReturn(mockHash); @@ -97,6 +104,24 @@ void shouldCreateFunctionForOpcodeTracer() { assertThat(result.getResult()).isInstanceOf(OpCodeLoggerTracerResult.class); } + @Test + @DisplayName("should create function for FOUR_BYTE_TRACER that returns FourByteTracerResult") + void shouldCreateFunctionForFourByteTracer() { + // Given + TracerType tracerType = TracerType.FOUR_BYTE_TRACER; + TraceOptions traceOptions = new TraceOptions(tracerType, null, null); + Function function = + DebugTraceTransactionStepFactory.create(traceOptions, mockProtocolSpec); + + // When + DebugTraceTransactionResult result = function.apply(mockTransactionTrace); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getTxHash()).isEqualTo(EXPECTED_HASH); + assertThat(result.getResult()).isInstanceOf(FourByteTracerResult.class); + } + @ParameterizedTest @EnumSource( value = TracerType.class, diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/FourByteTracerResultConverterTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/FourByteTracerResultConverterTest.java new file mode 100644 index 00000000000..1f5f58c60bf --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/FourByteTracerResultConverterTest.java @@ -0,0 +1,633 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTrace; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.evm.precompile.PrecompileContractRegistry; +import org.hyperledger.besu.evm.precompile.PrecompiledContract; +import org.hyperledger.besu.evm.tracing.TraceFrame; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class FourByteTracerResultConverterTest { + + // Helper methods for creating mocks + + /** + * Creates a mock TransactionTrace with a contract creation transaction (to skip initial + * transaction processing in most tests) + */ + private TransactionTrace createMockTraceWithContractCreation() { + TransactionTrace mockTrace = mock(TransactionTrace.class); + Transaction mockTx = mock(Transaction.class); + when(mockTx.isContractCreation()).thenReturn(true); + when(mockTrace.getTransaction()).thenReturn(mockTx); + return mockTrace; + } + + /** + * Creates a mock TransactionTrace with a regular contract call transaction + * + * @param to The recipient address + * @param payload The transaction input data + */ + private TransactionTrace createMockTraceWithContractCall(final Address to, final Bytes payload) { + TransactionTrace mockTrace = mock(TransactionTrace.class); + Transaction mockTx = mock(Transaction.class); + when(mockTx.isContractCreation()).thenReturn(false); + when(mockTx.getTo()).thenReturn(Optional.of(to)); + when(mockTx.getPayload()).thenReturn(payload); + when(mockTrace.getTransaction()).thenReturn(mockTx); + return mockTrace; + } + + private TraceFrame createCallFrame(final String opcode, final int depth) { + TraceFrame frame = mock(TraceFrame.class); + when(frame.getOpcode()).thenReturn(opcode); + when(frame.getDepth()).thenReturn(depth); + when(frame.isPrecompile()).thenReturn(false); + return frame; + } + + private TraceFrame createNextFrame(final int depth, final String inputDataHex) { + TraceFrame frame = mock(TraceFrame.class); + when(frame.getDepth()).thenReturn(depth); + when(frame.getInputData()).thenReturn(Bytes.fromHexString(inputDataHex)); + return frame; + } + + private TraceFrame createInstructionFrame(final String opcode, final int depth) { + TraceFrame frame = mock(TraceFrame.class); + when(frame.getOpcode()).thenReturn(opcode); + when(frame.getDepth()).thenReturn(depth); + return frame; + } + + /** + * Creates a mock ProtocolSpec with a PrecompileContractRegistry that returns null for all + * addresses (i.e., no precompiles). Tests that need to verify precompile behavior should mock + * specific addresses. + */ + private ProtocolSpec createMockProtocolSpec() { + ProtocolSpec mockSpec = mock(ProtocolSpec.class); + PrecompileContractRegistry mockRegistry = mock(PrecompileContractRegistry.class); + when(mockSpec.getPrecompileContractRegistry()).thenReturn(mockRegistry); + when(mockRegistry.get(org.mockito.ArgumentMatchers.any(Address.class))).thenReturn(null); + return mockSpec; + } + + @Test + @DisplayName("should return empty result when trace frames are null") + void shouldReturnEmptyResultWhenTraceFramesAreNull() { + // Given + TransactionTrace mockTrace = createMockTraceWithContractCreation(); + when(mockTrace.getTraceFrames()).thenReturn(null); + ProtocolSpec mockSpec = createMockProtocolSpec(); + + // When + FourByteTracerResult result = FourByteTracerResultConverter.convert(mockTrace, mockSpec); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getSelectorCounts()).isEmpty(); + } + + @Test + @DisplayName("should return empty result when trace frames are empty") + void shouldReturnEmptyResultWhenTraceFramesAreEmpty() { + // Given + TransactionTrace mockTrace = createMockTraceWithContractCreation(); + when(mockTrace.getTraceFrames()).thenReturn(List.of()); + ProtocolSpec mockSpec = createMockProtocolSpec(); + + // When + FourByteTracerResult result = FourByteTracerResultConverter.convert(mockTrace, mockSpec); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getSelectorCounts()).isEmpty(); + } + + @Test + @DisplayName("should count function selector from CALL operation that enters") + void shouldCountFunctionSelectorFromCallOperationThatEnters() { + // Given + TransactionTrace mockTrace = createMockTraceWithContractCreation(); + TraceFrame callFrame = mock(TraceFrame.class); + TraceFrame nextFrame = mock(TraceFrame.class); + + // Setup CALL frame + when(callFrame.getOpcode()).thenReturn("CALL"); + when(callFrame.getDepth()).thenReturn(1); + when(callFrame.isPrecompile()).thenReturn(false); + + // Setup next frame (call entered) + when(nextFrame.getDepth()).thenReturn(2); + // Function selector: 0x12345678, plus 96 bytes of data = 100 bytes total + Bytes inputData = Bytes.fromHexString("0x" + "12345678" + "00".repeat(96)); + when(nextFrame.getInputData()).thenReturn(inputData); + + when(mockTrace.getTraceFrames()).thenReturn(List.of(callFrame, nextFrame)); + ProtocolSpec mockSpec = createMockProtocolSpec(); + + // When + FourByteTracerResult result = FourByteTracerResultConverter.convert(mockTrace, mockSpec); + + // Then + assertThat(result).isNotNull(); + Map counts = result.getSelectorCounts(); + assertThat(counts).hasSize(1); + assertThat(counts).containsEntry("0x12345678-96", 1); + } + + @Test + @DisplayName("should skip CALL operation that does not enter") + void shouldSkipCallOperationThatDoesNotEnter() { + // Given + TransactionTrace mockTrace = createMockTraceWithContractCreation(); + TraceFrame callFrame = mock(TraceFrame.class); + TraceFrame nextFrame = mock(TraceFrame.class); + + // Setup CALL frame + when(callFrame.getOpcode()).thenReturn("CALL"); + when(callFrame.getDepth()).thenReturn(1); + when(callFrame.isPrecompile()).thenReturn(false); + + // Setup next frame (call did NOT enter - same depth) + when(nextFrame.getDepth()).thenReturn(1); + + when(mockTrace.getTraceFrames()).thenReturn(List.of(callFrame, nextFrame)); + ProtocolSpec mockSpec = createMockProtocolSpec(); + + // When + FourByteTracerResult result = FourByteTracerResultConverter.convert(mockTrace, mockSpec); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getSelectorCounts()).isEmpty(); + } + + @Test + @DisplayName("should skip precompiled contracts") + void shouldSkipPrecompiledContracts() { + // Given + TransactionTrace mockTrace = createMockTraceWithContractCreation(); + TraceFrame callFrame = mock(TraceFrame.class); + TraceFrame nextFrame = mock(TraceFrame.class); + + // Setup CALL frame for precompile + when(callFrame.getOpcode()).thenReturn("CALL"); + when(callFrame.getDepth()).thenReturn(1); + when(callFrame.isPrecompile()).thenReturn(true); // Precompile + + // Setup next frame + when(nextFrame.getDepth()).thenReturn(2); + when(nextFrame.getInputData()).thenReturn(Bytes.fromHexString("0x12345678")); + + when(mockTrace.getTraceFrames()).thenReturn(List.of(callFrame, nextFrame)); + ProtocolSpec mockSpec = createMockProtocolSpec(); + + // When + FourByteTracerResult result = FourByteTracerResultConverter.convert(mockTrace, mockSpec); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getSelectorCounts()).isEmpty(); + } + + @Test + @DisplayName("should skip calls with less than 4 bytes of input") + void shouldSkipCallsWithLessThanFourBytesOfInput() { + // Given + TransactionTrace mockTrace = createMockTraceWithContractCreation(); + TraceFrame callFrame = mock(TraceFrame.class); + TraceFrame nextFrame = mock(TraceFrame.class); + + // Setup CALL frame + when(callFrame.getOpcode()).thenReturn("CALL"); + when(callFrame.getDepth()).thenReturn(1); + when(callFrame.isPrecompile()).thenReturn(false); + + // Setup next frame with only 2 bytes of input + when(nextFrame.getDepth()).thenReturn(2); + when(nextFrame.getInputData()).thenReturn(Bytes.fromHexString("0x1234")); + + when(mockTrace.getTraceFrames()).thenReturn(List.of(callFrame, nextFrame)); + ProtocolSpec mockSpec = createMockProtocolSpec(); + + // When + FourByteTracerResult result = FourByteTracerResultConverter.convert(mockTrace, mockSpec); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getSelectorCounts()).isEmpty(); + } + + @Test + @DisplayName("should skip CREATE operations") + void shouldSkipCreateOperations() { + // Given + TransactionTrace mockTrace = createMockTraceWithContractCreation(); + TraceFrame createFrame = mock(TraceFrame.class); + TraceFrame nextFrame = mock(TraceFrame.class); + + // Setup CREATE frame + when(createFrame.getOpcode()).thenReturn("CREATE"); + when(createFrame.getDepth()).thenReturn(1); + + // Setup next frame + when(nextFrame.getDepth()).thenReturn(2); + when(nextFrame.getInputData()).thenReturn(Bytes.fromHexString("0x12345678")); + + when(mockTrace.getTraceFrames()).thenReturn(List.of(createFrame, nextFrame)); + ProtocolSpec mockSpec = createMockProtocolSpec(); + + // When + FourByteTracerResult result = FourByteTracerResultConverter.convert(mockTrace, mockSpec); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getSelectorCounts()).isEmpty(); + } + + @Test + @DisplayName("should count multiple calls with same selector") + void shouldCountMultipleCallsWithSameSelector() { + // Given + TransactionTrace mockTrace = createMockTraceWithContractCreation(); + + // First CALL + TraceFrame callFrame1 = mock(TraceFrame.class); + when(callFrame1.getOpcode()).thenReturn("CALL"); + when(callFrame1.getDepth()).thenReturn(1); + when(callFrame1.isPrecompile()).thenReturn(false); + + TraceFrame nextFrame1 = mock(TraceFrame.class); + when(nextFrame1.getDepth()).thenReturn(2); + when(nextFrame1.getInputData()).thenReturn(Bytes.fromHexString("0x12345678" + "00".repeat(32))); + + // Some other frame to separate the calls + TraceFrame otherFrame = mock(TraceFrame.class); + when(otherFrame.getOpcode()).thenReturn("ADD"); + when(otherFrame.getDepth()).thenReturn(1); + + // Second CALL with same selector + TraceFrame callFrame2 = mock(TraceFrame.class); + when(callFrame2.getOpcode()).thenReturn("DELEGATECALL"); + when(callFrame2.getDepth()).thenReturn(1); + when(callFrame2.isPrecompile()).thenReturn(false); + + TraceFrame nextFrame2 = mock(TraceFrame.class); + when(nextFrame2.getDepth()).thenReturn(2); + when(nextFrame2.getInputData()).thenReturn(Bytes.fromHexString("0x12345678" + "00".repeat(32))); + + when(mockTrace.getTraceFrames()) + .thenReturn(List.of(callFrame1, nextFrame1, otherFrame, callFrame2, nextFrame2)); + ProtocolSpec mockSpec = createMockProtocolSpec(); + + // When + FourByteTracerResult result = FourByteTracerResultConverter.convert(mockTrace, mockSpec); + + // Then + assertThat(result).isNotNull(); + Map counts = result.getSelectorCounts(); + assertThat(counts).hasSize(1); + assertThat(counts).containsEntry("0x12345678-32", 2); + } + + @Test + @DisplayName("should count different selectors separately") + void shouldCountDifferentSelectorsSeparately() { + // Given + TransactionTrace mockTrace = createMockTraceWithContractCreation(); + + // First CALL + TraceFrame callFrame1 = createCallFrame("CALL", 1); + TraceFrame nextFrame1 = createNextFrame(2, "0x12345678" + "00".repeat(32)); + + // Second CALL with different selector + TraceFrame callFrame2 = createCallFrame("STATICCALL", 1); + TraceFrame nextFrame2 = createNextFrame(2, "0xabcdef01" + "00".repeat(64)); + + when(mockTrace.getTraceFrames()) + .thenReturn(List.of(callFrame1, nextFrame1, callFrame2, nextFrame2)); + ProtocolSpec mockSpec = createMockProtocolSpec(); + + // When + FourByteTracerResult result = FourByteTracerResultConverter.convert(mockTrace, mockSpec); + + // Then + assertThat(result).isNotNull(); + Map counts = result.getSelectorCounts(); + assertThat(counts).hasSize(2); + assertThat(counts).containsEntry("0x12345678-32", 1); + assertThat(counts).containsEntry("0xabcdef01-64", 1); + } + + @Test + @DisplayName("should handle all CALL-type operations") + void shouldHandleAllCallTypeOperations() { + // Given + TransactionTrace mockTrace = createMockTraceWithContractCreation(); + + TraceFrame callFrame = createCallFrame("CALL", 1); + TraceFrame callNextFrame = createNextFrame(2, "0x11111111" + "00".repeat(10)); + + TraceFrame callcodeFrame = createCallFrame("CALLCODE", 1); + TraceFrame callcodeNextFrame = createNextFrame(2, "0x22222222" + "00".repeat(20)); + + TraceFrame delegatecallFrame = createCallFrame("DELEGATECALL", 1); + TraceFrame delegatecallNextFrame = createNextFrame(2, "0x33333333" + "00".repeat(30)); + + TraceFrame staticcallFrame = createCallFrame("STATICCALL", 1); + TraceFrame staticcallNextFrame = createNextFrame(2, "0x44444444" + "00".repeat(40)); + + when(mockTrace.getTraceFrames()) + .thenReturn( + Arrays.asList( + callFrame, + callNextFrame, + callcodeFrame, + callcodeNextFrame, + delegatecallFrame, + delegatecallNextFrame, + staticcallFrame, + staticcallNextFrame)); + ProtocolSpec mockSpec = createMockProtocolSpec(); + + // When + FourByteTracerResult result = FourByteTracerResultConverter.convert(mockTrace, mockSpec); + + // Then + assertThat(result).isNotNull(); + Map counts = result.getSelectorCounts(); + assertThat(counts).hasSize(4); + assertThat(counts).containsEntry("0x11111111-10", 1); + assertThat(counts).containsEntry("0x22222222-20", 1); + assertThat(counts).containsEntry("0x33333333-30", 1); + assertThat(counts).containsEntry("0x44444444-40", 1); + } + + @Test + @DisplayName("should handle exactly 4 bytes of input") + void shouldHandleExactlyFourBytesOfInput() { + // Given + TransactionTrace mockTrace = createMockTraceWithContractCreation(); + TraceFrame callFrame = createCallFrame("CALL", 1); + TraceFrame nextFrame = createNextFrame(2, "0x12345678"); + + when(mockTrace.getTraceFrames()).thenReturn(List.of(callFrame, nextFrame)); + ProtocolSpec mockSpec = createMockProtocolSpec(); + + // When + FourByteTracerResult result = FourByteTracerResultConverter.convert(mockTrace, mockSpec); + + // Then + assertThat(result).isNotNull(); + Map counts = result.getSelectorCounts(); + assertThat(counts).hasSize(1); + assertThat(counts).containsEntry("0x12345678-0", 1); + } + + @Test + @DisplayName("should handle nested sub-calls at different depths") + void shouldHandleNestedSubCalls() { + // Given: method-A (depth 1) calls method-B (depth 2) which calls method-C (depth 3) + TransactionTrace mockTrace = createMockTraceWithContractCreation(); + + // Initial call to method-A + TraceFrame callA = createCallFrame("CALL", 0); + TraceFrame insideA = createNextFrame(1, "0xAAAAAAAA" + "00".repeat(32)); + TraceFrame instructionInA = createInstructionFrame("ADD", 1); + + // method-A calls method-B + TraceFrame callB = createCallFrame("CALL", 1); + TraceFrame insideB = createNextFrame(2, "0xBBBBBBBB" + "00".repeat(64)); + TraceFrame instructionInB = createInstructionFrame("MUL", 2); + + // method-B calls method-C + TraceFrame callC = createCallFrame("CALL", 2); + TraceFrame insideC = createNextFrame(3, "0xCCCCCCCC" + "00".repeat(96)); + TraceFrame instructionInC = createInstructionFrame("SSTORE", 3); + + when(mockTrace.getTraceFrames()) + .thenReturn( + List.of( + callA, + insideA, + instructionInA, // method-A + callB, + insideB, + instructionInB, // method-B + callC, + insideC, + instructionInC)); // method-C + ProtocolSpec mockSpec = createMockProtocolSpec(); + + // When + FourByteTracerResult result = FourByteTracerResultConverter.convert(mockTrace, mockSpec); + + // Then + assertThat(result).isNotNull(); + Map counts = result.getSelectorCounts(); + assertThat(counts).hasSize(3); + assertThat(counts).containsEntry("0xaaaaaaaa-32", 1); // method-A called once + assertThat(counts).containsEntry("0xbbbbbbbb-64", 1); // method-B called once + assertThat(counts).containsEntry("0xcccccccc-96", 1); // method-C called once + } + + @Test + @DisplayName("should count multiple occurrences of same selector in nested calls") + void shouldCountMultipleOccurrencesInNestedCalls() { + // Given: method-A calls method-B, which calls method-C twice + TransactionTrace mockTrace = createMockTraceWithContractCreation(); + + // Initial call to method-A + TraceFrame callA = createCallFrame("CALL", 0); + TraceFrame insideA = createNextFrame(1, "0xAAAAAAAA" + "00".repeat(32)); + + // method-A calls method-B + TraceFrame callB = createCallFrame("CALL", 1); + TraceFrame insideB = createNextFrame(2, "0xBBBBBBBB" + "00".repeat(64)); + TraceFrame instructionInB1 = createInstructionFrame("MUL", 2); + + // method-B calls method-C (FIRST TIME) + TraceFrame callC1 = createCallFrame("CALL", 2); + TraceFrame insideC1 = createNextFrame(3, "0xCCCCCCCC" + "00".repeat(96)); + TraceFrame instructionInC1 = createInstructionFrame("SSTORE", 3); + + // Back in method-B, more instructions + TraceFrame instructionInB2 = createInstructionFrame("SUB", 2); + + // method-B calls method-C (SECOND TIME) + TraceFrame callC2 = createCallFrame("CALL", 2); + TraceFrame insideC2 = createNextFrame(3, "0xCCCCCCCC" + "00".repeat(96)); + TraceFrame instructionInC2 = createInstructionFrame("SLOAD", 3); + + when(mockTrace.getTraceFrames()) + .thenReturn( + List.of( + callA, + insideA, + callB, + insideB, + instructionInB1, // method-B + callC1, + insideC1, + instructionInC1, // method-C (first call) + instructionInB2, // back in method-B + callC2, + insideC2, + instructionInC2)); // method-C (second call) + ProtocolSpec mockSpec = createMockProtocolSpec(); + + // When + FourByteTracerResult result = FourByteTracerResultConverter.convert(mockTrace, mockSpec); + + // Then + assertThat(result).isNotNull(); + Map counts = result.getSelectorCounts(); + assertThat(counts).hasSize(3); + assertThat(counts).containsEntry("0xaaaaaaaa-32", 1); // method-A called once + assertThat(counts).containsEntry("0xbbbbbbbb-64", 1); // method-B called once + assertThat(counts).containsEntry("0xcccccccc-96", 2); // method-C called TWICE + } + + @Test + @DisplayName("should capture function selector from initial transaction") + void shouldCaptureFunctionSelectorFromInitialTransaction() { + // Given: A transaction calling a contract function (no internal calls) + Address contractAddress = Address.fromHexString("0x1234567890123456789012345678901234567890"); + Bytes inputData = Bytes.fromHexString("0x55241077" + "00".repeat(32)); // setValue(uint256) + + TransactionTrace mockTrace = createMockTraceWithContractCall(contractAddress, inputData); + when(mockTrace.getTraceFrames()).thenReturn(List.of()); // No internal calls + ProtocolSpec mockSpec = createMockProtocolSpec(); + + // When + FourByteTracerResult result = FourByteTracerResultConverter.convert(mockTrace, mockSpec); + + // Then + assertThat(result).isNotNull(); + Map counts = result.getSelectorCounts(); + assertThat(counts).hasSize(1); + assertThat(counts).containsEntry("0x55241077-32", 1); + } + + @Test + @DisplayName("should capture both initial transaction and internal calls") + void shouldCaptureBothInitialTransactionAndInternalCalls() { + // Given: Initial transaction calls contract, which then makes an internal call + Address contractAddress = Address.fromHexString("0x1234567890123456789012345678901234567890"); + Bytes initialInput = Bytes.fromHexString("0xaaaaaaaa" + "00".repeat(32)); + + TransactionTrace mockTrace = createMockTraceWithContractCall(contractAddress, initialInput); + + // Internal CALL + TraceFrame callFrame = createCallFrame("CALL", 1); + TraceFrame nextFrame = createNextFrame(2, "0xbbbbbbbb" + "00".repeat(64)); + + when(mockTrace.getTraceFrames()).thenReturn(List.of(callFrame, nextFrame)); + ProtocolSpec mockSpec = createMockProtocolSpec(); + + // When + FourByteTracerResult result = FourByteTracerResultConverter.convert(mockTrace, mockSpec); + + // Then + assertThat(result).isNotNull(); + Map counts = result.getSelectorCounts(); + assertThat(counts).hasSize(2); + assertThat(counts).containsEntry("0xaaaaaaaa-32", 1); // Initial transaction + assertThat(counts).containsEntry("0xbbbbbbbb-64", 1); // Internal call + } + + @Test + @DisplayName("should skip initial transaction if it's a contract creation") + void shouldSkipInitialTransactionIfContractCreation() { + // Given: Contract creation transaction + TransactionTrace mockTrace = createMockTraceWithContractCreation(); + when(mockTrace.getTraceFrames()).thenReturn(List.of()); + ProtocolSpec mockSpec = createMockProtocolSpec(); + + // When + FourByteTracerResult result = FourByteTracerResultConverter.convert(mockTrace, mockSpec); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getSelectorCounts()).isEmpty(); + } + + @Test + @DisplayName("should skip initial transaction with less than 4 bytes") + void shouldSkipInitialTransactionWithLessThanFourBytes() { + // Given: Transaction with only 2 bytes of input + Address contractAddress = Address.fromHexString("0x1234567890123456789012345678901234567890"); + Bytes inputData = Bytes.fromHexString("0x1234"); + + TransactionTrace mockTrace = createMockTraceWithContractCall(contractAddress, inputData); + when(mockTrace.getTraceFrames()).thenReturn(List.of()); + ProtocolSpec mockSpec = createMockProtocolSpec(); + + // When + FourByteTracerResult result = FourByteTracerResultConverter.convert(mockTrace, mockSpec); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getSelectorCounts()).isEmpty(); + } + + @Test + @DisplayName("should skip initial transaction to precompiled contract") + void shouldSkipInitialTransactionToPrecompiledContract() { + // Given: Transaction calling precompile (address 0x01) + Address precompileAddress = Address.fromHexString("0x0000000000000000000000000000000000000001"); + Bytes inputData = Bytes.fromHexString("0x12345678" + "00".repeat(32)); + + TransactionTrace mockTrace = createMockTraceWithContractCall(precompileAddress, inputData); + when(mockTrace.getTraceFrames()).thenReturn(List.of()); + + // Mock ProtocolSpec to recognize the precompile address + ProtocolSpec mockSpec = mock(ProtocolSpec.class); + PrecompileContractRegistry mockRegistry = mock(PrecompileContractRegistry.class); + when(mockSpec.getPrecompileContractRegistry()).thenReturn(mockRegistry); + // Return a non-null object for the precompile address to indicate it's a precompile + when(mockRegistry.get(precompileAddress)).thenReturn(mock(PrecompiledContract.class)); + // Return null for all other addresses + when(mockRegistry.get( + org.mockito.ArgumentMatchers.argThat(addr -> !addr.equals(precompileAddress)))) + .thenReturn(null); + + // When + FourByteTracerResult result = FourByteTracerResultConverter.convert(mockTrace, mockSpec); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getSelectorCounts()).isEmpty(); + } +} diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/0-debug-4byte-tracer-0x0-genesis.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/0-debug-4byte-tracer-0x0-genesis.json new file mode 100644 index 00000000000..e4c48740d41 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/0-debug-4byte-tracer-0x0-genesis.json @@ -0,0 +1,22 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x0", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "error": { + "code": -32000, + "message": "genesis is not traceable" + } + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/1-debug-4byte-tracer-0x1-empty.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/1-debug-4byte-tracer-0x1-empty.json new file mode 100644 index 00000000000..21f6a213d3a --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/1-debug-4byte-tracer-0x1-empty.json @@ -0,0 +1,19 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x1", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/10-debug-4byte-tracer-0xa-callcode-one-level.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/10-debug-4byte-tracer-0xa-callcode-one-level.json new file mode 100644 index 00000000000..2090ba00f78 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/10-debug-4byte-tracer-0xa-callcode-one-level.json @@ -0,0 +1,27 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0xa", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0x4ec95b7de430b61fc9a57ed35274fd766b7f5fac5213ab946963eb528deae6b5", + "result": { + "0x00000000-60": 1, + "0xf0000000-28": 1 + } + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/11-debug-4byte-tracer-0xb-delegate-call-one-level-deep.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/11-debug-4byte-tracer-0xb-delegate-call-one-level-deep.json new file mode 100644 index 00000000000..1e827afc30c --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/11-debug-4byte-tracer-0xb-delegate-call-one-level-deep.json @@ -0,0 +1,27 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0xb", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0x6f77512ee9d43474a884c0703c86712fb98dca772fa6e12252786e3e23f196c1", + "result": { + "0x00000000-60": 1, + "0xf0000000-28": 1 + } + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/12-debug-4byte-tracer-0xc-sequence-memory.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/12-debug-4byte-tracer-0xc-sequence-memory.json new file mode 100644 index 00000000000..845c1a8dae3 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/12-debug-4byte-tracer-0xc-sequence-memory.json @@ -0,0 +1,24 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0xc", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0x12e4a98e63825852a69d7702202a3b593e4059ec913c479443665d590da18724", + "result": {} + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/13-debug-4byte-tracer-0xd-MSTORE.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/13-debug-4byte-tracer-0xd-MSTORE.json new file mode 100644 index 00000000000..c687139ee08 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/13-debug-4byte-tracer-0xd-MSTORE.json @@ -0,0 +1,24 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0xd", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0x547c7e2fcdf9f88b03b4f4184d667d6768c669f279785774d0cf42435cab06f1", + "result": {} + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/14-debug-4byte-tracer-0xe-increment-storage.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/14-debug-4byte-tracer-0xe-increment-storage.json new file mode 100644 index 00000000000..13518a1f6ed --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/14-debug-4byte-tracer-0xe-increment-storage.json @@ -0,0 +1,32 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0xe", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0xe3ebc73c55176c3f2e72b289f8a9cefbcd5b25dcd205db5661f81b0bb974fa73", + "result": {} + }, + { + "txHash": "0xb4dc55ca4a7c1f72402860c594efaa7da32035003f8e203b7f6a3cf1826685a5", + "result": {} + }, + { + "txHash": "0x1a9f6256886437cfa21ee7685dd7a996024bea4d4bf6554b14787a6c9e5b90e6", + "result": {} + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/15-debug-4byte-tracer-0xf-logs.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/15-debug-4byte-tracer-0xf-logs.json new file mode 100644 index 00000000000..758b35a23aa --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/15-debug-4byte-tracer-0xf-logs.json @@ -0,0 +1,28 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0xf", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0x293cefc718137a74d36a5da204e3617604a9e8dfc79b6a6efb11056b3efd3b4a", + "result": {} + }, + { + "txHash": "0xb19370be2ace59794ee9bb5a69db98464cd6cb0b02b9afb3b978c9aad2bf5417", + "result": {} + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/16-debug-4byte-tracer-0x10-halts.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/16-debug-4byte-tracer-0x10-halts.json new file mode 100644 index 00000000000..33aa95c63b9 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/16-debug-4byte-tracer-0x10-halts.json @@ -0,0 +1,32 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x10", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0x6aed559a5dcf73efa132d8182585c40fdc6ef3b99a7629d80ffd4a62cb3db622", + "result": {} + }, + { + "txHash": "0xb39fa8a1cb1db4663dc3733bc014d524e90a862bf2607e028eeb3932d92574fe", + "result": {} + }, + { + "txHash": "0x11a725053a6ece79d8de11cb75dd1f09c40dcd109a1c63573e847ad4f456a296", + "result": {} + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/17-debug-4byte-tracer-0x11-push-swap.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/17-debug-4byte-tracer-0x11-push-swap.json new file mode 100644 index 00000000000..9b73fb86681 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/17-debug-4byte-tracer-0x11-push-swap.json @@ -0,0 +1,24 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x11", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0x93d974a14c784be231a7b580a6476a330e5d6f9ddfef70a6b5cacd9f91a1e495", + "result": {} + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/18-debug-4byte-tracer-0x12-memory-read-revert.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/18-debug-4byte-tracer-0x12-memory-read-revert.json new file mode 100644 index 00000000000..cdb5b02c740 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/18-debug-4byte-tracer-0x12-memory-read-revert.json @@ -0,0 +1,28 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x12", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0x2a5079cc535c429f668f13a7fb9a28bdba6831b5462bd04f781777b332a8fcbd", + "result": {} + }, + { + "txHash": "0xc388baa0e55e6b73e850b22dc7e9853700f6b995fd55d95dd6ccd5a13d63c566", + "result": {} + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/19-debug-4byte-tracer-0x13-self-destruct.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/19-debug-4byte-tracer-0x13-self-destruct.json new file mode 100644 index 00000000000..d0b5ae521f8 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/19-debug-4byte-tracer-0x13-self-destruct.json @@ -0,0 +1,44 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x13", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0x1309b6d2187aa8b0dfe78fcf0a96d4a3e861bfbc381959d253ede57624a37f9b", + "result": {} + }, + { + "txHash": "0x6b9b967cfbeedeb7f0f4956b8103075ddfcea26c01d6d5dc3f9e2ed2ec9c42c0", + "result": {} + }, + { + "txHash": "0xf12fd37f0836bd51f21bc15aa6bf5bea4b62fbbd39e1ee06725f91ac13ebc904", + "result": {} + }, + { + "txHash": "0x4c253746668dca6ac3f7b9bc18248b558a95b5fc881d140872c2dff984d344a7", + "result": {} + }, + { + "txHash": "0x821ca63d171c5a3c60d32a738803092a52562056db3727a175f659cf49aae283", + "result": {} + }, + { + "txHash": "0xb795475e8f1820b683b60eb1a366bdc23e29f9cdf9639f7768c6644d20e3bbd1", + "result": {} + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/2-debug-4byte-tracer-0x2-simple-transfer.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/2-debug-4byte-tracer-0x2-simple-transfer.json new file mode 100644 index 00000000000..56deeda4184 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/2-debug-4byte-tracer-0x2-simple-transfer.json @@ -0,0 +1,24 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x2", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0x28fa8042c7b5835f4f91fc20937f3e70dcf3585c1afe31202bb6075185f9abfe", + "result": {} + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/20-debug-4byte-tracer-0x14-create-create2.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/20-debug-4byte-tracer-0x14-create-create2.json new file mode 100644 index 00000000000..71844bac215 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/20-debug-4byte-tracer-0x14-create-create2.json @@ -0,0 +1,28 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x14", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0xd32c538fd1b3ef854c04ae39925dc9e849b568b92ea388f778b794851ab37f7c", + "result": {} + }, + { + "txHash": "0xaf151fc10eb21e76efb0bc74da179a674083e420280fb2985f76b0326682864a", + "result": {} + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/21-debug-4byte-tracer-0x15-set-and-clean-storage.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/21-debug-4byte-tracer-0x15-set-and-clean-storage.json new file mode 100644 index 00000000000..1750ab4797e --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/21-debug-4byte-tracer-0x15-set-and-clean-storage.json @@ -0,0 +1,48 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x15", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0x7ab106c58a1c4d5c5f545f94ad266a945e3ed82835528fd3adbf7973d2c0c953", + "result": { + "0x00000000-124": 1, + "0x00000000-156": 1 + } + }, + { + "txHash": "0x2b701e0b45e80f1c179a3696786aff2a411a51b03153528d1579f20915def5e4", + "result": { + "0x00000000-124": 1, + "0x00000000-156": 1 + } + }, + { + "txHash": "0xcc81ab56669a97c5debfff3e23ab5ffb366f04c8c7eaf83f259c975c1816cb41", + "result": { + "0x00000000-124": 1, + "0x00000000-156": 1 + } + }, + { + "txHash": "0x422edfe209e8b97f77a8e879db5182158f23e823ba7f59725d463d99d7ae66bd", + "result": { + "0x00000000-124": 1, + "0x00000000-156": 1 + } + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/22-debug-4byte-tracer-0x16-set-and-clean-storage.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/22-debug-4byte-tracer-0x16-set-and-clean-storage.json new file mode 100644 index 00000000000..acf6787120f --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/22-debug-4byte-tracer-0x16-set-and-clean-storage.json @@ -0,0 +1,48 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x16", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0x1f74ac428df5427f3a5576869e870cfff6712e4cffb1506bb4ef8f36c5e48162", + "result": { + "0x00000000-124": 1, + "0x00000000-156": 1 + } + }, + { + "txHash": "0x43d6ba1caeced03b6fa8cc3549c0557eea52917f1de50f4da23c9642beca95ee", + "result": { + "0x00000000-124": 1, + "0x00000000-156": 1 + } + }, + { + "txHash": "0x9eb50f31fc1d953e27331cd923f6b2f7fa11827d399c70aec00a04cf98cfd2ac", + "result": { + "0x00000000-124": 1, + "0x00000000-156": 1 + } + }, + { + "txHash": "0xe2ef09a0b71f50947bd0bd1cacac77903d2a5fce80f34862bd4559727e0f608c", + "result": { + "0x00000000-124": 1, + "0x00000000-156": 1 + } + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/23-debug-4byte-tracer-0x17-static-call-one-level-deep.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/23-debug-4byte-tracer-0x17-static-call-one-level-deep.json new file mode 100644 index 00000000000..fef3acce3ca --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/23-debug-4byte-tracer-0x17-static-call-one-level-deep.json @@ -0,0 +1,26 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x17", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0xea357b8947f2aa968eba99ac995f07dbd5d006dc2eb4c2f1a2d4f4278a150373", + "result": { + "0x00000000-144": 1 + } + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/24-debug-4byte-tracer-0x18-static-call-multiple-level-deeep.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/24-debug-4byte-tracer-0x18-static-call-multiple-level-deeep.json new file mode 100644 index 00000000000..bbc5ac55534 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/24-debug-4byte-tracer-0x18-static-call-multiple-level-deeep.json @@ -0,0 +1,28 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x18", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0x2f92ad4d4433cd8f78214c1ba3b3d3d8a2b1e993b6cf336eb9200c8d9ac3b94d", + "result": { + "0x00000000-144": 1, + "0x00000000-176": 1, + "0x00000000-208": 1 + } + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/25-debug-4byte-tracer-0x19-erc20-contract-transfer.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/25-debug-4byte-tracer-0x19-erc20-contract-transfer.json new file mode 100644 index 00000000000..138ec26fefc --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/25-debug-4byte-tracer-0x19-erc20-contract-transfer.json @@ -0,0 +1,30 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x19", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0xed9852eaa132d91f807e45dccd8d6859560f427637f9f4e3fc5741db997eefb0", + "result": {} + }, + { + "txHash": "0x752c025084f7cd0dae7fc6193db319e6931873780b3ee8c6f5835515c9c24110", + "result": { + "0xa9059cbb-64": 1 + } + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/26-debug-4byte-tracer-0x1a-call-one-level-gas-refund.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/26-debug-4byte-tracer-0x1a-call-one-level-gas-refund.json new file mode 100644 index 00000000000..262a7781584 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/26-debug-4byte-tracer-0x1a-call-one-level-gas-refund.json @@ -0,0 +1,27 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x1a", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0x897ebf0a704b9e29de555e115b474cb212a7d217073452eee02bc47506b0622c", + "result": { + "0x00000000-124": 1, + "0x00000000-156": 1 + } + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/27-debug-4byte-tracer-0x1b-self-destruct-send-self.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/27-debug-4byte-tracer-0x1b-self-destruct-send-self.json new file mode 100644 index 00000000000..c2dd75b25f2 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/27-debug-4byte-tracer-0x1b-self-destruct-send-self.json @@ -0,0 +1,26 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x1b", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0x728abdc65550943b21eba1eef7bb9c7b594d6f629dda1be4ae01b0f3ffcc6b16", + "result": { + "0x00000000-16": 1 + } + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/28-debug-4byte-tracer-0x1c-self-destruct-sender.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/28-debug-4byte-tracer-0x1c-self-destruct-sender.json new file mode 100644 index 00000000000..646db59c188 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/28-debug-4byte-tracer-0x1c-self-destruct-sender.json @@ -0,0 +1,24 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x1c", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0xbe1b55619f540ea8a7d7e9330ea642a195afe0d6d60ced6f463da67b46d3f503", + "result": {} + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/29-debug-4byte-tracer-0x1d-stack-underflow.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/29-debug-4byte-tracer-0x1d-stack-underflow.json new file mode 100644 index 00000000000..dac61c33ec1 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/29-debug-4byte-tracer-0x1d-stack-underflow.json @@ -0,0 +1,24 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x1d", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0xf2f9f56aab631d441ff7bd85a195cea43c7285a4e0bd03823f6c37b379eb0cbe", + "result": {} + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/3-debug-4byte-tracer-0x3-self-destruct-contract.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/3-debug-4byte-tracer-0x3-self-destruct-contract.json new file mode 100644 index 00000000000..bf831c83d68 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/3-debug-4byte-tracer-0x3-self-destruct-contract.json @@ -0,0 +1,24 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x3", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0x8d5477f0aae852c3e9487b0f8e7b9ecf9ccdf23d7934d4b4b7eff40c271031e5", + "result": {} + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/30-debug-4byte-tracer-0x1e-0g0v0_Istanbul.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/30-debug-4byte-tracer-0x1e-0g0v0_Istanbul.json new file mode 100644 index 00000000000..b561bbd93eb --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/30-debug-4byte-tracer-0x1e-0g0v0_Istanbul.json @@ -0,0 +1,26 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x1e", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0xbc7cedf9a139bc6a16959e1463a56ef5bdc07731e384d2a1c27e7828b563d594", + "result": { + "0x00000000-28": 1 + } + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/31-debug-4byte-tracer-0x1f-precompile.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/31-debug-4byte-tracer-0x1f-precompile.json new file mode 100644 index 00000000000..b0b6455d462 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/31-debug-4byte-tracer-0x1f-precompile.json @@ -0,0 +1,24 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x1f", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0x8b75ccd7087daeb5fc2c64bbd157d869a53d794f015d60b5718708ab9e7100e8", + "result": {} + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/32-debug-4byte-tracer-0x20-contract-creation-fails-level-1.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/32-debug-4byte-tracer-0x20-contract-creation-fails-level-1.json new file mode 100644 index 00000000000..1b380f2ac65 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/32-debug-4byte-tracer-0x20-contract-creation-fails-level-1.json @@ -0,0 +1,26 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x20", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0x3a9118fe52689f19ec58b4e1a50a0ae07021812f4c0d72c703a669e90622f3cd", + "result": { + "0x00000000-28": 1 + } + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/33-debug-4byte-tracer-0x21-stack-underflow.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/33-debug-4byte-tracer-0x21-stack-underflow.json new file mode 100644 index 00000000000..a079aeb44ff --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/33-debug-4byte-tracer-0x21-stack-underflow.json @@ -0,0 +1,24 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x21", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0xf0378e1bf579b23aff89255d93ade14fe9136a7cedd9c4985e9bec2d18ce65e9", + "result": {} + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/4-debug-4byte-tracer-0x4-set-contract-storage.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/4-debug-4byte-tracer-0x4-set-contract-storage.json new file mode 100644 index 00000000000..764a46d4376 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/4-debug-4byte-tracer-0x4-set-contract-storage.json @@ -0,0 +1,38 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x4", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0x4de634fe767d1f6d0512ca0c9c0a054d3a2596f7cdd7c1eea5f93046a740b3c7", + "result": { + "0x00000000-124": 1 + } + }, + { + "txHash": "0xf882ec206292910527fd7095e59a1ca027b873296f1eba3886aa1addc4ff0ab9", + "result": { + "0x00000000-124": 1 + } + }, + { + "txHash": "0x7ca6bf869e8882216f7443accb8d642df41af5bfa3a0e63bf03be2cfe629a030", + "result": { + "0x00000000-124": 1 + } + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/5-debug-4byte-tracer-0x5-clear-storage.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/5-debug-4byte-tracer-0x5-clear-storage.json new file mode 100644 index 00000000000..e6be321cde9 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/5-debug-4byte-tracer-0x5-clear-storage.json @@ -0,0 +1,26 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x5", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0xdb2cd5e93dedae66371fc4a95452c746e11f7d2097464707597b8807c889ef5b", + "result": { + "0x00000000-124": 1 + } + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/6-debug-4byte-tracer-0x6-self-destruct-send-funds.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/6-debug-4byte-tracer-0x6-self-destruct-send-funds.json new file mode 100644 index 00000000000..ef9d7c2593d --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/6-debug-4byte-tracer-0x6-self-destruct-send-funds.json @@ -0,0 +1,26 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x6", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0x91eeabc671e2dd2b1c8ddebb46ba59e8cb3e7d189f80bcc868a9787728c6e59e", + "result": { + "0x00000000-16": 1 + } + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/7-debug-4byte-tracer-0x7-increment-bytes.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/7-debug-4byte-tracer-0x7-increment-bytes.json new file mode 100644 index 00000000000..0e9bb60853e --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/7-debug-4byte-tracer-0x7-increment-bytes.json @@ -0,0 +1,26 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x7", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0x47f4d445ea1812cb1ddd3464ab23d2bfc6ed408a8a9db1c497f94e8e06e85286", + "result": { + "0xf0000000-28": 1 + } + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/8-debug-4byte-tracer-0x8-call-one-level-deep.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/8-debug-4byte-tracer-0x8-call-one-level-deep.json new file mode 100644 index 00000000000..e57c70a721f --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/8-debug-4byte-tracer-0x8-call-one-level-deep.json @@ -0,0 +1,27 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x8", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0xa29f9d6a4f183f4c22c4857544a9a6b69c48d7bb8a97652be06e50bb69470666", + "result": { + "0x00000000-60": 1, + "0xf0000000-28": 1 + } + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/9-debug-4byte-tracer-0x9-call-multi-level-deep.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/9-debug-4byte-tracer-0x9-call-multi-level-deep.json new file mode 100644 index 00000000000..e62d6ce0c81 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/4byte-tracer/9-debug-4byte-tracer-0x9-call-multi-level-deep.json @@ -0,0 +1,29 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x9", + { + "tracer": "4byteTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "txHash": "0x4af0ef28fbfcbdee7cc5925797c1b9030b3848c2f63f92737c3fe76b45582af5", + "result": { + "0x00000000-124": 1, + "0x00000000-60": 1, + "0x00000000-92": 1, + "0xf0000000-28": 1 + } + } + ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/README.md b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/README.md new file mode 100644 index 00000000000..b550a09d7ae --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug-geth/specs/README.md @@ -0,0 +1,113 @@ +# Tracer Specification Files + +This directory contains specification files for various Ethereum tracers. These specs include request/response pairs +from Geth and are used to verify that alternative implementations (like Besu) produce matching output. + +## Test Blockchain Source + +These specifications were generated from a Geth test node initialized with `../chain-data/blocks.bin`, which contains +33 pre-built test blocks (0x0 to 0x21) covering various EVM scenarios. The Geth node was started with debug APIs +enabled, and the tracer specs were generated by querying `debug_traceBlockByNumber` for each block with different +tracer configurations. + +**Initialization commands:** +```bash +cd ../chain-data + +# Initialize with genesis configuration +geth init --datadir ./data genesis.json + +# Import pre-built blocks +geth import --datadir ./data blocks.bin +``` + +## Directory Structure + +Each tracer has its own directory under `specs/`: + +- **`call-tracer/`** - 34 files - Call tracer specs +- **`prestate-tracer/`** - Pre-state tracer specs + - `diff-mode-false/` - 33 files - Pre-state only + - `diff-mode-true/` - 33 files - Pre and post state +- **`4byte-tracer/`** - 34 files - Function signature tracer + +## Tracer Types + +### callTracer +Traces call execution including: +- Call type (CALL, DELEGATECALL, STATICCALL, CREATE, etc.) +- From/to addresses +- Gas usage +- Input/output data +- Value transfers + +**Files**: `{number}-debug-call-tracer-0x{block}-{description}.json` + +### prestateTracer +Captures state before (and optionally after) transaction execution: +- Account balances +- Nonces +- Storage values +- Code + +**Modes**: +- `diffMode: false` - Shows only pre-state +- `diffMode: true` - Shows both pre and post state + +**Files**: `block_0x{block}.json` + +### 4byteTracer +Detects function signatures in transaction calldata: +- Function selector (first 4 bytes) +- Calldata length +- Call frequency + +**Files**: `{number}-debug-4byte-tracer-0x{block}-{description}.json` + +## File Format + +All spec files use the same structure: + +```json +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceBlockByNumber", + "params": [ + "0x2", + { + "tracer": "callTracer" + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ /* tracer-specific output */ ] + }, + "statusCode": 200 +} +``` + +## Coverage + +All tracers cover the same 34 blocks (0x0 to 0x21) from the test blockchain: +- Genesis block (0x0) +- Empty block (0x1) +- Simple transfers +- Contract deployments +- Contract calls (CALL, CALLCODE, DELEGATECALL, STATICCALL) +- Self-destructs +- CREATE/CREATE2 operations +- ERC20 token operations +- Error cases (reverts, out of gas, invalid opcodes) +- Edge cases + +## Usage + +These specs are intended for: +1. **Validation**: Verify alternative implementations match Geth's output +2. **Testing**: Automated test suites can load and validate against these specs +3. **Documentation**: Reference for expected tracer behavior +4. **Debugging**: Compare actual output against known-good responses diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/debug/TracerType.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/debug/TracerType.java index af4a8cdb663..0233535df37 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/debug/TracerType.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/debug/TracerType.java @@ -31,7 +31,10 @@ public enum TracerType { FLAT_CALL_TRACER("flatCallTracer", "Flat Call Tracer"), /** Prestate tracer that captures account states before transaction execution */ - PRESTATE_TRACER("prestateTracer", "Prestate Tracer"); + PRESTATE_TRACER("prestateTracer", "Prestate Tracer"), + + /** 4Byte tracer that collects function selectors and call data sizes */ + FOUR_BYTE_TRACER("4byteTracer", "4Byte Tracer"); private final String value; private final String displayName;