Skip to content

Commit c0af167

Browse files
feat(peerDAS): add max number of blob per transaction (#8761)
add max blob per transaction Signed-off-by: Gabriel-Trintinalia <gabriel.trintinalia@consensys.net> Signed-off-by: Sally MacFarlane <macfarla.github@gmail.com> Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
1 parent ccf0dba commit c0af167

File tree

17 files changed

+347
-137
lines changed

17 files changed

+347
-137
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#### Fusaka Devnet
2828
- EIP-7825 - Transaction gas limit cap [#8700](https://github.com/hyperledger/besu/pull/8700)
2929
- EIP-7823 - Modexp upper bounds [#8632](https://github.com/hyperledger/besu/pull/8632)
30+
- EIP-7892 - Max number of blobs per transaction [#8761](https://github.com/hyperledger/besu/pull/8761)
3031
- Implement rewardPercentile cap in eth_feeHistory [#8748](https://github.com/hyperledger/besu/pull/8748)
3132

3233
### Bug fixes

config/src/main/java/org/hyperledger/besu/config/BlobSchedule.java

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,31 +21,48 @@
2121
/** The Blob schedule for a particular fork. */
2222
public class BlobSchedule {
2323
/** The constant CANCUN_DEFAULT. */
24-
public static final BlobSchedule CANCUN_DEFAULT = new BlobSchedule(3, 6, 3338477);
24+
public static final BlobSchedule CANCUN_DEFAULT = BlobSchedule.create(3, 6, 6, 3338477);
2525

2626
/** The constant PRAGUE_DEFAULT. */
27-
public static final BlobSchedule PRAGUE_DEFAULT = new BlobSchedule(6, 9, 5007716);
27+
public static final BlobSchedule PRAGUE_DEFAULT = BlobSchedule.create(6, 9, 9, 5007716);
2828

2929
private final int target;
3030
private final int max;
31+
private final int maxPerTransaction;
3132
private final int baseFeeUpdateFraction;
3233

34+
private BlobSchedule(
35+
final int target,
36+
final int max,
37+
final int maxPerTransaction,
38+
final int baseFeeUpdateFraction) {
39+
this.target = target;
40+
this.max = max;
41+
this.maxPerTransaction = maxPerTransaction;
42+
this.baseFeeUpdateFraction = baseFeeUpdateFraction;
43+
}
44+
3345
/**
34-
* Instantiates a new Blob schedule.
46+
* Creates a BlobSchedule from a JSON configuration.
3547
*
36-
* @param blobScheduleConfigRoot the blob schedule config root
48+
* @param blobScheduleConfigRoot the JSON configuration for the BlobSchedule
49+
* @return a BlobSchedule instance
3750
*/
38-
public BlobSchedule(final ObjectNode blobScheduleConfigRoot) {
39-
this(
40-
JsonUtil.getInt(blobScheduleConfigRoot, "target").orElseThrow(),
41-
JsonUtil.getInt(blobScheduleConfigRoot, "max").orElseThrow(),
42-
JsonUtil.getInt(blobScheduleConfigRoot, "basefeeupdatefraction").orElseThrow());
51+
public static BlobSchedule create(final ObjectNode blobScheduleConfigRoot) {
52+
int target = JsonUtil.getInt(blobScheduleConfigRoot, "target").orElseThrow();
53+
int max = JsonUtil.getInt(blobScheduleConfigRoot, "max").orElseThrow();
54+
int maxPerTransaction = JsonUtil.getInt(blobScheduleConfigRoot, "maxblobspertx").orElse(max);
55+
int baseFeeUpdateFraction =
56+
JsonUtil.getInt(blobScheduleConfigRoot, "basefeeupdatefraction").orElseThrow();
57+
return create(target, max, maxPerTransaction, baseFeeUpdateFraction);
4358
}
4459

45-
private BlobSchedule(final int target, final int max, final int baseFeeUpdateFraction) {
46-
this.target = target;
47-
this.max = max;
48-
this.baseFeeUpdateFraction = baseFeeUpdateFraction;
60+
private static BlobSchedule create(
61+
final int target,
62+
final int max,
63+
final int maxPerTransaction,
64+
final int baseFeeUpdateFraction) {
65+
return new BlobSchedule(target, max, maxPerTransaction, baseFeeUpdateFraction);
4966
}
5067

5168
/**
@@ -66,6 +83,15 @@ public int getMax() {
6683
return max;
6784
}
6885

86+
/**
87+
* Gets max per transaction.
88+
*
89+
* @return the max per transaction
90+
*/
91+
public int getMaxPerTransaction() {
92+
return maxPerTransaction;
93+
}
94+
6995
/**
7096
* Gets base fee update fraction.
7197
*
@@ -100,7 +126,7 @@ public String toString() {
100126
public static class NoBlobSchedule extends BlobSchedule {
101127
/** Constructs a NoBlobSchedule */
102128
public NoBlobSchedule() {
103-
super(0, 0, 0);
129+
super(0, 0, 0, 0);
104130
}
105131

106132
@Override
@@ -118,6 +144,11 @@ public int getBaseFeeUpdateFraction() {
118144
throw new UnsupportedOperationException("NoBlobSchedule does not support this operation.");
119145
}
120146

147+
@Override
148+
public int getMaxPerTransaction() {
149+
throw new UnsupportedOperationException("NoBlobSchedule does not support this operation.");
150+
}
151+
121152
@Override
122153
public Map<String, Object> asMap() {
123154
throw new UnsupportedOperationException("NoBlobSchedule does not support this operation.");

config/src/main/java/org/hyperledger/besu/config/BlobScheduleOptions.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public Optional<BlobSchedule> getFutureEips() {
133133
* @return the blob schedule
134134
*/
135135
public Optional<BlobSchedule> getBlobSchedule(final String key) {
136-
return JsonUtil.getObjectNode(blobScheduleOptionsConfigRoot, key).map(BlobSchedule::new);
136+
return JsonUtil.getObjectNode(blobScheduleOptionsConfigRoot, key).map(BlobSchedule::create);
137137
}
138138

139139
/**

config/src/test/java/org/hyperledger/besu/config/BlobScheduleOptionsTest.java

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,34 +16,73 @@
1616

1717
import static org.assertj.core.api.Assertions.assertThat;
1818

19+
import java.util.Optional;
20+
import java.util.function.Supplier;
21+
22+
import org.junit.jupiter.api.BeforeEach;
1923
import org.junit.jupiter.api.Test;
2024

2125
public class BlobScheduleOptionsTest {
2226

27+
private BlobScheduleOptions options;
28+
29+
@BeforeEach
30+
public void setupConfig() {
31+
final GenesisConfig genesisConfig =
32+
GenesisConfig.fromResource("/mainnet_with_blob_schedule.json");
33+
final GenesisConfigOptions configOptions = genesisConfig.getConfigOptions();
34+
assertThat(configOptions.getBlobScheduleOptions()).isNotEmpty();
35+
options = configOptions.getBlobScheduleOptions().get();
36+
}
37+
2338
@Test
2439
public void blobScheduleIsParsed() {
25-
final GenesisConfig genesisConfigFile =
26-
GenesisConfig.fromResource("/mainnet_with_blob_schedule.json");
27-
final GenesisConfigOptions configOptions = genesisConfigFile.getConfigOptions();
40+
assertParsed(options::getCancun, 4, 7, 3338477);
41+
assertParsed(options::getPrague, 7, 10, 5007716);
42+
assertParsed(options::getBpo1, 11, 12, 5007716);
43+
assertParsed(options::getBpo2, 21, 22, 5007716);
44+
assertParsed(options::getBpo3, 31, 32, 5007716);
45+
assertParsed(options::getBpo4, 41, 42, 5007716);
46+
assertParsed(options::getBpo5, 51, 52, 5007716);
47+
}
2848

29-
assertThat(configOptions.getBlobScheduleOptions()).isNotEmpty();
30-
final BlobScheduleOptions blobScheduleOptions = configOptions.getBlobScheduleOptions().get();
31-
assertThat(blobScheduleOptions.getCancun()).isNotEmpty();
32-
assertThat(blobScheduleOptions.getCancun().get().getTarget()).isEqualTo(4);
33-
assertThat(blobScheduleOptions.getCancun().get().getMax()).isEqualTo(7);
34-
assertThat(blobScheduleOptions.getCancun().get().getBaseFeeUpdateFraction()).isEqualTo(3338477);
35-
assertThat(blobScheduleOptions.getPrague()).isNotEmpty();
36-
assertThat(blobScheduleOptions.getPrague().get().getTarget()).isEqualTo(7);
37-
assertThat(blobScheduleOptions.getPrague().get().getMax()).isEqualTo(10);
38-
assertThat(blobScheduleOptions.getPrague().get().getBaseFeeUpdateFraction()).isEqualTo(5007716);
39-
assertThat(blobScheduleOptions.getOsaka()).isNotEmpty();
40-
assertThat(blobScheduleOptions.getOsaka().get().getTarget()).isEqualTo(10);
41-
assertThat(blobScheduleOptions.getOsaka().get().getMax()).isEqualTo(13);
42-
assertThat(blobScheduleOptions.getOsaka().get().getBaseFeeUpdateFraction()).isEqualTo(5007716);
43-
assertThat(blobScheduleOptions.getFutureEips()).isNotEmpty();
44-
assertThat(blobScheduleOptions.getFutureEips().get().getTarget()).isEqualTo(12);
45-
assertThat(blobScheduleOptions.getFutureEips().get().getMax()).isEqualTo(16);
46-
assertThat(blobScheduleOptions.getFutureEips().get().getBaseFeeUpdateFraction())
47-
.isEqualTo(5007740);
49+
@Test
50+
public void blobScheduleDefaults() {
51+
assertThat(BlobSchedule.CANCUN_DEFAULT.getTarget()).isEqualTo(3);
52+
assertThat(BlobSchedule.CANCUN_DEFAULT.getMaxPerTransaction()).isEqualTo(6);
53+
assertThat(BlobSchedule.CANCUN_DEFAULT.getMax()).isEqualTo(6);
54+
assertThat(BlobSchedule.PRAGUE_DEFAULT.getTarget()).isEqualTo(6);
55+
assertThat(BlobSchedule.PRAGUE_DEFAULT.getMaxPerTransaction()).isEqualTo(9);
56+
assertThat(BlobSchedule.PRAGUE_DEFAULT.getMax()).isEqualTo(9);
57+
}
58+
59+
@Test
60+
public void blobScheduleMaxPerTransactionDefaultsToMax() {
61+
Optional<BlobSchedule> cancun = options.getCancun();
62+
assertThat(cancun).isNotEmpty();
63+
BlobSchedule schedule = cancun.get();
64+
assertThat(schedule.getMaxPerTransaction()).isEqualTo(schedule.getMax());
65+
}
66+
67+
@Test
68+
public void blobScheduleMaxPerTransactionIsSpecified() {
69+
Optional<BlobSchedule> bpo1 = options.getBpo1();
70+
assertThat(bpo1).isNotEmpty();
71+
BlobSchedule schedule = bpo1.get();
72+
assertThat(schedule.getMax()).isEqualTo(12);
73+
assertThat(schedule.getMaxPerTransaction()).isEqualTo(13);
74+
}
75+
76+
private void assertParsed(
77+
final Supplier<Optional<BlobSchedule>> scheduleSupplier,
78+
final int expectedTarget,
79+
final int expectedMax,
80+
final int expectedBaseFeeUpdateFraction) {
81+
82+
Optional<BlobSchedule> schedule = scheduleSupplier.get();
83+
assertThat(schedule).isNotEmpty();
84+
assertThat(schedule.get().getTarget()).isEqualTo(expectedTarget);
85+
assertThat(schedule.get().getMax()).isEqualTo(expectedMax);
86+
assertThat(schedule.get().getBaseFeeUpdateFraction()).isEqualTo(expectedBaseFeeUpdateFraction);
4887
}
4988
}

config/src/test/resources/mainnet_with_blob_schedule.json

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,34 @@
2727
"max": 10,
2828
"baseFeeUpdateFraction": 5007716
2929
},
30-
"osaka": {
31-
"target": 10,
32-
"max": 13,
30+
"bpo1": {
31+
"target": 11,
32+
"max": 12,
33+
"maxBlobsPerTx": 13,
34+
"baseFeeUpdateFraction": 5007716
35+
},
36+
"bpo2": {
37+
"target": 21,
38+
"max": 22,
39+
"maxBlobsPerTx": 23,
40+
"baseFeeUpdateFraction": 5007716
41+
},
42+
"bpo3": {
43+
"target": 31,
44+
"max": 32,
45+
"maxBlobsPerTx": 33,
46+
"baseFeeUpdateFraction": 5007716
47+
},
48+
"bpo4": {
49+
"target": 41,
50+
"max": 42,
51+
"maxBlobsPerTx": 43,
52+
"baseFeeUpdateFraction": 5007716
53+
},
54+
"bpo5": {
55+
"target": 51,
56+
"max": 52,
57+
"maxBlobsPerTx": 53,
3358
"baseFeeUpdateFraction": 5007716
3459
},
3560
"future_eips": {

ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineNewPayload.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,12 @@ protected ValidationResult<RpcErrorType> validateBlobs(
512512
return ValidationResult.invalid(
513513
RpcErrorType.INVALID_BLOB_COUNT, "There must be at least one blob");
514514
}
515+
if (protocolSpec.getGasCalculator().blobGasCost(versionedHashes.get().size())
516+
> protocolSpec.getGasLimitCalculator().transactionBlobGasLimitCap()) {
517+
return ValidationResult.invalid(
518+
RpcErrorType.INVALID_BLOB_COUNT,
519+
String.format("Blob transaction has too many blobs: %d", versionedHashes.get().size()));
520+
}
515521
transactionVersionedHashes.addAll(versionedHashes.get());
516522
}
517523

ethereum/core/src/main/java/org/hyperledger/besu/ethereum/GasLimitCalculator.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,16 @@ default long computeExcessBlobGas(
7676
default long transactionGasLimitCap() {
7777
return Long.MAX_VALUE;
7878
}
79+
80+
/**
81+
* Returns the transaction blob gas limit cap.
82+
*
83+
* <p>Before Osaka, there was no limit, the max number of blobs a transaction could have was
84+
* limited by the blob gas limit.
85+
*
86+
* @return the transaction blob gas limit cap
87+
*/
88+
default long transactionBlobGasLimitCap() {
89+
return BLOB_GAS_LIMIT;
90+
}
7991
}

ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CancunTargetingGasLimitCalculator.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
*/
1515
package org.hyperledger.besu.ethereum.mainnet;
1616

17+
import static com.google.common.base.Preconditions.checkArgument;
18+
1719
import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket;
1820
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
1921

@@ -22,6 +24,9 @@ public class CancunTargetingGasLimitCalculator extends LondonTargetingGasLimitCa
2224
/** The mainnet default maximum number of blobs per block for Cancun */
2325
private static final int DEFAULT_MAX_BLOBS_PER_BLOCK_CANCUN = 6;
2426

27+
/** The default target number of blobs per transaction for Cancun */
28+
private static final int DEFAULT_MAX_BLOBS_PER_TRANSACTION_CANCUN = 6;
29+
2530
/** The default mainnet target blobs per block for Cancun */
2631
private static final int DEFAULT_TARGET_BLOBS_PER_BLOCK_CANCUN = 3;
2732

@@ -32,6 +37,7 @@ public class CancunTargetingGasLimitCalculator extends LondonTargetingGasLimitCa
3237
private final long blobGasPerBlob;
3338
protected final int maxBlobsPerBlock;
3439
protected final int targetBlobsPerBlock;
40+
private final long transactionBlobGasLimitCap;
3541

3642
public CancunTargetingGasLimitCalculator(
3743
final long londonForkBlock,
@@ -42,7 +48,8 @@ public CancunTargetingGasLimitCalculator(
4248
feeMarket,
4349
gasCalculator,
4450
DEFAULT_MAX_BLOBS_PER_BLOCK_CANCUN,
45-
DEFAULT_TARGET_BLOBS_PER_BLOCK_CANCUN);
51+
DEFAULT_TARGET_BLOBS_PER_BLOCK_CANCUN,
52+
DEFAULT_MAX_BLOBS_PER_TRANSACTION_CANCUN);
4653
}
4754

4855
/**
@@ -54,13 +61,20 @@ public CancunTargetingGasLimitCalculator(
5461
final BaseFeeMarket feeMarket,
5562
final GasCalculator gasCalculator,
5663
final int maxBlobsPerBlock,
57-
final int targetBlobsPerBlock) {
64+
final int targetBlobsPerBlock,
65+
final int maxBlobsPerTransaction) {
5866
super(londonForkBlock, feeMarket);
5967
this.blobGasPerBlob = gasCalculator.getBlobGasPerBlob();
6068
this.targetBlobGasPerBlock = blobGasPerBlob * targetBlobsPerBlock;
6169
this.maxBlobGasPerBlock = blobGasPerBlob * maxBlobsPerBlock;
6270
this.maxBlobsPerBlock = maxBlobsPerBlock;
6371
this.targetBlobsPerBlock = targetBlobsPerBlock;
72+
this.transactionBlobGasLimitCap = gasCalculator.getBlobGasPerBlob() * maxBlobsPerTransaction;
73+
checkArgument(
74+
maxBlobsPerBlock >= maxBlobsPerTransaction,
75+
"maxBlobsPerTransaction (%s) must not be greater than maxBlobsPerBlock (%s)",
76+
maxBlobsPerTransaction,
77+
maxBlobsPerBlock);
6478
}
6579

6680
@Override
@@ -96,4 +110,9 @@ public long getTargetBlobGasPerBlock() {
96110
public long getMaxBlobGasPerBlock() {
97111
return maxBlobGasPerBlock;
98112
}
113+
114+
@Override
115+
public long transactionBlobGasLimitCap() {
116+
return transactionBlobGasLimitCap;
117+
}
99118
}

ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlobsValidator.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818

1919
import org.hyperledger.besu.datatypes.BlobType;
2020
import org.hyperledger.besu.datatypes.VersionedHash;
21+
import org.hyperledger.besu.ethereum.GasLimitCalculator;
2122
import org.hyperledger.besu.ethereum.core.Transaction;
2223
import org.hyperledger.besu.ethereum.core.kzg.BlobsWithCommitments;
2324
import org.hyperledger.besu.ethereum.core.kzg.KZGCommitment;
2425
import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason;
26+
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
2527

2628
import java.util.List;
2729
import java.util.Set;
@@ -31,9 +33,16 @@
3133

3234
public class MainnetBlobsValidator {
3335
final Set<BlobType> acceptedBlobVersions;
36+
final GasLimitCalculator gasLimitCalculator;
37+
final GasCalculator gasCalculator;
3438

35-
public MainnetBlobsValidator(final Set<BlobType> acceptedBlobVersions) {
39+
public MainnetBlobsValidator(
40+
final Set<BlobType> acceptedBlobVersions,
41+
final GasLimitCalculator gasLimitCalculator,
42+
final GasCalculator gasCalculator) {
3643
this.acceptedBlobVersions = acceptedBlobVersions;
44+
this.gasLimitCalculator = gasLimitCalculator;
45+
this.gasCalculator = gasCalculator;
3746
}
3847

3948
public ValidationResult<TransactionInvalidReason> validateTransactionsBlobs(
@@ -62,6 +71,13 @@ public ValidationResult<TransactionInvalidReason> validateTransactionsBlobs(
6271
}
6372
final List<VersionedHash> versionedHashes = transaction.getVersionedHashes().get();
6473

74+
final long blobGasCost = gasCalculator.blobGasCost(versionedHashes.size());
75+
if (blobGasCost > gasLimitCalculator.transactionBlobGasLimitCap()) {
76+
final String error =
77+
String.format("Blob transaction has too many blobs: %d", versionedHashes.size());
78+
return ValidationResult.invalid(TransactionInvalidReason.INVALID_BLOBS, error);
79+
}
80+
6581
for (int i = 0; i < versionedHashes.size(); i++) {
6682
final KZGCommitment commitment = blobsWithCommitments.getKzgCommitments().get(i);
6783
final VersionedHash versionedHash = versionedHashes.get(i);

0 commit comments

Comments
 (0)