EIP-7742 - gas calculation using target_blobs_per_block (#7823)

Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
This commit is contained in:
Simon Dudley
2024-12-05 11:50:23 +10:00
committed by GitHub
parent 1b7b6e8025
commit 472357f118
11 changed files with 185 additions and 87 deletions

View File

@@ -16,10 +16,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -124,7 +121,6 @@ public class TransactionTracerTest {
when(blockchain.getChainHeadHeader()).thenReturn(blockHeader);
when(protocolSpec.getGasCalculator()).thenReturn(gasCalculator);
when(protocolContext.getBadBlockManager()).thenReturn(badBlockManager);
lenient().when(gasCalculator.computeExcessBlobGas(anyLong(), anyInt())).thenReturn(0L);
}
@Test

View File

@@ -17,7 +17,7 @@ package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
@@ -61,7 +61,7 @@ class BlobSizeTransactionSelectorTest {
Suppliers.memoize(SignatureAlgorithmFactory::getInstance);
private static final KeyPair KEYS = SIGNATURE_ALGORITHM.get().generateKeyPair();
private static final long BLOB_GAS_PER_BLOB = CancunGasCalculator.BLOB_GAS_PER_BLOB;
private static final long BLOB_GAS_PER_BLOB = new CancunGasCalculator().getBlobGasPerBlob();
private static final int MAX_BLOBS = 6;
private static final long MAX_BLOB_GAS = BLOB_GAS_PER_BLOB * MAX_BLOBS;
@@ -89,8 +89,8 @@ class BlobSizeTransactionSelectorTest {
@Test
void firstBlobTransactionIsSelected() {
when(blockSelectionContext.gasLimitCalculator().currentBlobGasLimit()).thenReturn(MAX_BLOB_GAS);
when(blockSelectionContext.gasCalculator().blobGasCost(anyInt()))
.thenAnswer(iom -> BLOB_GAS_PER_BLOB * iom.getArgument(0, Integer.class));
when(blockSelectionContext.gasCalculator().blobGasCost(anyLong()))
.thenAnswer(iom -> BLOB_GAS_PER_BLOB * iom.getArgument(0, Long.class));
final var selector = new BlobSizeTransactionSelector(blockSelectionContext);
@@ -131,8 +131,8 @@ class BlobSizeTransactionSelectorTest {
@Test
void returnsTooLargeForRemainingBlobGas() {
when(blockSelectionContext.gasLimitCalculator().currentBlobGasLimit()).thenReturn(MAX_BLOB_GAS);
when(blockSelectionContext.gasCalculator().blobGasCost(anyInt()))
.thenAnswer(iom -> BLOB_GAS_PER_BLOB * iom.getArgument(0, Integer.class));
when(blockSelectionContext.gasCalculator().blobGasCost(anyLong()))
.thenAnswer(iom -> BLOB_GAS_PER_BLOB * iom.getArgument(0, Long.class));
final var selector = new BlobSizeTransactionSelector(blockSelectionContext);

View File

@@ -37,7 +37,6 @@ import org.hyperledger.besu.ethereum.trie.MerkleTrieException;
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldState;
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldStateUpdateAccumulator;
import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup;
import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator;
import org.hyperledger.besu.evm.operation.BlockHashOperation;
import org.hyperledger.besu.evm.tracing.OperationTracer;
import org.hyperledger.besu.evm.worldstate.WorldState;
@@ -174,7 +173,8 @@ public abstract class AbstractBlockProcessor implements BlockProcessor {
currentGasUsed += transaction.getGasLimit() - transactionProcessingResult.getGasRemaining();
if (transaction.getVersionedHashes().isPresent()) {
currentBlobGasUsed +=
(transaction.getVersionedHashes().get().size() * CancunGasCalculator.BLOB_GAS_PER_BLOB);
(transaction.getVersionedHashes().get().size()
* protocolSpec.getGasCalculator().getBlobGasPerBlob());
}
final TransactionReceipt transactionReceipt =

View File

@@ -36,7 +36,8 @@ public class ExcessBlobGasCalculator {
.getGasCalculator()
.computeExcessBlobGas(
parentHeader.getExcessBlobGas().map(BlobGas::toLong).orElse(0L),
parentHeader.getBlobGasUsed().orElse(0L));
parentHeader.getBlobGasUsed().orElse(0L),
parentHeader.getTargetBlobsPerBlock());
return BlobGas.of(headerExcess);
}
}

View File

@@ -17,7 +17,6 @@ package org.hyperledger.besu.ethereum.mainnet.headervalidationrules;
import org.hyperledger.besu.datatypes.BlobGas;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.mainnet.DetachedBlockHeaderValidationRule;
import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import org.slf4j.Logger;
@@ -45,7 +44,8 @@ public class BlobGasValidationRule implements DetachedBlockHeaderValidationRule
long parentBlobGasUsed = parent.getBlobGasUsed().orElse(0L);
long calculatedExcessBlobGas =
gasCalculator.computeExcessBlobGas(parentExcessBlobGas, parentBlobGasUsed);
gasCalculator.computeExcessBlobGas(
parentExcessBlobGas, parentBlobGasUsed, parent.getTargetBlobsPerBlock());
if (headerExcessBlobGas != calculatedExcessBlobGas) {
LOG.info(
@@ -55,10 +55,9 @@ public class BlobGasValidationRule implements DetachedBlockHeaderValidationRule
return false;
}
long headerBlobGasUsed = header.getBlobGasUsed().orElse(0L);
if (headerBlobGasUsed % CancunGasCalculator.BLOB_GAS_PER_BLOB != 0) {
if (headerBlobGasUsed % gasCalculator.getBlobGasPerBlob() != 0) {
LOG.info(
"blob gas used must be multiple of GAS_PER_BLOB ({})",
CancunGasCalculator.BLOB_GAS_PER_BLOB);
"blob gas used must be multiple of GAS_PER_BLOB ({})", gasCalculator.getBlobGasPerBlob());
return false;
}
return true;

View File

@@ -24,7 +24,7 @@ import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason
import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -589,7 +589,7 @@ public class MainnetTransactionValidatorTest {
@Test
public void shouldAcceptTransactionWithAtLeastOneBlob() {
when(gasCalculator.blobGasCost(anyInt())).thenReturn(2L);
when(gasCalculator.blobGasCost(anyLong())).thenReturn(2L);
final TransactionValidator validator =
createTransactionValidator(
gasCalculator,

View File

@@ -20,26 +20,37 @@ import org.hyperledger.besu.datatypes.BlobGas;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator;
import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator;
import org.apache.tuweni.units.bigints.UInt64;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Tests for the {@link BlobGasValidationRule} class. */
public class BlobGasValidationRuleTest {
private CancunGasCalculator gasCalculator;
private BlobGasValidationRule blobGasValidationRule;
private CancunGasCalculator cancunGasCalculator;
private BlobGasValidationRule cancunBlobGasValidationRule;
private PragueGasCalculator pragueGasCalculator;
private BlobGasValidationRule pragueBlobGasValidationRule;
@BeforeEach
public void setUp() {
gasCalculator = new CancunGasCalculator();
blobGasValidationRule = new BlobGasValidationRule(gasCalculator);
cancunGasCalculator = new CancunGasCalculator();
cancunBlobGasValidationRule = new BlobGasValidationRule(cancunGasCalculator);
pragueGasCalculator = new PragueGasCalculator();
pragueBlobGasValidationRule = new BlobGasValidationRule(pragueGasCalculator);
}
/** Tests that the header blob gas matches the calculated blob gas and passes validation. */
/**
* Cancun EIP-4844 - Tests that the header blob gas matches the calculated blob gas and passes
* validation.
*/
@Test
public void validateHeader_BlobGasMatchesCalculated_SuccessValidation() {
long target = gasCalculator.getTargetBlobGasPerBlock();
long target = cancunGasCalculator.getTargetBlobGasPerBlock();
// Create parent header
final BlockHeaderTestFixture parentBuilder = new BlockHeaderTestFixture();
@@ -52,15 +63,16 @@ public class BlobGasValidationRuleTest {
headerBuilder.excessBlobGas(BlobGas.of(1L));
final BlockHeader header = headerBuilder.buildHeader();
assertThat(blobGasValidationRule.validate(header, parentHeader)).isTrue();
assertThat(cancunBlobGasValidationRule.validate(header, parentHeader)).isTrue();
}
/**
* Tests that the header blob gas is different from the calculated blob gas and fails validation.
* Cancun EIP-4844 - Tests that the header blob gas is different from the calculated blob gas and
* fails validation.
*/
@Test
public void validateHeader_BlobGasDifferentFromCalculated_FailsValidation() {
long target = gasCalculator.getTargetBlobGasPerBlock();
long target = cancunGasCalculator.getTargetBlobGasPerBlock();
// Create parent header
final BlockHeaderTestFixture parentBuilder = new BlockHeaderTestFixture();
@@ -72,6 +84,48 @@ public class BlobGasValidationRuleTest {
final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture();
final BlockHeader header = headerBuilder.buildHeader();
assertThat(blobGasValidationRule.validate(header, parentHeader)).isFalse();
assertThat(cancunBlobGasValidationRule.validate(header, parentHeader)).isFalse();
}
/**
* Prague EIP-7742 - Tests that the header blob gas matches the calculated blob gas and passes
* validation.
*/
@Test
public void validateHeader_BlobGasMatchesCalculated_SuccessValidation_Prague_Target3() {
// Create parent header
final BlockHeaderTestFixture parentBuilder = new BlockHeaderTestFixture();
parentBuilder.excessBlobGas(BlobGas.of(1L));
parentBuilder.blobGasUsed(pragueGasCalculator.blobGasCost(3));
parentBuilder.targetBlobsPerBlock(UInt64.valueOf(3));
final BlockHeader parentHeader = parentBuilder.buildHeader();
// Create block header with matching excessBlobGas
final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture();
headerBuilder.excessBlobGas(BlobGas.of(1L));
final BlockHeader header = headerBuilder.buildHeader();
assertThat(pragueBlobGasValidationRule.validate(header, parentHeader)).isTrue();
}
/**
* Prague EIP-7742 - Tests that the header blob gas matches the calculated blob gas and passes
* validation.
*/
@Test
public void validateHeader_BlobGasMatchesCalculated_SuccessValidation_Prague_Target4() {
// Create parent header
final BlockHeaderTestFixture parentBuilder = new BlockHeaderTestFixture();
parentBuilder.excessBlobGas(BlobGas.of(1L));
parentBuilder.blobGasUsed(pragueGasCalculator.blobGasCost(4));
parentBuilder.targetBlobsPerBlock(UInt64.valueOf(4));
final BlockHeader parentHeader = parentBuilder.buildHeader();
// Create block header with matching excessBlobGas
final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture();
headerBuilder.excessBlobGas(BlobGas.of(1L));
final BlockHeader header = headerBuilder.buildHeader();
assertThat(pragueBlobGasValidationRule.validate(header, parentHeader)).isTrue();
}
}

View File

@@ -44,13 +44,13 @@ public class CancunGasCalculator extends ShanghaiGasCalculator {
private static final long TSTORE_GAS = WARM_STORAGE_READ_COST;
/**
* The blob gas cost per blob. This is the gas cost for each blob of blob that is added to the
* The blob gas cost per blob. This is the gas cost for each blob of data that is added to the
* block.
*/
public static final long BLOB_GAS_PER_BLOB = 1 << 17;
private static final long BLOB_GAS_PER_BLOB = 1 << 17;
/** The target blob gas per block. */
public static final long TARGET_BLOB_GAS_PER_BLOCK = 0x60000;
static final long TARGET_BLOB_GAS_PER_BLOCK = 0x60000;
// EIP-1153
@Override
@@ -64,8 +64,13 @@ public class CancunGasCalculator extends ShanghaiGasCalculator {
}
@Override
public long blobGasCost(final int blobCount) {
return BLOB_GAS_PER_BLOB * blobCount;
public long blobGasCost(final long blobCount) {
return getBlobGasPerBlob() * blobCount;
}
@Override
public long getBlobGasPerBlob() {
return BLOB_GAS_PER_BLOB;
}
/**
@@ -76,35 +81,4 @@ public class CancunGasCalculator extends ShanghaiGasCalculator {
public long getTargetBlobGasPerBlock() {
return TARGET_BLOB_GAS_PER_BLOCK;
}
/**
* Computes the excess blob gas for a given block based on the parent's excess blob gas and blob
* gas used. If the sum of parent's excess blob gas and parent's blob gas used is less than the
* target blob gas per block, the excess blob gas is calculated as 0. Otherwise, it is computed as
* the difference between the sum and the target blob gas per block.
*
* @param parentExcessBlobGas The excess blob gas of the parent block.
* @param newBlobs blob gas incurred by current block
* @return The excess blob gas for the current block.
*/
@Override
public long computeExcessBlobGas(final long parentExcessBlobGas, final int newBlobs) {
final long consumedBlobGas = blobGasCost(newBlobs);
final long currentExcessBlobGas = parentExcessBlobGas + consumedBlobGas;
if (currentExcessBlobGas < TARGET_BLOB_GAS_PER_BLOCK) {
return 0L;
}
return currentExcessBlobGas - TARGET_BLOB_GAS_PER_BLOCK;
}
@Override
public long computeExcessBlobGas(final long parentExcessBlobGas, final long parentBlobGasUsed) {
final long currentExcessBlobGas = parentExcessBlobGas + parentBlobGasUsed;
if (currentExcessBlobGas < TARGET_BLOB_GAS_PER_BLOCK) {
return 0L;
}
return currentExcessBlobGas - TARGET_BLOB_GAS_PER_BLOCK;
}
}

View File

@@ -40,10 +40,12 @@ import org.hyperledger.besu.evm.precompile.SHA256PrecompiledContract;
import org.hyperledger.besu.evm.processor.AbstractMessageProcessor;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.units.bigints.UInt256;
import org.apache.tuweni.units.bigints.UInt64;
/**
* Provides various gas cost lookups and calculations used during block processing.
@@ -613,37 +615,51 @@ public interface GasCalculator {
return 0L;
}
/**
* Returns the blob gas cost per blob. This is the gas cost for each blob of data that is added to
* the block.
*
* @return the blob gas cost per blob
*/
default long getBlobGasPerBlob() {
return 0L;
}
/**
* Return the gas cost given the number of blobs
*
* @param blobCount the number of blobs
* @return the total gas cost
*/
default long blobGasCost(final int blobCount) {
default long blobGasCost(final long blobCount) {
return 0L;
}
/**
* Compute the new value for the excess blob gas, given the parent value and the count of new
* blobs
* Compute the new value for the excess blob gas, given the parent value, the parent blob gas used
* and the parent target blobs per block, if present. Used from Cancun onwards. Presence of
* parentTargetBlobsPerBlock implies EIP-7442/Prague enabled. Default to Cancun constant target
* gas value if parentTargetBlobsPerBlock is not present.
*
* @param parentExcessBlobGas excess blob gas from the parent
* @param newBlobs count of new blobs
* @param parentBlobGasUsed blob gas used from the parent
* @param parentTargetBlobsPerBlock the optional target blobs per block from the parent
* @return the new excess blob gas value
*/
default long computeExcessBlobGas(final long parentExcessBlobGas, final int newBlobs) {
return 0L;
}
default long computeExcessBlobGas(
final long parentExcessBlobGas,
final long parentBlobGasUsed,
final Optional<UInt64> parentTargetBlobsPerBlock) {
final long parentTargetBlobGas =
parentTargetBlobsPerBlock
.map(blobCount -> blobGasCost(blobCount.toLong()))
.orElse(CancunGasCalculator.TARGET_BLOB_GAS_PER_BLOCK);
final long currentExcessBlobGas = parentExcessBlobGas + parentBlobGasUsed;
/**
* Compute the new value for the excess blob gas, given the parent value and the blob gas used
*
* @param parentExcessBlobGas excess blob gas from the parent
* @param blobGasUsed blob gas used
* @return the new excess blob gas value
*/
default long computeExcessBlobGas(final long parentExcessBlobGas, final long blobGasUsed) {
return 0L;
if (currentExcessBlobGas < parentTargetBlobGas) {
return 0L;
}
return currentExcessBlobGas - parentTargetBlobGas;
}
/**

View File

@@ -17,31 +17,37 @@ package org.hyperledger.besu.evm.gascalculator;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.List;
import java.util.Optional;
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)
public class CancunGasCalculatorTest {
private final CancunGasCalculator gasCalculator = new CancunGasCalculator();
private final CancunGasCalculator cancunGasCalculator = new CancunGasCalculator();
@ParameterizedTest(name = "{index} - parent gas {0}, used gas {1}, new excess {2}")
@MethodSource("blobGasses")
public void shouldCalculateExcessBlobGasCorrectly(
final long parentExcess, final long used, final long expected) {
assertThat(gasCalculator.computeExcessBlobGas(parentExcess, (int) used)).isEqualTo(expected);
final long usedBlobGas = cancunGasCalculator.blobGasCost(used);
assertThat(
cancunGasCalculator.computeExcessBlobGas(parentExcess, usedBlobGas, Optional.empty()))
.isEqualTo(expected);
}
static Iterable<Arguments> blobGasses() {
Iterable<Arguments> blobGasses() {
long targetGasPerBlock = CancunGasCalculator.TARGET_BLOB_GAS_PER_BLOCK;
return List.of(
Arguments.of(0L, 0L, 0L),
Arguments.of(targetGasPerBlock, 0L, 0L),
Arguments.of(0L, 3, 0L),
Arguments.of(1, 3, 1),
Arguments.of(targetGasPerBlock, 1, CancunGasCalculator.BLOB_GAS_PER_BLOB),
Arguments.of(targetGasPerBlock, 1, cancunGasCalculator.getBlobGasPerBlob()),
Arguments.of(targetGasPerBlock, 3, targetGasPerBlock));
}

View File

@@ -18,13 +18,65 @@ import static org.assertj.core.api.Assertions.assertThat;
import org.hyperledger.besu.datatypes.Address;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Optional;
import org.apache.tuweni.units.bigints.UInt64;
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 PragueGasCalculatorTest {
private final PragueGasCalculator pragueGasCalculator = new PragueGasCalculator();
@Test
void testPrecompileSize() {
PragueGasCalculator subject = new PragueGasCalculator();
assertThat(subject.isPrecompile(Address.precompiled(0x14))).isFalse();
assertThat(subject.isPrecompile(Address.BLS12_MAP_FP2_TO_G2)).isTrue();
}
@ParameterizedTest(
name = "{index} - parent gas {0}, used gas {1}, blob target {2} new excess {3}")
@MethodSource("blobGasses")
public void shouldCalculateExcessBlobGasCorrectly(
final long parentExcess, final long used, final long target, final long expected) {
final long usedBlobGas = pragueGasCalculator.blobGasCost(used);
assertThat(
pragueGasCalculator.computeExcessBlobGas(
parentExcess, usedBlobGas, Optional.of(UInt64.valueOf(target))))
.isEqualTo(expected);
}
Iterable<Arguments> blobGasses() {
long threeBlobTargetGas = CancunGasCalculator.TARGET_BLOB_GAS_PER_BLOCK;
long cancunTargetCount = 3;
long newTargetCount = 4;
return List.of(
// If blob target count remains at 3
Arguments.of(0L, 0L, cancunTargetCount, 0L),
Arguments.of(threeBlobTargetGas, 0L, cancunTargetCount, 0L),
Arguments.of(0L, cancunTargetCount, cancunTargetCount, 0L),
Arguments.of(1L, cancunTargetCount, cancunTargetCount, 1L),
Arguments.of(
threeBlobTargetGas, 1L, cancunTargetCount, pragueGasCalculator.getBlobGasPerBlob()),
Arguments.of(threeBlobTargetGas, 3L, cancunTargetCount, threeBlobTargetGas),
// New target count
Arguments.of(0L, 0L, newTargetCount, 0L),
Arguments.of(threeBlobTargetGas, 0L, newTargetCount, 0L),
Arguments.of(newTargetCount, 0L, newTargetCount, 0L),
Arguments.of(0L, newTargetCount, newTargetCount, 0L),
Arguments.of(1L, newTargetCount, newTargetCount, 1L),
Arguments.of(
pragueGasCalculator.blobGasCost(newTargetCount),
1L,
newTargetCount,
pragueGasCalculator.getBlobGasPerBlob()),
Arguments.of(threeBlobTargetGas, newTargetCount, newTargetCount, threeBlobTargetGas));
}
}