mirror of
https://github.com/vacp2p/linea-besu.git
synced 2026-01-08 04:33:56 -05:00
eth_simulateV1 - Fill gaps between block calls (#8375)
Signed-off-by: Gabriel-Trintinalia <gabriel.trintinalia@consensys.net>
This commit is contained in:
committed by
GitHub
parent
7d4c8be06b
commit
f92780859d
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user