From 35e9a9e8ad7fb407bf61b7a1a45d4b7576da5a50 Mon Sep 17 00:00:00 2001 From: Kasper Ziemianek Date: Mon, 15 Apr 2019 04:03:24 +0200 Subject: [PATCH] Add block trace RPC methods (#1088) Implements debug_traceBlock, debug_traceBlockByHash and debug_traceBlockByNumber methods. --- .../jsonrpc/JsonRpcMethodsFactory.java | 14 +- .../internal/methods/DebugTraceBlock.java | 91 ++++++++++++ .../methods/DebugTraceBlockByHash.java | 61 ++++++++ .../methods/DebugTraceBlockByNumber.java | 73 +++++++++ .../methods/DebugTraceTransaction.java | 19 ++- .../TransactionTraceParams.java | 2 +- .../internal/processor/BlockReplay.java | 140 +++++++++++++----- .../internal/processor/BlockTrace.java | 28 ++++ .../internal/processor/BlockTracer.java | 57 +++++++ .../internal/response/JsonRpcError.java | 3 + .../results/DebugTraceTransactionResult.java | 6 + .../methods/DebugStorageRangeAtTest.java | 4 +- .../methods/DebugTraceBlockByHashTest.java | 94 ++++++++++++ .../methods/DebugTraceBlockByNumberTest.java | 98 ++++++++++++ .../internal/methods/DebugTraceBlockTest.java | 140 ++++++++++++++++++ 15 files changed, 777 insertions(+), 53 deletions(-) create mode 100644 ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlock.java create mode 100644 ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockByHash.java create mode 100644 ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockByNumber.java rename ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/{processor => parameters}/TransactionTraceParams.java (95%) create mode 100644 ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/processor/BlockTrace.java create mode 100644 ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/processor/BlockTracer.java create mode 100644 ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockByHashTest.java create mode 100644 ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockByNumberTest.java create mode 100644 ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockTest.java diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java index 5c0af059fa..a53f5dc23f 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java @@ -26,6 +26,9 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.AdminRemovePeer; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.DebugMetrics; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.DebugStorageRangeAt; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.DebugTraceBlock; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.DebugTraceBlockByHash; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.DebugTraceBlockByNumber; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.DebugTraceTransaction; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.EthAccounts; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.EthBlockNumber; @@ -87,10 +90,12 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.privacy.EeaSendRawTransaction; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockReplay; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockTracer; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.TransactionTracer; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockchainQueries; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.results.BlockResultFactory; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; +import tech.pegasys.pantheon.ethereum.mainnet.ScheduleBasedBlockHashFunction; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController; @@ -236,7 +241,14 @@ public Map methods( new DebugTraceTransaction( blockchainQueries, new TransactionTracer(blockReplay), parameter), new DebugStorageRangeAt(parameter, blockchainQueries, blockReplay), - new DebugMetrics(metricsSystem)); + new DebugMetrics(metricsSystem), + new DebugTraceBlock( + parameter, + new BlockTracer(blockReplay), + ScheduleBasedBlockHashFunction.create(protocolSchedule), + blockchainQueries), + new DebugTraceBlockByNumber(parameter, new BlockTracer(blockReplay), blockchainQueries), + new DebugTraceBlockByHash(parameter, new BlockTracer(blockReplay))); } if (rpcApis.contains(RpcApis.NET)) { addMethods( diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlock.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlock.java new file mode 100644 index 0000000000..221ffef7d4 --- /dev/null +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlock.java @@ -0,0 +1,91 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * 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. + */ +package tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods; + +import tech.pegasys.pantheon.ethereum.core.Block; +import tech.pegasys.pantheon.ethereum.core.BlockHashFunction; +import tech.pegasys.pantheon.ethereum.debug.TraceOptions; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.TransactionTraceParams; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockTrace; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockTracer; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockchainQueries; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcError; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcErrorResponse; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.results.DebugTraceTransactionResult; +import tech.pegasys.pantheon.ethereum.rlp.RLP; +import tech.pegasys.pantheon.ethereum.rlp.RLPException; +import tech.pegasys.pantheon.ethereum.vm.DebugOperationTracer; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +import java.util.Collection; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class DebugTraceBlock implements JsonRpcMethod { + + private static final Logger LOG = LogManager.getLogger(); + private final JsonRpcParameter parameters; + private final BlockTracer blockTracer; + private final BlockHashFunction blockHashFunction; + private final BlockchainQueries blockchain; + + public DebugTraceBlock( + final JsonRpcParameter parameters, + final BlockTracer blockTracer, + final BlockHashFunction blockHashFunction, + final BlockchainQueries blockchain) { + this.parameters = parameters; + this.blockTracer = blockTracer; + this.blockHashFunction = blockHashFunction; + this.blockchain = blockchain; + } + + @Override + public String getName() { + return "debug_traceBlock"; + } + + @Override + public JsonRpcResponse response(final JsonRpcRequest request) { + final String input = parameters.required(request.getParams(), 0, String.class); + final Block block; + try { + block = Block.readFrom(RLP.input(BytesValue.fromHexString(input)), this.blockHashFunction); + } catch (final RLPException e) { + LOG.debug("Failed to parse block RLP", e); + return new JsonRpcErrorResponse(request.getId(), JsonRpcError.INVALID_PARAMS); + } + final TraceOptions traceOptions = + parameters + .optional(request.getParams(), 1, TransactionTraceParams.class) + .map(TransactionTraceParams::traceOptions) + .orElse(TraceOptions.DEFAULT); + + if (this.blockchain.blockByHash(block.getHeader().getParentHash()).isPresent()) { + final Collection results = + blockTracer + .trace(block, new DebugOperationTracer(traceOptions)) + .map(BlockTrace::getTransactionTraces) + .map(DebugTraceTransactionResult::of) + .orElse(null); + return new JsonRpcSuccessResponse(request.getId(), results); + } else { + return new JsonRpcErrorResponse(request.getId(), JsonRpcError.PARENT_BLOCK_NOT_FOUND); + } + } +} diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockByHash.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockByHash.java new file mode 100644 index 0000000000..b8a5600c86 --- /dev/null +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockByHash.java @@ -0,0 +1,61 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * 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. + */ +package tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods; + +import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.ethereum.debug.TraceOptions; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.TransactionTraceParams; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockTrace; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockTracer; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.results.DebugTraceTransactionResult; +import tech.pegasys.pantheon.ethereum.vm.DebugOperationTracer; + +import java.util.Collection; + +public class DebugTraceBlockByHash implements JsonRpcMethod { + + private final JsonRpcParameter parameters; + private final BlockTracer blockTracer; + + public DebugTraceBlockByHash(final JsonRpcParameter parameters, final BlockTracer blockTracer) { + this.parameters = parameters; + this.blockTracer = blockTracer; + } + + @Override + public String getName() { + return "debug_traceBlockByHash"; + } + + @Override + public JsonRpcResponse response(final JsonRpcRequest request) { + final Hash blockHash = parameters.required(request.getParams(), 0, Hash.class); + final TraceOptions traceOptions = + parameters + .optional(request.getParams(), 1, TransactionTraceParams.class) + .map(TransactionTraceParams::traceOptions) + .orElse(TraceOptions.DEFAULT); + + final Collection results = + blockTracer + .trace(blockHash, new DebugOperationTracer(traceOptions)) + .map(BlockTrace::getTransactionTraces) + .map(DebugTraceTransactionResult::of) + .orElse(null); + return new JsonRpcSuccessResponse(request.getId(), results); + } +} diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockByNumber.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockByNumber.java new file mode 100644 index 0000000000..2491882347 --- /dev/null +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockByNumber.java @@ -0,0 +1,73 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * 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. + */ +package tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods; + +import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.ethereum.debug.TraceOptions; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.TransactionTraceParams; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockTrace; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockTracer; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockchainQueries; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.results.DebugTraceTransactionResult; +import tech.pegasys.pantheon.ethereum.vm.DebugOperationTracer; + +import java.util.Collection; +import java.util.Optional; + +public class DebugTraceBlockByNumber implements JsonRpcMethod { + + private final JsonRpcParameter parameters; + private final BlockTracer blockTracer; + private final BlockchainQueries blockchain; + + public DebugTraceBlockByNumber( + final JsonRpcParameter parameters, + final BlockTracer blockTracer, + final BlockchainQueries blockchain) { + this.parameters = parameters; + this.blockTracer = blockTracer; + this.blockchain = blockchain; + } + + @Override + public String getName() { + return "debug_traceBlockByNumber"; + } + + @Override + public JsonRpcResponse response(final JsonRpcRequest request) { + final Long blockNumber = parameters.required(request.getParams(), 0, Long.class); + final Optional blockHash = this.blockchain.getBlockHashByNumber(blockNumber); + final TraceOptions traceOptions = + parameters + .optional(request.getParams(), 1, TransactionTraceParams.class) + .map(TransactionTraceParams::traceOptions) + .orElse(TraceOptions.DEFAULT); + + final Collection results = + blockHash + .map( + hash -> + blockTracer + .trace(hash, new DebugOperationTracer(traceOptions)) + .map(BlockTrace::getTransactionTraces) + .map(DebugTraceTransactionResult::of)) + .orElse(null) + .get(); + return new JsonRpcSuccessResponse(request.getId(), results); + } +} diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceTransaction.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceTransaction.java index f78c6125aa..f3a7080036 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceTransaction.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceTransaction.java @@ -16,7 +16,7 @@ import tech.pegasys.pantheon.ethereum.debug.TraceOptions; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; -import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.TransactionTraceParams; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.TransactionTraceParams; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.TransactionTracer; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockchainQueries; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.TransactionWithMetadata; @@ -50,13 +50,16 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequest request) { final Hash hash = parameters.required(request.getParams(), 0, Hash.class); - final Optional transactionTraceParams = - parameters.optional(request.getParams(), 1, TransactionTraceParams.class); final Optional transactionWithMetadata = blockchain.transactionByHash(hash); if (transactionWithMetadata.isPresent()) { - DebugTraceTransactionResult debugTraceTransactionResult = - debugTraceTransactionResult(hash, transactionWithMetadata.get(), transactionTraceParams); + final TraceOptions traceOptions = + parameters + .optional(request.getParams(), 1, TransactionTraceParams.class) + .map(TransactionTraceParams::traceOptions) + .orElse(TraceOptions.DEFAULT); + final DebugTraceTransactionResult debugTraceTransactionResult = + debugTraceTransactionResult(hash, transactionWithMetadata.get(), traceOptions); return new JsonRpcSuccessResponse(request.getId(), debugTraceTransactionResult); } else { @@ -67,12 +70,8 @@ public JsonRpcResponse response(final JsonRpcRequest request) { private DebugTraceTransactionResult debugTraceTransactionResult( final Hash hash, final TransactionWithMetadata transactionWithMetadata, - final Optional transactionTraceParams) { + final TraceOptions traceOptions) { final Hash blockHash = transactionWithMetadata.getBlockHash(); - final TraceOptions traceOptions = - transactionTraceParams - .map(TransactionTraceParams::traceOptions) - .orElse(TraceOptions.DEFAULT); final DebugOperationTracer execTracer = new DebugOperationTracer(traceOptions); diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/processor/TransactionTraceParams.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/TransactionTraceParams.java similarity index 95% rename from ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/processor/TransactionTraceParams.java rename to ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/TransactionTraceParams.java index 9b62a0156c..e453efa9c8 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/processor/TransactionTraceParams.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/TransactionTraceParams.java @@ -10,7 +10,7 @@ * 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. */ -package tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor; +package tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters; import tech.pegasys.pantheon.ethereum.debug.TraceOptions; diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/processor/BlockReplay.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/processor/BlockReplay.java index 2b2d56795e..64602a0bfb 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/processor/BlockReplay.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/processor/BlockReplay.java @@ -13,6 +13,7 @@ package tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor; import tech.pegasys.pantheon.ethereum.chain.Blockchain; +import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.BlockBody; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.Hash; @@ -24,7 +25,9 @@ import tech.pegasys.pantheon.ethereum.vm.BlockHashLookup; import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; +import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; public class BlockReplay { @@ -41,13 +44,89 @@ public BlockReplay( this.worldStateArchive = worldStateArchive; } + public BlockTrace block(final Block block, final TransactionAction action) { + return performActionWithBlock( + block.getHeader(), + block.getBody(), + (body, header, blockchain, mutableWorldState, transactionProcessor) -> { + List transactionTraces = + body.getTransactions().stream() + .map( + transaction -> + action.performAction( + transaction, + header, + blockchain, + mutableWorldState, + transactionProcessor)) + .collect(Collectors.toList()); + return Optional.of(new BlockTrace(transactionTraces)); + }) + .orElse(null); + } + + public BlockTrace block(final Hash blockHash, final TransactionAction action) { + return getBlock(blockHash).map(block -> block(block, action)).orElse(null); + } + public Optional beforeTransactionInBlock( - final Hash blockHash, final Hash transactionHash, final Action action) { - final BlockHeader header = blockchain.getBlockHeader(blockHash).orElse(null); + final Hash blockHash, final Hash transactionHash, final TransactionAction action) { + return performActionWithBlock( + blockHash, + (body, header, blockchain, mutableWorldState, transactionProcessor) -> { + final BlockHashLookup blockHashLookup = new BlockHashLookup(header, blockchain); + for (final Transaction transaction : body.getTransactions()) { + if (transaction.hash().equals(transactionHash)) { + return Optional.of( + action.performAction( + transaction, header, blockchain, mutableWorldState, transactionProcessor)); + } else { + final ProtocolSpec spec = protocolSchedule.getByBlockNumber(header.getNumber()); + transactionProcessor.processTransaction( + blockchain, + mutableWorldState.updater(), + header, + transaction, + spec.getMiningBeneficiaryCalculator().calculateBeneficiary(header), + blockHashLookup, + false); + } + } + return Optional.empty(); + }); + } + + public Optional afterTransactionInBlock( + final Hash blockHash, final Hash transactionHash, final TransactionAction action) { + return beforeTransactionInBlock( + blockHash, + transactionHash, + (transaction, blockHeader, blockchain, worldState, transactionProcessor) -> { + final ProtocolSpec spec = protocolSchedule.getByBlockNumber(blockHeader.getNumber()); + transactionProcessor.processTransaction( + blockchain, + worldState.updater(), + blockHeader, + transaction, + spec.getMiningBeneficiaryCalculator().calculateBeneficiary(blockHeader), + new BlockHashLookup(blockHeader, blockchain), + false); + return action.performAction( + transaction, blockHeader, blockchain, worldState, transactionProcessor); + }); + } + + private Optional performActionWithBlock( + final Hash blockHash, final BlockAction action) { + return getBlock(blockHash) + .flatMap(block -> performActionWithBlock(block.getHeader(), block.getBody(), action)); + } + + private Optional performActionWithBlock( + final BlockHeader header, final BlockBody body, final BlockAction action) { if (header == null) { return Optional.empty(); } - final BlockBody body = blockchain.getBlockBody(header.getHash()).orElse(null); if (body == null) { return Optional.empty(); } @@ -62,49 +141,32 @@ public Optional beforeTransactionInBlock( if (mutableWorldState == null) { return Optional.empty(); } - final BlockHashLookup blockHashLookup = new BlockHashLookup(header, blockchain); - for (final Transaction transaction : body.getTransactions()) { - if (transaction.hash().equals(transactionHash)) { - return Optional.of( - action.performAction( - transaction, header, blockchain, mutableWorldState, transactionProcessor)); - } else { - final ProtocolSpec spec = protocolSchedule.getByBlockNumber(header.getNumber()); - transactionProcessor.processTransaction( - blockchain, - mutableWorldState.updater(), - header, - transaction, - spec.getMiningBeneficiaryCalculator().calculateBeneficiary(header), - blockHashLookup, - false); + return action.perform(body, header, blockchain, mutableWorldState, transactionProcessor); + } + + private Optional getBlock(final Hash blockHash) { + final BlockHeader blockHeader = blockchain.getBlockHeader(blockHash).orElse(null); + if (blockHeader != null) { + final BlockBody blockBody = blockchain.getBlockBody(blockHeader.getHash()).orElse(null); + if (blockBody != null) { + return Optional.of(new Block(blockHeader, blockBody)); } } return Optional.empty(); } - public Optional afterTransactionInBlock( - final Hash blockHash, final Hash transactionHash, final Action action) { - return beforeTransactionInBlock( - blockHash, - transactionHash, - (transaction, blockHeader, blockchain, worldState, transactionProcessor) -> { - final ProtocolSpec spec = protocolSchedule.getByBlockNumber(blockHeader.getNumber()); - transactionProcessor.processTransaction( - blockchain, - worldState.updater(), - blockHeader, - transaction, - spec.getMiningBeneficiaryCalculator().calculateBeneficiary(blockHeader), - new BlockHashLookup(blockHeader, blockchain), - false); - return action.performAction( - transaction, blockHeader, blockchain, worldState, transactionProcessor); - }); + @FunctionalInterface + private interface BlockAction { + Optional perform( + BlockBody body, + BlockHeader blockHeader, + Blockchain blockchain, + MutableWorldState worldState, + TransactionProcessor transactionProcessor); } - public interface Action { - + @FunctionalInterface + public interface TransactionAction { T performAction( Transaction transaction, BlockHeader blockHeader, diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/processor/BlockTrace.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/processor/BlockTrace.java new file mode 100644 index 0000000000..4e9a5ef3cd --- /dev/null +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/processor/BlockTrace.java @@ -0,0 +1,28 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * 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. + */ +package tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor; + +import java.util.List; + +public class BlockTrace { + + private final List transactionTraces; + + public BlockTrace(final List transactionTraces) { + this.transactionTraces = transactionTraces; + } + + public List getTransactionTraces() { + return transactionTraces; + } +} diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/processor/BlockTracer.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/processor/BlockTracer.java new file mode 100644 index 0000000000..6cedc9d217 --- /dev/null +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/processor/BlockTracer.java @@ -0,0 +1,57 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * 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. + */ +package tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor; + +import tech.pegasys.pantheon.ethereum.core.Block; +import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockReplay.TransactionAction; +import tech.pegasys.pantheon.ethereum.mainnet.TransactionProcessor; +import tech.pegasys.pantheon.ethereum.vm.BlockHashLookup; +import tech.pegasys.pantheon.ethereum.vm.DebugOperationTracer; + +import java.util.Optional; + +/** Used to produce debug traces of blocks */ +public class BlockTracer { + + private final BlockReplay blockReplay; + + public BlockTracer(final BlockReplay blockReplay) { + this.blockReplay = blockReplay; + } + + public Optional trace(final Hash blockHash, final DebugOperationTracer tracer) { + return Optional.of(blockReplay.block(blockHash, prepareReplayAction(tracer))); + } + + public Optional trace(final Block block, final DebugOperationTracer tracer) { + return Optional.of(blockReplay.block(block, prepareReplayAction(tracer))); + } + + private TransactionAction prepareReplayAction( + final DebugOperationTracer tracer) { + return (transaction, header, blockchain, mutableWorldState, transactionProcessor) -> { + final TransactionProcessor.Result result = + transactionProcessor.processTransaction( + blockchain, + mutableWorldState.updater(), + header, + transaction, + header.getCoinbase(), + tracer, + new BlockHashLookup(header, blockchain), + false); + return new TransactionTrace(transaction, result, tracer.getTraceFrames()); + }; + } +} diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java index 95f8c5bed0..6c73ba1d2e 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java @@ -52,6 +52,9 @@ public enum JsonRpcError { // Wallet errors COINBASE_NOT_SPECIFIED(-32000, "Coinbase must be explicitly specified"), + // Debug failures + PARENT_BLOCK_NOT_FOUND(-32000, "Parent block not found"), + // Permissioning/Account whitelist errors ACCOUNT_WHITELIST_NOT_ENABLED(-32000, "Account whitelisting has not been enabled"), ACCOUNT_WHITELIST_EMPTY_ENTRY(-32000, "Request contains an empty list of accounts"), diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/results/DebugTraceTransactionResult.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/results/DebugTraceTransactionResult.java index 664d106739..d6416e3c6d 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/results/DebugTraceTransactionResult.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/results/DebugTraceTransactionResult.java @@ -15,6 +15,7 @@ import tech.pegasys.pantheon.ethereum.debug.TraceFrame; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.TransactionTrace; +import java.util.Collection; import java.util.List; import java.util.stream.Collectors; @@ -39,6 +40,11 @@ public DebugTraceTransactionResult(final TransactionTrace transactionTrace) { failed = !transactionTrace.getResult().isSuccessful(); } + public static Collection of( + final Collection traces) { + return traces.stream().map(DebugTraceTransactionResult::new).collect(Collectors.toList()); + } + private static StructLog createStructLog(final TraceFrame frame) { return frame.getExceptionalHaltReasons().isEmpty() ? new StructLog(frame) diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugStorageRangeAtTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugStorageRangeAtTest.java index f935fb2202..6095088835 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugStorageRangeAtTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugStorageRangeAtTest.java @@ -30,7 +30,7 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockReplay; -import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockReplay.Action; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockReplay.TransactionAction; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockchainQueries; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.TransactionWithMetadata; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -115,7 +115,7 @@ public void shouldRetrieveStorageRange() { private Object callAction(final InvocationOnMock invocation) { return Optional.of( - ((Action) invocation.getArgument(2)) + ((TransactionAction) invocation.getArgument(2)) .performAction(transaction, blockHeader, blockchain, worldState, transactionProcessor)); } } diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockByHashTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockByHashTest.java new file mode 100644 index 0000000000..7d23cdb0ad --- /dev/null +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockByHashTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * 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. + */ +package tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import tech.pegasys.pantheon.ethereum.core.Gas; +import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.ethereum.debug.TraceFrame; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockTrace; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockTracer; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.TransactionTrace; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; +import tech.pegasys.pantheon.ethereum.mainnet.TransactionProcessor; +import tech.pegasys.pantheon.ethereum.vm.ExceptionalHaltReason; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +import java.util.Arrays; +import java.util.Collection; +import java.util.EnumSet; +import java.util.Optional; + +import org.junit.Test; + +public class DebugTraceBlockByHashTest { + + private final JsonRpcParameter parameters = new JsonRpcParameter(); + private final BlockTracer blockTracer = mock(BlockTracer.class); + private final DebugTraceBlockByHash debugTraceBlockByHash = + new DebugTraceBlockByHash(parameters, blockTracer); + + private final Hash blockHash = + Hash.fromHexString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + + @Test + public void nameShouldBeDebugTraceBlockByHash() { + assertEquals("debug_traceBlockByHash", debugTraceBlockByHash.getName()); + } + + @Test + public void shouldReturnCorrectResponse() { + final Object[] params = new Object[] {blockHash}; + final JsonRpcRequest request = new JsonRpcRequest("2.0", "debug_traceBlockByHash", params); + + final TraceFrame traceFrame = + new TraceFrame( + 12, + "NONE", + Gas.of(45), + Optional.of(Gas.of(56)), + 2, + EnumSet.noneOf(ExceptionalHaltReason.class), + Optional.empty(), + Optional.empty(), + Optional.empty()); + + final TransactionProcessor.Result transaction1Result = mock(TransactionProcessor.Result.class); + final TransactionProcessor.Result transaction2Result = mock(TransactionProcessor.Result.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(BytesValue.fromHexString("1234")); + when(transaction2Result.getOutput()).thenReturn(BytesValue.fromHexString("1234")); + when(blockTracer.trace(eq(blockHash), any())).thenReturn(Optional.of(blockTrace)); + + final JsonRpcSuccessResponse response = + (JsonRpcSuccessResponse) debugTraceBlockByHash.response(request); + final Collection result = (Collection) response.getResult(); + assertEquals(2, result.size()); + } +} diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockByNumberTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockByNumberTest.java new file mode 100644 index 0000000000..f579aaa5ed --- /dev/null +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockByNumberTest.java @@ -0,0 +1,98 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * 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. + */ +package tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import tech.pegasys.pantheon.ethereum.core.Gas; +import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.ethereum.debug.TraceFrame; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockTrace; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockTracer; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.TransactionTrace; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockchainQueries; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; +import tech.pegasys.pantheon.ethereum.mainnet.TransactionProcessor; +import tech.pegasys.pantheon.ethereum.vm.ExceptionalHaltReason; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +import java.util.Arrays; +import java.util.Collection; +import java.util.EnumSet; +import java.util.Optional; + +import org.junit.Test; + +public class DebugTraceBlockByNumberTest { + + private final JsonRpcParameter parameters = new JsonRpcParameter(); + private final BlockchainQueries blockchain = mock(BlockchainQueries.class); + private final BlockTracer blockTracer = mock(BlockTracer.class); + private final DebugTraceBlockByNumber debugTraceBlockByNumber = + new DebugTraceBlockByNumber(parameters, blockTracer, blockchain); + + private final Hash blockHash = + Hash.fromHexString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + + @Test + public void nameShouldBeDebugTraceBlockByNumber() { + assertEquals("debug_traceBlockByNumber", debugTraceBlockByNumber.getName()); + } + + @Test + public void shouldReturnCorrectResponse() { + Long blockNumber = 1L; + final Object[] params = new Object[] {blockNumber}; + final JsonRpcRequest request = new JsonRpcRequest("2.0", "debug_traceBlockByNumber", params); + + final TraceFrame traceFrame = + new TraceFrame( + 12, + "NONE", + Gas.of(45), + Optional.of(Gas.of(56)), + 2, + EnumSet.noneOf(ExceptionalHaltReason.class), + Optional.empty(), + Optional.empty(), + Optional.empty()); + + final TransactionProcessor.Result transaction1Result = mock(TransactionProcessor.Result.class); + final TransactionProcessor.Result transaction2Result = mock(TransactionProcessor.Result.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(BytesValue.fromHexString("1234")); + when(transaction2Result.getOutput()).thenReturn(BytesValue.fromHexString("1234")); + when(blockchain.getBlockHashByNumber(blockNumber)).thenReturn(Optional.of(blockHash)); + when(blockTracer.trace(eq(blockHash), any())).thenReturn(Optional.of(blockTrace)); + + final JsonRpcSuccessResponse response = + (JsonRpcSuccessResponse) debugTraceBlockByNumber.response(request); + final Collection result = (Collection) response.getResult(); + assertEquals(2, result.size()); + } +} diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockTest.java new file mode 100644 index 0000000000..44bec8d812 --- /dev/null +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockTest.java @@ -0,0 +1,140 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * 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. + */ +package tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import tech.pegasys.pantheon.ethereum.core.Block; +import tech.pegasys.pantheon.ethereum.core.BlockDataGenerator; +import tech.pegasys.pantheon.ethereum.core.Gas; +import tech.pegasys.pantheon.ethereum.debug.TraceFrame; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockTrace; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockTracer; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.TransactionTrace; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockWithMetadata; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockchainQueries; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcError; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcErrorResponse; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; +import tech.pegasys.pantheon.ethereum.mainnet.MainnetBlockHashFunction; +import tech.pegasys.pantheon.ethereum.mainnet.TransactionProcessor; +import tech.pegasys.pantheon.ethereum.vm.ExceptionalHaltReason; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Optional; + +import org.junit.Test; +import org.mockito.Mockito; + +public class DebugTraceBlockTest { + + private final JsonRpcParameter parameters = new JsonRpcParameter(); + private final BlockTracer blockTracer = mock(BlockTracer.class); + private final BlockchainQueries blockchainQueries = mock(BlockchainQueries.class); + private final DebugTraceBlock debugTraceBlock = + new DebugTraceBlock( + parameters, blockTracer, MainnetBlockHashFunction::createHash, blockchainQueries); + + @Test + public void nameShouldBeDebugTraceBlock() { + assertEquals("debug_traceBlock", debugTraceBlock.getName()); + } + + @Test + public void shouldReturnCorrectResponse() { + final Block parentBlock = + new BlockDataGenerator() + .block( + BlockDataGenerator.BlockOptions.create() + .setBlockHashFunction(MainnetBlockHashFunction::createHash)); + final Block block = + new BlockDataGenerator() + .block( + BlockDataGenerator.BlockOptions.create() + .setBlockHashFunction(MainnetBlockHashFunction::createHash) + .setParentHash(parentBlock.getHash())); + + final Object[] params = new Object[] {block.toRlp().toString()}; + final JsonRpcRequest request = new JsonRpcRequest("2.0", "debug_traceBlock", params); + + final TraceFrame traceFrame = + new TraceFrame( + 12, + "NONE", + Gas.of(45), + Optional.of(Gas.of(56)), + 2, + EnumSet.noneOf(ExceptionalHaltReason.class), + Optional.empty(), + Optional.empty(), + Optional.empty()); + + final TransactionProcessor.Result transaction1Result = mock(TransactionProcessor.Result.class); + final TransactionProcessor.Result transaction2Result = mock(TransactionProcessor.Result.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(BytesValue.fromHexString("1234")); + when(transaction2Result.getOutput()).thenReturn(BytesValue.fromHexString("1234")); + when(blockTracer.trace(Mockito.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()))); + + final JsonRpcSuccessResponse response = + (JsonRpcSuccessResponse) debugTraceBlock.response(request); + final Collection result = (Collection) response.getResult(); + assertEquals(2, result.size()); + } + + @Test + public void shouldReturnErrorResponseWhenParentBlockMissing() { + final Block block = + new BlockDataGenerator() + .block( + BlockDataGenerator.BlockOptions.create() + .setBlockHashFunction(MainnetBlockHashFunction::createHash)); + + final Object[] params = new Object[] {block.toRlp().toString()}; + final JsonRpcRequest request = new JsonRpcRequest("2.0", "debug_traceBlock", params); + + when(blockchainQueries.blockByHash(any())).thenReturn(Optional.empty()); + + final JsonRpcErrorResponse response = (JsonRpcErrorResponse) debugTraceBlock.response(request); + assertEquals(JsonRpcError.PARENT_BLOCK_NOT_FOUND, response.getError()); + } +}