Add block trace RPC methods (#1088)

Implements debug_traceBlock, debug_traceBlockByHash and debug_traceBlockByNumber methods.
Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>
pull/2/head
Kasper Ziemianek 6 years ago committed by Adrian Sutton
parent 7a892e9943
commit 6e66e16a62
  1. 14
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java
  2. 91
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlock.java
  3. 61
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockByHash.java
  4. 73
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockByNumber.java
  5. 19
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceTransaction.java
  6. 2
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/TransactionTraceParams.java
  7. 140
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/processor/BlockReplay.java
  8. 28
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/processor/BlockTrace.java
  9. 57
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/processor/BlockTracer.java
  10. 3
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java
  11. 6
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/results/DebugTraceTransactionResult.java
  12. 4
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugStorageRangeAtTest.java
  13. 94
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockByHashTest.java
  14. 98
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockByNumberTest.java
  15. 140
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugTraceBlockTest.java

@ -26,6 +26,9 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.AdminPeers;
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.EeaGetTra
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 class JsonRpcMethodsFactory {
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(

@ -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<DebugTraceTransactionResult> 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);
}
}
}

@ -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<DebugTraceTransactionResult> results =
blockTracer
.trace(blockHash, new DebugOperationTracer(traceOptions))
.map(BlockTrace::getTransactionTraces)
.map(DebugTraceTransactionResult::of)
.orElse(null);
return new JsonRpcSuccessResponse(request.getId(), results);
}
}

@ -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<Hash> blockHash = this.blockchain.getBlockHashByNumber(blockNumber);
final TraceOptions traceOptions =
parameters
.optional(request.getParams(), 1, TransactionTraceParams.class)
.map(TransactionTraceParams::traceOptions)
.orElse(TraceOptions.DEFAULT);
final Collection<DebugTraceTransactionResult> 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);
}
}

@ -16,7 +16,7 @@ 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.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 class DebugTraceTransaction implements JsonRpcMethod {
@Override
public JsonRpcResponse response(final JsonRpcRequest request) {
final Hash hash = parameters.required(request.getParams(), 0, Hash.class);
final Optional<TransactionTraceParams> transactionTraceParams =
parameters.optional(request.getParams(), 1, TransactionTraceParams.class);
final Optional<TransactionWithMetadata> 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 class DebugTraceTransaction implements JsonRpcMethod {
private DebugTraceTransactionResult debugTraceTransactionResult(
final Hash hash,
final TransactionWithMetadata transactionWithMetadata,
final Optional<TransactionTraceParams> 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);

@ -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;

@ -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.mainnet.TransactionProcessor;
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 class BlockReplay {
this.worldStateArchive = worldStateArchive;
}
public BlockTrace block(final Block block, final TransactionAction<TransactionTrace> action) {
return performActionWithBlock(
block.getHeader(),
block.getBody(),
(body, header, blockchain, mutableWorldState, transactionProcessor) -> {
List<TransactionTrace> 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<TransactionTrace> action) {
return getBlock(blockHash).map(block -> block(block, action)).orElse(null);
}
public <T> Optional<T> beforeTransactionInBlock(
final Hash blockHash, final Hash transactionHash, final Action<T> action) {
final BlockHeader header = blockchain.getBlockHeader(blockHash).orElse(null);
final Hash blockHash, final Hash transactionHash, final TransactionAction<T> 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 <T> Optional<T> afterTransactionInBlock(
final Hash blockHash, final Hash transactionHash, final TransactionAction<T> 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 <T> Optional<T> performActionWithBlock(
final Hash blockHash, final BlockAction<T> action) {
return getBlock(blockHash)
.flatMap(block -> performActionWithBlock(block.getHeader(), block.getBody(), action));
}
private <T> Optional<T> performActionWithBlock(
final BlockHeader header, final BlockBody body, final BlockAction<T> 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 class BlockReplay {
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<Block> 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 <T> Optional<T> afterTransactionInBlock(
final Hash blockHash, final Hash transactionHash, final Action<T> 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<T> {
Optional<T> perform(
BlockBody body,
BlockHeader blockHeader,
Blockchain blockchain,
MutableWorldState worldState,
TransactionProcessor transactionProcessor);
}
public interface Action<T> {
@FunctionalInterface
public interface TransactionAction<T> {
T performAction(
Transaction transaction,
BlockHeader blockHeader,

@ -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<TransactionTrace> transactionTraces;
public BlockTrace(final List<TransactionTrace> transactionTraces) {
this.transactionTraces = transactionTraces;
}
public List<TransactionTrace> getTransactionTraces() {
return transactionTraces;
}
}

@ -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<BlockTrace> trace(final Hash blockHash, final DebugOperationTracer tracer) {
return Optional.of(blockReplay.block(blockHash, prepareReplayAction(tracer)));
}
public Optional<BlockTrace> trace(final Block block, final DebugOperationTracer tracer) {
return Optional.of(blockReplay.block(block, prepareReplayAction(tracer)));
}
private TransactionAction<TransactionTrace> 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());
};
}
}

@ -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"),

@ -15,6 +15,7 @@ package tech.pegasys.pantheon.ethereum.jsonrpc.internal.results;
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 class DebugTraceTransactionResult {
failed = !transactionTrace.getResult().isSuccessful();
}
public static Collection<DebugTraceTransactionResult> of(
final Collection<TransactionTrace> traces) {
return traces.stream().map(DebugTraceTransactionResult::new).collect(Collectors.toList());
}
private static StructLog createStructLog(final TraceFrame frame) {
return frame.getExceptionalHaltReasons().isEmpty()
? new StructLog(frame)

@ -30,7 +30,7 @@ import tech.pegasys.pantheon.ethereum.core.Transaction;
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 class DebugStorageRangeAtTest {
private Object callAction(final InvocationOnMock invocation) {
return Optional.of(
((Action) invocation.getArgument(2))
((TransactionAction) invocation.getArgument(2))
.performAction(transaction, blockHeader, blockchain, worldState, transactionProcessor));
}
}

@ -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());
}
}

@ -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());
}
}

@ -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());
}
}
Loading…
Cancel
Save