Skip to content

Commit 375f20c

Browse files
Only apply blob schedule bpoX if bpoXTime is available as well (#10166)
* only apply blob schedule bpoX if bpoXTime is available as well Signed-off-by: daniellehrner <daniel.lehrner@consensys.net> * addressed pr comments Signed-off-by: daniellehrner <daniel.lehrner@consensys.net> * rename forkTimestampGetter to blobScheduleTimestampGetter Signed-off-by: daniellehrner <daniel.lehrner@consensys.net> --------- Signed-off-by: daniellehrner <daniel.lehrner@consensys.net>
1 parent 300b201 commit 375f20c

File tree

6 files changed

+290
-55
lines changed

6 files changed

+290
-55
lines changed

config/src/main/java/org/hyperledger/besu/config/BlobSchedule.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ public class BlobSchedule {
2626
/** The constant PRAGUE_DEFAULT. */
2727
public static final BlobSchedule PRAGUE_DEFAULT = BlobSchedule.create(6, 9, 5007716);
2828

29+
/** The constant BPO2_DEFAULT. */
30+
public static final BlobSchedule BPO2_DEFAULT = BlobSchedule.create(14, 21, 11684671);
31+
2932
private final int target;
3033
private final int max;
3134
private final int baseFeeUpdateFraction;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright contributors to 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.mainnet;
16+
17+
import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket;
18+
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
19+
20+
import java.util.OptionalInt;
21+
22+
/** EIP-8037: Amsterdam disables the validation-time transaction gas limit cap. */
23+
public class AmsterdamTargetingGasLimitCalculator extends OsakaTargetingGasLimitCalculator {
24+
25+
public AmsterdamTargetingGasLimitCalculator(
26+
final long londonForkBlock,
27+
final BaseFeeMarket feeMarket,
28+
final GasCalculator gasCalculator,
29+
final int maxBlobsPerBlock,
30+
final int targetBlobsPerBlock,
31+
final OptionalInt maxBlobsPerTransaction,
32+
final OptionalInt userMaxBlobsPerBlock) {
33+
super(
34+
londonForkBlock,
35+
feeMarket,
36+
gasCalculator,
37+
maxBlobsPerBlock,
38+
targetBlobsPerBlock,
39+
maxBlobsPerTransaction,
40+
userMaxBlobsPerBlock,
41+
Long.MAX_VALUE);
42+
}
43+
}

ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@
118118
import java.util.NoSuchElementException;
119119
import java.util.Objects;
120120
import java.util.Optional;
121+
import java.util.OptionalLong;
121122
import java.util.Set;
122123
import java.util.function.Function;
123124
import java.util.stream.IntStream;
@@ -1065,7 +1066,12 @@ static ProtocolSpecBuilder bpo1Definition(
10651066
isParallelTxProcessingEnabled,
10661067
balConfiguration,
10671068
metricsSystem);
1068-
return applyBlobSchedule(builder, genesisConfigOptions, BlobScheduleOptions::getBpo1, BPO1);
1069+
return applyBlobSchedule(
1070+
builder,
1071+
genesisConfigOptions,
1072+
BlobScheduleOptions::getBpo1,
1073+
GenesisConfigOptions::getBpo1Time,
1074+
BPO1);
10691075
}
10701076

10711077
static ProtocolSpecBuilder bpo2Definition(
@@ -1087,7 +1093,12 @@ static ProtocolSpecBuilder bpo2Definition(
10871093
isParallelTxProcessingEnabled,
10881094
balConfiguration,
10891095
metricsSystem);
1090-
return applyBlobSchedule(builder, genesisConfigOptions, BlobScheduleOptions::getBpo2, BPO2);
1096+
return applyBlobSchedule(
1097+
builder,
1098+
genesisConfigOptions,
1099+
BlobScheduleOptions::getBpo2,
1100+
GenesisConfigOptions::getBpo2Time,
1101+
BPO2);
10911102
}
10921103

10931104
static ProtocolSpecBuilder bpo3Definition(
@@ -1109,7 +1120,12 @@ static ProtocolSpecBuilder bpo3Definition(
11091120
isParallelTxProcessingEnabled,
11101121
balConfiguration,
11111122
metricsSystem);
1112-
return applyBlobSchedule(builder, genesisConfigOptions, BlobScheduleOptions::getBpo3, BPO3);
1123+
return applyBlobSchedule(
1124+
builder,
1125+
genesisConfigOptions,
1126+
BlobScheduleOptions::getBpo3,
1127+
GenesisConfigOptions::getBpo3Time,
1128+
BPO3);
11131129
}
11141130

11151131
static ProtocolSpecBuilder bpo4Definition(
@@ -1131,7 +1147,12 @@ static ProtocolSpecBuilder bpo4Definition(
11311147
isParallelTxProcessingEnabled,
11321148
balConfiguration,
11331149
metricsSystem);
1134-
return applyBlobSchedule(builder, genesisConfigOptions, BlobScheduleOptions::getBpo4, BPO4);
1150+
return applyBlobSchedule(
1151+
builder,
1152+
genesisConfigOptions,
1153+
BlobScheduleOptions::getBpo4,
1154+
GenesisConfigOptions::getBpo4Time,
1155+
BPO4);
11351156
}
11361157

11371158
static ProtocolSpecBuilder bpo5Definition(
@@ -1153,7 +1174,12 @@ static ProtocolSpecBuilder bpo5Definition(
11531174
isParallelTxProcessingEnabled,
11541175
balConfiguration,
11551176
metricsSystem);
1156-
return applyBlobSchedule(builder, genesisConfigOptions, BlobScheduleOptions::getBpo5, BPO5);
1177+
return applyBlobSchedule(
1178+
builder,
1179+
genesisConfigOptions,
1180+
BlobScheduleOptions::getBpo5,
1181+
GenesisConfigOptions::getBpo5Time,
1182+
BPO5);
11571183
}
11581184

11591185
static ProtocolSpecBuilder amsterdamDefinition(
@@ -1230,15 +1256,14 @@ static ProtocolSpecBuilder amsterdamDefinition(
12301256
.gasLimitCalculatorBuilder(
12311257
(feeMarket, gasCalculator, blobSchedule) -> {
12321258
final long londonForkBlock = genesisConfigOptions.getLondonBlockNumber().orElse(0L);
1233-
return new OsakaTargetingGasLimitCalculator(
1259+
return new AmsterdamTargetingGasLimitCalculator(
12341260
londonForkBlock,
12351261
(BaseFeeMarket) feeMarket,
12361262
gasCalculator,
12371263
blobSchedule.getMax(),
12381264
blobSchedule.getTarget(),
12391265
miningConfiguration.getMaxBlobsPerTransaction(),
1240-
miningConfiguration.getMaxBlobsPerBlock(),
1241-
Long.MAX_VALUE);
1266+
miningConfiguration.getMaxBlobsPerBlock());
12421267
})
12431268
// EIP-8037: Amsterdam gas calculator with state gas cost support
12441269
.gasCalculator(AmsterdamGasCalculator::new)
@@ -1253,11 +1278,17 @@ private static ProtocolSpecBuilder applyBlobSchedule(
12531278
final ProtocolSpecBuilder builder,
12541279
final GenesisConfigOptions genesisConfigOptions,
12551280
final Function<BlobScheduleOptions, Optional<BlobSchedule>> blobGetter,
1281+
final Function<GenesisConfigOptions, OptionalLong> blobScheduleTimestampGetter,
12561282
final HardforkId hardforkId) {
1257-
genesisConfigOptions
1258-
.getBlobScheduleOptions()
1259-
.flatMap(blobGetter)
1260-
.ifPresent(builder::blobSchedule);
1283+
// Only apply a fork's blob schedule if the fork is actually activated (has a timestamp).
1284+
// This prevents inactive BPO forks from overriding the blob schedule with stale values
1285+
// from the genesis config.
1286+
if (blobScheduleTimestampGetter.apply(genesisConfigOptions).isPresent()) {
1287+
genesisConfigOptions
1288+
.getBlobScheduleOptions()
1289+
.flatMap(blobGetter)
1290+
.ifPresent(builder::blobSchedule);
1291+
}
12611292
return builder.hardforkId(hardforkId);
12621293
}
12631294

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Copyright contributors to 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.mainnet;
16+
17+
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
18+
19+
import org.hyperledger.besu.config.BlobSchedule;
20+
import org.hyperledger.besu.ethereum.GasLimitCalculator;
21+
import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket;
22+
import org.hyperledger.besu.evm.gascalculator.AmsterdamGasCalculator;
23+
24+
import java.util.List;
25+
import java.util.Optional;
26+
import java.util.OptionalInt;
27+
28+
import org.junit.jupiter.api.Test;
29+
import org.junit.jupiter.api.TestInstance;
30+
import org.junit.jupiter.params.ParameterizedTest;
31+
import org.junit.jupiter.params.provider.Arguments;
32+
import org.junit.jupiter.params.provider.MethodSource;
33+
34+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
35+
class AmsterdamTargetingGasLimitCalculatorTest {
36+
37+
// Amsterdam: target=14, max=21, baseFeeUpdateFraction=11684671 (EIP-7918)
38+
private static final long TARGET_BLOB_GAS_PER_BLOCK = 0x1C0000; // 14 * 131072
39+
private static final int TARGET_BLOBS = 14;
40+
private static final int MAX_BLOBS = 21;
41+
private static final AmsterdamGasCalculator gasCalculator = new AmsterdamGasCalculator();
42+
private final AmsterdamTargetingGasLimitCalculator gasLimitCalculator =
43+
new AmsterdamTargetingGasLimitCalculator(
44+
0L,
45+
FeeMarket.cancun(0L, Optional.empty(), BlobSchedule.BPO2_DEFAULT),
46+
gasCalculator,
47+
MAX_BLOBS,
48+
TARGET_BLOBS,
49+
OptionalInt.empty(),
50+
OptionalInt.empty());
51+
52+
/**
53+
* Test Amsterdam excessBlobGas calculation using the standard Cancun formula branch. With
54+
* base_fee_per_gas=7 and Amsterdam's baseFeeUpdateFraction=11684671, the blob gas price is 1 at
55+
* low excess values, so base_blob_tx_price (7*8192=57344) < target_blob_gas_price
56+
* (131072*1=131072) and the standard formula applies: excess = parentExcess + parentBlobGasUsed -
57+
* targetBlobGasPerBlock.
58+
*
59+
* <p>Test values derived from hive test: test_invalid_blob_gas_used_in_header
60+
* [fork_Amsterdam-new_blobs_1-header_blob_gas_used_1703936-blockchain_test_engine-parent_blobs_0]
61+
* where parent_excess_blobs=18 (=(21+14)//2+1), parent_blobs=0, block_base_fee_per_gas=7
62+
*/
63+
@ParameterizedTest(name = "{index} - parent excess {0}, used gas {1}, base fee {2}")
64+
@MethodSource("standardBranchBlobGases")
65+
public void shouldCalculateExcessBlobGasCorrectly_standardBranch(
66+
final long parentExcess,
67+
final long parentBlobGasUsed,
68+
final long baseFee,
69+
final long expected) {
70+
assertThat(gasLimitCalculator.computeExcessBlobGas(parentExcess, parentBlobGasUsed, baseFee))
71+
.isEqualTo(expected);
72+
}
73+
74+
Iterable<Arguments> standardBranchBlobGases() {
75+
return List.of(
76+
// zero + zero: below target → 0
77+
Arguments.of(0L, 0L, 7L, 0L),
78+
// at target + 0: at target → 0
79+
Arguments.of(TARGET_BLOB_GAS_PER_BLOCK, 0L, 7L, 0L),
80+
// 0 + target gas = target → 0
81+
Arguments.of(0L, TARGET_BLOB_GAS_PER_BLOCK, 7L, 0L),
82+
// just above target → 1
83+
Arguments.of(1L, TARGET_BLOB_GAS_PER_BLOCK, 7L, 1L),
84+
// hive default: parent_excess_blobs=18, parent_blobs=0
85+
// excess = 18*131072 - 14*131072 = 4*131072 = 524288
86+
Arguments.of(2359296L, 0L, 7L, 524288L),
87+
// 18 excess blobs + 1 blob
88+
// excess = 18*131072 + 131072 - 14*131072 = 5*131072 = 655360
89+
Arguments.of(2359296L, 131072L, 7L, 655360L),
90+
// 18 excess blobs + target blobs: excess stays same as parent
91+
// excess = 18*131072 + 14*131072 - 14*131072 = 18*131072 = 2359296
92+
Arguments.of(2359296L, TARGET_BLOB_GAS_PER_BLOCK, 7L, 2359296L));
93+
}
94+
95+
/**
96+
* Test Amsterdam excessBlobGas calculation using the EIP-7918 reserve price branch. When
97+
* base_fee_per_gas * BLOB_BASE_COST (8192) > blob_gas_price * GAS_PER_BLOB, the formula changes
98+
* to: excess = parentExcess + parentBlobGasUsed * (max - target) / max.
99+
*
100+
* <p>With base_fee=17: base_blob_tx_price = 17*8192 = 139264 > target_blob_gas_price = 131072*1 =
101+
* 131072 (at low excess where blob gas price = 1), so the EIP-7918 branch triggers. delta = 21 -
102+
* 14 = 7, so: excess = parentExcess + parentBlobGasUsed * 7 / 21
103+
*/
104+
@ParameterizedTest(name = "{index} - parent excess {0}, used gas {1}, base fee {2}")
105+
@MethodSource("eip7918BranchBlobGases")
106+
public void shouldCalculateExcessBlobGasCorrectly_eip7918Branch(
107+
final long parentExcess,
108+
final long parentBlobGasUsed,
109+
final long baseFee,
110+
final long expected) {
111+
assertThat(gasLimitCalculator.computeExcessBlobGas(parentExcess, parentBlobGasUsed, baseFee))
112+
.isEqualTo(expected);
113+
}
114+
115+
Iterable<Arguments> eip7918BranchBlobGases() {
116+
// parentExcess = TARGET_GAS + 1 = 1835009, just above target so not clamped to 0
117+
// With base_fee=17: 17*8192=139264 > 1*131072=131072 → EIP-7918 branch
118+
// excess = 1835009 + parentBlobGasUsed * 7 / 21
119+
return List.of(
120+
// 1 blob: 1835009 + 131072*7/21 = 1835009 + 43690 = 1878699
121+
Arguments.of(1835009L, 131072L, 17L, 1878699L),
122+
// 3 blobs: 1835009 + 393216*7/21 = 1835009 + 131072 = 1966081
123+
Arguments.of(1835009L, 393216L, 17L, 1966081L),
124+
// target (14) blobs: 1835009 + 1835008*7/21 = 1835009 + 611669 = 2446678
125+
Arguments.of(1835009L, 1835008L, 17L, 2446678L),
126+
// max (21) blobs: 1835009 + 2752512*7/21 = 1835009 + 917504 = 2752513
127+
Arguments.of(1835009L, 2752512L, 17L, 2752513L));
128+
}
129+
130+
@Test
131+
void defaultGasLimit() {
132+
GasLimitCalculator calculator =
133+
new AmsterdamTargetingGasLimitCalculator(
134+
0L,
135+
FeeMarket.cancun(0L, Optional.empty(), BlobSchedule.BPO2_DEFAULT),
136+
new AmsterdamGasCalculator(),
137+
BlobSchedule.BPO2_DEFAULT.getMax(),
138+
BlobSchedule.BPO2_DEFAULT.getTarget(),
139+
OptionalInt.empty(),
140+
OptionalInt.empty());
141+
// 21 * 131072 = 2752512 = 0x2A0000
142+
assertThat(calculator.currentBlobGasLimit()).isEqualTo(0x2A0000);
143+
// per-tx cap: DEFAULT_MAX_BLOBS_PER_TRANSACTION (6) * 131072 = 0xC0000
144+
assertThat(calculator.transactionBlobGasLimitCap()).isEqualTo(0xC0000);
145+
}
146+
}

ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/CancunTargetingGasLimitCalculatorTest.java

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,10 @@
2020
import org.hyperledger.besu.ethereum.GasLimitCalculator;
2121
import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket;
2222
import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator;
23-
import org.hyperledger.besu.evm.gascalculator.OsakaGasCalculator;
2423
import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator;
2524

2625
import java.util.List;
2726
import java.util.Optional;
28-
import java.util.OptionalInt;
2927

3028
import org.assertj.core.api.Assertions;
3129
import org.junit.jupiter.api.Test;
@@ -147,47 +145,6 @@ void shouldCalculateCorrectlyPragueBlobGasPerBlob() {
147145
assertThat(pragueTargetingGasLimitCalculator.currentBlobGasLimit()).isEqualTo(1310720);
148146
}
149147

150-
long nineBlobTargetGas = TARGET_BLOB_GAS_PER_BLOCK_OSAKA;
151-
int newTargetCount = 9;
152-
public static final OsakaGasCalculator osakaGasCalculator = new OsakaGasCalculator();
153-
// CancunTargetingGasLimitCalculator with Osaka numbers
154-
private final OsakaTargetingGasLimitCalculator osakaGasLimitCalculator =
155-
new OsakaTargetingGasLimitCalculator(
156-
0L,
157-
FeeMarket.cancunDefault(0L, Optional.empty()),
158-
osakaGasCalculator,
159-
BlobSchedule.PRAGUE_DEFAULT.getMax(),
160-
newTargetCount,
161-
OptionalInt.empty(),
162-
OptionalInt.empty());
163-
164-
private static final long TARGET_BLOB_GAS_PER_BLOCK_OSAKA = 0x120000;
165-
166-
@ParameterizedTest(name = "{index} - parent gas {0}, used gas {1}, blob target {2}")
167-
@MethodSource("osakaBlobGasses")
168-
public void shouldCalculateOsakaExcessBlobGasCorrectly(
169-
final long parentExcess, final long used, final long expected) {
170-
final long usedBlobGas = osakaGasCalculator.blobGasCost(used);
171-
assertThat(osakaGasLimitCalculator.computeExcessBlobGas(parentExcess, usedBlobGas, 0L))
172-
.isEqualTo(expected);
173-
}
174-
175-
Iterable<Arguments> osakaBlobGasses() {
176-
177-
return List.of(
178-
// New target count
179-
Arguments.of(0L, 0L, 0L),
180-
Arguments.of(nineBlobTargetGas, 0L, 0L),
181-
Arguments.of(newTargetCount, 0L, 0L),
182-
Arguments.of(0L, newTargetCount, 0L),
183-
Arguments.of(1L, newTargetCount, 1L),
184-
Arguments.of(
185-
osakaGasCalculator.blobGasCost(newTargetCount),
186-
1L,
187-
osakaGasLimitCalculator.getBlobGasPerBlob()),
188-
Arguments.of(nineBlobTargetGas, newTargetCount, nineBlobTargetGas));
189-
}
190-
191148
@Test
192149
void cancunDefaultGasLimit() {
193150
GasLimitCalculator gasLimitCalculator =

0 commit comments

Comments
 (0)