Skip to content

Commit 5259132

Browse files
daniellehrnersiladumacfarlaclaude
authored andcommitted
Amsterdam: EIP-8037: State Creation Gas Cost Increase (besu-eth#9815)
* EIP-8037: multidimensional gas metering - EVM core Implement state gas tracking in the EVM layer: - StateGasCostCalculator and Eip8037StateGasCostCalculator for cost-per-state-byte - AmsterdamGasCalculator with split regular/state gas costs - MessageFrame state gas reservoir, spill, and collision tracking - State gas charging in SSTORE, CREATE, CALL, and SELFDESTRUCT operations Signed-off-by: daniellehrner <daniel.lehrner@consensys.net> * EIP-8037: protocol-level 2D gas accounting integration Wire state gas into transaction processing, block building, and validation: - MainnetTransactionProcessor: intrinsic state gas, reservoir init, spill handling - BlockGasAccountingStrategy.AMSTERDAM: 2D gas metering (max of regular, state) - BlockGasUsedValidator.AMSTERDAM: pre-refund 2D validation - OsakaTargetingGasLimitCalculator: Amsterdam constructor with uncapped tx gas limit - Block creation: 2D headroom checks for transaction selection - TransactionProcessingResult: carry state gas used Signed-off-by: daniellehrner <daniel.lehrner@consensys.net> * EIP-8037: update execution spec tests for bal-devnet-3 Update reference test fixtures to bal@v5.2.0 for EIP-8037 compatibility. Signed-off-by: daniellehrner <daniel.lehrner@consensys.net> * EIP-8037: extract TransactionGasAccounting and add test coverage Extract gas accounting logic from MainnetTransactionProcessor into a testable TransactionGasAccounting class with builder pattern. Add tests for SSTORE state gas, block gas accounting strategy, state gas spill, and regular gas limit enforcement. Signed-off-by: daniellehrner <daniel.lehrner@consensys.net> * trigger DCO re-check Signed-off-by: daniellehrner <daniel.lehrner@consensys.net> * Add -Pcases to jmh (besu-eth#9982) e.g. ./gradlew --no-daemon :ethereum:core:jmh -Pincludes=Mod -Pexcludes=Mul,Add,SMod -Pcases=MOD_256_128,MOD_256_192 Signed-off-by: Simon Dudley <simon.dudley@consensys.net> * Preserve caller-provided gas pricing in eth_simulateV1 results (besu-eth#9972) * preserve caller-provided gas prices in TransactionSimulator When isAllowExceedingBalance is true but the caller explicitly provided non-zero gas pricing parameters, preserve them and the block header's baseFee so effective gas price is computed correctly during execution. This ensures gas fees are actually charged so that stateRoot and block hash are correct in eth_simulateV1 results. When gas params are absent or zero (typical eth_call, or explicitly zero maxFeePerGas), behavior is unchanged - all fields stay zero and baseFee is zeroed to avoid validation failures. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Sally MacFarlane <macfarla.github@gmail.com> --------- Signed-off-by: Sally MacFarlane <macfarla.github@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * trigger DCO re-check Signed-off-by: daniellehrner <daniel.lehrner@consensys.net> * spotless Signed-off-by: daniellehrner <daniel.lehrner@consensys.net> * update to BAL v5.3.0 spec tests Signed-off-by: daniellehrner <daniel.lehrner@consensys.net> * remove referenceTestDevnet test compilation from the referenceTest gradle task Signed-off-by: daniellehrner <daniel.lehrner@consensys.net> * addressed comments Signed-off-by: daniellehrner <daniel.lehrner@consensys.net> * renamed BlockGasAccountingStrategy.calculateBlockGas to calculateTransactionRegularGas Signed-off-by: daniellehrner <daniel.lehrner@consensys.net> * 1. use BlockGasAccountingStrategy.hasBlockCapacity() in AbstractBlockProcessor.java, AbstractBlockProcessorTest.java 2. move handleStateGasSpill into its own method 3. Added CREATE state gas underflow guard 4. Improved failCodeDepositWithoutRollback documentation Signed-off-by: daniellehrner <daniel.lehrner@consensys.net> --------- Signed-off-by: daniellehrner <daniel.lehrner@consensys.net> Signed-off-by: Simon Dudley <simon.dudley@consensys.net> Signed-off-by: Sally MacFarlane <macfarla.github@gmail.com> Co-authored-by: Simon Dudley <simon.dudley@consensys.net> Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Ameziane H. <ameziane.hamlat@consensys.net>
1 parent f1dd5ff commit 5259132

File tree

40 files changed

+2926
-230
lines changed

40 files changed

+2926
-230
lines changed

acceptance-tests/tests/src/acceptanceTest/java/org/hyperledger/besu/tests/acceptance/ethereum/EIP7708TransferLogAcceptanceTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ public void shouldEmitMultipleTransferLogsForContractCallWithValue() throws IOEx
218218
.nonce(0)
219219
.maxPriorityFeePerGas(Wei.of(1_000_000_000))
220220
.maxFeePerGas(Wei.fromHexString("0x02540BE400"))
221-
.gasLimit(100_000)
221+
.gasLimit(300_000)
222222
.to(forwarderContract)
223223
.value(transferAmount)
224224
.payload(callData)
@@ -295,7 +295,7 @@ public void shouldEmitTransferLogForSelfDestructToDifferentAddress() throws IOEx
295295
.nonce(0)
296296
.maxPriorityFeePerGas(Wei.of(1_000_000_000))
297297
.maxFeePerGas(Wei.fromHexString("0x02540BE400"))
298-
.gasLimit(100_000)
298+
.gasLimit(300_000)
299299
.to(selfDestructContract)
300300
.value(Wei.ZERO)
301301
.payload(callData)

ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,10 @@ public BlockCreationResult createBlock(
326326
BodyValidation.transactionsRoot(transactionResults.getSelectedTransactions()))
327327
.receiptsRoot(BodyValidation.receiptsRoot(transactionResults.getReceipts()))
328328
.logsBloom(BodyValidation.logsBloom(transactionResults.getReceipts()))
329-
.gasUsed(transactionResults.getCumulativeGasUsed())
329+
.gasUsed(
330+
Math.max(
331+
transactionResults.getCumulativeRegularGasUsed(),
332+
transactionResults.getCumulativeStateGasUsed()))
330333
.extraData(extraDataCalculator.get(parentHeader))
331334
.withdrawalsRoot(
332335
withdrawalsCanBeProcessed

ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,7 @@ private TransactionSelectionResult handleTransactionSelected(
683683
blockSelectionContext
684684
.protocolSpec()
685685
.getBlockGasAccountingStrategy()
686-
.calculateBlockGas(transaction, processingResult);
686+
.calculateTransactionRegularGas(transaction, processingResult);
687687

688688
// Receipt gas: Standard post-refund calculation (gasLimit - gasRemaining)
689689
// This is used for receipt cumulativeGasUsed field
@@ -882,7 +882,7 @@ void runOnCommit() {
882882
.ifPresent(blockAccessListBuilder::apply));
883883

884884
transactionSelectionResults.updateSelected(
885-
transaction, receipt, blockGasUsed, receiptGasUsed);
885+
transaction, receipt, blockGasUsed, receiptGasUsed, processingResult.getStateGasUsed());
886886

887887
notifySelected(evaluationContext, processingResult);
888888
LOG.atTrace()

ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/TransactionSelectionResults.java

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,29 +48,33 @@ public class TransactionSelectionResults {
4848
new ConcurrentHashMap<>();
4949

5050
// EIP-7778: Track two separate cumulative gas values
51-
// cumulativeGasUsed: For block gas limit enforcement (uses protocol-specific strategy)
51+
// cumulativeRegularGasUsed: For block gas limit enforcement (uses protocol-specific strategy)
5252
// cumulativeReceiptGasUsed: For receipt cumulativeGasUsed field (always post-refund)
53-
private long cumulativeGasUsed = 0;
53+
private long cumulativeRegularGasUsed = 0;
5454
private long cumulativeReceiptGasUsed = 0;
55+
// EIP-8037: Track cumulative state gas used for multidimensional gas metering
56+
private long cumulativeStateGasUsed = 0;
5557

5658
void updateSelected(
5759
final Transaction transaction,
5860
final TransactionReceipt receipt,
5961
final long blockGasUsed,
60-
final long receiptGasUsed) {
62+
final long receiptGasUsed,
63+
final long stateGasUsed) {
6164
selectedTransactions.add(transaction);
6265
transactionsByType
6366
.computeIfAbsent(transaction.getType(), type -> new ArrayList<>())
6467
.add(transaction);
6568
receipts.add(receipt);
66-
cumulativeGasUsed += blockGasUsed;
69+
cumulativeRegularGasUsed += blockGasUsed;
6770
cumulativeReceiptGasUsed += receiptGasUsed;
71+
cumulativeStateGasUsed += stateGasUsed;
6872
LOG.atTrace()
6973
.setMessage(
7074
"New selected transaction {}, total transactions {}, cumulative block gas {}, cumulative receipt gas {}")
7175
.addArgument(transaction::toTraceLog)
7276
.addArgument(selectedTransactions::size)
73-
.addArgument(cumulativeGasUsed)
77+
.addArgument(cumulativeRegularGasUsed)
7478
.addArgument(cumulativeReceiptGasUsed)
7579
.log();
7680
}
@@ -92,14 +96,18 @@ public List<TransactionReceipt> getReceipts() {
9296
return receipts;
9397
}
9498

95-
public long getCumulativeGasUsed() {
96-
return cumulativeGasUsed;
99+
public long getCumulativeRegularGasUsed() {
100+
return cumulativeRegularGasUsed;
97101
}
98102

99103
public long getCumulativeReceiptGasUsed() {
100104
return cumulativeReceiptGasUsed;
101105
}
102106

107+
public long getCumulativeStateGasUsed() {
108+
return cumulativeStateGasUsed;
109+
}
110+
103111
public Map<Transaction, TransactionSelectionResult> getNotSelectedTransactions() {
104112
return Map.copyOf(notSelectedTransactions);
105113
}
@@ -137,8 +145,9 @@ public boolean equals(final Object o) {
137145
return false;
138146
}
139147
TransactionSelectionResults that = (TransactionSelectionResults) o;
140-
return cumulativeGasUsed == that.cumulativeGasUsed
148+
return cumulativeRegularGasUsed == that.cumulativeRegularGasUsed
141149
&& cumulativeReceiptGasUsed == that.cumulativeReceiptGasUsed
150+
&& cumulativeStateGasUsed == that.cumulativeStateGasUsed
142151
&& selectedTransactions.equals(that.selectedTransactions)
143152
&& notSelectedTransactions.equals(that.notSelectedTransactions)
144153
&& receipts.equals(that.receipts);
@@ -150,15 +159,18 @@ public int hashCode() {
150159
selectedTransactions,
151160
notSelectedTransactions,
152161
receipts,
153-
cumulativeGasUsed,
154-
cumulativeReceiptGasUsed);
162+
cumulativeRegularGasUsed,
163+
cumulativeReceiptGasUsed,
164+
cumulativeStateGasUsed);
155165
}
156166

157167
public String toTraceLog() {
158-
return "cumulativeGasUsed="
159-
+ cumulativeGasUsed
168+
return "cumulativeRegularGasUsed="
169+
+ cumulativeRegularGasUsed
160170
+ ", cumulativeReceiptGasUsed="
161171
+ cumulativeReceiptGasUsed
172+
+ ", cumulativeStateGasUsed="
173+
+ cumulativeStateGasUsed
162174
+ ", selectedTransactions="
163175
+ selectedTransactions.stream()
164176
.map(Transaction::getHash)

ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlockSizeTransactionSelector.java

Lines changed: 61 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext;
1818
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionEvaluationContext;
1919
import org.hyperledger.besu.ethereum.core.Transaction;
20+
import org.hyperledger.besu.ethereum.mainnet.BlockGasAccountingStrategy;
2021
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
2122
import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
2223
import org.hyperledger.besu.plugin.services.txselection.SelectorsStateManager;
@@ -28,16 +29,22 @@
2829
* This class extends AbstractTransactionSelector and provides a specific implementation for
2930
* evaluating transactions based on block size. It checks if a transaction is too large for the
3031
* block and determines the selection result accordingly.
32+
*
33+
* <p>For EIP-8037 multidimensional gas, this selector tracks both regular and state gas dimensions.
34+
* A transaction fits if its gasLimit does not exceed the sum of remaining capacity in both
35+
* dimensions. Post-processing verifies the actual gas split.
3136
*/
32-
public class BlockSizeTransactionSelector extends AbstractStatefulTransactionSelector<Long> {
37+
public class BlockSizeTransactionSelector extends AbstractStatefulTransactionSelector<GasState> {
3338
private static final Logger LOG = LoggerFactory.getLogger(BlockSizeTransactionSelector.class);
3439

3540
private final long blockGasLimit;
41+
private final BlockGasAccountingStrategy gasAccountingStrategy;
3642

3743
public BlockSizeTransactionSelector(
3844
final BlockSelectionContext context, final SelectorsStateManager selectorsStateManager) {
39-
super(context, selectorsStateManager, 0L, SelectorsStateManager.StateDuplicator::duplicateLong);
45+
super(context, selectorsStateManager, GasState.ZERO, GasState::duplicate);
4046
this.blockGasLimit = context.pendingBlockHeader().getGasLimit();
47+
this.gasAccountingStrategy = context.protocolSpec().getBlockGasAccountingStrategy();
4148
}
4249

4350
/**
@@ -51,17 +58,17 @@ public BlockSizeTransactionSelector(
5158
public TransactionSelectionResult evaluateTransactionPreProcessing(
5259
final TransactionEvaluationContext evaluationContext) {
5360

54-
final long cumulativeGasUsed = getWorkingState();
61+
final GasState state = getWorkingState();
5562

56-
if (transactionTooLargeForBlock(evaluationContext.getTransaction(), cumulativeGasUsed)) {
63+
if (transactionTooLargeForBlock(evaluationContext.getTransaction(), state)) {
5764
LOG.atTrace()
5865
.setMessage("Transaction {} too large to select for block creation")
5966
.addArgument(evaluationContext.getPendingTransaction()::toTraceLog)
6067
.log();
61-
if (blockOccupancyAboveThreshold(cumulativeGasUsed)) {
68+
if (blockOccupancyAboveThreshold(state)) {
6269
LOG.trace("Block occupancy above threshold, completing operation");
6370
return TransactionSelectionResult.BLOCK_OCCUPANCY_ABOVE_THRESHOLD;
64-
} else if (blockFull(cumulativeGasUsed)) {
71+
} else if (blockFull(state)) {
6572
LOG.trace("Block full, completing operation");
6673
return TransactionSelectionResult.BLOCK_FULL;
6774
} else {
@@ -75,46 +82,67 @@ public TransactionSelectionResult evaluateTransactionPreProcessing(
7582
public TransactionSelectionResult evaluateTransactionPostProcessing(
7683
final TransactionEvaluationContext evaluationContext,
7784
final TransactionProcessingResult processingResult) {
78-
// EIP-7778: Use the protocol-specific gas accounting strategy
79-
// Pre-Amsterdam: gasLimit - gasRemaining (post-refund)
80-
// Amsterdam+: estimateGasUsedByTransaction (pre-refund, prevents block gas limit circumvention)
81-
final long gasUsedByTransaction =
82-
context
83-
.protocolSpec()
84-
.getBlockGasAccountingStrategy()
85-
.calculateBlockGas(evaluationContext.getTransaction(), processingResult);
86-
setWorkingState(getWorkingState() + gasUsedByTransaction);
87-
85+
final long txRegularGasUsed =
86+
gasAccountingStrategy.calculateTransactionRegularGas(
87+
evaluationContext.getTransaction(), processingResult);
88+
final long stateGasUsed = processingResult.getStateGasUsed();
89+
90+
final GasState state = getWorkingState();
91+
final GasState newState =
92+
new GasState(state.regularGas() + txRegularGasUsed, state.stateGas() + stateGasUsed);
93+
setWorkingState(newState);
94+
95+
final long gasMetered =
96+
gasAccountingStrategy.effectiveGasUsed(newState.regularGas(), newState.stateGas());
97+
if (gasMetered > blockGasLimit) {
98+
LOG.atTrace()
99+
.setMessage(
100+
"Transaction {} exceeds block gas limit post-processing:"
101+
+ " regularGas={}, stateGas={}, gasMetered={}, blockGasLimit={}")
102+
.addArgument(evaluationContext.getPendingTransaction()::toTraceLog)
103+
.addArgument(newState.regularGas())
104+
.addArgument(newState.stateGas())
105+
.addArgument(gasMetered)
106+
.addArgument(blockGasLimit)
107+
.log();
108+
return TransactionSelectionResult.TX_TOO_LARGE_FOR_REMAINING_GAS;
109+
}
88110
return TransactionSelectionResult.SELECTED;
89111
}
90112

91113
/**
92-
* Checks if the transaction is too large for the block.
114+
* Checks if the transaction is too large for the block using the gas accounting strategy. For 1D
115+
* gas, this checks regular gas only. For 2D gas (EIP-8037), this considers the sum of remaining
116+
* capacity in both dimensions.
93117
*
94-
* @param transaction The transaction to be checked. block.
95-
* @param cumulativeGasUsed The cumulative gas used by previous txs.
118+
* <p>This is a permissive heuristic: it may allow a transaction through pre-processing that later
119+
* exceeds the limit in post-processing, where the actual regular/state split is known.
120+
*
121+
* @param transaction The transaction to be checked.
122+
* @param state The current gas state with regular and state gas.
96123
* @return True if the transaction is too large for the block, false otherwise.
97124
*/
98-
private boolean transactionTooLargeForBlock(
99-
final Transaction transaction, final long cumulativeGasUsed) {
100-
101-
return transaction.getGasLimit() > blockGasLimit - cumulativeGasUsed;
125+
private boolean transactionTooLargeForBlock(final Transaction transaction, final GasState state) {
126+
return !gasAccountingStrategy.hasBlockCapacity(
127+
transaction.getGasLimit(), state.regularGas(), state.stateGas(), blockGasLimit);
102128
}
103129

104130
/**
105131
* Checks if the block occupancy is above the threshold.
106132
*
107-
* @param cumulativeGasUsed The cumulative gas used by previous txs.
133+
* @param state The current gas state.
108134
* @return True if the block occupancy is above the threshold, false otherwise.
109135
*/
110-
private boolean blockOccupancyAboveThreshold(final long cumulativeGasUsed) {
111-
final long gasRemaining = blockGasLimit - cumulativeGasUsed;
112-
final double occupancyRatio = (double) cumulativeGasUsed / (double) blockGasLimit;
136+
private boolean blockOccupancyAboveThreshold(final GasState state) {
137+
final long gasUsed =
138+
gasAccountingStrategy.effectiveGasUsed(state.regularGas(), state.stateGas());
139+
final long gasRemaining = blockGasLimit - gasUsed;
140+
final double occupancyRatio = (double) gasUsed / (double) blockGasLimit;
113141

114142
LOG.trace(
115143
"Min block occupancy ratio {}, gas used {}, available {}, remaining {}, used/available {}",
116144
context.miningConfiguration().getMinBlockOccupancyRatio(),
117-
cumulativeGasUsed,
145+
gasUsed,
118146
blockGasLimit,
119147
gasRemaining,
120148
occupancyRatio);
@@ -125,11 +153,13 @@ private boolean blockOccupancyAboveThreshold(final long cumulativeGasUsed) {
125153
/**
126154
* Checks if the block is full.
127155
*
128-
* @param cumulativeGasUsed The cumulative gas used by previous txs.
156+
* @param state The current gas state.
129157
* @return True if the block is full, false otherwise.
130158
*/
131-
private boolean blockFull(final long cumulativeGasUsed) {
132-
final long gasRemaining = blockGasLimit - cumulativeGasUsed;
159+
private boolean blockFull(final GasState state) {
160+
final long gasUsed =
161+
gasAccountingStrategy.effectiveGasUsed(state.regularGas(), state.stateGas());
162+
final long gasRemaining = blockGasLimit - gasUsed;
133163

134164
if (gasRemaining < context.gasCalculator().getMinimumTransactionCost()) {
135165
LOG.trace(
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright contributors to Hyperledger Besu.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*
13+
* SPDX-License-Identifier: Apache-2.0
14+
*/
15+
package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors;
16+
17+
/**
18+
* Tracks cumulative regular gas and state gas used during block building. Used by {@link
19+
* BlockSizeTransactionSelector} for multidimensional gas metering (EIP-8037).
20+
*
21+
* <p>For pre-EIP-8037, stateGas is always 0.
22+
*
23+
* @param regularGas cumulative regular (non-state) gas used
24+
* @param stateGas cumulative state gas used
25+
*/
26+
record GasState(long regularGas, long stateGas) {
27+
28+
static final GasState ZERO = new GasState(0, 0);
29+
30+
/**
31+
* Duplicator function for {@link
32+
* org.hyperledger.besu.plugin.services.txselection.SelectorsStateManager}. Since records are
33+
* immutable value types, returning the same instance is safe — no caller can mutate it.
34+
*
35+
* @param state the state to duplicate
36+
* @return the same instance
37+
*/
38+
static GasState duplicate(final GasState state) {
39+
return state;
40+
}
41+
}

0 commit comments

Comments
 (0)