@aztec/sequencer-client 0.0.1-commit.c31f2472 → 0.0.1-commit.c52d6e7

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 (133) hide show
  1. package/README.md +281 -21
  2. package/dest/client/sequencer-client.d.ts +34 -11
  3. package/dest/client/sequencer-client.d.ts.map +1 -1
  4. package/dest/client/sequencer-client.js +77 -39
  5. package/dest/config.d.ts +33 -6
  6. package/dest/config.d.ts.map +1 -1
  7. package/dest/config.js +100 -46
  8. package/dest/global_variable_builder/fee_predictor.d.ts +37 -0
  9. package/dest/global_variable_builder/fee_predictor.d.ts.map +1 -0
  10. package/dest/global_variable_builder/fee_predictor.js +138 -0
  11. package/dest/global_variable_builder/fee_provider.d.ts +21 -0
  12. package/dest/global_variable_builder/fee_provider.d.ts.map +1 -0
  13. package/dest/global_variable_builder/fee_provider.js +80 -0
  14. package/dest/global_variable_builder/global_builder.d.ts +13 -28
  15. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  16. package/dest/global_variable_builder/global_builder.js +9 -66
  17. package/dest/global_variable_builder/index.d.ts +4 -2
  18. package/dest/global_variable_builder/index.d.ts.map +1 -1
  19. package/dest/global_variable_builder/index.js +2 -0
  20. package/dest/publisher/config.d.ts +51 -17
  21. package/dest/publisher/config.d.ts.map +1 -1
  22. package/dest/publisher/config.js +131 -42
  23. package/dest/publisher/index.d.ts +2 -1
  24. package/dest/publisher/index.d.ts.map +1 -1
  25. package/dest/publisher/l1_to_l2_messaging.d.ts +21 -0
  26. package/dest/publisher/l1_to_l2_messaging.d.ts.map +1 -0
  27. package/dest/publisher/l1_to_l2_messaging.js +70 -0
  28. package/dest/publisher/l1_tx_failed_store/factory.d.ts +11 -0
  29. package/dest/publisher/l1_tx_failed_store/factory.d.ts.map +1 -0
  30. package/dest/publisher/l1_tx_failed_store/factory.js +22 -0
  31. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +98 -0
  32. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -0
  33. package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +68 -0
  34. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +15 -0
  35. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +1 -0
  36. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +36 -0
  37. package/dest/publisher/l1_tx_failed_store/index.d.ts +4 -0
  38. package/dest/publisher/l1_tx_failed_store/index.d.ts.map +1 -0
  39. package/dest/publisher/l1_tx_failed_store/index.js +3 -0
  40. package/dest/publisher/sequencer-bundle-simulator.d.ts +96 -0
  41. package/dest/publisher/sequencer-bundle-simulator.d.ts.map +1 -0
  42. package/dest/publisher/sequencer-bundle-simulator.js +198 -0
  43. package/dest/publisher/sequencer-publisher-factory.d.ts +11 -5
  44. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  45. package/dest/publisher/sequencer-publisher-factory.js +27 -3
  46. package/dest/publisher/sequencer-publisher.d.ts +123 -68
  47. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  48. package/dest/publisher/sequencer-publisher.js +775 -364
  49. package/dest/publisher/write_json.d.ts +11 -0
  50. package/dest/publisher/write_json.d.ts.map +1 -0
  51. package/dest/publisher/write_json.js +57 -0
  52. package/dest/sequencer/automine/automine_factory.d.ts +56 -0
  53. package/dest/sequencer/automine/automine_factory.d.ts.map +1 -0
  54. package/dest/sequencer/automine/automine_factory.js +85 -0
  55. package/dest/sequencer/automine/automine_sequencer.d.ts +189 -0
  56. package/dest/sequencer/automine/automine_sequencer.d.ts.map +1 -0
  57. package/dest/sequencer/automine/automine_sequencer.js +696 -0
  58. package/dest/sequencer/automine/index.d.ts +3 -0
  59. package/dest/sequencer/automine/index.d.ts.map +1 -0
  60. package/dest/sequencer/automine/index.js +2 -0
  61. package/dest/sequencer/checkpoint_proposal_job.d.ts +99 -20
  62. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  63. package/dest/sequencer/checkpoint_proposal_job.js +926 -286
  64. package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts +34 -0
  65. package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts.map +1 -0
  66. package/dest/sequencer/checkpoint_proposal_job_metrics.js +72 -0
  67. package/dest/sequencer/checkpoint_voter.d.ts +1 -2
  68. package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
  69. package/dest/sequencer/checkpoint_voter.js +2 -5
  70. package/dest/sequencer/events.d.ts +62 -5
  71. package/dest/sequencer/events.d.ts.map +1 -1
  72. package/dest/sequencer/metrics.d.ts +23 -8
  73. package/dest/sequencer/metrics.d.ts.map +1 -1
  74. package/dest/sequencer/metrics.js +117 -21
  75. package/dest/sequencer/missing_committee.d.ts +72 -0
  76. package/dest/sequencer/missing_committee.d.ts.map +1 -0
  77. package/dest/sequencer/missing_committee.js +139 -0
  78. package/dest/sequencer/requests_tracker.d.ts +22 -0
  79. package/dest/sequencer/requests_tracker.d.ts.map +1 -0
  80. package/dest/sequencer/requests_tracker.js +33 -0
  81. package/dest/sequencer/sequencer.d.ts +182 -44
  82. package/dest/sequencer/sequencer.d.ts.map +1 -1
  83. package/dest/sequencer/sequencer.js +623 -216
  84. package/dest/sequencer/types.d.ts +2 -2
  85. package/dest/sequencer/types.d.ts.map +1 -1
  86. package/dest/test/index.d.ts +5 -7
  87. package/dest/test/index.d.ts.map +1 -1
  88. package/dest/test/mock_checkpoint_builder.d.ts +12 -12
  89. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  90. package/dest/test/mock_checkpoint_builder.js +45 -36
  91. package/dest/test/utils.d.ts +17 -3
  92. package/dest/test/utils.d.ts.map +1 -1
  93. package/dest/test/utils.js +28 -9
  94. package/package.json +29 -29
  95. package/src/client/sequencer-client.ts +110 -47
  96. package/src/config.ts +127 -55
  97. package/src/global_variable_builder/README.md +44 -0
  98. package/src/global_variable_builder/fee_predictor.ts +182 -0
  99. package/src/global_variable_builder/fee_provider.ts +97 -0
  100. package/src/global_variable_builder/global_builder.ts +20 -89
  101. package/src/global_variable_builder/index.ts +3 -1
  102. package/src/publisher/config.ts +173 -40
  103. package/src/publisher/index.ts +3 -0
  104. package/src/publisher/l1_to_l2_messaging.ts +85 -0
  105. package/src/publisher/l1_tx_failed_store/factory.ts +32 -0
  106. package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +164 -0
  107. package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +47 -0
  108. package/src/publisher/l1_tx_failed_store/index.ts +3 -0
  109. package/src/publisher/sequencer-bundle-simulator.ts +254 -0
  110. package/src/publisher/sequencer-publisher-factory.ts +38 -9
  111. package/src/publisher/sequencer-publisher.ts +885 -461
  112. package/src/publisher/write_json.ts +78 -0
  113. package/src/sequencer/README.md +162 -450
  114. package/src/sequencer/automine/README.md +60 -0
  115. package/src/sequencer/automine/automine_factory.ts +152 -0
  116. package/src/sequencer/automine/automine_sequencer.ts +800 -0
  117. package/src/sequencer/automine/index.ts +6 -0
  118. package/src/sequencer/checkpoint_proposal_job.ts +1131 -325
  119. package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
  120. package/src/sequencer/checkpoint_voter.ts +1 -12
  121. package/src/sequencer/events.ts +66 -5
  122. package/src/sequencer/metrics.ts +132 -25
  123. package/src/sequencer/missing_committee.ts +192 -0
  124. package/src/sequencer/requests_tracker.ts +43 -0
  125. package/src/sequencer/sequencer.ts +737 -241
  126. package/src/sequencer/types.ts +1 -1
  127. package/src/test/index.ts +4 -6
  128. package/src/test/mock_checkpoint_builder.ts +65 -53
  129. package/src/test/utils.ts +64 -10
  130. package/dest/sequencer/timetable.d.ts +0 -90
  131. package/dest/sequencer/timetable.d.ts.map +0 -1
  132. package/dest/sequencer/timetable.js +0 -226
  133. package/src/sequencer/timetable.ts +0 -282
@@ -1,6 +1,5 @@
1
1
  import { Attributes, Metrics, createUpDownCounterWithDefault } from '@aztec/telemetry-client';
2
2
  import { formatUnits } from 'viem';
3
- // TODO(palla/mbps): Review all metrics and add any missing ones per checkpoint
4
3
  export class SequencerMetrics {
5
4
  rollup;
6
5
  tracer;
@@ -9,6 +8,7 @@ export class SequencerMetrics {
9
8
  blockBuildDuration;
10
9
  blockBuildManaPerSecond;
11
10
  stateTransitionBufferDuration;
11
+ stateDuration;
12
12
  // these are gauges because for individual sequencers building a block is not something that happens often enough to warrant a histogram
13
13
  timeToCollectAttestations;
14
14
  allowanceToCollectAttestations;
@@ -18,22 +18,32 @@ export class SequencerMetrics {
18
18
  slots;
19
19
  filledSlots;
20
20
  blockProposalFailed;
21
- blockProposalSuccess;
22
- blockProposalPrecheckFailed;
21
+ checkpointProposalSuccess;
22
+ checkpointPrecheckFailed;
23
+ checkpointProposalFailed;
23
24
  checkpointSuccess;
24
25
  slashingAttempts;
25
- checkpointAttestationDelay;
26
+ pipelineDepth;
27
+ pipelineDiscards;
28
+ pipelineParentCheckpointMismatches;
26
29
  // Fisherman fee analysis metrics
27
30
  fishermanWouldBeIncluded;
28
31
  fishermanTimeBeforeBlock;
29
32
  fishermanPendingBlobTxCount;
30
33
  fishermanIncludedBlobTxCount;
34
+ fishermanPendingBlobCount;
35
+ fishermanIncludedBlobCount;
36
+ fishermanBlockBlobsFull;
37
+ fishermanMaxBlobCapacity;
31
38
  fishermanCalculatedPriorityFee;
32
39
  fishermanPriorityFeeDelta;
33
40
  fishermanEstimatedCost;
34
41
  fishermanEstimatedOverpayment;
35
42
  fishermanMinedBlobTxPriorityFee;
36
43
  fishermanMinedBlobTxTotalCost;
44
+ blockInterBlockTime;
45
+ lastBlockBuiltTimestamp;
46
+ lastBlockBuiltSlot;
37
47
  lastSeenSlot;
38
48
  constructor(client, rollup, name = 'Sequencer'){
39
49
  this.rollup = rollup;
@@ -47,9 +57,10 @@ export class SequencerMetrics {
47
57
  });
48
58
  this.blockBuildDuration = this.meter.createHistogram(Metrics.SEQUENCER_BLOCK_BUILD_DURATION);
49
59
  this.blockBuildManaPerSecond = this.meter.createGauge(Metrics.SEQUENCER_BLOCK_BUILD_MANA_PER_SECOND);
60
+ this.blockInterBlockTime = this.meter.createHistogram(Metrics.SEQUENCER_BLOCK_INTER_BLOCK_TIME);
50
61
  this.stateTransitionBufferDuration = this.meter.createHistogram(Metrics.SEQUENCER_STATE_TRANSITION_BUFFER_DURATION);
51
- this.checkpointAttestationDelay = this.meter.createHistogram(Metrics.SEQUENCER_CHECKPOINT_ATTESTATION_DELAY);
52
- this.rewards = this.meter.createGauge(Metrics.SEQUENCER_CURRENT_BLOCK_REWARDS);
62
+ this.stateDuration = this.meter.createHistogram(Metrics.SEQUENCER_STATE_DURATION);
63
+ this.rewards = this.meter.createGauge(Metrics.SEQUENCER_CURRENT_SLOT_REWARDS);
53
64
  this.slots = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_SLOT_COUNT);
54
65
  /**
55
66
  * NOTE: we do not track missed slots as a separate metric. That would be difficult to determine
@@ -60,9 +71,9 @@ export class SequencerMetrics {
60
71
  this.requiredAttestions = this.meter.createGauge(Metrics.SEQUENCER_REQUIRED_ATTESTATIONS_COUNT);
61
72
  this.collectedAttestions = this.meter.createGauge(Metrics.SEQUENCER_COLLECTED_ATTESTATIONS_COUNT);
62
73
  this.blockProposalFailed = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_BLOCK_PROPOSAL_FAILED_COUNT);
63
- this.blockProposalSuccess = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_BLOCK_PROPOSAL_SUCCESS_COUNT);
74
+ this.checkpointProposalSuccess = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_CHECKPOINT_PROPOSAL_SUCCESS_COUNT);
64
75
  this.checkpointSuccess = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_CHECKPOINT_SUCCESS_COUNT);
65
- this.blockProposalPrecheckFailed = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_BLOCK_PROPOSAL_PRECHECK_FAILED_COUNT, {
76
+ this.checkpointPrecheckFailed = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_CHECKPOINT_PRECHECK_FAILED_COUNT, {
66
77
  [Attributes.ERROR_TYPE]: [
67
78
  'slot_already_taken',
68
79
  'rollup_contract_check_failed',
@@ -70,12 +81,29 @@ export class SequencerMetrics {
70
81
  'block_number_mismatch'
71
82
  ]
72
83
  });
84
+ this.checkpointProposalFailed = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_CHECKPOINT_PROPOSAL_FAILED_COUNT);
73
85
  this.slashingAttempts = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_SLASHING_ATTEMPTS_COUNT);
86
+ this.pipelineDepth = this.meter.createGauge(Metrics.SEQUENCER_PIPELINE_DEPTH);
87
+ this.pipelineDiscards = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_PIPELINE_DISCARDS_COUNT);
88
+ this.pipelineParentCheckpointMismatches = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_PIPELINE_PARENT_CHECKPOINT_MISMATCH_COUNT, {
89
+ [Attributes.ERROR_TYPE]: [
90
+ 'archiver-sync-timeout',
91
+ 'parent-not-on-l1',
92
+ 'parent-hash-mismatch',
93
+ 'parent-invalid-attestations',
94
+ 'unexpected-parent-appeared'
95
+ ]
96
+ });
97
+ this.pipelineDepth.record(0);
74
98
  // Fisherman fee analysis metrics
75
99
  this.fishermanWouldBeIncluded = createUpDownCounterWithDefault(this.meter, Metrics.FISHERMAN_FEE_ANALYSIS_WOULD_BE_INCLUDED, {
76
100
  [Attributes.OK]: [
77
101
  true,
78
102
  false
103
+ ],
104
+ [Attributes.BLOCK_FULL]: [
105
+ 'true',
106
+ 'false'
79
107
  ]
80
108
  });
81
109
  this.fishermanTimeBeforeBlock = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_TIME_BEFORE_BLOCK);
@@ -87,6 +115,15 @@ export class SequencerMetrics {
87
115
  this.fishermanEstimatedOverpayment = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_ESTIMATED_OVERPAYMENT);
88
116
  this.fishermanMinedBlobTxPriorityFee = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_MINED_BLOB_TX_PRIORITY_FEE);
89
117
  this.fishermanMinedBlobTxTotalCost = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_MINED_BLOB_TX_TOTAL_COST);
118
+ this.fishermanPendingBlobCount = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_PENDING_BLOB_COUNT);
119
+ this.fishermanIncludedBlobCount = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_INCLUDED_BLOB_COUNT);
120
+ this.fishermanBlockBlobsFull = createUpDownCounterWithDefault(this.meter, Metrics.FISHERMAN_FEE_ANALYSIS_BLOCK_BLOBS_FULL, {
121
+ [Attributes.OK]: [
122
+ true,
123
+ false
124
+ ]
125
+ });
126
+ this.fishermanMaxBlobCapacity = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_MAX_BLOB_CAPACITY);
90
127
  }
91
128
  recordRequiredAttestations(requiredAttestationsCount, allowanceMs) {
92
129
  this.requiredAttestions.record(requiredAttestationsCount);
@@ -95,19 +132,23 @@ export class SequencerMetrics {
95
132
  this.collectedAttestions.record(0);
96
133
  this.timeToCollectAttestations.record(0);
97
134
  }
98
- recordCheckpointAttestationDelay(duration) {
99
- this.checkpointAttestationDelay.record(duration);
100
- }
101
135
  recordCollectedAttestations(count, durationMs) {
102
136
  this.collectedAttestions.record(count);
103
137
  this.timeToCollectAttestations.record(Math.ceil(durationMs));
104
138
  }
105
- recordBuiltBlock(buildDurationMs, totalMana) {
139
+ recordBuiltBlock(buildDurationMs, totalMana, slot) {
106
140
  this.blockCounter.add(1, {
107
141
  [Attributes.STATUS]: 'built'
108
142
  });
109
143
  this.blockBuildDuration.record(Math.ceil(buildDurationMs));
110
144
  this.blockBuildManaPerSecond.record(Math.ceil(totalMana * 1000 / buildDurationMs));
145
+ // Only record inter-block time between blocks built within the same slot.
146
+ const now = Date.now();
147
+ if (this.lastBlockBuiltTimestamp !== undefined && this.lastBlockBuiltSlot === slot) {
148
+ this.blockInterBlockTime.record(now - this.lastBlockBuiltTimestamp);
149
+ }
150
+ this.lastBlockBuiltTimestamp = now;
151
+ this.lastBlockBuiltSlot = slot;
111
152
  }
112
153
  recordFailedBlock() {
113
154
  this.blockCounter.add(1, {
@@ -119,6 +160,22 @@ export class SequencerMetrics {
119
160
  [Attributes.SEQUENCER_STATE]: state
120
161
  });
121
162
  }
163
+ recordStateDuration(durationMs, state) {
164
+ this.stateDuration.record(Math.ceil(durationMs), {
165
+ [Attributes.SEQUENCER_STATE]: state
166
+ });
167
+ }
168
+ recordPipelineDepth(depth) {
169
+ this.pipelineDepth.record(depth);
170
+ }
171
+ recordPipelineDiscard(count = 1) {
172
+ this.pipelineDiscards.add(count);
173
+ }
174
+ recordPipelineParentCheckpointMismatch(reason) {
175
+ this.pipelineParentCheckpointMismatches.add(1, {
176
+ [Attributes.ERROR_TYPE]: reason
177
+ });
178
+ }
122
179
  incOpenSlot(slot, proposer) {
123
180
  // sequencer went through the loop a second time. Noop
124
181
  if (slot === this.lastSeenSlot) {
@@ -156,14 +213,21 @@ export class SequencerMetrics {
156
213
  }
157
214
  });
158
215
  }
159
- recordBlockProposalSuccess() {
160
- this.blockProposalSuccess.add(1);
216
+ recordCheckpointProposalSuccess() {
217
+ this.checkpointProposalSuccess.add(1);
161
218
  }
162
- recordBlockProposalPrecheckFailed(checkType) {
163
- this.blockProposalPrecheckFailed.add(1, {
219
+ recordCheckpointPrecheckFailed(checkType) {
220
+ this.checkpointPrecheckFailed.add(1, {
164
221
  [Attributes.ERROR_TYPE]: checkType
165
222
  });
166
223
  }
224
+ recordCheckpointProposalFailed(reason) {
225
+ this.checkpointProposalFailed.add(1, {
226
+ ...reason && {
227
+ [Attributes.ERROR_TYPE]: reason
228
+ }
229
+ });
230
+ }
167
231
  recordSlashingAttempt(actionCount) {
168
232
  this.slashingAttempts.add(actionCount);
169
233
  }
@@ -184,9 +248,11 @@ export class SequencerMetrics {
184
248
  };
185
249
  // Record pending block snapshot data (once per strategy for comparison)
186
250
  this.fishermanPendingBlobTxCount.record(analysis.pendingSnapshot.pendingBlobTxCount, strategyAttributes);
251
+ this.fishermanPendingBlobCount.record(analysis.pendingSnapshot.pendingBlobCount, strategyAttributes);
187
252
  // Record mined block data if available
188
253
  if (analysis.minedBlock) {
189
254
  this.fishermanIncludedBlobTxCount.record(analysis.minedBlock.includedBlobTxCount, strategyAttributes);
255
+ this.fishermanIncludedBlobCount.record(analysis.minedBlock.includedBlobCount, strategyAttributes);
190
256
  // Record actual fees from blob transactions in the mined block
191
257
  for (const blobTx of analysis.minedBlock.includedBlobTxs){
192
258
  // Record priority fee per gas in Gwei
@@ -211,16 +277,34 @@ export class SequencerMetrics {
211
277
  // Record analysis results if available
212
278
  if (analysis.analysis) {
213
279
  this.fishermanTimeBeforeBlock.record(Math.ceil(analysis.analysis.timeBeforeBlockMs), strategyAttributes);
280
+ // Record whether the block reached 100% blob capacity
281
+ if (analysis.analysis.blockBlobsFull) {
282
+ this.fishermanBlockBlobsFull.add(1, {
283
+ ...strategyAttributes,
284
+ [Attributes.OK]: true
285
+ });
286
+ } else {
287
+ this.fishermanBlockBlobsFull.add(1, {
288
+ ...strategyAttributes,
289
+ [Attributes.OK]: false
290
+ });
291
+ }
292
+ // Record the max blob capacity for this block
293
+ this.fishermanMaxBlobCapacity.record(analysis.analysis.maxBlobCapacity, strategyAttributes);
214
294
  // Record strategy-specific inclusion result
215
295
  if (strategyResult.wouldBeIncluded !== undefined) {
296
+ const inclusionAttributes = {
297
+ ...strategyAttributes,
298
+ [Attributes.BLOCK_FULL]: analysis.analysis.blockBlobsFull ? 'true' : 'false'
299
+ };
216
300
  if (strategyResult.wouldBeIncluded) {
217
301
  this.fishermanWouldBeIncluded.add(1, {
218
- ...strategyAttributes,
302
+ ...inclusionAttributes,
219
303
  [Attributes.OK]: true
220
304
  });
221
305
  } else {
222
306
  this.fishermanWouldBeIncluded.add(1, {
223
- ...strategyAttributes,
307
+ ...inclusionAttributes,
224
308
  [Attributes.OK]: false,
225
309
  ...strategyResult.exclusionReason && {
226
310
  [Attributes.ERROR_TYPE]: strategyResult.exclusionReason
@@ -231,15 +315,27 @@ export class SequencerMetrics {
231
315
  // Record strategy-specific priority fee delta
232
316
  if (strategyResult.priorityFeeDelta !== undefined) {
233
317
  const priorityFeeDeltaGwei = Number(strategyResult.priorityFeeDelta) / 1e9;
234
- this.fishermanPriorityFeeDelta.record(priorityFeeDeltaGwei, strategyAttributes);
318
+ const deltaAttributes = {
319
+ ...strategyAttributes,
320
+ [Attributes.BLOCK_FULL]: analysis.analysis.blockBlobsFull ? 'true' : 'false'
321
+ };
322
+ this.fishermanPriorityFeeDelta.record(priorityFeeDeltaGwei, deltaAttributes);
235
323
  }
236
324
  // Record estimated cost if available
237
325
  if (strategyResult.estimatedCostEth !== undefined) {
238
- this.fishermanEstimatedCost.record(strategyResult.estimatedCostEth, strategyAttributes);
326
+ const costAttributes = {
327
+ ...strategyAttributes,
328
+ [Attributes.BLOCK_FULL]: analysis.analysis.blockBlobsFull ? 'true' : 'false'
329
+ };
330
+ this.fishermanEstimatedCost.record(strategyResult.estimatedCostEth, costAttributes);
239
331
  }
240
332
  // Record estimated overpayment if available
241
333
  if (strategyResult.estimatedOverpaymentEth !== undefined) {
242
- this.fishermanEstimatedOverpayment.record(strategyResult.estimatedOverpaymentEth, strategyAttributes);
334
+ const overpaymentAttributes = {
335
+ ...strategyAttributes,
336
+ [Attributes.BLOCK_FULL]: analysis.analysis.blockBlobsFull ? 'true' : 'false'
337
+ };
338
+ this.fishermanEstimatedOverpayment.record(strategyResult.estimatedOverpaymentEth, overpaymentAttributes);
243
339
  }
244
340
  }
245
341
  }
@@ -0,0 +1,72 @@
1
+ import type { EpochCache } from '@aztec/epoch-cache';
2
+ import type { RollupContract } from '@aztec/ethereum/contracts';
3
+ import { EpochNumber, type SlotNumber } from '@aztec/foundation/branded-types';
4
+ import type { Logger } from '@aztec/foundation/log';
5
+ import type { DateProvider } from '@aztec/foundation/timer';
6
+ import type { L2BlockSource } from '@aztec/stdlib/block';
7
+ import { type L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
8
+ /** Collaborators {@link logMissingCommittee} needs to diagnose and report why no committee exists. */
9
+ export interface MissingCommitteeContext {
10
+ epochCache: Pick<EpochCache, 'getL1Constants' | 'getLagInEpochsForValidatorSet'>;
11
+ rollupContract: Pick<RollupContract, 'getActiveAttesterCount' | 'getAttesterCountAtTime'>;
12
+ l2BlockSource: Pick<L2BlockSource, 'getBlockNumber'>;
13
+ l1Constants: Pick<L1RollupConstants, 'l1GenesisTime' | 'slotDuration' | 'epochDuration'>;
14
+ dateProvider: Pick<DateProvider, 'nowInSeconds'>;
15
+ logger: Pick<Logger, 'info' | 'warn' | 'debug'>;
16
+ }
17
+ /**
18
+ * Why the sequencer cannot find a validator committee for an upcoming slot. A committee for an epoch is
19
+ * sampled from the validator set as of `lagInEpochsForValidatorSet` epochs earlier, and L1 refuses to
20
+ * produce one until at least `targetCommitteeSize` validators were staked at that sampling time.
21
+ */
22
+ export type MissingCommitteeCause =
23
+ /** Enough validators are staked now; the committee is just waiting for the sampling window to advance. */
24
+ 'awaiting-sampling-lag'
25
+ /** The chain has not produced any block yet and too few validators are staked: normal bootstrap state. */
26
+ | 'awaiting-first-validators'
27
+ /** The chain has produced blocks before but the validator set has since dropped below the required size. */
28
+ | 'validator-set-shrank';
29
+ export interface MissingCommitteeDiagnosis {
30
+ cause: MissingCommitteeCause;
31
+ /** Log severity: bootstrap and lag are expected (`info`); a shrunken set on a live chain is not (`warn`). */
32
+ severity: 'info' | 'warn';
33
+ }
34
+ /**
35
+ * Logs why no validator committee exists for the given slot. Rather than the cryptic "committee does not
36
+ * exist on L1", it diagnoses the cause from the live attester count and whether the chain has ever produced
37
+ * a block: a bootstrapping chain waiting for validators to stake, a full set still waiting for the sampling
38
+ * lag to elapse, or a validator set that has shrunk below the required size on a live chain. Only the last is
39
+ * a genuine problem, so only it is logged at `warn`.
40
+ *
41
+ * Best-effort: if the diagnostic L1 reads fail we fall back to a neutral message so this never throws on the
42
+ * propose path.
43
+ */
44
+ export declare function logMissingCommittee(targetSlot: SlotNumber, targetEpoch: EpochNumber, ctx: MissingCommitteeContext): Promise<void>;
45
+ /**
46
+ * Classifies why no committee exists for a slot, using only cheap live signals: the current attester count
47
+ * and whether the chain has ever produced a block. See {@link MissingCommitteeCause} for the reasoning.
48
+ */
49
+ export declare function classifyMissingCommittee(args: {
50
+ attesterCount: number;
51
+ targetCommitteeSize: number;
52
+ hasProducedBlocks: boolean;
53
+ }): MissingCommitteeDiagnosis;
54
+ /**
55
+ * Given each candidate epoch after the one we failed to propose at, paired with the attester count staked at
56
+ * that epoch's validator-set sample time (`undefined` when the sample time is still in the future), returns
57
+ * the earliest epoch that will have a committee, or `undefined` if none qualifies.
58
+ *
59
+ * Candidates must be ordered ascending by epoch. A committee for epoch `E` exists iff at least
60
+ * `targetCommitteeSize` validators were staked at `E`'s sample time, so the first candidate whose sampled
61
+ * count meets the target is the answer; a candidate whose sample time is still in the future is assumed to
62
+ * have a committee if the set holds and wins as soon as it is reached. The predicate is not monotone in `E`
63
+ * (the set can dip below target between sample times), so this must scan ascending rather than binary-search.
64
+ */
65
+ export declare function findFirstEpochWithCommittee(args: {
66
+ candidates: {
67
+ epoch: EpochNumber;
68
+ sampledAttesterCount: number | undefined;
69
+ }[];
70
+ targetCommitteeSize: number;
71
+ }): EpochNumber | undefined;
72
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWlzc2luZ19jb21taXR0ZWUuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZXF1ZW5jZXIvbWlzc2luZ19jb21taXR0ZWUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDckQsT0FBTyxLQUFLLEVBQUUsY0FBYyxFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDaEUsT0FBTyxFQUFFLFdBQVcsRUFBRSxLQUFLLFVBQVUsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBRS9FLE9BQU8sS0FBSyxFQUFFLE1BQU0sRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBRXBELE9BQU8sS0FBSyxFQUFFLFlBQVksRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQzVELE9BQU8sS0FBSyxFQUFFLGFBQWEsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ3pELE9BQU8sRUFBRSxLQUFLLGlCQUFpQixFQUE2QixNQUFNLDZCQUE2QixDQUFDO0FBRWhHLHNHQUFzRztBQUN0RyxNQUFNLFdBQVcsdUJBQXVCO0lBQ3RDLFVBQVUsRUFBRSxJQUFJLENBQUMsVUFBVSxFQUFFLGdCQUFnQixHQUFHLCtCQUErQixDQUFDLENBQUM7SUFDakYsY0FBYyxFQUFFLElBQUksQ0FBQyxjQUFjLEVBQUUsd0JBQXdCLEdBQUcsd0JBQXdCLENBQUMsQ0FBQztJQUMxRixhQUFhLEVBQUUsSUFBSSxDQUFDLGFBQWEsRUFBRSxnQkFBZ0IsQ0FBQyxDQUFDO0lBQ3JELFdBQVcsRUFBRSxJQUFJLENBQUMsaUJBQWlCLEVBQUUsZUFBZSxHQUFHLGNBQWMsR0FBRyxlQUFlLENBQUMsQ0FBQztJQUN6RixZQUFZLEVBQUUsSUFBSSxDQUFDLFlBQVksRUFBRSxjQUFjLENBQUMsQ0FBQztJQUNqRCxNQUFNLEVBQUUsSUFBSSxDQUFDLE1BQU0sRUFBRSxNQUFNLEdBQUcsTUFBTSxHQUFHLE9BQU8sQ0FBQyxDQUFDO0NBQ2pEO0FBRUQ7Ozs7R0FJRztBQUNILE1BQU0sTUFBTSxxQkFBcUI7QUFDL0IsMEdBQTBHO0FBQ3hHLHVCQUF1QjtBQUN6QiwwR0FBMEc7R0FDeEcsMkJBQTJCO0FBQzdCLDRHQUE0RztHQUMxRyxzQkFBc0IsQ0FBQztBQUUzQixNQUFNLFdBQVcseUJBQXlCO0lBQ3hDLEtBQUssRUFBRSxxQkFBcUIsQ0FBQztJQUM3Qiw2R0FBNkc7SUFDN0csUUFBUSxFQUFFLE1BQU0sR0FBRyxNQUFNLENBQUM7Q0FDM0I7QUFFRDs7Ozs7Ozs7O0dBU0c7QUFDSCx3QkFBc0IsbUJBQW1CLENBQ3ZDLFVBQVUsRUFBRSxVQUFVLEVBQ3RCLFdBQVcsRUFBRSxXQUFXLEVBQ3hCLEdBQUcsRUFBRSx1QkFBdUIsR0FDM0IsT0FBTyxDQUFDLElBQUksQ0FBQyxDQTBEZjtBQUVEOzs7R0FHRztBQUNILHdCQUFnQix3QkFBd0IsQ0FBQyxJQUFJLEVBQUU7SUFDN0MsYUFBYSxFQUFFLE1BQU0sQ0FBQztJQUN0QixtQkFBbUIsRUFBRSxNQUFNLENBQUM7SUFDNUIsaUJBQWlCLEVBQUUsT0FBTyxDQUFDO0NBQzVCLEdBQUcseUJBQXlCLENBWTVCO0FBRUQ7Ozs7Ozs7Ozs7R0FVRztBQUNILHdCQUFnQiwyQkFBMkIsQ0FBQyxJQUFJLEVBQUU7SUFDaEQsVUFBVSxFQUFFO1FBQUUsS0FBSyxFQUFFLFdBQVcsQ0FBQztRQUFDLG9CQUFvQixFQUFFLE1BQU0sR0FBRyxTQUFTLENBQUE7S0FBRSxFQUFFLENBQUM7SUFDL0UsbUJBQW1CLEVBQUUsTUFBTSxDQUFDO0NBQzdCLEdBQUcsV0FBVyxHQUFHLFNBQVMsQ0FJMUIifQ==
@@ -0,0 +1 @@
1
+ {"version":3,"file":"missing_committee.d.ts","sourceRoot":"","sources":["../../src/sequencer/missing_committee.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAE/E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAEpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,KAAK,iBAAiB,EAA6B,MAAM,6BAA6B,CAAC;AAEhG,sGAAsG;AACtG,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,gBAAgB,GAAG,+BAA+B,CAAC,CAAC;IACjF,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,wBAAwB,GAAG,wBAAwB,CAAC,CAAC;IAC1F,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,gBAAgB,CAAC,CAAC;IACrD,WAAW,EAAE,IAAI,CAAC,iBAAiB,EAAE,eAAe,GAAG,cAAc,GAAG,eAAe,CAAC,CAAC;IACzF,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC;IACjD,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;CACjD;AAED;;;;GAIG;AACH,MAAM,MAAM,qBAAqB;AAC/B,0GAA0G;AACxG,uBAAuB;AACzB,0GAA0G;GACxG,2BAA2B;AAC7B,4GAA4G;GAC1G,sBAAsB,CAAC;AAE3B,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,qBAAqB,CAAC;IAC7B,6GAA6G;IAC7G,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,UAAU,EACtB,WAAW,EAAE,WAAW,EACxB,GAAG,EAAE,uBAAuB,GAC3B,OAAO,CAAC,IAAI,CAAC,CA0Df;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,iBAAiB,EAAE,OAAO,CAAC;CAC5B,GAAG,yBAAyB,CAY5B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE;IAChD,UAAU,EAAE;QAAE,KAAK,EAAE,WAAW,CAAC;QAAC,oBAAoB,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,EAAE,CAAC;IAC/E,mBAAmB,EAAE,MAAM,CAAC;CAC7B,GAAG,WAAW,GAAG,SAAS,CAI1B"}
@@ -0,0 +1,139 @@
1
+ import { EpochNumber } from '@aztec/foundation/branded-types';
2
+ import { timesParallel } from '@aztec/foundation/collection';
3
+ import { formatSeconds } from '@aztec/foundation/string';
4
+ import { getStartTimestampForEpoch } from '@aztec/stdlib/epoch-helpers';
5
+ /**
6
+ * Logs why no validator committee exists for the given slot. Rather than the cryptic "committee does not
7
+ * exist on L1", it diagnoses the cause from the live attester count and whether the chain has ever produced
8
+ * a block: a bootstrapping chain waiting for validators to stake, a full set still waiting for the sampling
9
+ * lag to elapse, or a validator set that has shrunk below the required size on a live chain. Only the last is
10
+ * a genuine problem, so only it is logged at `warn`.
11
+ *
12
+ * Best-effort: if the diagnostic L1 reads fail we fall back to a neutral message so this never throws on the
13
+ * propose path.
14
+ */ export async function logMissingCommittee(targetSlot, targetEpoch, ctx) {
15
+ const targetCommitteeSize = ctx.epochCache.getL1Constants().targetCommitteeSize;
16
+ const lag = ctx.epochCache.getLagInEpochsForValidatorSet();
17
+ let attesterCount;
18
+ let hasProducedBlocks;
19
+ try {
20
+ [attesterCount, hasProducedBlocks] = await Promise.all([
21
+ ctx.rollupContract.getActiveAttesterCount(),
22
+ ctx.l2BlockSource.getBlockNumber().then((n)=>n > 0)
23
+ ]);
24
+ } catch (err) {
25
+ ctx.logger.warn(`No committee found for slot ${targetSlot}; could not determine validator set size`, {
26
+ targetSlot,
27
+ targetEpoch,
28
+ targetCommitteeSize,
29
+ err
30
+ });
31
+ return;
32
+ }
33
+ const diagnosis = classifyMissingCommittee({
34
+ attesterCount,
35
+ targetCommitteeSize,
36
+ hasProducedBlocks
37
+ });
38
+ const logCtx = {
39
+ targetSlot,
40
+ targetEpoch,
41
+ attesterCount,
42
+ targetCommitteeSize,
43
+ cause: diagnosis.cause
44
+ };
45
+ switch(diagnosis.cause){
46
+ case 'awaiting-sampling-lag':
47
+ {
48
+ const firstCommitteeEpoch = await estimateFirstCommitteeEpoch(targetEpoch, lag, targetCommitteeSize, ctx);
49
+ const staked = `${attesterCount} validators are staked (>= ${targetCommitteeSize} required)`;
50
+ const forming = `the committee is still forming as the ${lag}-epoch sampling window advances`;
51
+ // When we could not pin the exact epoch, report the safe upper bound rather than a misleading ETA.
52
+ const epoch = firstCommitteeEpoch ?? EpochNumber(targetEpoch + lag + 1);
53
+ const expectation = firstCommitteeEpoch === undefined ? `A committee should exist no later than epoch ${epoch}.` : `The first committee is expected at epoch ${epoch}${formatEpochEta(epoch, ctx)}.`;
54
+ ctx.logger.info(`No committee for slot ${targetSlot} yet: ${staked}, ${forming}. ${expectation}`, {
55
+ ...logCtx,
56
+ firstCommitteeEpoch: epoch
57
+ });
58
+ break;
59
+ }
60
+ case 'awaiting-first-validators':
61
+ ctx.logger.info(`No committee for slot ${targetSlot}: the chain has not started producing blocks and only ${attesterCount} ` + `of the ${targetCommitteeSize} required validators are staked. Block production begins once at least ` + `${targetCommitteeSize} validators stake and the ${lag}-epoch sampling lag elapses.`, logCtx);
62
+ break;
63
+ case 'validator-set-shrank':
64
+ ctx.logger.warn(`No committee for slot ${targetSlot}: the validator set has dropped to ${attesterCount}, below the ` + `${targetCommitteeSize} required to form a committee. The chain cannot progress until enough validators ` + `are staked again — check for validators exiting or being slashed.`, logCtx);
65
+ break;
66
+ }
67
+ }
68
+ /**
69
+ * Classifies why no committee exists for a slot, using only cheap live signals: the current attester count
70
+ * and whether the chain has ever produced a block. See {@link MissingCommitteeCause} for the reasoning.
71
+ */ export function classifyMissingCommittee(args) {
72
+ const { attesterCount, targetCommitteeSize, hasProducedBlocks } = args;
73
+ if (attesterCount >= targetCommitteeSize) {
74
+ return {
75
+ cause: 'awaiting-sampling-lag',
76
+ severity: 'info'
77
+ };
78
+ }
79
+ if (hasProducedBlocks) {
80
+ return {
81
+ cause: 'validator-set-shrank',
82
+ severity: 'warn'
83
+ };
84
+ }
85
+ return {
86
+ cause: 'awaiting-first-validators',
87
+ severity: 'info'
88
+ };
89
+ }
90
+ /**
91
+ * Given each candidate epoch after the one we failed to propose at, paired with the attester count staked at
92
+ * that epoch's validator-set sample time (`undefined` when the sample time is still in the future), returns
93
+ * the earliest epoch that will have a committee, or `undefined` if none qualifies.
94
+ *
95
+ * Candidates must be ordered ascending by epoch. A committee for epoch `E` exists iff at least
96
+ * `targetCommitteeSize` validators were staked at `E`'s sample time, so the first candidate whose sampled
97
+ * count meets the target is the answer; a candidate whose sample time is still in the future is assumed to
98
+ * have a committee if the set holds and wins as soon as it is reached. The predicate is not monotone in `E`
99
+ * (the set can dip below target between sample times), so this must scan ascending rather than binary-search.
100
+ */ export function findFirstEpochWithCommittee(args) {
101
+ return args.candidates.find((c)=>c.sampledAttesterCount === undefined || c.sampledAttesterCount >= args.targetCommitteeSize)?.epoch;
102
+ }
103
+ /**
104
+ * Earliest epoch expected to have a committee, replaying L1's validator-set sampling rule: a committee for
105
+ * epoch `E` exists iff at least `targetCommitteeSize` attesters were staked at `E`'s sample time
106
+ * (`epochStart(E) - lag * epochDuration`). Only epochs after the one we failed at can qualify, and the set
107
+ * must have crossed the target within the last `lag` epochs' sample window, so the answer lies in
108
+ * `(targetEpoch, targetEpoch + lag + 1]`. Reads the historical attester count at each candidate's sample time
109
+ * (at most `lag` on-chain reads; future sample times are assumed to hold). Returns `undefined` if no candidate
110
+ * qualifies or the reads fail, leaving the caller to report a bound instead of an ETA.
111
+ */ async function estimateFirstCommitteeEpoch(targetEpoch, lag, targetCommitteeSize, ctx) {
112
+ try {
113
+ const epochDurationSeconds = BigInt(ctx.l1Constants.epochDuration * ctx.l1Constants.slotDuration);
114
+ const nowSeconds = BigInt(ctx.dateProvider.nowInSeconds());
115
+ const candidates = await timesParallel(lag + 1, async (i)=>{
116
+ const epoch = EpochNumber(targetEpoch + 1 + i);
117
+ const sampleTime = getStartTimestampForEpoch(epoch, ctx.l1Constants) - epochDurationSeconds * BigInt(lag);
118
+ const sampledAttesterCount = sampleTime > nowSeconds ? undefined : await ctx.rollupContract.getAttesterCountAtTime(sampleTime);
119
+ return {
120
+ epoch,
121
+ sampledAttesterCount
122
+ };
123
+ });
124
+ return findFirstEpochWithCommittee({
125
+ candidates,
126
+ targetCommitteeSize
127
+ });
128
+ } catch (err) {
129
+ ctx.logger.debug(`Could not estimate first committee epoch after epoch ${targetEpoch}`, {
130
+ targetEpoch,
131
+ err
132
+ });
133
+ return undefined;
134
+ }
135
+ }
136
+ /** Formats the ETA to the start of the given epoch as ` (~12m from now)`, or empty if it is in the past. */ function formatEpochEta(epoch, ctx) {
137
+ const secondsUntil = Number(getStartTimestampForEpoch(epoch, ctx.l1Constants)) - ctx.dateProvider.nowInSeconds();
138
+ return secondsUntil > 0 ? ` (~${formatSeconds(secondsUntil)} from now)` : '';
139
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Tracks fire-and-forget requests so a shutdown path can interrupt any in-flight work and await it to
3
+ * settle. Each tracked request is a promise plus an optional interrupt callback; the entry removes itself
4
+ * from the bag once its promise settles, so the tracker only ever holds requests that are still in flight.
5
+ */
6
+ export declare class RequestsTracker {
7
+ private readonly requests;
8
+ private nextId;
9
+ /** Number of in-flight requests currently tracked. */
10
+ get size(): number;
11
+ /**
12
+ * Tracks a fire-and-forget request. `interrupt`, when provided, is invoked by {@link interruptRequests}
13
+ * to cancel the in-flight work so its promise settles promptly. The entry auto-removes once `promise`
14
+ * settles, whether it resolves or rejects.
15
+ */
16
+ trackRequest(promise: Promise<unknown>, interrupt?: () => void): void;
17
+ /** Interrupts every in-flight request that was tracked with an interrupt callback. */
18
+ interruptRequests(): void;
19
+ /** Awaits every currently in-flight request to settle. */
20
+ awaitRequests(): Promise<void>;
21
+ }
22
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVxdWVzdHNfdHJhY2tlci5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlcXVlbmNlci9yZXF1ZXN0c190cmFja2VyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUdBOzs7O0dBSUc7QUFDSCxxQkFBYSxlQUFlO0lBQzFCLE9BQU8sQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFxQztJQUM5RCxPQUFPLENBQUMsTUFBTSxDQUFLO0lBRW5CLHNEQUFzRDtJQUN0RCxJQUFXLElBQUksSUFBSSxNQUFNLENBRXhCO0lBRUQ7Ozs7T0FJRztJQUNJLFlBQVksQ0FBQyxPQUFPLEVBQUUsT0FBTyxDQUFDLE9BQU8sQ0FBQyxFQUFFLFNBQVMsQ0FBQyxFQUFFLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FPM0U7SUFFRCxzRkFBc0Y7SUFDL0UsaUJBQWlCLElBQUksSUFBSSxDQUkvQjtJQUVELDBEQUEwRDtJQUM3QyxhQUFhLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxDQUUxQztDQUNGIn0=
@@ -0,0 +1 @@
1
+ {"version":3,"file":"requests_tracker.d.ts","sourceRoot":"","sources":["../../src/sequencer/requests_tracker.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqC;IAC9D,OAAO,CAAC,MAAM,CAAK;IAEnB,sDAAsD;IACtD,IAAW,IAAI,IAAI,MAAM,CAExB;IAED;;;;OAIG;IACI,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI,CAO3E;IAED,sFAAsF;IAC/E,iBAAiB,IAAI,IAAI,CAI/B;IAED,0DAA0D;IAC7C,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAE1C;CACF"}
@@ -0,0 +1,33 @@
1
+ /** A tracked request: the awaitable to wait on, plus an optional interrupt to cancel it early. */ /**
2
+ * Tracks fire-and-forget requests so a shutdown path can interrupt any in-flight work and await it to
3
+ * settle. Each tracked request is a promise plus an optional interrupt callback; the entry removes itself
4
+ * from the bag once its promise settles, so the tracker only ever holds requests that are still in flight.
5
+ */ export class RequestsTracker {
6
+ requests = new Map();
7
+ nextId = 0;
8
+ /** Number of in-flight requests currently tracked. */ get size() {
9
+ return this.requests.size;
10
+ }
11
+ /**
12
+ * Tracks a fire-and-forget request. `interrupt`, when provided, is invoked by {@link interruptRequests}
13
+ * to cancel the in-flight work so its promise settles promptly. The entry auto-removes once `promise`
14
+ * settles, whether it resolves or rejects.
15
+ */ trackRequest(promise, interrupt) {
16
+ const id = this.nextId++;
17
+ const settled = promise.then(()=>this.requests.delete(id), ()=>this.requests.delete(id));
18
+ this.requests.set(id, {
19
+ promise: settled,
20
+ interrupt
21
+ });
22
+ }
23
+ /** Interrupts every in-flight request that was tracked with an interrupt callback. */ interruptRequests() {
24
+ for (const { interrupt } of this.requests.values()){
25
+ interrupt?.();
26
+ }
27
+ }
28
+ /** Awaits every currently in-flight request to settle. */ async awaitRequests() {
29
+ await Promise.all([
30
+ ...this.requests.values()
31
+ ].map((request)=>request.promise));
32
+ }
33
+ }