Skip to content

Commit

Permalink
fix: blob fees & l1-publisher logging (#11029)
Browse files Browse the repository at this point in the history
fixes: #11005  
fixes: #10066

Few things ended up being included here:
- Tx calculation for blobs
- Configuration to allow for fixed priority fee on all TXs
- Fixes around user configuration to allow for fractional percentages
(e.g. 8.55% increase on fees per retry)
- Stripping errors from eth nodes that included massive hex strings of
data
- Attempting to get revert errors for all actions of l1-publisher
- introduce 5s bot tx interval & fixed priority fee for our sepolia
deployments

---------

Co-authored-by: Santiago Palladino <[email protected]>
  • Loading branch information
spypsy and spalladino authored Jan 10, 2025
1 parent 927eabf commit c2c0bc6
Show file tree
Hide file tree
Showing 14 changed files with 510 additions and 148 deletions.
2 changes: 2 additions & 0 deletions spartan/aztec-network/templates/faucet.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{{- if not .Values.ethereum.externalHost }}
apiVersion: apps/v1
kind: Deployment
metadata:
Expand Down Expand Up @@ -127,3 +128,4 @@ spec:
- protocol: TCP
port: {{ .Values.faucet.apiServerPort }}
targetPort: {{ .Values.faucet.apiServerPort }}
{{ end }}
4 changes: 4 additions & 0 deletions spartan/aztec-network/templates/validator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ spec:
value: {{ .Values.validator.viemPollingInterval | quote }}
- name: SEQ_VIEM_POLLING_INTERVAL_MS
value: {{ .Values.validator.viemPollingInterval | quote }}
- name: L1_FIXED_PRIORITY_FEE_PER_GAS
value: {{ .Values.validator.l1FixedPriorityFeePerGas | quote }}
- name: L1_GAS_LIMIT_BUFFER_PERCENTAGE
value: {{ .Values.validator.l1GasLimitBufferPercentage | quote }}
- name: DATA_DIRECTORY
value: "{{ .Values.validator.dataDir }}"
- name: DATA_STORE_MAP_SIZE_KB
Expand Down
2 changes: 2 additions & 0 deletions spartan/aztec-network/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ validator:
viemPollingInterval: 1000
storageSize: "1Gi"
dataDir: "/data"
l1FixedPriorityFeePerGas: ""
l1GasLimitBufferPercentage: ""

proverNode:
proverPublisherPrivateKey: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ ethereum:
deployL1ContractsPrivateKey:

validator:
l1FixedPriorityFeePerGas: 1
l1GasLimitBufferPercentage: 5
replicas: 48
validatorKeys:
validatorAddresses:
Expand Down Expand Up @@ -73,4 +75,4 @@ proverNode:
proverPublisherPrivateKey:

bot:
txIntervalSeconds: 20
txIntervalSeconds: 5
4 changes: 2 additions & 2 deletions yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export class P2PNetworkTest {
this.logger.info('Syncing mock system time');
const { dateProvider, deployL1ContractsValues } = this.ctx!;
// Send a tx and only update the time after the tx is mined, as eth time is not continuous
const receipt = await this.gasUtils!.sendAndMonitorTransaction({
const { receipt } = await this.gasUtils!.sendAndMonitorTransaction({
to: this.baseAccount.address,
data: '0x',
value: 1n,
Expand Down Expand Up @@ -300,7 +300,7 @@ export class P2PNetworkTest {
this.ctx.deployL1ContractsValues.walletClient,
this.logger,
{
gasLimitBufferPercentage: 20n,
gasLimitBufferPercentage: 20,
maxGwei: 500n,
minGwei: 1n,
maxAttempts: 3,
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/ethereum/src/deploy_l1_contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,7 @@ export async function deployL1Contract(
} else {
// Regular deployment path
const deployData = encodeDeployData({ abi, bytecode, args });
const receipt = await l1TxUtils.sendAndMonitorTransaction({
const { receipt } = await l1TxUtils.sendAndMonitorTransaction({
to: null,
data: deployData,
});
Expand Down
137 changes: 116 additions & 21 deletions yarn-project/ethereum/src/l1_tx_utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Blob } from '@aztec/foundation/blob';
import { EthAddress } from '@aztec/foundation/eth-address';
import { createLogger } from '@aztec/foundation/log';
import { sleep } from '@aztec/foundation/sleep';
Expand All @@ -19,6 +20,7 @@ import { foundry } from 'viem/chains';
import { EthCheatCodes } from './eth_cheat_codes.js';
import { L1TxUtils, defaultL1TxUtilsConfig } from './l1_tx_utils.js';
import { startAnvil } from './test/start_anvil.js';
import { formatViemError } from './utils.js';

const MNEMONIC = 'test test test test test test test test test test test junk';
const WEI_CONST = 1_000_000_000n;
Expand Down Expand Up @@ -71,7 +73,7 @@ describe('GasUtils', () => {
await cheatCodes.evmMine();

gasUtils = new L1TxUtils(publicClient, walletClient, logger, {
gasLimitBufferPercentage: 20n,
gasLimitBufferPercentage: 20,
maxGwei: 500n,
minGwei: 1n,
maxAttempts: 3,
Expand All @@ -92,7 +94,7 @@ describe('GasUtils', () => {
}, 5_000);

it('sends and monitors a simple transaction', async () => {
const receipt = await gasUtils.sendAndMonitorTransaction({
const { receipt } = await gasUtils.sendAndMonitorTransaction({
to: '0x1234567890123456789012345678901234567890',
data: '0x',
value: 0n,
Expand All @@ -106,8 +108,9 @@ describe('GasUtils', () => {
await cheatCodes.setAutomine(false);
await cheatCodes.setIntervalMining(0);

// Ensure initial base fee is low
await cheatCodes.setNextBlockBaseFeePerGas(initialBaseFee);
// Add blob data
const blobData = new Uint8Array(131072).fill(1);
const kzg = Blob.getViemKzgInstance();

const request = {
to: '0x1234567890123456789012345678901234567890' as `0x${string}`,
Expand All @@ -119,12 +122,16 @@ describe('GasUtils', () => {

const originalMaxFeePerGas = WEI_CONST * 10n;
const originalMaxPriorityFeePerGas = WEI_CONST;
const originalMaxFeePerBlobGas = WEI_CONST * 10n;

const txHash = await walletClient.sendTransaction({
...request,
gas: estimatedGas,
maxFeePerGas: originalMaxFeePerGas,
maxPriorityFeePerGas: originalMaxPriorityFeePerGas,
blobs: [blobData],
kzg,
maxFeePerBlobGas: originalMaxFeePerBlobGas,
});

const rawTx = await cheatCodes.getRawTransaction(txHash);
Expand All @@ -142,11 +149,12 @@ describe('GasUtils', () => {
params: [rawTx],
});

// keeping auto-mining disabled to simulate a stuck transaction
// The monitor should detect the stall and create a replacement tx

// Monitor should detect stall and replace with higher gas price
const monitorFn = gasUtils.monitorTransaction(request, txHash, { gasLimit: estimatedGas });
const monitorFn = gasUtils.monitorTransaction(request, txHash, { gasLimit: estimatedGas }, undefined, {
blobs: [blobData],
kzg,
maxFeePerBlobGas: WEI_CONST * 20n,
});

await sleep(2000);
// re-enable mining
Expand All @@ -156,11 +164,12 @@ describe('GasUtils', () => {
// Verify that a replacement transaction was created
expect(receipt.transactionHash).not.toBe(txHash);

// Get details of replacement tx to verify higher gas price
// Get details of replacement tx to verify higher gas prices
const replacementTx = await publicClient.getTransaction({ hash: receipt.transactionHash });

expect(replacementTx.maxFeePerGas!).toBeGreaterThan(originalMaxFeePerGas);
expect(replacementTx.maxPriorityFeePerGas!).toBeGreaterThan(originalMaxPriorityFeePerGas);
expect(replacementTx.maxFeePerBlobGas!).toBeGreaterThan(originalMaxFeePerBlobGas);
}, 20_000);

it('respects max gas price limits during spikes', async () => {
Expand All @@ -173,11 +182,14 @@ describe('GasUtils', () => {
// Mine a new block to make the base fee change take effect
await cheatCodes.evmMine();

const receipt = await gasUtils.sendAndMonitorTransaction({
to: '0x1234567890123456789012345678901234567890',
data: '0x',
value: 0n,
});
const { receipt } = await gasUtils.sendAndMonitorTransaction(
{
to: '0x1234567890123456789012345678901234567890',
data: '0x',
value: 0n,
},
{ maxGwei },
);

expect(receipt.effectiveGasPrice).toBeLessThanOrEqual(maxGwei * WEI_CONST);
}, 60_000);
Expand All @@ -189,15 +201,15 @@ describe('GasUtils', () => {

// First deploy without any buffer
const baselineGasUtils = new L1TxUtils(publicClient, walletClient, logger, {
gasLimitBufferPercentage: 0n,
gasLimitBufferPercentage: 0,
maxGwei: 500n,
minGwei: 10n, // Increased minimum gas price
maxAttempts: 5,
checkIntervalMs: 100,
stallTimeMs: 1000,
});

const baselineTx = await baselineGasUtils.sendAndMonitorTransaction({
const { receipt: baselineTx } = await baselineGasUtils.sendAndMonitorTransaction({
to: EthAddress.ZERO.toString(),
data: SIMPLE_CONTRACT_BYTECODE,
});
Expand All @@ -209,15 +221,15 @@ describe('GasUtils', () => {

// Now deploy with 20% buffer
const bufferedGasUtils = new L1TxUtils(publicClient, walletClient, logger, {
gasLimitBufferPercentage: 20n,
gasLimitBufferPercentage: 20,
maxGwei: 500n,
minGwei: 1n,
maxAttempts: 3,
checkIntervalMs: 100,
stallTimeMs: 1000,
});

const bufferedTx = await bufferedGasUtils.sendAndMonitorTransaction({
const { receipt: bufferedTx } = await bufferedGasUtils.sendAndMonitorTransaction({
to: EthAddress.ZERO.toString(),
data: SIMPLE_CONTRACT_BYTECODE,
});
Expand Down Expand Up @@ -256,7 +268,7 @@ describe('GasUtils', () => {
const initialGasPrice = await gasUtils['getGasPrice']();

// Get retry gas price for 2nd attempt
const retryGasPrice = await gasUtils['getGasPrice'](undefined, 1, initialGasPrice);
const retryGasPrice = await gasUtils['getGasPrice'](undefined, false, 1, initialGasPrice);

// With default config, retry should bump fees by 50%
const expectedPriorityFee = (initialGasPrice.maxPriorityFeePerGas * 150n) / 100n;
Expand All @@ -269,13 +281,13 @@ describe('GasUtils', () => {
it('respects minimum gas price bump for replacements', async () => {
const gasUtils = new L1TxUtils(publicClient, walletClient, logger, {
...defaultL1TxUtilsConfig,
priorityFeeRetryBumpPercentage: 5n, // Set lower than minimum 10%
priorityFeeRetryBumpPercentage: 5, // Set lower than minimum 10%
});

const initialGasPrice = await gasUtils['getGasPrice']();

// Get retry gas price with attempt = 1
const retryGasPrice = await gasUtils['getGasPrice'](undefined, 1, initialGasPrice);
const retryGasPrice = await gasUtils['getGasPrice'](undefined, false, 1, initialGasPrice);

// Should use 10% minimum bump even though config specified 5%
const expectedPriorityFee = (initialGasPrice.maxPriorityFeePerGas * 110n) / 100n;
Expand All @@ -300,6 +312,89 @@ describe('GasUtils', () => {
expect(bufferedEstimate).toBe(expectedEstimate);
});

it('correctly handles transactions with blobs', async () => {
// Create a sample blob
const blobData = new Uint8Array(131072).fill(1); // 128KB blob
const kzg = Blob.getViemKzgInstance();

const { receipt } = await gasUtils.sendAndMonitorTransaction(
{
to: '0x1234567890123456789012345678901234567890',
data: '0x',
value: 0n,
},
undefined,
{
blobs: [blobData],
kzg,
maxFeePerBlobGas: 10000000000n, // 10 gwei
},
);

expect(receipt.status).toBe('success');
expect(receipt.blobGasUsed).toBeDefined();
expect(receipt.blobGasPrice).toBeDefined();
}, 20_000);

it('estimates gas correctly for blob transactions', async () => {
// Create a sample blob
const blobData = new Uint8Array(131072).fill(1); // 128KB blob
const kzg = Blob.getViemKzgInstance();

const request = {
to: '0x1234567890123456789012345678901234567890' as `0x${string}`,
data: '0x' as `0x${string}`,
value: 0n,
};

// Estimate gas without blobs first
const baseEstimate = await gasUtils.estimateGas(walletClient.account!, request);

// Estimate gas with blobs
const blobEstimate = await gasUtils.estimateGas(walletClient.account!, request, undefined, {
blobs: [blobData],
kzg,
maxFeePerBlobGas: 10000000000n,
});
// Blob transactions should require more gas
expect(blobEstimate).toBeGreaterThan(baseEstimate);
}, 20_000);

it('formats eth node errors correctly', async () => {
// Set base fee extremely high to trigger error
const extremelyHighBaseFee = WEI_CONST * 1_000_000n; // 1M gwei
await cheatCodes.setNextBlockBaseFeePerGas(extremelyHighBaseFee);
await cheatCodes.evmMine();

try {
await gasUtils.sendAndMonitorTransaction({
to: '0x1234567890123456789012345678901234567890',
data: '0x',
value: 0n,
});
fail('Should have thrown');
} catch (err: any) {
const formattedError = formatViemError(err);

// Verify the error contains actual newlines, not escaped \n
expect(formattedError).not.toContain('\\n');
expect(formattedError.split('\n').length).toBeGreaterThan(1);

// Check that we have the key error information
expect(formattedError).toContain('fee cap');

// Check request body formatting if present
if (formattedError.includes('Request body:')) {
const bodyStart = formattedError.indexOf('Request body:');
const body = formattedError.slice(bodyStart);
expect(body).toContain('eth_sendRawTransaction');
// Check params are truncated if too long
if (body.includes('0x')) {
expect(body).toContain('...');
}
}
}
}, 10_000);
it('stops trying after timeout', async () => {
await cheatCodes.setAutomine(false);
await cheatCodes.setIntervalMining(0);
Expand Down
Loading

0 comments on commit c2c0bc6

Please sign in to comment.