@aztec/sequencer-client 0.0.1-commit.d431d1c → 0.0.1-commit.d939eb5aa

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 (107) hide show
  1. package/dest/client/sequencer-client.d.ts +17 -7
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +63 -30
  4. package/dest/config.d.ts +26 -7
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +52 -38
  7. package/dest/global_variable_builder/fee_predictor.d.ts +37 -0
  8. package/dest/global_variable_builder/fee_predictor.d.ts.map +1 -0
  9. package/dest/global_variable_builder/fee_predictor.js +128 -0
  10. package/dest/global_variable_builder/fee_provider.d.ts +21 -0
  11. package/dest/global_variable_builder/fee_provider.d.ts.map +1 -0
  12. package/dest/global_variable_builder/fee_provider.js +58 -0
  13. package/dest/global_variable_builder/global_builder.d.ts +15 -16
  14. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  15. package/dest/global_variable_builder/global_builder.js +16 -50
  16. package/dest/global_variable_builder/index.d.ts +4 -2
  17. package/dest/global_variable_builder/index.d.ts.map +1 -1
  18. package/dest/global_variable_builder/index.js +2 -0
  19. package/dest/publisher/config.d.ts +47 -17
  20. package/dest/publisher/config.d.ts.map +1 -1
  21. package/dest/publisher/config.js +121 -42
  22. package/dest/publisher/index.d.ts +2 -1
  23. package/dest/publisher/index.d.ts.map +1 -1
  24. package/dest/publisher/l1_tx_failed_store/factory.d.ts +11 -0
  25. package/dest/publisher/l1_tx_failed_store/factory.d.ts.map +1 -0
  26. package/dest/publisher/l1_tx_failed_store/factory.js +22 -0
  27. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +58 -0
  28. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -0
  29. package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +1 -0
  30. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +15 -0
  31. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +1 -0
  32. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +34 -0
  33. package/dest/publisher/l1_tx_failed_store/index.d.ts +4 -0
  34. package/dest/publisher/l1_tx_failed_store/index.d.ts.map +1 -0
  35. package/dest/publisher/l1_tx_failed_store/index.js +2 -0
  36. package/dest/publisher/sequencer-publisher-factory.d.ts +11 -5
  37. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  38. package/dest/publisher/sequencer-publisher-factory.js +27 -3
  39. package/dest/publisher/sequencer-publisher-metrics.d.ts +1 -1
  40. package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
  41. package/dest/publisher/sequencer-publisher-metrics.js +12 -4
  42. package/dest/publisher/sequencer-publisher.d.ts +75 -49
  43. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  44. package/dest/publisher/sequencer-publisher.js +470 -156
  45. package/dest/sequencer/chain_state_overrides.d.ts +25 -0
  46. package/dest/sequencer/chain_state_overrides.d.ts.map +1 -0
  47. package/dest/sequencer/chain_state_overrides.js +39 -0
  48. package/dest/sequencer/checkpoint_proposal_job.d.ts +53 -13
  49. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  50. package/dest/sequencer/checkpoint_proposal_job.js +431 -235
  51. package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts +34 -0
  52. package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts.map +1 -0
  53. package/dest/sequencer/checkpoint_proposal_job_metrics.js +72 -0
  54. package/dest/sequencer/checkpoint_voter.d.ts +1 -2
  55. package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
  56. package/dest/sequencer/checkpoint_voter.js +2 -5
  57. package/dest/sequencer/events.d.ts +2 -1
  58. package/dest/sequencer/events.d.ts.map +1 -1
  59. package/dest/sequencer/metrics.d.ts +19 -8
  60. package/dest/sequencer/metrics.d.ts.map +1 -1
  61. package/dest/sequencer/metrics.js +120 -36
  62. package/dest/sequencer/sequencer.d.ts +45 -17
  63. package/dest/sequencer/sequencer.d.ts.map +1 -1
  64. package/dest/sequencer/sequencer.js +160 -94
  65. package/dest/sequencer/timetable.d.ts +17 -6
  66. package/dest/sequencer/timetable.d.ts.map +1 -1
  67. package/dest/sequencer/timetable.js +51 -46
  68. package/dest/sequencer/types.d.ts +2 -2
  69. package/dest/sequencer/types.d.ts.map +1 -1
  70. package/dest/test/index.d.ts +3 -5
  71. package/dest/test/index.d.ts.map +1 -1
  72. package/dest/test/mock_checkpoint_builder.d.ts +18 -15
  73. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  74. package/dest/test/mock_checkpoint_builder.js +63 -40
  75. package/dest/test/utils.d.ts +8 -8
  76. package/dest/test/utils.d.ts.map +1 -1
  77. package/dest/test/utils.js +10 -9
  78. package/package.json +27 -28
  79. package/src/client/sequencer-client.ts +81 -30
  80. package/src/config.ts +71 -49
  81. package/src/global_variable_builder/README.md +44 -0
  82. package/src/global_variable_builder/fee_predictor.ts +172 -0
  83. package/src/global_variable_builder/fee_provider.ts +75 -0
  84. package/src/global_variable_builder/global_builder.ts +27 -63
  85. package/src/global_variable_builder/index.ts +3 -1
  86. package/src/publisher/config.ts +157 -45
  87. package/src/publisher/index.ts +3 -0
  88. package/src/publisher/l1_tx_failed_store/factory.ts +32 -0
  89. package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +57 -0
  90. package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +46 -0
  91. package/src/publisher/l1_tx_failed_store/index.ts +3 -0
  92. package/src/publisher/sequencer-publisher-factory.ts +38 -9
  93. package/src/publisher/sequencer-publisher-metrics.ts +7 -3
  94. package/src/publisher/sequencer-publisher.ts +531 -214
  95. package/src/sequencer/README.md +83 -13
  96. package/src/sequencer/chain_state_overrides.ts +87 -0
  97. package/src/sequencer/checkpoint_proposal_job.ts +573 -277
  98. package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
  99. package/src/sequencer/checkpoint_voter.ts +1 -12
  100. package/src/sequencer/events.ts +1 -1
  101. package/src/sequencer/metrics.ts +136 -40
  102. package/src/sequencer/sequencer.ts +232 -107
  103. package/src/sequencer/timetable.ts +70 -57
  104. package/src/sequencer/types.ts +1 -1
  105. package/src/test/index.ts +2 -4
  106. package/src/test/mock_checkpoint_builder.ts +93 -65
  107. package/src/test/utils.ts +22 -13
@@ -0,0 +1,128 @@
1
+ import {
2
+ Attributes,
3
+ type Gauge,
4
+ type Histogram,
5
+ type Meter,
6
+ Metrics,
7
+ type TelemetryClient,
8
+ } from '@aztec/telemetry-client';
9
+
10
+ type CheckpointProposalJobInstruments = {
11
+ checkpointAttestationDelay: Histogram;
12
+ checkpointBuildDuration: Histogram;
13
+ checkpointStartToFirstBlockDuration: Histogram;
14
+ checkpointLastBlockToBroadcastDuration: Histogram;
15
+ pipelinedCheckpointBuildStartOffsetFromSlotBoundary: Histogram;
16
+ checkpointBlockCount: Gauge;
17
+ checkpointTxCount: Gauge;
18
+ checkpointTotalMana: Gauge;
19
+ };
20
+
21
+ /**
22
+ * Per-job recording surface used by {@link CheckpointProposalJob}.
23
+ *
24
+ * Ownership split:
25
+ * - {@link CheckpointProposalJobMetrics} owns the OpenTelemetry meter and instruments and should live for the
26
+ * lifetime of the sequencer process.
27
+ * - A recorder owns only mutable timing state for one checkpoint proposal job so overlapping jobs cannot
28
+ * overwrite each other's timing markers.
29
+ */
30
+ export interface CheckpointProposalJobMetricsRecorder {
31
+ recordCheckpointAttestationDelay(durationMs: number): void;
32
+ recordCheckpointBuild(durationMs: number, blockCount: number, txCount: number, totalMana: number): void;
33
+ recordPipelinedCheckpointBuildStartOffsetFromSlotBoundary(offsetMs: number): void;
34
+ startCheckpointTiming(nowMs: number): void;
35
+ noteCheckpointBlockBuilt(nowMs: number, opts: { isFirstBlock: boolean; isLastBlock: boolean }): void;
36
+ noteCheckpointBroadcast(nowMs: number): void;
37
+ }
38
+
39
+ /**
40
+ * Concrete per-job recorder.
41
+ *
42
+ * This class should be short-lived: create one recorder per checkpoint proposal job and discard it when the job
43
+ * completes. It intentionally does not create instruments; it only holds the job-local timestamps needed to derive
44
+ * timing metrics safely.
45
+ */
46
+ class CheckpointProposalJobMetricsRecorderImpl implements CheckpointProposalJobMetricsRecorder {
47
+ private checkpointStartedAt?: number;
48
+ private checkpointLastBlockBuiltAt?: number;
49
+
50
+ constructor(private readonly instruments: CheckpointProposalJobInstruments) {}
51
+
52
+ public recordCheckpointAttestationDelay(durationMs: number) {
53
+ this.instruments.checkpointAttestationDelay.record(Math.ceil(durationMs));
54
+ }
55
+
56
+ public recordCheckpointBuild(durationMs: number, blockCount: number, txCount: number, totalMana: number) {
57
+ this.instruments.checkpointBuildDuration.record(Math.ceil(durationMs));
58
+ this.instruments.checkpointBlockCount.record(blockCount);
59
+ this.instruments.checkpointTxCount.record(txCount);
60
+ this.instruments.checkpointTotalMana.record(totalMana);
61
+ }
62
+
63
+ public recordPipelinedCheckpointBuildStartOffsetFromSlotBoundary(offsetMs: number) {
64
+ this.instruments.pipelinedCheckpointBuildStartOffsetFromSlotBoundary.record(Math.ceil(Math.abs(offsetMs)), {
65
+ [Attributes.SLOT_BOUNDARY_SIDE]: offsetMs < 0 ? 'before' : 'after',
66
+ });
67
+ }
68
+
69
+ public startCheckpointTiming(nowMs: number) {
70
+ this.checkpointStartedAt = nowMs;
71
+ this.checkpointLastBlockBuiltAt = undefined;
72
+ }
73
+
74
+ public noteCheckpointBlockBuilt(nowMs: number, opts: { isFirstBlock: boolean; isLastBlock: boolean }) {
75
+ if (opts.isFirstBlock && this.checkpointStartedAt !== undefined) {
76
+ this.instruments.checkpointStartToFirstBlockDuration.record(Math.ceil(nowMs - this.checkpointStartedAt));
77
+ }
78
+
79
+ if (opts.isLastBlock) {
80
+ this.checkpointLastBlockBuiltAt = nowMs;
81
+ }
82
+ }
83
+
84
+ public noteCheckpointBroadcast(nowMs: number) {
85
+ if (this.checkpointLastBlockBuiltAt !== undefined) {
86
+ this.instruments.checkpointLastBlockToBroadcastDuration.record(
87
+ Math.ceil(nowMs - this.checkpointLastBlockBuiltAt),
88
+ );
89
+ }
90
+
91
+ this.checkpointLastBlockBuiltAt = undefined;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Long-lived owner of checkpoint proposal job telemetry instruments.
97
+ *
98
+ * The sequencer should construct this once and reuse it across jobs. Each call to {@link createRecorder} returns a
99
+ * lightweight recorder with isolated mutable state for a single checkpoint proposal job.
100
+ */
101
+ export class CheckpointProposalJobMetrics {
102
+ private readonly meter: Meter;
103
+ private readonly instruments: CheckpointProposalJobInstruments;
104
+
105
+ constructor(client: TelemetryClient, name = 'CheckpointProposalJob') {
106
+ this.meter = client.getMeter(name);
107
+ this.instruments = {
108
+ checkpointAttestationDelay: this.meter.createHistogram(Metrics.SEQUENCER_CHECKPOINT_ATTESTATION_DELAY),
109
+ checkpointBuildDuration: this.meter.createHistogram(Metrics.SEQUENCER_CHECKPOINT_BUILD_DURATION),
110
+ checkpointStartToFirstBlockDuration: this.meter.createHistogram(
111
+ Metrics.SEQUENCER_CHECKPOINT_START_TO_FIRST_BLOCK_DURATION,
112
+ ),
113
+ checkpointLastBlockToBroadcastDuration: this.meter.createHistogram(
114
+ Metrics.SEQUENCER_CHECKPOINT_LAST_BLOCK_TO_BROADCAST_DURATION,
115
+ ),
116
+ pipelinedCheckpointBuildStartOffsetFromSlotBoundary: this.meter.createHistogram(
117
+ Metrics.SEQUENCER_PIPELINED_CHECKPOINT_BUILD_START_OFFSET_FROM_SLOT_BOUNDARY,
118
+ ),
119
+ checkpointBlockCount: this.meter.createGauge(Metrics.SEQUENCER_CHECKPOINT_BLOCK_COUNT),
120
+ checkpointTxCount: this.meter.createGauge(Metrics.SEQUENCER_CHECKPOINT_TX_COUNT),
121
+ checkpointTotalMana: this.meter.createGauge(Metrics.SEQUENCER_CHECKPOINT_TOTAL_MANA),
122
+ };
123
+ }
124
+
125
+ public createRecorder(): CheckpointProposalJobMetricsRecorder {
126
+ return new CheckpointProposalJobMetricsRecorderImpl(this.instruments);
127
+ }
128
+ }
@@ -2,7 +2,6 @@ import type { SlotNumber } from '@aztec/foundation/branded-types';
2
2
  import type { EthAddress } from '@aztec/foundation/eth-address';
3
3
  import type { Logger } from '@aztec/foundation/log';
4
4
  import type { SlasherClientInterface } from '@aztec/slasher';
5
- import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
6
5
  import type { ResolvedSequencerConfig } from '@aztec/stdlib/interfaces/server';
7
6
  import type { ValidatorClient } from '@aztec/validator-client';
8
7
  import { DutyAlreadySignedError } from '@aztec/validator-ha-signer/errors';
@@ -18,7 +17,6 @@ import type { SequencerRollupConstants } from './types.js';
18
17
  * Handles governance and slashing voting for a given slot.
19
18
  */
20
19
  export class CheckpointVoter {
21
- private slotTimestamp: bigint;
22
20
  private governanceSigner: (msg: TypedDataDefinition) => Promise<`0x${string}`>;
23
21
  private slashingSigner: (msg: TypedDataDefinition) => Promise<`0x${string}`>;
24
22
 
@@ -33,8 +31,6 @@ export class CheckpointVoter {
33
31
  private readonly metrics: SequencerMetrics,
34
32
  private readonly log: Logger,
35
33
  ) {
36
- this.slotTimestamp = getTimestampForSlot(this.slot, this.l1Constants);
37
-
38
34
  // Create separate signers with appropriate duty contexts for governance and slashing votes
39
35
  // These use HA protection to ensure only one node signs per slot/duty
40
36
  const governanceContext: SigningContext = { slot: this.slot, dutyType: DutyType.GOVERNANCE_VOTE };
@@ -77,7 +73,6 @@ export class CheckpointVoter {
77
73
  return await this.publisher.enqueueGovernanceCastSignal(
78
74
  governanceProposerPayload,
79
75
  this.slot,
80
- this.slotTimestamp,
81
76
  this.attestorAddress,
82
77
  this.governanceSigner,
83
78
  );
@@ -108,13 +103,7 @@ export class CheckpointVoter {
108
103
 
109
104
  this.metrics.recordSlashingAttempt(actions.length);
110
105
 
111
- return await this.publisher.enqueueSlashingActions(
112
- actions,
113
- this.slot,
114
- this.slotTimestamp,
115
- this.attestorAddress,
116
- this.slashingSigner,
117
- );
106
+ return await this.publisher.enqueueSlashingActions(actions, this.slot, this.attestorAddress, this.slashingSigner);
118
107
  } catch (err) {
119
108
  if (err instanceof DutyAlreadySignedError) {
120
109
  this.log.info(`Slashing vote already signed by another node`, {
@@ -13,7 +13,7 @@ export type SequencerEvents = {
13
13
  ['proposer-rollup-check-failed']: (args: { reason: string; slot: SlotNumber }) => void;
14
14
  ['block-tx-count-check-failed']: (args: { minTxs: number; availableTxs: number; slot: SlotNumber }) => void;
15
15
  ['block-build-failed']: (args: { reason: string; slot: SlotNumber }) => void;
16
- ['block-proposed']: (args: { blockNumber: BlockNumber; slot: SlotNumber }) => void;
16
+ ['block-proposed']: (args: { blockNumber: BlockNumber; slot: SlotNumber; buildSlot: SlotNumber }) => void;
17
17
  ['checkpoint-empty']: (args: { slot: SlotNumber }) => void;
18
18
  ['checkpoint-publish-failed']: (args: {
19
19
  slot: SlotNumber;
@@ -11,13 +11,13 @@ import {
11
11
  type TelemetryClient,
12
12
  type Tracer,
13
13
  type UpDownCounter,
14
+ createUpDownCounterWithDefault,
14
15
  } from '@aztec/telemetry-client';
15
16
 
16
17
  import { type Hex, formatUnits } from 'viem';
17
18
 
18
19
  import type { SequencerState } from './utils.js';
19
20
 
20
- // TODO(palla/mbps): Review all metrics and add any missing ones per checkpoint
21
21
  export class SequencerMetrics {
22
22
  public readonly tracer: Tracer;
23
23
  private meter: Meter;
@@ -39,17 +39,23 @@ export class SequencerMetrics {
39
39
  private filledSlots: UpDownCounter;
40
40
 
41
41
  private blockProposalFailed: UpDownCounter;
42
- private blockProposalSuccess: UpDownCounter;
43
- private blockProposalPrecheckFailed: UpDownCounter;
42
+ private checkpointProposalSuccess: UpDownCounter;
43
+ private checkpointPrecheckFailed: UpDownCounter;
44
+ private checkpointProposalFailed: UpDownCounter;
44
45
  private checkpointSuccess: UpDownCounter;
45
46
  private slashingAttempts: UpDownCounter;
46
- private checkpointAttestationDelay: Histogram;
47
+ private pipelineDepth: Gauge;
48
+ private pipelineDiscards: UpDownCounter;
47
49
 
48
50
  // Fisherman fee analysis metrics
49
51
  private fishermanWouldBeIncluded: UpDownCounter;
50
52
  private fishermanTimeBeforeBlock: Histogram;
51
53
  private fishermanPendingBlobTxCount: Histogram;
52
54
  private fishermanIncludedBlobTxCount: Histogram;
55
+ private fishermanPendingBlobCount: Histogram;
56
+ private fishermanIncludedBlobCount: Histogram;
57
+ private fishermanBlockBlobsFull: UpDownCounter;
58
+ private fishermanMaxBlobCapacity: Histogram;
53
59
  private fishermanCalculatedPriorityFee: Histogram;
54
60
  private fishermanPriorityFeeDelta: Histogram;
55
61
  private fishermanEstimatedCost: Histogram;
@@ -57,6 +63,10 @@ export class SequencerMetrics {
57
63
  private fishermanMinedBlobTxPriorityFee: Histogram;
58
64
  private fishermanMinedBlobTxTotalCost: Histogram;
59
65
 
66
+ private blockInterBlockTime: Histogram;
67
+ private lastBlockBuiltTimestamp?: number;
68
+ private lastBlockBuiltSlot?: SlotNumber;
69
+
60
70
  private lastSeenSlot?: SlotNumber;
61
71
 
62
72
  constructor(
@@ -67,33 +77,27 @@ export class SequencerMetrics {
67
77
  this.meter = client.getMeter(name);
68
78
  this.tracer = client.getTracer(name);
69
79
 
70
- this.blockCounter = this.meter.createUpDownCounter(Metrics.SEQUENCER_BLOCK_COUNT);
80
+ this.blockCounter = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_BLOCK_COUNT, {
81
+ [Attributes.STATUS]: ['failed', 'built'],
82
+ });
71
83
 
72
84
  this.blockBuildDuration = this.meter.createHistogram(Metrics.SEQUENCER_BLOCK_BUILD_DURATION);
73
85
 
74
86
  this.blockBuildManaPerSecond = this.meter.createGauge(Metrics.SEQUENCER_BLOCK_BUILD_MANA_PER_SECOND);
75
87
 
76
- this.stateTransitionBufferDuration = this.meter.createHistogram(Metrics.SEQUENCER_STATE_TRANSITION_BUFFER_DURATION);
88
+ this.blockInterBlockTime = this.meter.createHistogram(Metrics.SEQUENCER_BLOCK_INTER_BLOCK_TIME);
77
89
 
78
- this.checkpointAttestationDelay = this.meter.createHistogram(Metrics.SEQUENCER_CHECKPOINT_ATTESTATION_DELAY);
79
-
80
- // Init gauges and counters
81
- this.blockCounter.add(0, {
82
- [Attributes.STATUS]: 'failed',
83
- });
84
- this.blockCounter.add(0, {
85
- [Attributes.STATUS]: 'built',
86
- });
90
+ this.stateTransitionBufferDuration = this.meter.createHistogram(Metrics.SEQUENCER_STATE_TRANSITION_BUFFER_DURATION);
87
91
 
88
- this.rewards = this.meter.createGauge(Metrics.SEQUENCER_CURRENT_BLOCK_REWARDS);
92
+ this.rewards = this.meter.createGauge(Metrics.SEQUENCER_CURRENT_SLOT_REWARDS);
89
93
 
90
- this.slots = this.meter.createUpDownCounter(Metrics.SEQUENCER_SLOT_COUNT);
94
+ this.slots = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_SLOT_COUNT);
91
95
 
92
96
  /**
93
97
  * NOTE: we do not track missed slots as a separate metric. That would be difficult to determine
94
98
  * Instead, use a computed metric, `slots - filledSlots` to get the number of slots a sequencer has missed.
95
99
  */
96
- this.filledSlots = this.meter.createUpDownCounter(Metrics.SEQUENCER_FILLED_SLOT_COUNT);
100
+ this.filledSlots = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_FILLED_SLOT_COUNT);
97
101
 
98
102
  this.timeToCollectAttestations = this.meter.createGauge(Metrics.SEQUENCER_COLLECT_ATTESTATIONS_DURATION);
99
103
 
@@ -103,20 +107,51 @@ export class SequencerMetrics {
103
107
 
104
108
  this.collectedAttestions = this.meter.createGauge(Metrics.SEQUENCER_COLLECTED_ATTESTATIONS_COUNT);
105
109
 
106
- this.blockProposalFailed = this.meter.createUpDownCounter(Metrics.SEQUENCER_BLOCK_PROPOSAL_FAILED_COUNT);
110
+ this.blockProposalFailed = createUpDownCounterWithDefault(
111
+ this.meter,
112
+ Metrics.SEQUENCER_BLOCK_PROPOSAL_FAILED_COUNT,
113
+ );
107
114
 
108
- this.blockProposalSuccess = this.meter.createUpDownCounter(Metrics.SEQUENCER_BLOCK_PROPOSAL_SUCCESS_COUNT);
115
+ this.checkpointProposalSuccess = createUpDownCounterWithDefault(
116
+ this.meter,
117
+ Metrics.SEQUENCER_CHECKPOINT_PROPOSAL_SUCCESS_COUNT,
118
+ );
109
119
 
110
- this.checkpointSuccess = this.meter.createUpDownCounter(Metrics.SEQUENCER_CHECKPOINT_SUCCESS_COUNT);
120
+ this.checkpointSuccess = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_CHECKPOINT_SUCCESS_COUNT);
121
+
122
+ this.checkpointPrecheckFailed = createUpDownCounterWithDefault(
123
+ this.meter,
124
+ Metrics.SEQUENCER_CHECKPOINT_PRECHECK_FAILED_COUNT,
125
+ {
126
+ [Attributes.ERROR_TYPE]: [
127
+ 'slot_already_taken',
128
+ 'rollup_contract_check_failed',
129
+ 'slot_mismatch',
130
+ 'block_number_mismatch',
131
+ ],
132
+ },
133
+ );
111
134
 
112
- this.blockProposalPrecheckFailed = this.meter.createUpDownCounter(
113
- Metrics.SEQUENCER_BLOCK_PROPOSAL_PRECHECK_FAILED_COUNT,
135
+ this.checkpointProposalFailed = createUpDownCounterWithDefault(
136
+ this.meter,
137
+ Metrics.SEQUENCER_CHECKPOINT_PROPOSAL_FAILED_COUNT,
114
138
  );
115
139
 
116
- this.slashingAttempts = this.meter.createUpDownCounter(Metrics.SEQUENCER_SLASHING_ATTEMPTS_COUNT);
140
+ this.slashingAttempts = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_SLASHING_ATTEMPTS_COUNT);
141
+
142
+ this.pipelineDepth = this.meter.createGauge(Metrics.SEQUENCER_PIPELINE_DEPTH);
143
+ this.pipelineDiscards = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_PIPELINE_DISCARDS_COUNT);
144
+ this.pipelineDepth.record(0);
117
145
 
118
146
  // Fisherman fee analysis metrics
119
- this.fishermanWouldBeIncluded = this.meter.createUpDownCounter(Metrics.FISHERMAN_FEE_ANALYSIS_WOULD_BE_INCLUDED);
147
+ this.fishermanWouldBeIncluded = createUpDownCounterWithDefault(
148
+ this.meter,
149
+ Metrics.FISHERMAN_FEE_ANALYSIS_WOULD_BE_INCLUDED,
150
+ {
151
+ [Attributes.OK]: [true, false],
152
+ [Attributes.BLOCK_FULL]: ['true', 'false'],
153
+ },
154
+ );
120
155
 
121
156
  this.fishermanTimeBeforeBlock = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_TIME_BEFORE_BLOCK);
122
157
 
@@ -145,6 +180,20 @@ export class SequencerMetrics {
145
180
  this.fishermanMinedBlobTxTotalCost = this.meter.createHistogram(
146
181
  Metrics.FISHERMAN_FEE_ANALYSIS_MINED_BLOB_TX_TOTAL_COST,
147
182
  );
183
+
184
+ this.fishermanPendingBlobCount = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_PENDING_BLOB_COUNT);
185
+
186
+ this.fishermanIncludedBlobCount = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_INCLUDED_BLOB_COUNT);
187
+
188
+ this.fishermanBlockBlobsFull = createUpDownCounterWithDefault(
189
+ this.meter,
190
+ Metrics.FISHERMAN_FEE_ANALYSIS_BLOCK_BLOBS_FULL,
191
+ {
192
+ [Attributes.OK]: [true, false],
193
+ },
194
+ );
195
+
196
+ this.fishermanMaxBlobCapacity = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_MAX_BLOB_CAPACITY);
148
197
  }
149
198
 
150
199
  public recordRequiredAttestations(requiredAttestationsCount: number, allowanceMs: number) {
@@ -156,21 +205,25 @@ export class SequencerMetrics {
156
205
  this.timeToCollectAttestations.record(0);
157
206
  }
158
207
 
159
- public recordCheckpointAttestationDelay(duration: number) {
160
- this.checkpointAttestationDelay.record(duration);
161
- }
162
-
163
208
  public recordCollectedAttestations(count: number, durationMs: number) {
164
209
  this.collectedAttestions.record(count);
165
210
  this.timeToCollectAttestations.record(Math.ceil(durationMs));
166
211
  }
167
212
 
168
- recordBuiltBlock(buildDurationMs: number, totalMana: number) {
213
+ recordBuiltBlock(buildDurationMs: number, totalMana: number, slot: SlotNumber) {
169
214
  this.blockCounter.add(1, {
170
215
  [Attributes.STATUS]: 'built',
171
216
  });
172
217
  this.blockBuildDuration.record(Math.ceil(buildDurationMs));
173
218
  this.blockBuildManaPerSecond.record(Math.ceil((totalMana * 1000) / buildDurationMs));
219
+
220
+ // Only record inter-block time between blocks built within the same slot.
221
+ const now = Date.now();
222
+ if (this.lastBlockBuiltTimestamp !== undefined && this.lastBlockBuiltSlot === slot) {
223
+ this.blockInterBlockTime.record(now - this.lastBlockBuiltTimestamp);
224
+ }
225
+ this.lastBlockBuiltTimestamp = now;
226
+ this.lastBlockBuiltSlot = slot;
174
227
  }
175
228
 
176
229
  recordFailedBlock() {
@@ -185,6 +238,14 @@ export class SequencerMetrics {
185
238
  });
186
239
  }
187
240
 
241
+ recordPipelineDepth(depth: number) {
242
+ this.pipelineDepth.record(depth);
243
+ }
244
+
245
+ recordPipelineDiscard(count = 1) {
246
+ this.pipelineDiscards.add(count);
247
+ }
248
+
188
249
  incOpenSlot(slot: SlotNumber, proposer: string) {
189
250
  // sequencer went through the loop a second time. Noop
190
251
  if (slot === this.lastSeenSlot) {
@@ -227,13 +288,19 @@ export class SequencerMetrics {
227
288
  });
228
289
  }
229
290
 
230
- recordBlockProposalSuccess() {
231
- this.blockProposalSuccess.add(1);
291
+ recordCheckpointProposalSuccess() {
292
+ this.checkpointProposalSuccess.add(1);
232
293
  }
233
294
 
234
- recordBlockProposalPrecheckFailed(checkType: string) {
235
- this.blockProposalPrecheckFailed.add(1, {
236
- [Attributes.ERROR_TYPE]: checkType,
295
+ recordCheckpointPrecheckFailed(
296
+ checkType: 'slot_already_taken' | 'rollup_contract_check_failed' | 'slot_mismatch' | 'block_number_mismatch',
297
+ ) {
298
+ this.checkpointPrecheckFailed.add(1, { [Attributes.ERROR_TYPE]: checkType });
299
+ }
300
+
301
+ recordCheckpointProposalFailed(reason?: string) {
302
+ this.checkpointProposalFailed.add(1, {
303
+ ...(reason && { [Attributes.ERROR_TYPE]: reason }),
237
304
  });
238
305
  }
239
306
 
@@ -263,10 +330,12 @@ export class SequencerMetrics {
263
330
 
264
331
  // Record pending block snapshot data (once per strategy for comparison)
265
332
  this.fishermanPendingBlobTxCount.record(analysis.pendingSnapshot.pendingBlobTxCount, strategyAttributes);
333
+ this.fishermanPendingBlobCount.record(analysis.pendingSnapshot.pendingBlobCount, strategyAttributes);
266
334
 
267
335
  // Record mined block data if available
268
336
  if (analysis.minedBlock) {
269
337
  this.fishermanIncludedBlobTxCount.record(analysis.minedBlock.includedBlobTxCount, strategyAttributes);
338
+ this.fishermanIncludedBlobCount.record(analysis.minedBlock.includedBlobCount, strategyAttributes);
270
339
 
271
340
  // Record actual fees from blob transactions in the mined block
272
341
  for (const blobTx of analysis.minedBlock.includedBlobTxs) {
@@ -300,13 +369,28 @@ export class SequencerMetrics {
300
369
  if (analysis.analysis) {
301
370
  this.fishermanTimeBeforeBlock.record(Math.ceil(analysis.analysis.timeBeforeBlockMs), strategyAttributes);
302
371
 
372
+ // Record whether the block reached 100% blob capacity
373
+ if (analysis.analysis.blockBlobsFull) {
374
+ this.fishermanBlockBlobsFull.add(1, { ...strategyAttributes, [Attributes.OK]: true });
375
+ } else {
376
+ this.fishermanBlockBlobsFull.add(1, { ...strategyAttributes, [Attributes.OK]: false });
377
+ }
378
+
379
+ // Record the max blob capacity for this block
380
+ this.fishermanMaxBlobCapacity.record(analysis.analysis.maxBlobCapacity, strategyAttributes);
381
+
303
382
  // Record strategy-specific inclusion result
304
383
  if (strategyResult.wouldBeIncluded !== undefined) {
384
+ const inclusionAttributes = {
385
+ ...strategyAttributes,
386
+ [Attributes.BLOCK_FULL]: analysis.analysis.blockBlobsFull ? 'true' : 'false',
387
+ };
388
+
305
389
  if (strategyResult.wouldBeIncluded) {
306
- this.fishermanWouldBeIncluded.add(1, { ...strategyAttributes, [Attributes.OK]: true });
390
+ this.fishermanWouldBeIncluded.add(1, { ...inclusionAttributes, [Attributes.OK]: true });
307
391
  } else {
308
392
  this.fishermanWouldBeIncluded.add(1, {
309
- ...strategyAttributes,
393
+ ...inclusionAttributes,
310
394
  [Attributes.OK]: false,
311
395
  ...(strategyResult.exclusionReason && { [Attributes.ERROR_TYPE]: strategyResult.exclusionReason }),
312
396
  });
@@ -316,17 +400,29 @@ export class SequencerMetrics {
316
400
  // Record strategy-specific priority fee delta
317
401
  if (strategyResult.priorityFeeDelta !== undefined) {
318
402
  const priorityFeeDeltaGwei = Number(strategyResult.priorityFeeDelta) / 1e9;
319
- this.fishermanPriorityFeeDelta.record(priorityFeeDeltaGwei, strategyAttributes);
403
+ const deltaAttributes = {
404
+ ...strategyAttributes,
405
+ [Attributes.BLOCK_FULL]: analysis.analysis.blockBlobsFull ? 'true' : 'false',
406
+ };
407
+ this.fishermanPriorityFeeDelta.record(priorityFeeDeltaGwei, deltaAttributes);
320
408
  }
321
409
 
322
410
  // Record estimated cost if available
323
411
  if (strategyResult.estimatedCostEth !== undefined) {
324
- this.fishermanEstimatedCost.record(strategyResult.estimatedCostEth, strategyAttributes);
412
+ const costAttributes = {
413
+ ...strategyAttributes,
414
+ [Attributes.BLOCK_FULL]: analysis.analysis.blockBlobsFull ? 'true' : 'false',
415
+ };
416
+ this.fishermanEstimatedCost.record(strategyResult.estimatedCostEth, costAttributes);
325
417
  }
326
418
 
327
419
  // Record estimated overpayment if available
328
420
  if (strategyResult.estimatedOverpaymentEth !== undefined) {
329
- this.fishermanEstimatedOverpayment.record(strategyResult.estimatedOverpaymentEth, strategyAttributes);
421
+ const overpaymentAttributes = {
422
+ ...strategyAttributes,
423
+ [Attributes.BLOCK_FULL]: analysis.analysis.blockBlobsFull ? 'true' : 'false',
424
+ };
425
+ this.fishermanEstimatedOverpayment.record(strategyResult.estimatedOverpaymentEth, overpaymentAttributes);
330
426
  }
331
427
  }
332
428
  }