Skip to content

Commit

Permalink
Add tests
Browse files Browse the repository at this point in the history
Signed-off-by: Gabriel-Trintinalia <[email protected]>
  • Loading branch information
Gabriel-Trintinalia committed Dec 10, 2024
1 parent 9f0e610 commit a3c3a20
Show file tree
Hide file tree
Showing 4 changed files with 388 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,7 @@ public BlockSimulationResult importBlockUnsafe(
new IllegalArgumentException(
"Public world state not available for block "
+ headerCore.toLogString()))) {
var results =
blockSimulator.processWithMutableWorldState(headerCore, List.of(blockStateCall), ws);
var results = blockSimulator.process(headerCore, List.of(blockStateCall), ws);
var result = results.getFirst();
ws.persist(result.getBlock().getHeader());
return response(result);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.hyperledger.besu.ethereum.transaction;

public class BlockSimulationException extends RuntimeException {
public BlockSimulationException(final String message) {
super(message);
}

public BlockSimulationException(final String message, final Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,18 @@
import java.util.List;
import java.util.Optional;

import com.google.common.annotations.VisibleForTesting;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.units.bigints.UInt256;

/**
* BlockSimulator is responsible for simulating the execution of a block. It processes transactions
* and applies state overrides to simulate the block execution.
* Simulates the execution of a block, processing transactions and applying state overrides. This
* class is responsible for simulating the execution of a block, which involves processing
* transactions and applying state overrides. It provides a way to test and validate the behavior of
* a block without actually executing it on the blockchain. The simulator takes into account various
* factors, such as the block header, transaction calls, and state overrides, to simulate the
* execution of the block. It returns a list of simulation results, which include the final block
* header, transaction receipts, and other relevant information.
*/
public class BlockSimulator {
private final TransactionSimulator transactionSimulator;
Expand Down Expand Up @@ -90,7 +96,9 @@ public List<BlockSimulationResult> process(
() ->
new IllegalArgumentException(
"Public world state not available for block " + header.toLogString()))) {
return processWithMutableWorldState(header, blockStateCalls, ws);
return process(header, blockStateCalls, ws);
} catch (IllegalArgumentException e) {
throw e;
} catch (final Exception e) {
throw new RuntimeException("Error simulating block", e);
}
Expand All @@ -104,14 +112,14 @@ public List<BlockSimulationResult> process(
* @param worldState The initial MutableWorldState to start with.
* @return A list of BlockSimulationResult objects from processing each BlockStateCall.
*/
public List<BlockSimulationResult> processWithMutableWorldState(
public List<BlockSimulationResult> process(
final BlockHeader header,
final List<? extends BlockStateCall> blockStateCalls,
final MutableWorldState worldState) {
List<BlockSimulationResult> simulationResults = new ArrayList<>();
for (BlockStateCall blockStateCall : blockStateCalls) {
BlockSimulationResult simulationResult =
processWithMutableWorldState(header, blockStateCall, worldState);
processSingleBlockStateCall(header, blockStateCall, worldState);
simulationResults.add(simulationResult);
}
return simulationResults;
Expand All @@ -125,28 +133,37 @@ public List<BlockSimulationResult> processWithMutableWorldState(
* @param ws The MutableWorldState to use for the simulation.
* @return A BlockSimulationResult from processing the BlockStateCall.
*/
private BlockSimulationResult processWithMutableWorldState(
private BlockSimulationResult processSingleBlockStateCall(
final BlockHeader header, final BlockStateCall blockStateCall, final MutableWorldState ws) {
BlockOverrides blockOverrides = blockStateCall.getBlockOverrides();
long timestamp = blockOverrides.getTimestamp().orElse(header.getTimestamp() + 1);

ProtocolSpec newProtocolSpec = protocolSchedule.getForNextBlockHeader(header, timestamp);

// Apply block header overrides
// Apply block header overrides and state overrides
BlockHeader blockHeader = applyBlockHeaderOverrides(header, newProtocolSpec, blockOverrides);

// Apply state overrides
blockStateCall.getAccountOverrides().ifPresent(overrides -> applyStateOverrides(overrides, ws));

long currentGasUsed = 0;
final var transactionReceiptFactory = newProtocolSpec.getTransactionReceiptFactory();
final List<TransactionReceipt> receipts = new ArrayList<>();
final List<Transaction> transactions = new ArrayList<>();
List<TransactionSimulatorResult> transactionSimulations = new ArrayList<>();

// Override the mining beneficiary calculator if a fee recipient is specified, otherwise use the
// default
MiningBeneficiaryCalculator miningBeneficiaryCalculator =
getMiningBeneficiaryCalculator(blockOverrides, newProtocolSpec);

List<TransactionSimulatorResult> transactionSimulatorResults =
processTransactions(blockHeader, blockStateCall, ws, miningBeneficiaryCalculator);

return finalizeBlock(
blockHeader, blockStateCall, ws, newProtocolSpec, transactionSimulatorResults);
}

@VisibleForTesting
protected List<TransactionSimulatorResult> processTransactions(
final BlockHeader blockHeader,
final BlockStateCall blockStateCall,
final MutableWorldState ws,
final MiningBeneficiaryCalculator miningBeneficiaryCalculator) {

List<TransactionSimulatorResult> transactionSimulations = new ArrayList<>();

for (CallParameter callParameter : blockStateCall.getCalls()) {
final WorldUpdater transactionUpdater = ws.updater();

Expand All @@ -161,21 +178,38 @@ private BlockSimulationResult processWithMutableWorldState(
miningBeneficiaryCalculator);

if (transactionSimulatorResult.isEmpty()) {
throw new RuntimeException("Transaction simulator result is empty");
throw new BlockSimulationException("Transaction simulator result is empty");
}

TransactionSimulatorResult result = transactionSimulatorResult.get();
if (result.result().isInvalid()) {
throw new RuntimeException(
"Transaction simulator result is invalid: " + result.result().getValidationResult());
if (result.isInvalid()) {
throw new BlockSimulationException(
"Transaction simulator result is invalid: " + result.result().getInvalidReason());
}
transactionSimulations.add(transactionSimulatorResult.get());

transactionUpdater.commit();
}
return transactionSimulations;
}

@VisibleForTesting
protected BlockSimulationResult finalizeBlock(
final BlockHeader blockHeader,
final BlockStateCall blockStateCall,
final MutableWorldState ws,
final ProtocolSpec protocolSpec,
final List<TransactionSimulatorResult> transactionSimulations) {

TransactionProcessingResult transactionProcessingResult =
transactionSimulatorResult.get().result();
final Transaction transaction = transactionSimulatorResult.get().transaction();
long currentGasUsed = 0;
final var transactionReceiptFactory = protocolSpec.getTransactionReceiptFactory();

final List<TransactionReceipt> receipts = new ArrayList<>();
final List<Transaction> transactions = new ArrayList<>();

for (TransactionSimulatorResult transactionSimulatorResult : transactionSimulations) {

TransactionProcessingResult transactionProcessingResult = transactionSimulatorResult.result();
final Transaction transaction = transactionSimulatorResult.transaction();

currentGasUsed += transaction.getGasLimit() - transactionProcessingResult.getGasRemaining();

Expand Down Expand Up @@ -205,11 +239,11 @@ private BlockSimulationResult processWithMutableWorldState(
* @param accountOverrideMap The AccountOverrideMap containing the state overrides.
* @param ws The MutableWorldState to apply the overrides to.
*/
private void applyStateOverrides(
@VisibleForTesting
protected void applyStateOverrides(
final AccountOverrideMap accountOverrideMap, final MutableWorldState ws) {
var updater = ws.updater();
for (Address accountToOverride : accountOverrideMap.keySet()) {

final AccountOverride override = accountOverrideMap.get(accountToOverride);
MutableAccount account = updater.getOrCreate(accountToOverride);
override.getNonce().ifPresent(account::setNonce);
Expand Down Expand Up @@ -237,7 +271,8 @@ private void applyStateOverrides(
* @param blockOverrides The BlockOverrides to apply.
* @return The modified block header.
*/
private BlockHeader applyBlockHeaderOverrides(
@VisibleForTesting
protected BlockHeader applyBlockHeaderOverrides(
final BlockHeader header,
final ProtocolSpec newProtocolSpec,
final BlockOverrides blockOverrides) {
Expand All @@ -251,19 +286,19 @@ private BlockHeader applyBlockHeaderOverrides(
.coinbase(
blockOverrides
.getFeeRecipient()
.orElse(miningConfiguration.getCoinbase().orElseThrow()))
.orElseGet(() -> miningConfiguration.getCoinbase().orElseThrow()))
.difficulty(
blockOverrides.getDifficulty().isPresent()
? Difficulty.of(blockOverrides.getDifficulty().get())
: header.getDifficulty())
.gasLimit(
blockOverrides
.getGasLimit()
.orElse(getNextGasLimit(newProtocolSpec, header, blockNumber)))
.orElseGet(()-> getNextGasLimit(newProtocolSpec, header, blockNumber)))
.baseFee(
blockOverrides
.getBaseFeePerGas()
.orElse(getNextBaseFee(newProtocolSpec, header, blockNumber)))
.orElseGet(()-> getNextBaseFee(newProtocolSpec, header, blockNumber)))
.mixHash(blockOverrides.getMixHashOrPrevRandao().orElse(Hash.EMPTY))
.extraData(blockOverrides.getExtraData().orElse(Bytes.EMPTY))
.blockHeaderFunctions(new SimulatorBlockHeaderFunctions(blockOverrides))
Expand Down Expand Up @@ -311,7 +346,8 @@ private BlockHeader createFinalBlockHeader(
* @param shouldValidate Whether to validate transactions.
* @return The TransactionValidationParams for the block simulation.
*/
private ImmutableTransactionValidationParams buildTransactionValidationParams(
@VisibleForTesting
ImmutableTransactionValidationParams buildTransactionValidationParams(
final boolean shouldValidate) {

if (shouldValidate) {
Expand All @@ -336,6 +372,10 @@ private long getNextGasLimit(
blockNumber);
}

/**
* Override the mining beneficiary calculator if a fee recipient is specified, otherwise use the
* default
*/
private MiningBeneficiaryCalculator getMiningBeneficiaryCalculator(
final BlockOverrides blockOverrides, final ProtocolSpec newProtocolSpec) {
if (blockOverrides.getFeeRecipient().isPresent()) {
Expand All @@ -347,19 +387,17 @@ private MiningBeneficiaryCalculator getMiningBeneficiaryCalculator(

private Wei getNextBaseFee(
final ProtocolSpec protocolSpec, final BlockHeader parentHeader, final long blockNumber) {
final Wei baseFee =
Optional.of(protocolSpec.getFeeMarket())
.filter(FeeMarket::implementsBaseFee)
.map(BaseFeeMarket.class::cast)
.map(
feeMarket ->
feeMarket.computeBaseFee(
blockNumber,
parentHeader.getBaseFee().orElse(Wei.ZERO),
parentHeader.getGasUsed(),
feeMarket.targetGasUsed(parentHeader)))
.orElse(null);
return baseFee;
return Optional.of(protocolSpec.getFeeMarket())
.filter(FeeMarket::implementsBaseFee)
.map(BaseFeeMarket.class::cast)
.map(
feeMarket ->
feeMarket.computeBaseFee(
blockNumber,
parentHeader.getBaseFee().orElse(Wei.ZERO),
parentHeader.getGasUsed(),
feeMarket.targetGasUsed(parentHeader)))
.orElse(null);
}

private static class SimulatorBlockHeaderFunctions implements BlockHeaderFunctions {
Expand Down
Loading

0 comments on commit a3c3a20

Please sign in to comment.