@aztec/sequencer-client 0.0.0-test.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.
Files changed (110) hide show
  1. package/README.md +45 -0
  2. package/dest/client/index.d.ts +2 -0
  3. package/dest/client/index.d.ts.map +1 -0
  4. package/dest/client/index.js +1 -0
  5. package/dest/client/sequencer-client.d.ts +71 -0
  6. package/dest/client/sequencer-client.d.ts.map +1 -0
  7. package/dest/client/sequencer-client.js +117 -0
  8. package/dest/config.d.ts +29 -0
  9. package/dest/config.d.ts.map +1 -0
  10. package/dest/config.js +143 -0
  11. package/dest/global_variable_builder/global_builder.d.ts +32 -0
  12. package/dest/global_variable_builder/global_builder.d.ts.map +1 -0
  13. package/dest/global_variable_builder/global_builder.js +79 -0
  14. package/dest/global_variable_builder/index.d.ts +2 -0
  15. package/dest/global_variable_builder/index.d.ts.map +1 -0
  16. package/dest/global_variable_builder/index.js +1 -0
  17. package/dest/index.d.ts +8 -0
  18. package/dest/index.d.ts.map +1 -0
  19. package/dest/index.js +9 -0
  20. package/dest/publisher/config.d.ts +31 -0
  21. package/dest/publisher/config.d.ts.map +1 -0
  22. package/dest/publisher/config.js +35 -0
  23. package/dest/publisher/index.d.ts +2 -0
  24. package/dest/publisher/index.d.ts.map +1 -0
  25. package/dest/publisher/index.js +1 -0
  26. package/dest/publisher/sequencer-publisher-metrics.d.ts +25 -0
  27. package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -0
  28. package/dest/publisher/sequencer-publisher-metrics.js +129 -0
  29. package/dest/publisher/sequencer-publisher.d.ts +152 -0
  30. package/dest/publisher/sequencer-publisher.d.ts.map +1 -0
  31. package/dest/publisher/sequencer-publisher.js +481 -0
  32. package/dest/sequencer/allowed.d.ts +3 -0
  33. package/dest/sequencer/allowed.d.ts.map +1 -0
  34. package/dest/sequencer/allowed.js +27 -0
  35. package/dest/sequencer/config.d.ts +2 -0
  36. package/dest/sequencer/config.d.ts.map +1 -0
  37. package/dest/sequencer/config.js +1 -0
  38. package/dest/sequencer/index.d.ts +4 -0
  39. package/dest/sequencer/index.d.ts.map +1 -0
  40. package/dest/sequencer/index.js +3 -0
  41. package/dest/sequencer/metrics.d.ts +24 -0
  42. package/dest/sequencer/metrics.d.ts.map +1 -0
  43. package/dest/sequencer/metrics.js +102 -0
  44. package/dest/sequencer/sequencer.d.ts +180 -0
  45. package/dest/sequencer/sequencer.d.ts.map +1 -0
  46. package/dest/sequencer/sequencer.js +623 -0
  47. package/dest/sequencer/timetable.d.ts +38 -0
  48. package/dest/sequencer/timetable.d.ts.map +1 -0
  49. package/dest/sequencer/timetable.js +110 -0
  50. package/dest/sequencer/utils.d.ts +48 -0
  51. package/dest/sequencer/utils.d.ts.map +1 -0
  52. package/dest/sequencer/utils.js +53 -0
  53. package/dest/slasher/factory.d.ts +7 -0
  54. package/dest/slasher/factory.d.ts.map +1 -0
  55. package/dest/slasher/factory.js +8 -0
  56. package/dest/slasher/index.d.ts +3 -0
  57. package/dest/slasher/index.d.ts.map +1 -0
  58. package/dest/slasher/index.js +2 -0
  59. package/dest/slasher/slasher_client.d.ts +75 -0
  60. package/dest/slasher/slasher_client.d.ts.map +1 -0
  61. package/dest/slasher/slasher_client.js +132 -0
  62. package/dest/test/index.d.ts +17 -0
  63. package/dest/test/index.d.ts.map +1 -0
  64. package/dest/test/index.js +10 -0
  65. package/dest/tx_validator/archive_cache.d.ts +14 -0
  66. package/dest/tx_validator/archive_cache.d.ts.map +1 -0
  67. package/dest/tx_validator/archive_cache.js +22 -0
  68. package/dest/tx_validator/gas_validator.d.ts +14 -0
  69. package/dest/tx_validator/gas_validator.d.ts.map +1 -0
  70. package/dest/tx_validator/gas_validator.js +78 -0
  71. package/dest/tx_validator/nullifier_cache.d.ts +16 -0
  72. package/dest/tx_validator/nullifier_cache.d.ts.map +1 -0
  73. package/dest/tx_validator/nullifier_cache.js +24 -0
  74. package/dest/tx_validator/phases_validator.d.ts +12 -0
  75. package/dest/tx_validator/phases_validator.d.ts.map +1 -0
  76. package/dest/tx_validator/phases_validator.js +80 -0
  77. package/dest/tx_validator/test_utils.d.ts +23 -0
  78. package/dest/tx_validator/test_utils.d.ts.map +1 -0
  79. package/dest/tx_validator/test_utils.js +26 -0
  80. package/dest/tx_validator/tx_validator_factory.d.ts +18 -0
  81. package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -0
  82. package/dest/tx_validator/tx_validator_factory.js +50 -0
  83. package/package.json +121 -0
  84. package/src/client/index.ts +1 -0
  85. package/src/client/sequencer-client.ts +219 -0
  86. package/src/config.ts +179 -0
  87. package/src/global_variable_builder/global_builder.ts +108 -0
  88. package/src/global_variable_builder/index.ts +1 -0
  89. package/src/index.ts +10 -0
  90. package/src/publisher/config.ts +75 -0
  91. package/src/publisher/index.ts +1 -0
  92. package/src/publisher/sequencer-publisher-metrics.ts +176 -0
  93. package/src/publisher/sequencer-publisher.ts +625 -0
  94. package/src/sequencer/allowed.ts +36 -0
  95. package/src/sequencer/config.ts +1 -0
  96. package/src/sequencer/index.ts +3 -0
  97. package/src/sequencer/metrics.ts +137 -0
  98. package/src/sequencer/sequencer.ts +759 -0
  99. package/src/sequencer/timetable.ts +123 -0
  100. package/src/sequencer/utils.ts +74 -0
  101. package/src/slasher/factory.ts +15 -0
  102. package/src/slasher/index.ts +2 -0
  103. package/src/slasher/slasher_client.ts +193 -0
  104. package/src/test/index.ts +20 -0
  105. package/src/tx_validator/archive_cache.ts +28 -0
  106. package/src/tx_validator/gas_validator.ts +101 -0
  107. package/src/tx_validator/nullifier_cache.ts +30 -0
  108. package/src/tx_validator/phases_validator.ts +98 -0
  109. package/src/tx_validator/test_utils.ts +48 -0
  110. package/src/tx_validator/tx_validator_factory.ts +120 -0
@@ -0,0 +1,481 @@
1
+ import { Blob } from '@aztec/blob-lib';
2
+ import { createBlobSinkClient } from '@aztec/blob-sink/client';
3
+ import { FormattedViemError, RollupContract, formatViemError } from '@aztec/ethereum';
4
+ import { toHex } from '@aztec/foundation/bigint-buffer';
5
+ import { EthAddress } from '@aztec/foundation/eth-address';
6
+ import { createLogger } from '@aztec/foundation/log';
7
+ import { Timer } from '@aztec/foundation/timer';
8
+ import { ForwarderAbi, RollupAbi } from '@aztec/l1-artifacts';
9
+ import { ConsensusPayload, SignatureDomainSeparator, getHashedSignaturePayload } from '@aztec/stdlib/p2p';
10
+ import { getTelemetryClient } from '@aztec/telemetry-client';
11
+ import pick from 'lodash.pick';
12
+ import { encodeFunctionData } from 'viem';
13
+ import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
14
+ export var VoteType = /*#__PURE__*/ function(VoteType) {
15
+ VoteType[VoteType["GOVERNANCE"] = 0] = "GOVERNANCE";
16
+ VoteType[VoteType["SLASHING"] = 1] = "SLASHING";
17
+ return VoteType;
18
+ }({});
19
+ export class SequencerPublisher {
20
+ interrupted = false;
21
+ metrics;
22
+ epochCache;
23
+ forwarderContract;
24
+ governanceLog = createLogger('sequencer:publisher:governance');
25
+ governanceProposerAddress;
26
+ governancePayload = EthAddress.ZERO;
27
+ slashingLog = createLogger('sequencer:publisher:slashing');
28
+ slashingProposerAddress;
29
+ getSlashPayload = undefined;
30
+ myLastVotes = {
31
+ [0]: 0n,
32
+ [1]: 0n
33
+ };
34
+ log = createLogger('sequencer:publisher');
35
+ ethereumSlotDuration;
36
+ blobSinkClient;
37
+ // @note - with blobs, the below estimate seems too large.
38
+ // Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
39
+ // Total used for emptier block from above test: 429k (of which 84k is 1x blob)
40
+ static PROPOSE_GAS_GUESS = 12_000_000n;
41
+ l1TxUtils;
42
+ rollupContract;
43
+ govProposerContract;
44
+ slashingProposerContract;
45
+ requests = [];
46
+ constructor(config, deps){
47
+ this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
48
+ this.epochCache = deps.epochCache;
49
+ this.blobSinkClient = deps.blobSinkClient ?? createBlobSinkClient(config);
50
+ const telemetry = deps.telemetry ?? getTelemetryClient();
51
+ this.metrics = new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
52
+ this.l1TxUtils = deps.l1TxUtils;
53
+ this.rollupContract = deps.rollupContract;
54
+ this.forwarderContract = deps.forwarderContract;
55
+ this.govProposerContract = deps.governanceProposerContract;
56
+ this.slashingProposerContract = deps.slashingProposerContract;
57
+ }
58
+ registerSlashPayloadGetter(callback) {
59
+ this.getSlashPayload = callback;
60
+ }
61
+ getForwarderAddress() {
62
+ return EthAddress.fromString(this.forwarderContract.getAddress());
63
+ }
64
+ getSenderAddress() {
65
+ return EthAddress.fromString(this.l1TxUtils.getSenderAddress());
66
+ }
67
+ getGovernancePayload() {
68
+ return this.governancePayload;
69
+ }
70
+ setGovernancePayload(payload) {
71
+ this.governancePayload = payload;
72
+ }
73
+ addRequest(request) {
74
+ this.requests.push(request);
75
+ }
76
+ getCurrentL2Slot() {
77
+ return this.epochCache.getEpochAndSlotNow().slot;
78
+ }
79
+ /**
80
+ * Sends all requests that are still valid.
81
+ * @returns one of:
82
+ * - A receipt and stats if the tx succeeded
83
+ * - a receipt and errorMsg if it failed on L1
84
+ * - undefined if no valid requests are found OR the tx failed to send.
85
+ */ async sendRequests() {
86
+ const requestsToProcess = [
87
+ ...this.requests
88
+ ];
89
+ this.requests = [];
90
+ if (this.interrupted) {
91
+ return undefined;
92
+ }
93
+ const currentL2Slot = this.getCurrentL2Slot();
94
+ this.log.debug(`Current L2 slot: ${currentL2Slot}`);
95
+ const validRequests = requestsToProcess.filter((request)=>request.lastValidL2Slot >= currentL2Slot);
96
+ if (validRequests.length !== requestsToProcess.length) {
97
+ this.log.warn(`Some requests were expired for slot ${currentL2Slot}`, {
98
+ validRequests: validRequests.map((request)=>({
99
+ action: request.action,
100
+ lastValidL2Slot: request.lastValidL2Slot
101
+ })),
102
+ requests: requestsToProcess.map((request)=>({
103
+ action: request.action,
104
+ lastValidL2Slot: request.lastValidL2Slot
105
+ }))
106
+ });
107
+ }
108
+ if (validRequests.length === 0) {
109
+ this.log.debug(`No valid requests to send`);
110
+ return undefined;
111
+ }
112
+ // @note - we can only have one gas config and one blob config per bundle
113
+ // find requests with gas and blob configs
114
+ // See https://github.com/AztecProtocol/aztec-packages/issues/11513
115
+ const gasConfigs = requestsToProcess.filter((request)=>request.gasConfig);
116
+ const blobConfigs = requestsToProcess.filter((request)=>request.blobConfig);
117
+ if (gasConfigs.length > 1 || blobConfigs.length > 1) {
118
+ throw new Error('Multiple gas or blob configs found');
119
+ }
120
+ const gasConfig = gasConfigs[0]?.gasConfig;
121
+ const blobConfig = blobConfigs[0]?.blobConfig;
122
+ try {
123
+ this.log.debug('Forwarding transactions', {
124
+ validRequests: validRequests.map((request)=>request.action)
125
+ });
126
+ const result = await this.forwarderContract.forward(validRequests.map((request)=>request.request), this.l1TxUtils, gasConfig, blobConfig, this.log);
127
+ this.callbackBundledTransactions(validRequests, result);
128
+ return result;
129
+ } catch (err) {
130
+ const viemError = formatViemError(err);
131
+ this.log.error(`Failed to publish bundled transactions`, viemError);
132
+ return undefined;
133
+ } finally{
134
+ try {
135
+ this.metrics.recordSenderBalance(await this.l1TxUtils.getSenderBalance(), this.l1TxUtils.getSenderAddress());
136
+ } catch (err) {
137
+ this.log.warn(`Failed to record balance after sending tx: ${err}`);
138
+ }
139
+ }
140
+ }
141
+ callbackBundledTransactions(requests, result) {
142
+ const success = result?.receipt.status === 'success';
143
+ const logger = success ? this.log.info : this.log.error;
144
+ for (const request of requests){
145
+ logger(`Bundled [${request.action}] transaction [${success ? 'succeeded' : 'failed'}]`);
146
+ request.onResult?.(request.request, result);
147
+ }
148
+ }
149
+ /**
150
+ * @notice Will call `canProposeAtNextEthBlock` to make sure that it is possible to propose
151
+ * @param tipArchive - The archive to check
152
+ * @returns The slot and block number if it is possible to propose, undefined otherwise
153
+ */ canProposeAtNextEthBlock(tipArchive) {
154
+ const ignoredErrors = [
155
+ 'SlotAlreadyInChain',
156
+ 'InvalidProposer',
157
+ 'InvalidArchive'
158
+ ];
159
+ return this.rollupContract.canProposeAtNextEthBlock(tipArchive, this.getForwarderAddress().toString(), this.ethereumSlotDuration).catch((err)=>{
160
+ if (err instanceof FormattedViemError && ignoredErrors.find((e)=>err.message.includes(e))) {
161
+ this.log.debug(err.message);
162
+ } else {
163
+ this.log.error(err.name, err);
164
+ }
165
+ return undefined;
166
+ });
167
+ }
168
+ /**
169
+ * @notice Will call `validateHeader` to make sure that it is possible to propose
170
+ *
171
+ * @dev Throws if unable to propose
172
+ *
173
+ * @param header - The header to propose
174
+ * @param digest - The digest that attestations are signing over
175
+ *
176
+ */ async validateBlockForSubmission(header, attestationData = {
177
+ digest: Buffer.alloc(32),
178
+ signatures: []
179
+ }) {
180
+ const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
181
+ const formattedSignatures = attestationData.signatures.map((attest)=>attest.toViemSignature());
182
+ const flags = {
183
+ ignoreDA: true,
184
+ ignoreSignatures: formattedSignatures.length == 0
185
+ };
186
+ const args = [
187
+ `0x${header.toBuffer().toString('hex')}`,
188
+ formattedSignatures,
189
+ `0x${attestationData.digest.toString('hex')}`,
190
+ ts,
191
+ `0x${header.contentCommitment.blobsHash.toString('hex')}`,
192
+ flags
193
+ ];
194
+ await this.rollupContract.validateHeader(args, this.getForwarderAddress().toString());
195
+ return ts;
196
+ }
197
+ async getCurrentEpochCommittee() {
198
+ const committee = await this.rollupContract.getCurrentEpochCommittee();
199
+ return committee.map(EthAddress.fromString);
200
+ }
201
+ async enqueueCastVoteHelper(slotNumber, timestamp, voteType, payload, base) {
202
+ if (this.myLastVotes[voteType] >= slotNumber) {
203
+ return false;
204
+ }
205
+ if (payload.equals(EthAddress.ZERO)) {
206
+ return false;
207
+ }
208
+ const round = await base.computeRound(slotNumber);
209
+ const [proposer, roundInfo] = await Promise.all([
210
+ this.rollupContract.getProposerAt(timestamp),
211
+ base.getRoundInfo(this.rollupContract.address, round)
212
+ ]);
213
+ if (proposer.toLowerCase() !== this.getForwarderAddress().toString().toLowerCase()) {
214
+ return false;
215
+ }
216
+ if (roundInfo.lastVote >= slotNumber) {
217
+ return false;
218
+ }
219
+ const cachedLastVote = this.myLastVotes[voteType];
220
+ this.myLastVotes[voteType] = slotNumber;
221
+ this.addRequest({
222
+ action: voteType === 0 ? 'governance-vote' : 'slashing-vote',
223
+ request: base.createVoteRequest(payload.toString()),
224
+ lastValidL2Slot: slotNumber,
225
+ onResult: (_request, result)=>{
226
+ if (!result || result.receipt.status !== 'success') {
227
+ this.myLastVotes[voteType] = cachedLastVote;
228
+ } else {
229
+ this.log.info(`Cast [${voteType}] vote for slot ${slotNumber}`);
230
+ }
231
+ }
232
+ });
233
+ return true;
234
+ }
235
+ async getVoteConfig(slotNumber, voteType) {
236
+ if (voteType === 0) {
237
+ return {
238
+ payload: this.governancePayload,
239
+ base: this.govProposerContract
240
+ };
241
+ } else if (voteType === 1) {
242
+ if (!this.getSlashPayload) {
243
+ return undefined;
244
+ }
245
+ const slashPayload = await this.getSlashPayload(slotNumber);
246
+ if (!slashPayload) {
247
+ return undefined;
248
+ }
249
+ return {
250
+ payload: slashPayload,
251
+ base: this.slashingProposerContract
252
+ };
253
+ }
254
+ throw new Error('Unreachable: Invalid vote type');
255
+ }
256
+ /**
257
+ * Enqueues a castVote transaction to cast a vote for a given slot number.
258
+ * @param slotNumber - The slot number to cast a vote for.
259
+ * @param timestamp - The timestamp of the slot to cast a vote for.
260
+ * @param voteType - The type of vote to cast.
261
+ * @returns True if the vote was successfully enqueued, false otherwise.
262
+ */ async enqueueCastVote(slotNumber, timestamp, voteType) {
263
+ const voteConfig = await this.getVoteConfig(slotNumber, voteType);
264
+ if (!voteConfig) {
265
+ return false;
266
+ }
267
+ const { payload, base } = voteConfig;
268
+ return this.enqueueCastVoteHelper(slotNumber, timestamp, voteType, payload, base);
269
+ }
270
+ /**
271
+ * Proposes a L2 block on L1.
272
+ *
273
+ * @param block - L2 block to propose.
274
+ * @returns True if the tx has been enqueued, throws otherwise. See #9315
275
+ */ async enqueueProposeL2Block(block, attestations, txHashes, opts = {}) {
276
+ const consensusPayload = new ConsensusPayload(block.header, block.archive.root, txHashes ?? []);
277
+ const digest = await getHashedSignaturePayload(consensusPayload, SignatureDomainSeparator.blockAttestation);
278
+ const blobs = await Blob.getBlobs(block.body.toBlobFields());
279
+ const proposeTxArgs = {
280
+ header: block.header.toBuffer(),
281
+ archive: block.archive.root.toBuffer(),
282
+ blockHash: (await block.header.hash()).toBuffer(),
283
+ body: block.body.toBuffer(),
284
+ blobs,
285
+ attestations,
286
+ txHashes: txHashes ?? []
287
+ };
288
+ // @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
289
+ // This means that we can avoid the simulation issues in later checks.
290
+ // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
291
+ // make time consistency checks break.
292
+ const ts = await this.validateBlockForSubmission(block.header, {
293
+ digest: digest.toBuffer(),
294
+ signatures: attestations ?? []
295
+ });
296
+ this.log.debug(`Submitting propose transaction`);
297
+ await this.addProposeTx(block, proposeTxArgs, opts, ts);
298
+ return true;
299
+ }
300
+ /**
301
+ * Calling `interrupt` will cause any in progress call to `publishRollup` to return `false` asap.
302
+ * Be warned, the call may return false even if the tx subsequently gets successfully mined.
303
+ * In practice this shouldn't matter, as we'll only ever be calling `interrupt` when we know it's going to fail.
304
+ * A call to `restart` is required before you can continue publishing.
305
+ */ interrupt() {
306
+ this.interrupted = true;
307
+ this.l1TxUtils.interrupt();
308
+ }
309
+ /** Restarts the publisher after calling `interrupt`. */ restart() {
310
+ this.interrupted = false;
311
+ this.l1TxUtils.restart();
312
+ }
313
+ async prepareProposeTx(encodedData, timestamp) {
314
+ const kzg = Blob.getViemKzgInstance();
315
+ const blobInput = Blob.getEthBlobEvaluationInputs(encodedData.blobs);
316
+ this.log.debug('Validating blob input', {
317
+ blobInput
318
+ });
319
+ const blobEvaluationGas = await this.l1TxUtils.estimateGas(this.l1TxUtils.walletClient.account, {
320
+ to: this.rollupContract.address,
321
+ data: encodeFunctionData({
322
+ abi: RollupAbi,
323
+ functionName: 'validateBlobs',
324
+ args: [
325
+ blobInput
326
+ ]
327
+ })
328
+ }, {}, {
329
+ blobs: encodedData.blobs.map((b)=>b.data),
330
+ kzg
331
+ }).catch((err)=>{
332
+ const { message, metaMessages } = formatViemError(err);
333
+ this.log.error(`Failed to validate blobs`, message, {
334
+ metaMessages
335
+ });
336
+ throw new Error('Failed to validate blobs');
337
+ });
338
+ const attestations = encodedData.attestations ? encodedData.attestations.map((attest)=>attest.toViemSignature()) : [];
339
+ const txHashes = encodedData.txHashes ? encodedData.txHashes.map((txHash)=>txHash.toString()) : [];
340
+ const args = [
341
+ {
342
+ header: `0x${encodedData.header.toString('hex')}`,
343
+ archive: `0x${encodedData.archive.toString('hex')}`,
344
+ oracleInput: {
345
+ // We are currently not modifying these. See #9963
346
+ feeAssetPriceModifier: 0n
347
+ },
348
+ blockHash: `0x${encodedData.blockHash.toString('hex')}`,
349
+ txHashes
350
+ },
351
+ attestations,
352
+ blobInput
353
+ ];
354
+ const rollupData = encodeFunctionData({
355
+ abi: RollupAbi,
356
+ functionName: 'propose',
357
+ args
358
+ });
359
+ const forwarderData = encodeFunctionData({
360
+ abi: ForwarderAbi,
361
+ functionName: 'forward',
362
+ args: [
363
+ [
364
+ this.rollupContract.address
365
+ ],
366
+ [
367
+ rollupData
368
+ ]
369
+ ]
370
+ });
371
+ const simulationResult = await this.l1TxUtils.simulateGasUsed({
372
+ to: this.getForwarderAddress().toString(),
373
+ data: forwarderData,
374
+ gas: SequencerPublisher.PROPOSE_GAS_GUESS
375
+ }, {
376
+ // @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
377
+ time: timestamp + 1n,
378
+ // @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit
379
+ gasLimit: SequencerPublisher.PROPOSE_GAS_GUESS * 2n
380
+ }, [
381
+ {
382
+ address: this.rollupContract.address,
383
+ // @note we override checkBlob to false since blobs are not part simulate()
384
+ stateDiff: [
385
+ {
386
+ slot: toHex(RollupContract.checkBlobStorageSlot, true),
387
+ value: toHex(0n, true)
388
+ }
389
+ ]
390
+ }
391
+ ], {
392
+ // @note fallback gas estimate to use if the node doesn't support simulation API
393
+ fallbackGasEstimate: SequencerPublisher.PROPOSE_GAS_GUESS
394
+ }).catch((err)=>{
395
+ const { message, metaMessages } = formatViemError(err);
396
+ this.log.error(`Failed to simulate gas used`, message, {
397
+ metaMessages
398
+ });
399
+ throw new Error('Failed to simulate gas used');
400
+ });
401
+ return {
402
+ args,
403
+ blobEvaluationGas,
404
+ rollupData,
405
+ simulationResult
406
+ };
407
+ }
408
+ async addProposeTx(block, encodedData, opts = {}, timestamp) {
409
+ const timer = new Timer();
410
+ const kzg = Blob.getViemKzgInstance();
411
+ const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, timestamp);
412
+ const startBlock = await this.l1TxUtils.getBlockNumber();
413
+ const blockHash = await block.hash();
414
+ return this.addRequest({
415
+ action: 'propose',
416
+ request: {
417
+ to: this.rollupContract.address,
418
+ data: rollupData
419
+ },
420
+ lastValidL2Slot: block.header.globalVariables.slotNumber.toBigInt(),
421
+ gasConfig: {
422
+ ...opts,
423
+ gasLimit: this.l1TxUtils.bumpGasLimit(simulationResult + blobEvaluationGas)
424
+ },
425
+ blobConfig: {
426
+ blobs: encodedData.blobs.map((b)=>b.data),
427
+ kzg
428
+ },
429
+ onResult: (request, result)=>{
430
+ if (!result) {
431
+ return;
432
+ }
433
+ const { receipt, stats, errorMsg } = result;
434
+ if (receipt.status === 'success') {
435
+ const endBlock = receipt.blockNumber;
436
+ const inclusionBlocks = Number(endBlock - startBlock);
437
+ const publishStats = {
438
+ gasPrice: receipt.effectiveGasPrice,
439
+ gasUsed: receipt.gasUsed,
440
+ blobGasUsed: receipt.blobGasUsed ?? 0n,
441
+ blobDataGas: receipt.blobGasPrice ?? 0n,
442
+ transactionHash: receipt.transactionHash,
443
+ ...pick(stats, 'calldataGas', 'calldataSize', 'sender'),
444
+ ...block.getStats(),
445
+ eventName: 'rollup-published-to-l1',
446
+ blobCount: encodedData.blobs.length,
447
+ inclusionBlocks
448
+ };
449
+ this.log.verbose(`Published L2 block to L1 rollup contract`, {
450
+ ...stats,
451
+ ...block.getStats()
452
+ });
453
+ this.metrics.recordProcessBlockTx(timer.ms(), publishStats);
454
+ // Send the blobs to the blob sink
455
+ this.sendBlobsToBlobSink(receipt.blockHash, encodedData.blobs).catch((_err)=>{
456
+ this.log.error('Failed to send blobs to blob sink');
457
+ });
458
+ return true;
459
+ } else {
460
+ this.metrics.recordFailedTx('process');
461
+ this.log.error(`Rollup process tx reverted. ${errorMsg ?? 'No error message'}`, undefined, {
462
+ ...block.getStats(),
463
+ txHash: receipt.transactionHash,
464
+ blockHash,
465
+ slotNumber: block.header.globalVariables.slotNumber.toBigInt()
466
+ });
467
+ }
468
+ }
469
+ });
470
+ }
471
+ /**
472
+ * Send blobs to the blob sink
473
+ *
474
+ * If a blob sink url is configured, then we send blobs to the blob sink
475
+ * - for now we use the blockHash as the identifier for the blobs;
476
+ * In the future this will move to be the beacon block id - which takes a bit more work
477
+ * to calculate and will need to be mocked in e2e tests
478
+ */ sendBlobsToBlobSink(blockHash, blobs) {
479
+ return this.blobSinkClient.sendBlobsToBlobSink(blockHash, blobs);
480
+ }
481
+ }
@@ -0,0 +1,3 @@
1
+ import type { AllowedElement } from '@aztec/stdlib/interfaces/server';
2
+ export declare function getDefaultAllowedSetupFunctions(): Promise<AllowedElement[]>;
3
+ //# sourceMappingURL=allowed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allowed.d.ts","sourceRoot":"","sources":["../../src/sequencer/allowed.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAItE,wBAAsB,+BAA+B,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CA2BjF"}
@@ -0,0 +1,27 @@
1
+ import { FPCContract } from '@aztec/noir-contracts.js/FPC';
2
+ import { TokenContractArtifact } from '@aztec/noir-contracts.js/Token';
3
+ import { ProtocolContractAddress } from '@aztec/protocol-contracts';
4
+ import { getContractClassFromArtifact } from '@aztec/stdlib/contract';
5
+ let defaultAllowedSetupFunctions = undefined;
6
+ export async function getDefaultAllowedSetupFunctions() {
7
+ if (defaultAllowedSetupFunctions === undefined) {
8
+ defaultAllowedSetupFunctions = [
9
+ // needed for authwit support
10
+ {
11
+ address: ProtocolContractAddress.AuthRegistry
12
+ },
13
+ // needed for claiming on the same tx as a spend
14
+ {
15
+ address: ProtocolContractAddress.FeeJuice
16
+ },
17
+ // needed for private transfers via FPC
18
+ {
19
+ classId: (await getContractClassFromArtifact(TokenContractArtifact)).id
20
+ },
21
+ {
22
+ classId: (await getContractClassFromArtifact(FPCContract.artifact)).id
23
+ }
24
+ ];
25
+ }
26
+ return defaultAllowedSetupFunctions;
27
+ }
@@ -0,0 +1,2 @@
1
+ export { type SequencerConfig } from '@aztec/stdlib/config';
2
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/sequencer/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,4 @@
1
+ export * from './config.js';
2
+ export * from './sequencer.js';
3
+ export * from './allowed.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sequencer/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from './config.js';
2
+ export * from './sequencer.js';
3
+ export * from './allowed.js';
@@ -0,0 +1,24 @@
1
+ import { type TelemetryClient, type Tracer } from '@aztec/telemetry-client';
2
+ import { type SequencerState, type SequencerStateCallback } from './utils.js';
3
+ export declare class SequencerMetrics {
4
+ readonly tracer: Tracer;
5
+ private blockCounter;
6
+ private blockBuildDuration;
7
+ private blockBuildManaPerSecond;
8
+ private stateTransitionBufferDuration;
9
+ private currentBlockNumber;
10
+ private currentBlockSize;
11
+ private blockBuilderInsertions;
12
+ private timeToCollectAttestations;
13
+ constructor(client: TelemetryClient, getState: SequencerStateCallback, name?: string);
14
+ startCollectingAttestationsTimer(): () => void;
15
+ recordTimeToCollectAttestations(time: number): void;
16
+ recordBlockBuilderTreeInsertions(timeUs: number): void;
17
+ recordCancelledBlock(): void;
18
+ recordBuiltBlock(buildDurationMs: number, totalMana: number): void;
19
+ recordFailedBlock(): void;
20
+ recordNewBlock(blockNumber: number, txCount: number): void;
21
+ recordStateTransitionBufferMs(durationMs: number, state: SequencerState): void;
22
+ private setCurrentBlock;
23
+ }
24
+ //# sourceMappingURL=metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/sequencer/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,eAAe,EACpB,KAAK,MAAM,EAGZ,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,sBAAsB,EAA0B,MAAM,YAAY,CAAC;AAEtG,qBAAa,gBAAgB;IAC3B,SAAgB,MAAM,EAAE,MAAM,CAAC;IAE/B,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,kBAAkB,CAAY;IACtC,OAAO,CAAC,uBAAuB,CAAQ;IACvC,OAAO,CAAC,6BAA6B,CAAY;IACjD,OAAO,CAAC,kBAAkB,CAAQ;IAClC,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,sBAAsB,CAAY;IAE1C,OAAO,CAAC,yBAAyB,CAAQ;gBAE7B,MAAM,EAAE,eAAe,EAAE,QAAQ,EAAE,sBAAsB,EAAE,IAAI,SAAc;IAyDzF,gCAAgC,IAAI,MAAM,IAAI;IAS9C,+BAA+B,CAAC,IAAI,EAAE,MAAM;IAI5C,gCAAgC,CAAC,MAAM,EAAE,MAAM;IAI/C,oBAAoB;IAOpB,gBAAgB,CAAC,eAAe,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAQ3D,iBAAiB;IAOjB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IAInD,6BAA6B,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc;IAMvE,OAAO,CAAC,eAAe;CAIxB"}
@@ -0,0 +1,102 @@
1
+ import { Attributes, Metrics, ValueType } from '@aztec/telemetry-client';
2
+ import { sequencerStateToNumber } from './utils.js';
3
+ export class SequencerMetrics {
4
+ tracer;
5
+ blockCounter;
6
+ blockBuildDuration;
7
+ blockBuildManaPerSecond;
8
+ stateTransitionBufferDuration;
9
+ currentBlockNumber;
10
+ currentBlockSize;
11
+ blockBuilderInsertions;
12
+ timeToCollectAttestations;
13
+ constructor(client, getState, name = 'Sequencer'){
14
+ const meter = client.getMeter(name);
15
+ this.tracer = client.getTracer(name);
16
+ this.blockCounter = meter.createUpDownCounter(Metrics.SEQUENCER_BLOCK_COUNT);
17
+ this.blockBuildDuration = meter.createHistogram(Metrics.SEQUENCER_BLOCK_BUILD_DURATION, {
18
+ unit: 'ms',
19
+ description: 'Duration to build a block',
20
+ valueType: ValueType.INT
21
+ });
22
+ this.blockBuildManaPerSecond = meter.createGauge(Metrics.SEQUENCER_BLOCK_BUILD_MANA_PER_SECOND, {
23
+ unit: 'mana/s',
24
+ description: 'Mana per second when building a block',
25
+ valueType: ValueType.INT
26
+ });
27
+ this.stateTransitionBufferDuration = meter.createHistogram(Metrics.SEQUENCER_STATE_TRANSITION_BUFFER_DURATION, {
28
+ unit: 'ms',
29
+ description: 'The time difference between when the sequencer needed to transition to a new state and when it actually did.',
30
+ valueType: ValueType.INT
31
+ });
32
+ const currentState = meter.createObservableGauge(Metrics.SEQUENCER_CURRENT_STATE, {
33
+ description: 'Current state of the sequencer'
34
+ });
35
+ currentState.addCallback((observer)=>{
36
+ observer.observe(sequencerStateToNumber(getState()));
37
+ });
38
+ this.currentBlockNumber = meter.createGauge(Metrics.SEQUENCER_CURRENT_BLOCK_NUMBER, {
39
+ description: 'Current block number',
40
+ valueType: ValueType.INT
41
+ });
42
+ this.currentBlockSize = meter.createGauge(Metrics.SEQUENCER_CURRENT_BLOCK_SIZE, {
43
+ description: 'Current block size',
44
+ valueType: ValueType.INT
45
+ });
46
+ this.timeToCollectAttestations = meter.createGauge(Metrics.SEQUENCER_TIME_TO_COLLECT_ATTESTATIONS, {
47
+ description: 'The time spent collecting attestations from committee members',
48
+ valueType: ValueType.INT
49
+ });
50
+ this.blockBuilderInsertions = meter.createHistogram(Metrics.SEQUENCER_BLOCK_BUILD_INSERTION_TIME, {
51
+ description: 'Timer for tree insertions performed by the block builder',
52
+ unit: 'us',
53
+ valueType: ValueType.INT
54
+ });
55
+ this.setCurrentBlock(0, 0);
56
+ }
57
+ startCollectingAttestationsTimer() {
58
+ const startTime = Date.now();
59
+ const stop = ()=>{
60
+ const duration = Date.now() - startTime;
61
+ this.recordTimeToCollectAttestations(duration);
62
+ };
63
+ return stop.bind(this);
64
+ }
65
+ recordTimeToCollectAttestations(time) {
66
+ this.timeToCollectAttestations.record(time);
67
+ }
68
+ recordBlockBuilderTreeInsertions(timeUs) {
69
+ this.blockBuilderInsertions.record(Math.ceil(timeUs));
70
+ }
71
+ recordCancelledBlock() {
72
+ this.blockCounter.add(1, {
73
+ [Attributes.STATUS]: 'cancelled'
74
+ });
75
+ this.setCurrentBlock(0, 0);
76
+ }
77
+ recordBuiltBlock(buildDurationMs, totalMana) {
78
+ this.blockCounter.add(1, {
79
+ [Attributes.STATUS]: 'built'
80
+ });
81
+ this.blockBuildDuration.record(Math.ceil(buildDurationMs));
82
+ this.blockBuildManaPerSecond.record(Math.ceil(totalMana * 1000 / buildDurationMs));
83
+ }
84
+ recordFailedBlock() {
85
+ this.blockCounter.add(1, {
86
+ [Attributes.STATUS]: 'failed'
87
+ });
88
+ this.setCurrentBlock(0, 0);
89
+ }
90
+ recordNewBlock(blockNumber, txCount) {
91
+ this.setCurrentBlock(blockNumber, txCount);
92
+ }
93
+ recordStateTransitionBufferMs(durationMs, state) {
94
+ this.stateTransitionBufferDuration.record(durationMs, {
95
+ [Attributes.SEQUENCER_STATE]: state
96
+ });
97
+ }
98
+ setCurrentBlock(blockNumber, txCount) {
99
+ this.currentBlockNumber.record(blockNumber);
100
+ this.currentBlockSize.record(txCount);
101
+ }
102
+ }