Skip to content

Commit 75689b8

Browse files
macfarlaclaude
andauthored
eth_simulateV1: conditionally include fork-specific fields (#10125)
Conditionally include baseFeePerGas (London+), parentBeaconBlockRoot (Merge+), excessBlobGas/blobGasUsed (Cancun+), and withdrawalsRoot/withdrawals (Shanghai+) in simulated block responses. Use fixed 12-second slot duration for timestamp auto-increment Signed-off-by: Sally MacFarlane <macfarla.github@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b910d4b commit 75689b8

File tree

4 files changed

+78
-62
lines changed

4 files changed

+78
-62
lines changed

ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulator.java

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,15 @@
3838
import org.hyperledger.besu.ethereum.core.Request;
3939
import org.hyperledger.besu.ethereum.core.Transaction;
4040
import org.hyperledger.besu.ethereum.core.TransactionReceipt;
41+
import org.hyperledger.besu.ethereum.core.Withdrawal;
4142
import org.hyperledger.besu.ethereum.mainnet.BodyValidation;
4243
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions;
4344
import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor;
4445
import org.hyperledger.besu.ethereum.mainnet.MiningBeneficiaryCalculator;
4546
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
4647
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
4748
import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams;
49+
import org.hyperledger.besu.ethereum.mainnet.WithdrawalsValidator.AllowedWithdrawals;
4850
import org.hyperledger.besu.ethereum.mainnet.block.access.list.AccessLocationTracker;
4951
import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessList.BlockAccessListBuilder;
5052
import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessListFactory;
@@ -184,10 +186,7 @@ public List<BlockSimulationResult> process(
184186

185187
// Fill gaps between blocks and set the correct block number and timestamp
186188
List<BlockStateCall> blockStateCalls =
187-
fillBlockStateCalls(
188-
protocolSchedule.getByBlockHeader(blockHeader),
189-
simulationParameter.getBlockStateCalls(),
190-
blockHeader);
189+
fillBlockStateCalls(simulationParameter.getBlockStateCalls(), blockHeader);
191190

192191
BlockHeader currentBlockHeader = blockHeader;
193192
HashMap<Long, Hash> blockHashCache = HashMap.newHashMap(countStateCalls);
@@ -324,6 +323,7 @@ private BlockSimulationResult processBlockStateCall(
324323
overridenBaseBlockHeader,
325324
blockStateCallSimulationResult,
326325
blockOverrides,
326+
protocolSpec,
327327
ws,
328328
maybeRequests,
329329
returnTrieLog);
@@ -492,14 +492,18 @@ private BlockSimulationResult createFinalBlock(
492492
final BlockHeader blockHeader,
493493
final BlockStateCallSimulationResult simResult,
494494
final BlockOverrides blockOverrides,
495+
final ProtocolSpec protocolSpec,
495496
final MutableWorldState ws,
496497
final Optional<List<Request>> maybeRequests,
497498
final boolean returnTrieLog) {
498499

499500
List<Transaction> transactions = simResult.getTransactions();
500501
List<TransactionReceipt> receipts = simResult.getReceipts();
501502

502-
BlockHeader finalBlockHeader =
503+
boolean isShanghaiPlus = protocolSpec.getWithdrawalsValidator() instanceof AllowedWithdrawals;
504+
boolean isCancunPlus = protocolSpec.getFeeMarket().implementsBlobFee();
505+
506+
BlockHeaderBuilder headerBuilder =
503507
BlockHeaderBuilder.createDefault()
504508
.populateFrom(blockHeader)
505509
.ommersHash(BodyValidation.ommersHash(List.of()))
@@ -508,16 +512,29 @@ private BlockSimulationResult createFinalBlock(
508512
.receiptsRoot(BodyValidation.receiptsRoot(receipts))
509513
.logsBloom(BodyValidation.logsBloom(receipts))
510514
.gasUsed(simResult.getCumulativeGasUsed())
511-
.blobGasUsed(simResult.getCumulativeBlobGasUsed())
512-
.withdrawalsRoot(BodyValidation.withdrawalsRoot(List.of()))
513515
.requestsHash(maybeRequests.map(BodyValidation::requestsHash).orElse(null))
514516
.balHash(simResult.getBlockAccessList().map(BodyValidation::balHash).orElse(null))
515517
.extraData(blockOverrides.getExtraData().orElse(Bytes.EMPTY))
516-
.blockHeaderFunctions(new BlockStateCallBlockHeaderFunctions(blockOverrides))
517-
.buildBlockHeader();
518+
.blockHeaderFunctions(new BlockStateCallBlockHeaderFunctions(blockOverrides));
519+
520+
if (isCancunPlus) {
521+
headerBuilder.blobGasUsed(simResult.getCumulativeBlobGasUsed());
522+
} else {
523+
// Clear defaults set by createDefault() for pre-Cancun blocks
524+
headerBuilder.blobGasUsed(null);
525+
headerBuilder.excessBlobGas(null);
526+
}
527+
528+
if (isShanghaiPlus) {
529+
headerBuilder.withdrawalsRoot(BodyValidation.withdrawalsRoot(List.of()));
530+
}
531+
532+
BlockHeader finalBlockHeader = headerBuilder.buildBlockHeader();
533+
534+
Optional<List<Withdrawal>> withdrawals =
535+
isShanghaiPlus ? Optional.of(List.of()) : Optional.empty();
518536

519-
Block block =
520-
new Block(finalBlockHeader, new BlockBody(transactions, List.of(), Optional.of(List.of())));
537+
Block block = new Block(finalBlockHeader, new BlockBody(transactions, List.of(), withdrawals));
521538

522539
if (returnTrieLog && ws instanceof PathBasedWorldState) {
523540
// if requested and path-based worldstate, return result with trielog and serializer:
@@ -594,19 +611,36 @@ protected BlockHeader overrideBlockHeader(
594611
blockOverrides
595612
.getGasLimit()
596613
.orElseGet(() -> getNextGasLimit(newProtocolSpec, header, blockNumber)))
597-
.baseFee(
598-
blockOverrides
599-
.getBaseFeePerGas()
600-
.orElseGet(
601-
() ->
602-
shouldValidate
603-
? getNextBaseFee(newProtocolSpec, header, blockNumber)
604-
: Wei.ZERO))
605614
.extraData(blockOverrides.getExtraData().orElse(Bytes.EMPTY))
606-
.parentBeaconBlockRoot(blockOverrides.getParentBeaconBlockRoot().orElse(Bytes32.ZERO))
607-
.prevRandao(blockOverrides.getMixHashOrPrevRandao().orElse(Bytes32.ZERO))
608-
.excessBlobGas(
609-
ExcessBlobGasCalculator.calculateExcessBlobGasForParent(newProtocolSpec, header));
615+
.prevRandao(blockOverrides.getMixHashOrPrevRandao().orElse(Bytes32.ZERO));
616+
617+
// London+: baseFee
618+
if (newProtocolSpec.getFeeMarket().implementsBaseFee()) {
619+
builder.baseFee(
620+
blockOverrides
621+
.getBaseFeePerGas()
622+
.orElseGet(
623+
() ->
624+
shouldValidate
625+
? getNextBaseFee(newProtocolSpec, header, blockNumber)
626+
: Wei.ZERO));
627+
}
628+
629+
// Merge+: parentBeaconBlockRoot
630+
if (newProtocolSpec.isPoS()) {
631+
builder.parentBeaconBlockRoot(blockOverrides.getParentBeaconBlockRoot().orElse(Bytes32.ZERO));
632+
} else {
633+
builder.parentBeaconBlockRoot(null);
634+
}
635+
636+
// Cancun+: excessBlobGas
637+
if (newProtocolSpec.getFeeMarket().implementsBlobFee()) {
638+
builder.excessBlobGas(
639+
ExcessBlobGasCalculator.calculateExcessBlobGasForParent(newProtocolSpec, header));
640+
} else {
641+
builder.excessBlobGas(null);
642+
builder.blobGasUsed(null);
643+
}
610644

611645
return builder
612646
.blockHeaderFunctions(new BlockStateCallBlockHeaderFunctions(blockOverrides))

ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockStateCalls.java

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
package org.hyperledger.besu.ethereum.transaction;
1616

1717
import org.hyperledger.besu.ethereum.core.BlockHeader;
18-
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
1918
import org.hyperledger.besu.ethereum.transaction.exceptions.BlockStateCallError;
2019
import org.hyperledger.besu.ethereum.transaction.exceptions.BlockStateCallException;
2120
import org.hyperledger.besu.plugin.data.BlockOverrides;
@@ -31,6 +30,9 @@
3130
public class BlockStateCalls {
3231
private static final long MAX_BLOCK_CALL_SIZE = 256;
3332

33+
/** Standard Ethereum slot duration used for eth_simulateV1 timestamp auto-increment. */
34+
private static final long SLOT_DURATION_SECONDS = 12;
35+
3436
/**
3537
* Normalizes a list of BlockStateCalls by filling gaps and setting the correct block number and
3638
* timestamp.
@@ -40,9 +42,7 @@ public class BlockStateCalls {
4042
* @return a normalized list of BlockStateCalls
4143
*/
4244
public static List<BlockStateCall> fillBlockStateCalls(
43-
final ProtocolSpec protocolSpec,
44-
final List<? extends BlockStateCall> blockStateCalls,
45-
final BlockHeader header) {
45+
final List<? extends BlockStateCall> blockStateCalls, final BlockHeader header) {
4646
long lastPresentBlockNumber = findLastBlockNumber(blockStateCalls);
4747
if (lastPresentBlockNumber > header.getNumber() + MAX_BLOCK_CALL_SIZE) {
4848
String errorMessage =
@@ -62,8 +62,7 @@ public static List<BlockStateCall> fillBlockStateCalls(
6262
blockStateCall.getBlockOverrides().getBlockNumber().orElse(currentBlock + 1);
6363
List<BlockStateCall> intermediateBlocks =
6464
new ArrayList<>(
65-
generateIntermediateBlocks(
66-
protocolSpec, nextBlockNumber, currentBlock, currentTimestamp));
65+
generateIntermediateBlocks(nextBlockNumber, currentBlock, currentTimestamp));
6766
// Add intermediate blocks
6867
for (BlockStateCall intermediateBlock : intermediateBlocks) {
6968
add(filledCalls, intermediateBlock, currentBlock, currentTimestamp);
@@ -75,9 +74,7 @@ public static List<BlockStateCall> fillBlockStateCalls(
7574
blockStateCall.getBlockOverrides().setBlockNumber(currentBlock + 1);
7675
}
7776
if (blockStateCall.getBlockOverrides().getTimestamp().isEmpty()) {
78-
blockStateCall
79-
.getBlockOverrides()
80-
.setTimestamp(currentTimestamp + protocolSpec.getSlotDuration().toSeconds());
77+
blockStateCall.getBlockOverrides().setTimestamp(currentTimestamp + SLOT_DURATION_SECONDS);
8178
}
8279
// Add the current block
8380
add(filledCalls, blockStateCall, currentBlock, currentTimestamp);
@@ -112,15 +109,12 @@ private static void add(
112109
}
113110

114111
private static List<BlockStateCall> generateIntermediateBlocks(
115-
final ProtocolSpec protocolSpec,
116-
final long targetBlockNumber,
117-
final long startBlockNumber,
118-
final long startTimestamp) {
112+
final long targetBlockNumber, final long startBlockNumber, final long startTimestamp) {
119113
List<BlockStateCall> intermediateBlocks = new ArrayList<>();
120114
long blockNumberDiff = targetBlockNumber - startBlockNumber;
121115
for (long i = 1; i < blockNumberDiff; i++) {
122116
long nextBlockNumber = startBlockNumber + i;
123-
long nextTimestamp = startTimestamp + protocolSpec.getSlotDuration().toSeconds() * i;
117+
long nextTimestamp = startTimestamp + SLOT_DURATION_SECONDS * i;
124118
var nextBlockOverrides =
125119
BlockOverrides.builder().blockNumber(nextBlockNumber).timestamp(nextTimestamp).build();
126120
intermediateBlocks.add(new BlockStateCall(nextBlockOverrides));

ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/BlockSimulatorTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,10 @@ public void shouldApplyStateOverridesCorrectly() {
235235

236236
@Test
237237
public void shouldOverrideBlockHeaderCorrectly() {
238+
FeeMarket feeMarket = mock(FeeMarket.class);
239+
when(feeMarket.implementsBaseFee()).thenReturn(true);
240+
when(protocolSpec.getFeeMarket()).thenReturn(feeMarket);
241+
when(protocolSpec.isPoS()).thenReturn(true);
238242

239243
var expectedTimestamp = 1L;
240244
var expectedBlockNumber = 2L;

ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/BlockStateCallsTest.java

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,10 @@
2121
import static org.mockito.Mockito.when;
2222

2323
import org.hyperledger.besu.ethereum.core.BlockHeader;
24-
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
2524
import org.hyperledger.besu.ethereum.transaction.exceptions.BlockStateCallError;
2625
import org.hyperledger.besu.ethereum.transaction.exceptions.BlockStateCallException;
2726
import org.hyperledger.besu.plugin.data.BlockOverrides;
2827

29-
import java.time.Duration;
3028
import java.util.ArrayList;
3129
import java.util.Collections;
3230
import java.util.List;
@@ -35,16 +33,12 @@
3533
import org.junit.jupiter.api.Test;
3634

3735
class BlockStateCallsTest {
38-
private static final Duration SLOT_DURATION = Duration.ofSeconds(12);
3936
private static final long MAX_BLOCK_CALL_SIZE = 256;
40-
private ProtocolSpec mockProtocolSpec;
4137
private BlockHeader mockBlockHeader;
4238
private final long headerTimestamp = 1000L;
4339

4440
@BeforeEach
4541
void setUp() {
46-
mockProtocolSpec = mock(ProtocolSpec.class);
47-
when(mockProtocolSpec.getSlotDuration()).thenReturn(SLOT_DURATION);
4842
mockBlockHeader = mock(BlockHeader.class);
4943
when(mockBlockHeader.getTimestamp()).thenReturn(headerTimestamp);
5044
when(mockBlockHeader.getNumber()).thenReturn(1L);
@@ -60,8 +54,7 @@ void shouldFillGapsBetweenBlockNumbers() {
6054
BlockStateCall blockStateCall = createBlockStateCall(4L, 1036L);
6155

6256
List<BlockStateCall> blockStateCalls =
63-
BlockStateCalls.fillBlockStateCalls(
64-
mockProtocolSpec, List.of(blockStateCall), mockBlockHeader);
57+
BlockStateCalls.fillBlockStateCalls(List.of(blockStateCall), mockBlockHeader);
6558
assertEquals(3, blockStateCalls.size());
6659
assertEquals(2L, blockStateCalls.get(0).getBlockOverrides().getBlockNumber().orElseThrow());
6760
assertEquals(1012L, blockStateCalls.get(0).getBlockOverrides().getTimestamp().orElseThrow());
@@ -83,8 +76,7 @@ void shouldUpdateBlockNumberIfNotPresent() {
8376
long expectedBlockNumber = 2L;
8477
BlockStateCall blockStateCall = createBlockStateCall(null, null);
8578
List<BlockStateCall> blockStateCalls =
86-
BlockStateCalls.fillBlockStateCalls(
87-
mockProtocolSpec, List.of(blockStateCall), mockBlockHeader);
79+
BlockStateCalls.fillBlockStateCalls(List.of(blockStateCall), mockBlockHeader);
8880

8981
assertEquals(1, blockStateCalls.size());
9082
assertEquals(
@@ -103,8 +95,7 @@ void shouldUpdateTimestampIfNotPresent() {
10395
long expectedTimestamp = headerTimestamp + (blockNumber - 1L) * 12;
10496
BlockStateCall blockStateCall = createBlockStateCall(blockNumber, null);
10597
List<BlockStateCall> blockStateCalls =
106-
BlockStateCalls.fillBlockStateCalls(
107-
mockProtocolSpec, List.of(blockStateCall), mockBlockHeader);
98+
BlockStateCalls.fillBlockStateCalls(List.of(blockStateCall), mockBlockHeader);
10899
assertEquals(
109100
expectedTimestamp,
110101
blockStateCalls.getLast().getBlockOverrides().getTimestamp().orElseThrow());
@@ -124,8 +115,7 @@ void shouldFillBlockStateCalls() {
124115
blockStateCalls.add(createBlockStateCall(3L, 1024L));
125116
blockStateCalls.add(createBlockStateCall(5L, 1048L));
126117

127-
var normalizedCalls =
128-
BlockStateCalls.fillBlockStateCalls(mockProtocolSpec, blockStateCalls, mockBlockHeader);
118+
var normalizedCalls = BlockStateCalls.fillBlockStateCalls(blockStateCalls, mockBlockHeader);
129119

130120
assertEquals(4, normalizedCalls.size());
131121
assertEquals(2L, normalizedCalls.get(0).getBlockOverrides().getBlockNumber().orElseThrow());
@@ -152,7 +142,7 @@ void shouldThrowExceptionForInvalidBlockNumber() {
152142
BlockStateCallException.class,
153143
() ->
154144
BlockStateCalls.fillBlockStateCalls(
155-
mockProtocolSpec, List.of(createBlockStateCall(1L, 1012L)), mockBlockHeader));
145+
List.of(createBlockStateCall(1L, 1012L)), mockBlockHeader));
156146
assertThat(exception.getError()).isEqualTo(BlockStateCallError.BLOCK_NUMBERS_NOT_ASCENDING);
157147
String expectedMessage =
158148
String.format(
@@ -175,9 +165,7 @@ void shouldThrowExceptionForInvalidTimestamp() {
175165
BlockStateCallException.class,
176166
() ->
177167
BlockStateCalls.fillBlockStateCalls(
178-
mockProtocolSpec,
179-
List.of(createBlockStateCall(2L, headerTimestamp)),
180-
mockBlockHeader));
168+
List.of(createBlockStateCall(2L, headerTimestamp)), mockBlockHeader));
181169
assertThat(exception.getError()).isEqualTo(BlockStateCallError.TIMESTAMPS_NOT_ASCENDING);
182170
String expectedMessage =
183171
String.format(
@@ -200,7 +188,7 @@ void shouldNormalizeChainAndFailOnInvalidTimestamp() {
200188
BlockStateCallException.class,
201189
() ->
202190
BlockStateCalls.fillBlockStateCalls(
203-
mockProtocolSpec, List.of(createBlockStateCall(3L, 1012L)), mockBlockHeader));
191+
List.of(createBlockStateCall(3L, 1012L)), mockBlockHeader));
204192
assertThat(exception.getError()).isEqualTo(BlockStateCallError.TIMESTAMPS_NOT_ASCENDING);
205193
assertEquals(
206194
"Timestamp is invalid. Trying to add a call at timestamp 1012, while current timestamp is 1012.",
@@ -216,9 +204,7 @@ void shouldThrowExceptionWhenExceedingMaxBlocks() {
216204
BlockStateCallException exception =
217205
assertThrows(
218206
BlockStateCallException.class,
219-
() ->
220-
BlockStateCalls.fillBlockStateCalls(
221-
mockProtocolSpec, List.of(blockStateCall), mockBlockHeader));
207+
() -> BlockStateCalls.fillBlockStateCalls(List.of(blockStateCall), mockBlockHeader));
222208
assertThat(exception.getError()).isEqualTo(BlockStateCallError.TOO_MANY_BLOCK_CALLS);
223209
String expectedMessage =
224210
String.format(
@@ -236,9 +222,7 @@ void shouldThrowExceptionWhenFillBlockStateCallsExceedsMaxBlockCallSize() {
236222
BlockStateCallException exception =
237223
assertThrows(
238224
BlockStateCallException.class,
239-
() ->
240-
BlockStateCalls.fillBlockStateCalls(
241-
mockProtocolSpec, blockStateCalls, mockBlockHeader));
225+
() -> BlockStateCalls.fillBlockStateCalls(blockStateCalls, mockBlockHeader));
242226
assertThat(exception.getError()).isEqualTo(BlockStateCallError.TOO_MANY_BLOCK_CALLS);
243227
assertEquals(
244228
"Block number 258 exceeds the limit of 257 (header: 1 + MAX_BLOCK_CALL_SIZE: 256)",

0 commit comments

Comments
 (0)