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 cb3e1860b92..551eb91c759 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 @@ -18,6 +18,7 @@ import org.hyperledger.besu.datatypes.DataGas; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.GasLimitCalculator; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.blockcreation.BlockTransactionSelector.TransactionSelectionResults; import org.hyperledger.besu.ethereum.core.Block; @@ -232,6 +233,10 @@ protected BlockCreationResult createBlock( throwIfStopped(); + final DataGas newExcessDataGas = computeExcessDataGas(transactionResults, newProtocolSpec); + + throwIfStopped(); + final SealableBlockHeader sealableBlockHeader = BlockHeaderBuilder.create() .populateFrom(processableBlockHeader) @@ -362,13 +367,13 @@ private ProcessableBlockHeader createPendingBlockHeader( final Optional maybePrevRandao, final ProtocolSpec protocolSpec) { final long newBlockNumber = parentHeader.getNumber() + 1; - long gasLimit = - protocolSpec - .getGasLimitCalculator() - .nextGasLimit( - parentHeader.getGasLimit(), - targetGasLimitSupplier.get().orElse(parentHeader.getGasLimit()), - newBlockNumber); + final GasLimitCalculator gasLimitCalculator = protocolSpec.getGasLimitCalculator(); + + final long gasLimit = + gasLimitCalculator.nextGasLimit( + parentHeader.getGasLimit(), + targetGasLimitSupplier.get().orElse(parentHeader.getGasLimit()), + newBlockNumber); final DifficultyCalculator difficultyCalculator = protocolSpec.getDifficultyCalculator(); final BigInteger difficulty = diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockTransactionSelector.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockTransactionSelector.java index 6427c08e2af..a2774b147bc 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 @@ -34,6 +34,8 @@ import org.hyperledger.besu.ethereum.vm.BlockHashLookup; import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup; import org.hyperledger.besu.evm.gascalculator.GasCalculator; +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.TransactionSelectionResult; import org.hyperledger.besu.plugin.data.TransactionType; @@ -41,6 +43,8 @@ import org.hyperledger.besu.plugin.services.txselection.TransactionSelectorFactory; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.EnumMap; import java.util.HashMap; import java.util.List; @@ -51,6 +55,7 @@ import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; import com.google.common.collect.Lists; import org.slf4j.Logger; @@ -77,136 +82,6 @@ * not cleared between executions of buildTransactionListForBlock(). */ public class BlockTransactionSelector { - public static class TransactionValidationResult { - private final Transaction transaction; - private final ValidationResult validationResult; - - public TransactionValidationResult( - final Transaction transaction, - final ValidationResult validationResult) { - this.transaction = transaction; - this.validationResult = validationResult; - } - - public Transaction getTransaction() { - return transaction; - } - - public ValidationResult getValidationResult() { - return validationResult; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - TransactionValidationResult that = (TransactionValidationResult) o; - return Objects.equals(transaction, that.transaction) - && Objects.equals(validationResult, that.validationResult); - } - - @Override - public int hashCode() { - return Objects.hash(transaction, validationResult); - } - } - - public static class TransactionSelectionResults { - private final List transactions = Lists.newArrayList(); - private final Map> transactionsByType = - new EnumMap<>(TransactionType.class); - private final List receipts = Lists.newArrayList(); - private final List invalidTransactions = Lists.newArrayList(); - private long cumulativeGasUsed = 0; - private long cumulativeDataGasUsed = 0; - - private void update( - final Transaction transaction, - final TransactionReceipt receipt, - final long gasUsed, - final long dataGasUsed) { - transactions.add(transaction); - transactionsByType - .computeIfAbsent(transaction.getType(), type -> new ArrayList<>()) - .add(transaction); - receipts.add(receipt); - cumulativeGasUsed += gasUsed; - cumulativeDataGasUsed += dataGasUsed; - LOG.atTrace() - .setMessage( - "New selected transaction {}, total transactions {}, cumulative gas used {}, cumulative data gas used {}") - .addArgument(transaction::toTraceLog) - .addArgument(transactions::size) - .addArgument(cumulativeGasUsed) - .addArgument(cumulativeDataGasUsed) - .log(); - } - - private void updateWithInvalidTransaction( - final Transaction transaction, - final ValidationResult validationResult) { - invalidTransactions.add(new TransactionValidationResult(transaction, validationResult)); - } - - public List getTransactions() { - return transactions; - } - - public List getTransactionsByType(final TransactionType type) { - return transactionsByType.getOrDefault(type, List.of()); - } - - public List getReceipts() { - return receipts; - } - - public long getCumulativeGasUsed() { - return cumulativeGasUsed; - } - - public long getCumulativeDataGasUsed() { - return cumulativeDataGasUsed; - } - - public List getInvalidTransactions() { - return invalidTransactions; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - TransactionSelectionResults that = (TransactionSelectionResults) o; - return cumulativeGasUsed == that.cumulativeGasUsed - && cumulativeDataGasUsed == that.cumulativeDataGasUsed - && transactions.equals(that.transactions) - && receipts.equals(that.receipts) - && invalidTransactions.equals(that.invalidTransactions); - } - - @Override - public int hashCode() { - return Objects.hash( - transactions, receipts, invalidTransactions, cumulativeGasUsed, cumulativeDataGasUsed); - } - - public String toTraceLog() { - return "cumulativeGasUsed=" - + cumulativeGasUsed - + ", cumulativeDataGasUsed=" - + cumulativeDataGasUsed - + ", transactions=" - + transactions.stream().map(Transaction::toTraceLog).collect(Collectors.joining("; ")); - } - } private static final Logger LOG = LoggerFactory.getLogger(BlockTransactionSelector.class); @@ -404,7 +279,13 @@ private List getLogs(final List logs) private boolean transactionDataPriceBelowMin(final Transaction transaction) { if (transaction.getType().supportsBlob()) { - if (transaction.getMaxFeePerDataGas().orElseThrow().lessThan(dataGasPrice)) { + if (transaction + .getMaxFeePerDataGas() + .orElseThrow() + .lessThan( + feeMarket + .getTransactionPriceCalculator() + .dataPrice(transaction, processableBlockHeader))) { return true; } } @@ -474,7 +355,6 @@ private void updateTransactionResultTracking( final Transaction transaction, final TransactionProcessingResult result) { final long gasUsedByTransaction = transaction.getGasLimit() - result.getGasRemaining(); - final long cumulativeGasUsed = transactionSelectionResult.getCumulativeGasUsed() + gasUsedByTransaction; @@ -522,4 +402,140 @@ private boolean blockOccupancyAboveThreshold() { return occupancyRatio >= minBlockOccupancyRatio; } + + public static class TransactionValidationResult { + private final Transaction transaction; + private final ValidationResult validationResult; + + public TransactionValidationResult( + final Transaction transaction, + final ValidationResult validationResult) { + this.transaction = transaction; + this.validationResult = validationResult; + } + + public Transaction getTransaction() { + return transaction; + } + + public ValidationResult getValidationResult() { + return validationResult; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TransactionValidationResult that = (TransactionValidationResult) o; + return Objects.equals(transaction, that.transaction) + && Objects.equals(validationResult, that.validationResult); + } + + @Override + public int hashCode() { + return Objects.hash(transaction, validationResult); + } + } + + public static class TransactionSelectionResults { + + private final Map> transactionsByType = new HashMap<>(); + private final List receipts = Lists.newArrayList(); + private final List invalidTransactions = Lists.newArrayList(); + private long cumulativeGasUsed = 0; + private long cumulativeDataGasUsed = 0; + + private void update( + final Transaction transaction, + final TransactionReceipt receipt, + final long gasUsed, + final long dataGasUsed) { + transactionsByType + .computeIfAbsent(transaction.getType(), type -> new ArrayList<>()) + .add(transaction); + receipts.add(receipt); + cumulativeGasUsed += gasUsed; + cumulativeDataGasUsed += dataGasUsed; + traceLambda( + LOG, + "New selected transaction {}, total transactions {}, cumulative gas used {}, cumulative data gas used {}", + transaction::toTraceLog, + () -> transactionsByType.values().stream().mapToInt(List::size).sum(), + () -> cumulativeGasUsed, + () -> cumulativeDataGasUsed); + } + + private void updateWithInvalidTransaction( + final Transaction transaction, + final ValidationResult validationResult) { + invalidTransactions.add(new TransactionValidationResult(transaction, validationResult)); + } + + public List getTransactions() { + return streamAllTransactions().collect(Collectors.toList()); + } + + public List getTransactionsByType(final TransactionType type) { + return transactionsByType.getOrDefault(type, List.of()); + } + + public List getReceipts() { + return receipts; + } + + public long getCumulativeGasUsed() { + return cumulativeGasUsed; + } + + public long getCumulativeDataGasUsed() { + return cumulativeDataGasUsed; + } + + public List getInvalidTransactions() { + return invalidTransactions; + } + + private Stream streamAllTransactions() { + return transactionsByType.values().stream().flatMap(List::stream); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TransactionSelectionResults that = (TransactionSelectionResults) o; + return cumulativeGasUsed == that.cumulativeGasUsed + && cumulativeDataGasUsed == that.cumulativeDataGasUsed + && transactionsByType.equals(that.transactionsByType) + && receipts.equals(that.receipts) + && invalidTransactions.equals(that.invalidTransactions); + } + + @Override + public int hashCode() { + return Objects.hash( + transactionsByType, + receipts, + invalidTransactions, + cumulativeGasUsed, + cumulativeDataGasUsed); + } + + public String toTraceLog() { + return "cumulativeGasUsed=" + + cumulativeGasUsed + + ", cumulativeDataGasUsed=" + + cumulativeDataGasUsed + + ", transactions=" + + streamAllTransactions().map(Transaction::toTraceLog).collect(Collectors.joining("; ")); + } + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculator.java index 70a0f4bf143..945ecfcb5e2 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculator.java @@ -14,7 +14,8 @@ */ package org.hyperledger.besu.ethereum.core.feemarket; -import org.hyperledger.besu.datatypes.DataGas; +import static org.hyperledger.besu.util.Slf4jLambdaHelper.traceLambda; + import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; @@ -22,17 +23,32 @@ import java.math.BigInteger; import java.util.Optional; -@FunctionalInterface +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public interface TransactionPriceCalculator { - Wei price(Transaction transaction, Optional baseFee); + Wei price(Transaction transaction, Optional maybeFee); + + default Wei dataPrice(final Transaction transaction, final ProcessableBlockHeader blockHeader) { + return Wei.ZERO; + } + + class Frontier implements TransactionPriceCalculator { + @Override + public Wei price(final Transaction transaction, final Optional maybeFee) { + return transaction.getGasPrice().orElse(Wei.ZERO); + } - static TransactionPriceCalculator frontier() { - return (transaction, baseFee) -> transaction.getGasPrice().orElse(Wei.ZERO); + @Override + public Wei dataPrice(final Transaction transaction, final ProcessableBlockHeader blockHeader) { + return Wei.ZERO; + } } - static TransactionPriceCalculator eip1559() { - return (transaction, maybeBaseFee) -> { - final Wei baseFee = maybeBaseFee.orElseThrow(); + class EIP1559 implements TransactionPriceCalculator { + @Override + public Wei price(final Transaction transaction, final Optional maybeFee) { + final Wei baseFee = maybeFee.orElseThrow(); if (!transaction.getType().supports1559FeeMarket()) { return transaction.getGasPrice().orElse(Wei.ZERO); } @@ -43,7 +59,50 @@ static TransactionPriceCalculator eip1559() { price = maxFeePerGas; } return price; - }; + } + } + + class DataBlob extends EIP1559 { + private static final Logger LOG = LoggerFactory.getLogger(DataBlob.class); + private final BigInteger minDataGasPrice; + private final BigInteger dataGasPriceUpdateFraction; + + public DataBlob(final int minDataGasPrice, final int dataGasPriceUpdateFraction) { + this.minDataGasPrice = BigInteger.valueOf(minDataGasPrice); + this.dataGasPriceUpdateFraction = BigInteger.valueOf(dataGasPriceUpdateFraction); + } + + @Override + public Wei dataPrice(final Transaction transaction, final ProcessableBlockHeader blockHeader) { + final var excessDataGas = blockHeader.getExcessDataGas().orElseThrow(); + + final var dataGasPrice = + Wei.of( + fakeExponential( + minDataGasPrice, excessDataGas.toBigInteger(), dataGasPriceUpdateFraction)); + traceLambda( + LOG, + "block #{} parentExcessDataGas: {} dataGasPrice: {}", + blockHeader::getNumber, + excessDataGas::toShortHexString, + dataGasPrice::toHexString); + + return dataGasPrice; + } + + private BigInteger fakeExponential( + final BigInteger factor, final BigInteger numerator, final BigInteger denominator) { + BigInteger i = BigInteger.ONE; + BigInteger output = BigInteger.ZERO; + BigInteger numeratorAccumulator = factor.multiply(denominator); + while (numeratorAccumulator.compareTo(BigInteger.ZERO) > 0) { + output = output.add(numeratorAccumulator); + numeratorAccumulator = + (numeratorAccumulator.multiply(numerator)).divide(denominator.multiply(i)); + i = i.add(BigInteger.ONE); + } + return output.divide(denominator); + } } static TransactionPriceCalculator dataGas( diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LegacyFeeMarket.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LegacyFeeMarket.java index 8e73ec2948c..ec328d29824 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LegacyFeeMarket.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LegacyFeeMarket.java @@ -22,7 +22,7 @@ public class LegacyFeeMarket implements FeeMarket { private final TransactionPriceCalculator txPriceCalculator; public LegacyFeeMarket() { - this.txPriceCalculator = TransactionPriceCalculator.frontier(); + this.txPriceCalculator = new TransactionPriceCalculator.Frontier(); } @Override diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LondonFeeMarket.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LondonFeeMarket.java index 2fff3a629fa..98b23ad1945 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LondonFeeMarket.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LondonFeeMarket.java @@ -46,7 +46,7 @@ public LondonFeeMarket(final long londonForkBlockNumber) { public LondonFeeMarket( final long londonForkBlockNumber, final Optional baseFeePerGasOverride) { - this(TransactionPriceCalculator.eip1559(), londonForkBlockNumber, baseFeePerGasOverride); + this(new TransactionPriceCalculator.EIP1559(), londonForkBlockNumber, baseFeePerGasOverride); } protected LondonFeeMarket( diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculatorTest.java index 3caa7234908..e57c3a3ee1c 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculatorTest.java @@ -18,8 +18,11 @@ import static org.hyperledger.besu.plugin.data.TransactionType.ACCESS_LIST; import static org.hyperledger.besu.plugin.data.TransactionType.EIP1559; import static org.hyperledger.besu.plugin.data.TransactionType.FRONTIER; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.plugin.data.TransactionType; @@ -37,9 +40,9 @@ public class TransactionPriceCalculatorTest { private static final TransactionPriceCalculator FRONTIER_CALCULATOR = - TransactionPriceCalculator.frontier(); + new TransactionPriceCalculator.Frontier(); private static final TransactionPriceCalculator EIP_1559_CALCULATOR = - TransactionPriceCalculator.eip1559(); + new TransactionPriceCalculator.EIP1559(); private final TransactionPriceCalculator transactionPriceCalculator; private final TransactionType transactionType; @@ -133,6 +136,9 @@ public static Collection data() { @Test public void assertThatCalculatorWorks() { + final ProcessableBlockHeader blockHeader = mock(ProcessableBlockHeader.class); + when(blockHeader.getBaseFee()).thenReturn(baseFee); + assertThat( transactionPriceCalculator.price( Transaction.builder() @@ -143,7 +149,7 @@ public void assertThatCalculatorWorks() { .maxFeePerGas(maxFeePerGas) .chainId(BigInteger.ONE) .build(), - baseFee)) + blockHeader.getBaseFee())) .isEqualByComparingTo(expectedPrice); } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ZeroBaseFeeMarketTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ZeroBaseFeeMarketTest.java index d14510b6ac9..67f4b1e66af 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ZeroBaseFeeMarketTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ZeroBaseFeeMarketTest.java @@ -15,11 +15,14 @@ package org.hyperledger.besu.ethereum.mainnet.feemarket; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import org.hyperledger.besu.crypto.KeyPair; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.datatypes.DataGas; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionTestFixture; import org.hyperledger.besu.plugin.data.TransactionType; @@ -69,10 +72,13 @@ public void getTransactionPriceCalculatorShouldBeEIP1559() { .gasPrice(null) .createTransaction(KEY_PAIR1); + final ProcessableBlockHeader blockHeader = mock(ProcessableBlockHeader.class); + when(blockHeader.getBaseFee()).thenReturn(Optional.of(Wei.ZERO)); + assertThat( zeroBaseFeeMarket .getTransactionPriceCalculator() - .price(transaction, Optional.of(Wei.ZERO))) + .price(transaction, blockHeader.getBaseFee())) .isEqualTo(Wei.of(8)); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementByFeeMarketRule.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementByFeeMarketRule.java index e89cc4600cd..1c2bd84c027 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementByFeeMarketRule.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementByFeeMarketRule.java @@ -24,9 +24,9 @@ public class TransactionReplacementByFeeMarketRule implements TransactionPoolReplacementRule { private static final TransactionPriceCalculator FRONTIER_CALCULATOR = - TransactionPriceCalculator.frontier(); + new TransactionPriceCalculator.Frontier(); private static final TransactionPriceCalculator EIP1559_CALCULATOR = - TransactionPriceCalculator.eip1559(); + new TransactionPriceCalculator.EIP1559(); private final Percentage priceBump; public TransactionReplacementByFeeMarketRule(final Percentage priceBump) {