Skip to content

Commit

Permalink
Add pipeline implementation for debug_traceBlockByHash
Browse files Browse the repository at this point in the history
Signed-off-by: Ameziane H. <[email protected]>
  • Loading branch information
ahamlat committed Jan 3, 2025
1 parent cee113e commit 66a9714
Show file tree
Hide file tree
Showing 6 changed files with 391 additions and 320 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,62 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods;

import static org.hyperledger.besu.services.pipeline.PipelineBuilder.createPipelineFrom;

import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.TransactionTraceParams;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTrace;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTracer;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.Tracer;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTrace;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.DebugTraceTransactionResult;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.debug.TraceOptions;
import org.hyperledger.besu.ethereum.eth.manager.EthScheduler;
import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
import org.hyperledger.besu.ethereum.vm.DebugOperationTracer;
import org.hyperledger.besu.metrics.BesuMetricCategory;
import org.hyperledger.besu.metrics.ObservableMetricsSystem;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import org.hyperledger.besu.plugin.services.metrics.Counter;
import org.hyperledger.besu.plugin.services.metrics.LabelledMetric;
import org.hyperledger.besu.services.pipeline.Pipeline;

import java.util.Collection;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;

import com.google.common.base.Suppliers;

public class DebugTraceBlockByHash implements JsonRpcMethod {

private final Supplier<BlockTracer> blockTracerSupplier;
private final Supplier<BlockchainQueries> blockchainQueries;
private final ProtocolSchedule protocolSchedule;
private final LabelledMetric<Counter> outputCounter;

public DebugTraceBlockByHash(
final Supplier<BlockTracer> blockTracerSupplier,
final Supplier<BlockchainQueries> blockchainQueriesSupplier) {
this.blockTracerSupplier = blockTracerSupplier;
this.blockchainQueries = blockchainQueriesSupplier;
final ProtocolSchedule protocolSchedule,
final BlockchainQueries blockchainQueries,
final ObservableMetricsSystem metricsSystem) {
this.blockchainQueries = Suppliers.ofInstance(blockchainQueries);
this.protocolSchedule = protocolSchedule;
this.outputCounter =
metricsSystem.createLabelledCounter(
BesuMetricCategory.BLOCKCHAIN,
"transactions_debugTraceblock_pipeline_processed_total",
"Number of transactions processed for each block",
"step",
"action");
}

@Override
Expand Down Expand Up @@ -73,20 +99,69 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) {
RpcErrorType.INVALID_TRANSACTION_TRACE_PARAMS,
e);
}
Optional<Block> maybeBlock = blockchainQueries.get().getBlockchain().getBlockByHash(blockHash);

final Collection<DebugTraceTransactionResult> results =
Tracer.processTracing(
blockchainQueries.get(),
blockHash,
mutableWorldState ->
blockTracerSupplier
.get()
.trace(
mutableWorldState,
blockHash,
new DebugOperationTracer(traceOptions, true))
.map(BlockTrace::getTransactionTraces)
.map(DebugTraceTransactionResult::of))
maybeBlock
.flatMap(
block ->
Tracer.processTracing(
blockchainQueries.get(),
blockHash,
traceableState -> {
Collection<DebugTraceTransactionResult> tracesList =
new CopyOnWriteArrayList<>();
final ProtocolSpec protocolSpec =
protocolSchedule.getByBlockHeader(block.getHeader());
final MainnetTransactionProcessor transactionProcessor =
protocolSpec.getTransactionProcessor();
final TraceBlock.ChainUpdater chainUpdater =
new TraceBlock.ChainUpdater(traceableState);

TransactionSource transactionSource = new TransactionSource(block);
DebugOperationTracer debugOperationTracer =
new DebugOperationTracer(traceOptions, true);
ExecuteTransactionStep executeTransactionStep =
new ExecuteTransactionStep(
chainUpdater,
transactionProcessor,
blockchainQueries.get().getBlockchain(),
debugOperationTracer,
protocolSpec,
block);
DebugTraceTransactionStep debugTraceTransactionStep =
new DebugTraceTransactionStep();
Pipeline<TransactionTrace> traceBlockPipeline =
createPipelineFrom(
"getTransactions",
transactionSource,
4,
outputCounter,
false,
"debug_trace_block_by_number")
.thenProcess("executeTransaction", executeTransactionStep)
.thenProcessAsyncOrdered(
"debugTraceTransactionStep", debugTraceTransactionStep, 4)
.andFinishWith("collect_results", tracesList::add);

try {
if (blockchainQueries.get().getEthScheduler().isPresent()) {
blockchainQueries
.get()
.getEthScheduler()
.get()
.startPipeline(traceBlockPipeline)
.get();
} else {
EthScheduler ethScheduler =
new EthScheduler(1, 1, 1, 1, new NoOpMetricsSystem());
ethScheduler.startPipeline(traceBlockPipeline).get();
}
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
return Optional.of(tracesList);
}))
.orElse(null);
return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), results);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@

public class DebugTraceBlockByNumber extends AbstractBlockParameterMethod {

protected final ProtocolSchedule protocolSchedule;
private final ProtocolSchedule protocolSchedule;
private final LabelledMetric<Counter> outputCounter;

public DebugTraceBlockByNumber(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ protected Map<String, JsonRpcMethod> create() {
new DebugSetHead(blockchainQueries, protocolContext),
new DebugReplayBlock(blockchainQueries, protocolContext, protocolSchedule),
new DebugTraceBlockByNumber(protocolSchedule, blockchainQueries, metricsSystem),
new DebugTraceBlockByHash(() -> new BlockTracer(blockReplay), () -> blockchainQueries),
new DebugTraceBlockByHash(protocolSchedule, blockchainQueries, metricsSystem),
new DebugBatchSendRawTransaction(transactionPool),
new DebugGetBadBlocks(protocolContext, blockResult),
new DebugStandardTraceBlockToFile(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,125 +15,111 @@
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;

import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTrace;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTracer;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.Tracer;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTrace;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.DebugTraceTransactionResult;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
import org.hyperledger.besu.ethereum.debug.TraceFrame;
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.metrics.ObservableMetricsSystem;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.function.Function;

import org.apache.tuweni.bytes.Bytes;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.MockitoAnnotations;

public class DebugTraceBlockByHashTest {
@Mock private ProtocolSchedule protocolSchedule;

private final BlockTracer blockTracer = mock(BlockTracer.class);
@Mock private BlockchainQueries blockchainQueries;

private final BlockchainQueries blockchainQueries =
mock(BlockchainQueries.class, Answers.RETURNS_DEEP_STUBS);
private final MutableWorldState mutableWorldState = mock(MutableWorldState.class);
private final BlockHeader blockHeader = mock(BlockHeader.class, Answers.RETURNS_DEEP_STUBS);
private final DebugTraceBlockByHash debugTraceBlockByHash =
new DebugTraceBlockByHash(() -> blockTracer, () -> blockchainQueries);
@Mock private ObservableMetricsSystem metricsSystem;

@Mock private Blockchain blockchain;

@Mock private Block block;

private DebugTraceBlockByHash debugTraceBlockByHash;

private final Hash blockHash =
Hash.fromHexString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");

@BeforeEach
public void setUp() {
doAnswer(
invocation ->
invocation
.<Function<MutableWorldState, Optional<? extends JsonRpcResponse>>>getArgument(
1)
.apply(mutableWorldState))
.when(blockchainQueries)
.getAndMapWorldState(any(), any());
when(blockchainQueries.getBlockHeaderByHash(any(Hash.class)))
.thenReturn(Optional.of(blockHeader));
MockitoAnnotations.openMocks(this);
debugTraceBlockByHash =
new DebugTraceBlockByHash(protocolSchedule, blockchainQueries, metricsSystem);
}

@Test
public void nameShouldBeDebugTraceBlockByHash() {
assertThat(debugTraceBlockByHash.getName()).isEqualTo("debug_traceBlockByHash");
}

@SuppressWarnings("unchecked")
@Test
public void shouldReturnCorrectResponse() {
final Object[] params = new Object[] {blockHash};
final JsonRpcRequestContext request =
new JsonRpcRequestContext(new JsonRpcRequest("2.0", "debug_traceBlockByHash", params));

final TraceFrame traceFrame =
new TraceFrame(
12,
Optional.of("NONE"),
Integer.MAX_VALUE,
45L,
OptionalLong.of(56L),
0L,
2,
Optional.empty(),
null,
Wei.ZERO,
Bytes.EMPTY,
Bytes.EMPTY,
Optional.empty(),
Optional.empty(),
Optional.empty(),
null,
Optional.empty(),
Optional.empty(),
Optional.empty(),
0,
Optional.empty(),
false,
Optional.empty(),
Optional.empty());

final TransactionProcessingResult transaction1Result = mock(TransactionProcessingResult.class);
final TransactionProcessingResult transaction2Result = mock(TransactionProcessingResult.class);

final TransactionTrace transaction1Trace = mock(TransactionTrace.class);
final TransactionTrace transaction2Trace = mock(TransactionTrace.class);

BlockTrace blockTrace = new BlockTrace(Arrays.asList(transaction1Trace, transaction2Trace));

when(transaction1Trace.getTraceFrames()).thenReturn(Arrays.asList(traceFrame));
when(transaction2Trace.getTraceFrames()).thenReturn(Arrays.asList(traceFrame));
when(transaction1Trace.getResult()).thenReturn(transaction1Result);
when(transaction2Trace.getResult()).thenReturn(transaction2Result);
when(transaction1Result.getOutput()).thenReturn(Bytes.fromHexString("1234"));
when(transaction2Result.getOutput()).thenReturn(Bytes.fromHexString("1234"));
when(blockTracer.trace(any(Tracer.TraceableState.class), eq(blockHash), any()))
.thenReturn(Optional.of(blockTrace));

final JsonRpcSuccessResponse response =
(JsonRpcSuccessResponse) debugTraceBlockByHash.response(request);
final Collection<?> result = (Collection<?>) response.getResult();
assertThat(result).hasSize(2);
when(blockchainQueries.getBlockchain()).thenReturn(blockchain);
when(blockchain.getBlockByHash(blockHash)).thenReturn(Optional.of(block));

DebugTraceTransactionResult result1 = mock(DebugTraceTransactionResult.class);
DebugTraceTransactionResult result2 = mock(DebugTraceTransactionResult.class);

List<DebugTraceTransactionResult> resultList = Arrays.asList(result1, result2);

try (MockedStatic<Tracer> mockedTracer = mockStatic(Tracer.class)) {
mockedTracer
.when(
() ->
Tracer.processTracing(eq(blockchainQueries), eq(blockHash), any(Function.class)))
.thenReturn(Optional.of(resultList));

final JsonRpcSuccessResponse response =
(JsonRpcSuccessResponse) debugTraceBlockByHash.response(request);
final Collection<DebugTraceTransactionResult> traceResult = getResult(response);
assertThat(traceResult).isNotEmpty();
assertThat(traceResult).isInstanceOf(Collection.class).hasSize(2);
assertThat(traceResult).containsExactly(result1, result2);
}
}

@SuppressWarnings("unchecked")
private Collection<DebugTraceTransactionResult> getResult(final JsonRpcSuccessResponse response) {
return (Collection<DebugTraceTransactionResult>) response.getResult();
}

@Test
public void shouldHandleInvalidParametersGracefully() {
final Object[] invalidParams = new Object[] {"aaaa"};
final JsonRpcRequestContext request =
new JsonRpcRequestContext(
new JsonRpcRequest("2.0", "debug_traceBlockByHash", invalidParams));

assertThatThrownBy(() -> debugTraceBlockByHash.response(request))
.isInstanceOf(InvalidJsonRpcParameters.class)
.hasMessageContaining("Invalid block hash parameter");
}
}
Loading

0 comments on commit 66a9714

Please sign in to comment.