diff --git a/CHANGELOG.md b/CHANGELOG.md index 783ca077435..b7ab3e8256c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Additions and Improvements * Added support for the upcoming YOLOv2 ephemeral testnet and removed the flag for the deprecated YOLOv1 ephemeral testnet. [#1386](https://github.com/hyperledger/besu/pull/1386) * Added `debug_standardTraceBlockToFile` JSON-RPC API. This API accepts a block hash and will replay the block. It returns a list of files containing the result of the trace (one file per transaction). [\#1392](https://github.com/hyperledger/besu/pull/1392) +* Added `debug_standardTraceBadBlockToFile` JSON-RPC API. This API is similar to `debug_standardTraceBlockToFile`, but can be used to obtain info about a block which has been rejected as invalid. [\#1403](https://github.com/hyperledger/besu/pull/1403) * Added support for EIP-2929 to YOLOv2. [#1387](https://github.com/hyperledger/besu/pull/1387) * Added `--start-block` and `--end-block` to the `blocks import` subcommand [\#1399](https://github.com/hyperledger/besu/pull/1399) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java index 6a3c68434bb..d28cb3d67f6 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java @@ -38,6 +38,7 @@ public enum RpcMethod { DEBUG_TRACE_BLOCK_BY_HASH("debug_traceBlockByHash"), DEBUG_TRACE_BLOCK_BY_NUMBER("debug_traceBlockByNumber"), DEBUG_STANDARD_TRACE_BLOCK_TO_FILE("debug_standardTraceBlockToFile"), + DEBUG_STANDARD_TRACE_BAD_BLOCK_TO_FILE("debug_standardTraceBadBlockToFile"), DEBUG_TRACE_TRANSACTION("debug_traceTransaction"), DEBUG_BATCH_RAW_TRANSACTION("debug_batchSendRawTransaction"), DEBUG_GET_BAD_BLOCKS("debug_getBadBlocks"), diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBadBlockToFile.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBadBlockToFile.java new file mode 100644 index 00000000000..cff97dc99de --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBadBlockToFile.java @@ -0,0 +1,78 @@ +/* + * Copyright 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; + +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.TransactionTraceParams; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTracer; +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.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; + +import java.nio.file.Path; +import java.util.Optional; +import java.util.function.Supplier; + +public class DebugStandardTraceBadBlockToFile extends DebugStandardTraceBlockToFile + implements JsonRpcMethod { + + private final ProtocolSchedule protocolSchedule; + + public DebugStandardTraceBadBlockToFile( + final Supplier transactionTracerSupplier, + final BlockchainQueries blockchainQueries, + final ProtocolSchedule protocolSchedule, + final Path dataDir) { + super(transactionTracerSupplier, blockchainQueries, dataDir); + this.protocolSchedule = protocolSchedule; + } + + @Override + public String getName() { + return RpcMethod.DEBUG_STANDARD_TRACE_BAD_BLOCK_TO_FILE.getMethodName(); + } + + @Override + public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { + final Hash blockHash = requestContext.getRequiredParameter(0, Hash.class); + final Optional transactionTraceParams = + requestContext.getOptionalParameter(1, TransactionTraceParams.class); + + final Blockchain blockchain = blockchainQueries.get().getBlockchain(); + final ProtocolSpec protocolSpec = + protocolSchedule.getByBlockNumber(blockchain.getChainHeadHeader().getNumber()); + final BadBlockManager badBlockManager = protocolSpec.getBadBlocksManager(); + + return badBlockManager + .getBadBlock(blockHash) + .map( + block -> + (JsonRpcResponse) + new JsonRpcSuccessResponse( + requestContext.getRequest().getId(), + traceBlock(block, transactionTraceParams))) + .orElse( + new JsonRpcErrorResponse( + requestContext.getRequest().getId(), JsonRpcError.BLOCK_NOT_FOUND)); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBlockToFile.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBlockToFile.java index c1829fa259b..230b0f28bbe 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBlockToFile.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBlockToFile.java @@ -38,8 +38,8 @@ public class DebugStandardTraceBlockToFile implements JsonRpcMethod { + protected final Supplier blockchainQueries; private final Supplier transactionTracerSupplier; - private final Supplier blockchainQueries; private final Path dataDir; public DebugStandardTraceBlockToFile( @@ -77,7 +77,7 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { requestContext.getRequest().getId(), JsonRpcError.BLOCK_NOT_FOUND)); } - private List traceBlock( + protected List traceBlock( final Block block, final Optional transactionTraceParams) { return transactionTracerSupplier .get() diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockReplay.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockReplay.java index cb318d6213b..75b321a4165 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockReplay.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockReplay.java @@ -124,8 +124,12 @@ public Optional afterTransactionInBlock( private Optional performActionWithBlock( final Hash blockHash, final BlockAction action) { - return getBlock(blockHash) - .flatMap(block -> performActionWithBlock(block.getHeader(), block.getBody(), action)); + Optional maybeBlock = getBlock(blockHash); + if (maybeBlock.isEmpty()) { + maybeBlock = getBadBlock(blockHash); + } + return maybeBlock.flatMap( + block -> performActionWithBlock(block.getHeader(), block.getBody(), action)); } private Optional performActionWithBlock( @@ -161,6 +165,12 @@ private Optional getBlock(final Hash blockHash) { return Optional.empty(); } + private Optional getBadBlock(final Hash blockHash) { + final ProtocolSpec protocolSpec = + protocolSchedule.getByBlockNumber(blockchain.getChainHeadHeader().getNumber()); + return protocolSpec.getBadBlocksManager().getBadBlock(blockHash); + } + @FunctionalInterface private interface BlockAction { Optional perform( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/DebugJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/DebugJsonRpcMethods.java index d7e51d78486..13c5fd6c99a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/DebugJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/DebugJsonRpcMethods.java @@ -20,6 +20,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugBatchSendRawTransaction; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugGetBadBlocks; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugMetrics; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugStandardTraceBadBlockToFile; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugStandardTraceBlockToFile; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugStorageRangeAt; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugTraceBlock; @@ -90,6 +91,11 @@ protected Map create() { new DebugBatchSendRawTransaction(transactionPool), new DebugGetBadBlocks(blockchainQueries, protocolSchedule, blockResult), new DebugStandardTraceBlockToFile( - () -> new TransactionTracer(blockReplay), blockchainQueries, dataDir)); + () -> new TransactionTracer(blockReplay), blockchainQueries, dataDir), + new DebugStandardTraceBadBlockToFile( + () -> new TransactionTracer(blockReplay), + blockchainQueries, + protocolSchedule, + dataDir)); } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBadBlockToFileTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBadBlockToFileTest.java new file mode 100644 index 00000000000..e6b4c3bd086 --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBadBlockToFileTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTracer; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockDataGenerator; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class DebugStandardTraceBadBlockToFileTest { + + @ClassRule public static final TemporaryFolder folder = new TemporaryFolder(); + + private final BlockchainQueries blockchainQueries = mock(BlockchainQueries.class); + private final Blockchain blockchain = mock(Blockchain.class); + private final ProtocolSchedule protocolSchedule = mock(ProtocolSchedule.class); + private final TransactionTracer transactionTracer = mock(TransactionTracer.class); + private final ProtocolSpec protocolSpec = mock(ProtocolSpec.class); + + private final DebugStandardTraceBadBlockToFile debugStandardTraceBadBlockToFile = + new DebugStandardTraceBadBlockToFile( + () -> transactionTracer, blockchainQueries, protocolSchedule, folder.getRoot().toPath()); + + @Test + public void nameShouldBeDebugTraceTransaction() { + assertThat(debugStandardTraceBadBlockToFile.getName()) + .isEqualTo("debug_standardTraceBadBlockToFile"); + } + + @SuppressWarnings("rawtypes") + @Test + public void shouldTraceTheTransactionUsingTheTransactionTracer() { + + final BlockDataGenerator blockGenerator = new BlockDataGenerator(); + final Block genesis = blockGenerator.genesisBlock(); + final Block block = + blockGenerator.block( + new BlockDataGenerator.BlockOptions().setParentHash(genesis.getHeader().getHash())); + + final Object[] params = new Object[] {block.getHash(), new HashMap<>()}; + final JsonRpcRequestContext request = + new JsonRpcRequestContext( + new JsonRpcRequest("2.0", "debug_standardTraceBadBlockToFile", params)); + + final List paths = new ArrayList<>(); + paths.add("path-1"); + + final BadBlockManager badBlockManager = new BadBlockManager(); + badBlockManager.addBadBlock(block); + + final BlockHeader blockHeader = new BlockHeaderTestFixture().buildHeader(); + when(protocolSpec.getBadBlocksManager()).thenReturn(badBlockManager); + when(blockchainQueries.getBlockchain()).thenReturn(blockchain); + when(blockchain.getChainHeadHeader()).thenReturn(new BlockHeaderTestFixture().buildHeader()); + when(protocolSchedule.getByBlockNumber(blockHeader.getNumber())).thenReturn(protocolSpec); + when(transactionTracer.traceTransactionToFile(eq(block.getHash()), any(), any(), any())) + .thenReturn(paths); + final JsonRpcSuccessResponse response = + (JsonRpcSuccessResponse) debugStandardTraceBadBlockToFile.response(request); + final List result = (ArrayList) response.getResult(); + + assertThat(result.size()).isEqualTo(1); + } +} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracerTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracerTest.java index 865cfc0bc6c..224d99db2f1 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracerTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracerTest.java @@ -21,6 +21,7 @@ import static org.mockito.Mockito.when; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.ImmutableTransactionTraceParams; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.Address; import org.hyperledger.besu.ethereum.core.BlockBody; @@ -113,6 +114,8 @@ public void setUp() throws Exception { when(protocolSchedule.getByBlockNumber(12)).thenReturn(protocolSpec); when(protocolSpec.getTransactionProcessor()).thenReturn(transactionProcessor); when(protocolSpec.getMiningBeneficiaryCalculator()).thenReturn(BlockHeader::getCoinbase); + when(blockchain.getChainHeadHeader()).thenReturn(blockHeader); + when(protocolSpec.getBadBlocksManager()).thenReturn(new BadBlockManager()); } @Test @@ -259,7 +262,6 @@ public void traceTransactionToFileShouldReturnResultFromProcessTransaction() thr transactions, Optional.of(ImmutableTransactionTraceParams.builder().build()), traceDir.getRoot().toPath()); - ; assertThat(transactionTraces.size()).isEqualTo(1); assertThat(Files.readString(Path.of(transactionTraces.get(0)))) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockManager.java index e4c5f85476d..50f0011a5d2 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockManager.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockManager.java @@ -18,6 +18,7 @@ import org.hyperledger.besu.ethereum.core.Hash; import java.util.Collection; +import java.util.Optional; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; @@ -46,4 +47,14 @@ public void addBadBlock(final Block badBlock) { public Collection getBadBlocks() { return badBlocks.asMap().values(); } + + /** + * Return an invalid block based on the hash + * + * @param hash of the block + * @return an invalid block + */ + public Optional getBadBlock(final Hash hash) { + return Optional.ofNullable(badBlocks.getIfPresent(hash)); + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/MainnetBlockValidatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/MainnetBlockValidatorTest.java index d07afcb445b..ca5be707d2b 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/MainnetBlockValidatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/MainnetBlockValidatorTest.java @@ -237,4 +237,22 @@ public boolean isSuccessful() { HeaderValidationMode.DETACHED_ONLY); assertThat(badBlockManager.getBadBlocks()).isEmpty(); } + + @Test + public void shouldReturnBadBlockBasedOnTheHash() { + when(blockchain.getBlockHeader(any(Hash.class))) + .thenReturn(Optional.of(new BlockHeaderTestFixture().buildHeader())); + when(blockHeaderValidator.validateHeader( + any(BlockHeader.class), + any(BlockHeader.class), + eq(protocolContext), + eq(HeaderValidationMode.DETACHED_ONLY))) + .thenReturn(false); + mainnetBlockValidator.validateAndProcessBlock( + protocolContext, + badBlock, + HeaderValidationMode.DETACHED_ONLY, + HeaderValidationMode.DETACHED_ONLY); + assertThat(badBlockManager.getBadBlock(badBlock.getHash())).containsSame(badBlock); + } }