diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/GasLimitCalculator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/GasLimitCalculator.java index 818a1720fbd..6aad53671b9 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/GasLimitCalculator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/GasLimitCalculator.java @@ -57,9 +57,11 @@ default long currentBlobGasLimit() { * * @param parentExcessBlobGas excess blob gas from the parent * @param blobGasUsed blob gas used + * @param parentBaseFeePerGas base fee per gas from the parent * @return the new excess blob gas value */ - default long computeExcessBlobGas(final long parentExcessBlobGas, final long blobGasUsed) { + default long computeExcessBlobGas( + final long parentExcessBlobGas, final long blobGasUsed, final long parentBaseFeePerGas) { return 0L; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CancunTargetingGasLimitCalculator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CancunTargetingGasLimitCalculator.java index 50662e0b42a..5d7dc60a1eb 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CancunTargetingGasLimitCalculator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CancunTargetingGasLimitCalculator.java @@ -30,6 +30,8 @@ public class CancunTargetingGasLimitCalculator extends LondonTargetingGasLimitCa private final long targetBlobGasPerBlock; private final long blobGasPerBlob; + protected final int maxBlobsPerBlock; + protected final int targetBlobsPerBlock; public CancunTargetingGasLimitCalculator( final long londonForkBlock, @@ -57,15 +59,20 @@ public CancunTargetingGasLimitCalculator( this.blobGasPerBlob = gasCalculator.getBlobGasPerBlob(); this.targetBlobGasPerBlock = blobGasPerBlob * targetBlobsPerBlock; this.maxBlobGasPerBlock = blobGasPerBlob * maxBlobsPerBlock; + this.maxBlobsPerBlock = maxBlobsPerBlock; + this.targetBlobsPerBlock = targetBlobsPerBlock; } @Override public long currentBlobGasLimit() { - return maxBlobGasPerBlock; + return getMaxBlobGasPerBlock(); } @Override - public long computeExcessBlobGas(final long parentExcessBlobGas, final long parentBlobGasUsed) { + public long computeExcessBlobGas( + final long parentExcessBlobGas, + final long parentBlobGasUsed, + final long parentBaseFeePerGas) { final long currentExcessBlobGas = parentExcessBlobGas + parentBlobGasUsed; if (currentExcessBlobGas < targetBlobGasPerBlock) { return 0L; @@ -85,4 +92,8 @@ public long getBlobGasPerBlob() { public long getTargetBlobGasPerBlock() { return targetBlobGasPerBlock; } + + public long getMaxBlobGasPerBlock() { + return maxBlobGasPerBlock; + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/LondonTargetingGasLimitCalculator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/LondonTargetingGasLimitCalculator.java index 3c86f770739..68a1132a18f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/LondonTargetingGasLimitCalculator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/LondonTargetingGasLimitCalculator.java @@ -23,7 +23,7 @@ public class LondonTargetingGasLimitCalculator extends FrontierTargetingGasLimit private static final Logger LOG = LoggerFactory.getLogger(LondonTargetingGasLimitCalculator.class); private final long londonForkBlock; - private final BaseFeeMarket feeMarket; + protected final BaseFeeMarket feeMarket; public LondonTargetingGasLimitCalculator( final long londonForkBlock, final BaseFeeMarket feeMarket) { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/OsakaTargetingGasLimitCalculator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/OsakaTargetingGasLimitCalculator.java index 15116021bac..bc0da097b0c 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/OsakaTargetingGasLimitCalculator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/OsakaTargetingGasLimitCalculator.java @@ -14,10 +14,15 @@ */ package org.hyperledger.besu.ethereum.mainnet; +import org.hyperledger.besu.datatypes.BlobGas; +import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket; import org.hyperledger.besu.evm.gascalculator.GasCalculator; public class OsakaTargetingGasLimitCalculator extends CancunTargetingGasLimitCalculator { + /** The blob base cost constant for Osaka */ + private static final long BLOB_BASE_COST = 1 << 14; // 2^14 + /** The mainnet transaction gas limit cap for Osaka */ private static final long DEFAULT_TRANSACTION_GAS_LIMIT_CAP_OSAKA = 30_000_000L; @@ -53,4 +58,28 @@ public OsakaTargetingGasLimitCalculator( public long transactionGasLimitCap() { return transactionGasLimitCap; } + + @Override + public long computeExcessBlobGas( + final long parentExcessBlobGas, + final long parentBlobGasUsed, + final long parentBaseFeePerGas) { + final long currentExcessBlobGas = parentExcessBlobGas + parentBlobGasUsed; + + // First check if we're below the target + if (currentExcessBlobGas < getTargetBlobGasPerBlock()) { + return 0L; + } + + // EIP-7918 https://eips.ethereum.org/EIPS/eip-7918 + Wei baseFeeBlobGas = feeMarket.blobGasPricePerGas(BlobGas.of(parentExcessBlobGas)); + long baseFeeBlobGasLong = baseFeeBlobGas.toLong(); + if (BLOB_BASE_COST * parentBaseFeePerGas > getBlobGasPerBlob() * baseFeeBlobGasLong) { + return parentExcessBlobGas + + parentBlobGasUsed * (maxBlobsPerBlock - targetBlobsPerBlock) / maxBlobsPerBlock; + } else { + // same as Cancun + return currentExcessBlobGas - getTargetBlobGasPerBlock(); + } + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ExcessBlobGasCalculator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ExcessBlobGasCalculator.java index 3bb0f2cb818..243eb312c59 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ExcessBlobGasCalculator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ExcessBlobGasCalculator.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.mainnet.feemarket; import org.hyperledger.besu.datatypes.BlobGas; +import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; @@ -35,7 +36,8 @@ public static BlobGas calculateExcessBlobGasForParent( .getGasLimitCalculator() .computeExcessBlobGas( parentHeader.getExcessBlobGas().map(BlobGas::toLong).orElse(0L), - parentHeader.getBlobGasUsed().orElse(0L)); + parentHeader.getBlobGasUsed().orElse(0L), + parentHeader.getBaseFee().orElse(Wei.ZERO).toLong()); return BlobGas.of(headerExcess); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/BlobGasValidationRule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/BlobGasValidationRule.java index 814df5879a4..b1d6c2506bc 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/BlobGasValidationRule.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/BlobGasValidationRule.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.mainnet.headervalidationrules; import org.hyperledger.besu.datatypes.BlobGas; +import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.GasLimitCalculator; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.mainnet.DetachedBlockHeaderValidationRule; @@ -48,7 +49,8 @@ public boolean validate(final BlockHeader header, final BlockHeader parent) { long parentBlobGasUsed = parent.getBlobGasUsed().orElse(0L); long calculatedExcessBlobGas = - gasLimitCalculator.computeExcessBlobGas(parentExcessBlobGas, parentBlobGasUsed); + gasLimitCalculator.computeExcessBlobGas( + parentExcessBlobGas, parentBlobGasUsed, parent.getBaseFee().orElse(Wei.ZERO).toLong()); if (headerExcessBlobGas != calculatedExcessBlobGas) { LOG.info( diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/CancunTargetingGasLimitCalculatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/CancunTargetingGasLimitCalculatorTest.java index 0da4b3a2aac..3feef1acb7a 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/CancunTargetingGasLimitCalculatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/CancunTargetingGasLimitCalculatorTest.java @@ -44,7 +44,8 @@ class CancunTargetingGasLimitCalculatorTest { public void shouldCalculateCancunExcessBlobGasCorrectly( final long parentExcess, final long used, final long expected) { final long usedBlobGas = new CancunGasCalculator().blobGasCost(used); - assertThat(cancunTargetingGasLimitCalculator.computeExcessBlobGas(parentExcess, usedBlobGas)) + assertThat( + cancunTargetingGasLimitCalculator.computeExcessBlobGas(parentExcess, usedBlobGas, 0L)) .isEqualTo(expected); } @@ -126,7 +127,7 @@ void shouldUseFutureForkCalculatorBlobGasPerBlob() { public void shouldCalculatePragueExcessBlobGasCorrectly( final long parentExcess, final long used, final long expected) { final long usedBlobGas = pragueGasCalculator.blobGasCost(used); - assertThat(pragueGasLimitCalculator.computeExcessBlobGas(parentExcess, usedBlobGas)) + assertThat(pragueGasLimitCalculator.computeExcessBlobGas(parentExcess, usedBlobGas, 0L)) .isEqualTo(expected); } @@ -172,8 +173,8 @@ void shouldCalculateCorrectlyPragueBlobGasPerBlob() { int newTargetCount = 9; public static final OsakaGasCalculator osakaGasCalculator = new OsakaGasCalculator(); // CancunTargetingGasLimitCalculator with Osaka numbers - private final CancunTargetingGasLimitCalculator osakaGasLimitCalculator = - new CancunTargetingGasLimitCalculator( + private final OsakaTargetingGasLimitCalculator osakaGasLimitCalculator = + new OsakaTargetingGasLimitCalculator( 0L, FeeMarket.cancunDefault(0L, Optional.empty()), osakaGasCalculator, @@ -187,7 +188,7 @@ void shouldCalculateCorrectlyPragueBlobGasPerBlob() { public void shouldCalculateOsakaExcessBlobGasCorrectly( final long parentExcess, final long used, final long expected) { final long usedBlobGas = osakaGasCalculator.blobGasCost(used); - assertThat(osakaGasLimitCalculator.computeExcessBlobGas(parentExcess, usedBlobGas)) + assertThat(osakaGasLimitCalculator.computeExcessBlobGas(parentExcess, usedBlobGas, 0L)) .isEqualTo(expected); } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/OsakaTargetingGasLimitCalculatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/OsakaTargetingGasLimitCalculatorTest.java new file mode 100644 index 00000000000..c258ce0336c --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/OsakaTargetingGasLimitCalculatorTest.java @@ -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.mainnet; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket; +import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; +import org.hyperledger.besu.evm.gascalculator.OsakaGasCalculator; + +import java.util.List; +import java.util.Optional; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class OsakaTargetingGasLimitCalculatorTest { + private static final long TARGET_BLOB_GAS_PER_BLOCK_OSAKA = 0x120000; + + private final OsakaGasCalculator osakaGasCalculator = new OsakaGasCalculator(); + private final BaseFeeMarket feeMarket = FeeMarket.cancunDefault(0L, Optional.empty()); + + @ParameterizedTest( + name = "{index} - parent gas {0}, used gas {1}, parent base fee per gas {2}, new excess {3}") + @MethodSource("osakaExcessBlobGasTestCases") + public void shouldCalculateOsakaExcessBlobGasCorrectly( + final long parentExcess, + final long used, + final long parentBaseFeePerGas, + final long expected) { + int maxBlobs = 10; + int targetBlobs = 9; + final OsakaTargetingGasLimitCalculator osakaTargetingGasLimitCalculator = + new OsakaTargetingGasLimitCalculator( + 0L, feeMarket, osakaGasCalculator, maxBlobs, targetBlobs); + + final long usedBlobGas = osakaGasCalculator.blobGasCost(used); + assertThat( + osakaTargetingGasLimitCalculator.computeExcessBlobGas( + parentExcess, usedBlobGas, parentBaseFeePerGas)) + .isEqualTo(expected); + } + + Iterable osakaExcessBlobGasTestCases() { + long targetGasPerBlock = TARGET_BLOB_GAS_PER_BLOCK_OSAKA; + return List.of( + // Case 1: Below target, should return 0 + Arguments.of(0L, 0L, 0L, 0L), + Arguments.of(targetGasPerBlock - 1, 0L, 0L, 0L), + Arguments.of(0L, 8, 0L, 0L), // 8 blobs is below target (9) + + // Case 2: Above target, BLOB_BASE_COST * baseFee <= GAS_PER_BLOB * blobFee + // This should use the formula: parentExcess + parentBlobGasUsed - target + Arguments.of(targetGasPerBlock * 2, 10, 17L, 2490368L), + + // Case 3: Above target, BLOB_BASE_COST * baseFee > GAS_PER_BLOB * blobFee + // This should use the formula: parentExcess + parentBlobGasUsed * (max - target) / max + Arguments.of(targetGasPerBlock, 1, 0L, osakaGasCalculator.getBlobGasPerBlob()), + Arguments.of(targetGasPerBlock, 10, 1L, 1310720L), + Arguments.of(targetGasPerBlock, 10, 16L, 1310720L)); + } + + @Test + void shouldUseCorrectBlobGasPerBlob() { + // should use OsakaGasCalculator's blob gas per blob to calculate the gas limit + final long blobGasPerBlob = osakaGasCalculator.getBlobGasPerBlob(); + assertThat(blobGasPerBlob).isEqualTo(131072); // same as Cancun + + int maxBlobs = 10; + int targetBlobs = 9; + var osakaTargetingGasLimitCalculator = + new OsakaTargetingGasLimitCalculator( + 0L, feeMarket, osakaGasCalculator, maxBlobs, targetBlobs); + + // if maxBlobs = 10, then the gas limit would be 131072 * 10 = 1310720 + assertThat(osakaTargetingGasLimitCalculator.currentBlobGasLimit()) + .isEqualTo(blobGasPerBlob * maxBlobs); + assertThat(osakaTargetingGasLimitCalculator.currentBlobGasLimit()).isEqualTo(1310720); + assertThat(osakaTargetingGasLimitCalculator.getTargetBlobGasPerBlock()) + .isEqualTo(blobGasPerBlob * targetBlobs); + } + + @Test + void testComputeExcessBlobGasWithDifferentConditions() { + // Create a test instance with specific parameters + int maxBlobs = 10; + int targetBlobs = 9; + var calculator = + new OsakaTargetingGasLimitCalculator( + 0L, feeMarket, osakaGasCalculator, maxBlobs, targetBlobs); + assertThat(calculator.maxBlobsPerBlock).isEqualTo(maxBlobs); + assertThat(calculator.targetBlobsPerBlock).isEqualTo(targetBlobs); + + long parentExcessBlobGas = calculator.getTargetBlobGasPerBlock(); + long parentBlobGasUsed = osakaGasCalculator.getBlobGasPerBlob() * 2; + + // Test the condition where BLOB_BASE_COST * baseFee > GAS_PER_BLOB * blobFee + // This should use the first formula + long result1 = calculator.computeExcessBlobGas(parentExcessBlobGas, parentBlobGasUsed, 0L); + // Expected value based on the test case + assertThat(result1).isEqualTo(262144L); + + // Test with very high excess blob gas to trigger the second formula + // When excess is high, blob fee will be high, potentially making BLOB_BASE_COST * baseFee <= + // GAS_PER_BLOB * blobFee + long highExcess = calculator.getTargetBlobGasPerBlock() * 10; + long result2 = calculator.computeExcessBlobGas(highExcess, parentBlobGasUsed, 0L); + // Expected value based on the test case + assertThat(result2).isEqualTo(10878976L); + } + + @Test + void dryRunDetector() { + Assertions.assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } +}