From a3c3a202281333d258fd067b519777ce1258bbc6 Mon Sep 17 00:00:00 2001 From: Gabriel-Trintinalia Date: Tue, 10 Dec 2024 13:24:42 +1100 Subject: [PATCH] Add tests Signed-off-by: Gabriel-Trintinalia --- .../services/BlockSimulatorServiceImpl.java | 3 +- .../transaction/BlockSimulationException.java | 11 + .../ethereum/transaction/BlockSimulator.java | 126 +++++--- .../transaction/BlockSimulatorTest.java | 294 ++++++++++++++++++ 4 files changed, 388 insertions(+), 46 deletions(-) create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulationException.java create mode 100644 ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/BlockSimulatorTest.java diff --git a/besu/src/main/java/org/hyperledger/besu/services/BlockSimulatorServiceImpl.java b/besu/src/main/java/org/hyperledger/besu/services/BlockSimulatorServiceImpl.java index 85107bfd662..4673b9c3588 100644 --- a/besu/src/main/java/org/hyperledger/besu/services/BlockSimulatorServiceImpl.java +++ b/besu/src/main/java/org/hyperledger/besu/services/BlockSimulatorServiceImpl.java @@ -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); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulationException.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulationException.java new file mode 100644 index 00000000000..9874fbd9322 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulationException.java @@ -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); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulator.java index 0bfb8727ba3..bb30672b224 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulator.java @@ -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; @@ -90,7 +96,9 @@ public List 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); } @@ -104,14 +112,14 @@ public List process( * @param worldState The initial MutableWorldState to start with. * @return A list of BlockSimulationResult objects from processing each BlockStateCall. */ - public List processWithMutableWorldState( + public List process( final BlockHeader header, final List blockStateCalls, final MutableWorldState worldState) { List simulationResults = new ArrayList<>(); for (BlockStateCall blockStateCall : blockStateCalls) { BlockSimulationResult simulationResult = - processWithMutableWorldState(header, blockStateCall, worldState); + processSingleBlockStateCall(header, blockStateCall, worldState); simulationResults.add(simulationResult); } return simulationResults; @@ -125,28 +133,37 @@ public List 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 receipts = new ArrayList<>(); - final List transactions = new ArrayList<>(); - List transactionSimulations = new ArrayList<>(); - + // Override the mining beneficiary calculator if a fee recipient is specified, otherwise use the + // default MiningBeneficiaryCalculator miningBeneficiaryCalculator = getMiningBeneficiaryCalculator(blockOverrides, newProtocolSpec); + List transactionSimulatorResults = + processTransactions(blockHeader, blockStateCall, ws, miningBeneficiaryCalculator); + + return finalizeBlock( + blockHeader, blockStateCall, ws, newProtocolSpec, transactionSimulatorResults); + } + + @VisibleForTesting + protected List processTransactions( + final BlockHeader blockHeader, + final BlockStateCall blockStateCall, + final MutableWorldState ws, + final MiningBeneficiaryCalculator miningBeneficiaryCalculator) { + + List transactionSimulations = new ArrayList<>(); + for (CallParameter callParameter : blockStateCall.getCalls()) { final WorldUpdater transactionUpdater = ws.updater(); @@ -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 transactionSimulations) { - TransactionProcessingResult transactionProcessingResult = - transactionSimulatorResult.get().result(); - final Transaction transaction = transactionSimulatorResult.get().transaction(); + long currentGasUsed = 0; + final var transactionReceiptFactory = protocolSpec.getTransactionReceiptFactory(); + + final List receipts = new ArrayList<>(); + final List transactions = new ArrayList<>(); + + for (TransactionSimulatorResult transactionSimulatorResult : transactionSimulations) { + + TransactionProcessingResult transactionProcessingResult = transactionSimulatorResult.result(); + final Transaction transaction = transactionSimulatorResult.transaction(); currentGasUsed += transaction.getGasLimit() - transactionProcessingResult.getGasRemaining(); @@ -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); @@ -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) { @@ -251,7 +286,7 @@ private BlockHeader applyBlockHeaderOverrides( .coinbase( blockOverrides .getFeeRecipient() - .orElse(miningConfiguration.getCoinbase().orElseThrow())) + .orElseGet(() -> miningConfiguration.getCoinbase().orElseThrow())) .difficulty( blockOverrides.getDifficulty().isPresent() ? Difficulty.of(blockOverrides.getDifficulty().get()) @@ -259,11 +294,11 @@ private BlockHeader applyBlockHeaderOverrides( .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)) @@ -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) { @@ -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()) { @@ -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 { diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/BlockSimulatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/BlockSimulatorTest.java new file mode 100644 index 00000000000..387c839c38a --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/BlockSimulatorTest.java @@ -0,0 +1,294 @@ +/* + * 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.ethereum.transaction; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.tuweni.bytes.Bytes32; +import org.assertj.core.internal.Diff; +import org.hyperledger.besu.datatypes.AccountOverride; +import org.hyperledger.besu.datatypes.AccountOverrideMap; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BlockOverrides; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.GasLimitCalculator; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; +import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.MiningConfiguration; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.mainnet.ImmutableTransactionValidationParams; +import org.hyperledger.besu.ethereum.mainnet.MiningBeneficiaryCalculator; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; +import org.hyperledger.besu.ethereum.mainnet.ValidationResult; +import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; +import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.tracing.OperationTracer; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt256; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +@SuppressWarnings("AlmostJavadoc") +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class BlockSimulatorTest { + + @Mock private WorldStateArchive worldStateArchive; + @Mock private ProtocolSchedule protocolSchedule; + @Mock private TransactionSimulator transactionSimulator; + @Mock private MiningConfiguration miningConfiguration; + @Mock private MutableWorldState mutableWorldState; + @Mock private BlockHeader blockHeader; + + private BlockSimulator blockSimulator; + + @BeforeEach + public void setUp() { + blockSimulator = + new BlockSimulator( + worldStateArchive, protocolSchedule, transactionSimulator, miningConfiguration); + blockHeader = BlockHeaderBuilder.createDefault().buildBlockHeader(); + ProtocolSpec protocolSpec = mock(ProtocolSpec.class); + when(miningConfiguration.getCoinbase()).thenReturn(Optional.ofNullable(Address.fromHexString("0x1"))); + when(protocolSchedule.getForNextBlockHeader(any(), anyLong())).thenReturn(protocolSpec); + when(protocolSpec.getMiningBeneficiaryCalculator()).thenReturn(mock(MiningBeneficiaryCalculator.class)); + GasLimitCalculator gasLimitCalculator = mock(GasLimitCalculator.class); + when(protocolSpec.getGasLimitCalculator()).thenReturn(gasLimitCalculator); + when(gasLimitCalculator.nextGasLimit( anyLong(), anyLong(), anyLong())).thenReturn(1L); + when(protocolSpec.getFeeMarket()).thenReturn(mock(FeeMarket.class)); + } + + @Test + public void testProcessWithValidWorldState() { + when(worldStateArchive.getMutable(any(BlockHeader.class), eq(false))) + .thenReturn(Optional.of(mutableWorldState)); + + List results = + blockSimulator.process(blockHeader, Collections.emptyList()); + + assertNotNull(results); + verify(worldStateArchive).getMutable(any(BlockHeader.class), eq(false)); + } + + @Test + public void testProcessWithInvalidWorldState() { + when(worldStateArchive.getMutable(any(BlockHeader.class), eq(false))) + .thenReturn(Optional.empty()); + when(blockHeader.toLogString()).thenReturn("1"); + + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> blockSimulator.process(blockHeader, Collections.emptyList())); + + assertEquals("Public world state not available for block 1", exception.getMessage()); + } + + + @Test + public void shouldStopBlockSimulationWhenTransactionSimulationIsInvalid() { + + CallParameter callParameter = mock(CallParameter.class); + BlockStateCall blockStateCall = new BlockStateCall(List.of(callParameter), null, null, true); + + TransactionSimulatorResult transactionSimulatorResult = mock(TransactionSimulatorResult.class); + + TransactionProcessingResult transactionProcessingResult = mock(TransactionProcessingResult.class); + when(transactionSimulatorResult.result()).thenReturn(transactionProcessingResult); + when(transactionProcessingResult.isInvalid()).thenReturn(true); + when(transactionSimulatorResult.getValidationResult()).thenReturn(ValidationResult.invalid()); + + when(worldStateArchive.getMutable(any(BlockHeader.class), eq(false))) + .thenReturn(Optional.of(mutableWorldState)); + when(transactionSimulator.processWithWorldUpdater( + any(), + any(), + any(), + any(), + any(), + any(), + any(MiningBeneficiaryCalculator.class))) + .thenReturn(Optional.of(transactionSimulatorResult)); + + when(transactionSimulator.processWithWorldUpdater( + any(), + any(), + any(), + any(), + any(), + any(), + any(MiningBeneficiaryCalculator.class))) + .thenReturn(Optional.of(transactionSimulatorResult)); + + BlockSimulationException exception = + assertThrows( + BlockSimulationException.class, + () -> + blockSimulator.process(blockHeader, List.of(blockStateCall), mutableWorldState) + ); + + assertEquals("Public world state not available for block 1", exception.getMessage()); + } + + + @Test + public void testApplyStateOverrides() { + AccountOverrideMap accountOverrideMap = mock(AccountOverrideMap.class); + Address address = mock(Address.class); + AccountOverride accountOverride = mock(AccountOverride.class); + MutableAccount mutableAccount = mock(MutableAccount.class); + + when(accountOverrideMap.keySet()).thenReturn(Set.of(address)); + when(accountOverrideMap.get(address)).thenReturn(accountOverride); + + WorldUpdater worldUpdater = mock(WorldUpdater.class); + when(mutableWorldState.updater()).thenReturn(worldUpdater); + + when(worldUpdater.getOrCreate(address)).thenReturn(mutableAccount); + + when(accountOverride.getNonce()).thenReturn(Optional.of(123L)); + when(accountOverride.getBalance()).thenReturn(Optional.of(Wei.of(456L))); + when(accountOverride.getCode()).thenReturn(Optional.of("")); + when(accountOverride.getStateDiff()) + .thenReturn(Optional.of(new HashMap<>(Map.of("0x0", "0x1")))); + + blockSimulator.applyStateOverrides(accountOverrideMap, mutableWorldState); + + verify(mutableAccount).setNonce(anyLong()); + verify(mutableAccount).setBalance(any(Wei.class)); + verify(mutableAccount).setCode(any(Bytes.class)); + verify(mutableAccount).setStorageValue(any(UInt256.class), any(UInt256.class)); + } + + @Test + public void testApplyBlockHeaderOverrides() { + BlockOverrides blockOverrides = mock(BlockOverrides.class); + ProtocolSpec protocolSpec = mock(ProtocolSpec.class); + + var expectedTimestamp = Optional.of(1L); + var expectedBlockNumber = Optional.of(2L); + var expectedFeeRecipient = Optional.of(Address.fromHexString("0x1")); + var expectedBaseFeePerGas = Optional.of(Wei.of(7L)); + var expectedGasLimit = Optional.of(5L); + var expectedDifficulty = Optional.of(BigInteger.ONE); + var expectedMixHashOrPrevRandao = Optional.of(Hash.hash( Bytes.fromHexString("0x01"))); + var expectedExtraData = Optional.of(Bytes.fromHexString("0x02")); + + when(blockOverrides.getTimestamp()).thenReturn(expectedTimestamp); + when(blockOverrides.getBlockNumber()).thenReturn(expectedBlockNumber); + when(blockOverrides.getFeeRecipient()).thenReturn(expectedFeeRecipient); + when(blockOverrides.getBaseFeePerGas()).thenReturn(expectedBaseFeePerGas); + when(blockOverrides.getGasLimit()).thenReturn(expectedGasLimit); + when(blockOverrides.getDifficulty()).thenReturn(expectedDifficulty); + when(blockOverrides.getMixHashOrPrevRandao()).thenReturn(expectedMixHashOrPrevRandao); + when(blockOverrides.getExtraData()).thenReturn(expectedExtraData); + + BlockHeader result = + blockSimulator.applyBlockHeaderOverrides(blockHeader, protocolSpec, blockOverrides); + + assertNotNull(result); + assertEquals(expectedTimestamp.get(), result.getTimestamp()); + assertEquals(expectedBlockNumber.get(), result.getNumber()); + assertEquals(expectedFeeRecipient.get(), result.getCoinbase()); + assertEquals(expectedBaseFeePerGas, result.getBaseFee()); + assertEquals(expectedGasLimit.get(), result.getGasLimit()); + assertThat(result.getDifficulty()).isEqualTo(Difficulty.of(expectedDifficulty.get())); + assertEquals(expectedMixHashOrPrevRandao.get(), result.getMixHash()); + assertEquals(expectedExtraData.get(), result.getExtraData()); + } + + /* @Test + public void testProcessTransactions() { + + BlockStateCall blockStateCall = mock(BlockStateCall.class); + CallParameter callParameter = mock(CallParameter.class); + TransactionSimulatorResult transactionSimulatorResult = mock(TransactionSimulatorResult.class); + TransactionProcessingResult transactionProcessingResult = mock(TransactionProcessingResult.class); + MiningBeneficiaryCalculator miningBeneficiaryCalculator = mock(MiningBeneficiaryCalculator.class); + WorldUpdater worldUpdater = mock(WorldUpdater.class); + + when(blockStateCall.getCalls()).thenReturn(List.of(callParameter)); + when(transactionSimulator.processWithWorldUpdater( + eq(callParameter), + eq(Optional.empty()), + any(TransactionValidationParams.class), + eq(OperationTracer.NO_TRACING), + eq(blockHeader), + any(WorldUpdater.class), + eq(miningBeneficiaryCalculator))) + .thenReturn(Optional.of(transactionSimulatorResult)); + when(transactionSimulatorResult.result()).thenReturn(transactionProcessingResult); + when(transactionProcessingResult.isInvalid()).thenReturn(false); + when(mutableWorldState.updater()).thenReturn(worldUpdater); + + List results = blockSimulator.processTransactions( + blockHeader, blockStateCall, mutableWorldState, miningBeneficiaryCalculator); + + assertNotNull(results); + assertEquals(1, results.size()); + verify(worldUpdater).commit(); + }*/ + + + @Test + public void testBuildTransactionValidationParams() { + var configWhenValidate = + ImmutableTransactionValidationParams.builder() + .from(TransactionValidationParams.processingBlock()) + .build(); + + ImmutableTransactionValidationParams params = + blockSimulator.buildTransactionValidationParams(true); + assertThat(params).isEqualTo(configWhenValidate); + assertThat(params.isAllowExceedingBalance()).isFalse(); + + params = blockSimulator.buildTransactionValidationParams(false); + assertThat(params.isAllowExceedingBalance()).isTrue(); + } +}