From 949e3fe49183312c3cb03953a7377d5cd5efeef9 Mon Sep 17 00:00:00 2001 From: Fabio Di Fabio Date: Wed, 25 Jan 2023 11:16:40 +0100 Subject: [PATCH] [Interop-4844] Data gas accounting (#4998) merge of #4992 into interop feature branch Signed-off-by: Fabio Di Fabio --- CHANGELOG.md | 2 +- .../blockcreation/AbstractBlockCreator.java | 46 ++- .../BlockTransactionSelector.java | 297 +++++++++++------- .../AbstractBlockTransactionSelectorTest.java | 10 +- .../besu/ethereum/GasLimitCalculator.java | 4 + .../ethereum/core/BlockHeaderBuilder.java | 1 - .../besu/ethereum/core/Transaction.java | 50 +-- .../feemarket/TransactionPriceCalculator.java | 78 ++++- .../CancunTargetingGasLimitCalculator.java | 31 ++ .../mainnet/ClassicProtocolSpecs.java | 11 +- .../mainnet/MainnetProtocolSpecs.java | 54 +++- .../mainnet/MainnetTransactionProcessor.java | 16 +- .../mainnet/MainnetTransactionValidator.java | 81 +++-- .../ethereum/mainnet/ProtocolSpecBuilder.java | 8 +- .../mainnet/feemarket/CancunFeeMarket.java | 39 +++ .../ethereum/mainnet/feemarket/FeeMarket.java | 9 + .../mainnet/feemarket/LegacyFeeMarket.java | 2 +- .../mainnet/feemarket/LondonFeeMarket.java | 13 +- .../mainnet/feemarket/ZeroBaseFeeMarket.java | 5 + .../transaction/TransactionInvalidReason.java | 3 +- .../ethereum/core/TransactionEIP1559Test.java | 79 ----- .../TransactionPriceCalculatorTest.java | 12 +- .../MainnetTransactionValidatorTest.java | 105 +++++-- .../feemarket/ZeroBaseFeeMarketTest.java | 9 +- ...TransactionReplacementByFeeMarketRule.java | 25 +- .../gascalculator/CancunGasCalculator.java | 46 +++ .../besu/evm/gascalculator/GasCalculator.java | 24 +- 27 files changed, 742 insertions(+), 318 deletions(-) create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CancunTargetingGasLimitCalculator.java create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/CancunFeeMarket.java delete mode 100644 ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/TransactionEIP1559Test.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/gascalculator/CancunGasCalculator.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 47db6c2a20b..2e7612e3f1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,6 @@ ### Additions and Improvements - Added option to evm CLI tool to allow code execution at specific forks [#4913](https://github.com/hyperledger/besu/pull/4913) - Improve get account performance by using the world state updater cache [#4897](https://github.com/hyperledger/besu/pull/4897) -- Add new KZG precompile and option to override the trusted setup being used [#4822](https://github.com/hyperledger/besu/issues/4822) - Add implementation for eth_createAccessList RPC method [#4942](https://github.com/hyperledger/besu/pull/4942) ### Bug Fixes @@ -24,6 +23,7 @@ - Send only hash announcement for blob transaction type [#4940](https://github.com/hyperledger/besu/pull/4940) - Add `excess_data_gas` field to block header [#4958](https://github.com/hyperledger/besu/pull/4958) - Add `max_fee_per_data_gas` field to transaction [#4970](https://github.com/hyperledger/besu/pull/4970) +- Gas accounting for EIP-4844 [#4992](https://github.com/hyperledger/besu/pull/4992) ### Bug Fixes - Mitigation fix for stale bonsai code storage leading to log rolling issues on contract recreates [#4906](https://github.com/hyperledger/besu/pull/4906) diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java index 63f1bdb7cec..e056b0cb461 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java @@ -15,8 +15,10 @@ package org.hyperledger.besu.ethereum.blockcreation; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.DataGas; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.GasLimitCalculator; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockBody; @@ -43,6 +45,7 @@ import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.evm.account.EvmAccount; import org.hyperledger.besu.evm.worldstate.WorldUpdater; +import org.hyperledger.besu.plugin.data.TransactionType; import org.hyperledger.besu.plugin.services.exception.StorageException; import org.hyperledger.besu.plugin.services.securitymodule.SecurityModuleException; @@ -209,6 +212,10 @@ protected BlockCreationResult createBlock( throwIfStopped(); + final DataGas newExcessDataGas = computeExcessDataGas(transactionResults, newProtocolSpec); + + throwIfStopped(); + final SealableBlockHeader sealableBlockHeader = BlockHeaderBuilder.create() .populateFrom(processableBlockHeader) @@ -224,6 +231,7 @@ protected BlockCreationResult createBlock( withdrawalsCanBeProcessed ? BodyValidation.withdrawalsRoot(maybeWithdrawals.get()) : null) + .excessDataGas(newExcessDataGas) .buildSealableBlockHeader(); final BlockHeader blockHeader = createFinalBlockHeader(sealableBlockHeader); @@ -245,6 +253,26 @@ protected BlockCreationResult createBlock( } } + private DataGas computeExcessDataGas( + BlockTransactionSelector.TransactionSelectionResults transactionResults, + ProtocolSpec newProtocolSpec) { + + if (newProtocolSpec.getFeeMarket().implementsDataFee()) { + final var gasCalculator = newProtocolSpec.getGasCalculator(); + final int newBlobsCount = + transactionResults.getTransactionsByType(TransactionType.BLOB).stream() + .map(tx -> tx.getVersionedHashes().orElseThrow()) + .mapToInt(List::size) + .sum(); + // casting parent excess data gas to long since for the moment it should be well below that + // limit + return DataGas.of( + gasCalculator.computeExcessDataGas( + parentHeader.getExcessDataGas().map(DataGas::toLong).orElse(0L), newBlobsCount)); + } + return null; + } + private BlockTransactionSelector.TransactionSelectionResults selectTransactions( final ProcessableBlockHeader processableBlockHeader, final MutableWorldState disposableWorldState, @@ -269,7 +297,9 @@ private BlockTransactionSelector.TransactionSelectionResults selectTransactions( minBlockOccupancyRatio, isCancelled::get, miningBeneficiary, - protocolSpec.getFeeMarket()); + protocolSpec.getFeeMarket(), + protocolSpec.getGasCalculator(), + protocolSpec.getGasLimitCalculator()); if (transactions.isPresent()) { return selector.evaluateTransactions(transactions.get()); @@ -312,13 +342,13 @@ private ProcessableBlockHeader createPendingBlockHeader( final Optional maybePrevRandao, final ProtocolSpec protocolSpec) { final long newBlockNumber = parentHeader.getNumber() + 1; - long gasLimit = - protocolSpec - .getGasLimitCalculator() - .nextGasLimit( - parentHeader.getGasLimit(), - targetGasLimitSupplier.get().orElse(parentHeader.getGasLimit()), - newBlockNumber); + final GasLimitCalculator gasLimitCalculator = protocolSpec.getGasLimitCalculator(); + + final long gasLimit = + gasLimitCalculator.nextGasLimit( + parentHeader.getGasLimit(), + targetGasLimitSupplier.get().orElse(parentHeader.getGasLimit()), + newBlockNumber); final DifficultyCalculator difficultyCalculator = protocolSpec.getDifficultyCalculator(); final BigInteger difficulty = diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockTransactionSelector.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockTransactionSelector.java index bacbd076419..a1560e835ee 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockTransactionSelector.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockTransactionSelector.java @@ -18,6 +18,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.GasLimitCalculator; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; @@ -35,14 +36,20 @@ import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; import org.hyperledger.besu.ethereum.vm.BlockHashLookup; import org.hyperledger.besu.evm.account.EvmAccount; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.worldstate.WorldUpdater; +import org.hyperledger.besu.plugin.data.TransactionType; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.concurrent.CancellationException; import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; import com.google.common.collect.Lists; import org.apache.tuweni.bytes.Bytes; @@ -74,115 +81,6 @@ public class BlockTransactionSelector { private final Wei minTransactionGasPrice; private final Double minBlockOccupancyRatio; - - public static class TransactionValidationResult { - private final Transaction transaction; - private final ValidationResult validationResult; - - public TransactionValidationResult( - final Transaction transaction, - final ValidationResult validationResult) { - this.transaction = transaction; - this.validationResult = validationResult; - } - - public Transaction getTransaction() { - return transaction; - } - - public ValidationResult getValidationResult() { - return validationResult; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - TransactionValidationResult that = (TransactionValidationResult) o; - return Objects.equals(transaction, that.transaction) - && Objects.equals(validationResult, that.validationResult); - } - - @Override - public int hashCode() { - return Objects.hash(transaction, validationResult); - } - } - - public static class TransactionSelectionResults { - - private final List transactions = Lists.newArrayList(); - private final List receipts = Lists.newArrayList(); - private final List invalidTransactions = Lists.newArrayList(); - private long cumulativeGasUsed = 0; - - private void update( - final Transaction transaction, final TransactionReceipt receipt, final long gasUsed) { - transactions.add(transaction); - receipts.add(receipt); - cumulativeGasUsed += gasUsed; - traceLambda( - LOG, - "New selected transaction {}, total transactions {}, cumulative gas used {}", - transaction::toTraceLog, - transactions::size, - () -> cumulativeGasUsed); - } - - private void updateWithInvalidTransaction( - final Transaction transaction, - final ValidationResult validationResult) { - invalidTransactions.add(new TransactionValidationResult(transaction, validationResult)); - } - - public List getTransactions() { - return transactions; - } - - public List getReceipts() { - return receipts; - } - - public long getCumulativeGasUsed() { - return cumulativeGasUsed; - } - - public List getInvalidTransactions() { - return invalidTransactions; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - TransactionSelectionResults that = (TransactionSelectionResults) o; - return cumulativeGasUsed == that.cumulativeGasUsed - && transactions.equals(that.transactions) - && receipts.equals(that.receipts) - && invalidTransactions.equals(that.invalidTransactions); - } - - @Override - public int hashCode() { - return Objects.hash(transactions, receipts, invalidTransactions, cumulativeGasUsed); - } - - public String toTraceLog() { - return "cumulativeGasUsed=" - + cumulativeGasUsed - + ", transactions=" - + transactions.stream().map(Transaction::toTraceLog).collect(Collectors.joining("; ")); - } - } - private final Supplier isCancelled; private final MainnetTransactionProcessor transactionProcessor; private final ProcessableBlockHeader processableBlockHeader; @@ -192,6 +90,8 @@ public String toTraceLog() { private final AbstractBlockProcessor.TransactionReceiptFactory transactionReceiptFactory; private final Address miningBeneficiary; private final FeeMarket feeMarket; + private final GasCalculator gasCalculator; + private final GasLimitCalculator gasLimitCalculator; private final TransactionSelectionResults transactionSelectionResult = new TransactionSelectionResults(); @@ -207,7 +107,9 @@ public BlockTransactionSelector( final Double minBlockOccupancyRatio, final Supplier isCancelled, final Address miningBeneficiary, - final FeeMarket feeMarket) { + final FeeMarket feeMarket, + final GasCalculator gasCalculator, + final GasLimitCalculator gasLimitCalculator) { this.transactionProcessor = transactionProcessor; this.blockchain = blockchain; this.worldState = worldState; @@ -219,6 +121,8 @@ public BlockTransactionSelector( this.minBlockOccupancyRatio = minBlockOccupancyRatio; this.miningBeneficiary = miningBeneficiary; this.feeMarket = feeMarket; + this.gasCalculator = gasCalculator; + this.gasLimitCalculator = gasLimitCalculator; } /* @@ -278,6 +182,9 @@ private TransactionSelectionResult evaluateTransaction( if (transactionCurrentPriceBelowMin(transaction)) { return TransactionSelectionResult.CONTINUE; } + if (transactionDataPriceBelowMin(transaction)) { + return TransactionSelectionResult.CONTINUE; + } final WorldUpdater worldStateUpdater = worldState.updater(); final BlockHashLookup blockHashLookup = new BlockHashLookup(processableBlockHeader, blockchain); @@ -331,6 +238,21 @@ private TransactionSelectionResult evaluateTransaction( return TransactionSelectionResult.CONTINUE; } + private boolean transactionDataPriceBelowMin(final Transaction transaction) { + if (transaction.getType().supportsBlob()) { + if (transaction + .getMaxFeePerDataGas() + .orElseThrow() + .lessThan( + feeMarket + .getTransactionPriceCalculator() + .dataPrice(transaction, processableBlockHeader))) { + return true; + } + } + return false; + } + private boolean transactionCurrentPriceBelowMin(final Transaction transaction) { // Here we only care about EIP1159 since for Frontier and local transactions the checks // that we do when accepting them in the pool are enough @@ -420,8 +342,12 @@ private void updateTransactionResultTracking( transaction.isGoQuorumPrivateTransaction( transactionProcessor.getTransactionValidator().getGoQuorumCompatibilityMode()); + final long dataGasUsed = gasCalculator.dataGasCost(transaction.getBlobCount()); + final long gasUsedByTransaction = - isGoQuorumPrivateTransaction ? 0 : transaction.getGasLimit() - result.getGasRemaining(); + isGoQuorumPrivateTransaction + ? 0 + : transaction.getGasLimit() + dataGasUsed - result.getGasRemaining(); final long cumulativeGasUsed = transactionSelectionResult.getCumulativeGasUsed() + gasUsedByTransaction; @@ -430,7 +356,8 @@ private void updateTransactionResultTracking( transaction, transactionReceiptFactory.create( transaction.getType(), result, worldState, cumulativeGasUsed), - gasUsedByTransaction); + gasUsedByTransaction, + dataGasUsed); } private boolean isIncorrectNonce(final ValidationResult result) { @@ -448,7 +375,15 @@ private TransactionProcessingResult publicResultForWhenWeHaveAPrivateTransaction } private boolean transactionTooLargeForBlock(final Transaction transaction) { - return transaction.getGasLimit() + final long dataGasUsed = gasCalculator.dataGasCost(transaction.getBlobCount()); + + if (dataGasUsed + > gasLimitCalculator.currentDataGasLimit() + - transactionSelectionResult.getCumulativeDataGasUsed()) { + return true; + } + + return transaction.getGasLimit() + dataGasUsed > processableBlockHeader.getGasLimit() - transactionSelectionResult.getCumulativeGasUsed(); } @@ -464,4 +399,140 @@ private boolean blockOccupancyAboveThreshold() { occupancyRatio); return occupancyRatio >= minBlockOccupancyRatio; } + + public static class TransactionValidationResult { + private final Transaction transaction; + private final ValidationResult validationResult; + + public TransactionValidationResult( + final Transaction transaction, + final ValidationResult validationResult) { + this.transaction = transaction; + this.validationResult = validationResult; + } + + public Transaction getTransaction() { + return transaction; + } + + public ValidationResult getValidationResult() { + return validationResult; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TransactionValidationResult that = (TransactionValidationResult) o; + return Objects.equals(transaction, that.transaction) + && Objects.equals(validationResult, that.validationResult); + } + + @Override + public int hashCode() { + return Objects.hash(transaction, validationResult); + } + } + + public static class TransactionSelectionResults { + + private final Map> transactionsByType = new HashMap<>(); + private final List receipts = Lists.newArrayList(); + private final List invalidTransactions = Lists.newArrayList(); + private long cumulativeGasUsed = 0; + private long cumulativeDataGasUsed = 0; + + private void update( + final Transaction transaction, + final TransactionReceipt receipt, + final long gasUsed, + final long dataGasUsed) { + transactionsByType + .computeIfAbsent(transaction.getType(), type -> new ArrayList<>()) + .add(transaction); + receipts.add(receipt); + cumulativeGasUsed += gasUsed; + cumulativeDataGasUsed += dataGasUsed; + traceLambda( + LOG, + "New selected transaction {}, total transactions {}, cumulative gas used {}, cumulative data gas used {}", + transaction::toTraceLog, + () -> transactionsByType.values().stream().mapToInt(List::size).sum(), + () -> cumulativeGasUsed, + () -> cumulativeDataGasUsed); + } + + private void updateWithInvalidTransaction( + final Transaction transaction, + final ValidationResult validationResult) { + invalidTransactions.add(new TransactionValidationResult(transaction, validationResult)); + } + + public List getTransactions() { + return streamAllTransactions().collect(Collectors.toList()); + } + + public List getTransactionsByType(final TransactionType type) { + return transactionsByType.getOrDefault(type, List.of()); + } + + public List getReceipts() { + return receipts; + } + + public long getCumulativeGasUsed() { + return cumulativeGasUsed; + } + + public long getCumulativeDataGasUsed() { + return cumulativeDataGasUsed; + } + + public List getInvalidTransactions() { + return invalidTransactions; + } + + private Stream streamAllTransactions() { + return transactionsByType.values().stream().flatMap(List::stream); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TransactionSelectionResults that = (TransactionSelectionResults) o; + return cumulativeGasUsed == that.cumulativeGasUsed + && cumulativeDataGasUsed == that.cumulativeDataGasUsed + && transactionsByType.equals(that.transactionsByType) + && receipts.equals(that.receipts) + && invalidTransactions.equals(that.invalidTransactions); + } + + @Override + public int hashCode() { + return Objects.hash( + transactionsByType, + receipts, + invalidTransactions, + cumulativeGasUsed, + cumulativeDataGasUsed); + } + + public String toTraceLog() { + return "cumulativeGasUsed=" + + cumulativeGasUsed + + ", cumulativeDataGasUsed=" + + cumulativeDataGasUsed + + ", transactions=" + + streamAllTransactions().map(Transaction::toTraceLog).collect(Collectors.joining("; ")); + } + } } diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockTransactionSelectorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockTransactionSelectorTest.java index f6e0f9b263e..ebe8e8bb224 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockTransactionSelectorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockTransactionSelectorTest.java @@ -27,6 +27,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.GasLimitCalculator; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.AddressHelpers; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -50,6 +51,7 @@ import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.referencetests.ReferenceTestBlockchain; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; +import org.hyperledger.besu.evm.gascalculator.LondonGasCalculator; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.worldstate.WorldState; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; @@ -255,7 +257,9 @@ public void useSingleGasSpaceForAllTransactions() { 0.8, this::isCancelled, miningBeneficiary, - FeeMarket.london(0L)); + FeeMarket.london(0L), + new LondonGasCalculator(), + GasLimitCalculator.constant()); // this should fill up all the block space final Transaction fillingLegacyTx = @@ -452,7 +456,9 @@ protected BlockTransactionSelector createBlockSelector( 0.8, this::isCancelled, miningBeneficiary, - getFeeMarket()); + getFeeMarket(), + new LondonGasCalculator(), + GasLimitCalculator.constant()); return selector; } 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 e358a1aeb54..30a35389afb 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 @@ -21,4 +21,8 @@ public interface GasLimitCalculator { static GasLimitCalculator constant() { return (currentGasLimit, targetGasLimit, newBlockNumber) -> currentGasLimit; } + + default long currentDataGasLimit() { + return 0L; + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeaderBuilder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeaderBuilder.java index 896fb80604d..d3a2a7e7f0d 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeaderBuilder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeaderBuilder.java @@ -385,7 +385,6 @@ public BlockHeaderBuilder withdrawalsRoot(final Hash hash) { } public BlockHeaderBuilder excessDataGas(final DataGas excessDataGas) { - checkArgument(gasLimit >= 0L); this.excessDataGas = excessDataGas; return this; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java index f5346455191..224d2c9d5ab 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java @@ -45,7 +45,6 @@ import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; -import java.util.stream.Stream; import com.google.common.primitives.Longs; import org.apache.tuweni.bytes.Bytes; @@ -329,7 +328,6 @@ public Transaction( final Address sender, final Optional chainId, final Optional> versionedHashes) { - this( nonce, Optional.of(gasPrice), @@ -492,6 +490,15 @@ public long getGasLimit() { return gasLimit; } + /** + * Returns the number of blobs this transaction has, or 0 if not a blob transaction type + * + * @return return the count + */ + public int getBlobCount() { + return versionedHashes.map(List::size).orElse(0); + } + /** * Returns the transaction recipient. * @@ -706,18 +713,13 @@ public boolean isContractCreation() { } /** - * Calculates the up-front cost for the gas the transaction can use. + * Calculates the max up-front cost for the gas the transaction can use. * - * @return the up-front cost for the gas the transaction can use. + * @return the max up-front cost for the gas the transaction can use. */ - public Wei getUpfrontGasCost() { + private Wei getMaxUpfrontGasCost(final long dataGasPerBlock) { return getUpfrontGasCost( - Stream.concat(maxFeePerGas.stream(), gasPrice.stream()) - .findFirst() - .orElseThrow( - () -> - new IllegalStateException( - String.format("Transaction requires either gasPrice or maxFeePerGas")))); + getMaxGasPrice(), getMaxFeePerDataGas().orElse(Wei.ZERO), dataGasPerBlock); } /** @@ -726,21 +728,23 @@ public Wei getUpfrontGasCost() { * @return true is upfront data cost overflow uint256 max value */ private boolean isUpfrontGasCostTooHigh() { - return calculateUpfrontGasCost(getMaxGasPrice()).bitLength() > 256; + return calculateUpfrontGasCost(getMaxGasPrice(), Wei.ZERO, 0L).bitLength() > 256; } /** - * Calculates the up-front cost for the gas the transaction can use. + * Calculates the up-front cost for the gas and data gas the transaction can use. * * @param gasPrice the gas price to use + * @param dataGasPrice the data gas price to use * @return the up-front cost for the gas the transaction can use. */ - public Wei getUpfrontGasCost(final Wei gasPrice) { + public Wei getUpfrontGasCost( + final Wei gasPrice, final Wei dataGasPrice, final long totalDataGas) { if (gasPrice == null || gasPrice.isZero()) { return Wei.ZERO; } - final var cost = calculateUpfrontGasCost(gasPrice); + final var cost = calculateUpfrontGasCost(gasPrice, dataGasPrice, totalDataGas); if (cost.bitLength() > 256) { return Wei.MAX_WEI; @@ -749,8 +753,16 @@ public Wei getUpfrontGasCost(final Wei gasPrice) { } } - private BigInteger calculateUpfrontGasCost(final Wei gasPrice) { - return new BigInteger(1, Longs.toByteArray(getGasLimit())).multiply(gasPrice.getAsBigInteger()); + private BigInteger calculateUpfrontGasCost( + final Wei gasPrice, final Wei dataGasPrice, final long totalDataGas) { + var cost = + new BigInteger(1, Longs.toByteArray(getGasLimit())).multiply(gasPrice.getAsBigInteger()); + + if (transactionType.supportsBlob()) { + cost = cost.add(dataGasPrice.getAsBigInteger().multiply(BigInteger.valueOf(totalDataGas))); + } + + return cost; } /** @@ -762,8 +774,8 @@ private BigInteger calculateUpfrontGasCost(final Wei gasPrice) { * * @return the up-front gas cost for the transaction */ - public Wei getUpfrontCost() { - return getUpfrontGasCost().addExact(getValue()); + public Wei getUpfrontCost(final long totalDataGas) { + return getMaxUpfrontGasCost(totalDataGas).addExact(getValue()); } /** diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculator.java index b68da184006..531dff5d4f3 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculator.java @@ -14,22 +14,41 @@ */ package org.hyperledger.besu.ethereum.core.feemarket; +import static org.hyperledger.besu.util.Slf4jLambdaHelper.traceLambda; + import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; +import java.math.BigInteger; import java.util.Optional; -@FunctionalInterface +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public interface TransactionPriceCalculator { - Wei price(Transaction transaction, Optional baseFee); + Wei price(Transaction transaction, Optional maybeFee); - static TransactionPriceCalculator frontier() { - return (transaction, baseFee) -> transaction.getGasPrice().orElse(Wei.ZERO); + default Wei dataPrice(final Transaction transaction, final ProcessableBlockHeader blockHeader) { + return Wei.ZERO; } - static TransactionPriceCalculator eip1559() { - return (transaction, maybeBaseFee) -> { - final Wei baseFee = maybeBaseFee.orElseThrow(); + class Frontier implements TransactionPriceCalculator { + @Override + public Wei price(final Transaction transaction, final Optional maybeFee) { + return transaction.getGasPrice().orElse(Wei.ZERO); + } + + @Override + public Wei dataPrice(final Transaction transaction, final ProcessableBlockHeader blockHeader) { + return Wei.ZERO; + } + } + + class EIP1559 implements TransactionPriceCalculator { + @Override + public Wei price(final Transaction transaction, final Optional maybeFee) { + final Wei baseFee = maybeFee.orElseThrow(); if (!transaction.getType().supports1559FeeMarket()) { return transaction.getGasPrice().orElse(Wei.ZERO); } @@ -40,6 +59,49 @@ static TransactionPriceCalculator eip1559() { price = maxFeePerGas; } return price; - }; + } + } + + class DataBlob extends EIP1559 { + private static final Logger LOG = LoggerFactory.getLogger(DataBlob.class); + private final BigInteger minDataGasPrice; + private final BigInteger dataGasPriceUpdateFraction; + + public DataBlob(final int minDataGasPrice, final int dataGasPriceUpdateFraction) { + this.minDataGasPrice = BigInteger.valueOf(minDataGasPrice); + this.dataGasPriceUpdateFraction = BigInteger.valueOf(dataGasPriceUpdateFraction); + } + + @Override + public Wei dataPrice(final Transaction transaction, final ProcessableBlockHeader blockHeader) { + final var excessDataGas = blockHeader.getExcessDataGas().orElseThrow(); + + final var dataGasPrice = + Wei.of( + fakeExponential( + minDataGasPrice, excessDataGas.toBigInteger(), dataGasPriceUpdateFraction)); + traceLambda( + LOG, + "block #{} parentExcessDataGas: {} dataGasPrice: {}", + blockHeader::getNumber, + excessDataGas::toShortHexString, + dataGasPrice::toHexString); + + return dataGasPrice; + } + + private BigInteger fakeExponential( + final BigInteger factor, final BigInteger numerator, final BigInteger denominator) { + BigInteger i = BigInteger.ONE; + BigInteger output = BigInteger.ZERO; + BigInteger numeratorAccumulator = factor.multiply(denominator); + while (numeratorAccumulator.compareTo(BigInteger.ZERO) > 0) { + output = output.add(numeratorAccumulator); + numeratorAccumulator = + (numeratorAccumulator.multiply(numerator)).divide(denominator.multiply(i)); + i = i.add(BigInteger.ONE); + } + return output.divide(denominator); + } } } 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 new file mode 100644 index 00000000000..c0c3366f4df --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CancunTargetingGasLimitCalculator.java @@ -0,0 +1,31 @@ +/* + * Copyright contributors to Hyperledger 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 org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket; + +public class CancunTargetingGasLimitCalculator extends LondonTargetingGasLimitCalculator { + private static final long MAX_DATA_GAS_PER_BLOCK = 1 << 19; + + public CancunTargetingGasLimitCalculator( + final long londonForkBlock, final BaseFeeMarket feeMarket) { + super(londonForkBlock, feeMarket); + } + + @Override + public long currentDataGasLimit() { + return MAX_DATA_GAS_PER_BLOCK; + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ClassicProtocolSpecs.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ClassicProtocolSpecs.java index 89548664210..d041f0b41b4 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ClassicProtocolSpecs.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ClassicProtocolSpecs.java @@ -76,9 +76,9 @@ public static ProtocolSpecBuilder tangerineWhistleDefinition( contractSizeLimit, configStackSizeLimit, quorumCompatibilityMode, evmConfiguration) .gasCalculator(TangerineWhistleGasCalculator::new) .transactionValidatorBuilder( - gasCalculator -> + (gasCalculator, gasLimitCalculator) -> new MainnetTransactionValidator( - gasCalculator, true, chainId, quorumCompatibilityMode)) + gasCalculator, gasLimitCalculator, true, chainId, quorumCompatibilityMode)) .name("ClassicTangerineWhistle"); } @@ -149,9 +149,9 @@ public static ProtocolSpecBuilder defuseDifficultyBombDefinition( evmConfiguration) .difficultyCalculator(ClassicDifficultyCalculators.DIFFICULTY_BOMB_REMOVED) .transactionValidatorBuilder( - gasCalculator -> + (gasCalculator, gasLimitCalculator) -> new MainnetTransactionValidator( - gasCalculator, true, chainId, quorumCompatibilityMode)) + gasCalculator, gasLimitCalculator, true, chainId, quorumCompatibilityMode)) .name("DefuseDifficultyBomb"); } @@ -353,9 +353,10 @@ public static ProtocolSpecBuilder magnetoDefinition( evmConfiguration) .gasCalculator(BerlinGasCalculator::new) .transactionValidatorBuilder( - gasCalculator -> + (gasCalculator, gasLimitCalculator) -> new MainnetTransactionValidator( gasCalculator, + gasLimitCalculator, true, chainId, Set.of(TransactionType.FRONTIER, TransactionType.ACCESS_LIST), diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java index 874b73afb17..dd92d44edfd 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java @@ -19,6 +19,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.BlockProcessingResult; +import org.hyperledger.besu.ethereum.GasLimitCalculator; import org.hyperledger.besu.ethereum.MainnetBlockValidator; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -45,6 +46,7 @@ import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.BerlinGasCalculator; import org.hyperledger.besu.evm.gascalculator.ByzantiumGasCalculator; +import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; import org.hyperledger.besu.evm.gascalculator.FrontierGasCalculator; import org.hyperledger.besu.evm.gascalculator.HomesteadGasCalculator; @@ -122,9 +124,9 @@ public static ProtocolSpecBuilder frontierDefinition( Collections.singletonList(MaxCodeSizeRule.of(contractSizeLimit)), 0)) .transactionValidatorBuilder( - gasCalculator -> + (gasCalculator, gasLimitCalculator) -> new MainnetTransactionValidator( - gasCalculator, false, Optional.empty(), goQuorumMode)) + gasCalculator, gasLimitCalculator, false, Optional.empty(), goQuorumMode)) .transactionProcessorBuilder( (gasCalculator, transactionValidator, @@ -228,9 +230,13 @@ public static ProtocolSpecBuilder homesteadDefinition( Collections.singletonList(MaxCodeSizeRule.of(contractSizeLimit)), 0)) .transactionValidatorBuilder( - gasCalculator -> + (gasCalculator, gasLimitCalculator) -> new MainnetTransactionValidator( - gasCalculator, true, Optional.empty(), quorumCompatibilityMode)) + gasCalculator, + gasLimitCalculator, + true, + Optional.empty(), + quorumCompatibilityMode)) .difficultyCalculator(MainnetDifficultyCalculators.HOMESTEAD) .name("Homestead"); } @@ -315,9 +321,9 @@ public static ProtocolSpecBuilder spuriousDragonDefinition( 1, SPURIOUS_DRAGON_FORCE_DELETE_WHEN_EMPTY_ADDRESSES)) .transactionValidatorBuilder( - gasCalculator -> + (gasCalculator, gasLimitCalculator) -> new MainnetTransactionValidator( - gasCalculator, true, chainId, quorumCompatibilityMode)) + gasCalculator, gasLimitCalculator, true, chainId, quorumCompatibilityMode)) .transactionProcessorBuilder( (gasCalculator, transactionValidator, @@ -482,9 +488,10 @@ static ProtocolSpecBuilder berlinDefinition( evmConfiguration) .gasCalculator(BerlinGasCalculator::new) .transactionValidatorBuilder( - gasCalculator -> + (gasCalculator, gasLimitCalculator) -> new MainnetTransactionValidator( gasCalculator, + gasLimitCalculator, true, chainId, Set.of(TransactionType.FRONTIER, TransactionType.ACCESS_LIST), @@ -524,9 +531,10 @@ static ProtocolSpecBuilder londonDefinition( .gasLimitCalculator( new LondonTargetingGasLimitCalculator(londonForkBlockNumber, londonFeeMarket)) .transactionValidatorBuilder( - gasCalculator -> + (gasCalculator, gasLimitCalculator) -> new MainnetTransactionValidator( gasCalculator, + gasLimitCalculator, londonFeeMarket, true, chainId, @@ -691,9 +699,10 @@ static ProtocolSpecBuilder shanghaiDefinition( CoinbaseFeePriceCalculator.eip1559())) // Contract creation rules for EIP-3860 Limit and meter intitcode .transactionValidatorBuilder( - gasCalculator -> + (gasCalculator, gasLimitCalculator) -> new MainnetTransactionValidator( gasCalculator, + gasLimitCalculator, londonFeeMarket, true, chainId, @@ -719,6 +728,14 @@ static ProtocolSpecBuilder cancunDefinition( final int contractSizeLimit = configContractSizeLimit.orElse(SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT); + final long londonForkBlockNumber = genesisConfigOptions.getLondonBlockNumber().orElse(0L); + final BaseFeeMarket cancunFeeMarket = + genesisConfigOptions.isZeroBaseFee() + ? FeeMarket.zeroBaseFee(londonForkBlockNumber) + : FeeMarket.cancun(londonForkBlockNumber, genesisConfigOptions.getBaseFeePerGas()); + + final GasLimitCalculator cancunGasLimitCalculator = + new CancunTargetingGasLimitCalculator(londonForkBlockNumber, cancunFeeMarket); return shanghaiDefinition( chainId, @@ -728,6 +745,10 @@ static ProtocolSpecBuilder cancunDefinition( genesisConfigOptions, quorumCompatibilityMode, evmConfiguration) + // gas calculator for EIP-4844 data gas + .gasCalculator(CancunGasCalculator::new) + // gas limit with EIP-4844 max data gas per block + .gasLimitCalculator(cancunGasLimitCalculator) // EVM changes to support EOF EIPs (3670, 4200, 4750, 5450) .evmBuilder( (gasCalculator, jdCacheConfig) -> @@ -744,6 +765,21 @@ static ProtocolSpecBuilder cancunDefinition( MaxCodeSizeRule.of(contractSizeLimit), EOFValidationCodeRule.of(1, false)), 1, SPURIOUS_DRAGON_FORCE_DELETE_WHEN_EMPTY_ADDRESSES)) + // change to check for max data gas per block for EIP-4844 + .transactionValidatorBuilder( + (gasCalculator, gasLimitCalculator) -> + new MainnetTransactionValidator( + gasCalculator, + gasLimitCalculator, + cancunFeeMarket, + true, + chainId, + Set.of( + TransactionType.FRONTIER, + TransactionType.ACCESS_LIST, + TransactionType.EIP1559), + quorumCompatibilityMode, + SHANGHAI_INIT_CODE_SIZE_LIMIT)) .precompileContractRegistryBuilder(MainnetPrecompiledContractRegistries::cancun) .name("Cancun"); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java index f9c731744a9..9348011f814 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java @@ -291,13 +291,18 @@ public TransactionProcessingResult processTransaction( final long previousNonce = senderMutableAccount.incrementNonce(); final Wei transactionGasPrice = feeMarket.getTransactionPriceCalculator().price(transaction, blockHeader.getBaseFee()); + final Wei dataGasPrice = + feeMarket.getTransactionPriceCalculator().dataPrice(transaction, blockHeader); LOG.trace( "Incremented sender {} nonce ({} -> {})", senderAddress, previousNonce, sender.getNonce()); - final Wei upfrontGasCost = transaction.getUpfrontGasCost(transactionGasPrice); + final long dataGas = gasCalculator.dataGasCost(transaction.getBlobCount()); + + final Wei upfrontGasCost = + transaction.getUpfrontGasCost(transactionGasPrice, dataGasPrice, dataGas); final Wei previousBalance = senderMutableAccount.decrementBalance(upfrontGasCost); LOG.trace( "Deducted sender {} upfront gas cost {} ({} -> {})", @@ -328,12 +333,15 @@ public TransactionProcessingResult processTransaction( transaction.getPayload(), transaction.isContractCreation()); final long accessListGas = gasCalculator.accessListGasCost(accessListEntries.size(), accessListStorageCount); - final long gasAvailable = transaction.getGasLimit() - intrinsicGas - accessListGas; + + final long gasAvailable = transaction.getGasLimit() - intrinsicGas - accessListGas - dataGas; LOG.trace( - "Gas available for execution {} = {} - {} (limit - intrinsic)", + "Gas available for execution {} = {} - {} - {} - {} (limit - intrinsic - accessList - data)", gasAvailable, transaction.getGasLimit(), - intrinsicGas); + intrinsicGas, + accessListGas, + dataGas); final WorldUpdater worldUpdater = worldState.updater(); final Deque messageFrameStack = new ArrayDeque<>(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java index 68b445ae63c..becea162e12 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java @@ -20,6 +20,7 @@ import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.GasLimitCalculator; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionFilter; import org.hyperledger.besu.ethereum.core.encoding.ssz.TransactionNetworkPayload; @@ -46,12 +47,10 @@ * {@link Transaction}. */ public class MainnetTransactionValidator { - - private final long MAX_DATA_GAS_PER_BLOCK = 524_288; // 2**19 - private final long DATA_GAS_PER_BLOB = 131_072; // 2**17 private final byte BLOB_COMMITMENT_VERSION_KZG = 0x01; private final GasCalculator gasCalculator; + private final GasLimitCalculator gasLimitCalculator; private final FeeMarket feeMarket; private final boolean disallowSignatureMalleability; @@ -66,11 +65,13 @@ public class MainnetTransactionValidator { public MainnetTransactionValidator( final GasCalculator gasCalculator, + final GasLimitCalculator gasLimitCalculator, final boolean checkSignatureMalleability, final Optional chainId, final boolean goQuorumCompatibilityMode) { this( gasCalculator, + gasLimitCalculator, checkSignatureMalleability, chainId, Set.of(TransactionType.FRONTIER), @@ -79,12 +80,14 @@ public MainnetTransactionValidator( public MainnetTransactionValidator( final GasCalculator gasCalculator, + final GasLimitCalculator gasLimitCalculator, final boolean checkSignatureMalleability, final Optional chainId, final Set acceptedTransactionTypes, final boolean quorumCompatibilityMode) { this( gasCalculator, + gasLimitCalculator, FeeMarket.legacy(), checkSignatureMalleability, chainId, @@ -95,6 +98,7 @@ public MainnetTransactionValidator( public MainnetTransactionValidator( final GasCalculator gasCalculator, + final GasLimitCalculator gasLimitCalculator, final FeeMarket feeMarket, final boolean checkSignatureMalleability, final Optional chainId, @@ -102,6 +106,7 @@ public MainnetTransactionValidator( final boolean goQuorumCompatibilityMode, final int maxInitcodeSize) { this.gasCalculator = gasCalculator; + this.gasLimitCalculator = gasLimitCalculator; this.feeMarket = feeMarket; this.disallowSignatureMalleability = checkSignatureMalleability; this.chainId = chainId; @@ -138,12 +143,6 @@ public ValidationResult validate( } } - if (goQuorumCompatibilityMode && transaction.hasCostParams()) { - return ValidationResult.invalid( - TransactionInvalidReason.GAS_PRICE_MUST_BE_ZERO, - "gasPrice must be set to zero on a GoQuorum compatible network"); - } - final TransactionType transactionType = transaction.getType(); if (!acceptedTransactionTypes.contains(transactionType)) { return ValidationResult.invalid( @@ -153,10 +152,37 @@ public ValidationResult validate( transactionType, acceptedTransactionTypes)); } - if (baseFee.isPresent()) { - final Wei price = feeMarket.getTransactionPriceCalculator().price(transaction, baseFee); + if (transaction.getNonce() == MAX_NONCE) { + return ValidationResult.invalid( + TransactionInvalidReason.NONCE_OVERFLOW, "Nonce must be less than 2^64-1"); + } + + if (transaction.isContractCreation() && transaction.getPayload().size() > maxInitcodeSize) { + return ValidationResult.invalid( + TransactionInvalidReason.INITCODE_TOO_LARGE, + String.format( + "Initcode size of %d exceeds maximum size of %s", + transaction.getPayload().size(), maxInitcodeSize)); + } + + return validateCostAndFee(transaction, baseFee, transactionValidationParams); + } + + private ValidationResult validateCostAndFee( + final Transaction transaction, + final Optional maybeBaseFee, + final TransactionValidationParams transactionValidationParams) { + + if (goQuorumCompatibilityMode && transaction.hasCostParams()) { + return ValidationResult.invalid( + TransactionInvalidReason.GAS_PRICE_MUST_BE_ZERO, + "gasPrice must be set to zero on a GoQuorum compatible network"); + } + + if (maybeBaseFee.isPresent()) { + final Wei price = feeMarket.getTransactionPriceCalculator().price(transaction, maybeBaseFee); if (!transactionValidationParams.isAllowMaxFeeGasBelowBaseFee() - && price.compareTo(baseFee.orElseThrow()) < 0) { + && price.compareTo(maybeBaseFee.orElseThrow()) < 0) { return ValidationResult.invalid( TransactionInvalidReason.GAS_PRICE_BELOW_CURRENT_BASE_FEE, "gasPrice is less than the current BaseFee"); @@ -181,6 +207,17 @@ public ValidationResult validate( TransactionInvalidReason.NONCE_OVERFLOW, "Nonce must be less than 2^64-1"); } + if (transaction.getType().supportsBlob()) { + final long txTotalDataGas = gasCalculator.dataGasCost(transaction.getBlobCount()); + if (txTotalDataGas > gasLimitCalculator.currentDataGasLimit()) { + return ValidationResult.invalid( + TransactionInvalidReason.TOTAL_DATA_GAS_TOO_HIGH, + String.format( + "total data gas %d exceeds max data gas per block %d", + txTotalDataGas, gasLimitCalculator.currentDataGasLimit())); + } + } + final long intrinsicGasCost = gasCalculator.transactionIntrinsicGasCost( transaction.getPayload(), transaction.isContractCreation()) @@ -193,14 +230,6 @@ public ValidationResult validate( intrinsicGasCost, transaction.getGasLimit())); } - if (transaction.isContractCreation() && transaction.getPayload().size() > maxInitcodeSize) { - return ValidationResult.invalid( - TransactionInvalidReason.INITCODE_TOO_LARGE, - String.format( - "Initcode size of %d exceeds maximum size of %s", - transaction.getPayload().size(), maxInitcodeSize)); - } - return ValidationResult.valid(); } @@ -218,12 +247,14 @@ public ValidationResult validateForSender( if (sender.getCodeHash() != null) codeHash = sender.getCodeHash(); } - if (transaction.getUpfrontCost().compareTo(senderBalance) > 0) { + final Wei upfrontCost = + transaction.getUpfrontCost(gasCalculator.dataGasCost(transaction.getBlobCount())); + if (upfrontCost.compareTo(senderBalance) > 0) { return ValidationResult.invalid( TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE, String.format( "transaction up-front cost %s exceeds transaction sender account balance %s", - transaction.getUpfrontCost(), senderBalance)); + upfrontCost, senderBalance)); } if (transaction.getNonce() < senderNonce) { @@ -316,14 +347,14 @@ public ValidationResult validateTransactionsBlobs( Transaction.BlobsWithCommitments blobsWithCommitments = transaction.getBlobsWithCommitments().get(); - if (blobsWithCommitments.blobs.getElements().size() - > MAX_DATA_GAS_PER_BLOCK / DATA_GAS_PER_BLOB) { + final long blobsLimit = gasLimitCalculator.currentDataGasLimit() / gasCalculator.dataGasCost(1); + if (blobsWithCommitments.blobs.getElements().size() > blobsLimit) { return ValidationResult.invalid( TransactionInvalidReason.INVALID_BLOBS, "Too many transaction blobs (" + blobsWithCommitments.blobs.getElements().size() + ") in transaction, max is " - + MAX_DATA_GAS_PER_BLOCK / DATA_GAS_PER_BLOB); + + blobsLimit); } if (blobsWithCommitments.blobs.getElements().size() diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java index 043155dc932..4e1c0028e8e 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java @@ -54,7 +54,8 @@ public class ProtocolSpecBuilder { private DifficultyCalculator difficultyCalculator; private EvmConfiguration evmConfiguration; private BiFunction evmBuilder; - private Function transactionValidatorBuilder; + private BiFunction + transactionValidatorBuilder; private Function blockHeaderValidatorBuilder; private Function ommerHeaderValidatorBuilder; private Function blockBodyValidatorBuilder; @@ -123,7 +124,8 @@ public ProtocolSpecBuilder evmBuilder( } public ProtocolSpecBuilder transactionValidatorBuilder( - final Function transactionValidatorBuilder) { + final BiFunction + transactionValidatorBuilder) { this.transactionValidatorBuilder = transactionValidatorBuilder; return this; } @@ -290,7 +292,7 @@ public ProtocolSpec build(final HeaderBasedProtocolSchedule protocolSchedule) { final PrecompiledContractConfiguration precompiledContractConfiguration = new PrecompiledContractConfiguration(gasCalculator, privacyParameters); final MainnetTransactionValidator transactionValidator = - transactionValidatorBuilder.apply(gasCalculator); + transactionValidatorBuilder.apply(gasCalculator, gasLimitCalculator); final AbstractMessageProcessor contractCreationProcessor = contractCreationProcessorBuilder.apply(gasCalculator, evm); final PrecompileContractRegistry precompileContractRegistry = diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/CancunFeeMarket.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/CancunFeeMarket.java new file mode 100644 index 00000000000..fba11a96459 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/CancunFeeMarket.java @@ -0,0 +1,39 @@ +/* + * Copyright contributors to Hyperledger 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.feemarket; + +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.feemarket.TransactionPriceCalculator; + +import java.util.Optional; + +public class CancunFeeMarket extends LondonFeeMarket { + + private static final int MIN_DATA_GAS_PRICE = 1; + private static final int DATA_GAS_PRICE_UPDATE_FRACTION = 2225652; + + public CancunFeeMarket( + final long londonForkBlockNumber, final Optional baseFeePerGasOverride) { + super( + new TransactionPriceCalculator.DataBlob(MIN_DATA_GAS_PRICE, DATA_GAS_PRICE_UPDATE_FRACTION), + londonForkBlockNumber, + baseFeePerGasOverride); + } + + @Override + public boolean implementsDataFee() { + return true; + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/FeeMarket.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/FeeMarket.java index f0a5df1098c..a674e602b1c 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/FeeMarket.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/FeeMarket.java @@ -26,6 +26,10 @@ default boolean implementsBaseFee() { return false; } + default boolean implementsDataFee() { + return false; + } + TransactionPriceCalculator getTransactionPriceCalculator(); boolean satisfiesFloorTxFee(Transaction txn); @@ -39,6 +43,11 @@ static BaseFeeMarket london( return new LondonFeeMarket(londonForkBlockNumber, baseFeePerGasOverride); } + static BaseFeeMarket cancun( + final long londonForkBlockNumber, final Optional baseFeePerGasOverride) { + return new CancunFeeMarket(londonForkBlockNumber, baseFeePerGasOverride); + } + static BaseFeeMarket zeroBaseFee(final long londonForkBlockNumber) { return new ZeroBaseFeeMarket(londonForkBlockNumber); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LegacyFeeMarket.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LegacyFeeMarket.java index 8e73ec2948c..ec328d29824 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LegacyFeeMarket.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LegacyFeeMarket.java @@ -22,7 +22,7 @@ public class LegacyFeeMarket implements FeeMarket { private final TransactionPriceCalculator txPriceCalculator; public LegacyFeeMarket() { - this.txPriceCalculator = TransactionPriceCalculator.frontier(); + this.txPriceCalculator = new TransactionPriceCalculator.Frontier(); } @Override diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LondonFeeMarket.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LondonFeeMarket.java index 87d1fdffb81..98b23ad1945 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LondonFeeMarket.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LondonFeeMarket.java @@ -26,12 +26,14 @@ import org.slf4j.LoggerFactory; public class LondonFeeMarket implements BaseFeeMarket { + private static final Logger LOG = LoggerFactory.getLogger(LondonFeeMarket.class); + static final Wei DEFAULT_BASEFEE_INITIAL_VALUE = GenesisConfigFile.BASEFEE_AT_GENESIS_DEFAULT_VALUE; static final long DEFAULT_BASEFEE_MAX_CHANGE_DENOMINATOR = 8L; static final long DEFAULT_SLACK_COEFFICIENT = 2L; + private static final Wei DEFAULT_BASEFEE_FLOOR = Wei.of(7L); - private static final Logger LOG = LoggerFactory.getLogger(LondonFeeMarket.class); private final Wei baseFeeInitialValue; private final long londonForkBlockNumber; @@ -44,7 +46,14 @@ public LondonFeeMarket(final long londonForkBlockNumber) { public LondonFeeMarket( final long londonForkBlockNumber, final Optional baseFeePerGasOverride) { - this.txPriceCalculator = TransactionPriceCalculator.eip1559(); + this(new TransactionPriceCalculator.EIP1559(), londonForkBlockNumber, baseFeePerGasOverride); + } + + protected LondonFeeMarket( + final TransactionPriceCalculator txPriceCalculator, + final long londonForkBlockNumber, + final Optional baseFeePerGasOverride) { + this.txPriceCalculator = txPriceCalculator; this.londonForkBlockNumber = londonForkBlockNumber; this.baseFeeInitialValue = baseFeePerGasOverride.orElse(DEFAULT_BASEFEE_INITIAL_VALUE); this.baseFeeFloor = baseFeeInitialValue.isZero() ? Wei.ZERO : DEFAULT_BASEFEE_FLOOR; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ZeroBaseFeeMarket.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ZeroBaseFeeMarket.java index c2f406cad04..85d55ef429d 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ZeroBaseFeeMarket.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ZeroBaseFeeMarket.java @@ -38,4 +38,9 @@ public Wei computeBaseFee( public ValidationMode baseFeeValidationMode(final long blockNumber) { return ValidationMode.NONE; } + + @Override + public boolean implementsDataFee() { + return true; + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java index bc355b19ee8..bd2ad48e01f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java @@ -55,5 +55,6 @@ public enum TransactionInvalidReason { UPFRONT_FEE_TOO_HIGH, NONCE_TOO_FAR_IN_FUTURE_FOR_SENDER, INVALID_BLOBS, - LOWER_NONCE_INVALID_TRANSACTION_EXISTS + LOWER_NONCE_INVALID_TRANSACTION_EXISTS, + TOTAL_DATA_GAS_TOO_HIGH } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/TransactionEIP1559Test.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/TransactionEIP1559Test.java deleted file mode 100644 index 2a2211b9380..00000000000 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/TransactionEIP1559Test.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * 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.core; - -import org.hyperledger.besu.crypto.KeyPair; -import org.hyperledger.besu.crypto.SignatureAlgorithm; -import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; -import org.hyperledger.besu.ethereum.rlp.RLP; -import org.hyperledger.besu.evm.AccessListEntry; - -import java.math.BigInteger; -import java.util.List; - -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.junit.Test; - -public class TransactionEIP1559Test { - private static final Supplier SIGNATURE_ALGORITHM = - Suppliers.memoize(SignatureAlgorithmFactory::getInstance); - - @Test - public void buildEip1559Transaction() { - final List accessListEntries = - List.of( - new AccessListEntry( - Address.fromHexString("0x000000000000000000000000000000000000aaaa"), - List.of(Bytes32.ZERO))); - final Transaction tx = - Transaction.builder() - .chainId(new BigInteger("1559", 10)) - .nonce(0) - .value(Wei.ZERO) - .gasLimit(30000) - .maxPriorityFeePerGas(Wei.of(2)) - .payload(Bytes.EMPTY.trimLeadingZeros()) - .maxFeePerGas(Wei.of(new BigInteger("5000000000", 10))) - .gasPrice(null) - .to(Address.fromHexString("0x000000000000000000000000000000000000aaaa")) - .accessList(accessListEntries) - .guessType() - .signAndBuild( - keyPair("0x8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63")); - final BytesValueRLPOutput out = new BytesValueRLPOutput(); - tx.writeTo(out); - System.out.println(out.encoded().toHexString()); - System.out.println(tx.getUpfrontCost()); - // final String raw = - // "b8a902f8a686796f6c6f7632800285012a05f20082753094000000000000000000000000000000000000aaaa8080f838f794000000000000000000000000000000000000aaaae1a0000000000000000000000000000000000000000000000000000000000000000001a00c1d69648e348fe26155b45de45004f0e4195f6352d8f0935bc93e98a3e2a862a060064e5b9765c0ac74223b0cf49635c59ae0faf82044fd17bcc68a549ade6f95"; - final String raw = out.encoded().toHexString(); - final Transaction decoded = Transaction.readFrom(RLP.input(Bytes.fromHexString(raw))); - System.out.println(decoded); - System.out.println(decoded.getAccessList().orElseThrow().get(0).getAddressString()); - System.out.println(decoded.getAccessList().orElseThrow().get(0).getStorageKeysString()); - } - - private static KeyPair keyPair(final String privateKey) { - final SignatureAlgorithm signatureAlgorithm = SIGNATURE_ALGORITHM.get(); - return signatureAlgorithm.createKeyPair( - signatureAlgorithm.createPrivateKey(Bytes32.fromHexString(privateKey))); - } -} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculatorTest.java index 3caa7234908..e57c3a3ee1c 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculatorTest.java @@ -18,8 +18,11 @@ import static org.hyperledger.besu.plugin.data.TransactionType.ACCESS_LIST; import static org.hyperledger.besu.plugin.data.TransactionType.EIP1559; import static org.hyperledger.besu.plugin.data.TransactionType.FRONTIER; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.plugin.data.TransactionType; @@ -37,9 +40,9 @@ public class TransactionPriceCalculatorTest { private static final TransactionPriceCalculator FRONTIER_CALCULATOR = - TransactionPriceCalculator.frontier(); + new TransactionPriceCalculator.Frontier(); private static final TransactionPriceCalculator EIP_1559_CALCULATOR = - TransactionPriceCalculator.eip1559(); + new TransactionPriceCalculator.EIP1559(); private final TransactionPriceCalculator transactionPriceCalculator; private final TransactionType transactionType; @@ -133,6 +136,9 @@ public static Collection data() { @Test public void assertThatCalculatorWorks() { + final ProcessableBlockHeader blockHeader = mock(ProcessableBlockHeader.class); + when(blockHeader.getBaseFee()).thenReturn(baseFee); + assertThat( transactionPriceCalculator.price( Transaction.builder() @@ -143,7 +149,7 @@ public void assertThatCalculatorWorks() { .maxFeePerGas(maxFeePerGas) .chainId(BigInteger.ONE) .build(), - baseFee)) + blockHeader.getBaseFee())) .isEqualByComparingTo(expectedPrice); } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidatorTest.java index db24e7dfbfd..285735483a8 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidatorTest.java @@ -33,6 +33,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.GasLimitCalculator; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionFilter; import org.hyperledger.besu.ethereum.core.TransactionTestFixture; @@ -78,7 +79,11 @@ public class MainnetTransactionValidatorTest { public void shouldRejectTransactionIfIntrinsicGasExceedsGasLimit() { final MainnetTransactionValidator validator = new MainnetTransactionValidator( - gasCalculator, false, Optional.empty(), defaultGoQuorumCompatibilityMode); + gasCalculator, + GasLimitCalculator.constant(), + false, + Optional.empty(), + defaultGoQuorumCompatibilityMode); final Transaction transaction = new TransactionTestFixture() .gasLimit(10) @@ -95,7 +100,11 @@ public void shouldRejectTransactionIfIntrinsicGasExceedsGasLimit() { public void shouldRejectTransactionWhenTransactionHasChainIdAndValidatorDoesNot() { final MainnetTransactionValidator validator = new MainnetTransactionValidator( - gasCalculator, false, Optional.empty(), defaultGoQuorumCompatibilityMode); + gasCalculator, + GasLimitCalculator.constant(), + false, + Optional.empty(), + defaultGoQuorumCompatibilityMode); assertThat(validator.validate(basicTransaction, Optional.empty(), transactionValidationParams)) .isEqualTo( ValidationResult.invalid( @@ -107,6 +116,7 @@ public void shouldRejectTransactionWhenTransactionHasIncorrectChainId() { final MainnetTransactionValidator validator = new MainnetTransactionValidator( gasCalculator, + GasLimitCalculator.constant(), false, Optional.of(BigInteger.valueOf(2)), defaultGoQuorumCompatibilityMode); @@ -118,7 +128,11 @@ public void shouldRejectTransactionWhenTransactionHasIncorrectChainId() { public void shouldRejectTransactionWhenSenderAccountDoesNotExist() { final MainnetTransactionValidator validator = new MainnetTransactionValidator( - gasCalculator, false, Optional.of(BigInteger.ONE), defaultGoQuorumCompatibilityMode); + gasCalculator, + GasLimitCalculator.constant(), + false, + Optional.of(BigInteger.ONE), + defaultGoQuorumCompatibilityMode); assertThat(validator.validateForSender(basicTransaction, null, false)) .isEqualTo(ValidationResult.invalid(TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE)); } @@ -127,7 +141,11 @@ public void shouldRejectTransactionWhenSenderAccountDoesNotExist() { public void shouldRejectTransactionWhenTransactionNonceBelowAccountNonce() { final MainnetTransactionValidator validator = new MainnetTransactionValidator( - gasCalculator, false, Optional.of(BigInteger.ONE), defaultGoQuorumCompatibilityMode); + gasCalculator, + GasLimitCalculator.constant(), + false, + Optional.of(BigInteger.ONE), + defaultGoQuorumCompatibilityMode); final Account account = accountWithNonce(basicTransaction.getNonce() + 1); assertThat(validator.validateForSender(basicTransaction, account, false)) @@ -139,7 +157,11 @@ public void shouldRejectTransactionWhenTransactionNonceBelowAccountNonce() { shouldRejectTransactionWhenTransactionNonceAboveAccountNonceAndFutureNonceIsNotAllowed() { final MainnetTransactionValidator validator = new MainnetTransactionValidator( - gasCalculator, false, Optional.of(BigInteger.ONE), defaultGoQuorumCompatibilityMode); + gasCalculator, + GasLimitCalculator.constant(), + false, + Optional.of(BigInteger.ONE), + defaultGoQuorumCompatibilityMode); final Account account = accountWithNonce(basicTransaction.getNonce() - 1); assertThat(validator.validateForSender(basicTransaction, account, false)) @@ -151,7 +173,11 @@ public void shouldRejectTransactionWhenTransactionNonceBelowAccountNonce() { shouldAcceptTransactionWhenTransactionNonceAboveAccountNonceAndFutureNonceIsAllowed() { final MainnetTransactionValidator validator = new MainnetTransactionValidator( - gasCalculator, false, Optional.of(BigInteger.ONE), defaultGoQuorumCompatibilityMode); + gasCalculator, + GasLimitCalculator.constant(), + false, + Optional.of(BigInteger.ONE), + defaultGoQuorumCompatibilityMode); final Account account = accountWithNonce(basicTransaction.getNonce() - 1); assertThat(validator.validateForSender(basicTransaction, account, true)) @@ -162,7 +188,11 @@ public void shouldRejectTransactionWhenTransactionNonceBelowAccountNonce() { public void shouldRejectTransactionWhenNonceExceedsMaximumAllowedNonce() { final MainnetTransactionValidator validator = new MainnetTransactionValidator( - gasCalculator, false, Optional.of(BigInteger.ONE), defaultGoQuorumCompatibilityMode); + gasCalculator, + GasLimitCalculator.constant(), + false, + Optional.of(BigInteger.ONE), + defaultGoQuorumCompatibilityMode); final Transaction transaction = new TransactionTestFixture().nonce(11).createTransaction(senderKeys); @@ -176,7 +206,11 @@ public void shouldRejectTransactionWhenNonceExceedsMaximumAllowedNonce() { public void transactionWithNullSenderCanBeValidIfGasPriceAndValueIsZero() { final MainnetTransactionValidator validator = new MainnetTransactionValidator( - gasCalculator, false, Optional.of(BigInteger.ONE), defaultGoQuorumCompatibilityMode); + gasCalculator, + GasLimitCalculator.constant(), + false, + Optional.of(BigInteger.ONE), + defaultGoQuorumCompatibilityMode); final TransactionTestFixture builder = new TransactionTestFixture(); final KeyPair senderKeyPair = SIGNATURE_ALGORITHM.get().generateKeyPair(); @@ -191,11 +225,16 @@ public void transactionWithNullSenderCanBeValidIfGasPriceAndValueIsZero() { public void shouldRejectTransactionIfAccountIsNotEOA() { final MainnetTransactionValidator validator = new MainnetTransactionValidator( - gasCalculator, false, Optional.empty(), defaultGoQuorumCompatibilityMode); + gasCalculator, + GasLimitCalculator.constant(), + false, + Optional.empty(), + defaultGoQuorumCompatibilityMode); validator.setTransactionFilter(transactionFilter(false)); Account invalidEOA = - when(account(basicTransaction.getUpfrontCost(), basicTransaction.getNonce()).getCodeHash()) + when(account(basicTransaction.getUpfrontCost(0L), basicTransaction.getNonce()) + .getCodeHash()) .thenReturn(Hash.fromHexStringLenient("0xdeadbeef")) .getMock(); @@ -207,7 +246,11 @@ public void shouldRejectTransactionIfAccountIsNotEOA() { public void shouldRejectTransactionIfAccountIsNotPermitted() { final MainnetTransactionValidator validator = new MainnetTransactionValidator( - gasCalculator, false, Optional.empty(), defaultGoQuorumCompatibilityMode); + gasCalculator, + GasLimitCalculator.constant(), + false, + Optional.empty(), + defaultGoQuorumCompatibilityMode); validator.setTransactionFilter(transactionFilter(false)); assertThat(validator.validateForSender(basicTransaction, accountWithNonce(0), true)) @@ -218,7 +261,11 @@ public void shouldRejectTransactionIfAccountIsNotPermitted() { public void shouldAcceptValidTransactionIfAccountIsPermitted() { final MainnetTransactionValidator validator = new MainnetTransactionValidator( - gasCalculator, false, Optional.empty(), defaultGoQuorumCompatibilityMode); + gasCalculator, + GasLimitCalculator.constant(), + false, + Optional.empty(), + defaultGoQuorumCompatibilityMode); validator.setTransactionFilter(transactionFilter(true)); assertThat(validator.validateForSender(basicTransaction, accountWithNonce(0), true)) @@ -229,7 +276,11 @@ public void shouldAcceptValidTransactionIfAccountIsPermitted() { public void shouldRejectTransactionWithMaxFeeTimesGasLimitGreaterThanBalance() { final MainnetTransactionValidator validator = new MainnetTransactionValidator( - gasCalculator, false, Optional.empty(), defaultGoQuorumCompatibilityMode); + gasCalculator, + GasLimitCalculator.constant(), + false, + Optional.empty(), + defaultGoQuorumCompatibilityMode); validator.setTransactionFilter(transactionFilter(true)); assertThat( @@ -255,6 +306,7 @@ public void shouldRejectTransactionWithMaxPriorityFeeGreaterThanMaxFee() { final MainnetTransactionValidator validator = new MainnetTransactionValidator( gasCalculator, + GasLimitCalculator.constant(), FeeMarket.london(0L), false, Optional.of(BigInteger.ONE), @@ -302,7 +354,11 @@ public void shouldPropagateCorrectStateChangeParamToTransactionFilter() { final MainnetTransactionValidator validator = new MainnetTransactionValidator( - gasCalculator, false, Optional.empty(), defaultGoQuorumCompatibilityMode); + gasCalculator, + GasLimitCalculator.constant(), + false, + Optional.empty(), + defaultGoQuorumCompatibilityMode); validator.setTransactionFilter(transactionFilter); final TransactionValidationParams validationParams = @@ -320,7 +376,11 @@ public void shouldNotCheckAccountPermissionIfBothValidationParamsCheckPermission final MainnetTransactionValidator validator = new MainnetTransactionValidator( - gasCalculator, false, Optional.empty(), defaultGoQuorumCompatibilityMode); + gasCalculator, + GasLimitCalculator.constant(), + false, + Optional.empty(), + defaultGoQuorumCompatibilityMode); validator.setTransactionFilter(transactionFilter); final TransactionValidationParams validationParams = @@ -342,6 +402,7 @@ public void shouldAcceptOnlyTransactionsInAcceptedTransactionTypes() { final MainnetTransactionValidator frontierValidator = new MainnetTransactionValidator( gasCalculator, + GasLimitCalculator.constant(), FeeMarket.legacy(), false, Optional.of(BigInteger.ONE), @@ -352,6 +413,7 @@ public void shouldAcceptOnlyTransactionsInAcceptedTransactionTypes() { final MainnetTransactionValidator eip1559Validator = new MainnetTransactionValidator( gasCalculator, + GasLimitCalculator.constant(), FeeMarket.london(0L), false, Optional.of(BigInteger.ONE), @@ -385,6 +447,7 @@ public void shouldRejectTransactionIfEIP1559TransactionGasPriceLessBaseFee() { final MainnetTransactionValidator validator = new MainnetTransactionValidator( gasCalculator, + GasLimitCalculator.constant(), FeeMarket.london(0L), false, Optional.of(BigInteger.ONE), @@ -409,6 +472,7 @@ public void shouldAcceptZeroGasPriceTransactionIfBaseFeeIsZero() { final MainnetTransactionValidator validator = new MainnetTransactionValidator( gasCalculator, + GasLimitCalculator.constant(), FeeMarket.london(0L, zeroBaseFee), false, Optional.of(BigInteger.ONE), @@ -432,6 +496,7 @@ public void shouldAcceptValidEIP1559() { final MainnetTransactionValidator validator = new MainnetTransactionValidator( gasCalculator, + GasLimitCalculator.constant(), FeeMarket.london(0L), false, Optional.of(BigInteger.ONE), @@ -457,6 +522,7 @@ public void shouldValidate1559TransactionWithPriceLowerThanBaseFeeForTransaction final MainnetTransactionValidator validator = new MainnetTransactionValidator( gasCalculator, + GasLimitCalculator.constant(), FeeMarket.london(0L), false, Optional.of(BigInteger.ONE), @@ -483,6 +549,7 @@ public void shouldRejectTooLargeInitcode() { final MainnetTransactionValidator validator = new MainnetTransactionValidator( gasCalculator, + GasLimitCalculator.constant(), FeeMarket.london(0L), false, Optional.of(BigInteger.ONE), @@ -508,7 +575,8 @@ public void shouldRejectTooLargeInitcode() { @Test public void goQuorumCompatibilityModeRejectNonZeroGasPrice() { final MainnetTransactionValidator validator = - new MainnetTransactionValidator(gasCalculator, false, Optional.empty(), true); + new MainnetTransactionValidator( + gasCalculator, GasLimitCalculator.constant(), false, Optional.empty(), true); final Transaction transaction = new TransactionTestFixture() .gasPrice(Wei.ONE) @@ -530,7 +598,8 @@ public void goQuorumCompatibilityModeRejectNonZeroGasPrice() { @Test public void goQuorumCompatibilityModeSuccessZeroGasPrice() { final MainnetTransactionValidator validator = - new MainnetTransactionValidator(gasCalculator, false, Optional.empty(), true); + new MainnetTransactionValidator( + gasCalculator, GasLimitCalculator.constant(), false, Optional.empty(), true); final Transaction transaction = new TransactionTestFixture() .gasPrice(Wei.ZERO) @@ -547,7 +616,7 @@ public void goQuorumCompatibilityModeSuccessZeroGasPrice() { } private Account accountWithNonce(final long nonce) { - return account(basicTransaction.getUpfrontCost(), nonce); + return account(basicTransaction.getUpfrontCost(0L), nonce); } private Account account(final Wei balance, final long nonce) { diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ZeroBaseFeeMarketTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ZeroBaseFeeMarketTest.java index 052044b7018..74776da3ef2 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ZeroBaseFeeMarketTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ZeroBaseFeeMarketTest.java @@ -15,10 +15,13 @@ package org.hyperledger.besu.ethereum.mainnet.feemarket; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import org.hyperledger.besu.crypto.KeyPair; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionTestFixture; import org.hyperledger.besu.plugin.data.TransactionType; @@ -67,10 +70,14 @@ public void getTransactionPriceCalculatorShouldBeEIP1559() { .maxPriorityFeePerGas(Optional.of(Wei.of(8))) .gasPrice(null) .createTransaction(KEY_PAIR1); + + final ProcessableBlockHeader blockHeader = mock(ProcessableBlockHeader.class); + when(blockHeader.getBaseFee()).thenReturn(Optional.of(Wei.ZERO)); + assertThat( zeroBaseFeeMarket .getTransactionPriceCalculator() - .price(transaction, Optional.of(Wei.ZERO))) + .price(transaction, blockHeader.getBaseFee())) .isEqualTo(Wei.of(8)); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementByFeeMarketRule.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementByFeeMarketRule.java index fe9a0b7ca83..1c2bd84c027 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementByFeeMarketRule.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementByFeeMarketRule.java @@ -17,7 +17,6 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.feemarket.TransactionPriceCalculator; -import org.hyperledger.besu.plugin.data.TransactionType; import org.hyperledger.besu.util.number.Percentage; import java.util.Optional; @@ -25,9 +24,9 @@ public class TransactionReplacementByFeeMarketRule implements TransactionPoolReplacementRule { private static final TransactionPriceCalculator FRONTIER_CALCULATOR = - TransactionPriceCalculator.frontier(); + new TransactionPriceCalculator.Frontier(); private static final TransactionPriceCalculator EIP1559_CALCULATOR = - TransactionPriceCalculator.eip1559(); + new TransactionPriceCalculator.EIP1559(); private final Percentage priceBump; public TransactionReplacementByFeeMarketRule(final Percentage priceBump) { @@ -38,26 +37,26 @@ public TransactionReplacementByFeeMarketRule(final Percentage priceBump) { public boolean shouldReplace( final PendingTransaction existingPendingTransaction, final PendingTransaction newPendingTransaction, - final Optional baseFee) { + final Optional maybeBaseFee) { // bail early if basefee is absent or neither transaction supports 1559 fee market - if (baseFee.isEmpty() + if (maybeBaseFee.isEmpty() || !(isNotGasPriced(existingPendingTransaction) || isNotGasPriced(newPendingTransaction))) { return false; } - Wei newEffPrice = priceOf(newPendingTransaction.getTransaction(), baseFee); + Wei newEffPrice = priceOf(newPendingTransaction.getTransaction(), maybeBaseFee); Wei newEffPriority = - newPendingTransaction.getTransaction().getEffectivePriorityFeePerGas(baseFee); + newPendingTransaction.getTransaction().getEffectivePriorityFeePerGas(maybeBaseFee); // bail early if price is not strictly positive if (newEffPrice.equals(Wei.ZERO)) { return false; } - Wei curEffPrice = priceOf(existingPendingTransaction.getTransaction(), baseFee); + Wei curEffPrice = priceOf(existingPendingTransaction.getTransaction(), maybeBaseFee); Wei curEffPriority = - existingPendingTransaction.getTransaction().getEffectivePriorityFeePerGas(baseFee); + existingPendingTransaction.getTransaction().getEffectivePriorityFeePerGas(maybeBaseFee); if (isBumpedBy(curEffPrice, newEffPrice, priceBump)) { // if effective price is bumped by percent: @@ -71,12 +70,10 @@ public boolean shouldReplace( return false; } - private Wei priceOf(final Transaction transaction, final Optional baseFee) { + private Wei priceOf(final Transaction transaction, final Optional maybeBaseFee) { final TransactionPriceCalculator transactionPriceCalculator = - transaction.getType().equals(TransactionType.EIP1559) - ? EIP1559_CALCULATOR - : FRONTIER_CALCULATOR; - return transactionPriceCalculator.price(transaction, baseFee); + transaction.getType().supports1559FeeMarket() ? EIP1559_CALCULATOR : FRONTIER_CALCULATOR; + return transactionPriceCalculator.price(transaction, maybeBaseFee); } private boolean isBumpedBy(final Wei val, final Wei bumpVal, final Percentage percent) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/CancunGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/CancunGasCalculator.java new file mode 100644 index 00000000000..e0c19690f59 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/CancunGasCalculator.java @@ -0,0 +1,46 @@ +/* + * Copyright contributors to Hyperledger 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.evm.gascalculator; + +/** The Cancun gas calculator as defined in EIP-4844 */ +public class CancunGasCalculator extends LondonGasCalculator { + + private static final long CANCUN_DATA_GAS_PER_BLOB = 1 << 17; + private static final long CANCUN_TARGET_DATA_GAS_PER_BLOCK = 1 << 18; + + @Override + public long dataGasCost(final int blobCount) { + return CANCUN_DATA_GAS_PER_BLOB * blobCount; + } + + /** + * Compute the new excess data gas for the block, using the parent value and the number of new + * blobs + * + * @param parentExcessDataGas the excess data gas value from the parent block + * @param newBlobs the number of blobs in the new block + * @return the new excess data gas value + */ + @Override + public long computeExcessDataGas(final long parentExcessDataGas, final int newBlobs) { + final long consumedDataGas = CANCUN_DATA_GAS_PER_BLOB * newBlobs; + final long currentExcessDataGas = parentExcessDataGas + consumedDataGas; + + if (currentExcessDataGas < CANCUN_TARGET_DATA_GAS_PER_BLOCK) { + return 0L; + } + return currentExcessDataGas - CANCUN_TARGET_DATA_GAS_PER_BLOCK; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java index 52fb63ea6a9..7758e72a873 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java @@ -438,7 +438,7 @@ default long modExpGasCost(final Bytes input) { long codeDepositGasCost(int codeSize); /** - * Returns the intrinsic gas cost of a transaction pauload, i.e. the cost deriving from its + * Returns the intrinsic gas cost of a transaction payload, i.e. the cost deriving from its * encoded binary representation when stored on-chain. * * @param transactionPayload The encoded transaction, as bytes @@ -487,4 +487,26 @@ default long getMaxRefundQuotient() { */ // what would be the gas for a PMT with hash of all non-zeros long getMaximumTransactionCost(int size); + + /** + * Return the gas cost given the number of blobs + * + * @param blobCount the number of blobs + * @return the total gas cost + */ + default long dataGasCost(final int blobCount) { + return 0L; + } + + /** + * Compute the new value for the excess data gas, given the parent value and the count of new + * blobs + * + * @param parentExcessDataGas excess data gas from the parent + * @param newBlobs count of new blobs + * @return the new excess data gas value + */ + default long computeExcessDataGas(final long parentExcessDataGas, final int newBlobs) { + return 0L; + } }