diff --git a/CHANGELOG.md b/CHANGELOG.md index 6898dc39a3c..076fbdaa5a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Additions and Improvements - "Big-EOF" (the EOF version initially slotted for Shanghai) has been moved from Cancun to FutureEIPs [#5429](https://github.com/hyperledger/besu/pull/5429) +- EIP-4844: Zero blob transactions are invalid [#5425](https://github.com/hyperledger/besu/pull/5425) ### Bug Fixes - Fix eth_feeHistory response for the case in which blockCount is higher than highestBlock requested. [#5397](https://github.com/hyperledger/besu/pull/5397) diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionCompleteResultTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionCompleteResultTest.java index 674e46c825c..5c0a6c8f46f 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionCompleteResultTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionCompleteResultTest.java @@ -41,6 +41,7 @@ public void eip1559TransactionWithShortWeiVals() { new TransactionCompleteResult( new TransactionWithMetadata( new TransactionTestFixture() + .type(TransactionType.EIP1559) .maxFeePerGas(Optional.of(Wei.ONE)) .maxPriorityFeePerGas(Optional.of(Wei.ZERO)) .createTransaction(gen.generateKeyPair()), 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 f04d1a8a2c7..093e1e0486f 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 @@ -186,6 +186,8 @@ public Transaction( if (transactionType.supportsBlob()) { checkArgument( versionedHashes.isPresent(), "Must specify blob versioned hashes for blob transaction"); + checkArgument( + !versionedHashes.get().isEmpty(), "Blob transaction must have at least one blob"); checkArgument( maxFeePerDataGas.isPresent(), "Must specify max fee per data gas for blob transaction"); } diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java index 165140c15c1..d766abf1017 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java @@ -16,21 +16,29 @@ import org.hyperledger.besu.crypto.KeyPair; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.AccessListEntry; import org.hyperledger.besu.plugin.data.TransactionType; import java.math.BigInteger; +import java.util.List; import java.util.Optional; import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; public class TransactionTestFixture { + private static final Hash DEFAULT_VERSIONED_HASH = + Hash.wrap( + Bytes32.wrap( + Bytes.concatenate(Bytes.fromHexString("0x01"), Bytes.repeat((byte) 42, 31)))); private TransactionType transactionType = TransactionType.FRONTIER; private long nonce = 0; - private Wei gasPrice = Wei.of(5000); + private Optional gasPrice = Optional.empty(); private long gasLimit = 5000; @@ -45,6 +53,10 @@ public class TransactionTestFixture { private Optional maxPriorityFeePerGas = Optional.empty(); private Optional maxFeePerGas = Optional.empty(); + private Optional maxFeePerDataGas = Optional.empty(); + + private Optional> accessListEntries = Optional.empty(); + private Optional> versionedHashes = Optional.empty(); private Optional v = Optional.empty(); @@ -53,18 +65,35 @@ public Transaction createTransaction(final KeyPair keys) { builder .type(transactionType) .gasLimit(gasLimit) - .gasPrice(gasPrice) .nonce(nonce) .payload(payload) .value(value) .sender(sender); + switch (transactionType) { + case FRONTIER: + builder.gasPrice(gasPrice.orElse(Wei.of(5000))); + break; + case ACCESS_LIST: + builder.gasPrice(gasPrice.orElse(Wei.of(5000))); + builder.accessList(accessListEntries.orElse(List.of())); + break; + case EIP1559: + builder.maxPriorityFeePerGas(maxPriorityFeePerGas.orElse(Wei.of(500))); + builder.maxFeePerGas(maxFeePerGas.orElse(Wei.of(5000))); + builder.accessList(accessListEntries.orElse(List.of())); + break; + case BLOB: + builder.maxPriorityFeePerGas(maxPriorityFeePerGas.orElse(Wei.of(500))); + builder.maxFeePerGas(maxFeePerGas.orElse(Wei.of(5000))); + builder.accessList(accessListEntries.orElse(List.of())); + builder.maxFeePerDataGas(maxFeePerDataGas.orElse(Wei.ONE)); + builder.versionedHashes(versionedHashes.orElse(List.of(DEFAULT_VERSIONED_HASH))); + break; + } + to.ifPresent(builder::to); chainId.ifPresent(builder::chainId); - - maxPriorityFeePerGas.ifPresent(builder::maxPriorityFeePerGas); - maxFeePerGas.ifPresent(builder::maxFeePerGas); - v.ifPresent(builder::v); return builder.signAndBuild(keys); @@ -81,7 +110,7 @@ public TransactionTestFixture nonce(final long nonce) { } public TransactionTestFixture gasPrice(final Wei gasPrice) { - this.gasPrice = gasPrice; + this.gasPrice = Optional.ofNullable(gasPrice); return this; } @@ -125,6 +154,21 @@ public TransactionTestFixture maxFeePerGas(final Optional maxFeePerGas) { return this; } + public TransactionTestFixture maxFeePerDataGas(final Optional maxFeePerDataGas) { + this.maxFeePerDataGas = maxFeePerDataGas; + return this; + } + + public TransactionTestFixture accessList(final List accessListEntries) { + this.accessListEntries = Optional.ofNullable(accessListEntries); + return this; + } + + public TransactionTestFixture versionedHashes(final List versionedHashes) { + this.versionedHashes = Optional.ofNullable(versionedHashes); + return this; + } + public TransactionTestFixture v(final Optional v) { this.v = v; return this; diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/TransactionBuilderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/TransactionBuilderTest.java index 7f9605585d9..a4ffbb27948 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/TransactionBuilderTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/TransactionBuilderTest.java @@ -17,18 +17,29 @@ import static java.util.stream.Collectors.toUnmodifiableSet; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; +import org.hyperledger.besu.crypto.KeyPair; +import org.hyperledger.besu.crypto.SignatureAlgorithm; +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.AccessListEntry; import org.hyperledger.besu.plugin.data.TransactionType; +import java.math.BigInteger; import java.util.List; +import java.util.Optional; import java.util.Set; +import java.util.function.Supplier; import java.util.stream.Stream; +import com.google.common.base.Suppliers; import org.junit.Test; public class TransactionBuilderTest { + private static final Supplier SIGNATURE_ALGORITHM = + Suppliers.memoize(SignatureAlgorithmFactory::getInstance); + private static final KeyPair senderKeys = SIGNATURE_ALGORITHM.get().generateKeyPair(); @Test public void guessTypeCanGuessAllTypes() { @@ -50,4 +61,18 @@ public void guessTypeCanGuessAllTypes() { TransactionType.FRONTIER, TransactionType.ACCESS_LIST, TransactionType.EIP1559 }); } + + @Test + public void zeroBlobTransactionIsInvalid() { + try { + new TransactionTestFixture() + .type(TransactionType.BLOB) + .chainId(Optional.of(BigInteger.ONE)) + .versionedHashes(List.of()) + .createTransaction(senderKeys); + fail(); + } catch (IllegalArgumentException iea) { + assertThat(iea).hasMessage("Blob transaction must have at least one blob"); + } + } } 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 6153dfc0020..868c39d5370 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 @@ -504,6 +504,29 @@ public void shouldRejectTooLargeInitcode() { .isEqualTo("Initcode size of 49153 exceeds maximum size of 49152"); } + @Test + public void shouldAcceptTransactionWithAtLeastOneBlob() { + final MainnetTransactionValidator validator = + new MainnetTransactionValidator( + gasCalculator, + GasLimitCalculator.constant(), + FeeMarket.london(0L), + false, + Optional.of(BigInteger.ONE), + Set.of(TransactionType.FRONTIER, TransactionType.EIP1559, TransactionType.BLOB), + 0xc000); + + var blobTx = + new TransactionTestFixture() + .type(TransactionType.BLOB) + .chainId(Optional.of(BigInteger.ONE)) + .createTransaction(senderKeys); + var validationResult = + validator.validate(blobTx, Optional.empty(), transactionValidationParams); + + assertThat(validationResult.isValid()).isTrue(); + } + private Account accountWithNonce(final long nonce) { return account(basicTransaction.getUpfrontCost(0L), nonce); }