@aztec/ethereum 3.0.0-nightly.20250930 → 3.0.0-nightly.20251002
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/config.js +2 -2
- package/dest/contracts/fee_asset_handler.d.ts +2 -2
- package/dest/contracts/governance_proposer.d.ts +1 -2
- package/dest/contracts/governance_proposer.d.ts.map +1 -1
- package/dest/contracts/governance_proposer.js +1 -2
- package/dest/contracts/multicall.d.ts +0 -2
- package/dest/contracts/multicall.d.ts.map +1 -1
- package/dest/contracts/multicall.js +2 -4
- package/dest/contracts/rollup.d.ts +2 -2
- package/dest/deploy_l1_contracts.d.ts +11 -1
- package/dest/deploy_l1_contracts.d.ts.map +1 -1
- package/dest/deploy_l1_contracts.js +174 -54
- package/dest/l1_artifacts.d.ts +1978 -0
- package/dest/l1_artifacts.d.ts.map +1 -1
- package/dest/l1_artifacts.js +6 -1
- package/dest/l1_contract_addresses.d.ts +5 -1
- package/dest/l1_contract_addresses.d.ts.map +1 -1
- package/dest/l1_contract_addresses.js +2 -1
- package/dest/l1_tx_utils/factory.d.ts.map +1 -1
- package/dest/l1_tx_utils/factory.js +2 -2
- package/dest/l1_tx_utils/l1_tx_utils.d.ts +14 -26
- package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
- package/dest/l1_tx_utils/l1_tx_utils.js +140 -136
- package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +4 -11
- package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +1 -1
- package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +10 -70
- package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +1 -1
- package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
- package/dest/l1_tx_utils/types.d.ts +15 -2
- package/dest/l1_tx_utils/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/config.ts +2 -2
- package/src/contracts/governance_proposer.ts +3 -4
- package/src/contracts/multicall.ts +4 -4
- package/src/deploy_l1_contracts.ts +161 -51
- package/src/l1_artifacts.ts +8 -0
- package/src/l1_contract_addresses.ts +3 -1
- package/src/l1_tx_utils/factory.ts +2 -2
- package/src/l1_tx_utils/l1_tx_utils.ts +159 -157
- package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +8 -99
- package/src/l1_tx_utils/readonly_l1_tx_utils.ts +1 -1
- package/src/l1_tx_utils/types.ts +16 -2
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { maxBigint } from '@aztec/foundation/bigint';
|
|
1
2
|
import { times } from '@aztec/foundation/collection';
|
|
2
3
|
import { TimeoutError } from '@aztec/foundation/error';
|
|
3
4
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
@@ -30,18 +31,19 @@ import { type L1TxUtilsConfig, l1TxUtilsConfigMappings } from './config.js';
|
|
|
30
31
|
import { LARGE_GAS_LIMIT } from './constants.js';
|
|
31
32
|
import { ReadOnlyL1TxUtils } from './readonly_l1_tx_utils.js';
|
|
32
33
|
import {
|
|
33
|
-
type GasPrice,
|
|
34
34
|
type L1BlobInputs,
|
|
35
35
|
type L1GasConfig,
|
|
36
36
|
type L1TxRequest,
|
|
37
|
+
type L1TxState,
|
|
37
38
|
type SigningCallback,
|
|
38
39
|
TxUtilsState,
|
|
39
40
|
} from './types.js';
|
|
40
41
|
|
|
42
|
+
const MAX_L1_TX_STATES = 32;
|
|
43
|
+
|
|
41
44
|
export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
private nonceManager: NonceManager;
|
|
45
|
+
protected nonceManager: NonceManager;
|
|
46
|
+
protected txs: L1TxState[] = [];
|
|
45
47
|
|
|
46
48
|
constructor(
|
|
47
49
|
public override client: ViemClient,
|
|
@@ -57,21 +59,28 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
public get state() {
|
|
60
|
-
return this.
|
|
62
|
+
return this.txs.at(-1)?.status ?? TxUtilsState.IDLE;
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
public get lastMinedAtBlockNumber() {
|
|
64
|
-
|
|
66
|
+
const minedBlockNumbers = this.txs.map(tx => tx.receipt?.blockNumber).filter(bn => bn !== undefined);
|
|
67
|
+
return minedBlockNumbers.length === 0 ? undefined : maxBigint(...minedBlockNumbers);
|
|
65
68
|
}
|
|
66
69
|
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
protected updateState(l1TxState: L1TxState, newState: TxUtilsState) {
|
|
71
|
+
const oldState = l1TxState.status;
|
|
72
|
+
l1TxState.status = newState;
|
|
73
|
+
const sender = this.getSenderAddress().toString();
|
|
74
|
+
this.logger.debug(
|
|
75
|
+
`State changed from ${TxUtilsState[oldState]} to ${TxUtilsState[newState]} for nonce ${l1TxState.nonce} account ${sender}`,
|
|
76
|
+
);
|
|
69
77
|
}
|
|
70
78
|
|
|
71
|
-
|
|
72
|
-
this.
|
|
73
|
-
this.logger
|
|
74
|
-
|
|
79
|
+
public updateConfig(newConfig: Partial<L1TxUtilsConfig>) {
|
|
80
|
+
this.config = { ...this.config, ...newConfig };
|
|
81
|
+
this.logger.info(
|
|
82
|
+
'Updated L1TxUtils config',
|
|
83
|
+
pickBy(newConfig, (_, key) => key in l1TxUtilsConfigMappings),
|
|
75
84
|
);
|
|
76
85
|
}
|
|
77
86
|
|
|
@@ -103,12 +112,12 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
103
112
|
*/
|
|
104
113
|
public async sendTransaction(
|
|
105
114
|
request: L1TxRequest,
|
|
106
|
-
|
|
115
|
+
gasConfigOverrides?: L1GasConfig,
|
|
107
116
|
blobInputs?: L1BlobInputs,
|
|
108
117
|
stateChange: TxUtilsState = TxUtilsState.SENT,
|
|
109
|
-
): Promise<{ txHash: Hex;
|
|
118
|
+
): Promise<{ txHash: Hex; state: L1TxState }> {
|
|
110
119
|
try {
|
|
111
|
-
const gasConfig = { ...this.config, ...
|
|
120
|
+
const gasConfig = { ...this.config, ...gasConfigOverrides };
|
|
112
121
|
const account = this.getSenderAddress().toString();
|
|
113
122
|
|
|
114
123
|
let gasLimit: bigint;
|
|
@@ -133,32 +142,41 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
133
142
|
chainId: this.client.chain.id,
|
|
134
143
|
});
|
|
135
144
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
145
|
+
const l1TxState: L1TxState = {
|
|
146
|
+
txHashes: [],
|
|
147
|
+
cancelTxHashes: [],
|
|
148
|
+
gasPrice,
|
|
149
|
+
request,
|
|
150
|
+
status: TxUtilsState.IDLE,
|
|
151
|
+
nonce,
|
|
152
|
+
gasLimit,
|
|
153
|
+
txConfig: gasConfig,
|
|
154
|
+
blobInputs,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
this.updateState(l1TxState, stateChange);
|
|
158
|
+
|
|
159
|
+
const baseTxData = {
|
|
160
|
+
...request,
|
|
161
|
+
gas: gasLimit,
|
|
162
|
+
maxFeePerGas: gasPrice.maxFeePerGas,
|
|
163
|
+
maxPriorityFeePerGas: gasPrice.maxPriorityFeePerGas,
|
|
164
|
+
nonce,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const txData = blobInputs
|
|
168
|
+
? { ...baseTxData, ...blobInputs, maxFeePerBlobGas: gasPrice.maxFeePerBlobGas! }
|
|
169
|
+
: baseTxData;
|
|
170
|
+
|
|
171
|
+
const signedRequest = await this.prepareSignedTransaction(txData);
|
|
172
|
+
const txHash = await this.client.sendRawTransaction({ serializedTransaction: signedRequest });
|
|
173
|
+
|
|
174
|
+
l1TxState.txHashes.push(txHash);
|
|
175
|
+
this.txs.push(l1TxState);
|
|
176
|
+
if (this.txs.length > MAX_L1_TX_STATES) {
|
|
177
|
+
this.txs.shift();
|
|
160
178
|
}
|
|
161
|
-
|
|
179
|
+
|
|
162
180
|
const cleanGasConfig = pickBy(gasConfig, (_, key) => key in l1TxUtilsConfigMappings);
|
|
163
181
|
this.logger?.info(`Sent L1 transaction ${txHash}`, {
|
|
164
182
|
gasLimit,
|
|
@@ -168,7 +186,7 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
168
186
|
...(gasPrice.maxFeePerBlobGas && { maxFeePerBlobGas: formatGwei(gasPrice.maxFeePerBlobGas) }),
|
|
169
187
|
});
|
|
170
188
|
|
|
171
|
-
return { txHash,
|
|
189
|
+
return { txHash, state: l1TxState };
|
|
172
190
|
} catch (err: any) {
|
|
173
191
|
const viemError = formatViemError(err, request.abi);
|
|
174
192
|
this.logger?.error(`Failed to send L1 transaction`, viemError.message, {
|
|
@@ -178,60 +196,51 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
178
196
|
}
|
|
179
197
|
}
|
|
180
198
|
|
|
199
|
+
private async tryGetTxReceipt(
|
|
200
|
+
txHashes: Hex[],
|
|
201
|
+
nonce: number,
|
|
202
|
+
isCancelTx: boolean,
|
|
203
|
+
): Promise<TransactionReceipt | undefined> {
|
|
204
|
+
for (const hash of txHashes) {
|
|
205
|
+
try {
|
|
206
|
+
const receipt = await this.client.getTransactionReceipt({ hash });
|
|
207
|
+
if (receipt) {
|
|
208
|
+
const what = isCancelTx ? 'Cancellation L1 transaction' : 'L1 transaction';
|
|
209
|
+
if (receipt.status === 'reverted') {
|
|
210
|
+
this.logger?.warn(`${what} ${hash} with nonce ${nonce} reverted`, receipt);
|
|
211
|
+
} else {
|
|
212
|
+
this.logger?.verbose(`${what} ${hash} with nonce ${nonce} mined`, receipt);
|
|
213
|
+
}
|
|
214
|
+
return receipt;
|
|
215
|
+
}
|
|
216
|
+
} catch (err) {
|
|
217
|
+
if (err instanceof Error && err.name === 'TransactionReceiptNotFoundError') {
|
|
218
|
+
continue;
|
|
219
|
+
} else {
|
|
220
|
+
this.logger.error(`Error getting receipt for tx ${hash}`, err);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
181
227
|
/**
|
|
182
228
|
* Monitors a transaction until completion, handling speed-ups if needed
|
|
183
|
-
* @param request - Original transaction request (needed for speed-ups)
|
|
184
|
-
* @param initialTxHash - Hash of the initial transaction
|
|
185
|
-
* @param allVersions - Hashes of all transactions submitted under the same nonce (any of them could mine)
|
|
186
|
-
* @param params - Parameters used in the initial transaction
|
|
187
|
-
* @param gasConfig - Optional gas configuration
|
|
188
229
|
*/
|
|
189
|
-
|
|
190
|
-
request:
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
params: { gasLimit: bigint },
|
|
194
|
-
_gasConfig?: Partial<L1TxUtilsConfig> & { txTimeoutAt?: Date },
|
|
195
|
-
_blobInputs?: L1BlobInputs,
|
|
196
|
-
isCancelTx: boolean = false,
|
|
197
|
-
): Promise<TransactionReceipt> {
|
|
198
|
-
const isBlobTx = !!_blobInputs;
|
|
199
|
-
const gasConfig = { ...this.config, ..._gasConfig };
|
|
230
|
+
protected async monitorTransaction(state: L1TxState): Promise<TransactionReceipt> {
|
|
231
|
+
const { request, nonce, txHashes, cancelTxHashes, gasLimit, blobInputs, txConfig: gasConfig } = state;
|
|
232
|
+
const isCancelTx = cancelTxHashes.length > 0;
|
|
233
|
+
const isBlobTx = !!blobInputs;
|
|
200
234
|
const account = this.getSenderAddress().toString();
|
|
201
235
|
|
|
202
|
-
const blobInputs = _blobInputs || {};
|
|
203
236
|
const makeGetTransactionBackoff = () =>
|
|
204
237
|
makeBackoff(times(gasConfig.txPropagationMaxQueryAttempts ?? 3, i => i + 1));
|
|
205
238
|
|
|
206
|
-
|
|
207
|
-
const tx = await retry<GetTransactionReturnType>(
|
|
208
|
-
() => this.client.getTransaction({ hash: initialTxHash }),
|
|
209
|
-
`Getting L1 transaction ${initialTxHash}`,
|
|
210
|
-
makeGetTransactionBackoff(),
|
|
211
|
-
this.logger,
|
|
212
|
-
true,
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
if (!tx) {
|
|
216
|
-
throw new Error(`Failed to get L1 transaction ${initialTxHash} to monitor`);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (tx?.nonce === undefined || tx?.nonce === null) {
|
|
220
|
-
throw new Error(`Failed to get L1 transaction ${initialTxHash} nonce`);
|
|
221
|
-
}
|
|
222
|
-
const nonce = tx.nonce;
|
|
223
|
-
|
|
224
|
-
allVersions.add(initialTxHash);
|
|
225
|
-
let currentTxHash = initialTxHash;
|
|
239
|
+
let currentTxHash = isCancelTx ? cancelTxHashes[0] : txHashes[0];
|
|
226
240
|
let attempts = 0;
|
|
227
241
|
let lastAttemptSent = this.dateProvider.now();
|
|
228
|
-
let lastGasPrice: GasPrice = {
|
|
229
|
-
maxFeePerGas: tx.maxFeePerGas!,
|
|
230
|
-
maxPriorityFeePerGas: tx.maxPriorityFeePerGas!,
|
|
231
|
-
maxFeePerBlobGas: tx.maxFeePerBlobGas!,
|
|
232
|
-
};
|
|
233
|
-
const initialTxTime = lastAttemptSent;
|
|
234
242
|
|
|
243
|
+
const initialTxTime = lastAttemptSent;
|
|
235
244
|
let txTimedOut = false;
|
|
236
245
|
let latestBlockTimestamp: bigint | undefined;
|
|
237
246
|
|
|
@@ -256,28 +265,19 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
256
265
|
const currentNonce = await this.client.getTransactionCount({ address: account });
|
|
257
266
|
// If the current nonce on our account is greater than our transaction's nonce then a tx with the same nonce has been mined.
|
|
258
267
|
if (currentNonce > nonce) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
this.state = TxUtilsState.MINED;
|
|
269
|
-
this.lastMinedAtBlockNumber = receipt.blockNumber;
|
|
270
|
-
return receipt;
|
|
271
|
-
}
|
|
272
|
-
} catch (err) {
|
|
273
|
-
if (err instanceof Error && err.message.includes('reverted')) {
|
|
274
|
-
throw formatViemError(err);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
268
|
+
const receipt =
|
|
269
|
+
(await this.tryGetTxReceipt(cancelTxHashes, nonce, true)) ??
|
|
270
|
+
(await this.tryGetTxReceipt(txHashes, nonce, false));
|
|
271
|
+
|
|
272
|
+
if (receipt) {
|
|
273
|
+
this.updateState(state, TxUtilsState.MINED);
|
|
274
|
+
state.receipt = receipt;
|
|
275
|
+
return receipt;
|
|
277
276
|
}
|
|
277
|
+
|
|
278
278
|
// If we get here then we have checked all of our tx versions and not found anything.
|
|
279
279
|
// We should consider the nonce as MINED
|
|
280
|
-
this.state
|
|
280
|
+
this.updateState(state, TxUtilsState.MINED);
|
|
281
281
|
throw new Error(`Nonce ${nonce} is MINED but not by one of our expected transactions`);
|
|
282
282
|
}
|
|
283
283
|
|
|
@@ -328,7 +328,7 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
328
328
|
}
|
|
329
329
|
: undefined,
|
|
330
330
|
);
|
|
331
|
-
|
|
331
|
+
state.gasPrice = newGasPrice;
|
|
332
332
|
|
|
333
333
|
this.logger?.debug(
|
|
334
334
|
`L1 transaction ${currentTxHash} appears stuck. Attempting speed-up ${attempts}/${gasConfig.maxAttempts} ` +
|
|
@@ -340,26 +340,28 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
340
340
|
},
|
|
341
341
|
);
|
|
342
342
|
|
|
343
|
-
const
|
|
343
|
+
const baseTxData = {
|
|
344
344
|
...request,
|
|
345
|
-
|
|
346
|
-
nonce,
|
|
347
|
-
gas: params.gasLimit,
|
|
345
|
+
gas: gasLimit,
|
|
348
346
|
maxFeePerGas: newGasPrice.maxFeePerGas,
|
|
349
347
|
maxPriorityFeePerGas: newGasPrice.maxPriorityFeePerGas,
|
|
348
|
+
nonce,
|
|
350
349
|
};
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
350
|
+
|
|
351
|
+
const txData = blobInputs
|
|
352
|
+
? { ...baseTxData, ...blobInputs, maxFeePerBlobGas: newGasPrice.maxFeePerBlobGas! }
|
|
353
|
+
: baseTxData;
|
|
354
|
+
|
|
354
355
|
const signedRequest = await this.prepareSignedTransaction(txData);
|
|
355
356
|
const newHash = await this.client.sendRawTransaction({ serializedTransaction: signedRequest });
|
|
357
|
+
|
|
356
358
|
if (!isCancelTx) {
|
|
357
|
-
this.state
|
|
359
|
+
this.updateState(state, TxUtilsState.SPEED_UP);
|
|
358
360
|
}
|
|
359
361
|
|
|
360
362
|
const cleanGasConfig = pickBy(gasConfig, (_, key) => key in l1TxUtilsConfigMappings);
|
|
361
363
|
this.logger?.verbose(`Sent L1 speed-up tx ${newHash}, replacing ${currentTxHash}`, {
|
|
362
|
-
gasLimit
|
|
364
|
+
gasLimit,
|
|
363
365
|
maxFeePerGas: formatGwei(newGasPrice.maxFeePerGas),
|
|
364
366
|
maxPriorityFeePerGas: formatGwei(newGasPrice.maxPriorityFeePerGas),
|
|
365
367
|
gasConfig: cleanGasConfig,
|
|
@@ -368,7 +370,7 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
368
370
|
|
|
369
371
|
currentTxHash = newHash;
|
|
370
372
|
|
|
371
|
-
|
|
373
|
+
(isCancelTx ? cancelTxHashes : txHashes).push(currentTxHash);
|
|
372
374
|
lastAttemptSent = this.dateProvider.now();
|
|
373
375
|
}
|
|
374
376
|
await sleep(gasConfig.checkIntervalMs!);
|
|
@@ -387,10 +389,10 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
387
389
|
// The transaction has timed out. If it's a cancellation then we are giving up on it.
|
|
388
390
|
// Otherwise we may attempt to cancel it if configured to do so.
|
|
389
391
|
if (isCancelTx) {
|
|
390
|
-
this.state
|
|
392
|
+
this.updateState(state, TxUtilsState.NOT_MINED);
|
|
391
393
|
} else if (gasConfig.cancelTxOnTimeout) {
|
|
392
394
|
// Fire cancellation without awaiting to avoid blocking the main thread
|
|
393
|
-
this.attemptTxCancellation(
|
|
395
|
+
this.attemptTxCancellation(state, attempts).catch(err => {
|
|
394
396
|
const viemError = formatViemError(err);
|
|
395
397
|
this.logger?.error(`Failed to send cancellation for timed out tx ${currentTxHash}:`, viemError.message, {
|
|
396
398
|
metaMessages: viemError.metaMessages,
|
|
@@ -398,7 +400,7 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
398
400
|
});
|
|
399
401
|
}
|
|
400
402
|
|
|
401
|
-
this.logger?.
|
|
403
|
+
this.logger?.warn(`L1 transaction ${currentTxHash} timed out`, {
|
|
402
404
|
txHash: currentTxHash,
|
|
403
405
|
txTimeoutAt: gasConfig.txTimeoutAt,
|
|
404
406
|
txTimeoutMs: gasConfig.txTimeoutMs,
|
|
@@ -406,7 +408,6 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
406
408
|
now: this.dateProvider.now(),
|
|
407
409
|
attempts,
|
|
408
410
|
isInterrupted: this.interrupted,
|
|
409
|
-
...tx,
|
|
410
411
|
});
|
|
411
412
|
|
|
412
413
|
throw new TimeoutError(`L1 transaction ${currentTxHash} timed out`);
|
|
@@ -422,10 +423,10 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
422
423
|
request: L1TxRequest,
|
|
423
424
|
gasConfig?: L1GasConfig,
|
|
424
425
|
blobInputs?: L1BlobInputs,
|
|
425
|
-
): Promise<{ receipt: TransactionReceipt;
|
|
426
|
-
const {
|
|
427
|
-
const receipt = await this.monitorTransaction(
|
|
428
|
-
return { receipt,
|
|
426
|
+
): Promise<{ receipt: TransactionReceipt; state: L1TxState }> {
|
|
427
|
+
const { state } = await this.sendTransaction(request, gasConfig, blobInputs);
|
|
428
|
+
const receipt = await this.monitorTransaction(state);
|
|
429
|
+
return { receipt, state };
|
|
429
430
|
}
|
|
430
431
|
|
|
431
432
|
public override async simulate(
|
|
@@ -458,23 +459,11 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
458
459
|
|
|
459
460
|
/**
|
|
460
461
|
* Attempts to cancel a transaction by sending a 0-value tx to self with same nonce but higher gas prices
|
|
461
|
-
* @param nonce - The nonce of the transaction to cancel
|
|
462
|
-
* @param allVersions - Hashes of all transactions submitted under the same nonce (any of them could mine)
|
|
463
|
-
* @param previousGasPrice - The gas price of the previous transaction
|
|
464
|
-
* @param attempts - The number of attempts to cancel the transaction
|
|
465
462
|
* @returns The hash of the cancellation transaction
|
|
466
463
|
*/
|
|
467
|
-
protected async attemptTxCancellation(
|
|
468
|
-
|
|
469
|
-
nonce:
|
|
470
|
-
allVersions: Set<Hex>,
|
|
471
|
-
isBlobTx = false,
|
|
472
|
-
previousGasPrice?: GasPrice,
|
|
473
|
-
attempts = 0,
|
|
474
|
-
) {
|
|
475
|
-
if (isBlobTx) {
|
|
476
|
-
throw new Error('Cannot cancel blob transactions, please use L1TxUtilsWithBlobsClass');
|
|
477
|
-
}
|
|
464
|
+
protected async attemptTxCancellation(state: L1TxState, attempts: number) {
|
|
465
|
+
const isBlobTx = state.blobInputs !== undefined;
|
|
466
|
+
const { nonce, gasPrice: previousGasPrice } = state;
|
|
478
467
|
|
|
479
468
|
// Get gas price with higher priority fee for cancellation
|
|
480
469
|
const cancelGasPrice = await this.getGasPrice(
|
|
@@ -488,40 +477,53 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
488
477
|
previousGasPrice,
|
|
489
478
|
);
|
|
490
479
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
480
|
+
const { maxFeePerGas, maxPriorityFeePerGas, maxFeePerBlobGas } = cancelGasPrice;
|
|
481
|
+
this.logger?.info(
|
|
482
|
+
`Attempting to cancel L1 ${isBlobTx ? 'blob' : 'vanilla'} transaction ${state.txHashes[0]} with nonce ${nonce}`,
|
|
483
|
+
{
|
|
484
|
+
maxFeePerGas: formatGwei(maxFeePerGas),
|
|
485
|
+
maxPriorityFeePerGas: formatGwei(maxPriorityFeePerGas),
|
|
486
|
+
...(maxFeePerBlobGas && { maxFeePerBlobGas: formatGwei(maxFeePerBlobGas) }),
|
|
487
|
+
},
|
|
488
|
+
);
|
|
489
|
+
|
|
495
490
|
const request = {
|
|
496
491
|
to: this.getSenderAddress().toString(),
|
|
497
492
|
value: 0n,
|
|
498
493
|
};
|
|
499
494
|
|
|
500
495
|
// Send 0-value tx to self with higher gas price
|
|
501
|
-
const
|
|
496
|
+
const baseTxData = {
|
|
502
497
|
...request,
|
|
503
498
|
nonce,
|
|
504
|
-
gas: 21_000n,
|
|
505
|
-
maxFeePerGas
|
|
506
|
-
maxPriorityFeePerGas
|
|
499
|
+
gas: 21_000n,
|
|
500
|
+
maxFeePerGas,
|
|
501
|
+
maxPriorityFeePerGas,
|
|
507
502
|
};
|
|
503
|
+
|
|
504
|
+
const txData = isBlobTx ? { ...baseTxData, ...this.makeEmptyBlobInputs(maxFeePerBlobGas!) } : baseTxData;
|
|
508
505
|
const signedRequest = await this.prepareSignedTransaction(txData);
|
|
509
506
|
const cancelTxHash = await this.client.sendRawTransaction({ serializedTransaction: signedRequest });
|
|
510
507
|
|
|
511
|
-
|
|
508
|
+
state.gasPrice = cancelGasPrice;
|
|
509
|
+
state.gasLimit = 21_000n;
|
|
510
|
+
state.cancelTxHashes.push(cancelTxHash);
|
|
512
511
|
|
|
513
|
-
this.
|
|
512
|
+
this.updateState(state, TxUtilsState.CANCELLED);
|
|
514
513
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
514
|
+
this.logger?.info(`Sent cancellation tx ${cancelTxHash} for timed out tx with nonce ${nonce}`, {
|
|
515
|
+
nonce,
|
|
516
|
+
txData,
|
|
517
|
+
isBlobTx,
|
|
518
|
+
txHashes: state.txHashes,
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
const { transactionHash } = await this.monitorTransaction(state);
|
|
522
|
+
return transactionHash;
|
|
523
|
+
}
|
|
524
524
|
|
|
525
|
-
|
|
525
|
+
/** Makes empty blob inputs for the cancellation tx. To be overridden in L1TxUtilsWithBlobs. */
|
|
526
|
+
protected makeEmptyBlobInputs(_maxFeePerBlobGas: bigint): Required<L1BlobInputs> {
|
|
527
|
+
throw new Error('Cannot make empty blob inputs for cancellation');
|
|
526
528
|
}
|
|
527
529
|
}
|
|
@@ -3,113 +3,22 @@ import { EthAddress } from '@aztec/foundation/eth-address';
|
|
|
3
3
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
4
4
|
import { DateProvider } from '@aztec/foundation/timer';
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
import type { TransactionSerializable } from 'viem';
|
|
7
7
|
|
|
8
8
|
import type { EthSigner } from '../eth-signer/eth-signer.js';
|
|
9
9
|
import type { ExtendedViemWalletClient, ViemClient } from '../types.js';
|
|
10
10
|
import type { L1TxUtilsConfig } from './config.js';
|
|
11
11
|
import { L1TxUtils } from './l1_tx_utils.js';
|
|
12
12
|
import { createViemSigner } from './signer.js';
|
|
13
|
-
import type {
|
|
13
|
+
import type { L1BlobInputs, SigningCallback } from './types.js';
|
|
14
14
|
|
|
15
|
+
/** Extends L1TxUtils with the capability to cancel blobs. This needs to be a separate class so we don't require a dependency on blob-lib unnecessarily. */
|
|
15
16
|
export class L1TxUtilsWithBlobs extends L1TxUtils {
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* @param attempts - The number of attempts to cancel the transaction
|
|
22
|
-
* @returns The hash of the cancellation transaction
|
|
23
|
-
*/
|
|
24
|
-
protected override async attemptTxCancellation(
|
|
25
|
-
currentTxHash: Hex,
|
|
26
|
-
nonce: number,
|
|
27
|
-
allVersions: Set<Hex>,
|
|
28
|
-
isBlobTx = false,
|
|
29
|
-
previousGasPrice?: GasPrice,
|
|
30
|
-
attempts = 0,
|
|
31
|
-
) {
|
|
32
|
-
// Get gas price with higher priority fee for cancellation
|
|
33
|
-
const cancelGasPrice = await this.getGasPrice(
|
|
34
|
-
{
|
|
35
|
-
...this.config,
|
|
36
|
-
// Use high bump for cancellation to ensure it replaces the original tx
|
|
37
|
-
priorityFeeRetryBumpPercentage: 150, // 150% bump should be enough to replace any tx
|
|
38
|
-
},
|
|
39
|
-
isBlobTx,
|
|
40
|
-
attempts + 1,
|
|
41
|
-
previousGasPrice,
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
this.logger?.info(`Attempting to cancel blob L1 transaction ${currentTxHash} with nonce ${nonce}`, {
|
|
45
|
-
maxFeePerGas: formatGwei(cancelGasPrice.maxFeePerGas),
|
|
46
|
-
maxPriorityFeePerGas: formatGwei(cancelGasPrice.maxPriorityFeePerGas),
|
|
47
|
-
maxFeePerBlobGas:
|
|
48
|
-
cancelGasPrice.maxFeePerBlobGas === undefined ? undefined : formatGwei(cancelGasPrice.maxFeePerBlobGas),
|
|
49
|
-
});
|
|
50
|
-
const request = {
|
|
51
|
-
to: this.getSenderAddress().toString(),
|
|
52
|
-
value: 0n,
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
// Send 0-value tx to self with higher gas price
|
|
56
|
-
if (!isBlobTx) {
|
|
57
|
-
const txData = {
|
|
58
|
-
...request,
|
|
59
|
-
nonce,
|
|
60
|
-
gas: 21_000n, // Standard ETH transfer gas
|
|
61
|
-
maxFeePerGas: cancelGasPrice.maxFeePerGas,
|
|
62
|
-
maxPriorityFeePerGas: cancelGasPrice.maxPriorityFeePerGas,
|
|
63
|
-
};
|
|
64
|
-
const signedRequest = await this.prepareSignedTransaction(txData);
|
|
65
|
-
const cancelTxHash = await this.client.sendRawTransaction({ serializedTransaction: signedRequest });
|
|
66
|
-
|
|
67
|
-
this.logger?.info(`Sent cancellation tx ${cancelTxHash} for timed out tx ${currentTxHash}`);
|
|
68
|
-
|
|
69
|
-
const receipt = await this.monitorTransaction(
|
|
70
|
-
request,
|
|
71
|
-
cancelTxHash,
|
|
72
|
-
allVersions,
|
|
73
|
-
{ gasLimit: 21_000n },
|
|
74
|
-
undefined,
|
|
75
|
-
undefined,
|
|
76
|
-
true,
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
return receipt.transactionHash;
|
|
80
|
-
} else {
|
|
81
|
-
const blobData = new Uint8Array(131072).fill(0);
|
|
82
|
-
const kzg = Blob.getViemKzgInstance();
|
|
83
|
-
const blobInputs = {
|
|
84
|
-
blobs: [blobData],
|
|
85
|
-
kzg,
|
|
86
|
-
maxFeePerBlobGas: cancelGasPrice.maxFeePerBlobGas!,
|
|
87
|
-
};
|
|
88
|
-
const txData = {
|
|
89
|
-
...request,
|
|
90
|
-
...blobInputs,
|
|
91
|
-
nonce,
|
|
92
|
-
gas: 21_000n,
|
|
93
|
-
maxFeePerGas: cancelGasPrice.maxFeePerGas,
|
|
94
|
-
maxPriorityFeePerGas: cancelGasPrice.maxPriorityFeePerGas,
|
|
95
|
-
};
|
|
96
|
-
const signedRequest = await this.prepareSignedTransaction(txData);
|
|
97
|
-
const cancelTxHash = await this.client.sendRawTransaction({ serializedTransaction: signedRequest });
|
|
98
|
-
|
|
99
|
-
this.logger?.info(`Sent cancellation tx ${cancelTxHash} for timed out tx ${currentTxHash}`);
|
|
100
|
-
|
|
101
|
-
const receipt = await this.monitorTransaction(
|
|
102
|
-
request,
|
|
103
|
-
cancelTxHash,
|
|
104
|
-
allVersions,
|
|
105
|
-
{ gasLimit: 21_000n },
|
|
106
|
-
undefined,
|
|
107
|
-
blobInputs,
|
|
108
|
-
true,
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
return receipt.transactionHash;
|
|
112
|
-
}
|
|
17
|
+
/** Makes empty blob inputs for the cancellation tx. */
|
|
18
|
+
protected override makeEmptyBlobInputs(maxFeePerBlobGas: bigint): Required<L1BlobInputs> {
|
|
19
|
+
const blobData = new Uint8Array(131072).fill(0);
|
|
20
|
+
const kzg = Blob.getViemKzgInstance();
|
|
21
|
+
return { blobs: [blobData], kzg, maxFeePerBlobGas };
|
|
113
22
|
}
|
|
114
23
|
}
|
|
115
24
|
|
|
@@ -34,7 +34,7 @@ import type { GasPrice, L1BlobInputs, L1TxRequest, TransactionStats } from './ty
|
|
|
34
34
|
import { getCalldataGasUsage, tryGetCustomErrorNameContractFunction } from './utils.js';
|
|
35
35
|
|
|
36
36
|
export class ReadOnlyL1TxUtils {
|
|
37
|
-
public
|
|
37
|
+
public config: L1TxUtilsConfig;
|
|
38
38
|
protected interrupted = false;
|
|
39
39
|
|
|
40
40
|
constructor(
|
package/src/l1_tx_utils/types.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import type { BlobKzgInstance } from '@aztec/blob-lib/types';
|
|
1
2
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
2
3
|
import type { ViemTransactionSignature } from '@aztec/foundation/eth-signature';
|
|
3
4
|
|
|
4
|
-
import type { Abi, Address, Hex, TransactionSerializable } from 'viem';
|
|
5
|
+
import type { Abi, Address, Hex, TransactionReceipt, TransactionSerializable } from 'viem';
|
|
5
6
|
|
|
6
7
|
import type { L1TxUtilsConfig } from './config.js';
|
|
7
8
|
|
|
@@ -16,7 +17,7 @@ export type L1GasConfig = Partial<L1TxUtilsConfig> & { gasLimit?: bigint; txTime
|
|
|
16
17
|
|
|
17
18
|
export interface L1BlobInputs {
|
|
18
19
|
blobs: Uint8Array[];
|
|
19
|
-
kzg:
|
|
20
|
+
kzg: BlobKzgInstance;
|
|
20
21
|
maxFeePerBlobGas?: bigint;
|
|
21
22
|
}
|
|
22
23
|
|
|
@@ -46,6 +47,19 @@ export enum TxUtilsState {
|
|
|
46
47
|
MINED,
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
export type L1TxState = {
|
|
51
|
+
txHashes: Hex[];
|
|
52
|
+
cancelTxHashes: Hex[];
|
|
53
|
+
gasLimit: bigint;
|
|
54
|
+
gasPrice: GasPrice;
|
|
55
|
+
txConfig: L1GasConfig;
|
|
56
|
+
request: L1TxRequest;
|
|
57
|
+
status: TxUtilsState;
|
|
58
|
+
nonce: number;
|
|
59
|
+
receipt?: TransactionReceipt;
|
|
60
|
+
blobInputs: L1BlobInputs | undefined;
|
|
61
|
+
};
|
|
62
|
+
|
|
49
63
|
export type SigningCallback = (
|
|
50
64
|
transaction: TransactionSerializable,
|
|
51
65
|
signingAddress: EthAddress,
|