diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f16e375bf1..3053578eb03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ tests are updated to use EC private keys instead of RSA keys. - Support for EIP-4895 - Withdrawals for Shanghai fork - If a PoS block creation repetition takes less than a configurable duration, then waits before next repetition [#5048](https://github.com/hyperledger/besu/pull/5048) - Added the option --kzg-trusted-setup to pass a custom setup file for custom networks or to override the default one for named networks [#5084](https://github.com/hyperledger/besu/pull/5084) +- 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/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinatorTest.java b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinatorTest.java index c3a9637996f..48f4d66fa97 100644 --- a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinatorTest.java +++ b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinatorTest.java @@ -26,7 +26,6 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -437,7 +436,8 @@ public void shouldRetryBlockCreationOnRecoverableError() .getTransactions() .isEmpty()) { // this is called by the first empty block - doThrow(new MerkleTrieException("lock")) // first fail + doCallRealMethod() // first work + .doThrow(new MerkleTrieException("lock")) // second fail .doCallRealMethod() // then work .when(blockchain) .getBlockHeader(any()); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcErrorConverter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcErrorConverter.java index 37831775606..4f3777fc3d5 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcErrorConverter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcErrorConverter.java @@ -67,6 +67,8 @@ public static JsonRpcError convertTransactionInvalidReason( return JsonRpcError.NONCE_TOO_FAR_IN_FUTURE_FOR_SENDER; case LOWER_NONCE_INVALID_TRANSACTION_EXISTS: return JsonRpcError.LOWER_NONCE_INVALID_TRANSACTION_EXISTS; + case TOTAL_DATA_GAS_TOO_HIGH: + return JsonRpcError.TOTAL_DATA_GAS_TOO_HIGH; default: return JsonRpcError.INVALID_PARAMS; } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStorageRangeAt.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStorageRangeAt.java index 488ea999ea5..3c5db065ee7 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStorageRangeAt.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStorageRangeAt.java @@ -96,7 +96,12 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { .afterTransactionInBlock( blockHash, transactionWithMetadata.getTransaction().getHash(), - (transaction, blockHeader, blockchain, worldState, transactionProcessor) -> + (transaction, + blockHeader, + blockchain, + worldState, + transactionProcessor, + protocolSpec) -> extractStorageAt( requestContext, accountAddress, startKey, limit, worldState)) .orElseGet(() -> emptyResponse(requestContext)))) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockReplay.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockReplay.java index 57e115d9b39..8516c71eb7e 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockReplay.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockReplay.java @@ -14,7 +14,9 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor; +import org.hyperledger.besu.datatypes.DataGas; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockBody; @@ -52,8 +54,17 @@ public Optional block( return performActionWithBlock( block.getHeader(), block.getBody(), - (body, header, blockchain, mutableWorldState, transactionProcessor) -> { - List transactionTraces = + (body, header, blockchain, mutableWorldState, transactionProcessor, protocolSpec) -> { + final Wei dataGasPrice = + protocolSpec + .getFeeMarket() + .dataPrice( + blockchain + .getBlockHeader(header.getParentHash()) + .flatMap(BlockHeader::getExcessDataGas) + .orElse(DataGas.ZERO)); + + final List transactionTraces = body.getTransactions().stream() .map( transaction -> @@ -62,7 +73,8 @@ public Optional block( header, blockchain, mutableWorldState, - transactionProcessor)) + transactionProcessor, + dataGasPrice)) .collect(Collectors.toList()); return Optional.of(new BlockTrace(transactionTraces)); }); @@ -77,24 +89,38 @@ public Optional beforeTransactionInBlock( final Hash blockHash, final Hash transactionHash, final TransactionAction action) { return performActionWithBlock( blockHash, - (body, header, blockchain, mutableWorldState, transactionProcessor) -> { + (body, header, blockchain, mutableWorldState, transactionProcessor, protocolSpec) -> { final BlockHashLookup blockHashLookup = new BlockHashLookup(header, blockchain); + final Wei dataGasPrice = + protocolSpec + .getFeeMarket() + .dataPrice( + blockchain + .getBlockHeader(header.getParentHash()) + .flatMap(BlockHeader::getExcessDataGas) + .orElse(DataGas.ZERO)); + for (final Transaction transaction : body.getTransactions()) { if (transaction.getHash().equals(transactionHash)) { return Optional.of( action.performAction( - transaction, header, blockchain, mutableWorldState, transactionProcessor)); + transaction, + header, + blockchain, + mutableWorldState, + transactionProcessor, + dataGasPrice)); } else { - final ProtocolSpec spec = protocolSchedule.getByBlockHeader(header); transactionProcessor.processTransaction( blockchain, mutableWorldState.updater(), header, transaction, - spec.getMiningBeneficiaryCalculator().calculateBeneficiary(header), + protocolSpec.getMiningBeneficiaryCalculator().calculateBeneficiary(header), blockHashLookup, false, - TransactionValidationParams.blockReplay()); + TransactionValidationParams.blockReplay(), + dataGasPrice); } } return Optional.empty(); @@ -106,7 +132,7 @@ public Optional afterTransactionInBlock( return beforeTransactionInBlock( blockHash, transactionHash, - (transaction, blockHeader, blockchain, worldState, transactionProcessor) -> { + (transaction, blockHeader, blockchain, worldState, transactionProcessor, dataGasPrice) -> { final ProtocolSpec spec = protocolSchedule.getByBlockHeader(blockHeader); transactionProcessor.processTransaction( blockchain, @@ -116,9 +142,10 @@ public Optional afterTransactionInBlock( spec.getMiningBeneficiaryCalculator().calculateBeneficiary(blockHeader), new BlockHashLookup(blockHeader, blockchain), false, - TransactionValidationParams.blockReplay()); + TransactionValidationParams.blockReplay(), + dataGasPrice); return action.performAction( - transaction, blockHeader, blockchain, worldState, transactionProcessor); + transaction, blockHeader, blockchain, worldState, transactionProcessor, dataGasPrice); }); } @@ -160,7 +187,8 @@ private Optional performActionWithBlock( new IllegalArgumentException( "Missing worldstate for stateroot " + previous.getStateRoot().toShortHexString()))) { - return action.perform(body, header, blockchain, worldState, transactionProcessor); + return action.perform( + body, header, blockchain, worldState, transactionProcessor, protocolSpec); } catch (Exception ex) { return Optional.empty(); } @@ -190,7 +218,8 @@ Optional perform( BlockHeader blockHeader, Blockchain blockchain, MutableWorldState worldState, - MainnetTransactionProcessor transactionProcessor); + MainnetTransactionProcessor transactionProcessor, + ProtocolSpec protocolSpec); } @FunctionalInterface @@ -200,6 +229,7 @@ T performAction( BlockHeader blockHeader, Blockchain blockchain, MutableWorldState worldState, - MainnetTransactionProcessor transactionProcessor); + MainnetTransactionProcessor transactionProcessor, + Wei dataGasPrice); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockTracer.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockTracer.java index b3eca1e4dba..4dc4157b66f 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockTracer.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockTracer.java @@ -47,7 +47,12 @@ public Optional trace(final Block block, final DebugOperationTracer private BlockReplay.TransactionAction prepareReplayAction( final DebugOperationTracer tracer) { - return (transaction, header, blockchain, mutableWorldState, transactionProcessor) -> { + return (transaction, + header, + blockchain, + mutableWorldState, + transactionProcessor, + dataGasPrice) -> { // if we have no prior updater, it must be the first TX, so use the block's initial state if (chainedUpdater == null) { chainedUpdater = mutableWorldState.updater(); @@ -65,7 +70,8 @@ private BlockReplay.TransactionAction prepareReplayAction( header.getCoinbase(), tracer, new BlockHashLookup(header, blockchain), - false); + false, + dataGasPrice); final List traceFrames = tracer.copyTraceFrames(); tracer.reset(); return new TransactionTrace(transaction, result, traceFrames); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracer.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracer.java index b00f68db54e..f99506eea01 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracer.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracer.java @@ -16,7 +16,9 @@ import static java.util.function.Predicate.isEqual; +import org.hyperledger.besu.datatypes.DataGas; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.TransactionTraceParams; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -65,7 +67,7 @@ public Optional traceTransaction( return blockReplay.beforeTransactionInBlock( blockHash, transactionHash, - (transaction, header, blockchain, worldState, transactionProcessor) -> { + (transaction, header, blockchain, worldState, transactionProcessor, dataGasPrice) -> { final TransactionProcessingResult result = processTransaction( header, @@ -73,7 +75,8 @@ public Optional traceTransaction( worldState.updater(), transaction, transactionProcessor, - tracer); + tracer, + dataGasPrice); return new TransactionTrace(transaction, result, tracer.getTraceFrames()); }); } @@ -101,9 +104,17 @@ public List traceTransactionToFile( return blockReplay .performActionWithBlock( blockHash, - (body, header, blockchain, worldState, transactionProcessor) -> { + (body, header, blockchain, worldState, transactionProcessor, protocolSpec) -> { WorldUpdater stackedUpdater = worldState.updater().updater(); final List traces = new ArrayList<>(); + final Wei dataGasPrice = + protocolSpec + .getFeeMarket() + .dataPrice( + blockchain + .getBlockHeader(header.getParentHash()) + .flatMap(BlockHeader::getExcessDataGas) + .orElse(DataGas.ZERO)); for (int i = 0; i < body.getTransactions().size(); i++) { ((StackedUpdater) stackedUpdater).markTransactionBoundary(); final Transaction transaction = body.getTransactions().get(i); @@ -119,7 +130,8 @@ public List traceTransactionToFile( stackedUpdater, transaction, transactionProcessor, - new StandardJsonTracer(out, showMemory, true, true)); + new StandardJsonTracer(out, showMemory, true, true), + dataGasPrice); out.println( summaryTrace( transaction, timer.stop().elapsed(TimeUnit.NANOSECONDS), result)); @@ -135,7 +147,8 @@ public List traceTransactionToFile( stackedUpdater, transaction, transactionProcessor, - OperationTracer.NO_TRACING); + OperationTracer.NO_TRACING, + dataGasPrice); } } return Optional.of(traces); @@ -165,7 +178,9 @@ private TransactionProcessingResult processTransaction( final WorldUpdater worldUpdater, final Transaction transaction, final MainnetTransactionProcessor transactionProcessor, - final OperationTracer tracer) { + final OperationTracer tracer, + final Wei dataGasPrice) { + return transactionProcessor.processTransaction( blockchain, worldUpdater, @@ -175,7 +190,8 @@ private TransactionProcessingResult processTransaction( tracer, new BlockHashLookup(header, blockchain), false, - ImmutableTransactionValidationParams.builder().isAllowFutureNonce(true).build()); + ImmutableTransactionValidationParams.builder().isAllowFutureNonce(true).build(), + dataGasPrice); } public static String summaryTrace( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcError.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcError.java index 8349f93afd6..ef205ecea27 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcError.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcError.java @@ -77,6 +77,7 @@ public enum JsonRpcError { -32000, "Transaction nonce is too distant from current sender nonce"), LOWER_NONCE_INVALID_TRANSACTION_EXISTS( -32000, "An invalid transaction with a lower nonce exists"), + TOTAL_DATA_GAS_TOO_HIGH(-32000, "Total data gas too high"), // Execution engine failures UNKNOWN_PAYLOAD(-32001, "Payload does not exist / is not available"), diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStorageRangeAtTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStorageRangeAtTest.java index ed01d89ab08..488f0d05fe6 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStorageRangeAtTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStorageRangeAtTest.java @@ -23,6 +23,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockReplay; @@ -155,6 +156,7 @@ private Object callAction(final InvocationOnMock invocation) { //noinspection rawtypes return Optional.of( ((BlockReplay.TransactionAction) invocation.getArgument(2)) - .performAction(transaction, blockHeader, blockchain, worldState, transactionProcessor)); + .performAction( + transaction, blockHeader, blockchain, worldState, transactionProcessor, Wei.ZERO)); } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracerTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracerTest.java index af79442fd67..0728e53f2f7 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracerTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracerTest.java @@ -21,6 +21,7 @@ import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.ImmutableTransactionTraceParams; import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.Blockchain; @@ -32,6 +33,7 @@ import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.vm.DebugOperationTracer; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; @@ -115,6 +117,7 @@ public void setUp() throws Exception { when(protocolSchedule.getByBlockHeader(blockHeader)).thenReturn(protocolSpec); when(protocolSpec.getTransactionProcessor()).thenReturn(transactionProcessor); when(protocolSpec.getMiningBeneficiaryCalculator()).thenReturn(BlockHeader::getCoinbase); + when(protocolSpec.getFeeMarket()).thenReturn(FeeMarket.london(0L)); when(blockchain.getChainHeadHeader()).thenReturn(blockHeader); when(protocolSpec.getBadBlocksManager()).thenReturn(new BadBlockManager()); when(mutableWorldState.copy()).thenReturn(mutableWorldState); @@ -180,7 +183,8 @@ public void traceTransactionShouldReturnResultFromProcessTransaction() { eq(tracer), any(), any(), - any())) + any(), + eq(Wei.ZERO))) .thenReturn(result); final Optional transactionTrace = @@ -267,7 +271,8 @@ public void traceTransactionToFileShouldReturnResultFromProcessTransaction() thr any(StandardJsonTracer.class), any(), any(), - any())) + any(), + eq(Wei.ZERO))) .thenReturn(result); final List transactionTraces = 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..d5377cb8a34 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,6 +15,7 @@ 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.ProtocolContext; @@ -43,6 +44,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; @@ -166,6 +168,10 @@ protected BlockCreationResult createBlock( createPendingBlockHeader(timestamp, maybePrevRandao, newProtocolSpec); final Address miningBeneficiary = miningBeneficiaryCalculator.getMiningBeneficiary(processableBlockHeader.getNumber()); + final Wei dataGasPrice = + newProtocolSpec + .getFeeMarket() + .dataPrice(parentHeader.getExcessDataGas().orElse(DataGas.ZERO)); throwIfStopped(); @@ -178,6 +184,7 @@ protected BlockCreationResult createBlock( disposableWorldState, maybeTransactions, miningBeneficiary, + dataGasPrice, newProtocolSpec); throwIfStopped(); @@ -209,6 +216,10 @@ protected BlockCreationResult createBlock( throwIfStopped(); + final DataGas newExcessDataGas = computeExcessDataGas(transactionResults, newProtocolSpec); + + throwIfStopped(); + final SealableBlockHeader sealableBlockHeader = BlockHeaderBuilder.create() .populateFrom(processableBlockHeader) @@ -224,6 +235,7 @@ protected BlockCreationResult createBlock( withdrawalsCanBeProcessed ? BodyValidation.withdrawalsRoot(maybeWithdrawals.get()) : null) + .excessDataGas(newExcessDataGas) .buildSealableBlockHeader(); final BlockHeader blockHeader = createFinalBlockHeader(sealableBlockHeader); @@ -245,11 +257,32 @@ 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, final Optional> transactions, final Address miningBeneficiary, + final Wei dataGasPrice, final ProtocolSpec protocolSpec) throws RuntimeException { final MainnetTransactionProcessor transactionProcessor = protocolSpec.getTransactionProcessor(); @@ -269,7 +302,10 @@ private BlockTransactionSelector.TransactionSelectionResults selectTransactions( minBlockOccupancyRatio, isCancelled::get, miningBeneficiary, - protocolSpec.getFeeMarket()); + dataGasPrice, + protocolSpec.getFeeMarket(), + protocolSpec.getGasCalculator(), + protocolSpec.getGasLimitCalculator()); if (transactions.isPresent()) { return selector.evaluateTransactions(transactions.get()); 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..a8b9055179a 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; @@ -70,11 +77,6 @@ * not cleared between executions of buildTransactionListForBlock(). */ public class BlockTransactionSelector { - private static final Logger LOG = LoggerFactory.getLogger(BlockTransactionSelector.class); - - private final Wei minTransactionGasPrice; - private final Double minBlockOccupancyRatio; - public static class TransactionValidationResult { private final Transaction transaction; private final ValidationResult validationResult; @@ -115,22 +117,30 @@ public int hashCode() { public static class TransactionSelectionResults { - private final List transactions = Lists.newArrayList(); + 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) { - transactions.add(transaction); + 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 {}", + "New selected transaction {}, total transactions {}, cumulative gas used {}, cumulative data gas used {}", transaction::toTraceLog, - transactions::size, - () -> cumulativeGasUsed); + () -> transactionsByType.values().stream().mapToInt(List::size).sum(), + () -> cumulativeGasUsed, + () -> cumulativeDataGasUsed); } private void updateWithInvalidTransaction( @@ -140,7 +150,11 @@ private void updateWithInvalidTransaction( } public List getTransactions() { - return transactions; + return streamAllTransactions().collect(Collectors.toList()); + } + + public List getTransactionsByType(final TransactionType type) { + return transactionsByType.getOrDefault(type, List.of()); } public List getReceipts() { @@ -151,10 +165,18 @@ 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) { @@ -165,24 +187,36 @@ public boolean equals(final Object o) { } TransactionSelectionResults that = (TransactionSelectionResults) o; return cumulativeGasUsed == that.cumulativeGasUsed - && transactions.equals(that.transactions) + && cumulativeDataGasUsed == that.cumulativeDataGasUsed + && transactionsByType.equals(that.transactionsByType) && receipts.equals(that.receipts) && invalidTransactions.equals(that.invalidTransactions); } @Override public int hashCode() { - return Objects.hash(transactions, receipts, invalidTransactions, cumulativeGasUsed); + return Objects.hash( + transactionsByType, + receipts, + invalidTransactions, + cumulativeGasUsed, + cumulativeDataGasUsed); } public String toTraceLog() { return "cumulativeGasUsed=" + cumulativeGasUsed + + ", cumulativeDataGasUsed=" + + cumulativeDataGasUsed + ", transactions=" - + transactions.stream().map(Transaction::toTraceLog).collect(Collectors.joining("; ")); + + streamAllTransactions().map(Transaction::toTraceLog).collect(Collectors.joining("; ")); } } + private static final Logger LOG = LoggerFactory.getLogger(BlockTransactionSelector.class); + + private final Wei minTransactionGasPrice; + private final Double minBlockOccupancyRatio; private final Supplier isCancelled; private final MainnetTransactionProcessor transactionProcessor; private final ProcessableBlockHeader processableBlockHeader; @@ -191,7 +225,10 @@ public String toTraceLog() { private final PendingTransactions pendingTransactions; private final AbstractBlockProcessor.TransactionReceiptFactory transactionReceiptFactory; private final Address miningBeneficiary; + private final Wei dataGasPrice; private final FeeMarket feeMarket; + private final GasCalculator gasCalculator; + private final GasLimitCalculator gasLimitCalculator; private final TransactionSelectionResults transactionSelectionResult = new TransactionSelectionResults(); @@ -207,7 +244,10 @@ public BlockTransactionSelector( final Double minBlockOccupancyRatio, final Supplier isCancelled, final Address miningBeneficiary, - final FeeMarket feeMarket) { + final Wei dataGasPrice, + final FeeMarket feeMarket, + final GasCalculator gasCalculator, + final GasLimitCalculator gasLimitCalculator) { this.transactionProcessor = transactionProcessor; this.blockchain = blockchain; this.worldState = worldState; @@ -218,7 +258,10 @@ public BlockTransactionSelector( this.minTransactionGasPrice = minTransactionGasPrice; this.minBlockOccupancyRatio = minBlockOccupancyRatio; this.miningBeneficiary = miningBeneficiary; + this.dataGasPrice = dataGasPrice; this.feeMarket = feeMarket; + this.gasCalculator = gasCalculator; + this.gasLimitCalculator = gasLimitCalculator; } /* @@ -278,6 +321,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); @@ -298,7 +344,7 @@ private TransactionSelectionResult evaluateTransaction( transaction.getHash().toHexString()); return transactionSelectionResultForInvalidResult(transaction, validationResult); } else { - // valid GoQuorum private tx, we need to hand craft the receipt and increment the nonce + // valid GoQuorum private tx, we need to handcraft the receipt and increment the nonce effectiveResult = publicResultForWhenWeHaveAPrivateTransaction(transaction); worldStateUpdater.getOrCreate(transaction.getSender()).getMutable().incrementNonce(); } @@ -312,7 +358,8 @@ private TransactionSelectionResult evaluateTransaction( miningBeneficiary, blockHashLookup, false, - TransactionValidationParams.mining()); + TransactionValidationParams.mining(), + dataGasPrice); } if (!effectiveResult.isInvalid()) { @@ -331,6 +378,15 @@ private TransactionSelectionResult evaluateTransaction( return TransactionSelectionResult.CONTINUE; } + private boolean transactionDataPriceBelowMin(final Transaction transaction) { + if (transaction.getType().supportsBlob()) { + if (transaction.getMaxFeePerDataGas().orElseThrow().lessThan(dataGasPrice)) { + 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 @@ -426,11 +482,14 @@ private void updateTransactionResultTracking( final long cumulativeGasUsed = transactionSelectionResult.getCumulativeGasUsed() + gasUsedByTransaction; + final long dataGasUsed = gasCalculator.dataGasCost(transaction.getBlobCount()); + transactionSelectionResult.update( transaction, transactionReceiptFactory.create( transaction.getType(), result, worldState, cumulativeGasUsed), - gasUsedByTransaction); + gasUsedByTransaction, + dataGasUsed); } private boolean isIncorrectNonce(final ValidationResult result) { @@ -448,7 +507,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(); } 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..64861c85104 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; @@ -130,7 +132,8 @@ public void emptyPendingTransactionsResultsInEmptyVettingResult() { final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelector(mainnetTransactionProcessor, blockHeader, Wei.ZERO, miningBeneficiary); + createBlockSelector( + mainnetTransactionProcessor, blockHeader, Wei.ZERO, miningBeneficiary, Wei.ZERO); final BlockTransactionSelector.TransactionSelectionResults results = selector.buildTransactionListForBlock(); @@ -153,7 +156,8 @@ public void failedTransactionsAreIncludedInTheBlock() { final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelector(transactionProcessor, blockHeader, Wei.ZERO, miningBeneficiary); + createBlockSelector( + transactionProcessor, blockHeader, Wei.ZERO, miningBeneficiary, Wei.ZERO); final BlockTransactionSelector.TransactionSelectionResults results = selector.buildTransactionListForBlock(); @@ -184,7 +188,8 @@ public void invalidTransactionsTransactionProcessingAreSkippedButBlockStillFills final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelector(transactionProcessor, blockHeader, Wei.ZERO, miningBeneficiary); + createBlockSelector( + transactionProcessor, blockHeader, Wei.ZERO, miningBeneficiary, Wei.ZERO); final BlockTransactionSelector.TransactionSelectionResults results = selector.buildTransactionListForBlock(); @@ -211,7 +216,8 @@ public void subsetOfPendingTransactionsIncludedWhenBlockGasLimitHit() { final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelector(transactionProcessor, blockHeader, Wei.ZERO, miningBeneficiary); + createBlockSelector( + transactionProcessor, blockHeader, Wei.ZERO, miningBeneficiary, Wei.ZERO); final BlockTransactionSelector.TransactionSelectionResults results = selector.buildTransactionListForBlock(); @@ -255,7 +261,10 @@ public void useSingleGasSpaceForAllTransactions() { 0.8, this::isCancelled, miningBeneficiary, - FeeMarket.london(0L)); + Wei.ZERO, + FeeMarket.london(0L), + new LondonGasCalculator(), + GasLimitCalculator.constant()); // this should fill up all the block space final Transaction fillingLegacyTx = @@ -303,7 +312,8 @@ public void transactionTooLargeForBlockDoesNotPreventMoreBeingAddedIfBlockOccupa final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelector(transactionProcessor, blockHeader, Wei.ZERO, miningBeneficiary); + createBlockSelector( + transactionProcessor, blockHeader, Wei.ZERO, miningBeneficiary, Wei.ZERO); final TransactionTestFixture txTestFixture = new TransactionTestFixture(); // Add 3 transactions to the Pending Transactions, 79% of block, 100% of block and 10% of block @@ -342,7 +352,8 @@ public void transactionSelectionStopsWhenSufficientBlockOccupancyIsReached() { final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelector(transactionProcessor, blockHeader, Wei.ZERO, miningBeneficiary); + createBlockSelector( + transactionProcessor, blockHeader, Wei.ZERO, miningBeneficiary, Wei.ZERO); final TransactionTestFixture txTestFixture = new TransactionTestFixture(); // Add 4 transactions to the Pending Transactions 15% (ok), 79% (ok), 25% (too large), 10% @@ -390,7 +401,8 @@ public void shouldDiscardTransactionsThatFailValidation() { final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelector(transactionProcessor, blockHeader, Wei.ZERO, miningBeneficiary); + createBlockSelector( + transactionProcessor, blockHeader, Wei.ZERO, miningBeneficiary, Wei.ZERO); final TransactionTestFixture txTestFixture = new TransactionTestFixture(); final Transaction validTransaction = @@ -425,7 +437,8 @@ public void transactionWithIncorrectNonceRemainsInPoolAndNotSelected() { final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelector(transactionProcessor, blockHeader, Wei.ZERO, miningBeneficiary); + createBlockSelector( + transactionProcessor, blockHeader, Wei.ZERO, miningBeneficiary, Wei.ZERO); final BlockTransactionSelector.TransactionSelectionResults results = selector.buildTransactionListForBlock(); @@ -439,7 +452,8 @@ protected BlockTransactionSelector createBlockSelector( final MainnetTransactionProcessor transactionProcessor, final ProcessableBlockHeader blockHeader, final Wei minGasPrice, - final Address miningBeneficiary) { + final Address miningBeneficiary, + final Wei dataGasPrice) { final BlockTransactionSelector selector = new BlockTransactionSelector( transactionProcessor, @@ -452,7 +466,10 @@ protected BlockTransactionSelector createBlockSelector( 0.8, this::isCancelled, miningBeneficiary, - getFeeMarket()); + dataGasPrice, + getFeeMarket(), + new LondonGasCalculator(), + GasLimitCalculator.constant()); return selector; } @@ -489,7 +506,7 @@ protected void ensureTransactionIsValid(final Transaction tx) { protected void ensureTransactionIsValid( final Transaction tx, final long gasUsedByTransaction, final long gasRemaining) { when(transactionProcessor.processTransaction( - any(), any(), any(), eq(tx), any(), any(), anyBoolean(), any())) + any(), any(), any(), eq(tx), any(), any(), anyBoolean(), any(), any())) .thenReturn( TransactionProcessingResult.successful( new ArrayList<>(), @@ -502,7 +519,7 @@ protected void ensureTransactionIsValid( protected void ensureTransactionIsInvalid( final Transaction tx, final TransactionInvalidReason invalidReason) { when(transactionProcessor.processTransaction( - any(), any(), any(), eq(tx), any(), any(), anyBoolean(), any())) + any(), any(), any(), eq(tx), any(), any(), anyBoolean(), any(), any())) .thenReturn(TransactionProcessingResult.invalid(ValidationResult.invalid(invalidReason))); } } diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LondonFeeMarketBlockTransactionSelectorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LondonFeeMarketBlockTransactionSelectorTest.java index 9771b2fc382..be893f49645 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LondonFeeMarketBlockTransactionSelectorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LondonFeeMarketBlockTransactionSelectorTest.java @@ -70,7 +70,8 @@ public void eip1559TransactionCurrentGasPriceLessThanMinimumIsSkippedAndKeptInTh final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelector(transactionProcessor, blockHeader, Wei.of(6), miningBeneficiary); + createBlockSelector( + transactionProcessor, blockHeader, Wei.of(6), miningBeneficiary, Wei.ZERO); // tx is willing to pay max 6 wei for gas, but current network condition (baseFee == 1) // result in it paying 2 wei, that is below the minimum accepted by the node, so it is skipped @@ -90,7 +91,8 @@ public void eip1559TransactionCurrentGasPriceGreaterThanMinimumIsSelected() { final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelector(transactionProcessor, blockHeader, Wei.of(6), miningBeneficiary); + createBlockSelector( + transactionProcessor, blockHeader, Wei.of(6), miningBeneficiary, Wei.ZERO); // tx is willing to pay max 6 wei for gas, and current network condition (baseFee == 5) // result in it paying the max, that is >= the minimum accepted by the node, so it is selected @@ -112,7 +114,8 @@ public void eip1559LocalTransactionCurrentGasPriceLessThanMinimumIsSelected() { final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelector(transactionProcessor, blockHeader, Wei.of(6), miningBeneficiary); + createBlockSelector( + transactionProcessor, blockHeader, Wei.of(6), miningBeneficiary, Wei.ZERO); // tx is willing to pay max 6 wei for gas, but current network condition (baseFee == 1) // result in it paying 2 wei, that is below the minimum accepted by the node, but since it is diff --git a/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/vm/TraceTransactionIntegrationTest.java b/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/vm/TraceTransactionIntegrationTest.java index 0d3e1388f18..d3e37ec5d6a 100644 --- a/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/vm/TraceTransactionIntegrationTest.java +++ b/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/vm/TraceTransactionIntegrationTest.java @@ -103,7 +103,8 @@ public void shouldTraceSStoreOperation() { genesisBlockHeader.getCoinbase(), blockHashLookup, false, - TransactionValidationParams.blockReplay()); + TransactionValidationParams.blockReplay(), + Wei.ZERO); assertThat(result.isSuccessful()).isTrue(); final Account createdContract = createTransactionUpdater.getTouchedAccounts().stream() @@ -135,7 +136,8 @@ public void shouldTraceSStoreOperation() { genesisBlockHeader.getCoinbase(), tracer, blockHashLookup, - false); + false, + Wei.ZERO); assertThat(result.isSuccessful()).isTrue(); @@ -176,7 +178,8 @@ public void shouldTraceContractCreation() { genesisBlockHeader.getCoinbase(), tracer, new BlockHashLookup(genesisBlockHeader, blockchain), - false); + false, + Wei.ZERO); final int expectedDepth = 0; // Reference impl returned 1. Why the difference? 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 38e77ed07dd..b3a6da73810 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 @@ -44,7 +44,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; @@ -478,6 +477,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. * @@ -692,18 +700,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); } /** @@ -712,21 +715,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; @@ -735,8 +740,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; } /** @@ -748,8 +761,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/goquorum/GoQuorumBlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/goquorum/GoQuorumBlockProcessor.java index 361851c32a4..1aba23adafd 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/goquorum/GoQuorumBlockProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/goquorum/GoQuorumBlockProcessor.java @@ -150,7 +150,8 @@ public BlockProcessingResult processBlock( blockHashLookup, true, TransactionValidationParams.processingBlock(), - null); + null, + Wei.ZERO); if (result.isInvalid()) { String errorMessage = diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java index fdf8620d7dd..32dbca85fa8 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.mainnet; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.DataGas; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.BlockProcessingOutputs; import org.hyperledger.besu.ethereum.BlockProcessingResult; @@ -96,6 +97,9 @@ public BlockProcessingResult processBlock( final PrivateMetadataUpdater privateMetadataUpdater) { final List receipts = new ArrayList<>(); long currentGasUsed = 0; + + final ProtocolSpec protocolSpec = protocolSchedule.getByBlockHeader(blockHeader); + for (final Transaction transaction : transactions) { if (!hasAvailableBlockBudget(blockHeader, transaction, currentGasUsed)) { return new BlockProcessingResult(Optional.empty(), "provided gas insufficient"); @@ -105,6 +109,14 @@ public BlockProcessingResult processBlock( final BlockHashLookup blockHashLookup = new BlockHashLookup(blockHeader, blockchain); final Address miningBeneficiary = miningBeneficiaryCalculator.calculateBeneficiary(blockHeader); + final Wei dataGasPrice = + protocolSpec + .getFeeMarket() + .dataPrice( + blockchain + .getBlockHeader(blockHeader.getParentHash()) + .flatMap(BlockHeader::getExcessDataGas) + .orElse(DataGas.ZERO)); final TransactionProcessingResult result = transactionProcessor.processTransaction( @@ -117,7 +129,8 @@ public BlockProcessingResult processBlock( blockHashLookup, true, TransactionValidationParams.processingBlock(), - privateMetadataUpdater); + privateMetadataUpdater, + dataGasPrice); if (result.isInvalid()) { String errorMessage = MessageFormat.format( @@ -141,7 +154,7 @@ public BlockProcessingResult processBlock( } final Optional maybeWithdrawalsProcessor = - protocolSchedule.getByBlockHeader(blockHeader).getWithdrawalsProcessor(); + protocolSpec.getWithdrawalsProcessor(); if (maybeWithdrawalsProcessor.isPresent() && maybeWithdrawals.isPresent()) { try { maybeWithdrawalsProcessor 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 068713d5e2b..959fe9acf35 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, @@ -651,7 +659,7 @@ static ProtocolSpecBuilder shanghaiDefinition( final boolean quorumCompatibilityMode, final EvmConfiguration evmConfiguration) { - // extra vaiables need to support flipping the warm coinbase flag. + // extra variables need to support flipping the warm coinbase flag. final int stackSizeLimit = configStackSizeLimit.orElse(MessageFrame.DEFAULT_MAX_STACK_SIZE); final long londonForkBlockNumber = genesisConfigOptions.getLondonBlockNumber().orElse(0L); final BaseFeeMarket londonFeeMarket = @@ -692,9 +700,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, @@ -718,8 +727,17 @@ static ProtocolSpecBuilder cancunDefinition( final boolean quorumCompatibilityMode, final EvmConfiguration evmConfiguration) { + final int stackSizeLimit = configStackSizeLimit.orElse(MessageFrame.DEFAULT_MAX_STACK_SIZE); 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, @@ -729,6 +747,11 @@ static ProtocolSpecBuilder cancunDefinition( genesisConfigOptions, quorumCompatibilityMode, evmConfiguration) + .feeMarket(cancunFeeMarket) + // 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) -> @@ -745,6 +768,38 @@ static ProtocolSpecBuilder cancunDefinition( MaxCodeSizeRule.of(contractSizeLimit), EOFValidationCodeRule.of(1, false)), 1, SPURIOUS_DRAGON_FORCE_DELETE_WHEN_EMPTY_ADDRESSES)) + // use Cancun fee market + .transactionProcessorBuilder( + (gasCalculator, + transactionValidator, + contractCreationProcessor, + messageCallProcessor) -> + new MainnetTransactionProcessor( + gasCalculator, + transactionValidator, + contractCreationProcessor, + messageCallProcessor, + true, + true, + stackSizeLimit, + cancunFeeMarket, + CoinbaseFeePriceCalculator.eip1559())) + // 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, + TransactionType.BLOB), + 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 2863859c52f..37cc2b37126 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 @@ -125,7 +125,8 @@ public TransactionProcessingResult processTransaction( final Address miningBeneficiary, final BlockHashLookup blockHashLookup, final Boolean isPersistingPrivateState, - final TransactionValidationParams transactionValidationParams) { + final TransactionValidationParams transactionValidationParams, + final Wei dataGasPrice) { return processTransaction( blockchain, worldState, @@ -136,7 +137,8 @@ public TransactionProcessingResult processTransaction( blockHashLookup, isPersistingPrivateState, transactionValidationParams, - null); + null, + dataGasPrice); } /** @@ -165,7 +167,8 @@ public TransactionProcessingResult processTransaction( final BlockHashLookup blockHashLookup, final Boolean isPersistingPrivateState, final TransactionValidationParams transactionValidationParams, - final OperationTracer operationTracer) { + final OperationTracer operationTracer, + final Wei dataGasPrice) { return processTransaction( blockchain, worldState, @@ -176,7 +179,8 @@ public TransactionProcessingResult processTransaction( blockHashLookup, isPersistingPrivateState, transactionValidationParams, - null); + null, + dataGasPrice); } /** @@ -200,7 +204,8 @@ public TransactionProcessingResult processTransaction( final Address miningBeneficiary, final OperationTracer operationTracer, final BlockHashLookup blockHashLookup, - final Boolean isPersistingPrivateState) { + final Boolean isPersistingPrivateState, + final Wei dataGasPrice) { return processTransaction( blockchain, worldState, @@ -211,7 +216,8 @@ public TransactionProcessingResult processTransaction( blockHashLookup, isPersistingPrivateState, ImmutableTransactionValidationParams.builder().build(), - null); + null, + dataGasPrice); } /** @@ -237,7 +243,8 @@ public TransactionProcessingResult processTransaction( final OperationTracer operationTracer, final BlockHashLookup blockHashLookup, final Boolean isPersistingPrivateState, - final TransactionValidationParams transactionValidationParams) { + final TransactionValidationParams transactionValidationParams, + final Wei dataGasPrice) { return processTransaction( blockchain, worldState, @@ -248,7 +255,8 @@ public TransactionProcessingResult processTransaction( blockHashLookup, isPersistingPrivateState, transactionValidationParams, - null); + null, + dataGasPrice); } public TransactionProcessingResult processTransaction( @@ -261,7 +269,8 @@ public TransactionProcessingResult processTransaction( final BlockHashLookup blockHashLookup, final Boolean isPersistingPrivateState, final TransactionValidationParams transactionValidationParams, - final PrivateMetadataUpdater privateMetadataUpdater) { + final PrivateMetadataUpdater privateMetadataUpdater, + final Wei dataGasPrice) { try { LOG.trace("Starting execution of {}", transaction); ValidationResult validationResult = @@ -288,15 +297,19 @@ public TransactionProcessingResult processTransaction( final MutableAccount senderMutableAccount = sender.getMutable(); final long previousNonce = senderMutableAccount.incrementNonce(); - final Wei transactionGasPrice = - feeMarket.getTransactionPriceCalculator().price(transaction, blockHeader.getBaseFee()); LOG.trace( "Incremented sender {} nonce ({} -> {})", senderAddress, previousNonce, sender.getNonce()); - final Wei upfrontGasCost = transaction.getUpfrontGasCost(transactionGasPrice); + final Wei transactionGasPrice = + feeMarket.getTransactionPriceCalculator().price(transaction, blockHeader.getBaseFee()); + + 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 {} ({} -> {})", @@ -327,12 +340,14 @@ public TransactionProcessingResult processTransaction( transaction.getPayload(), transaction.isContractCreation()); final long accessListGas = gasCalculator.accessListGasCost(accessListEntries.size(), accessListStorageCount); + final long gasAvailable = transaction.getGasLimit() - intrinsicGas - accessListGas; LOG.trace( - "Gas available for execution {} = {} - {} (limit - intrinsic)", + "Gas available for execution {} = {} - {} - {} (limit - intrinsic - accessList)", gasAvailable, transaction.getGasLimit(), - intrinsicGas); + intrinsicGas, + accessListGas); final WorldUpdater worldUpdater = worldState.updater(); final Deque messageFrameStack = new ArrayDeque<>(); @@ -424,9 +439,9 @@ public TransactionProcessingResult processTransaction( // after the other so that if it is the same account somehow, we end up with the right result) final long selfDestructRefund = gasCalculator.getSelfDestructRefundAmount() * initialFrame.getSelfDestructs().size(); - final long refundGas = initialFrame.getGasRefund() + selfDestructRefund; - final long refunded = refunded(transaction, initialFrame.getRemainingGas(), refundGas); - final Wei refundedWei = transactionGasPrice.multiply(refunded); + final long baseRefundGas = initialFrame.getGasRefund() + selfDestructRefund; + final long refundedGas = refunded(transaction, initialFrame.getRemainingGas(), baseRefundGas); + final Wei refundedWei = transactionGasPrice.multiply(refundedGas); senderMutableAccount.incrementBalance(refundedWei); final long gasUsedByTransaction = transaction.getGasLimit() - initialFrame.getRemainingGas(); @@ -434,25 +449,26 @@ public TransactionProcessingResult processTransaction( if (!worldState.getClass().equals(GoQuorumMutablePrivateWorldStateUpdater.class)) { // if this is not a private GoQuorum transaction we have to update the coinbase final var coinbase = worldState.getOrCreate(miningBeneficiary).getMutable(); - final long coinbaseFee = transaction.getGasLimit() - refunded; + final long usedGas = transaction.getGasLimit() - refundedGas; + final CoinbaseFeePriceCalculator coinbaseCalculator; if (blockHeader.getBaseFee().isPresent()) { final Wei baseFee = blockHeader.getBaseFee().get(); if (transactionGasPrice.compareTo(baseFee) < 0) { return TransactionProcessingResult.failed( gasUsedByTransaction, - refunded, + refundedGas, ValidationResult.invalid( TransactionInvalidReason.TRANSACTION_PRICE_TOO_LOW, "transaction price must be greater than base fee"), Optional.empty()); } + coinbaseCalculator = coinbaseFeePriceCalculator; + } else { + coinbaseCalculator = CoinbaseFeePriceCalculator.frontier(); } - final CoinbaseFeePriceCalculator coinbaseCalculator = - blockHeader.getBaseFee().isPresent() - ? coinbaseFeePriceCalculator - : CoinbaseFeePriceCalculator.frontier(); + final Wei coinbaseWeiDelta = - coinbaseCalculator.price(coinbaseFee, transactionGasPrice, blockHeader.getBaseFee()); + coinbaseCalculator.price(usedGas, transactionGasPrice, blockHeader.getBaseFee()); coinbase.incrementBalance(coinbaseWeiDelta); } @@ -467,12 +483,12 @@ public TransactionProcessingResult processTransaction( return TransactionProcessingResult.successful( initialFrame.getLogs(), gasUsedByTransaction, - refunded, + refundedGas, initialFrame.getOutputData(), validationResult); } else { return TransactionProcessingResult.failed( - gasUsedByTransaction, refunded, validationResult, initialFrame.getRevertReason()); + gasUsedByTransaction, refundedGas, validationResult, initialFrame.getRevertReason()); } } catch (final MerkleTrieException re) { // need to throw to trigger the heal @@ -508,7 +524,7 @@ private AbstractMessageProcessor getMessageProcessor(final MessageFrame.Type typ protected long refunded( final Transaction transaction, final long gasRemaining, final long gasRefund) { - // Integer truncation takes care of the the floor calculation needed after the divide. + // Integer truncation takes care of the floor calculation needed after the divide. final long maxRefundAllowance = (transaction.getGasLimit() - gasRemaining) / gasCalculator.getMaxRefundQuotient(); final long refundAllowance = Math.min(maxRefundAllowance, gasRefund); 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 260bc969853..3d6a4f8cf81 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.mainnet.feemarket.FeeMarket; @@ -41,6 +42,7 @@ public class MainnetTransactionValidator { private final GasCalculator gasCalculator; + private final GasLimitCalculator gasLimitCalculator; private final FeeMarket feeMarket; private final boolean disallowSignatureMalleability; @@ -55,11 +57,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), @@ -68,12 +72,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, @@ -84,6 +90,7 @@ public MainnetTransactionValidator( public MainnetTransactionValidator( final GasCalculator gasCalculator, + final GasLimitCalculator gasLimitCalculator, final FeeMarket feeMarket, final boolean checkSignatureMalleability, final Optional chainId, @@ -91,6 +98,7 @@ public MainnetTransactionValidator( final boolean goQuorumCompatibilityMode, final int maxInitcodeSize) { this.gasCalculator = gasCalculator; + this.gasLimitCalculator = gasLimitCalculator; this.feeMarket = feeMarket; this.disallowSignatureMalleability = checkSignatureMalleability; this.chainId = chainId; @@ -119,12 +127,6 @@ public ValidationResult validate( return signatureResult; } - 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( @@ -134,10 +136,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"); @@ -157,9 +186,15 @@ public ValidationResult validate( } } - if (transaction.getNonce() == MAX_NONCE) { - return ValidationResult.invalid( - 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 = @@ -174,14 +209,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(); } @@ -199,13 +226,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().toQuantityHexString(), - senderBalance.toQuantityHexString())); + upfrontCost.toQuantityHexString(), senderBalance.toQuantityHexString())); } if (transaction.getNonce() < senderNonce) { 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 0882196aadb..ccd6955ff5c 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; @@ -124,7 +125,8 @@ public ProtocolSpecBuilder evmBuilder( } public ProtocolSpecBuilder transactionValidatorBuilder( - final Function transactionValidatorBuilder) { + final BiFunction + transactionValidatorBuilder) { this.transactionValidatorBuilder = transactionValidatorBuilder; return this; } @@ -296,7 +298,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..433ae60f14b --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/CancunFeeMarket.java @@ -0,0 +1,72 @@ +/* + * 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 static org.hyperledger.besu.util.Slf4jLambdaHelper.traceLambda; + +import org.hyperledger.besu.datatypes.DataGas; +import org.hyperledger.besu.datatypes.Wei; + +import java.math.BigInteger; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CancunFeeMarket extends LondonFeeMarket { + private static final Logger LOG = LoggerFactory.getLogger(CancunFeeMarket.class); + private static final BigInteger MIN_DATA_GAS_PRICE = BigInteger.ONE; + private static final BigInteger DATA_GAS_PRICE_UPDATE_FRACTION = BigInteger.valueOf(2225652); + + public CancunFeeMarket( + final long londonForkBlockNumber, final Optional baseFeePerGasOverride) { + super(londonForkBlockNumber, baseFeePerGasOverride); + } + + @Override + public boolean implementsDataFee() { + return true; + } + + @Override + public Wei dataPrice(final DataGas excessDataGas) { + final var dataGasPrice = + Wei.of( + fakeExponential( + MIN_DATA_GAS_PRICE, excessDataGas.toBigInteger(), DATA_GAS_PRICE_UPDATE_FRACTION)); + traceLambda( + LOG, + "parentExcessDataGas: {} dataGasPrice: {}", + excessDataGas::toShortHexString, + dataGasPrice::toHexString); + + return dataGasPrice; + } + + private BigInteger fakeExponential( + final BigInteger factor, final BigInteger numerator, final BigInteger denominator) { + int i = 1; + BigInteger output = BigInteger.ZERO; + BigInteger numeratorAccumulator = factor.multiply(denominator); + while (numeratorAccumulator.signum() > 0) { + output = output.add(numeratorAccumulator); + numeratorAccumulator = + (numeratorAccumulator.multiply(numerator)) + .divide(denominator.multiply(BigInteger.valueOf(i))); + ++i; + } + return output.divide(denominator); + } +} 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..dc20b43438f 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 @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.ethereum.mainnet.feemarket; +import org.hyperledger.besu.datatypes.DataGas; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.feemarket.TransactionPriceCalculator; @@ -26,6 +27,10 @@ default boolean implementsBaseFee() { return false; } + default boolean implementsDataFee() { + return false; + } + TransactionPriceCalculator getTransactionPriceCalculator(); boolean satisfiesFloorTxFee(Transaction txn); @@ -39,6 +44,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); } @@ -46,4 +56,8 @@ static BaseFeeMarket zeroBaseFee(final long londonForkBlockNumber) { static FeeMarket legacy() { return new LegacyFeeMarket(); } + + default Wei dataPrice(final DataGas excessDataGas) { + return Wei.ZERO; + } } 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..6f454b464eb 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; 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/privacy/PrivateGroupRehydrationBlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateGroupRehydrationBlockProcessor.java index 63dfe260f91..c06a9a0271a 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateGroupRehydrationBlockProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateGroupRehydrationBlockProcessor.java @@ -177,7 +177,8 @@ public BlockProcessingResult processBlock( miningBeneficiary, blockHashLookup, false, - TransactionValidationParams.processingBlock()); + TransactionValidationParams.processingBlock(), + Wei.ZERO); if (result.isInvalid()) { return BlockProcessingResult.FAILED; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/migration/PrivateMigrationBlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/migration/PrivateMigrationBlockProcessor.java index 9a8b7e0be23..c6743354e4b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/migration/PrivateMigrationBlockProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/migration/PrivateMigrationBlockProcessor.java @@ -109,7 +109,8 @@ public BlockProcessingResult processBlock( miningBeneficiary, blockHashLookup, true, - TransactionValidationParams.processingBlock()); + TransactionValidationParams.processingBlock(), + Wei.ZERO); if (result.isInvalid()) { return BlockProcessingResult.FAILED; } 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 d4297b7fa33..6bd44a03389 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 @@ -54,5 +54,6 @@ public enum TransactionInvalidReason { ETHER_VALUE_NOT_SUPPORTED, UPFRONT_FEE_TOO_HIGH, NONCE_TOO_FAR_IN_FUTURE_FOR_SENDER, - LOWER_NONCE_INVALID_TRANSACTION_EXISTS + LOWER_NONCE_INVALID_TRANSACTION_EXISTS, + TOTAL_DATA_GAS_TOO_HIGH } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java index 4f96b5c7239..fab12cf58e9 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java @@ -20,6 +20,7 @@ import org.hyperledger.besu.crypto.SignatureAlgorithm; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; 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.chain.Blockchain; @@ -222,6 +223,14 @@ public Optional processWithWorldUpdater( return Optional.empty(); } + final Optional maybeParentHeader = + blockchain.getBlockHeader(blockHeaderToProcess.getParentHash()); + final Wei dataGasPrice = + protocolSpec + .getFeeMarket() + .dataPrice( + maybeParentHeader.flatMap(BlockHeader::getExcessDataGas).orElse(DataGas.ZERO)); + final Transaction transaction = maybeTransaction.get(); final TransactionProcessingResult result = transactionProcessor.processTransaction( @@ -235,7 +244,8 @@ public Optional processWithWorldUpdater( new BlockHashLookup(blockHeaderToProcess, blockchain), false, transactionValidationParams, - operationTracer); + operationTracer, + dataGasPrice); // If GoQuorum privacy enabled, and value = zero, get max gas possible for a PMT hash. // It is possible to have a data field that has a lower intrinsic value than the PMT hash. 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/mainnet/MainnetTransactionProcessorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessorTest.java index e5c43eb96a2..ce555391570 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessorTest.java @@ -119,7 +119,8 @@ public void shouldWarmCoinbaseIfRequested() { coinbaseAddress, blockHashLookup, false, - ImmutableTransactionValidationParams.builder().build()); + ImmutableTransactionValidationParams.builder().build(), + Wei.ZERO); assertThat(coinbaseWarmed).isTrue(); @@ -132,7 +133,8 @@ public void shouldWarmCoinbaseIfRequested() { coinbaseAddress, blockHashLookup, false, - ImmutableTransactionValidationParams.builder().build()); + ImmutableTransactionValidationParams.builder().build(), + Wei.ZERO); assertThat(coinbaseWarmed).isFalse(); } @@ -155,7 +157,8 @@ public void shouldCallTransactionValidatorWithExpectedTransactionValidationParam Address.fromHexString("1"), blockHashLookup, false, - ImmutableTransactionValidationParams.builder().build()); + ImmutableTransactionValidationParams.builder().build(), + Wei.ZERO); assertThat(txValidationParamCaptor.getValue()) .usingRecursiveComparison() 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/PrivacyBlockProcessorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java index 9a25a3ce226..ab18c52b68c 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java @@ -202,7 +202,7 @@ private ProtocolSpec mockProtocolSpec() { final MainnetTransactionProcessor mockPublicTransactionProcessor = mock(MainnetTransactionProcessor.class); when(mockPublicTransactionProcessor.processTransaction( - any(), any(), any(), any(), any(), any(), anyBoolean(), any())) + any(), any(), any(), any(), any(), any(), anyBoolean(), any(), any())) .thenReturn( TransactionProcessingResult.successful( Collections.emptyList(), 0, 0, Bytes.EMPTY, ValidationResult.valid())); 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..20d389d4700 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 @@ -67,6 +67,7 @@ public void getTransactionPriceCalculatorShouldBeEIP1559() { .maxPriorityFeePerGas(Optional.of(Wei.of(8))) .gasPrice(null) .createTransaction(KEY_PAIR1); + assertThat( zeroBaseFeeMarket .getTransactionPriceCalculator() diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java index 5309ae7307e..e8b07d5d925 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java @@ -39,6 +39,7 @@ import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionValidator; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult.Status; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; @@ -539,6 +540,7 @@ private void mockProcessorStatusForTransaction( when(protocolSpec.getTransactionProcessor()).thenReturn(transactionProcessor); when(protocolSpec.getMiningBeneficiaryCalculator()).thenReturn(BlockHeader::getCoinbase); when(protocolSpec.getBlockHeaderFunctions()).thenReturn(blockHeaderFunctions); + when(protocolSpec.getFeeMarket()).thenReturn(FeeMarket.london(0)); final TransactionProcessingResult result = mock(TransactionProcessingResult.class); switch (status) { @@ -551,14 +553,32 @@ private void mockProcessorStatusForTransaction( break; } when(transactionProcessor.processTransaction( - any(), any(), any(), eq(transaction), any(), any(), anyBoolean(), any(), any())) + any(), + any(), + any(), + eq(transaction), + any(), + any(), + anyBoolean(), + any(), + any(), + any(Wei.class))) .thenReturn(result); } private void verifyTransactionWasProcessed(final Transaction expectedTransaction) { verify(transactionProcessor) .processTransaction( - any(), any(), any(), eq(expectedTransaction), any(), any(), anyBoolean(), any(), any()); + any(), + any(), + any(), + eq(expectedTransaction), + any(), + any(), + anyBoolean(), + any(), + any(), + any(Wei.class)); } private CallParameter legacyTransactionCallParameter() { 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..e89cc4600cd 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; @@ -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/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/StateTestSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/StateTestSubCommand.java index 60e0219bcb3..99ba124ead9 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/StateTestSubCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/StateTestSubCommand.java @@ -20,7 +20,9 @@ import static org.hyperledger.besu.ethereum.referencetests.ReferenceTestProtocolSchedules.shouldClearEmptyAccounts; import static org.hyperledger.besu.evmtool.StateTestSubCommand.COMMAND_NAME; +import org.hyperledger.besu.datatypes.DataGas; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.Transaction; @@ -210,12 +212,14 @@ private void traceTestSpecs(final String test, final List validTransactions = new ArrayList<>(); ArrayNode receiptsArray = objectMapper.createArrayNode(); long gasUsed = 0; + // Todo: EIP-4844 use the excessDataGas of the parent instead of DataGas.ZERO + final Wei dataGasPrice = protocolSpec.getFeeMarket().dataPrice(DataGas.ZERO); + for (int i = 0; i < transactions.size(); i++) { Transaction transaction = transactions.get(i); @@ -300,6 +304,7 @@ public void run() { } else { tracer = OperationTracer.NO_TRACING; } + result = processor.processTransaction( blockchain, @@ -310,7 +315,8 @@ public void run() { new BlockHashLookup(referenceTestEnv, blockchain), false, TransactionValidationParams.processingBlock(), - tracer); + tracer, + dataGasPrice); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java index fcec3aa3e56..66e23f90971 100644 --- a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java +++ b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java @@ -16,12 +16,15 @@ import static org.assertj.core.api.Assertions.assertThat; +import org.hyperledger.besu.datatypes.DataGas; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.referencetests.GeneralStateTestCaseEipSpec; @@ -49,12 +52,16 @@ public class GeneralStateReferenceTestTools { Arrays.asList("Frontier", "Homestead", "EIP150"); private static MainnetTransactionProcessor transactionProcessor(final String name) { - return REFERENCE_TEST_PROTOCOL_SCHEDULES - .getByName(name) - .getByBlockHeader(BlockHeaderBuilder.createDefault().buildBlockHeader()) + return protocolSpec(name) .getTransactionProcessor(); } + private static ProtocolSpec protocolSpec(final String name) { + return REFERENCE_TEST_PROTOCOL_SCHEDULES + .getByName(name) + .getByBlockHeader(BlockHeaderBuilder.createDefault().buildBlockHeader()); + } + private static final List EIPS_TO_RUN; static { @@ -138,6 +145,8 @@ public static void executeTest(final GeneralStateTestCaseEipSpec spec) { final MainnetTransactionProcessor processor = transactionProcessor(spec.getFork()); final WorldUpdater worldStateUpdater = worldState.updater(); final ReferenceTestBlockchain blockchain = new ReferenceTestBlockchain(blockHeader.getNumber()); + // Todo: EIP-4844 use the excessDataGas of the parent instead of DataGas.ZERO + final Wei dataGasPrice = protocolSpec(spec.getFork()).getFeeMarket().dataPrice(DataGas.ZERO); final TransactionProcessingResult result = processor.processTransaction( blockchain, @@ -147,7 +156,8 @@ public static void executeTest(final GeneralStateTestCaseEipSpec spec) { blockHeader.getCoinbase(), new BlockHashLookup(blockHeader, blockchain), false, - TransactionValidationParams.processingBlock()); + TransactionValidationParams.processingBlock(), + dataGasPrice); if (result.isInvalid()) { assertThat(spec.getExpectException()).isNotNull(); return; 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 index c56cd6c60f9..b78e765cc8b 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/CancunGasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/CancunGasCalculator.java @@ -19,6 +19,7 @@ * *
    *
  • Gas costs for TSTORE/TLOAD + *
  • Data gas for EIP-4844 *
*/ public class CancunGasCalculator extends LondonGasCalculator { @@ -26,6 +27,9 @@ public class CancunGasCalculator extends LondonGasCalculator { private static final long TLOAD_GAS = WARM_STORAGE_READ_COST; private static final long TSTORE_GAS = WARM_STORAGE_READ_COST; + private static final long DATA_GAS_PER_BLOB = 1 << 17; + private static final long TARGET_DATA_GAS_PER_BLOCK = 1 << 18; + // EIP-1153 @Override public long getTransientLoadOperationGasCost() { @@ -36,4 +40,20 @@ public long getTransientLoadOperationGasCost() { public long getTransientStoreOperationGasCost() { return TSTORE_GAS; } + + @Override + public long dataGasCost(final int blobCount) { + return DATA_GAS_PER_BLOB * blobCount; + } + + @Override + public long computeExcessDataGas(final long parentExcessDataGas, final int newBlobs) { + final long consumedDataGas = dataGasCost(newBlobs); + final long currentExcessDataGas = parentExcessDataGas + consumedDataGas; + + if (currentExcessDataGas < TARGET_DATA_GAS_PER_BLOCK) { + return 0L; + } + return currentExcessDataGas - 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 e2b237abfa3..417b89ca8cf 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 @@ -505,4 +505,26 @@ default long getTransientLoadOperationGasCost() { default long getTransientStoreOperationGasCost() { return 0L; } + + /** + * 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; + } }