From 9bcbda3b62c8915a6be3db851f5406259db00194 Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Fri, 3 Jan 2025 15:10:39 +0100 Subject: [PATCH] Add pipeline implementation for debug_traceBlock Signed-off-by: Ameziane H. --- .../internal/methods/DebugTraceBlock.java | 117 ++++++++-- .../jsonrpc/methods/DebugJsonRpcMethods.java | 6 +- .../methods/DebugTraceBlockByHashTest.java | 13 +- .../methods/DebugTraceBlockByNumberTest.java | 14 +- .../internal/methods/DebugTraceBlockTest.java | 204 +++++++++++------- 5 files changed, 227 insertions(+), 127 deletions(-) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlock.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlock.java index edfb816bb9d..5545b2aa155 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlock.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlock.java @@ -14,14 +14,15 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; +import static org.hyperledger.besu.services.pipeline.PipelineBuilder.createPipelineFrom; + 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.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -31,14 +32,28 @@ import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; 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.mainnet.ScheduleBasedBlockHeaderFunctions; import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.rlp.RLPException; 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; import org.apache.tuweni.bytes.Bytes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,17 +61,26 @@ public class DebugTraceBlock implements JsonRpcMethod { private static final Logger LOG = LoggerFactory.getLogger(DebugTraceBlock.class); - private final Supplier blockTracerSupplier; private final BlockHeaderFunctions blockHeaderFunctions; - private final BlockchainQueries blockchainQueries; + private final Supplier blockchainQueries; + private final ProtocolSchedule protocolSchedule; + private final LabelledMetric outputCounter; public DebugTraceBlock( - final Supplier blockTracerSupplier, - final BlockHeaderFunctions blockHeaderFunctions, - final BlockchainQueries blockchainQueries) { - this.blockTracerSupplier = blockTracerSupplier; - this.blockHeaderFunctions = blockHeaderFunctions; - this.blockchainQueries = blockchainQueries; + final ProtocolSchedule protocolSchedule, + final BlockchainQueries blockchainQueries, + final ObservableMetricsSystem metricsSystem) { + this.blockHeaderFunctions = ScheduleBasedBlockHeaderFunctions.create(protocolSchedule); + 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 @@ -70,7 +94,7 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { try { final String input = requestContext.getRequiredParameter(0, String.class); block = Block.readFrom(RLP.input(Bytes.fromHexString(input)), this.blockHeaderFunctions); - } catch (final RLPException e) { + } catch (final RLPException | IllegalArgumentException e) { LOG.debug("Failed to parse block RLP (index 0)", e); return new JsonRpcErrorResponse( requestContext.getRequest().getId(), RpcErrorType.INVALID_BLOCK_PARAMS); @@ -92,20 +116,69 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { e); } - if (this.blockchainQueries.blockByHash(block.getHeader().getParentHash()).isPresent()) { + if (blockchainQueries + .get() + .getBlockchain() + .getBlockByHash(block.getHeader().getParentHash()) + .isPresent()) { final Collection results = Tracer.processTracing( - blockchainQueries, + blockchainQueries.get(), Optional.of(block.getHeader()), - mutableWorldState -> - blockTracerSupplier - .get() - .trace( - mutableWorldState, - block, - new DebugOperationTracer(traceOptions, true)) - .map(BlockTrace::getTransactionTraces) - .map(DebugTraceTransactionResult::of)) + traceableState -> { + Collection 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 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); } else { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/DebugJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/DebugJsonRpcMethods.java index 07e03d52b24..9a1513f7a82 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/DebugJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/DebugJsonRpcMethods.java @@ -45,7 +45,6 @@ import org.hyperledger.besu.ethereum.core.Synchronizer; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; import org.hyperledger.besu.metrics.ObservableMetricsSystem; @@ -100,10 +99,7 @@ protected Map create() { new DebugStorageRangeAt(blockchainQueries, blockReplay), new DebugMetrics(metricsSystem), new DebugResyncWorldstate(protocolContext, synchronizer), - new DebugTraceBlock( - () -> new BlockTracer(blockReplay), - ScheduleBasedBlockHeaderFunctions.create(protocolSchedule), - blockchainQueries), + new DebugTraceBlock(protocolSchedule, blockchainQueries, metricsSystem), new DebugSetHead(blockchainQueries, protocolContext), new DebugReplayBlock(blockchainQueries, protocolContext, protocolSchedule), new DebugTraceBlockByNumber(protocolSchedule, blockchainQueries, metricsSystem), diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByHashTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByHashTest.java index 5e21316573a..55f3def1cba 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByHashTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByHashTest.java @@ -27,6 +27,7 @@ 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.processor.Tracer; +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; @@ -49,17 +50,11 @@ public class DebugTraceBlockByHashTest { @Mock private ProtocolSchedule protocolSchedule; - @Mock private BlockchainQueries blockchainQueries; - @Mock private ObservableMetricsSystem metricsSystem; - @Mock private Blockchain blockchain; - @Mock private Block block; - private DebugTraceBlockByHash debugTraceBlockByHash; - private final Hash blockHash = Hash.fromHexString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); @@ -97,8 +92,10 @@ public void shouldReturnCorrectResponse() { Tracer.processTracing(eq(blockchainQueries), eq(blockHash), any(Function.class))) .thenReturn(Optional.of(resultList)); - final JsonRpcSuccessResponse response = - (JsonRpcSuccessResponse) debugTraceBlockByHash.response(request); + final JsonRpcResponse jsonRpcResponse = debugTraceBlockByHash.response(request); + assertThat(jsonRpcResponse).isInstanceOf(JsonRpcSuccessResponse.class); + JsonRpcSuccessResponse response = (JsonRpcSuccessResponse) jsonRpcResponse; + final Collection traceResult = getResult(response); assertThat(traceResult).isNotEmpty(); assertThat(traceResult).isInstanceOf(Collection.class).hasSize(2); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByNumberTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByNumberTest.java index 8df427fb3bc..3441881f53f 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByNumberTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByNumberTest.java @@ -26,6 +26,7 @@ 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.processor.Tracer; +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; @@ -50,17 +51,11 @@ public class DebugTraceBlockByNumberTest { @Mock private BlockchainQueries blockchainQueries; - @Mock private Blockchain blockchain; - @Mock private Block block; - @Mock private BlockHeader blockHeader; - @Mock private ProtocolSchedule protocolSchedule; - @Mock private ObservableMetricsSystem metricsSystem; - private DebugTraceBlockByNumber debugTraceBlockByNumber; @BeforeEach @@ -100,10 +95,11 @@ public void shouldReturnCorrectResponse() { eq(blockchainQueries), eq(Optional.of(blockHeader)), any(Function.class))) .thenReturn(Optional.of(resultList)); - final JsonRpcSuccessResponse response = - (JsonRpcSuccessResponse) debugTraceBlockByNumber.response(request); - final Collection traceResult = getResult(response); + final JsonRpcResponse jsonRpcResponse = debugTraceBlockByNumber.response(request); + assertThat(jsonRpcResponse).isInstanceOf(JsonRpcSuccessResponse.class); + JsonRpcSuccessResponse response = (JsonRpcSuccessResponse) jsonRpcResponse; + final Collection traceResult = getResult(response); assertThat(traceResult).isNotEmpty(); assertThat(traceResult).isInstanceOf(Collection.class).hasSize(2); assertThat(traceResult).containsExactly(result1, result2); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockTest.java index f35f3d1a16a..9f9ae1383a5 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockTest.java @@ -14,53 +14,106 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; -import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.config.GenesisConfig; 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.processor.Tracer; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTrace; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; +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.query.BlockWithMetadata; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.DebugTraceTransactionResult; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; +import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockDataGenerator; -import org.hyperledger.besu.ethereum.debug.TraceFrame; +import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture; +import org.hyperledger.besu.ethereum.core.MiningConfiguration; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; -import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; - +import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; +import org.hyperledger.besu.ethereum.mainnet.TransactionValidatorFactory; +import org.hyperledger.besu.ethereum.mainnet.WithdrawalsProcessor; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.metrics.ObservableMetricsSystem; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; + +import java.math.BigInteger; +import java.util.Arrays; import java.util.Collection; -import java.util.Collections; +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.Mock; +import org.mockito.MockedStatic; +import org.mockito.MockitoAnnotations; public class DebugTraceBlockTest { - - private final BlockTracer blockTracer = mock(BlockTracer.class); - private final BlockchainQueries blockchainQueries = mock(BlockchainQueries.class); - private final DebugTraceBlock debugTraceBlock = - new DebugTraceBlock(() -> blockTracer, new MainnetBlockHeaderFunctions(), blockchainQueries); + @Mock private BlockchainQueries blockchainQueries; + @Mock private Blockchain blockchain; + @Mock private ObservableMetricsSystem metricsSystem; + @Mock private WithdrawalsProcessor withdrawalsProcessor; + @Mock private TransactionValidatorFactory alwaysValidTransactionValidatorFactory; + private DebugTraceBlock debugTraceBlock; + + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + // As we build the block from RLP in DebugTraceBlock, we need to have non mocked + // protocolSchedule (and ProtocolSpec) + // to be able to get the hash of the block + final var genesisConfig = + GenesisConfig.fromResource( + "/org/hyperledger/besu/ethereum/api/jsonrpc/trace/chain-data/genesis.json"); + final ProtocolSpecAdapters protocolSpecAdapters = + ProtocolSpecAdapters.create( + 0, + specBuilder -> { + specBuilder.isReplayProtectionSupported(true); + specBuilder.withdrawalsProcessor(withdrawalsProcessor); + specBuilder.transactionValidatorFactoryBuilder( + (evm, gasLimitCalculator, feeMarket) -> alwaysValidTransactionValidatorFactory); + return specBuilder; + }); + final ExecutionContextTestFixture executionContextTestFixture = + ExecutionContextTestFixture.builder(genesisConfig) + .protocolSchedule( + new ProtocolScheduleBuilder( + genesisConfig.getConfigOptions(), + Optional.of(BigInteger.valueOf(42)), + protocolSpecAdapters, + PrivacyParameters.DEFAULT, + false, + EvmConfiguration.DEFAULT, + MiningConfiguration.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()) + .createProtocolSchedule()) + .build(); + debugTraceBlock = + new DebugTraceBlock( + executionContextTestFixture.getProtocolSchedule(), blockchainQueries, metricsSystem); + } @Test public void nameShouldBeDebugTraceBlock() { assertThat(debugTraceBlock.getName()).isEqualTo("debug_traceBlock"); } + @SuppressWarnings("unchecked") @Test public void shouldReturnCorrectResponse() { final Block parentBlock = @@ -79,71 +132,39 @@ public void shouldReturnCorrectResponse() { final JsonRpcRequestContext request = new JsonRpcRequestContext(new JsonRpcRequest("2.0", "debug_traceBlock", 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); - - final BlockTrace blockTrace = new BlockTrace(asList(transaction1Trace, transaction2Trace)); - - when(transaction1Trace.getTraceFrames()).thenReturn(singletonList(traceFrame)); - when(transaction2Trace.getTraceFrames()).thenReturn(singletonList(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(block), any())) - .thenReturn(Optional.of(blockTrace)); - - when(blockchainQueries.blockByHash(parentBlock.getHash())) - .thenReturn( - Optional.of( - new BlockWithMetadata<>( - parentBlock.getHeader(), - Collections.emptyList(), - Collections.emptyList(), - parentBlock.getHeader().getDifficulty(), - parentBlock.calculateSize()))); - when(blockchainQueries.getAndMapWorldState(eq(parentBlock.getHash()), any())) - .thenAnswer( - invocationOnMock -> { - Function> mapper = - invocationOnMock.getArgument(1); - return mapper.apply(mock(Tracer.TraceableState.class)); - }); + when(blockchainQueries.getBlockchain()).thenReturn(blockchain); + when(blockchain.getBlockByHash(block.getHeader().getParentHash())) + .thenReturn(Optional.of(parentBlock)); + + DebugTraceTransactionResult result1 = mock(DebugTraceTransactionResult.class); + DebugTraceTransactionResult result2 = mock(DebugTraceTransactionResult.class); + + List resultList = Arrays.asList(result1, result2); + + try (MockedStatic mockedTracer = mockStatic(Tracer.class)) { + mockedTracer + .when( + () -> + Tracer.processTracing( + eq(blockchainQueries), + eq(Optional.of(block.getHeader())), + any(Function.class))) + .thenReturn(Optional.of(resultList)); + + final JsonRpcResponse jsonRpcResponse = debugTraceBlock.response(request); + assertThat(jsonRpcResponse).isInstanceOf(JsonRpcSuccessResponse.class); + JsonRpcSuccessResponse response = (JsonRpcSuccessResponse) jsonRpcResponse; + + final Collection traceResult = getResult(response); + assertThat(traceResult).isNotEmpty(); + assertThat(traceResult).isInstanceOf(Collection.class).hasSize(2); + assertThat(traceResult).containsExactly(result1, result2); + } + } - final JsonRpcSuccessResponse response = - (JsonRpcSuccessResponse) debugTraceBlock.response(request); - final Collection result = (Collection) response.getResult(); - assertThat(result).hasSize(2); + @SuppressWarnings("unchecked") + private Collection getResult(final JsonRpcSuccessResponse response) { + return (Collection) response.getResult(); } @Test @@ -158,9 +179,26 @@ public void shouldReturnErrorResponseWhenParentBlockMissing() { final JsonRpcRequestContext request = new JsonRpcRequestContext(new JsonRpcRequest("2.0", "debug_traceBlock", params)); - when(blockchainQueries.blockByHash(any())).thenReturn(Optional.empty()); + when(blockchainQueries.getBlockchain()).thenReturn(blockchain); + when(blockchain.getBlockByHash(block.getHeader().getParentHash())).thenReturn(Optional.empty()); + final JsonRpcResponse jsonRpcResponse = debugTraceBlock.response(request); + assertThat(jsonRpcResponse).isInstanceOf(JsonRpcErrorResponse.class); final JsonRpcErrorResponse response = (JsonRpcErrorResponse) debugTraceBlock.response(request); + assertThat(response.getErrorType()).isEqualByComparingTo(RpcErrorType.PARENT_BLOCK_NOT_FOUND); } + + @Test + public void shouldHandleInvalidParametersGracefully() { + final Object[] invalidParams = new Object[] {"invalid RLP"}; + final JsonRpcRequestContext request = + new JsonRpcRequestContext(new JsonRpcRequest("2.0", "debug_traceBlock", invalidParams)); + + final JsonRpcResponse jsonRpcResponse = debugTraceBlock.response(request); + assertThat(jsonRpcResponse).isInstanceOf(JsonRpcErrorResponse.class); + final JsonRpcErrorResponse response = (JsonRpcErrorResponse) debugTraceBlock.response(request); + + assertThat(response.getError().getMessage()).contains("Invalid block, unable to parse RLP"); + } }