Skip to content

Commit 4b1e6cf

Browse files
author
danielntmd
committed
fix: add buffer to maxFeePerBlobGas for gas estimation and fix bump loop truncation
The blob validateBlobs estimateGas call was intermittently failing with "max fee per blob gas less than block blob gas fee" because it computed a precise maxFeePerBlobGas that could go stale between the getBlobBaseFee RPC call and the estimateGas RPC call. Since gas estimation is a read-only, we now use a 2x buffer to pass EIP-4844 validation. Also fix integer truncation in the base fee bump loop where ceil is needed to ensure fees increase at small values (e.g. 1 wei).
1 parent ed10419 commit 4b1e6cf

2 files changed

Lines changed: 52 additions & 4 deletions

File tree

yarn-project/ethereum/src/l1_tx_utils/l1_tx_utils.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,50 @@ describe('L1TxUtils', () => {
454454
}
455455
});
456456

457+
it('bumps gas fees correctly at very low wei values (ceiling division)', async () => {
458+
await cheatCodes.setNextBlockBaseFeePerGas(1n);
459+
await cheatCodes.evmMine();
460+
461+
const originalGetBlobBaseFee = l1Client.getBlobBaseFee;
462+
l1Client.getBlobBaseFee = () => Promise.resolve(1n);
463+
464+
const originalEstimate = l1Client.estimateMaxPriorityFeePerGas;
465+
l1Client.estimateMaxPriorityFeePerGas = () => Promise.resolve(0n);
466+
467+
try {
468+
gasUtils.updateConfig({
469+
...defaultL1TxUtilsConfig,
470+
stallTimeMs: 12_000,
471+
priorityFeeBumpPercentage: 0,
472+
minimumPriorityFeePerGas: 0,
473+
});
474+
475+
const gasPrice = await gasUtils['getGasPrice'](undefined, true);
476+
477+
// With ceiling division: (1n * 1125n + 999n) / 1000n = 2n
478+
expect(gasPrice.maxFeePerGas).toBe(2n);
479+
expect(gasPrice.maxFeePerBlobGas).toBe(2n);
480+
481+
// Verify compounding works across multiple iterations
482+
gasUtils.updateConfig({
483+
...defaultL1TxUtilsConfig,
484+
stallTimeMs: 24_000,
485+
priorityFeeBumpPercentage: 0,
486+
minimumPriorityFeePerGas: 0,
487+
});
488+
489+
const gasPrice2 = await gasUtils['getGasPrice'](undefined, true);
490+
491+
// Iteration 1: ceil(1 * 1125 / 1000) = 2
492+
// Iteration 2: ceil(2 * 1125 / 1000) = ceil(2.25) = 3
493+
expect(gasPrice2.maxFeePerGas).toBe(3n);
494+
expect(gasPrice2.maxFeePerBlobGas).toBe(3n);
495+
} finally {
496+
l1Client.getBlobBaseFee = originalGetBlobBaseFee;
497+
l1Client.estimateMaxPriorityFeePerGas = originalEstimate;
498+
}
499+
});
500+
457501
it('calculates correct gas prices for retry attempts', async () => {
458502
await cheatCodes.setNextBlockBaseFeePerGas(WEI_CONST);
459503
await cheatCodes.evmMine();

yarn-project/ethereum/src/l1_tx_utils/readonly_l1_tx_utils.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,10 @@ export class ReadOnlyL1TxUtils {
130130
const numBlocks = Math.ceil(gasConfig.stallTimeMs! / BLOCK_TIME_MS);
131131
for (let i = 0; i < numBlocks; i++) {
132132
// each block can go up 12.5% from previous baseFee
133-
maxFeePerGas = (maxFeePerGas * (1_000n + 125n)) / 1_000n;
133+
// ceil, (a+b-1)/b, to avoid truncation at small values (e.g. 1 wei blob base fee)
134+
maxFeePerGas = (maxFeePerGas * (1_000n + 125n) + 999n) / 1_000n;
134135
// same for blob gas fee
135-
maxFeePerBlobGas = (maxFeePerBlobGas * (1_000n + 125n)) / 1_000n;
136+
maxFeePerBlobGas = (maxFeePerBlobGas * (1_000n + 125n) + 999n) / 1_000n;
136137
}
137138

138139
if (attempt > 0) {
@@ -242,13 +243,16 @@ export class ReadOnlyL1TxUtils {
242243
const gasConfig = { ...this.config, ..._gasConfig };
243244
let initialEstimate = 0n;
244245
if (_blobInputs) {
245-
// @note requests with blobs also require maxFeePerBlobGas to be set
246+
// @note requests with blobs also require maxFeePerBlobGas to be set.
247+
// Use 2x buffer for maxFeePerBlobGas to avoid stale fees and to pass EIP-4844 validation (even if it is a gas estimation call).
248+
// 1. maxFeePerBlobGas >= blobBaseFee
249+
// 2. account balance >= gas * maxFeePerGas + maxFeePerBlobGas * blobCount + value
246250
const gasPrice = await this.getGasPrice(gasConfig, true, 0);
247251
initialEstimate = await this.client.estimateGas({
248252
account,
249253
...request,
250254
..._blobInputs,
251-
maxFeePerBlobGas: gasPrice.maxFeePerBlobGas!,
255+
maxFeePerBlobGas: gasPrice.maxFeePerBlobGas! * 2n,
252256
gas: MAX_L1_TX_LIMIT,
253257
blockTag: 'latest',
254258
});

0 commit comments

Comments
 (0)