diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractTraceByBlock.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractTraceByBlock.java new file mode 100644 index 00000000000..e7977117f85 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractTraceByBlock.java @@ -0,0 +1,112 @@ +/* + * Copyright Hyperledger 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.methods; + +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.TraceTypeParameter.TraceType; + +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.TraceTypeParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTrace; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TraceCallResult; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.diff.StateDiffGenerator; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.diff.StateDiffTrace; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.flat.FlatTrace; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.flat.FlatTraceGenerator; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.flat.MixInIgnoreRevertReason; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.vm.VmTrace; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.vm.VmTraceGenerator; +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.mainnet.ImmutableTransactionValidationParams; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; +import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; +import org.hyperledger.besu.ethereum.transaction.TransactionSimulatorResult; + +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +public abstract class AbstractTraceByBlock extends AbstractBlockParameterMethod + implements JsonRpcMethod { + protected final ProtocolSchedule protocolSchedule; + protected final TransactionSimulator transactionSimulator; + + protected static final ObjectMapper mapper = new ObjectMapper(); + + protected AbstractTraceByBlock( + final BlockchainQueries blockchainQueries, + final ProtocolSchedule protocolSchedule, + final TransactionSimulator transactionSimulator) { + super(blockchainQueries); + + this.protocolSchedule = protocolSchedule; + this.transactionSimulator = transactionSimulator; + } + + protected JsonNode getTraceCallResult( + final ProtocolSchedule protocolSchedule, + final Set traceTypes, + final Optional maybeSimulatorResult, + final TransactionTrace transactionTrace, + final Block block) { + final TraceCallResult.Builder builder = TraceCallResult.builder(); + // The trace_call specification does not output the revert reason, so we have to remove it + mapper.addMixIn(FlatTrace.class, MixInIgnoreRevertReason.class); + + transactionTrace + .getResult() + .getRevertReason() + .ifPresentOrElse( + revertReason -> builder.output(revertReason.toHexString()), + () -> builder.output(maybeSimulatorResult.get().getOutput().toString())); + + if (traceTypes.contains(TraceType.STATE_DIFF)) { + new StateDiffGenerator() + .generateStateDiff(transactionTrace) + .forEachOrdered(stateDiff -> builder.stateDiff((StateDiffTrace) stateDiff)); + } + + if (traceTypes.contains(TraceType.TRACE)) { + FlatTraceGenerator.generateFromTransactionTrace( + protocolSchedule, transactionTrace, block, new AtomicInteger(), false) + .forEachOrdered(trace -> builder.addTrace((FlatTrace) trace)); + } + + if (traceTypes.contains(TraceType.VM_TRACE)) { + new VmTraceGenerator(transactionTrace) + .generateTraceStream() + .forEachOrdered(vmTrace -> builder.vmTrace((VmTrace) vmTrace)); + } + + return mapper.valueToTree(builder.build()); + } + + protected TransactionValidationParams buildTransactionValidationParams() { + return ImmutableTransactionValidationParams.builder() + .from(TransactionValidationParams.transactionSimulator()) + .build(); + } + + protected TraceOptions buildTraceOptions(final Set traceTypes) { + return new TraceOptions( + traceTypes.contains(TraceType.STATE_DIFF), + false, + traceTypes.contains(TraceType.TRACE) || traceTypes.contains(TraceType.VM_TRACE)); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractTraceByHash.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractTraceByHash.java new file mode 100644 index 00000000000..8589e1b109d --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractTraceByHash.java @@ -0,0 +1,94 @@ +/* + * Copyright Hyperledger 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.methods; + +import org.hyperledger.besu.datatypes.Hash; +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.TransactionTrace; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.flat.FlatTrace; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.flat.FlatTraceGenerator; +import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; +import org.hyperledger.besu.ethereum.api.query.TransactionWithMetadata; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.debug.TraceOptions; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.vm.DebugOperationTracer; + +import java.util.Collections; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; + +public abstract class AbstractTraceByHash implements JsonRpcMethod { + protected final Supplier blockTracerSupplier; + protected final BlockchainQueries blockchainQueries; + protected final ProtocolSchedule protocolSchedule; + + protected AbstractTraceByHash( + final Supplier blockTracerSupplier, + final BlockchainQueries blockchainQueries, + final ProtocolSchedule protocolSchedule) { + this.blockTracerSupplier = blockTracerSupplier; + this.blockchainQueries = blockchainQueries; + this.protocolSchedule = protocolSchedule; + } + + public Stream resultByTransactionHash(final Hash transactionHash) { + return blockchainQueries + .transactionByHash(transactionHash) + .flatMap(TransactionWithMetadata::getBlockNumber) + .flatMap(blockNumber -> blockchainQueries.getBlockchain().getBlockByNumber(blockNumber)) + .map((block) -> getTraceBlock(block, transactionHash)) + .orElse(Stream.empty()); + } + + private Stream getTraceBlock(final Block block, final Hash transactionHash) { + if (block == null || block.getBody().getTransactions().isEmpty()) { + return Stream.empty(); + } + final TransactionTrace transactionTrace = getTransactionTrace(block, transactionHash); + return getTraceStream(transactionTrace, block); + } + + private TransactionTrace getTransactionTrace(final Block block, final Hash transactionHash) { + return blockTracerSupplier + .get() + .trace(block, new DebugOperationTracer(new TraceOptions(false, false, true))) + .map(BlockTrace::getTransactionTraces) + .orElse(Collections.emptyList()) + .stream() + .filter(trxTrace -> trxTrace.getTransaction().getHash().equals(transactionHash)) + .findFirst() + .orElseThrow(); + } + + private Stream getTraceStream( + final TransactionTrace transactionTrace, final Block block) { + return FlatTraceGenerator.generateFromTransactionTraceAndBlock( + this.protocolSchedule, transactionTrace, block) + .map(trace -> (FlatTrace) trace); + } + + protected JsonNode arrayNodeFromTraceStream(final Stream traceStream) { + final ObjectMapper mapper = new ObjectMapper(); + final ArrayNode resultArrayNode = mapper.createArrayNode(); + traceStream.forEachOrdered(resultArrayNode::addPOJO); + return resultArrayNode; + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCall.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCall.java index 5f07811ebe8..9cd816c50ae 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCall.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCall.java @@ -14,7 +14,6 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; -import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.TraceTypeParameter.TraceType.VM_TRACE; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.BLOCK_NOT_FOUND; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.INTERNAL_ERROR; @@ -24,48 +23,27 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.TraceTypeParameter; 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.results.TraceCallResult; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.diff.StateDiffGenerator; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.diff.StateDiffTrace; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.flat.FlatTrace; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.flat.FlatTraceGenerator; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.flat.MixInIgnoreRevertReason; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.vm.VmTrace; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.vm.VmTraceGenerator; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.debug.TraceOptions; -import org.hyperledger.besu.ethereum.mainnet.ImmutableTransactionValidationParams; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; import org.hyperledger.besu.ethereum.transaction.TransactionSimulatorResult; import org.hyperledger.besu.ethereum.vm.DebugOperationTracer; import java.util.Optional; import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; - -import com.fasterxml.jackson.databind.ObjectMapper; - -public class TraceCall extends AbstractBlockParameterMethod implements JsonRpcMethod { - private final ProtocolSchedule protocolSchedule; - private final TransactionSimulator transactionSimulator; - - private static final ObjectMapper MAPPER_IGNORE_REVERT_REASON = new ObjectMapper(); +public class TraceCall extends AbstractTraceByBlock implements JsonRpcMethod { public TraceCall( final BlockchainQueries blockchainQueries, final ProtocolSchedule protocolSchedule, final TransactionSimulator transactionSimulator) { - super(blockchainQueries); - - this.protocolSchedule = protocolSchedule; - this.transactionSimulator = transactionSimulator; - + super(blockchainQueries, protocolSchedule, transactionSimulator); // The trace_call specification does not output the revert reason, so we have to remove it - MAPPER_IGNORE_REVERT_REASON.addMixIn(FlatTrace.class, MixInIgnoreRevertReason.class); + mapper.addMixIn(FlatTrace.class, MixInIgnoreRevertReason.class); } @Override @@ -119,47 +97,7 @@ protected Object resultByBlockNumber( final Block block = blockchainQueries.get().getBlockchain().getChainHeadBlock(); - final TraceCallResult.Builder builder = TraceCallResult.builder(); - - transactionTrace - .getResult() - .getRevertReason() - .ifPresentOrElse( - revertReason -> builder.output(revertReason.toHexString()), - () -> builder.output(maybeSimulatorResult.get().getOutput().toString())); - - if (traceTypes.contains(TraceTypeParameter.TraceType.STATE_DIFF)) { - new StateDiffGenerator() - .generateStateDiff(transactionTrace) - .forEachOrdered(stateDiff -> builder.stateDiff((StateDiffTrace) stateDiff)); - } - - if (traceTypes.contains(TraceTypeParameter.TraceType.TRACE)) { - FlatTraceGenerator.generateFromTransactionTrace( - protocolSchedule, transactionTrace, block, new AtomicInteger(), false) - .forEachOrdered(trace -> builder.addTrace((FlatTrace) trace)); - } - - if (traceTypes.contains(VM_TRACE)) { - new VmTraceGenerator(transactionTrace) - .generateTraceStream() - .forEachOrdered(vmTrace -> builder.vmTrace((VmTrace) vmTrace)); - } - - return MAPPER_IGNORE_REVERT_REASON.valueToTree(builder.build()); - } - - private TransactionValidationParams buildTransactionValidationParams() { - return ImmutableTransactionValidationParams.builder() - .from(TransactionValidationParams.transactionSimulator()) - .build(); - } - - private TraceOptions buildTraceOptions(final Set traceTypes) { - return new TraceOptions( - traceTypes.contains(TraceTypeParameter.TraceType.STATE_DIFF), - false, - traceTypes.contains(TraceTypeParameter.TraceType.TRACE) - || traceTypes.contains(TraceTypeParameter.TraceType.VM_TRACE)); + return getTraceCallResult( + protocolSchedule, traceTypes, maybeSimulatorResult, transactionTrace, block); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceTransaction.java index d1fadef4bf7..9c972e9966b 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceTransaction.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceTransaction.java @@ -17,39 +17,20 @@ 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.processor.BlockTrace; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTracer; -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.tracing.flat.FlatTraceGenerator; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; -import org.hyperledger.besu.ethereum.api.query.TransactionWithMetadata; -import org.hyperledger.besu.ethereum.core.Block; -import org.hyperledger.besu.ethereum.debug.TraceOptions; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.vm.DebugOperationTracer; -import java.util.Collections; import java.util.function.Supplier; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; - -public class TraceTransaction implements JsonRpcMethod { - private final Supplier blockTracerSupplier; - - private final BlockchainQueries blockchainQueries; - private final ProtocolSchedule protocolSchedule; - +public class TraceTransaction extends AbstractTraceByHash implements JsonRpcMethod { public TraceTransaction( final Supplier blockTracerSupplier, final ProtocolSchedule protocolSchedule, final BlockchainQueries blockchainQueries) { - this.blockTracerSupplier = blockTracerSupplier; - this.blockchainQueries = blockchainQueries; - this.protocolSchedule = protocolSchedule; + super(blockTracerSupplier, blockchainQueries, protocolSchedule); } @Override @@ -61,52 +42,7 @@ public String getName() { public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { final Hash transactionHash = requestContext.getRequiredParameter(0, Hash.class); return new JsonRpcSuccessResponse( - requestContext.getRequest().getId(), resultByTransactionHash(transactionHash)); - } - - private Object resultByTransactionHash(final Hash transactionHash) { - return blockchainQueries - .transactionByHash(transactionHash) - .flatMap(TransactionWithMetadata::getBlockNumber) - .flatMap(blockNumber -> blockchainQueries.getBlockchain().getBlockByNumber(blockNumber)) - .map((block) -> traceBlock(block, transactionHash)) - .orElse(emptyResult()); - } - - private Object traceBlock(final Block block, final Hash transactionHash) { - if (block == null || block.getBody().getTransactions().isEmpty()) { - return emptyResult(); - } - final TransactionTrace transactionTrace = - blockTracerSupplier - .get() - .trace(block, new DebugOperationTracer(new TraceOptions(false, false, true))) - .map(BlockTrace::getTransactionTraces) - .orElse(Collections.emptyList()) - .stream() - .filter(trxTrace -> trxTrace.getTransaction().getHash().equals(transactionHash)) - .findFirst() - .orElseThrow(); - return generateTracesFromTransactionTraceAndBlock(protocolSchedule, transactionTrace, block); - } - - private JsonNode generateTracesFromTransactionTraceAndBlock( - final ProtocolSchedule protocolSchedule, - final TransactionTrace transactionTrace, - final Block block) { - final ObjectMapper mapper = new ObjectMapper(); - - final ArrayNode resultArrayNode = mapper.createArrayNode(); - - FlatTraceGenerator.generateFromTransactionTraceAndBlock( - protocolSchedule, transactionTrace, block) - .forEachOrdered(resultArrayNode::addPOJO); - - return resultArrayNode; - } - - private Object emptyResult() { - final ObjectMapper mapper = new ObjectMapper(); - return mapper.createArrayNode(); + requestContext.getRequest().getId(), + arrayNodeFromTraceStream(resultByTransactionHash(transactionHash))); } }