Skip to content

Commit

Permalink
Feature/trace callmany (hyperledger#3468)
Browse files Browse the repository at this point in the history
* unit test + working code + test spec files

Signed-off-by: Frank Li <b439988l@gmail.com>

Signed-off-by: Stefan Pingel <stefan.pingel@consensys.net>

Co-authored-by: Frank Li <b439988l@gmail.com>
  • Loading branch information
2 people authored and eum602 committed Nov 3, 2023
1 parent 99acb65 commit 6d8aead
Show file tree
Hide file tree
Showing 117 changed files with 22,972 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ public enum RpcMethod {
RPC_MODULES("rpc_modules"),
TRACE_BLOCK("trace_block"),
TRACE_CALL("trace_call"),
TRACE_CALL_MANY("trace_callMany"),
TRACE_GET("trace_get"),
TRACE_FILTER("trace_filter"),
TRACE_RAW_TRANSACTION("trace_rawTransaction"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,23 @@

public abstract class AbstractBlockParameterMethod implements JsonRpcMethod {

protected final Supplier<BlockchainQueries> blockchainQueries;
protected final Supplier<BlockchainQueries> blockchainQueriesSupplier;

protected AbstractBlockParameterMethod(final BlockchainQueries blockchainQueries) {
this(Suppliers.ofInstance(blockchainQueries));
}

protected AbstractBlockParameterMethod(final Supplier<BlockchainQueries> blockchainQueries) {
this.blockchainQueries = blockchainQueries;
protected AbstractBlockParameterMethod(
final Supplier<BlockchainQueries> blockchainQueriesSupplier) {
this.blockchainQueriesSupplier = blockchainQueriesSupplier;
}

protected abstract BlockParameter blockParameter(JsonRpcRequestContext request);

protected abstract Object resultByBlockNumber(JsonRpcRequestContext request, long blockNumber);

protected BlockchainQueries getBlockchainQueries() {
return blockchainQueries.get();
return blockchainQueriesSupplier.get();
}

protected Object pendingResult(final JsonRpcRequestContext request) {
Expand All @@ -53,7 +54,7 @@ protected Object pendingResult(final JsonRpcRequestContext request) {
}

protected Object latestResult(final JsonRpcRequestContext request) {
return resultByBlockNumber(request, blockchainQueries.get().headBlockNumber());
return resultByBlockNumber(request, blockchainQueriesSupplier.get().headBlockNumber());
}

protected Object findResultByParamType(final JsonRpcRequestContext request) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,17 @@ protected Object resultByBlockNumber(
@Override
protected Object latestResult(final JsonRpcRequestContext request) {

final long headBlockNumber = blockchainQueries.get().headBlockNumber();
Blockchain chain = blockchainQueries.get().getBlockchain();
final long headBlockNumber = blockchainQueriesSupplier.get().headBlockNumber();
Blockchain chain = blockchainQueriesSupplier.get().getBlockchain();
BlockHeader headHeader = chain.getBlockHeader(headBlockNumber).orElse(null);

Hash block = headHeader.getHash();
Hash stateRoot = headHeader.getStateRoot();

if (blockchainQueries.get().getWorldStateArchive().isWorldStateAvailable(stateRoot, block)) {
if (blockchainQueriesSupplier
.get()
.getWorldStateArchive()
.isWorldStateAvailable(stateRoot, block)) {
if (this.synchronizer.getSyncStatus().isEmpty()) { // we are already in sync
return resultByBlockNumber(request, headBlockNumber);
} else { // out of sync, return highest pulled block
Expand All @@ -97,7 +100,8 @@ protected Object latestResult(final JsonRpcRequestContext request) {

LOGGER.trace("no world state available for block {} returning genesis", headBlockNumber);
return resultByBlockNumber(
request, blockchainQueries.get().getBlockchain().getGenesisBlock().getHeader().getNumber());
request,
blockchainQueriesSupplier.get().getBlockchain().getGenesisBlock().getHeader().getNumber());
}

private BlockResult transactionComplete(final long blockNumber) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@
import java.util.Optional;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TraceCall extends AbstractTraceByBlock implements JsonRpcMethod {
private static final Logger LOG = LoggerFactory.getLogger(TraceCall.class);

public TraceCall(
final BlockchainQueries blockchainQueries,
final ProtocolSchedule protocolSchedule,
Expand All @@ -57,7 +62,7 @@ protected Object resultByBlockNumber(
requestContext.getRequiredParameter(1, TraceTypeParameter.class);

final Optional<BlockHeader> maybeBlockHeader =
blockchainQueries.get().getBlockHeaderByNumber(blockNumber);
blockchainQueriesSupplier.get().getBlockHeaderByNumber(blockNumber);

if (maybeBlockHeader.isEmpty()) {
return new JsonRpcErrorResponse(requestContext.getRequest().getId(), BLOCK_NOT_FOUND);
Expand All @@ -73,16 +78,24 @@ protected Object resultByBlockNumber(
maybeBlockHeader.get());

if (maybeSimulatorResult.isEmpty()) {
LOG.error(
"Empty simulator result, call params: {}, blockHeader: {} ",
JsonCallParameterUtil.validateAndGetCallParams(requestContext),
maybeBlockHeader.get());
return new JsonRpcErrorResponse(requestContext.getRequest().getId(), INTERNAL_ERROR);
}
final TransactionSimulatorResult simulatorResult = maybeSimulatorResult.get();

if (simulatorResult.isInvalid()) {
LOG.error(String.format("Invalid simulator result %s", maybeSimulatorResult));
return new JsonRpcErrorResponse(requestContext.getRequest().getId(), INTERNAL_ERROR);
}

final TransactionTrace transactionTrace =
new TransactionTrace(
maybeSimulatorResult.get().getTransaction(),
maybeSimulatorResult.get().getResult(),
tracer.getTraceFrames());
simulatorResult.getTransaction(), simulatorResult.getResult(), tracer.getTraceFrames());

final Block block = blockchainQueries.get().getBlockchain().getChainHeadBlock();
final Block block = blockchainQueriesSupplier.get().getBlockchain().getChainHeadBlock();

return getTraceCallResult(
protocolSchedule, traceTypes, maybeSimulatorResult, transactionTrace, block);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* 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.response.JsonRpcError.BLOCK_NOT_FOUND;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.INTERNAL_ERROR;

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.parameters.BlockParameter;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.TraceCallManyParameter;
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.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.flat.FlatTrace;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.flat.MixInIgnoreRevertReason;
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.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulator;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulatorResult;
import org.hyperledger.besu.ethereum.vm.DebugOperationTracer;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TraceCallMany extends TraceCall implements JsonRpcMethod {

private static final Logger LOG = LoggerFactory.getLogger(TraceCallMany.class);
private static final ObjectMapper MAPPER_IGNORE_REVERT_REASON = new ObjectMapper();

public TraceCallMany(
final BlockchainQueries blockchainQueries,
final ProtocolSchedule protocolSchedule,
final TransactionSimulator transactionSimulator) {
super(blockchainQueries, protocolSchedule, transactionSimulator);

MAPPER_IGNORE_REVERT_REASON.addMixIn(FlatTrace.class, MixInIgnoreRevertReason.class);
}

@Override
public String getName() {
return transactionSimulator != null ? RpcMethod.TRACE_CALL_MANY.getMethodName() : null;
}

@Override
protected BlockParameter blockParameter(final JsonRpcRequestContext request) {
final Optional<BlockParameter> maybeBlockParameter =
request.getOptionalParameter(2, BlockParameter.class);

if (maybeBlockParameter.isPresent()) {
return maybeBlockParameter.get();
}

return BlockParameter.LATEST;
}

@Override
protected Object resultByBlockNumber(
final JsonRpcRequestContext requestContext, final long blockNumber) {

if (requestContext.getRequest().getParamLength() != 2) {
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(), JsonRpcError.INVALID_PARAMS);
}

final TraceCallManyParameter[] transactionsAndTraceTypeParameters;
try {
transactionsAndTraceTypeParameters =
requestContext.getRequiredParameter(0, TraceCallManyParameter[].class);
} catch (final Exception e) {
LOG.error("Error parsing trace call many parameter: {}", e.getLocalizedMessage());
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(), JsonRpcError.INVALID_PARAMS);
}

final Optional<BlockHeader> maybeBlockHeader =
blockchainQueriesSupplier.get().getBlockHeaderByNumber(blockNumber);

if (maybeBlockHeader.isEmpty()) {
return new JsonRpcErrorResponse(requestContext.getRequest().getId(), BLOCK_NOT_FOUND);
}
final BlockHeader blockHeader = maybeBlockHeader.get();

final List<JsonNode> traceCallResults = new ArrayList<>();
final WorldUpdater updater = transactionSimulator.getWorldUpdater(blockHeader);
try {
Arrays.stream(transactionsAndTraceTypeParameters)
.forEachOrdered(
param -> {
final WorldUpdater localUpdater = updater.updater();
traceCallResults.add(
getSingleCallResult(
param.getTuple().getJsonCallParameter(),
param.getTuple().getTraceTypeParameter(),
blockHeader,
localUpdater));
localUpdater.commit();
});
} catch (final TransactionInvalidException e) {
LOG.error("Invalid transaction simulator result");
return new JsonRpcErrorResponse(requestContext.getRequest().getId(), INTERNAL_ERROR);
} catch (final EmptySimulatorResultException e) {
LOG.error(
"Empty simulator result, call params: {}, blockHeader: {} ",
JsonCallParameterUtil.validateAndGetCallParams(requestContext),
blockHeader);
return new JsonRpcErrorResponse(requestContext.getRequest().getId(), INTERNAL_ERROR);
} catch (final Exception e) {
return new JsonRpcErrorResponse(requestContext.getRequest().getId(), INTERNAL_ERROR);
}
return traceCallResults;
}

private JsonNode getSingleCallResult(
final JsonCallParameter callParameter,
final TraceTypeParameter traceTypeParameter,
final BlockHeader header,
final WorldUpdater worldUpdater) {
final Set<TraceTypeParameter.TraceType> traceTypes = traceTypeParameter.getTraceTypes();
final DebugOperationTracer tracer = new DebugOperationTracer(buildTraceOptions(traceTypes));
final Optional<TransactionSimulatorResult> maybeSimulatorResult =
transactionSimulator.processWithWorldUpdater(
callParameter, buildTransactionValidationParams(), tracer, header, worldUpdater);

LOG.trace("Executing {} call for transaction {}", traceTypeParameter, callParameter);
if (maybeSimulatorResult.isEmpty()) {
throw new EmptySimulatorResultException();
}
final TransactionSimulatorResult simulatorResult = maybeSimulatorResult.get();
if (simulatorResult.isInvalid()) {
throw new TransactionInvalidException();
}

final TransactionTrace transactionTrace =
new TransactionTrace(
simulatorResult.getTransaction(), simulatorResult.getResult(), tracer.getTraceFrames());

final Block block = blockchainQueriesSupplier.get().getBlockchain().getChainHeadBlock();

return getTraceCallResult(
protocolSchedule, traceTypes, maybeSimulatorResult, transactionTrace, block);
}

private static class TransactionInvalidException extends RuntimeException {
TransactionInvalidException() {
super();
}
}

private static class EmptySimulatorResultException extends RuntimeException {
EmptySimulatorResultException() {
super();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) {
long currentBlockNumber = fromBlock;
while (currentBlockNumber <= toBlock && !resultArrayNode.isFull()) {
Optional<Block> blockByNumber =
blockchainQueries.get().getBlockchain().getBlockByNumber(currentBlockNumber);
blockchainQueriesSupplier.get().getBlockchain().getBlockByNumber(currentBlockNumber);
blockByNumber.ifPresent(
block -> resultArrayNode.addAll(traceBlock(block, Optional.of(filterParameter))));
currentBlockNumber++;
Expand Down Expand Up @@ -150,7 +150,7 @@ private long resolveBlockNumber(final BlockParameter param) {
if (param.getNumber().isPresent()) {
return param.getNumber().get();
} else if (param.isLatest()) {
return blockchainQueries.get().headBlockNumber();
return blockchainQueriesSupplier.get().headBlockNumber();
} else {
throw new IllegalStateException("Unknown block parameter type.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) {

final Set<TraceTypeParameter.TraceType> traceTypes = traceTypeParameter.getTraceTypes();
final DebugOperationTracer tracer = new DebugOperationTracer(buildTraceOptions(traceTypes));
final long headBlockNumber = blockchainQueries.get().headBlockNumber();
final long headBlockNumber = blockchainQueriesSupplier.get().headBlockNumber();
final Optional<TransactionSimulatorResult> maybeSimulatorResult =
transactionSimulator.process(
CallParameter.fromTransaction(transaction),
Expand All @@ -104,7 +104,7 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) {
maybeSimulatorResult.get().getResult(),
tracer.getTraceFrames());
final Optional<Block> maybeBlock =
blockchainQueries.get().getBlockchain().getBlockByNumber(headBlockNumber);
blockchainQueriesSupplier.get().getBlockchain().getBlockByNumber(headBlockNumber);

if (maybeBlock.isEmpty()) {
return new JsonRpcErrorResponse(requestContext.getRequest().getId(), INTERNAL_ERROR);
Expand Down
Loading

0 comments on commit 6d8aead

Please sign in to comment.