diff --git a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index 973c00c9de..5bfea9212b 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -117,6 +117,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.JwtAlgorithm; import org.hyperledger.besu.ethereum.api.jsonrpc.ipc.JsonRpcIpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration; +import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.api.tls.FileBasedPasswordProvider; import org.hyperledger.besu.ethereum.api.tls.TlsClientAuthConfiguration; import org.hyperledger.besu.ethereum.api.tls.TlsConfiguration; @@ -172,6 +173,7 @@ import org.hyperledger.besu.plugin.services.PrivacyPluginService; import org.hyperledger.besu.plugin.services.RpcEndpointService; import org.hyperledger.besu.plugin.services.SecurityModuleService; import org.hyperledger.besu.plugin.services.StorageService; +import org.hyperledger.besu.plugin.services.TraceService; import org.hyperledger.besu.plugin.services.exception.StorageException; import org.hyperledger.besu.plugin.services.metrics.MetricCategory; import org.hyperledger.besu.plugin.services.metrics.MetricCategoryRegistry; @@ -187,6 +189,7 @@ import org.hyperledger.besu.services.PrivacyPluginServiceImpl; import org.hyperledger.besu.services.RpcEndpointServiceImpl; import org.hyperledger.besu.services.SecurityModuleServiceImpl; import org.hyperledger.besu.services.StorageServiceImpl; +import org.hyperledger.besu.services.TraceServiceImpl; import org.hyperledger.besu.services.kvstore.InMemoryStoragePlugin; import org.hyperledger.besu.util.InvalidConfigurationException; import org.hyperledger.besu.util.LogConfigurator; @@ -1738,6 +1741,14 @@ public class BesuCommand implements DefaultCommandValues, Runnable { BlockchainService.class, new BlockchainServiceImpl(besuController.getProtocolContext().getBlockchain())); + besuPluginContext.addService( + TraceService.class, + new TraceServiceImpl( + new BlockchainQueries( + besuController.getProtocolContext().getBlockchain(), + besuController.getProtocolContext().getWorldStateArchive()), + besuController.getProtocolSchedule())); + besuController.getAdditionalPluginServices().appendPluginServices(besuPluginContext); besuPluginContext.startPlugins(); } diff --git a/besu/src/main/java/org/hyperledger/besu/services/TraceServiceImpl.java b/besu/src/main/java/org/hyperledger/besu/services/TraceServiceImpl.java new file mode 100644 index 0000000000..321996d51e --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/services/TraceServiceImpl.java @@ -0,0 +1,133 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.services; + +import static com.google.common.base.Preconditions.checkArgument; + +import org.hyperledger.besu.datatypes.DataGas; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.TraceBlock.ChainUpdater; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.Tracer; +import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; +import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup; +import org.hyperledger.besu.evm.tracing.OperationTracer; +import org.hyperledger.besu.plugin.Unstable; +import org.hyperledger.besu.plugin.services.TraceService; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** The Trace service implementation. */ +@Unstable +public class TraceServiceImpl implements TraceService { + private static final Logger LOG = LoggerFactory.getLogger(TraceServiceImpl.class); + + private final BlockchainQueries blockchainQueries; + private final ProtocolSchedule protocolSchedule; + + /** + * Instantiates a new TraceServiceImpl service. + * + * @param blockchainQueries the blockchainQueries + * @param protocolSchedule the protocolSchedule + */ + public TraceServiceImpl( + final BlockchainQueries blockchainQueries, final ProtocolSchedule protocolSchedule) { + this.blockchainQueries = blockchainQueries; + this.protocolSchedule = protocolSchedule; + } + + /** + * Traces block + * + * @param blockNumber the block number to be traced + * @param tracer an instance of OperationTracer + */ + @Override + public void traceBlock(final long blockNumber, final OperationTracer tracer) { + checkArgument(tracer != null); + final Optional block = blockchainQueries.getBlockchain().getBlockByNumber(blockNumber); + block.ifPresent(value -> trace(value, tracer)); + } + + /** + * Traces block + * + * @param hash the block hash to be traced + * @param tracer an instance of OperationTracer + */ + @Override + public void traceBlock(final Hash hash, final OperationTracer tracer) { + checkArgument(tracer != null); + final Optional block = blockchainQueries.getBlockchain().getBlockByHash(hash); + block.ifPresent(value -> trace(value, tracer)); + } + + private void trace(final Block block, final OperationTracer tracer) { + LOG.debug("Tracing block {}", block.toLogString()); + final List results = new ArrayList<>(); + Tracer.processTracing( + blockchainQueries, + block.getHash(), + traceableState -> { + final Blockchain blockchain = blockchainQueries.getBlockchain(); + final ChainUpdater chainUpdater = new ChainUpdater(traceableState); + final ProtocolSpec protocolSpec = protocolSchedule.getByBlockHeader(block.getHeader()); + final MainnetTransactionProcessor transactionProcessor = + protocolSpec.getTransactionProcessor(); + final BlockHeader header = block.getHeader(); + block + .getBody() + .getTransactions() + .forEach( + transaction -> { + final Optional maybeParentHeader = + blockchain.getBlockHeader(header.getParentHash()); + final Wei dataGasPrice = + protocolSpec + .getFeeMarket() + .dataPrice( + maybeParentHeader + .flatMap(BlockHeader::getExcessDataGas) + .orElse(DataGas.ZERO)); + final TransactionProcessingResult result = + transactionProcessor.processTransaction( + blockchain, + chainUpdater.getNextUpdater(), + header, + transaction, + header.getCoinbase(), + tracer, + new CachingBlockHashLookup(header, blockchain), + false, + dataGasPrice); + results.add(result); + }); + return Optional.of(results); + }); + } +} diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle index a199a81456..e7ef4f0887 100644 --- a/plugin-api/build.gradle +++ b/plugin-api/build.gradle @@ -33,6 +33,7 @@ dependencies { api 'org.apache.tuweni:tuweni-bytes' api 'org.apache.tuweni:tuweni-units' implementation 'com.google.guava:guava' + implementation project(':evm') } configurations { testArtifacts } @@ -68,7 +69,7 @@ Calculated : ${currentHash} tasks.register('checkAPIChanges', FileStateChecker) { description = "Checks that the API for the Plugin-API project does not change without deliberate thought" files = sourceSets.main.allJava.files - knownHash = 'QyxfU17c/+NmSzQTkJ4gfZrrGvnXuup3Gv2H6P45wjg=' + knownHash = 'Ko+XIaOkLfpXUbQnErMJb5CXprobCrT6Xath/7Vtxio=' } check.dependsOn('checkAPIChanges') diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TraceService.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TraceService.java new file mode 100644 index 0000000000..d3b776043d --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TraceService.java @@ -0,0 +1,39 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.evm.tracing.OperationTracer; +import org.hyperledger.besu.plugin.Unstable; + +/** The Trace service interface */ +@Unstable +public interface TraceService extends BesuService { + /** + * Traces a block + * + * @param blockNumber the block number + * @param tracer the tracer (OperationTracer) + */ + void traceBlock(long blockNumber, OperationTracer tracer); + + /** + * Traces a block by hash + * + * @param hash the block hash + * @param tracer the tracer (OperationTracer) + */ + void traceBlock(Hash hash, OperationTracer tracer); +}