@aztec/ethereum 3.0.0-nightly.20251004 → 3.0.0-nightly.20251007
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/contracts/governance.js +6 -2
- package/dest/deploy_l1_contracts.d.ts.map +1 -1
- package/dest/deploy_l1_contracts.js +13 -2
- package/dest/l1_tx_utils/factory.d.ts +18 -3
- package/dest/l1_tx_utils/factory.d.ts.map +1 -1
- package/dest/l1_tx_utils/factory.js +4 -6
- package/dest/l1_tx_utils/index.d.ts +1 -0
- package/dest/l1_tx_utils/index.d.ts.map +1 -1
- package/dest/l1_tx_utils/index.js +1 -0
- package/dest/l1_tx_utils/interfaces.d.ts +76 -0
- package/dest/l1_tx_utils/interfaces.d.ts.map +1 -0
- package/dest/l1_tx_utils/interfaces.js +4 -0
- package/dest/l1_tx_utils/l1_tx_utils.d.ts +18 -3
- package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
- package/dest/l1_tx_utils/l1_tx_utils.js +130 -70
- package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +14 -3
- 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 +4 -6
- package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
- package/dest/l1_tx_utils/readonly_l1_tx_utils.js +1 -1
- package/dest/l1_tx_utils/types.d.ts +1 -0
- package/dest/l1_tx_utils/types.d.ts.map +1 -1
- package/dest/publisher_manager.d.ts +2 -0
- package/dest/publisher_manager.d.ts.map +1 -1
- package/dest/publisher_manager.js +3 -0
- package/package.json +5 -5
- package/src/contracts/governance.ts +2 -2
- package/src/deploy_l1_contracts.ts +7 -5
- package/src/l1_tx_utils/factory.ts +35 -15
- package/src/l1_tx_utils/index.ts +1 -0
- package/src/l1_tx_utils/interfaces.ts +86 -0
- package/src/l1_tx_utils/l1_tx_utils.ts +135 -70
- package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +31 -10
- package/src/l1_tx_utils/readonly_l1_tx_utils.ts +1 -1
- package/src/l1_tx_utils/types.ts +1 -0
- package/src/publisher_manager.ts +5 -0
package/src/l1_tx_utils/index.ts
CHANGED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { L1BlobInputs, L1TxState } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Interface for L1 transaction state storage.
|
|
5
|
+
* Implementations handle persistence of transaction states across restarts.
|
|
6
|
+
*/
|
|
7
|
+
export interface IL1TxStore {
|
|
8
|
+
/**
|
|
9
|
+
* Gets the next available state ID for an account.
|
|
10
|
+
*/
|
|
11
|
+
consumeNextStateId(account: string): Promise<number>;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Saves a single transaction state for a specific account.
|
|
15
|
+
* Does not save blob data (see saveBlobs).
|
|
16
|
+
* @param account - The sender account address
|
|
17
|
+
* @param state - Transaction state to save
|
|
18
|
+
*/
|
|
19
|
+
saveState(account: string, state: L1TxState): Promise<L1TxState>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Saves blobs for a given state.
|
|
23
|
+
* @param account - The sender account address
|
|
24
|
+
* @param stateId - The state ID
|
|
25
|
+
* @param blobInputs - Blob inputs to save
|
|
26
|
+
*/
|
|
27
|
+
saveBlobs(account: string, stateId: number, blobInputs: L1BlobInputs | undefined): Promise<void>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Loads all transaction states for a specific account.
|
|
31
|
+
* @param account - The sender account address
|
|
32
|
+
* @returns Array of transaction states with their IDs
|
|
33
|
+
*/
|
|
34
|
+
loadStates(account: string): Promise<L1TxState[]>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Loads a single state by ID.
|
|
38
|
+
* @param account - The sender account address
|
|
39
|
+
* @param stateId - The state ID
|
|
40
|
+
* @returns The transaction state or undefined if not found
|
|
41
|
+
*/
|
|
42
|
+
loadState(account: string, stateId: number): Promise<L1TxState | undefined>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Deletes a specific state and its associated blobs.
|
|
46
|
+
* @param account - The sender account address
|
|
47
|
+
* @param stateId - The state ID to delete
|
|
48
|
+
*/
|
|
49
|
+
deleteState(account: string, stateId: number): Promise<void>;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Clears all transaction states for a specific account.
|
|
53
|
+
* @param account - The sender account address
|
|
54
|
+
*/
|
|
55
|
+
clearStates(account: string): Promise<void>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Gets all accounts that have stored states.
|
|
59
|
+
* @returns Array of account addresses
|
|
60
|
+
*/
|
|
61
|
+
getAllAccounts(): Promise<string[]>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Closes the store.
|
|
65
|
+
*/
|
|
66
|
+
close(): Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Interface for L1 transaction metrics recording.
|
|
71
|
+
* Implementations handle tracking of transaction lifecycle and gas costs.
|
|
72
|
+
*/
|
|
73
|
+
export interface IL1TxMetrics {
|
|
74
|
+
/**
|
|
75
|
+
* Records metrics when a transaction is mined
|
|
76
|
+
* @param state - The L1 transaction state
|
|
77
|
+
* @param l1Timestamp - The current L1 timestamp
|
|
78
|
+
*/
|
|
79
|
+
recordMinedTx(state: L1TxState, l1Timestamp: Date): void;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Records metrics when a transaction is dropped
|
|
83
|
+
* @param state - The L1 transaction state
|
|
84
|
+
*/
|
|
85
|
+
recordDroppedTx(state: L1TxState): void;
|
|
86
|
+
}
|
|
@@ -28,6 +28,7 @@ import type { ViemClient } from '../types.js';
|
|
|
28
28
|
import { formatViemError } from '../utils.js';
|
|
29
29
|
import { type L1TxUtilsConfig, l1TxUtilsConfigMappings } from './config.js';
|
|
30
30
|
import { LARGE_GAS_LIMIT } from './constants.js';
|
|
31
|
+
import type { IL1TxMetrics, IL1TxStore } from './interfaces.js';
|
|
31
32
|
import { ReadOnlyL1TxUtils } from './readonly_l1_tx_utils.js';
|
|
32
33
|
import {
|
|
33
34
|
DroppedTransactionError,
|
|
@@ -51,10 +52,12 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
51
52
|
public override client: ViemClient,
|
|
52
53
|
public address: EthAddress,
|
|
53
54
|
protected signer: SigningCallback,
|
|
54
|
-
|
|
55
|
+
logger: Logger = createLogger('ethereum:publisher'),
|
|
55
56
|
dateProvider: DateProvider = new DateProvider(),
|
|
56
57
|
config?: Partial<L1TxUtilsConfig>,
|
|
57
58
|
debugMaxGasLimit: boolean = false,
|
|
59
|
+
protected store?: IL1TxStore,
|
|
60
|
+
protected metrics?: IL1TxMetrics,
|
|
58
61
|
) {
|
|
59
62
|
super(client, logger, dateProvider, config, debugMaxGasLimit);
|
|
60
63
|
this.nonceManager = createNonceManager({ source: jsonRpc() });
|
|
@@ -69,13 +72,27 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
69
72
|
return minedBlockNumbers.length === 0 ? undefined : maxBigint(...minedBlockNumbers);
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
protected updateState(l1TxState: L1TxState, newState: TxUtilsState)
|
|
75
|
+
protected async updateState(l1TxState: L1TxState, newState: TxUtilsState.MINED, l1Timestamp: number): Promise<void>;
|
|
76
|
+
protected async updateState(l1TxState: L1TxState, newState: TxUtilsState, l1Timestamp?: undefined): Promise<void>;
|
|
77
|
+
protected async updateState(l1TxState: L1TxState, newState: TxUtilsState, l1Timestamp?: number) {
|
|
73
78
|
const oldState = l1TxState.status;
|
|
74
79
|
l1TxState.status = newState;
|
|
75
80
|
const sender = this.getSenderAddress().toString();
|
|
76
81
|
this.logger.debug(
|
|
77
82
|
`Tx state changed from ${TxUtilsState[oldState]} to ${TxUtilsState[newState]} for nonce ${l1TxState.nonce} account ${sender}`,
|
|
78
83
|
);
|
|
84
|
+
|
|
85
|
+
// Record metrics
|
|
86
|
+
if (newState === TxUtilsState.MINED && l1Timestamp !== undefined) {
|
|
87
|
+
this.metrics?.recordMinedTx(l1TxState, new Date(l1Timestamp));
|
|
88
|
+
} else if (newState === TxUtilsState.NOT_MINED) {
|
|
89
|
+
this.metrics?.recordDroppedTx(l1TxState);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Update state in the store
|
|
93
|
+
await this.store
|
|
94
|
+
?.saveState(sender, l1TxState)
|
|
95
|
+
.catch(err => this.logger.error('Failed to persist L1 tx state', err));
|
|
79
96
|
}
|
|
80
97
|
|
|
81
98
|
public updateConfig(newConfig: Partial<L1TxUtilsConfig>) {
|
|
@@ -96,6 +113,48 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
96
113
|
});
|
|
97
114
|
}
|
|
98
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Rehydrates transaction states from the store and resumes monitoring for pending transactions.
|
|
118
|
+
* This should be called on startup to restore state and resume monitoring of any in-flight transactions.
|
|
119
|
+
*/
|
|
120
|
+
public async loadStateAndResumeMonitoring(): Promise<void> {
|
|
121
|
+
if (!this.store) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const account = this.getSenderAddress().toString();
|
|
126
|
+
const loadedStates = await this.store.loadStates(account);
|
|
127
|
+
|
|
128
|
+
if (loadedStates.length === 0) {
|
|
129
|
+
this.logger.debug(`No states to rehydrate for account ${account}`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Convert loaded states (which have id) to the txs format
|
|
134
|
+
this.txs = loadedStates;
|
|
135
|
+
this.logger.info(`Rehydrated ${loadedStates.length} tx states for account ${account}`);
|
|
136
|
+
|
|
137
|
+
// Find all pending states and resume monitoring
|
|
138
|
+
const pendingStates = loadedStates.filter(state => !TerminalTxUtilsState.includes(state.status));
|
|
139
|
+
if (pendingStates.length === 0) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
this.logger.info(`Resuming monitoring for ${pendingStates.length} pending transactions for account ${account}`, {
|
|
144
|
+
txs: pendingStates.map(s => ({ id: s.id, nonce: s.nonce, status: TxUtilsState[s.status] })),
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
for (const state of pendingStates) {
|
|
148
|
+
void this.monitorTransaction(state).catch(err => {
|
|
149
|
+
this.logger.error(
|
|
150
|
+
`Error monitoring rehydrated tx with nonce ${state.nonce} for account ${account}`,
|
|
151
|
+
formatViemError(err),
|
|
152
|
+
{ nonce: state.nonce, account },
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
99
158
|
private async signTransaction(txRequest: TransactionSerializable): Promise<`0x${string}`> {
|
|
100
159
|
const signature = await this.signer(txRequest, this.getSenderAddress());
|
|
101
160
|
return serializeTransaction(txRequest, signature);
|
|
@@ -138,23 +197,18 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
138
197
|
|
|
139
198
|
const gasPrice = await this.getGasPrice(gasConfig, !!blobInputs);
|
|
140
199
|
|
|
200
|
+
if (this.interrupted) {
|
|
201
|
+
throw new InterruptError(`Transaction sending is interrupted`);
|
|
202
|
+
}
|
|
203
|
+
|
|
141
204
|
const nonce = await this.nonceManager.consume({
|
|
142
205
|
client: this.client,
|
|
143
206
|
address: account,
|
|
144
207
|
chainId: this.client.chain.id,
|
|
145
208
|
});
|
|
146
209
|
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
gas: gasLimit,
|
|
150
|
-
maxFeePerGas: gasPrice.maxFeePerGas,
|
|
151
|
-
maxPriorityFeePerGas: gasPrice.maxPriorityFeePerGas,
|
|
152
|
-
nonce,
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
const txData = blobInputs
|
|
156
|
-
? { ...baseTxData, ...blobInputs, maxFeePerBlobGas: gasPrice.maxFeePerBlobGas! }
|
|
157
|
-
: baseTxData;
|
|
210
|
+
const baseState = { request, gasLimit, blobInputs, gasPrice, nonce };
|
|
211
|
+
const txData = this.makeTxData(baseState, { isCancelTx: false });
|
|
158
212
|
|
|
159
213
|
const now = new Date(await this.getL1Timestamp());
|
|
160
214
|
if (gasConfig.txTimeoutAt && now > gasConfig.txTimeoutAt) {
|
|
@@ -163,32 +217,33 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
163
217
|
);
|
|
164
218
|
}
|
|
165
219
|
|
|
166
|
-
|
|
167
|
-
throw new InterruptError(`Transaction sending is interrupted`);
|
|
168
|
-
}
|
|
169
|
-
|
|
220
|
+
// Send the new tx
|
|
170
221
|
const signedRequest = await this.prepareSignedTransaction(txData);
|
|
171
222
|
const txHash = await this.client.sendRawTransaction({ serializedTransaction: signedRequest });
|
|
172
223
|
|
|
224
|
+
// Create the new state for monitoring
|
|
173
225
|
const l1TxState: L1TxState = {
|
|
226
|
+
...baseState,
|
|
227
|
+
id: (await this.store?.consumeNextStateId(account)) ?? Math.max(...this.txs.map(tx => tx.id), 0),
|
|
174
228
|
txHashes: [txHash],
|
|
175
229
|
cancelTxHashes: [],
|
|
176
|
-
gasPrice,
|
|
177
|
-
request,
|
|
178
230
|
status: TxUtilsState.IDLE,
|
|
179
|
-
nonce,
|
|
180
|
-
gasLimit,
|
|
181
231
|
txConfigOverrides: gasConfigOverrides ?? {},
|
|
182
|
-
blobInputs,
|
|
183
232
|
sentAtL1Ts: now,
|
|
184
233
|
lastSentAtL1Ts: now,
|
|
185
234
|
};
|
|
186
235
|
|
|
187
|
-
|
|
188
|
-
|
|
236
|
+
// And persist it
|
|
237
|
+
await this.updateState(l1TxState, stateChange);
|
|
238
|
+
await this.store?.saveBlobs(account, l1TxState.id, blobInputs);
|
|
189
239
|
this.txs.push(l1TxState);
|
|
240
|
+
|
|
241
|
+
// Clean up stale states
|
|
190
242
|
if (this.txs.length > MAX_L1_TX_STATES) {
|
|
191
|
-
this.txs.shift();
|
|
243
|
+
const removed = this.txs.shift();
|
|
244
|
+
if (removed && this.store) {
|
|
245
|
+
await this.store.deleteState(account, removed.id);
|
|
246
|
+
}
|
|
192
247
|
}
|
|
193
248
|
|
|
194
249
|
this.logger.info(`Sent L1 transaction ${txHash}`, {
|
|
@@ -296,7 +351,7 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
296
351
|
* Monitors a transaction until completion, handling speed-ups if needed
|
|
297
352
|
*/
|
|
298
353
|
protected async monitorTransaction(state: L1TxState): Promise<TransactionReceipt> {
|
|
299
|
-
const {
|
|
354
|
+
const { nonce, gasLimit, blobInputs, txConfigOverrides: gasConfigOverrides } = state;
|
|
300
355
|
const gasConfig = merge(this.config, gasConfigOverrides);
|
|
301
356
|
const { maxSpeedUpAttempts, stallTimeMs } = gasConfig;
|
|
302
357
|
const isCancelTx = state.cancelTxHashes.length > 0;
|
|
@@ -305,7 +360,7 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
305
360
|
const account = this.getSenderAddress().toString();
|
|
306
361
|
|
|
307
362
|
const initialTxHash = txHashes[0];
|
|
308
|
-
let currentTxHash =
|
|
363
|
+
let currentTxHash = txHashes.at(-1)!;
|
|
309
364
|
let l1Timestamp: number;
|
|
310
365
|
|
|
311
366
|
while (true) {
|
|
@@ -329,14 +384,14 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
329
384
|
(await this.tryGetTxReceipt(state.txHashes, nonce, false));
|
|
330
385
|
|
|
331
386
|
if (receipt) {
|
|
332
|
-
this.updateState(state, TxUtilsState.MINED);
|
|
333
387
|
state.receipt = receipt;
|
|
388
|
+
await this.updateState(state, TxUtilsState.MINED, l1Timestamp);
|
|
334
389
|
return receipt;
|
|
335
390
|
}
|
|
336
391
|
|
|
337
392
|
// If we get here then we have checked all of our tx versions and not found anything.
|
|
338
393
|
// We should consider the nonce as MINED
|
|
339
|
-
this.updateState(state, TxUtilsState.MINED);
|
|
394
|
+
await this.updateState(state, TxUtilsState.MINED, l1Timestamp);
|
|
340
395
|
throw new UnknownMinedTxError(nonce, account);
|
|
341
396
|
}
|
|
342
397
|
|
|
@@ -347,7 +402,7 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
347
402
|
`Cancellation tx with nonce ${nonce} for account ${account} has been dropped from the visible mempool`,
|
|
348
403
|
{ nonce, account, pendingNonce, timePassed },
|
|
349
404
|
);
|
|
350
|
-
this.updateState(state, TxUtilsState.NOT_MINED);
|
|
405
|
+
await this.updateState(state, TxUtilsState.NOT_MINED);
|
|
351
406
|
this.nonceManager.reset({ address: account, chainId: this.client.chain.id });
|
|
352
407
|
throw new DroppedTransactionError(nonce, account);
|
|
353
408
|
}
|
|
@@ -376,25 +431,11 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
376
431
|
},
|
|
377
432
|
);
|
|
378
433
|
|
|
379
|
-
const
|
|
380
|
-
...request,
|
|
381
|
-
gas: gasLimit,
|
|
382
|
-
maxFeePerGas: newGasPrice.maxFeePerGas,
|
|
383
|
-
maxPriorityFeePerGas: newGasPrice.maxPriorityFeePerGas,
|
|
384
|
-
nonce,
|
|
385
|
-
};
|
|
386
|
-
|
|
387
|
-
const txData = blobInputs
|
|
388
|
-
? { ...baseTxData, ...blobInputs, maxFeePerBlobGas: newGasPrice.maxFeePerBlobGas! }
|
|
389
|
-
: baseTxData;
|
|
434
|
+
const txData = this.makeTxData(state, { isCancelTx });
|
|
390
435
|
|
|
391
436
|
const signedRequest = await this.prepareSignedTransaction(txData);
|
|
392
437
|
const newHash = await this.client.sendRawTransaction({ serializedTransaction: signedRequest });
|
|
393
438
|
|
|
394
|
-
if (!isCancelTx) {
|
|
395
|
-
this.updateState(state, TxUtilsState.SPEED_UP);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
439
|
this.logger.verbose(
|
|
399
440
|
`Sent L1 speed-up tx ${newHash} replacing ${currentTxHash} for nonce ${nonce} from ${account}`,
|
|
400
441
|
{
|
|
@@ -411,6 +452,8 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
411
452
|
currentTxHash = newHash;
|
|
412
453
|
txHashes.push(currentTxHash);
|
|
413
454
|
state.lastSentAtL1Ts = new Date(l1Timestamp);
|
|
455
|
+
await this.updateState(state, isCancelTx ? TxUtilsState.CANCELLED : TxUtilsState.SPEED_UP);
|
|
456
|
+
|
|
414
457
|
await sleep(gasConfig.checkIntervalMs!);
|
|
415
458
|
continue;
|
|
416
459
|
}
|
|
@@ -456,13 +499,13 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
456
499
|
// and reset the nonce manager, so the next tx that comes along can reuse the nonce if/when this tx gets dropped.
|
|
457
500
|
// This is the nastiest scenario for us, since the new tx could acquire the next nonce, but then this tx is dropped,
|
|
458
501
|
// and the new tx would never get mined. Eventually, the new tx would also drop.
|
|
459
|
-
this.updateState(state, TxUtilsState.NOT_MINED);
|
|
502
|
+
await this.updateState(state, TxUtilsState.NOT_MINED);
|
|
460
503
|
this.nonceManager.reset({ address: account, chainId: this.client.chain.id });
|
|
461
504
|
} else {
|
|
462
505
|
// Otherwise we fire the cancellation without awaiting to avoid blocking the caller,
|
|
463
506
|
// and monitor it in the background so we can speed it up as needed.
|
|
464
|
-
void this.attemptTxCancellation(state).catch(err => {
|
|
465
|
-
this.updateState(state, TxUtilsState.NOT_MINED);
|
|
507
|
+
void this.attemptTxCancellation(state).catch(async err => {
|
|
508
|
+
await this.updateState(state, TxUtilsState.NOT_MINED);
|
|
466
509
|
this.logger.error(`Failed to send cancellation for timed out tx ${initialTxHash} with nonce ${nonce}`, err, {
|
|
467
510
|
account,
|
|
468
511
|
nonce,
|
|
@@ -489,6 +532,41 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
489
532
|
throw new TimeoutError(`L1 transaction ${initialTxHash} timed out`);
|
|
490
533
|
}
|
|
491
534
|
|
|
535
|
+
/**
|
|
536
|
+
* Creates tx data to be signed by viem signTransaction method, using the state as input.
|
|
537
|
+
* If isCancelTx is true, creates a 0-value tx to self with 21k gas and no data instead,
|
|
538
|
+
* and an empty blob input if the original tx also had blobs.
|
|
539
|
+
*/
|
|
540
|
+
private makeTxData(
|
|
541
|
+
state: Pick<L1TxState, 'request' | 'gasLimit' | 'blobInputs' | 'gasPrice' | 'nonce'>,
|
|
542
|
+
opts: { isCancelTx: boolean },
|
|
543
|
+
): PrepareTransactionRequestRequest {
|
|
544
|
+
const { request, gasLimit, blobInputs, gasPrice, nonce } = state;
|
|
545
|
+
const isBlobTx = blobInputs !== undefined;
|
|
546
|
+
|
|
547
|
+
const baseTxOpts = { nonce, ...pick(gasPrice, 'maxFeePerGas', 'maxPriorityFeePerGas') };
|
|
548
|
+
|
|
549
|
+
if (opts.isCancelTx) {
|
|
550
|
+
const baseTxData = {
|
|
551
|
+
to: this.getSenderAddress().toString(),
|
|
552
|
+
value: 0n,
|
|
553
|
+
data: '0x' as const,
|
|
554
|
+
gas: 21_000n,
|
|
555
|
+
...baseTxOpts,
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
return isBlobTx ? { ...baseTxData, ...this.makeEmptyBlobInputs(gasPrice.maxFeePerBlobGas!) } : baseTxData;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const baseTxData = {
|
|
562
|
+
...request,
|
|
563
|
+
...baseTxOpts,
|
|
564
|
+
gas: gasLimit,
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
return blobInputs ? { ...baseTxData, ...blobInputs, maxFeePerBlobGas: gasPrice.maxFeePerBlobGas! } : baseTxData;
|
|
568
|
+
}
|
|
569
|
+
|
|
492
570
|
/** Returns when all monitor loops have stopped. */
|
|
493
571
|
public async waitMonitoringStopped(timeoutSeconds = 10) {
|
|
494
572
|
const account = this.getSenderAddress().toString();
|
|
@@ -549,7 +627,7 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
549
627
|
* Only sends the cancellation if the original tx is still pending, not if it was dropped
|
|
550
628
|
* @returns The hash of the cancellation transaction
|
|
551
629
|
*/
|
|
552
|
-
protected async attemptTxCancellation(state: L1TxState) {
|
|
630
|
+
protected async attemptTxCancellation(state: L1TxState): Promise<void> {
|
|
553
631
|
const isBlobTx = state.blobInputs !== undefined;
|
|
554
632
|
const { nonce, gasPrice: previousGasPrice } = state;
|
|
555
633
|
const account = this.getSenderAddress().toString();
|
|
@@ -560,7 +638,7 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
560
638
|
`Not sending cancellation for L1 tx from account ${account} with nonce ${nonce} as interrupted`,
|
|
561
639
|
{ nonce, account },
|
|
562
640
|
);
|
|
563
|
-
this.updateState(state, TxUtilsState.NOT_MINED);
|
|
641
|
+
await this.updateState(state, TxUtilsState.NOT_MINED);
|
|
564
642
|
this.nonceManager.reset({ address: account, chainId: this.client.chain.id });
|
|
565
643
|
return;
|
|
566
644
|
}
|
|
@@ -572,7 +650,7 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
572
650
|
`Not sending cancellation for L1 tx from account ${account} with nonce ${nonce} as it is dropped`,
|
|
573
651
|
{ nonce, account, currentNonce },
|
|
574
652
|
);
|
|
575
|
-
this.updateState(state, TxUtilsState.NOT_MINED);
|
|
653
|
+
await this.updateState(state, TxUtilsState.NOT_MINED);
|
|
576
654
|
this.nonceManager.reset({ address: account, chainId: this.client.chain.id });
|
|
577
655
|
return;
|
|
578
656
|
}
|
|
@@ -599,34 +677,20 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
599
677
|
},
|
|
600
678
|
);
|
|
601
679
|
|
|
602
|
-
const request = {
|
|
603
|
-
to: this.getSenderAddress().toString(),
|
|
604
|
-
value: 0n,
|
|
605
|
-
};
|
|
606
|
-
|
|
607
680
|
// Send 0-value tx to self with higher gas price
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
nonce,
|
|
611
|
-
gas: 21_000n,
|
|
612
|
-
maxFeePerGas,
|
|
613
|
-
maxPriorityFeePerGas,
|
|
614
|
-
};
|
|
681
|
+
state.gasPrice = cancelGasPrice;
|
|
682
|
+
state.lastSentAtL1Ts = new Date(await this.getL1Timestamp());
|
|
615
683
|
|
|
616
|
-
const txData =
|
|
684
|
+
const txData = this.makeTxData(state, { isCancelTx: true });
|
|
617
685
|
const signedRequest = await this.prepareSignedTransaction(txData);
|
|
618
686
|
const cancelTxHash = await this.client.sendRawTransaction({ serializedTransaction: signedRequest });
|
|
619
687
|
|
|
620
|
-
state.gasPrice = cancelGasPrice;
|
|
621
|
-
state.gasLimit = 21_000n;
|
|
622
688
|
state.cancelTxHashes.push(cancelTxHash);
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
this.updateState(state, TxUtilsState.CANCELLED);
|
|
689
|
+
await this.updateState(state, TxUtilsState.CANCELLED);
|
|
626
690
|
|
|
627
691
|
this.logger.warn(`Sent cancellation tx ${cancelTxHash} for timed out tx from ${account} with nonce ${nonce}`, {
|
|
628
692
|
nonce,
|
|
629
|
-
|
|
693
|
+
cancelGasPrice,
|
|
630
694
|
isBlobTx,
|
|
631
695
|
txHashes: state.txHashes,
|
|
632
696
|
});
|
|
@@ -641,6 +705,7 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
641
705
|
});
|
|
642
706
|
}
|
|
643
707
|
|
|
708
|
+
/** Returns L1 timestamps in milliseconds */
|
|
644
709
|
private async getL1Timestamp() {
|
|
645
710
|
const { timestamp } = await this.client.getBlock({ blockTag: 'latest', includeTransactions: false });
|
|
646
711
|
return Number(timestamp) * 1000;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Blob } from '@aztec/blob-lib';
|
|
2
2
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
3
|
-
import {
|
|
3
|
+
import type { Logger } from '@aztec/foundation/log';
|
|
4
4
|
import { DateProvider } from '@aztec/foundation/timer';
|
|
5
5
|
|
|
6
6
|
import type { TransactionSerializable } from 'viem';
|
|
@@ -8,6 +8,7 @@ import type { TransactionSerializable } from 'viem';
|
|
|
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
|
+
import type { IL1TxMetrics, IL1TxStore } from './interfaces.js';
|
|
11
12
|
import { L1TxUtils } from './l1_tx_utils.js';
|
|
12
13
|
import { createViemSigner } from './signer.js';
|
|
13
14
|
import type { L1BlobInputs, SigningCallback } from './types.js';
|
|
@@ -24,33 +25,53 @@ export class L1TxUtilsWithBlobs extends L1TxUtils {
|
|
|
24
25
|
|
|
25
26
|
export function createL1TxUtilsWithBlobsFromViemWallet(
|
|
26
27
|
client: ExtendedViemWalletClient,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
deps: {
|
|
29
|
+
logger?: Logger;
|
|
30
|
+
dateProvider?: DateProvider;
|
|
31
|
+
store?: IL1TxStore;
|
|
32
|
+
metrics?: IL1TxMetrics;
|
|
33
|
+
} = {},
|
|
34
|
+
config: Partial<L1TxUtilsConfig> = {},
|
|
30
35
|
debugMaxGasLimit: boolean = false,
|
|
31
36
|
) {
|
|
32
37
|
return new L1TxUtilsWithBlobs(
|
|
33
38
|
client,
|
|
34
39
|
EthAddress.fromString(client.account.address),
|
|
35
40
|
createViemSigner(client),
|
|
36
|
-
logger,
|
|
37
|
-
dateProvider,
|
|
41
|
+
deps.logger,
|
|
42
|
+
deps.dateProvider,
|
|
38
43
|
config,
|
|
39
44
|
debugMaxGasLimit,
|
|
45
|
+
deps.store,
|
|
46
|
+
deps.metrics,
|
|
40
47
|
);
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
export function createL1TxUtilsWithBlobsFromEthSigner(
|
|
44
51
|
client: ViemClient,
|
|
45
52
|
signer: EthSigner,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
deps: {
|
|
54
|
+
logger?: Logger;
|
|
55
|
+
dateProvider?: DateProvider;
|
|
56
|
+
store?: IL1TxStore;
|
|
57
|
+
metrics?: IL1TxMetrics;
|
|
58
|
+
} = {},
|
|
59
|
+
config: Partial<L1TxUtilsConfig> = {},
|
|
49
60
|
debugMaxGasLimit: boolean = false,
|
|
50
61
|
) {
|
|
51
62
|
const callback: SigningCallback = async (transaction: TransactionSerializable, _signingAddress) => {
|
|
52
63
|
return (await signer.signTransaction(transaction)).toViemTransactionSignature();
|
|
53
64
|
};
|
|
54
65
|
|
|
55
|
-
return new L1TxUtilsWithBlobs(
|
|
66
|
+
return new L1TxUtilsWithBlobs(
|
|
67
|
+
client,
|
|
68
|
+
signer.address,
|
|
69
|
+
callback,
|
|
70
|
+
deps.logger,
|
|
71
|
+
deps.dateProvider,
|
|
72
|
+
config,
|
|
73
|
+
debugMaxGasLimit,
|
|
74
|
+
deps.store,
|
|
75
|
+
deps.metrics,
|
|
76
|
+
);
|
|
56
77
|
}
|
|
@@ -39,7 +39,7 @@ export class ReadOnlyL1TxUtils {
|
|
|
39
39
|
|
|
40
40
|
constructor(
|
|
41
41
|
public client: ViemClient,
|
|
42
|
-
protected logger: Logger = createLogger('
|
|
42
|
+
protected logger: Logger = createLogger('ethereum:readonly-l1-utils'),
|
|
43
43
|
public readonly dateProvider: DateProvider,
|
|
44
44
|
config?: Partial<L1TxUtilsConfig>,
|
|
45
45
|
protected debugMaxGasLimit: boolean = false,
|
package/src/l1_tx_utils/types.ts
CHANGED
package/src/publisher_manager.ts
CHANGED
|
@@ -40,6 +40,11 @@ export class PublisherManager<UtilsType extends L1TxUtils = L1TxUtils> {
|
|
|
40
40
|
this.config = pick(config, 'publisherAllowInvalidStates');
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/** Loads the state of all publishers and resumes monitoring any pending txs */
|
|
44
|
+
public async loadState(): Promise<void> {
|
|
45
|
+
await Promise.all(this.publishers.map(pub => pub.loadStateAndResumeMonitoring()));
|
|
46
|
+
}
|
|
47
|
+
|
|
43
48
|
// Finds and prioritises available publishers based on
|
|
44
49
|
// 1. Validity as per the provided filter function
|
|
45
50
|
// 2. Validity based on the state the publisher is in
|