Skip to content
Draft
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
import org.hyperledger.besu.plugin.data.EnodeURL;
import org.hyperledger.besu.plugin.services.BesuConfiguration;
import org.hyperledger.besu.plugin.services.BesuEvents;
import org.hyperledger.besu.plugin.services.BlockReplayService;
import org.hyperledger.besu.plugin.services.BlockchainService;
import org.hyperledger.besu.plugin.services.MetricsSystem;
import org.hyperledger.besu.plugin.services.PermissioningService;
Expand All @@ -92,6 +93,7 @@
import org.hyperledger.besu.services.BesuConfigurationImpl;
import org.hyperledger.besu.services.BesuEventsImpl;
import org.hyperledger.besu.services.BesuPluginContextImpl;
import org.hyperledger.besu.services.BlockReplayServiceImpl;
import org.hyperledger.besu.services.BlockchainServiceImpl;
import org.hyperledger.besu.services.MiningServiceImpl;
import org.hyperledger.besu.services.PermissioningServiceImpl;
Expand Down Expand Up @@ -264,15 +266,21 @@ public boolean isActive(final String nodeName) {

private void loadAdditionalServices(
final BesuController besuController, final BesuPluginContextImpl besuPluginContext) {
final BlockchainQueries blockchainQueries =
new BlockchainQueries(
besuController.getProtocolSchedule(),
besuController.getProtocolContext().getBlockchain(),
besuController.getProtocolContext().getWorldStateArchive(),
besuController.getMiningParameters());
final TraceServiceImpl traceService =
new TraceServiceImpl(
new BlockchainQueries(
besuController.getProtocolSchedule(),
besuController.getProtocolContext().getBlockchain(),
besuController.getProtocolContext().getWorldStateArchive(),
besuController.getMiningParameters()),
besuController.getProtocolSchedule());
new TraceServiceImpl(blockchainQueries, besuController.getProtocolSchedule());
besuPluginContext.addService(TraceService.class, traceService);
besuPluginContext.addService(
BlockReplayService.class,
new BlockReplayServiceImpl(
blockchainQueries,
besuController.getProtocolSchedule(),
besuController.getProtocolContext()));
besuPluginContext.addService(
BesuEvents.class,
new BesuEventsImpl(
Expand Down
23 changes: 16 additions & 7 deletions app/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@
import org.hyperledger.besu.plugin.data.EnodeURL;
import org.hyperledger.besu.plugin.services.BesuConfiguration;
import org.hyperledger.besu.plugin.services.BesuEvents;
import org.hyperledger.besu.plugin.services.BlockReplayService;
import org.hyperledger.besu.plugin.services.BlockSimulationService;
import org.hyperledger.besu.plugin.services.BlockchainService;
import org.hyperledger.besu.plugin.services.MetricsSystem;
Expand Down Expand Up @@ -185,6 +186,7 @@
import org.hyperledger.besu.services.BesuConfigurationImpl;
import org.hyperledger.besu.services.BesuEventsImpl;
import org.hyperledger.besu.services.BesuPluginContextImpl;
import org.hyperledger.besu.services.BlockReplayServiceImpl;
import org.hyperledger.besu.services.BlockSimulatorServiceImpl;
import org.hyperledger.besu.services.BlockchainServiceImpl;
import org.hyperledger.besu.services.MiningServiceImpl;
Expand Down Expand Up @@ -1369,15 +1371,22 @@ private void startPlugins(final Runner runner) {
RlpConverterService.class,
new RlpConverterServiceImpl(besuController.getProtocolSchedule()));

BlockchainQueries blockchainQueries =
new BlockchainQueries(
besuController.getProtocolSchedule(),
besuController.getProtocolContext().getBlockchain(),
besuController.getProtocolContext().getWorldStateArchive(),
miningParametersSupplier.get());
besuPluginContext.addService(
TraceService.class,
new TraceServiceImpl(
new BlockchainQueries(
besuController.getProtocolSchedule(),
besuController.getProtocolContext().getBlockchain(),
besuController.getProtocolContext().getWorldStateArchive(),
miningParametersSupplier.get()),
besuController.getProtocolSchedule()));
new TraceServiceImpl(blockchainQueries, besuController.getProtocolSchedule()));

besuPluginContext.addService(
BlockReplayService.class,
new BlockReplayServiceImpl(
blockchainQueries,
besuController.getProtocolSchedule(),
besuController.getProtocolContext()));

besuPluginContext.addService(
MiningService.class, new MiningServiceImpl(besuController.getMiningCoordinator()));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/*
* Copyright contributors to Besu.
*
* 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.Hash;
import org.hyperledger.besu.ethereum.ProtocolContext;
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.AbstractBlockProcessor;
import org.hyperledger.besu.ethereum.mainnet.BlockProcessor;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.evm.worldstate.WorldView;
import org.hyperledger.besu.plugin.data.BlockBody;
import org.hyperledger.besu.plugin.data.BlockContext;
import org.hyperledger.besu.plugin.data.BlockProcessingResult;
import org.hyperledger.besu.plugin.data.BlockReplayResult;
import org.hyperledger.besu.plugin.services.BlockReplayService;
import org.hyperledger.besu.plugin.services.tracer.BlockAwareOperationTracer;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.LongStream;

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

/**
* Default implementation of {@link BlockReplayService}.
*
* <p>Replays and traces block execution across a contiguous range of block numbers, restoring the
* correct pre-execution world state for each block.
*/
public class BlockReplayServiceImpl implements BlockReplayService {

private static final Logger LOG = LoggerFactory.getLogger(BlockReplayServiceImpl.class);

private final Blockchain blockchain;
private final ProtocolSchedule protocolSchedule;
private final ProtocolContext protocolContext;

private final BlockchainQueries blockchainQueries;

/**
* Constructs a BlockReplayServiceImpl.
*
* @param blockchainQueries the blockchainQueries to replay blocks from
* @param protocolSchedule the protocol schedule to determine block processing rules
* @param protocolContext the protocol context containing world state and other necessary data
*/
public BlockReplayServiceImpl(
final BlockchainQueries blockchainQueries,
final ProtocolSchedule protocolSchedule,
final ProtocolContext protocolContext) {
this.blockchain = blockchainQueries.getBlockchain();
this.blockchainQueries = blockchainQueries;
this.protocolSchedule = protocolSchedule;
this.protocolContext = protocolContext;
}

@Override
public List<BlockReplayResult> replayBlocks(
final long fromBlockNumber,
final long toBlockNumber,
final Consumer<WorldView> beforeTracing,
final Consumer<WorldView> afterTracing,
final BlockAwareOperationTracer tracer) {
validateInputs(fromBlockNumber, toBlockNumber, tracer);

LOG.debug("Replaying blocks [{} → {}]", fromBlockNumber, toBlockNumber);
final List<Block> blocks = getBlocks(fromBlockNumber, toBlockNumber);

// Hook to provide world view before tracing starts
beforeTracing(blocks.getFirst(), beforeTracing);

final List<BlockReplayResult> results = new ArrayList<>(blocks.size());
for (final Block block : blocks) {
final BlockProcessingResult result =
replayBlock(block, tracer)
.orElseThrow(
() ->
new IllegalStateException(
"Unexpected empty result while replaying block: "
+ block.getHeader().toLogString()));
results.add(new BlockReplayResultImpl(block, result));
}

// Hook to provide world view after tracing ends
afterTracing(blocks.getLast(), afterTracing);
return Collections.unmodifiableList(results);
}

/** Hooks to provide world view before and after tracing */
private void beforeTracing(final Block firstBlock, final Consumer<WorldView> beforeTracing) {
withWorldView(firstBlock.getHeader().getParentHash(), beforeTracing);
}

/** Hooks to provide world view before and after tracing */
private void afterTracing(final Block lastBlock, final Consumer<WorldView> afterTracing) {
withWorldView(lastBlock.getHeader().getHash(), afterTracing);
}

/** Replays a single block and returns the processing result. */
private Optional<BlockProcessingResult> replayBlock(
final Block block, final BlockAwareOperationTracer tracer) {
return Tracer.processTracing(
blockchainQueries,
block.getHeader().getHash(),
mutableWorldState -> {
final BlockProcessor processor =
protocolSchedule.getByBlockHeader(block.getHeader()).getBlockProcessor();
final BlockProcessingResult processingResult =
processor.processBlock(
protocolContext,
blockchain,
mutableWorldState,
block,
Optional.empty(),
new AbstractBlockProcessor.PreprocessingFunction.NoPreprocessing(),
tracer);
if (!processingResult.isSuccessful()) {
throw new RuntimeException(
"Block processing failed for block: " + block.getHeader().toLogString());
}
return Optional.of(processingResult);
});
}

/** Validates the input parameters for block replay. */
private void validateInputs(
final long fromBlockNumber,
final long toBlockNumber,
final BlockAwareOperationTracer tracer) {

checkArgument(tracer != null, "Tracer must not be null");
checkArgument(fromBlockNumber <= toBlockNumber, "Invalid block range: from > to");
}

private List<Block> getBlocks(final long from, final long to) {
return LongStream.rangeClosed(from, to)
.mapToObj(
number ->
blockchain
.getBlockByNumber(number)
.orElseThrow(() -> new IllegalArgumentException("Block not found: " + number)))
.toList();
}

private void withWorldView(final Hash blockHash, final Consumer<WorldView> consumer) {
blockchainQueries.getAndMapWorldState(
blockHash,
ws -> {
consumer.accept(ws);
return Optional.empty();
});
}

private record BlockReplayResultImpl(Block block, BlockProcessingResult processingResult)
implements BlockReplayResult {

@Override
public BlockContext blockContext() {
return new BlockContext() {
@Override
public BlockHeader getBlockHeader() {
return block.getHeader();
}

@Override
public BlockBody getBlockBody() {
return block.getBody();
}
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
import java.util.Optional;

/** Contains the outputs of processing a block. */
public class BlockProcessingResult extends BlockValidationResult {
public class BlockProcessingResult extends BlockValidationResult
implements org.hyperledger.besu.plugin.data.BlockProcessingResult {

private final Optional<BlockProcessingOutputs> yield;
private final boolean isPartial;
Expand Down Expand Up @@ -161,6 +162,7 @@ public boolean isPartial() {
*
* @return the transaction receipts of the result
*/
@Override
public List<TransactionReceipt> getReceipts() {
return yield.map(BlockProcessingOutputs::getReceipts).orElse(List.of());
}
Expand All @@ -170,6 +172,7 @@ public List<TransactionReceipt> getReceipts() {
*
* @return the requests of the result
*/
@Override
public Optional<List<Request>> getRequests() {
return yield.flatMap(BlockProcessingOutputs::getRequests);
}
Expand All @@ -191,4 +194,14 @@ public Optional<Integer> getNbParallelizedTransactions() {
public Optional<BlockAccessList> getGeneratedBlockAccessList() {
return maybeGeneratedBlockAccessList;
}

/**
* Returns the block access list produced during processing, when available.
*
* @return the generated block access list
*/
@Override
public Optional<BlockAccessList> getBlockAccessList() {
return yield.flatMap(BlockProcessingOutputs::getBlockAccessList);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,27 @@ public BlockProcessingResult processBlock(
final Block block,
final Optional<BlockAccessList> blockAccessList,
final PreprocessingFunction preprocessingBlockFunction) {
final BlockAwareOperationTracer operationTracer =
getBlockImportTracer(protocolContext, block.getHeader());
return processBlock(
protocolContext,
blockchain,
worldState,
block,
blockAccessList,
preprocessingBlockFunction,
operationTracer);
}

@Override
public BlockProcessingResult processBlock(
final ProtocolContext protocolContext,
final Blockchain blockchain,
final MutableWorldState worldState,
final Block block,
final Optional<BlockAccessList> blockAccessList,
final PreprocessingFunction preprocessingBlockFunction,
final BlockAwareOperationTracer blockTracer) {
final List<TransactionReceipt> receipts = new ArrayList<>();
long currentGasUsed = 0;
long currentBlobGasUsed = 0;
Expand All @@ -199,9 +220,6 @@ public BlockProcessingResult processBlock(
final BlockHashLookup blockHashLookup =
protocolSpec.getPreExecutionProcessor().createBlockHashLookup(blockchain, blockHeader);

final BlockAwareOperationTracer blockTracer =
getBlockImportTracer(protocolContext, blockHeader);

final Address miningBeneficiary = miningBeneficiaryCalculator.calculateBeneficiary(blockHeader);

LOG.trace("traceStartBlock for {}", blockHeader.getNumber());
Expand Down
Loading