eth_simulateV1 - Fill gaps between block calls (#8375)

Signed-off-by: Gabriel-Trintinalia <gabriel.trintinalia@consensys.net>
This commit is contained in:
Gabriel-Trintinalia
2025-03-06 15:13:51 +11:00
committed by GitHub
parent 7d4c8be06b
commit f92780859d
9 changed files with 485 additions and 29 deletions

View File

@@ -21,6 +21,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MiningConfiguration;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.transaction.BlockSimulationParameter;
import org.hyperledger.besu.ethereum.transaction.BlockSimulationResult;
import org.hyperledger.besu.ethereum.transaction.BlockSimulator;
import org.hyperledger.besu.ethereum.transaction.BlockStateCall;
@@ -116,10 +117,12 @@ public class BlockSimulatorServiceImpl implements BlockSimulationService {
List<CallParameter> callParameters =
transactions.stream().map(CallParameter::fromTransaction).toList();
BlockStateCall blockStateCall =
new BlockStateCall(callParameters, blockOverrides, stateOverrides, true);
new BlockStateCall(callParameters, blockOverrides, stateOverrides);
try (final MutableWorldState ws = getWorldState(header, persistWorldState)) {
BlockSimulationParameter blockSimulationParameter =
new BlockSimulationParameter(blockStateCall, true);
List<BlockSimulationResult> results =
blockSimulator.process(header, List.of(blockStateCall), ws);
blockSimulator.process(header, blockSimulationParameter, ws);
BlockSimulationResult result = results.getFirst();
if (persistWorldState) {
ws.persist(result.getBlock().getHeader());

View File

@@ -0,0 +1,55 @@
/*
* 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 com.google.common.base.Preconditions.checkNotNull;
import java.util.List;
public class BlockSimulationParameter {
static final BlockSimulationParameter EMPTY = new BlockSimulationParameter(List.of(), false);
final List<BlockStateCall> blockStateCalls;
private final boolean validation;
public BlockSimulationParameter(final List<BlockStateCall> blockStateCalls) {
this.blockStateCalls = blockStateCalls;
this.validation = false;
}
public BlockSimulationParameter(final BlockStateCall blockStateCall) {
this(List.of(blockStateCall));
}
public BlockSimulationParameter(final BlockStateCall blockStateCall, final boolean validation) {
this(List.of(blockStateCall), validation);
}
public BlockSimulationParameter(
final List<BlockStateCall> blockStateCalls, final boolean validation) {
checkNotNull(blockStateCalls);
this.blockStateCalls = blockStateCalls;
this.validation = validation;
}
public List<BlockStateCall> getBlockStateCalls() {
return blockStateCalls;
}
public boolean isValidation() {
return validation;
}
}

View File

@@ -14,6 +14,7 @@
*/
package org.hyperledger.besu.ethereum.transaction;
import static org.hyperledger.besu.ethereum.transaction.BlockStateCalls.fillBlockStateCalls;
import static org.hyperledger.besu.ethereum.trie.diffbased.common.provider.WorldStateQueryParams.withBlockHeaderAndNoUpdateNodeHead;
import org.hyperledger.besu.datatypes.Address;
@@ -90,11 +91,11 @@ public class BlockSimulator {
* Processes a list of BlockStateCalls sequentially, collecting the results.
*
* @param header The block header for all simulations.
* @param blockStateCalls The list of BlockStateCalls to process.
* @param blockSimulationParameter The blockSimulationParameter containing the block state calls.
* @return A list of BlockSimulationResult objects from processing each BlockStateCall.
*/
public List<BlockSimulationResult> process(
final BlockHeader header, final List<? extends BlockStateCall> blockStateCalls) {
final BlockHeader header, final BlockSimulationParameter blockSimulationParameter) {
try (final MutableWorldState ws =
worldStateArchive
.getWorldState(withBlockHeaderAndNoUpdateNodeHead(header))
@@ -102,7 +103,7 @@ public class BlockSimulator {
() ->
new IllegalArgumentException(
"Public world state not available for block " + header.toLogString()))) {
return process(header, blockStateCalls, ws);
return process(header, blockSimulationParameter, ws);
} catch (IllegalArgumentException e) {
throw e;
} catch (final Exception e) {
@@ -114,18 +115,24 @@ public class BlockSimulator {
* Processes a list of BlockStateCalls sequentially, collecting the results.
*
* @param header The block header for all simulations.
* @param blockStateCalls The list of BlockStateCalls to process.
* @param blockSimulationParameter The blockSimulationParameter containing the block state calls.
* @param worldState The initial MutableWorldState to start with.
* @return A list of BlockSimulationResult objects from processing each BlockStateCall.
*/
public List<BlockSimulationResult> process(
final BlockHeader header,
final List<? extends BlockStateCall> blockStateCalls,
final BlockSimulationParameter blockSimulationParameter,
final MutableWorldState worldState) {
List<BlockSimulationResult> simulationResults = new ArrayList<>();
// Fill gaps between blocks
List<BlockStateCall> blockStateCalls =
fillBlockStateCalls(blockSimulationParameter.getBlockStateCalls(), header);
for (BlockStateCall blockStateCall : blockStateCalls) {
BlockSimulationResult simulationResult =
processSingleBlockStateCall(header, blockStateCall, worldState);
processBlockStateCall(
header, blockStateCall, worldState, blockSimulationParameter.isValidation());
simulationResults.add(simulationResult);
}
return simulationResults;
@@ -139,8 +146,11 @@ public class BlockSimulator {
* @param ws The MutableWorldState to use for the simulation.
* @return A BlockSimulationResult from processing the BlockStateCall.
*/
private BlockSimulationResult processSingleBlockStateCall(
final BlockHeader header, final BlockStateCall blockStateCall, final MutableWorldState ws) {
private BlockSimulationResult processBlockStateCall(
final BlockHeader header,
final BlockStateCall blockStateCall,
final MutableWorldState ws,
final boolean shouldValidate) {
BlockOverrides blockOverrides = blockStateCall.getBlockOverrides();
long timestamp = blockOverrides.getTimestamp().orElse(header.getTimestamp() + 1);
ProtocolSpec newProtocolSpec = protocolSchedule.getForNextBlockHeader(header, timestamp);
@@ -159,7 +169,12 @@ public class BlockSimulator {
List<TransactionSimulatorResult> transactionSimulatorResults =
processTransactions(
blockHeader, blockStateCall, ws, miningBeneficiaryCalculator, blockHashLookup);
blockHeader,
blockStateCall,
ws,
shouldValidate,
miningBeneficiaryCalculator,
blockHashLookup);
return finalizeBlock(
blockHeader, blockStateCall, ws, newProtocolSpec, transactionSimulatorResults);
@@ -170,6 +185,7 @@ public class BlockSimulator {
final BlockHeader blockHeader,
final BlockStateCall blockStateCall,
final MutableWorldState ws,
final boolean shouldValidate,
final MiningBeneficiaryCalculator miningBeneficiaryCalculator,
final BlockHashLookup blockHashLookup) {
@@ -182,7 +198,7 @@ public class BlockSimulator {
transactionSimulator.processWithWorldUpdater(
callParameter,
Optional.empty(), // We have already applied state overrides on block level
buildTransactionValidationParams(blockStateCall.isValidate()),
buildTransactionValidationParams(shouldValidate),
OperationTracer.NO_TRACING,
blockHeader,
transactionUpdater,

View File

@@ -29,22 +29,18 @@ public class BlockStateCall {
private final StateOverrideMap stateOverrideMap;
private final boolean validation;
public BlockStateCall(
final List<? extends CallParameter> calls,
final BlockOverrides blockOverrides,
final StateOverrideMap stateOverrideMap,
final boolean validation) {
final StateOverrideMap stateOverrideMap) {
this.calls = calls != null ? calls : new ArrayList<>();
this.blockOverrides =
blockOverrides != null ? blockOverrides : BlockOverrides.builder().build();
this.stateOverrideMap = stateOverrideMap;
this.validation = validation;
}
public boolean isValidate() {
return validation;
public BlockStateCall(final BlockOverrides blockOverrides) {
this(null, blockOverrides, null);
}
public BlockOverrides getBlockOverrides() {

View File

@@ -0,0 +1,135 @@
/*
* 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 org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.plugin.data.BlockOverrides;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* A class that manages a chain of BlockStateCalls. It fills gaps between blocks and sets the
* correct block number and timestamp when they are not set.
*/
public class BlockStateCalls {
private static final long MAX_BLOCK_CALL_SIZE = 256;
private static final long TIMESTAMP_INCREMENT = 12;
/**
* Normalizes a list of BlockStateCalls by filling gaps and setting the correct block number and
* timestamp.
*
* @param blockStateCalls the list of BlockStateCalls to normalize
* @param header the initial block header
* @return a normalized list of BlockStateCalls
*/
public static List<BlockStateCall> fillBlockStateCalls(
final List<? extends BlockStateCall> blockStateCalls, final BlockHeader header) {
long lastPresentBlockNumber = findLastBlockNumber(blockStateCalls);
if (lastPresentBlockNumber > header.getNumber() + MAX_BLOCK_CALL_SIZE) {
throw new IllegalArgumentException(
String.format(
"Block number %d exceeds the limit of %d (header: %d + MAX_BLOCK_CALL_SIZE: %d)",
lastPresentBlockNumber,
header.getNumber() + MAX_BLOCK_CALL_SIZE,
header.getNumber(),
MAX_BLOCK_CALL_SIZE));
}
List<BlockStateCall> filledCalls = new ArrayList<>();
long currentBlock = header.getNumber();
long currentTimestamp = header.getTimestamp();
for (BlockStateCall blockStateCall : blockStateCalls) {
long nextBlockNumber =
blockStateCall.getBlockOverrides().getBlockNumber().orElse(currentBlock + 1);
List<BlockStateCall> intermediateBlocks =
new ArrayList<>(
generateIntermediateBlocks(nextBlockNumber, currentBlock, currentTimestamp));
// Add intermediate blocks
for (BlockStateCall intermediateBlock : intermediateBlocks) {
add(filledCalls, intermediateBlock, currentBlock, currentTimestamp);
currentBlock = intermediateBlock.getBlockOverrides().getBlockNumber().orElseThrow();
currentTimestamp = intermediateBlock.getBlockOverrides().getTimestamp().orElseThrow();
}
// set the block number and timestamp if they are not set
if (blockStateCall.getBlockOverrides().getBlockNumber().isEmpty()) {
blockStateCall.getBlockOverrides().setBlockNumber(currentBlock + 1);
}
if (blockStateCall.getBlockOverrides().getTimestamp().isEmpty()) {
blockStateCall.getBlockOverrides().setTimestamp(currentTimestamp + TIMESTAMP_INCREMENT);
}
// Add the current block
add(filledCalls, blockStateCall, currentBlock, currentTimestamp);
currentBlock = blockStateCall.getBlockOverrides().getBlockNumber().orElseThrow();
currentTimestamp = blockStateCall.getBlockOverrides().getTimestamp().orElseThrow();
}
return filledCalls;
}
private static void add(
final List<BlockStateCall> blockStateCalls,
final BlockStateCall blockStateCall,
final long currentBlockNumber,
final long currentTimestamp) {
long blockNumber = blockStateCall.getBlockOverrides().getBlockNumber().orElseThrow();
long timestamp = blockStateCall.getBlockOverrides().getTimestamp().orElseThrow();
if (blockNumber <= currentBlockNumber) {
throw new IllegalArgumentException(
String.format(
"Block number is invalid. Trying to add a call at block number %s, while current block number is %s.",
blockNumber, currentBlockNumber));
}
if (timestamp <= currentTimestamp) {
throw new IllegalArgumentException(
String.format(
"Timestamp is invalid. Trying to add a call at timestamp %s, while current timestamp is %s.",
timestamp, currentTimestamp));
}
blockStateCalls.add(blockStateCall);
}
private static List<BlockStateCall> generateIntermediateBlocks(
final long targetBlockNumber, final long startBlockNumber, final long startTimestamp) {
List<BlockStateCall> intermediateBlocks = new ArrayList<>();
long blockNumberDiff = targetBlockNumber - startBlockNumber;
for (int i = 1; i < blockNumberDiff; i++) {
long nextBlockNumber = startBlockNumber + i;
long nextTimestamp = startTimestamp + TIMESTAMP_INCREMENT * i;
var nextBlockOverrides =
BlockOverrides.builder().blockNumber(nextBlockNumber).timestamp(nextTimestamp).build();
intermediateBlocks.add(new BlockStateCall(nextBlockOverrides));
}
return intermediateBlocks;
}
private static long findLastBlockNumber(final List<? extends BlockStateCall> blockStateCalls) {
var lastPresentBlockNumber =
blockStateCalls.stream()
.map(blockStateCall -> blockStateCall.getBlockOverrides().getBlockNumber())
.filter(Optional::isPresent)
.mapToLong(Optional::get)
.max()
.orElse(-1);
long callsAfterLastPresentBlockNumber =
blockStateCalls.stream()
.filter(
blockStateCall ->
blockStateCall.getBlockOverrides().getBlockNumber().orElse(Long.MAX_VALUE)
> lastPresentBlockNumber)
.count();
return lastPresentBlockNumber + callsAfterLastPresentBlockNumber;
}
}

View File

@@ -52,7 +52,6 @@ import org.hyperledger.besu.evm.worldstate.WorldUpdater;
import org.hyperledger.besu.plugin.data.BlockOverrides;
import java.math.BigInteger;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -112,7 +111,7 @@ public class BlockSimulatorTest {
.thenReturn(Optional.of(mutableWorldState));
List<BlockSimulationResult> results =
blockSimulator.process(blockHeader, Collections.emptyList());
blockSimulator.process(blockHeader, BlockSimulationParameter.EMPTY);
assertNotNull(results);
verify(worldStateArchive).getWorldState(withBlockHeaderAndNoUpdateNodeHead(blockHeader));
}
@@ -125,7 +124,7 @@ public class BlockSimulatorTest {
IllegalArgumentException exception =
assertThrows(
IllegalArgumentException.class,
() -> blockSimulator.process(blockHeader, Collections.emptyList()));
() -> blockSimulator.process(blockHeader, BlockSimulationParameter.EMPTY));
assertEquals(
String.format("Public world state not available for block %s", blockHeader.toLogString()),
@@ -136,7 +135,7 @@ public class BlockSimulatorTest {
public void shouldStopWhenTransactionSimulationIsInvalid() {
CallParameter callParameter = mock(CallParameter.class);
BlockStateCall blockStateCall = new BlockStateCall(List.of(callParameter), null, null, true);
BlockStateCall blockStateCall = new BlockStateCall(List.of(callParameter), null, null);
TransactionSimulatorResult transactionSimulatorResult = mock(TransactionSimulatorResult.class);
when(transactionSimulatorResult.isInvalid()).thenReturn(true);
@@ -157,7 +156,9 @@ public class BlockSimulatorTest {
BlockSimulationException exception =
assertThrows(
BlockSimulationException.class,
() -> blockSimulator.process(blockHeader, List.of(blockStateCall), mutableWorldState));
() ->
blockSimulator.process(
blockHeader, new BlockSimulationParameter(blockStateCall), mutableWorldState));
assertEquals(
"Transaction simulator result is invalid: Invalid Transaction", exception.getMessage());
@@ -167,7 +168,7 @@ public class BlockSimulatorTest {
public void shouldStopWhenTransactionSimulationIsEmpty() {
CallParameter callParameter = mock(CallParameter.class);
BlockStateCall blockStateCall = new BlockStateCall(List.of(callParameter), null, null, true);
BlockStateCall blockStateCall = new BlockStateCall(List.of(callParameter), null, null);
when(transactionSimulator.processWithWorldUpdater(
any(),
@@ -183,7 +184,9 @@ public class BlockSimulatorTest {
BlockSimulationException exception =
assertThrows(
BlockSimulationException.class,
() -> blockSimulator.process(blockHeader, List.of(blockStateCall), mutableWorldState));
() ->
blockSimulator.process(
blockHeader, new BlockSimulationParameter(blockStateCall), mutableWorldState));
assertEquals("Transaction simulator result is empty", exception.getMessage());
}

View File

@@ -0,0 +1,230 @@
/*
* 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.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.plugin.data.BlockOverrides;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class BlockStateCallsTest {
private BlockHeader mockBlockHeader;
private static final long MAX_BLOCK_CALL_SIZE = 256;
private final long headerTimestamp = 1000L;
@BeforeEach
void setUp() {
mockBlockHeader = mock(BlockHeader.class);
when(mockBlockHeader.getTimestamp()).thenReturn(headerTimestamp);
when(mockBlockHeader.getNumber()).thenReturn(1L);
}
/** Tests that gaps between block numbers are filled correctly when adding a BlockStateCall. */
@Test
void shouldFillGapsBetweenBlockNumbers() {
// BlockHeader is at block number 1
// BlockStateCall is at block number 4
// Should fill gaps between 1 and 4 with block numbers 2 and 3
BlockStateCall blockStateCall = createBlockStateCall(4L, 1036L);
List<BlockStateCall> blockStateCalls =
BlockStateCalls.fillBlockStateCalls(List.of(blockStateCall), mockBlockHeader);
assertEquals(3, blockStateCalls.size());
assertEquals(2L, blockStateCalls.get(0).getBlockOverrides().getBlockNumber().orElseThrow());
assertEquals(1012L, blockStateCalls.get(0).getBlockOverrides().getTimestamp().orElseThrow());
assertEquals(3L, blockStateCalls.get(1).getBlockOverrides().getBlockNumber().orElseThrow());
assertEquals(1024L, blockStateCalls.get(1).getBlockOverrides().getTimestamp().orElseThrow());
assertEquals(4L, blockStateCalls.get(2).getBlockOverrides().getBlockNumber().orElseThrow());
assertEquals(1036L, blockStateCalls.get(2).getBlockOverrides().getTimestamp().orElseThrow());
}
/**
* Tests that the block number is updated correctly if it is not present in the BlockStateCall.
*/
@Test
void shouldUpdateBlockNumberIfNotPresent() {
// BlockHeader is at block number 1
// BlockStateCall does not have a block number set
// Should set block number to 2
long expectedBlockNumber = 2L;
BlockStateCall blockStateCall = createBlockStateCall(null, null);
List<BlockStateCall> blockStateCalls =
BlockStateCalls.fillBlockStateCalls(List.of(blockStateCall), mockBlockHeader);
assertEquals(1, blockStateCalls.size());
assertEquals(
expectedBlockNumber,
blockStateCalls.getFirst().getBlockOverrides().getBlockNumber().orElseThrow());
}
/** Tests that the timestamp is updated correctly if it is not present in the BlockStateCall. */
@Test
void shouldUpdateTimestampIfNotPresent() {
// BlockHeader is at block number 1 and timestamp 1000
// BlockStateCall does not have a timestamp set
// Should set timestamp to 1024
long blockNumber = 3L;
long expectedTimestamp = headerTimestamp + (blockNumber - 1L) * 12;
BlockStateCall blockStateCall = createBlockStateCall(blockNumber, null);
List<BlockStateCall> blockStateCalls =
BlockStateCalls.fillBlockStateCalls(List.of(blockStateCall), mockBlockHeader);
assertEquals(
expectedTimestamp,
blockStateCalls.getLast().getBlockOverrides().getTimestamp().orElseThrow());
}
/**
* Tests that a list of BlockStateCalls is normalized correctly by filling gaps and setting block
* numbers and timestamps.
*/
@Test
void shouldFillBlockStateCalls() {
// BlockHeader is at block number 1 and timestamp 1000
// BlockStateCalls are at block numbers 3 and 5
// Should fill gaps between 1 and 3 and 3 and 5 with block numbers 2 and 4
List<BlockStateCall> blockStateCalls = new ArrayList<>();
blockStateCalls.add(createBlockStateCall(3L, 1024L));
blockStateCalls.add(createBlockStateCall(5L, 1048L));
var normalizedCalls = BlockStateCalls.fillBlockStateCalls(blockStateCalls, mockBlockHeader);
assertEquals(4, normalizedCalls.size());
assertEquals(2L, normalizedCalls.get(0).getBlockOverrides().getBlockNumber().orElseThrow());
assertEquals(1012L, normalizedCalls.get(0).getBlockOverrides().getTimestamp().orElseThrow());
assertEquals(3L, normalizedCalls.get(1).getBlockOverrides().getBlockNumber().orElseThrow());
assertEquals(1024L, normalizedCalls.get(1).getBlockOverrides().getTimestamp().orElseThrow());
assertEquals(4L, normalizedCalls.get(2).getBlockOverrides().getBlockNumber().orElseThrow());
assertEquals(1036L, normalizedCalls.get(2).getBlockOverrides().getTimestamp().orElseThrow());
assertEquals(5L, normalizedCalls.get(3).getBlockOverrides().getBlockNumber().orElseThrow());
assertEquals(1048L, normalizedCalls.get(3).getBlockOverrides().getTimestamp().orElseThrow());
}
/**
* Tests that an exception is thrown when a BlockStateCall with a block number less than or equal
* to the last block number is added.
*/
@Test
void shouldThrowExceptionForInvalidBlockNumber() {
// BlockHeader is at block number 1
// BlockStateCall block number is 1
// Should throw an exception because the block number is not greater than 1
IllegalArgumentException exception =
assertThrows(
IllegalArgumentException.class,
() ->
BlockStateCalls.fillBlockStateCalls(
List.of(createBlockStateCall(1L, 1012L)), mockBlockHeader));
String expectedMessage =
String.format(
"Block number is invalid. Trying to add a call at block number %s, while current block number is %s.",
1L, 1L);
assertEquals(expectedMessage, exception.getMessage());
}
/**
* Tests that an exception is thrown when a BlockStateCall with a timestamp less than or equal to
* the last timestamp is added.
*/
@Test
void shouldThrowExceptionForInvalidTimestamp() {
// BlockHeader is at block number 1 and timestamp 1000
// BlockStateCall is at block number 2 and timestamp 1000
// Should throw an exception because the timestamp is not greater than the 1000
IllegalArgumentException exception =
assertThrows(
IllegalArgumentException.class,
() ->
BlockStateCalls.fillBlockStateCalls(
List.of(createBlockStateCall(2L, headerTimestamp)), mockBlockHeader));
String expectedMessage =
String.format(
"Timestamp is invalid. Trying to add a call at timestamp %s, while current timestamp is %s.",
headerTimestamp, headerTimestamp); // next timestamp
assertEquals(expectedMessage, exception.getMessage());
}
/**
* Tests that the chain is normalized by adding intermediate blocks and then fails when adding the
* last call due to an invalid timestamp.
*/
@Test
void shouldNormalizeChainAndFailOnInvalidTimestamp() {
// BlockHeader is at block number 1 and timestamp 1000
// BlockStateCall is at block number 3 and timestamp 1012
// Should throw an exception because the timestamp is not greater than 1012
IllegalArgumentException exception =
assertThrows(
IllegalArgumentException.class,
() ->
BlockStateCalls.fillBlockStateCalls(
List.of(createBlockStateCall(3L, 1012L)), mockBlockHeader));
assertEquals(
"Timestamp is invalid. Trying to add a call at timestamp 1012, while current timestamp is 1012.",
exception.getMessage());
}
/** Tests that an exception is thrown when the maximum number of BlockStateCalls is exceeded. */
@Test
void shouldThrowExceptionWhenExceedingMaxBlocks() {
long maxAllowedBlockNumber = MAX_BLOCK_CALL_SIZE + 1;
long invalidBlockNumber = maxAllowedBlockNumber + 1;
BlockStateCall blockStateCall = createBlockStateCall(invalidBlockNumber, null);
IllegalArgumentException exception =
assertThrows(
IllegalArgumentException.class,
() -> BlockStateCalls.fillBlockStateCalls(List.of(blockStateCall), mockBlockHeader));
String expectedMessage =
String.format(
"Block number %d exceeds the limit of %d (header: %d + MAX_BLOCK_CALL_SIZE: %d)",
invalidBlockNumber, maxAllowedBlockNumber, 1L, MAX_BLOCK_CALL_SIZE);
assertEquals(expectedMessage, exception.getMessage());
}
@Test
void shouldThrowExceptionWhenFillBlockStateCallsExceedsMaxBlockCallSize() {
List<BlockStateCall> blockStateCalls = new ArrayList<>();
blockStateCalls.add(createBlockStateCall(101L, 1609459212L));
blockStateCalls.add(createBlockStateCall(257L, 1609459248L));
blockStateCalls.add(createBlockStateCall(null, 1609459224L));
IllegalArgumentException exception =
assertThrows(
IllegalArgumentException.class,
() -> BlockStateCalls.fillBlockStateCalls(blockStateCalls, mockBlockHeader));
assertEquals(
"Block number 258 exceeds the limit of 257 (header: 1 + MAX_BLOCK_CALL_SIZE: 256)",
exception.getMessage());
}
private BlockStateCall createBlockStateCall(final Long blockNumber, final Long timestamp) {
BlockOverrides blockOverrides =
BlockOverrides.builder().blockNumber(blockNumber).timestamp(timestamp).build();
return new BlockStateCall(Collections.emptyList(), blockOverrides, null);
}
}

View File

@@ -71,7 +71,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 = 'D2ZMRGb2HS/9FgDE2mcizdxTQhFsGD4LS9lgngG/TnU='
knownHash = 'Mp4ewH82ykJPHzMATeiPenUJ4TTMyDEad1MI8idyhWk='
}
check.dependsOn('checkAPIChanges')

View File

@@ -28,8 +28,8 @@ import org.apache.tuweni.bytes.Bytes32;
/** BlockOverrides represents the block overrides for a block. */
public class BlockOverrides {
private final Optional<Long> timestamp;
private final Optional<Long> blockNumber;
private Optional<Long> timestamp;
private Optional<Long> blockNumber;
private final Optional<Hash> blockHash;
private final Optional<Bytes32> prevRandao;
private final Optional<Long> gasLimit;
@@ -224,6 +224,24 @@ public class BlockOverrides {
return blockHashLookup;
}
/**
* Sets the timestamp.
*
* @param timestamp the timestamp to set
*/
public void setTimestamp(final Long timestamp) {
this.timestamp = Optional.ofNullable(timestamp);
}
/**
* Sets the block number.
*
* @param blockNumber the block number to set
*/
public void setBlockNumber(final Long blockNumber) {
this.blockNumber = Optional.ofNullable(blockNumber);
}
/**
* Creates a new Builder instance.
*