@aztec/sequencer-client 0.51.0 → 0.52.0
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/global_variable_builder/global_builder.d.ts +2 -1
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +9 -7
- package/dest/index.d.ts +2 -0
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +3 -1
- package/dest/publisher/l1-publisher.d.ts +33 -22
- package/dest/publisher/l1-publisher.d.ts.map +1 -1
- package/dest/publisher/l1-publisher.js +138 -69
- package/dest/sequencer/sequencer.d.ts +25 -2
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +174 -119
- package/package.json +19 -18
- package/src/global_variable_builder/global_builder.ts +8 -5
- package/src/index.ts +2 -0
- package/src/publisher/l1-publisher.ts +161 -77
- package/src/sequencer/sequencer.ts +229 -148
|
@@ -52,23 +52,26 @@ export class GlobalVariableBuilder {
|
|
|
52
52
|
* @param blockNumber - The block number to build global variables for.
|
|
53
53
|
* @param coinbase - The address to receive block reward.
|
|
54
54
|
* @param feeRecipient - The address to receive fees.
|
|
55
|
+
* @param slotNumber - The slot number to use for the global variables, if undefined it will be calculated.
|
|
55
56
|
* @returns The global variables for the given block number.
|
|
56
57
|
*/
|
|
57
58
|
public async buildGlobalVariables(
|
|
58
59
|
blockNumber: Fr,
|
|
59
60
|
coinbase: EthAddress,
|
|
60
61
|
feeRecipient: AztecAddress,
|
|
62
|
+
slotNumber?: bigint,
|
|
61
63
|
): Promise<GlobalVariables> {
|
|
62
64
|
const version = new Fr(await this.rollupContract.read.VERSION());
|
|
63
65
|
const chainId = new Fr(this.publicClient.chain.id);
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
if (slotNumber === undefined) {
|
|
68
|
+
const ts = BigInt((await this.publicClient.getBlock()).timestamp + BigInt(ETHEREUM_SLOT_DURATION));
|
|
69
|
+
slotNumber = await this.rollupContract.read.getSlotAt([ts]);
|
|
70
|
+
}
|
|
66
71
|
|
|
67
|
-
|
|
68
|
-
const slot = await this.rollupContract.read.getSlotAt([ts + BigInt(ETHEREUM_SLOT_DURATION)]);
|
|
69
|
-
const timestamp = await this.rollupContract.read.getTimestampForSlot([slot]);
|
|
72
|
+
const timestamp = await this.rollupContract.read.getTimestampForSlot([slotNumber]);
|
|
70
73
|
|
|
71
|
-
const slotFr = new Fr(
|
|
74
|
+
const slotFr = new Fr(slotNumber);
|
|
72
75
|
const timestampFr = new Fr(timestamp);
|
|
73
76
|
|
|
74
77
|
const gasFees = GasFees.default();
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,8 @@ export * from './publisher/index.js';
|
|
|
4
4
|
export * from './sequencer/index.js';
|
|
5
5
|
export * from './tx_validator/aggregate_tx_validator.js';
|
|
6
6
|
export * from './tx_validator/data_validator.js';
|
|
7
|
+
export * from './tx_validator/double_spend_validator.js';
|
|
8
|
+
export * from './tx_validator/metadata_validator.js';
|
|
7
9
|
|
|
8
10
|
// Used by the node to simulate public parts of transactions. Should these be moved to a shared library?
|
|
9
11
|
export * from './global_variable_builder/index.js';
|
|
@@ -12,6 +12,7 @@ import { type TelemetryClient } from '@aztec/telemetry-client';
|
|
|
12
12
|
|
|
13
13
|
import pick from 'lodash.pick';
|
|
14
14
|
import {
|
|
15
|
+
ContractFunctionRevertedError,
|
|
15
16
|
type GetContractReturnType,
|
|
16
17
|
type Hex,
|
|
17
18
|
type HttpTransport,
|
|
@@ -59,13 +60,6 @@ export type MinimalTransactionReceipt = {
|
|
|
59
60
|
logs: any[];
|
|
60
61
|
};
|
|
61
62
|
|
|
62
|
-
/**
|
|
63
|
-
* @notice An attestation for the sequencing model.
|
|
64
|
-
* @todo This is not where it belongs. But I think we should do a bigger rewrite of some of
|
|
65
|
-
* this spaghetti.
|
|
66
|
-
*/
|
|
67
|
-
export type Attestation = { isEmpty: boolean; v: number; r: `0x${string}`; s: `0x${string}` };
|
|
68
|
-
|
|
69
63
|
/** Arguments to the process method of the rollup contract */
|
|
70
64
|
export type L1ProcessArgs = {
|
|
71
65
|
/** The L2 block header. */
|
|
@@ -94,6 +88,13 @@ export type L1SubmitProofArgs = {
|
|
|
94
88
|
aggregationObject: Buffer;
|
|
95
89
|
};
|
|
96
90
|
|
|
91
|
+
export type MetadataForSlot = {
|
|
92
|
+
proposer: EthAddress;
|
|
93
|
+
slot: bigint;
|
|
94
|
+
pendingBlockNumber: bigint;
|
|
95
|
+
archive: Buffer;
|
|
96
|
+
};
|
|
97
|
+
|
|
97
98
|
/**
|
|
98
99
|
* Publishes L2 blocks to L1. This implementation does *not* retry a transaction in
|
|
99
100
|
* the event of network congestion, but should work for local development.
|
|
@@ -103,6 +104,15 @@ export type L1SubmitProofArgs = {
|
|
|
103
104
|
* Adapted from https://github.com/AztecProtocol/aztec2-internal/blob/master/falafel/src/rollup_publisher.ts.
|
|
104
105
|
*/
|
|
105
106
|
export class L1Publisher {
|
|
107
|
+
// @note If we want to simulate in the future, we have to skip the viem simulations and use `reads` instead
|
|
108
|
+
// This is because the viem simulations are not able to simulate the future, only the current state.
|
|
109
|
+
// This means that we will be simulating as if `block.timestamp` is the same for the next block
|
|
110
|
+
// as for the last block.
|
|
111
|
+
// Nevertheless, it can be quite useful for figuring out why exactly the transaction is failing
|
|
112
|
+
// as a middle ground right now, we will be skipping the simulation and just sending the transaction
|
|
113
|
+
// but only after we have done a successful run of the `validateHeader` for the timestamp in the future.
|
|
114
|
+
public static SKIP_SIMULATION = true;
|
|
115
|
+
|
|
106
116
|
private interruptibleSleep = new InterruptibleSleep();
|
|
107
117
|
private sleepTimeMs: number;
|
|
108
118
|
private interrupted = false;
|
|
@@ -154,24 +164,86 @@ export class L1Publisher {
|
|
|
154
164
|
return Promise.resolve(EthAddress.fromString(this.account.address));
|
|
155
165
|
}
|
|
156
166
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
167
|
+
/**
|
|
168
|
+
* @notice Calls `canProposeAtTime` with the time of the next Ethereum block and the sender address
|
|
169
|
+
*
|
|
170
|
+
* @dev Throws if unable to propose
|
|
171
|
+
*
|
|
172
|
+
* @param archive - The archive that we expect to be current state
|
|
173
|
+
* @return slot - The L2 slot number of the next Ethereum block,
|
|
174
|
+
* @return blockNumber - The L2 block number of the next L2 block
|
|
175
|
+
*/
|
|
176
|
+
public async canProposeAtNextEthBlock(archive: Buffer): Promise<[bigint, bigint]> {
|
|
177
|
+
const ts = BigInt((await this.publicClient.getBlock()).timestamp + BigInt(ETHEREUM_SLOT_DURATION));
|
|
178
|
+
const [slot, blockNumber] = await this.rollupContract.read.canProposeAtTime([
|
|
179
|
+
ts,
|
|
180
|
+
this.account.address,
|
|
181
|
+
`0x${archive.toString('hex')}`,
|
|
182
|
+
]);
|
|
183
|
+
return [slot, blockNumber];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @notice Will call `validateHeader` to make sure that it is possible to propose
|
|
188
|
+
*
|
|
189
|
+
* @dev Throws if unable to propose
|
|
190
|
+
*
|
|
191
|
+
* @param header - The header to propose
|
|
192
|
+
* @param digest - The digest that attestations are signing over
|
|
193
|
+
*
|
|
194
|
+
*/
|
|
195
|
+
public async validateBlockForSubmission(
|
|
196
|
+
header: Header,
|
|
197
|
+
attestationData: { digest: Buffer; signatures: Signature[] } = {
|
|
198
|
+
digest: Buffer.alloc(32),
|
|
199
|
+
signatures: [],
|
|
200
|
+
},
|
|
201
|
+
): Promise<void> {
|
|
202
|
+
const ts = BigInt((await this.publicClient.getBlock()).timestamp + BigInt(ETHEREUM_SLOT_DURATION));
|
|
203
|
+
|
|
204
|
+
const formattedSignatures = attestationData.signatures.map(attest => attest.toViemSignature());
|
|
205
|
+
const flags = { ignoreDA: true, ignoreSignatures: formattedSignatures.length == 0 };
|
|
206
|
+
|
|
207
|
+
const args = [
|
|
208
|
+
`0x${header.toBuffer().toString('hex')}`,
|
|
209
|
+
formattedSignatures,
|
|
210
|
+
`0x${attestationData.digest.toString('hex')}`,
|
|
211
|
+
ts,
|
|
212
|
+
flags,
|
|
213
|
+
] as const;
|
|
214
|
+
|
|
161
215
|
try {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
216
|
+
await this.rollupContract.read.validateHeader(args, { account: this.account });
|
|
217
|
+
} catch (error: unknown) {
|
|
218
|
+
// Specify the type of error
|
|
219
|
+
if (error instanceof ContractFunctionRevertedError) {
|
|
220
|
+
const err = error as ContractFunctionRevertedError;
|
|
221
|
+
this.log.debug(`Validation failed: ${err.message}`, err.data);
|
|
222
|
+
} else {
|
|
223
|
+
this.log.debug(`Unexpected error during validation: ${error}`);
|
|
224
|
+
}
|
|
225
|
+
throw error;
|
|
168
226
|
}
|
|
169
227
|
}
|
|
170
228
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
229
|
+
// @note Assumes that all ethereum slots have blocks
|
|
230
|
+
// Using next Ethereum block so we do NOT need to wait for it being mined before seeing the effect
|
|
231
|
+
public async getMetadataForSlotAtNextEthBlock(): Promise<MetadataForSlot> {
|
|
232
|
+
const ts = BigInt((await this.publicClient.getBlock()).timestamp + BigInt(ETHEREUM_SLOT_DURATION));
|
|
233
|
+
|
|
234
|
+
const [submitter, slot, pendingBlockCount, archive] = await Promise.all([
|
|
235
|
+
this.rollupContract.read.getProposerAt([ts]),
|
|
236
|
+
this.rollupContract.read.getSlotAt([ts]),
|
|
237
|
+
this.rollupContract.read.pendingBlockCount(),
|
|
238
|
+
this.rollupContract.read.archive(),
|
|
239
|
+
]);
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
proposer: EthAddress.fromString(submitter),
|
|
243
|
+
slot,
|
|
244
|
+
pendingBlockNumber: pendingBlockCount - 1n,
|
|
245
|
+
archive: Buffer.from(archive.replace('0x', ''), 'hex'),
|
|
246
|
+
};
|
|
175
247
|
}
|
|
176
248
|
|
|
177
249
|
public async getCurrentEpochCommittee(): Promise<EthAddress[]> {
|
|
@@ -203,44 +275,52 @@ export class L1Publisher {
|
|
|
203
275
|
* @returns True once the tx has been confirmed and is successful, false on revert or interrupt, blocks otherwise.
|
|
204
276
|
*/
|
|
205
277
|
public async processL2Block(block: L2Block, attestations?: Signature[]): Promise<boolean> {
|
|
206
|
-
const ctx = {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
return false;
|
|
212
|
-
}
|
|
213
|
-
const encodedBody = block.body.toBuffer();
|
|
278
|
+
const ctx = {
|
|
279
|
+
blockNumber: block.number,
|
|
280
|
+
slotNumber: block.header.globalVariables.slotNumber.toBigInt(),
|
|
281
|
+
blockHash: block.hash().toString(),
|
|
282
|
+
};
|
|
214
283
|
|
|
215
284
|
const processTxArgs = {
|
|
216
285
|
header: block.header.toBuffer(),
|
|
217
286
|
archive: block.archive.root.toBuffer(),
|
|
218
287
|
blockHash: block.header.hash().toBuffer(),
|
|
219
|
-
body:
|
|
288
|
+
body: block.body.toBuffer(),
|
|
220
289
|
attestations,
|
|
221
290
|
};
|
|
222
291
|
|
|
223
|
-
//
|
|
224
|
-
|
|
292
|
+
// Publish body and propose block (if not already published)
|
|
293
|
+
if (!this.interrupted) {
|
|
225
294
|
let txHash;
|
|
226
295
|
const timer = new Timer();
|
|
227
296
|
|
|
228
|
-
|
|
297
|
+
const isAvailable = await this.checkIfTxsAreAvailable(block);
|
|
298
|
+
|
|
299
|
+
// @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
|
|
300
|
+
// This means that we can avoid the simulation issues in later checks.
|
|
301
|
+
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
302
|
+
// make time consistency checks break.
|
|
303
|
+
await this.validateBlockForSubmission(block.header, {
|
|
304
|
+
digest: block.archive.root.toBuffer(),
|
|
305
|
+
signatures: attestations ?? [],
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
if (isAvailable) {
|
|
229
309
|
this.log.verbose(`Transaction effects of block ${block.number} already published.`, ctx);
|
|
230
|
-
txHash = await this.
|
|
310
|
+
txHash = await this.sendProposeWithoutBodyTx(processTxArgs);
|
|
231
311
|
} else {
|
|
232
|
-
txHash = await this.
|
|
312
|
+
txHash = await this.sendProposeTx(processTxArgs);
|
|
233
313
|
}
|
|
234
314
|
|
|
235
315
|
if (!txHash) {
|
|
236
316
|
this.log.info(`Failed to publish block ${block.number} to L1`, ctx);
|
|
237
|
-
|
|
317
|
+
return false;
|
|
238
318
|
}
|
|
239
319
|
|
|
240
320
|
const receipt = await this.getTransactionReceipt(txHash);
|
|
241
321
|
if (!receipt) {
|
|
242
322
|
this.log.info(`Failed to get receipt for tx ${txHash}`, ctx);
|
|
243
|
-
|
|
323
|
+
return false;
|
|
244
324
|
}
|
|
245
325
|
|
|
246
326
|
// Tx was mined successfully
|
|
@@ -254,17 +334,12 @@ export class L1Publisher {
|
|
|
254
334
|
};
|
|
255
335
|
this.log.info(`Published L2 block to L1 rollup contract`, { ...stats, ...ctx });
|
|
256
336
|
this.metrics.recordProcessBlockTx(timer.ms(), stats);
|
|
337
|
+
|
|
257
338
|
return true;
|
|
258
339
|
}
|
|
259
340
|
|
|
260
341
|
this.metrics.recordFailedTx('process');
|
|
261
342
|
|
|
262
|
-
// Check if someone else incremented the block number
|
|
263
|
-
if (!(await this.checkLastArchiveHash(lastArchive))) {
|
|
264
|
-
this.log.warn('Publish failed. Detected different last archive hash.', ctx);
|
|
265
|
-
break;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
343
|
this.log.error(`Rollup.process tx status failed: ${receipt.transactionHash}`, ctx);
|
|
269
344
|
await this.sleepOrInterrupted();
|
|
270
345
|
}
|
|
@@ -280,7 +355,7 @@ export class L1Publisher {
|
|
|
280
355
|
aggregationObject: Fr[],
|
|
281
356
|
proof: Proof,
|
|
282
357
|
): Promise<boolean> {
|
|
283
|
-
const ctx = { blockNumber: header.globalVariables.blockNumber };
|
|
358
|
+
const ctx = { blockNumber: header.globalVariables.blockNumber, slotNumber: header.globalVariables.slotNumber };
|
|
284
359
|
|
|
285
360
|
const txArgs: L1SubmitProofArgs = {
|
|
286
361
|
header: header.toBuffer(),
|
|
@@ -291,16 +366,16 @@ export class L1Publisher {
|
|
|
291
366
|
};
|
|
292
367
|
|
|
293
368
|
// Process block
|
|
294
|
-
|
|
369
|
+
if (!this.interrupted) {
|
|
295
370
|
const timer = new Timer();
|
|
296
371
|
const txHash = await this.sendSubmitProofTx(txArgs);
|
|
297
372
|
if (!txHash) {
|
|
298
|
-
|
|
373
|
+
return false;
|
|
299
374
|
}
|
|
300
375
|
|
|
301
376
|
const receipt = await this.getTransactionReceipt(txHash);
|
|
302
377
|
if (!receipt) {
|
|
303
|
-
|
|
378
|
+
return false;
|
|
304
379
|
}
|
|
305
380
|
|
|
306
381
|
// Tx was mined successfully
|
|
@@ -341,26 +416,6 @@ export class L1Publisher {
|
|
|
341
416
|
this.interrupted = false;
|
|
342
417
|
}
|
|
343
418
|
|
|
344
|
-
async getCurrentArchive(): Promise<Buffer> {
|
|
345
|
-
const archive = await this.rollupContract.read.archive();
|
|
346
|
-
return Buffer.from(archive.replace('0x', ''), 'hex');
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* Verifies that the given value of last archive in a block header equals current archive of the rollup contract
|
|
351
|
-
* @param lastArchive - The last archive of the block we wish to publish.
|
|
352
|
-
* @returns Boolean indicating if the hashes are equal.
|
|
353
|
-
*/
|
|
354
|
-
private async checkLastArchiveHash(lastArchive: Buffer): Promise<boolean> {
|
|
355
|
-
const fromChain = await this.getCurrentArchive();
|
|
356
|
-
const areSame = lastArchive.equals(fromChain);
|
|
357
|
-
if (!areSame) {
|
|
358
|
-
this.log.debug(`Contract archive: ${fromChain.toString('hex')}`);
|
|
359
|
-
this.log.debug(`New block last archive: ${lastArchive.toString('hex')}`);
|
|
360
|
-
}
|
|
361
|
-
return areSame;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
419
|
private async sendSubmitProofTx(submitProofArgs: L1SubmitProofArgs): Promise<string | undefined> {
|
|
365
420
|
try {
|
|
366
421
|
const size = Object.values(submitProofArgs).reduce((acc, arg) => acc + arg.length, 0);
|
|
@@ -375,6 +430,12 @@ export class L1Publisher {
|
|
|
375
430
|
`0x${proof.toString('hex')}`,
|
|
376
431
|
] as const;
|
|
377
432
|
|
|
433
|
+
if (!L1Publisher.SKIP_SIMULATION) {
|
|
434
|
+
await this.rollupContract.simulate.submitBlockRootProof(args, {
|
|
435
|
+
account: this.account,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
378
439
|
return await this.rollupContract.write.submitBlockRootProof(args, {
|
|
379
440
|
account: this.account,
|
|
380
441
|
});
|
|
@@ -384,12 +445,17 @@ export class L1Publisher {
|
|
|
384
445
|
}
|
|
385
446
|
}
|
|
386
447
|
|
|
448
|
+
// This is used in `integration_l1_publisher.test.ts` currently. Could be removed though.
|
|
387
449
|
private async sendPublishTx(encodedBody: Buffer): Promise<string | undefined> {
|
|
388
|
-
|
|
450
|
+
if (!this.interrupted) {
|
|
389
451
|
try {
|
|
390
452
|
this.log.info(`TxEffects size=${encodedBody.length} bytes`);
|
|
391
453
|
const args = [`0x${encodedBody.toString('hex')}`] as const;
|
|
392
454
|
|
|
455
|
+
await this.availabilityOracleContract.simulate.publish(args, {
|
|
456
|
+
account: this.account,
|
|
457
|
+
});
|
|
458
|
+
|
|
393
459
|
return await this.availabilityOracleContract.write.publish(args, {
|
|
394
460
|
account: this.account,
|
|
395
461
|
});
|
|
@@ -400,8 +466,8 @@ export class L1Publisher {
|
|
|
400
466
|
}
|
|
401
467
|
}
|
|
402
468
|
|
|
403
|
-
private async
|
|
404
|
-
|
|
469
|
+
private async sendProposeWithoutBodyTx(encodedData: L1ProcessArgs): Promise<string | undefined> {
|
|
470
|
+
if (!this.interrupted) {
|
|
405
471
|
try {
|
|
406
472
|
if (encodedData.attestations) {
|
|
407
473
|
const attestations = encodedData.attestations.map(attest => attest.toViemSignature());
|
|
@@ -412,7 +478,11 @@ export class L1Publisher {
|
|
|
412
478
|
attestations,
|
|
413
479
|
] as const;
|
|
414
480
|
|
|
415
|
-
|
|
481
|
+
if (!L1Publisher.SKIP_SIMULATION) {
|
|
482
|
+
await this.rollupContract.simulate.propose(args, { account: this.account });
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return await this.rollupContract.write.propose(args, {
|
|
416
486
|
account: this.account,
|
|
417
487
|
});
|
|
418
488
|
} else {
|
|
@@ -422,7 +492,10 @@ export class L1Publisher {
|
|
|
422
492
|
`0x${encodedData.blockHash.toString('hex')}`,
|
|
423
493
|
] as const;
|
|
424
494
|
|
|
425
|
-
|
|
495
|
+
if (!L1Publisher.SKIP_SIMULATION) {
|
|
496
|
+
await this.rollupContract.simulate.propose(args, { account: this.account });
|
|
497
|
+
}
|
|
498
|
+
return await this.rollupContract.write.propose(args, {
|
|
426
499
|
account: this.account,
|
|
427
500
|
});
|
|
428
501
|
}
|
|
@@ -433,10 +506,9 @@ export class L1Publisher {
|
|
|
433
506
|
}
|
|
434
507
|
}
|
|
435
508
|
|
|
436
|
-
private async
|
|
437
|
-
|
|
509
|
+
private async sendProposeTx(encodedData: L1ProcessArgs): Promise<string | undefined> {
|
|
510
|
+
if (!this.interrupted) {
|
|
438
511
|
try {
|
|
439
|
-
// @note This is quite a sin, but I'm committing war crimes in this code already.
|
|
440
512
|
if (encodedData.attestations) {
|
|
441
513
|
const attestations = encodedData.attestations.map(attest => attest.toViemSignature());
|
|
442
514
|
const args = [
|
|
@@ -447,7 +519,13 @@ export class L1Publisher {
|
|
|
447
519
|
`0x${encodedData.body.toString('hex')}`,
|
|
448
520
|
] as const;
|
|
449
521
|
|
|
450
|
-
|
|
522
|
+
if (!L1Publisher.SKIP_SIMULATION) {
|
|
523
|
+
await this.rollupContract.simulate.propose(args, {
|
|
524
|
+
account: this.account,
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return await this.rollupContract.write.propose(args, {
|
|
451
529
|
account: this.account,
|
|
452
530
|
});
|
|
453
531
|
} else {
|
|
@@ -458,7 +536,13 @@ export class L1Publisher {
|
|
|
458
536
|
`0x${encodedData.body.toString('hex')}`,
|
|
459
537
|
] as const;
|
|
460
538
|
|
|
461
|
-
|
|
539
|
+
if (!L1Publisher.SKIP_SIMULATION) {
|
|
540
|
+
await this.rollupContract.simulate.propose(args, {
|
|
541
|
+
account: this.account,
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return await this.rollupContract.write.propose(args, {
|
|
462
546
|
account: this.account,
|
|
463
547
|
});
|
|
464
548
|
}
|