@aztec/sequencer-client 4.0.0-nightly.20250907 → 4.0.0-nightly.20260108

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 (108) hide show
  1. package/dest/client/index.d.ts +1 -1
  2. package/dest/client/sequencer-client.d.ts +10 -8
  3. package/dest/client/sequencer-client.d.ts.map +1 -1
  4. package/dest/client/sequencer-client.js +40 -28
  5. package/dest/config.d.ts +13 -5
  6. package/dest/config.d.ts.map +1 -1
  7. package/dest/config.js +82 -25
  8. package/dest/global_variable_builder/global_builder.d.ts +22 -16
  9. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  10. package/dest/global_variable_builder/global_builder.js +52 -39
  11. package/dest/global_variable_builder/index.d.ts +1 -1
  12. package/dest/index.d.ts +2 -2
  13. package/dest/index.d.ts.map +1 -1
  14. package/dest/index.js +1 -1
  15. package/dest/publisher/config.d.ts +11 -8
  16. package/dest/publisher/config.d.ts.map +1 -1
  17. package/dest/publisher/config.js +21 -13
  18. package/dest/publisher/index.d.ts +2 -2
  19. package/dest/publisher/index.d.ts.map +1 -1
  20. package/dest/publisher/index.js +1 -1
  21. package/dest/publisher/sequencer-publisher-factory.d.ts +11 -5
  22. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  23. package/dest/publisher/sequencer-publisher-factory.js +9 -2
  24. package/dest/publisher/sequencer-publisher-metrics.d.ts +4 -4
  25. package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
  26. package/dest/publisher/sequencer-publisher-metrics.js +1 -1
  27. package/dest/publisher/sequencer-publisher.d.ts +78 -70
  28. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  29. package/dest/publisher/sequencer-publisher.js +687 -182
  30. package/dest/sequencer/block_builder.d.ts +6 -10
  31. package/dest/sequencer/block_builder.d.ts.map +1 -1
  32. package/dest/sequencer/block_builder.js +21 -10
  33. package/dest/sequencer/checkpoint_builder.d.ts +63 -0
  34. package/dest/sequencer/checkpoint_builder.d.ts.map +1 -0
  35. package/dest/sequencer/checkpoint_builder.js +131 -0
  36. package/dest/sequencer/checkpoint_proposal_job.d.ts +76 -0
  37. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -0
  38. package/dest/sequencer/checkpoint_proposal_job.js +1070 -0
  39. package/dest/sequencer/checkpoint_voter.d.ts +34 -0
  40. package/dest/sequencer/checkpoint_voter.d.ts.map +1 -0
  41. package/dest/sequencer/checkpoint_voter.js +85 -0
  42. package/dest/sequencer/config.d.ts +3 -2
  43. package/dest/sequencer/config.d.ts.map +1 -1
  44. package/dest/sequencer/errors.d.ts +11 -0
  45. package/dest/sequencer/errors.d.ts.map +1 -0
  46. package/dest/sequencer/errors.js +15 -0
  47. package/dest/sequencer/events.d.ts +46 -0
  48. package/dest/sequencer/events.d.ts.map +1 -0
  49. package/dest/sequencer/events.js +1 -0
  50. package/dest/sequencer/index.d.ts +5 -1
  51. package/dest/sequencer/index.d.ts.map +1 -1
  52. package/dest/sequencer/index.js +4 -0
  53. package/dest/sequencer/metrics.d.ts +37 -20
  54. package/dest/sequencer/metrics.d.ts.map +1 -1
  55. package/dest/sequencer/metrics.js +211 -85
  56. package/dest/sequencer/sequencer.d.ts +110 -121
  57. package/dest/sequencer/sequencer.d.ts.map +1 -1
  58. package/dest/sequencer/sequencer.js +809 -524
  59. package/dest/sequencer/timetable.d.ts +57 -21
  60. package/dest/sequencer/timetable.d.ts.map +1 -1
  61. package/dest/sequencer/timetable.js +150 -68
  62. package/dest/sequencer/types.d.ts +3 -0
  63. package/dest/sequencer/types.d.ts.map +1 -0
  64. package/dest/sequencer/types.js +1 -0
  65. package/dest/sequencer/utils.d.ts +20 -28
  66. package/dest/sequencer/utils.d.ts.map +1 -1
  67. package/dest/sequencer/utils.js +12 -24
  68. package/dest/test/index.d.ts +4 -2
  69. package/dest/test/index.d.ts.map +1 -1
  70. package/dest/test/mock_checkpoint_builder.d.ts +83 -0
  71. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -0
  72. package/dest/test/mock_checkpoint_builder.js +179 -0
  73. package/dest/test/utils.d.ts +49 -0
  74. package/dest/test/utils.d.ts.map +1 -0
  75. package/dest/test/utils.js +94 -0
  76. package/dest/tx_validator/nullifier_cache.d.ts +1 -1
  77. package/dest/tx_validator/nullifier_cache.d.ts.map +1 -1
  78. package/dest/tx_validator/tx_validator_factory.d.ts +4 -3
  79. package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
  80. package/dest/tx_validator/tx_validator_factory.js +12 -9
  81. package/package.json +32 -31
  82. package/src/client/sequencer-client.ts +34 -40
  83. package/src/config.ts +89 -29
  84. package/src/global_variable_builder/global_builder.ts +67 -59
  85. package/src/index.ts +2 -0
  86. package/src/publisher/config.ts +32 -19
  87. package/src/publisher/index.ts +1 -1
  88. package/src/publisher/sequencer-publisher-factory.ts +19 -6
  89. package/src/publisher/sequencer-publisher-metrics.ts +3 -3
  90. package/src/publisher/sequencer-publisher.ts +418 -242
  91. package/src/sequencer/README.md +531 -0
  92. package/src/sequencer/block_builder.ts +28 -30
  93. package/src/sequencer/checkpoint_builder.ts +217 -0
  94. package/src/sequencer/checkpoint_proposal_job.ts +722 -0
  95. package/src/sequencer/checkpoint_voter.ts +105 -0
  96. package/src/sequencer/config.ts +2 -1
  97. package/src/sequencer/errors.ts +21 -0
  98. package/src/sequencer/events.ts +27 -0
  99. package/src/sequencer/index.ts +4 -0
  100. package/src/sequencer/metrics.ts +269 -94
  101. package/src/sequencer/sequencer.ts +508 -675
  102. package/src/sequencer/timetable.ts +181 -91
  103. package/src/sequencer/types.ts +6 -0
  104. package/src/sequencer/utils.ts +24 -29
  105. package/src/test/index.ts +3 -1
  106. package/src/test/mock_checkpoint_builder.ts +247 -0
  107. package/src/test/utils.ts +137 -0
  108. package/src/tx_validator/tx_validator_factory.ts +13 -7
@@ -1,25 +1,396 @@
1
- import { Blob } from '@aztec/blob-lib';
2
- import { createBlobSinkClient } from '@aztec/blob-sink/client';
3
- import { FormattedViemError, MULTI_CALL_3_ADDRESS, Multicall3, RollupContract, formatViemError, tryExtractEvent } from '@aztec/ethereum';
1
+ function applyDecs2203RFactory() {
2
+ function createAddInitializerMethod(initializers, decoratorFinishedRef) {
3
+ return function addInitializer(initializer) {
4
+ assertNotFinished(decoratorFinishedRef, "addInitializer");
5
+ assertCallable(initializer, "An initializer");
6
+ initializers.push(initializer);
7
+ };
8
+ }
9
+ function memberDec(dec, name, desc, initializers, kind, isStatic, isPrivate, metadata, value) {
10
+ var kindStr;
11
+ switch(kind){
12
+ case 1:
13
+ kindStr = "accessor";
14
+ break;
15
+ case 2:
16
+ kindStr = "method";
17
+ break;
18
+ case 3:
19
+ kindStr = "getter";
20
+ break;
21
+ case 4:
22
+ kindStr = "setter";
23
+ break;
24
+ default:
25
+ kindStr = "field";
26
+ }
27
+ var ctx = {
28
+ kind: kindStr,
29
+ name: isPrivate ? "#" + name : name,
30
+ static: isStatic,
31
+ private: isPrivate,
32
+ metadata: metadata
33
+ };
34
+ var decoratorFinishedRef = {
35
+ v: false
36
+ };
37
+ ctx.addInitializer = createAddInitializerMethod(initializers, decoratorFinishedRef);
38
+ var get, set;
39
+ if (kind === 0) {
40
+ if (isPrivate) {
41
+ get = desc.get;
42
+ set = desc.set;
43
+ } else {
44
+ get = function() {
45
+ return this[name];
46
+ };
47
+ set = function(v) {
48
+ this[name] = v;
49
+ };
50
+ }
51
+ } else if (kind === 2) {
52
+ get = function() {
53
+ return desc.value;
54
+ };
55
+ } else {
56
+ if (kind === 1 || kind === 3) {
57
+ get = function() {
58
+ return desc.get.call(this);
59
+ };
60
+ }
61
+ if (kind === 1 || kind === 4) {
62
+ set = function(v) {
63
+ desc.set.call(this, v);
64
+ };
65
+ }
66
+ }
67
+ ctx.access = get && set ? {
68
+ get: get,
69
+ set: set
70
+ } : get ? {
71
+ get: get
72
+ } : {
73
+ set: set
74
+ };
75
+ try {
76
+ return dec(value, ctx);
77
+ } finally{
78
+ decoratorFinishedRef.v = true;
79
+ }
80
+ }
81
+ function assertNotFinished(decoratorFinishedRef, fnName) {
82
+ if (decoratorFinishedRef.v) {
83
+ throw new Error("attempted to call " + fnName + " after decoration was finished");
84
+ }
85
+ }
86
+ function assertCallable(fn, hint) {
87
+ if (typeof fn !== "function") {
88
+ throw new TypeError(hint + " must be a function");
89
+ }
90
+ }
91
+ function assertValidReturnValue(kind, value) {
92
+ var type = typeof value;
93
+ if (kind === 1) {
94
+ if (type !== "object" || value === null) {
95
+ throw new TypeError("accessor decorators must return an object with get, set, or init properties or void 0");
96
+ }
97
+ if (value.get !== undefined) {
98
+ assertCallable(value.get, "accessor.get");
99
+ }
100
+ if (value.set !== undefined) {
101
+ assertCallable(value.set, "accessor.set");
102
+ }
103
+ if (value.init !== undefined) {
104
+ assertCallable(value.init, "accessor.init");
105
+ }
106
+ } else if (type !== "function") {
107
+ var hint;
108
+ if (kind === 0) {
109
+ hint = "field";
110
+ } else if (kind === 10) {
111
+ hint = "class";
112
+ } else {
113
+ hint = "method";
114
+ }
115
+ throw new TypeError(hint + " decorators must return a function or void 0");
116
+ }
117
+ }
118
+ function applyMemberDec(ret, base, decInfo, name, kind, isStatic, isPrivate, initializers, metadata) {
119
+ var decs = decInfo[0];
120
+ var desc, init, value;
121
+ if (isPrivate) {
122
+ if (kind === 0 || kind === 1) {
123
+ desc = {
124
+ get: decInfo[3],
125
+ set: decInfo[4]
126
+ };
127
+ } else if (kind === 3) {
128
+ desc = {
129
+ get: decInfo[3]
130
+ };
131
+ } else if (kind === 4) {
132
+ desc = {
133
+ set: decInfo[3]
134
+ };
135
+ } else {
136
+ desc = {
137
+ value: decInfo[3]
138
+ };
139
+ }
140
+ } else if (kind !== 0) {
141
+ desc = Object.getOwnPropertyDescriptor(base, name);
142
+ }
143
+ if (kind === 1) {
144
+ value = {
145
+ get: desc.get,
146
+ set: desc.set
147
+ };
148
+ } else if (kind === 2) {
149
+ value = desc.value;
150
+ } else if (kind === 3) {
151
+ value = desc.get;
152
+ } else if (kind === 4) {
153
+ value = desc.set;
154
+ }
155
+ var newValue, get, set;
156
+ if (typeof decs === "function") {
157
+ newValue = memberDec(decs, name, desc, initializers, kind, isStatic, isPrivate, metadata, value);
158
+ if (newValue !== void 0) {
159
+ assertValidReturnValue(kind, newValue);
160
+ if (kind === 0) {
161
+ init = newValue;
162
+ } else if (kind === 1) {
163
+ init = newValue.init;
164
+ get = newValue.get || value.get;
165
+ set = newValue.set || value.set;
166
+ value = {
167
+ get: get,
168
+ set: set
169
+ };
170
+ } else {
171
+ value = newValue;
172
+ }
173
+ }
174
+ } else {
175
+ for(var i = decs.length - 1; i >= 0; i--){
176
+ var dec = decs[i];
177
+ newValue = memberDec(dec, name, desc, initializers, kind, isStatic, isPrivate, metadata, value);
178
+ if (newValue !== void 0) {
179
+ assertValidReturnValue(kind, newValue);
180
+ var newInit;
181
+ if (kind === 0) {
182
+ newInit = newValue;
183
+ } else if (kind === 1) {
184
+ newInit = newValue.init;
185
+ get = newValue.get || value.get;
186
+ set = newValue.set || value.set;
187
+ value = {
188
+ get: get,
189
+ set: set
190
+ };
191
+ } else {
192
+ value = newValue;
193
+ }
194
+ if (newInit !== void 0) {
195
+ if (init === void 0) {
196
+ init = newInit;
197
+ } else if (typeof init === "function") {
198
+ init = [
199
+ init,
200
+ newInit
201
+ ];
202
+ } else {
203
+ init.push(newInit);
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
209
+ if (kind === 0 || kind === 1) {
210
+ if (init === void 0) {
211
+ init = function(instance, init) {
212
+ return init;
213
+ };
214
+ } else if (typeof init !== "function") {
215
+ var ownInitializers = init;
216
+ init = function(instance, init) {
217
+ var value = init;
218
+ for(var i = 0; i < ownInitializers.length; i++){
219
+ value = ownInitializers[i].call(instance, value);
220
+ }
221
+ return value;
222
+ };
223
+ } else {
224
+ var originalInitializer = init;
225
+ init = function(instance, init) {
226
+ return originalInitializer.call(instance, init);
227
+ };
228
+ }
229
+ ret.push(init);
230
+ }
231
+ if (kind !== 0) {
232
+ if (kind === 1) {
233
+ desc.get = value.get;
234
+ desc.set = value.set;
235
+ } else if (kind === 2) {
236
+ desc.value = value;
237
+ } else if (kind === 3) {
238
+ desc.get = value;
239
+ } else if (kind === 4) {
240
+ desc.set = value;
241
+ }
242
+ if (isPrivate) {
243
+ if (kind === 1) {
244
+ ret.push(function(instance, args) {
245
+ return value.get.call(instance, args);
246
+ });
247
+ ret.push(function(instance, args) {
248
+ return value.set.call(instance, args);
249
+ });
250
+ } else if (kind === 2) {
251
+ ret.push(value);
252
+ } else {
253
+ ret.push(function(instance, args) {
254
+ return value.call(instance, args);
255
+ });
256
+ }
257
+ } else {
258
+ Object.defineProperty(base, name, desc);
259
+ }
260
+ }
261
+ }
262
+ function applyMemberDecs(Class, decInfos, metadata) {
263
+ var ret = [];
264
+ var protoInitializers;
265
+ var staticInitializers;
266
+ var existingProtoNonFields = new Map();
267
+ var existingStaticNonFields = new Map();
268
+ for(var i = 0; i < decInfos.length; i++){
269
+ var decInfo = decInfos[i];
270
+ if (!Array.isArray(decInfo)) continue;
271
+ var kind = decInfo[1];
272
+ var name = decInfo[2];
273
+ var isPrivate = decInfo.length > 3;
274
+ var isStatic = kind >= 5;
275
+ var base;
276
+ var initializers;
277
+ if (isStatic) {
278
+ base = Class;
279
+ kind = kind - 5;
280
+ staticInitializers = staticInitializers || [];
281
+ initializers = staticInitializers;
282
+ } else {
283
+ base = Class.prototype;
284
+ protoInitializers = protoInitializers || [];
285
+ initializers = protoInitializers;
286
+ }
287
+ if (kind !== 0 && !isPrivate) {
288
+ var existingNonFields = isStatic ? existingStaticNonFields : existingProtoNonFields;
289
+ var existingKind = existingNonFields.get(name) || 0;
290
+ if (existingKind === true || existingKind === 3 && kind !== 4 || existingKind === 4 && kind !== 3) {
291
+ throw new Error("Attempted to decorate a public method/accessor that has the same name as a previously decorated public method/accessor. This is not currently supported by the decorators plugin. Property name was: " + name);
292
+ } else if (!existingKind && kind > 2) {
293
+ existingNonFields.set(name, kind);
294
+ } else {
295
+ existingNonFields.set(name, true);
296
+ }
297
+ }
298
+ applyMemberDec(ret, base, decInfo, name, kind, isStatic, isPrivate, initializers, metadata);
299
+ }
300
+ pushInitializers(ret, protoInitializers);
301
+ pushInitializers(ret, staticInitializers);
302
+ return ret;
303
+ }
304
+ function pushInitializers(ret, initializers) {
305
+ if (initializers) {
306
+ ret.push(function(instance) {
307
+ for(var i = 0; i < initializers.length; i++){
308
+ initializers[i].call(instance);
309
+ }
310
+ return instance;
311
+ });
312
+ }
313
+ }
314
+ function applyClassDecs(targetClass, classDecs, metadata) {
315
+ if (classDecs.length > 0) {
316
+ var initializers = [];
317
+ var newClass = targetClass;
318
+ var name = targetClass.name;
319
+ for(var i = classDecs.length - 1; i >= 0; i--){
320
+ var decoratorFinishedRef = {
321
+ v: false
322
+ };
323
+ try {
324
+ var nextNewClass = classDecs[i](newClass, {
325
+ kind: "class",
326
+ name: name,
327
+ addInitializer: createAddInitializerMethod(initializers, decoratorFinishedRef),
328
+ metadata
329
+ });
330
+ } finally{
331
+ decoratorFinishedRef.v = true;
332
+ }
333
+ if (nextNewClass !== undefined) {
334
+ assertValidReturnValue(10, nextNewClass);
335
+ newClass = nextNewClass;
336
+ }
337
+ }
338
+ return [
339
+ defineMetadata(newClass, metadata),
340
+ function() {
341
+ for(var i = 0; i < initializers.length; i++){
342
+ initializers[i].call(newClass);
343
+ }
344
+ }
345
+ ];
346
+ }
347
+ }
348
+ function defineMetadata(Class, metadata) {
349
+ return Object.defineProperty(Class, Symbol.metadata || Symbol.for("Symbol.metadata"), {
350
+ configurable: true,
351
+ enumerable: true,
352
+ value: metadata
353
+ });
354
+ }
355
+ return function applyDecs2203R(targetClass, memberDecs, classDecs, parentClass) {
356
+ if (parentClass !== void 0) {
357
+ var parentMetadata = parentClass[Symbol.metadata || Symbol.for("Symbol.metadata")];
358
+ }
359
+ var metadata = Object.create(parentMetadata === void 0 ? null : parentMetadata);
360
+ var e = applyMemberDecs(targetClass, memberDecs, metadata);
361
+ if (!classDecs.length) defineMetadata(targetClass, metadata);
362
+ return {
363
+ e: e,
364
+ get c () {
365
+ return applyClassDecs(targetClass, classDecs, metadata);
366
+ }
367
+ };
368
+ };
369
+ }
370
+ function _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass) {
371
+ return (_apply_decs_2203_r = applyDecs2203RFactory())(targetClass, memberDecs, classDecs, parentClass);
372
+ }
373
+ var _dec, _dec1, _dec2, _initProto;
374
+ import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/blob-lib';
375
+ import { MULTI_CALL_3_ADDRESS, Multicall3, RollupContract } from '@aztec/ethereum/contracts';
376
+ import { L1FeeAnalyzer } from '@aztec/ethereum/l1-fee-analysis';
377
+ import { WEI_CONST } from '@aztec/ethereum/l1-tx-utils';
378
+ import { FormattedViemError, formatViemError, tryExtractEvent } from '@aztec/ethereum/utils';
4
379
  import { sumBigint } from '@aztec/foundation/bigint';
5
380
  import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
381
+ import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
382
+ import { pick } from '@aztec/foundation/collection';
6
383
  import { EthAddress } from '@aztec/foundation/eth-address';
384
+ import { Signature } from '@aztec/foundation/eth-signature';
7
385
  import { createLogger } from '@aztec/foundation/log';
8
386
  import { bufferToHex } from '@aztec/foundation/string';
9
387
  import { Timer } from '@aztec/foundation/timer';
10
388
  import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
11
389
  import { encodeSlashConsensusVotes } from '@aztec/slasher';
12
- import { CommitteeAttestation } from '@aztec/stdlib/block';
13
- import { ConsensusPayload, SignatureDomainSeparator, getHashedSignaturePayload } from '@aztec/stdlib/p2p';
14
- import { getTelemetryClient } from '@aztec/telemetry-client';
15
- import pick from 'lodash.pick';
390
+ import { CommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
391
+ import { getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
16
392
  import { encodeFunctionData, toHex } from 'viem';
17
393
  import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
18
- export var SignalType = /*#__PURE__*/ function(SignalType) {
19
- SignalType[SignalType["GOVERNANCE"] = 0] = "GOVERNANCE";
20
- SignalType[SignalType["SLASHING"] = 1] = "SLASHING";
21
- return SignalType;
22
- }({});
23
394
  export const Actions = [
24
395
  'invalidate-by-invalid-attestation',
25
396
  'invalidate-by-insufficient-attestations',
@@ -33,17 +404,40 @@ export const Actions = [
33
404
  ];
34
405
  // Sorting for actions such that invalidations go before proposals, and proposals go before votes
35
406
  export const compareActions = (a, b)=>Actions.indexOf(a) - Actions.indexOf(b);
407
+ _dec = trackSpan('SequencerPublisher.sendRequests'), _dec1 = trackSpan('SequencerPublisher.validateBlockHeader'), _dec2 = trackSpan('SequencerPublisher.validateCheckpointForSubmission');
36
408
  export class SequencerPublisher {
37
409
  config;
410
+ static{
411
+ ({ e: [_initProto] } = _apply_decs_2203_r(this, [
412
+ [
413
+ _dec,
414
+ 2,
415
+ "sendRequests"
416
+ ],
417
+ [
418
+ _dec1,
419
+ 2,
420
+ "validateBlockHeader"
421
+ ],
422
+ [
423
+ _dec2,
424
+ 2,
425
+ "validateCheckpointForSubmission"
426
+ ]
427
+ ], []));
428
+ }
38
429
  interrupted;
39
430
  metrics;
40
431
  epochCache;
41
432
  governanceLog;
42
433
  slashingLog;
43
- myLastSignals;
434
+ lastActions;
435
+ isPayloadEmptyCache;
44
436
  log;
45
437
  ethereumSlotDuration;
46
- blobSinkClient;
438
+ blobClient;
439
+ /** Address to use for simulations in fisherman mode (actual proposer's address) */ proposerAddressForSimulation;
440
+ /** L1 fee analyzer for fisherman mode */ l1FeeAnalyzer;
47
441
  // @note - with blobs, the below estimate seems too large.
48
442
  // Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
49
443
  // Total used for emptier block from above test: 429k (of which 84k is 1x blob)
@@ -57,25 +451,24 @@ export class SequencerPublisher {
57
451
  govProposerContract;
58
452
  slashingProposerContract;
59
453
  slashFactoryContract;
454
+ tracer;
60
455
  requests;
61
456
  constructor(config, deps){
62
457
  this.config = config;
63
- this.interrupted = false;
458
+ this.interrupted = (_initProto(this), false);
64
459
  this.governanceLog = createLogger('sequencer:publisher:governance');
65
460
  this.slashingLog = createLogger('sequencer:publisher:slashing');
66
- this.myLastSignals = {
67
- [0]: 0n,
68
- [1]: 0n
69
- };
70
- this.log = createLogger('sequencer:publisher');
461
+ this.lastActions = {};
462
+ this.isPayloadEmptyCache = new Map();
71
463
  this.requests = [];
464
+ this.log = deps.log ?? createLogger('sequencer:publisher');
72
465
  this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
73
466
  this.epochCache = deps.epochCache;
74
- this.blobSinkClient = deps.blobSinkClient ?? createBlobSinkClient(config, {
75
- logger: createLogger('sequencer:blob-sink:client')
76
- });
467
+ this.lastActions = deps.lastActions;
468
+ this.blobClient = deps.blobClient;
77
469
  const telemetry = deps.telemetry ?? getTelemetryClient();
78
470
  this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
471
+ this.tracer = telemetry.getTracer('SequencerPublisher');
79
472
  this.l1TxUtils = deps.l1TxUtils;
80
473
  this.rollupContract = deps.rollupContract;
81
474
  this.govProposerContract = deps.governanceProposerContract;
@@ -86,6 +479,10 @@ export class SequencerPublisher {
86
479
  this.slashingProposerContract = newSlashingProposer;
87
480
  });
88
481
  this.slashFactoryContract = deps.slashFactoryContract;
482
+ // Initialize L1 fee analyzer for fisherman mode
483
+ if (config.fishermanMode) {
484
+ this.l1FeeAnalyzer = new L1FeeAnalyzer(this.l1TxUtils.client, deps.dateProvider, createLogger('sequencer:publisher:fee-analyzer'));
485
+ }
89
486
  }
90
487
  getRollupContract() {
91
488
  return this.rollupContract;
@@ -93,6 +490,17 @@ export class SequencerPublisher {
93
490
  getSenderAddress() {
94
491
  return this.l1TxUtils.getSenderAddress();
95
492
  }
493
+ /**
494
+ * Gets the L1 fee analyzer instance (only available in fisherman mode)
495
+ */ getL1FeeAnalyzer() {
496
+ return this.l1FeeAnalyzer;
497
+ }
498
+ /**
499
+ * Sets the proposer address to use for simulations in fisherman mode.
500
+ * @param proposerAddress - The actual proposer's address to use for balance lookups in simulations
501
+ */ setProposerAddressForSimulation(proposerAddress) {
502
+ this.proposerAddressForSimulation = proposerAddress;
503
+ }
96
504
  addRequest(request) {
97
505
  this.requests.push(request);
98
506
  }
@@ -100,6 +508,55 @@ export class SequencerPublisher {
100
508
  return this.epochCache.getEpochAndSlotNow().slot;
101
509
  }
102
510
  /**
511
+ * Clears all pending requests without sending them.
512
+ */ clearPendingRequests() {
513
+ const count = this.requests.length;
514
+ this.requests = [];
515
+ if (count > 0) {
516
+ this.log.debug(`Cleared ${count} pending request(s)`);
517
+ }
518
+ }
519
+ /**
520
+ * Analyzes L1 fees for the pending requests without sending them.
521
+ * This is used in fisherman mode to validate fee calculations.
522
+ * @param l2SlotNumber - The L2 slot number for this analysis
523
+ * @param onComplete - Optional callback to invoke when analysis completes (after block is mined)
524
+ * @returns The analysis result (incomplete until block mines), or undefined if no requests
525
+ */ async analyzeL1Fees(l2SlotNumber, onComplete) {
526
+ if (!this.l1FeeAnalyzer) {
527
+ this.log.warn('L1 fee analyzer not available (not in fisherman mode)');
528
+ return undefined;
529
+ }
530
+ const requestsToAnalyze = [
531
+ ...this.requests
532
+ ];
533
+ if (requestsToAnalyze.length === 0) {
534
+ this.log.debug('No requests to analyze for L1 fees');
535
+ return undefined;
536
+ }
537
+ // Extract blob config from requests (if any)
538
+ const blobConfigs = requestsToAnalyze.filter((request)=>request.blobConfig).map((request)=>request.blobConfig);
539
+ const blobConfig = blobConfigs[0];
540
+ // Get gas configs
541
+ const gasConfigs = requestsToAnalyze.filter((request)=>request.gasConfig).map((request)=>request.gasConfig);
542
+ const gasLimits = gasConfigs.map((g)=>g?.gasLimit).filter((g)=>g !== undefined);
543
+ const gasLimit = gasLimits.length > 0 ? gasLimits.reduce((sum, g)=>sum + g, 0n) : 0n;
544
+ // Get the transaction requests
545
+ const l1Requests = requestsToAnalyze.map((r)=>r.request);
546
+ // Start the analysis
547
+ const analysisId = await this.l1FeeAnalyzer.startAnalysis(l2SlotNumber, gasLimit > 0n ? gasLimit : SequencerPublisher.PROPOSE_GAS_GUESS, l1Requests, blobConfig, onComplete);
548
+ this.log.info('Started L1 fee analysis', {
549
+ analysisId,
550
+ l2SlotNumber: l2SlotNumber.toString(),
551
+ requestCount: requestsToAnalyze.length,
552
+ hasBlobConfig: !!blobConfig,
553
+ gasLimit: gasLimit.toString(),
554
+ actions: requestsToAnalyze.map((r)=>r.action)
555
+ });
556
+ // Return the analysis result (will be incomplete until block mines)
557
+ return this.l1FeeAnalyzer.getAnalysis(analysisId);
558
+ }
559
+ /**
103
560
  * Sends all requests that are still valid.
104
561
  * @returns one of:
105
562
  * - A receipt and stats if the tx succeeded
@@ -110,7 +567,7 @@ export class SequencerPublisher {
110
567
  ...this.requests
111
568
  ];
112
569
  this.requests = [];
113
- if (this.interrupted) {
570
+ if (this.interrupted || requestsToProcess.length === 0) {
114
571
  return undefined;
115
572
  }
116
573
  const currentL2Slot = this.getCurrentL2Slot();
@@ -148,7 +605,7 @@ export class SequencerPublisher {
148
605
  const gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
149
606
  const txTimeoutAts = gasConfigs.map((g)=>g?.txTimeoutAt).filter((g)=>g !== undefined);
150
607
  const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map((g)=>g.getTime()))) : undefined; // earliest
151
- const gasConfig = {
608
+ const txConfig = {
152
609
  gasLimit,
153
610
  txTimeoutAt
154
611
  };
@@ -157,9 +614,10 @@ export class SequencerPublisher {
157
614
  validRequests.sort((a, b)=>compareActions(a.action, b.action));
158
615
  try {
159
616
  this.log.debug('Forwarding transactions', {
160
- validRequests: validRequests.map((request)=>request.action)
617
+ validRequests: validRequests.map((request)=>request.action),
618
+ txConfig
161
619
  });
162
- const result = await Multicall3.forward(validRequests.map((request)=>request.request), this.l1TxUtils, gasConfig, blobConfig, this.rollupContract.address, this.log);
620
+ const result = await Multicall3.forward(validRequests.map((request)=>request.request), this.l1TxUtils, txConfig, blobConfig, this.rollupContract.address, this.log);
163
621
  const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result);
164
622
  return {
165
623
  result,
@@ -218,7 +676,9 @@ export class SequencerPublisher {
218
676
  'InvalidProposer',
219
677
  'InvalidArchive'
220
678
  ];
221
- return this.rollupContract.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), this.ethereumSlotDuration, opts).catch((err)=>{
679
+ return this.rollupContract.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
680
+ forcePendingCheckpointNumber: opts.forcePendingBlockNumber !== undefined ? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber) : undefined
681
+ }).catch((err)=>{
222
682
  if (err instanceof FormattedViemError && ignoredErrors.find((e)=>err.message.includes(e))) {
223
683
  this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find((e)=>err.message.includes(e))}`, {
224
684
  error: err.message
@@ -241,16 +701,28 @@ export class SequencerPublisher {
241
701
  };
242
702
  const args = [
243
703
  header.toViem(),
244
- RollupContract.packAttestations([]),
704
+ CommitteeAttestationsAndSigners.empty().getPackedAttestations(),
245
705
  [],
706
+ Signature.empty().toViemSignature(),
246
707
  `0x${'0'.repeat(64)}`,
247
708
  header.contentCommitment.blobsHash.toString(),
248
709
  flags
249
710
  ];
250
711
  const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
251
- // use sender balance to simulate
252
- const balance = await this.l1TxUtils.getSenderBalance();
253
- this.log.debug(`Simulating validateHeader with balance: ${balance}`);
712
+ const optsForcePendingCheckpointNumber = opts?.forcePendingBlockNumber !== undefined ? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber) : undefined;
713
+ const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(optsForcePendingCheckpointNumber);
714
+ let balance = 0n;
715
+ if (this.config.fishermanMode) {
716
+ // In fisherman mode, we can't know where the proposer is publishing from
717
+ // so we just add sufficient balance to the multicall3 address
718
+ balance = 10n * WEI_CONST * WEI_CONST; // 10 ETH
719
+ } else {
720
+ balance = await this.l1TxUtils.getSenderBalance();
721
+ }
722
+ stateOverrides.push({
723
+ address: MULTI_CALL_3_ADDRESS,
724
+ balance
725
+ });
254
726
  await this.l1TxUtils.simulate({
255
727
  to: this.rollupContract.address,
256
728
  data: encodeFunctionData({
@@ -261,13 +733,7 @@ export class SequencerPublisher {
261
733
  from: MULTI_CALL_3_ADDRESS
262
734
  }, {
263
735
  time: ts + 1n
264
- }, [
265
- {
266
- address: MULTI_CALL_3_ADDRESS,
267
- balance
268
- },
269
- ...await this.rollupContract.makePendingBlockNumberOverride(opts?.forcePendingBlockNumber)
270
- ]);
736
+ }, stateOverrides);
271
737
  this.log.debug(`Simulated validateHeader`);
272
738
  }
273
739
  /**
@@ -278,13 +744,13 @@ export class SequencerPublisher {
278
744
  return undefined;
279
745
  }
280
746
  const { reason, block } = validationResult;
281
- const blockNumber = block.block.number;
747
+ const blockNumber = block.blockNumber;
282
748
  const logData = {
283
- ...block.block.toBlockInfo(),
749
+ ...block,
284
750
  reason
285
751
  };
286
- const currentBlockNumber = await this.rollupContract.getBlockNumber();
287
- if (currentBlockNumber < validationResult.block.block.number) {
752
+ const currentBlockNumber = await this.rollupContract.getCheckpointNumber();
753
+ if (currentBlockNumber < validationResult.block.blockNumber) {
288
754
  this.log.verbose(`Skipping block ${blockNumber} invalidation since it has already been removed from the pending chain`, {
289
755
  currentBlockNumber,
290
756
  ...logData
@@ -292,7 +758,10 @@ export class SequencerPublisher {
292
758
  return undefined;
293
759
  }
294
760
  const request = this.buildInvalidateBlockRequest(validationResult);
295
- this.log.debug(`Simulating invalidate block ${blockNumber}`, logData);
761
+ this.log.debug(`Simulating invalidate block ${blockNumber}`, {
762
+ ...logData,
763
+ request
764
+ });
296
765
  try {
297
766
  const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, ErrorsAbi);
298
767
  this.log.verbose(`Simulation for invalidate block ${blockNumber} succeeded`, {
@@ -304,7 +773,7 @@ export class SequencerPublisher {
304
773
  request,
305
774
  gasUsed,
306
775
  blockNumber,
307
- forcePendingBlockNumber: blockNumber - 1,
776
+ forcePendingBlockNumber: BlockNumber(blockNumber - 1),
308
777
  reason
309
778
  };
310
779
  } catch (err) {
@@ -317,7 +786,7 @@ export class SequencerPublisher {
317
786
  request,
318
787
  error: viemError.message
319
788
  });
320
- const latestPendingBlockNumber = await this.rollupContract.getBlockNumber();
789
+ const latestPendingBlockNumber = await this.rollupContract.getCheckpointNumber();
321
790
  if (latestPendingBlockNumber < blockNumber) {
322
791
  this.log.verbose(`Block number ${blockNumber} has already been invalidated`, {
323
792
  ...logData
@@ -343,66 +812,58 @@ export class SequencerPublisher {
343
812
  }
344
813
  const { block, committee, reason } = validationResult;
345
814
  const logData = {
346
- ...block.block.toBlockInfo(),
815
+ ...block,
347
816
  reason
348
817
  };
349
- this.log.debug(`Simulating invalidate block ${block.block.number}`, logData);
818
+ this.log.debug(`Simulating invalidate block ${block.blockNumber}`, logData);
819
+ const attestationsAndSigners = new CommitteeAttestationsAndSigners(validationResult.attestations).getPackedAttestations();
350
820
  if (reason === 'invalid-attestation') {
351
- return this.rollupContract.buildInvalidateBadAttestationRequest(block.block.number, block.attestations.map((a)=>a.toViem()), committee, validationResult.invalidIndex);
821
+ return this.rollupContract.buildInvalidateBadAttestationRequest(CheckpointNumber.fromBlockNumber(block.blockNumber), attestationsAndSigners, committee, validationResult.invalidIndex);
352
822
  } else if (reason === 'insufficient-attestations') {
353
- return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(block.block.number, block.attestations.map((a)=>a.toViem()), committee);
823
+ return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(CheckpointNumber.fromBlockNumber(block.blockNumber), attestationsAndSigners, committee);
354
824
  } else {
355
825
  const _ = reason;
356
826
  throw new Error(`Unknown reason for invalidation`);
357
827
  }
358
828
  }
359
- /**
360
- * @notice Will simulate `propose` to make sure that the block is valid for submission
361
- *
362
- * @dev Throws if unable to propose
363
- *
364
- * @param block - The block to propose
365
- * @param attestationData - The block's attestation data
366
- *
367
- */ async validateBlockForSubmission(block, attestationData = {
368
- digest: Buffer.alloc(32),
369
- attestations: []
370
- }, options) {
829
+ /** Simulates `propose` to make sure that the checkpoint is valid for submission */ async validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, options) {
371
830
  const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
831
+ // TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
372
832
  // If we have no attestations, we still need to provide the empty attestations
373
833
  // so that the committee is recalculated correctly
374
- const ignoreSignatures = attestationData.attestations.length === 0;
375
- if (ignoreSignatures) {
376
- const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber.toBigInt());
377
- if (!committee) {
378
- this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber.toBigInt()}`);
379
- throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber.toBigInt()}`);
380
- }
381
- attestationData.attestations = committee.map((committeeMember)=>CommitteeAttestation.fromAddress(committeeMember));
382
- }
383
- const blobs = await Blob.getBlobsPerBlock(block.body.toBlobFields());
384
- const blobInput = Blob.getPrefixedEthBlobCommitments(blobs);
385
- const formattedAttestations = attestationData.attestations.map((attest)=>attest.toViem());
386
- const signers = attestationData.attestations.filter((attest)=>!attest.signature.isEmpty()).map((attest)=>attest.address.toString());
834
+ // const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
835
+ // if (ignoreSignatures) {
836
+ // const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
837
+ // if (!committee) {
838
+ // this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
839
+ // throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
840
+ // }
841
+ // attestationsAndSigners.attestations = committee.map(committeeMember =>
842
+ // CommitteeAttestation.fromAddress(committeeMember),
843
+ // );
844
+ // }
845
+ const blobFields = checkpoint.toBlobFields();
846
+ const blobs = getBlobsPerL1Block(blobFields);
847
+ const blobInput = getPrefixedEthBlobCommitments(blobs);
387
848
  const args = [
388
849
  {
389
- header: block.header.toPropose().toViem(),
390
- archive: toHex(block.archive.root.toBuffer()),
391
- stateReference: block.header.state.toViem(),
392
- txHashes: block.body.txEffects.map((txEffect)=>txEffect.txHash.toString()),
850
+ header: checkpoint.header.toViem(),
851
+ archive: toHex(checkpoint.archive.root.toBuffer()),
393
852
  oracleInput: {
394
853
  feeAssetPriceModifier: 0n
395
854
  }
396
855
  },
397
- RollupContract.packAttestations(formattedAttestations),
398
- signers,
856
+ attestationsAndSigners.getPackedAttestations(),
857
+ attestationsAndSigners.getSigners().map((signer)=>signer.toString()),
858
+ attestationsAndSignersSignature.toViemSignature(),
399
859
  blobInput
400
860
  ];
401
861
  await this.simulateProposeTx(args, ts, options);
402
862
  return ts;
403
863
  }
404
864
  async enqueueCastSignalHelper(slotNumber, timestamp, signalType, payload, base, signerAddress, signer) {
405
- if (this.myLastSignals[signalType] >= slotNumber) {
865
+ if (this.lastActions[signalType] && this.lastActions[signalType] === slotNumber) {
866
+ this.log.debug(`Skipping duplicate vote cast signal ${signalType} for slot ${slotNumber}`);
406
867
  return false;
407
868
  }
408
869
  if (payload.equals(EthAddress.ZERO)) {
@@ -414,12 +875,19 @@ export class SequencerPublisher {
414
875
  }
415
876
  const round = await base.computeRound(slotNumber);
416
877
  const roundInfo = await base.getRoundInfo(this.rollupContract.address, round);
878
+ if (roundInfo.quorumReached) {
879
+ return false;
880
+ }
417
881
  if (roundInfo.lastSignalSlot >= slotNumber) {
418
882
  return false;
419
883
  }
420
- const cachedLastVote = this.myLastSignals[signalType];
421
- this.myLastSignals[signalType] = slotNumber;
422
- const action = signalType === 0 ? 'governance-signal' : 'empire-slashing-signal';
884
+ if (await this.isPayloadEmpty(payload)) {
885
+ this.log.warn(`Skipping vote cast for payload with empty code`);
886
+ return false;
887
+ }
888
+ const cachedLastVote = this.lastActions[signalType];
889
+ this.lastActions[signalType] = slotNumber;
890
+ const action = signalType;
423
891
  const request = await base.createSignalRequestWithSignature(payload.toString(), slotNumber, this.config.l1ChainId, signerAddress.toString(), signer);
424
892
  this.log.debug(`Created ${action} request with signature`, {
425
893
  request,
@@ -455,24 +923,34 @@ export class SequencerPublisher {
455
923
  payload: payload.toString()
456
924
  };
457
925
  if (!success) {
458
- this.log.error(`Signaling in [${action}] for ${payload} at slot ${slotNumber} in round ${round} failed`, logData);
459
- this.myLastSignals[signalType] = cachedLastVote;
926
+ this.log.error(`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} failed`, logData);
927
+ this.lastActions[signalType] = cachedLastVote;
460
928
  return false;
461
929
  } else {
462
- this.log.info(`Signaling in [${action}] for ${payload} at slot ${slotNumber} in round ${round} succeeded`, logData);
930
+ this.log.info(`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} succeeded`, logData);
463
931
  return true;
464
932
  }
465
933
  }
466
934
  });
467
935
  return true;
468
936
  }
937
+ async isPayloadEmpty(payload) {
938
+ const key = payload.toString();
939
+ const cached = this.isPayloadEmptyCache.get(key);
940
+ if (cached) {
941
+ return cached;
942
+ }
943
+ const isEmpty = !await this.l1TxUtils.getCode(payload);
944
+ this.isPayloadEmptyCache.set(key, isEmpty);
945
+ return isEmpty;
946
+ }
469
947
  /**
470
948
  * Enqueues a governance castSignal transaction to cast a signal for a given slot number.
471
949
  * @param slotNumber - The slot number to cast a signal for.
472
950
  * @param timestamp - The timestamp of the slot to cast a signal for.
473
951
  * @returns True if the signal was successfully enqueued, false otherwise.
474
952
  */ enqueueGovernanceCastSignal(governancePayload, slotNumber, timestamp, signerAddress, signer) {
475
- return this.enqueueCastSignalHelper(slotNumber, timestamp, 0, governancePayload, this.govProposerContract, signerAddress, signer);
953
+ return this.enqueueCastSignalHelper(slotNumber, timestamp, 'governance-signal', governancePayload, this.govProposerContract, signerAddress, signer);
476
954
  }
477
955
  /** Enqueues all slashing actions as returned by the slasher client. */ async enqueueSlashingActions(actions, slotNumber, timestamp, signerAddress, signer) {
478
956
  if (actions.length === 0) {
@@ -490,7 +968,7 @@ export class SequencerPublisher {
490
968
  this.log.debug(`Enqueuing slashing vote for payload ${action.payload} at slot ${slotNumber}`, {
491
969
  signerAddress
492
970
  });
493
- await this.enqueueCastSignalHelper(slotNumber, timestamp, 1, action.payload, this.slashingProposerContract, signerAddress, signer);
971
+ await this.enqueueCastSignalHelper(slotNumber, timestamp, 'empire-slashing-signal', action.payload, this.slashingProposerContract, signerAddress, signer);
494
972
  break;
495
973
  }
496
974
  case 'create-empire-payload':
@@ -561,24 +1039,16 @@ export class SequencerPublisher {
561
1039
  }
562
1040
  return true;
563
1041
  }
564
- /**
565
- * Proposes a L2 block on L1.
566
- *
567
- * @param block - L2 block to propose.
568
- * @returns True if the tx has been enqueued, throws otherwise. See #9315
569
- */ async enqueueProposeL2Block(block, attestations, txHashes, opts = {}) {
570
- const proposedBlockHeader = block.header.toPropose();
571
- const consensusPayload = ConsensusPayload.fromBlock(block);
572
- const digest = getHashedSignaturePayload(consensusPayload, SignatureDomainSeparator.blockAttestation);
573
- const blobs = await Blob.getBlobsPerBlock(block.body.toBlobFields());
1042
+ /** Simulates and enqueues a proposal for a checkpoint on L1 */ async enqueueProposeCheckpoint(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts = {}) {
1043
+ const checkpointHeader = checkpoint.header;
1044
+ const blobFields = checkpoint.toBlobFields();
1045
+ const blobs = getBlobsPerL1Block(blobFields);
574
1046
  const proposeTxArgs = {
575
- header: proposedBlockHeader,
576
- archive: block.archive.root.toBuffer(),
577
- stateReference: block.header.state,
578
- body: block.body.toBuffer(),
1047
+ header: checkpointHeader,
1048
+ archive: checkpoint.archive.root.toBuffer(),
579
1049
  blobs,
580
- attestations,
581
- txHashes: txHashes ?? []
1050
+ attestationsAndSigners,
1051
+ attestationsAndSignersSignature
582
1052
  };
583
1053
  let ts;
584
1054
  try {
@@ -586,26 +1056,21 @@ export class SequencerPublisher {
586
1056
  // This means that we can avoid the simulation issues in later checks.
587
1057
  // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
588
1058
  // make time consistency checks break.
589
- const attestationData = {
590
- digest: digest.toBuffer(),
591
- attestations: attestations ?? []
592
- };
593
1059
  // TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
594
- ts = await this.validateBlockForSubmission(block, attestationData, opts);
1060
+ ts = await this.validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts);
595
1061
  } catch (err) {
596
- this.log.error(`Block validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
597
- ...block.getStats(),
598
- slotNumber: block.header.globalVariables.slotNumber.toBigInt(),
1062
+ this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
1063
+ ...checkpoint.getStats(),
1064
+ slotNumber: checkpoint.header.slotNumber,
599
1065
  forcePendingBlockNumber: opts.forcePendingBlockNumber
600
1066
  });
601
1067
  throw err;
602
1068
  }
603
- this.log.verbose(`Enqueuing block propose transaction`, {
604
- ...block.toBlockInfo(),
1069
+ this.log.verbose(`Enqueuing checkpoint propose transaction`, {
1070
+ ...checkpoint.toCheckpointInfo(),
605
1071
  ...opts
606
1072
  });
607
- await this.addProposeTx(block, proposeTxArgs, opts, ts);
608
- return true;
1073
+ await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
609
1074
  }
610
1075
  enqueueInvalidateBlock(request, opts = {}) {
611
1076
  if (!request) {
@@ -613,8 +1078,10 @@ export class SequencerPublisher {
613
1078
  }
614
1079
  // We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
615
1080
  const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(request.gasUsed) * 64 / 63)));
1081
+ const { gasUsed, blockNumber } = request;
616
1082
  const logData = {
617
- ...pick(request, 'gasUsed', 'blockNumber'),
1083
+ gasUsed,
1084
+ blockNumber,
618
1085
  gasLimit,
619
1086
  opts
620
1087
  };
@@ -626,9 +1093,9 @@ export class SequencerPublisher {
626
1093
  gasLimit,
627
1094
  txTimeoutAt: opts.txTimeoutAt
628
1095
  },
629
- lastValidL2Slot: this.getCurrentL2Slot() + 2n,
1096
+ lastValidL2Slot: SlotNumber(this.getCurrentL2Slot() + 2),
630
1097
  checkSuccess: (_req, result)=>{
631
- const success = result && result.receipt && result.receipt.status === 'success' && tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'BlockInvalidated');
1098
+ const success = result && result.receipt && result.receipt.status === 'success' && tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
632
1099
  if (!success) {
633
1100
  this.log.warn(`Invalidate block ${request.blockNumber} failed`, {
634
1101
  ...result,
@@ -650,8 +1117,14 @@ export class SequencerPublisher {
650
1117
  timestamp,
651
1118
  gasLimit: undefined
652
1119
  };
1120
+ if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
1121
+ this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
1122
+ return false;
1123
+ }
1124
+ const cachedLastActionSlot = this.lastActions[action];
1125
+ this.lastActions[action] = slotNumber;
1126
+ this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
653
1127
  let gasUsed;
654
- this.log.debug(`Simulating ${action}`, logData);
655
1128
  try {
656
1129
  ({ gasUsed } = await this.l1TxUtils.simulate(request, {
657
1130
  time: timestamp
@@ -684,6 +1157,7 @@ export class SequencerPublisher {
684
1157
  ...result,
685
1158
  ...logData
686
1159
  });
1160
+ this.lastActions[action] = cachedLastActionSlot;
687
1161
  } else {
688
1162
  this.log.info(`Action ${action} at ${slotNumber} succeeded`, {
689
1163
  ...result,
@@ -710,45 +1184,52 @@ export class SequencerPublisher {
710
1184
  }
711
1185
  async prepareProposeTx(encodedData, timestamp, options) {
712
1186
  const kzg = Blob.getViemKzgInstance();
713
- const blobInput = Blob.getPrefixedEthBlobCommitments(encodedData.blobs);
1187
+ const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
714
1188
  this.log.debug('Validating blob input', {
715
1189
  blobInput
716
1190
  });
717
- const blobEvaluationGas = await this.l1TxUtils.estimateGas(this.getSenderAddress().toString(), {
718
- to: this.rollupContract.address,
719
- data: encodeFunctionData({
720
- abi: RollupAbi,
721
- functionName: 'validateBlobs',
722
- args: [
723
- blobInput
724
- ]
725
- })
726
- }, {}, {
727
- blobs: encodedData.blobs.map((b)=>b.data),
728
- kzg
729
- }).catch((err)=>{
730
- const { message, metaMessages } = formatViemError(err);
731
- this.log.error(`Failed to validate blobs`, message, {
732
- metaMessages
1191
+ // Get blob evaluation gas
1192
+ let blobEvaluationGas;
1193
+ if (this.config.fishermanMode) {
1194
+ // In fisherman mode, we can't estimate blob gas because estimateGas doesn't support state overrides
1195
+ // Use a fixed estimate.
1196
+ blobEvaluationGas = BigInt(encodedData.blobs.length) * 21_000n;
1197
+ this.log.debug(`Using fixed blob evaluation gas estimate in fisherman mode: ${blobEvaluationGas}`);
1198
+ } else {
1199
+ // Normal mode - use estimateGas with blob inputs
1200
+ blobEvaluationGas = await this.l1TxUtils.estimateGas(this.getSenderAddress().toString(), {
1201
+ to: this.rollupContract.address,
1202
+ data: encodeFunctionData({
1203
+ abi: RollupAbi,
1204
+ functionName: 'validateBlobs',
1205
+ args: [
1206
+ blobInput
1207
+ ]
1208
+ })
1209
+ }, {}, {
1210
+ blobs: encodedData.blobs.map((b)=>b.data),
1211
+ kzg
1212
+ }).catch((err)=>{
1213
+ const { message, metaMessages } = formatViemError(err);
1214
+ this.log.error(`Failed to validate blobs`, message, {
1215
+ metaMessages
1216
+ });
1217
+ throw new Error('Failed to validate blobs');
733
1218
  });
734
- throw new Error('Failed to validate blobs');
735
- });
736
- const attestations = encodedData.attestations ? encodedData.attestations.map((attest)=>attest.toViem()) : [];
737
- const txHashes = encodedData.txHashes ? encodedData.txHashes.map((txHash)=>txHash.toString()) : [];
738
- const signers = encodedData.attestations?.filter((attest)=>!attest.signature.isEmpty()).map((attest)=>attest.address.toString());
1219
+ }
1220
+ const signers = encodedData.attestationsAndSigners.getSigners().map((signer)=>signer.toString());
739
1221
  const args = [
740
1222
  {
741
1223
  header: encodedData.header.toViem(),
742
1224
  archive: toHex(encodedData.archive),
743
- stateReference: encodedData.stateReference.toViem(),
744
1225
  oracleInput: {
745
1226
  // We are currently not modifying these. See #9963
746
1227
  feeAssetPriceModifier: 0n
747
- },
748
- txHashes
1228
+ }
749
1229
  },
750
- RollupContract.packAttestations(attestations),
751
- signers ?? [],
1230
+ encodedData.attestationsAndSigners.getPackedAttestations(),
1231
+ signers,
1232
+ encodedData.attestationsAndSignersSignature.toViemSignature(),
752
1233
  blobInput
753
1234
  ];
754
1235
  const { rollupData, simulationResult } = await this.simulateProposeTx(args, timestamp, options);
@@ -770,18 +1251,10 @@ export class SequencerPublisher {
770
1251
  functionName: 'propose',
771
1252
  args
772
1253
  });
773
- // override the pending block number if requested
774
- const forcePendingBlockNumberStateDiff = (options.forcePendingBlockNumber !== undefined ? await this.rollupContract.makePendingBlockNumberOverride(options.forcePendingBlockNumber) : []).flatMap((override)=>override.stateDiff ?? []);
775
- const simulationResult = await this.l1TxUtils.simulate({
776
- to: this.rollupContract.address,
777
- data: rollupData,
778
- gas: SequencerPublisher.PROPOSE_GAS_GUESS
779
- }, {
780
- // @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
781
- time: timestamp + 1n,
782
- // @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
783
- gasLimit: SequencerPublisher.PROPOSE_GAS_GUESS * 2n
784
- }, [
1254
+ // override the pending checkpoint number if requested
1255
+ const optsForcePendingCheckpointNumber = options.forcePendingBlockNumber !== undefined ? CheckpointNumber.fromBlockNumber(options.forcePendingBlockNumber) : undefined;
1256
+ const forcePendingCheckpointNumberStateDiff = (optsForcePendingCheckpointNumber !== undefined ? await this.rollupContract.makePendingCheckpointNumberOverride(optsForcePendingCheckpointNumber) : []).flatMap((override)=>override.stateDiff ?? []);
1257
+ const stateOverrides = [
785
1258
  {
786
1259
  address: this.rollupContract.address,
787
1260
  // @note we override checkBlob to false since blobs are not part simulate()
@@ -790,14 +1263,44 @@ export class SequencerPublisher {
790
1263
  slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true),
791
1264
  value: toPaddedHex(0n, true)
792
1265
  },
793
- ...forcePendingBlockNumberStateDiff
1266
+ ...forcePendingCheckpointNumberStateDiff
794
1267
  ]
795
1268
  }
796
- ], RollupAbi, {
1269
+ ];
1270
+ // In fisherman mode, simulate as the proposer but with sufficient balance
1271
+ if (this.proposerAddressForSimulation) {
1272
+ stateOverrides.push({
1273
+ address: this.proposerAddressForSimulation.toString(),
1274
+ balance: 10n * WEI_CONST * WEI_CONST
1275
+ });
1276
+ }
1277
+ const simulationResult = await this.l1TxUtils.simulate({
1278
+ to: this.rollupContract.address,
1279
+ data: rollupData,
1280
+ gas: SequencerPublisher.PROPOSE_GAS_GUESS,
1281
+ ...this.proposerAddressForSimulation && {
1282
+ from: this.proposerAddressForSimulation.toString()
1283
+ }
1284
+ }, {
1285
+ // @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
1286
+ time: timestamp + 1n,
1287
+ // @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
1288
+ gasLimit: SequencerPublisher.PROPOSE_GAS_GUESS * 2n
1289
+ }, stateOverrides, RollupAbi, {
797
1290
  // @note fallback gas estimate to use if the node doesn't support simulation API
798
1291
  fallbackGasEstimate: SequencerPublisher.PROPOSE_GAS_GUESS
799
1292
  }).catch((err)=>{
800
- this.log.error(`Failed to simulate propose tx`, err);
1293
+ // In fisherman mode, we expect ValidatorSelection__MissingProposerSignature since fisherman doesn't have proposer signature
1294
+ const viemError = formatViemError(err);
1295
+ if (this.config.fishermanMode && viemError.message?.includes('ValidatorSelection__MissingProposerSignature')) {
1296
+ this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
1297
+ // Return a minimal simulation result with the fallback gas estimate
1298
+ return {
1299
+ gasUsed: SequencerPublisher.PROPOSE_GAS_GUESS,
1300
+ logs: []
1301
+ };
1302
+ }
1303
+ this.log.error(`Failed to simulate propose tx`, viemError);
801
1304
  throw err;
802
1305
  });
803
1306
  return {
@@ -805,24 +1308,25 @@ export class SequencerPublisher {
805
1308
  simulationResult
806
1309
  };
807
1310
  }
808
- async addProposeTx(block, encodedData, opts = {}, timestamp) {
1311
+ async addProposeTx(checkpoint, encodedData, opts = {}, timestamp) {
1312
+ const slot = checkpoint.header.slotNumber;
809
1313
  const timer = new Timer();
810
1314
  const kzg = Blob.getViemKzgInstance();
811
1315
  const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, timestamp, opts);
812
1316
  const startBlock = await this.l1TxUtils.getBlockNumber();
813
1317
  const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(simulationResult.gasUsed) * 64 / 63)) + blobEvaluationGas + SequencerPublisher.MULTICALL_OVERHEAD_GAS_GUESS);
814
- // Send the blobs to the blob sink preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
815
- // tx fails but it does get mined. We make sure that the blobs are sent to the blob sink regardless of the tx outcome.
816
- void this.blobSinkClient.sendBlobsToBlobSink(encodedData.blobs).catch((_err)=>{
817
- this.log.error('Failed to send blobs to blob sink');
818
- });
1318
+ // Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
1319
+ // tx fails but it does get mined. We make sure that the blobs are sent to the blob client regardless of the tx outcome.
1320
+ void Promise.resolve().then(()=>this.blobClient.sendBlobsToFilestore(encodedData.blobs).catch((_err)=>{
1321
+ this.log.error('Failed to send blobs to blob client');
1322
+ }));
819
1323
  return this.addRequest({
820
1324
  action: 'propose',
821
1325
  request: {
822
1326
  to: this.rollupContract.address,
823
1327
  data: rollupData
824
1328
  },
825
- lastValidL2Slot: block.header.globalVariables.slotNumber.toBigInt(),
1329
+ lastValidL2Slot: checkpoint.header.slotNumber,
826
1330
  gasConfig: {
827
1331
  ...opts,
828
1332
  gasLimit
@@ -836,36 +1340,37 @@ export class SequencerPublisher {
836
1340
  return false;
837
1341
  }
838
1342
  const { receipt, stats, errorMsg } = result;
839
- const success = receipt && receipt.status === 'success' && tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'L2BlockProposed');
1343
+ const success = receipt && receipt.status === 'success' && tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointProposed');
840
1344
  if (success) {
841
1345
  const endBlock = receipt.blockNumber;
842
1346
  const inclusionBlocks = Number(endBlock - startBlock);
1347
+ const { calldataGas, calldataSize, sender } = stats;
843
1348
  const publishStats = {
844
1349
  gasPrice: receipt.effectiveGasPrice,
845
1350
  gasUsed: receipt.gasUsed,
846
1351
  blobGasUsed: receipt.blobGasUsed ?? 0n,
847
1352
  blobDataGas: receipt.blobGasPrice ?? 0n,
848
1353
  transactionHash: receipt.transactionHash,
849
- ...pick(stats, 'calldataGas', 'calldataSize', 'sender'),
850
- ...block.getStats(),
1354
+ calldataGas,
1355
+ calldataSize,
1356
+ sender,
1357
+ ...checkpoint.getStats(),
851
1358
  eventName: 'rollup-published-to-l1',
852
1359
  blobCount: encodedData.blobs.length,
853
1360
  inclusionBlocks
854
1361
  };
855
- this.log.info(`Published L2 block to L1 rollup contract`, {
1362
+ this.log.info(`Published checkpoint ${checkpoint.number} at slot ${slot} to rollup contract`, {
856
1363
  ...stats,
857
- ...block.getStats(),
858
- ...receipt
1364
+ ...checkpoint.getStats(),
1365
+ ...pick(receipt, 'transactionHash', 'blockHash')
859
1366
  });
860
1367
  this.metrics.recordProcessBlockTx(timer.ms(), publishStats);
861
1368
  return true;
862
1369
  } else {
863
1370
  this.metrics.recordFailedTx('process');
864
- this.log.error(`Rollup process tx failed: ${errorMsg ?? 'no error message'}`, undefined, {
865
- ...block.getStats(),
866
- receipt,
867
- txHash: receipt.transactionHash,
868
- slotNumber: block.header.globalVariables.slotNumber.toBigInt()
1371
+ this.log.error(`Publishing checkpoint at slot ${slot} failed with ${errorMsg ?? 'no error message'}`, undefined, {
1372
+ ...checkpoint.getStats(),
1373
+ ...receipt
869
1374
  });
870
1375
  return false;
871
1376
  }