@aztec-labs/aztec-node 6.0.0-nightly.20261021 → 6.0.0-nightly.20261022

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.
@@ -8,10 +8,27 @@ import type { SlasherConfig } from '@aztec-labs/slasher/config';
8
8
  import { type L2BlockSource } from '@aztec-labs/stdlib/block';
9
9
  import type { CheckpointReexecutionTracker } from '@aztec-labs/stdlib/checkpoint';
10
10
  import type { ChainConfig } from '@aztec-labs/stdlib/config';
11
+ import { type CheckpointAttestation } from '@aztec-labs/stdlib/p2p';
11
12
  import type { SingleValidatorStats, ValidatorStats, ValidatorStatusHistory, ValidatorStatusInSlot, ValidatorStatusType, ValidatorsEpochPerformance, ValidatorsStats } from '@aztec-labs/stdlib/validators';
12
13
  import type { SentinelConfig } from './config.js';
13
14
  import { SentinelStore } from './store.js';
14
15
  export type SentinelRuntimeConfig = Pick<SlasherConfig, 'slashInactivityTargetPercentage' | 'slashInactivityPenalty' | 'slashInactivityConsecutiveEpochThreshold'> & Pick<SentinelConfig, 'sentinelEpochEndBufferSlots'> & Pick<ChainConfig, 'l1ChainId' | 'rollupAddress'>;
16
+ /**
17
+ * Why a valid checkpoint proposal that never reached L1 could not have landed, regardless of its proposer.
18
+ * `unexpected-parent-appeared` matches the reason the sequencer gives when it discards its own pipelined checkpoint
19
+ * for the same cause.
20
+ */
21
+ type OrphanedProposalReason = 'parent-not-on-l1' | 'parent-hash-mismatch' | 'unexpected-parent-appeared';
22
+ /** Refined status of a valid checkpoint proposal for a slot whose checkpoint never reached L1. */
23
+ type UnpublishedProposalClassification = {
24
+ status: 'checkpoint-unpublished';
25
+ } | {
26
+ status: 'checkpoint-orphaned';
27
+ reason: OrphanedProposalReason;
28
+ } | {
29
+ status: 'checkpoint-valid';
30
+ reason: 'equivocation' | 'no-proposal-data' | 'below-quorum' | 'unexpected-parent-appeared';
31
+ };
15
32
  declare const Sentinel_base: new () => WatcherEmitter;
16
33
  /**
17
34
  * The Sentinel observes validator behaviour every L2 slot, classifies it into a per-slot status,
@@ -42,12 +59,19 @@ declare const Sentinel_base: new () => WatcherEmitter;
42
59
  * ## Six-case taxonomy in `getSlotActivity`
43
60
  *
44
61
  * For each slot, the sentinel assigns the proposer one of six statuses, ranked highest-confidence
45
- * first:
62
+ * first, with case 5 refined into 5a and 5b:
46
63
  *
47
64
  * - `checkpoint-mined` — a checkpoint covering this slot has landed on L1
48
65
  * (fetched on demand via `archiver.getCheckpoint({ slot })`).
66
+ * - `checkpoint-unpublished` — (5a) a valid checkpoint proposal built on a parent that is on L1
67
+ * reached quorum, yet the proposer did not land it.
68
+ * - `checkpoint-orphaned` — (5b) a valid checkpoint proposal could not land because the parent
69
+ * it built on never reached L1 (the proposer pipelining cascade), or
70
+ * because an earlier slot's checkpoint landed with its number after it
71
+ * had built on the previous one.
49
72
  * - `checkpoint-valid` — the local node re-executed a checkpoint proposal for this slot
50
- * successfully (consulted via `CheckpointReexecutionTracker`).
73
+ * successfully (consulted via `CheckpointReexecutionTracker`), and it
74
+ * could not be refined into 5a or 5b.
51
75
  * - `checkpoint-invalid` — the local node re-executed a checkpoint proposal for this slot
52
76
  * and rejected it (e.g. header/archive/out-hash mismatch, limit
53
77
  * breach). Proposer-fault.
@@ -57,9 +81,15 @@ declare const Sentinel_base: new () => WatcherEmitter;
57
81
  * - `checkpoint-missed` — block proposals seen on P2P but no checkpoint proposal at all.
58
82
  * - `blocks-missed` — no block proposals seen for this slot.
59
83
  *
60
- * Missing-attestor faults are recorded only in `checkpoint-mined` and `checkpoint-valid`, where
61
- * the local node has positive evidence the checkpoint was canonical or valid. In the other four
62
- * cases the proposer is at fault and no attestor penalty applies.
84
+ * Missing-attestor faults are recorded only in `checkpoint-mined` and the three valid-proposal
85
+ * statuses (`checkpoint-unpublished`, `checkpoint-orphaned`, `checkpoint-valid`), where the local
86
+ * node has positive evidence the checkpoint was canonical or valid. In the other four cases the
87
+ * proposer is at fault and no attestor penalty applies. A valid proposal that conflicts with a checkpoint
88
+ * that landed on L1, in its parent's position or its own, is also exempt: a validator holding the landed
89
+ * checkpoint's blocks rightly refuses it.
90
+ *
91
+ * `checkpoint-unpublished` and `checkpoint-orphaned` are neutral for the proposer, like
92
+ * `checkpoint-valid`: neither counts toward `missedProposals` or inactivity slashing.
63
93
  *
64
94
  * ## Re-execution tracker
65
95
  *
@@ -176,8 +206,12 @@ export declare class Sentinel extends Sentinel_base implements Watcher {
176
206
  *
177
207
  * Proposer status:
178
208
  * - case 6 `checkpoint-mined` — a checkpoint covering this slot has landed on L1.
209
+ * - case 5a `checkpoint-unpublished` — a valid proposal with its parent on L1 reached quorum but
210
+ * was not landed by the proposer.
211
+ * - case 5b `checkpoint-orphaned` — a valid proposal could not land because its parent never
212
+ * reached L1, or because an earlier slot took its checkpoint number.
179
213
  * - case 5 `checkpoint-valid` — the local node re-executed a checkpoint proposal for this
180
- * slot successfully.
214
+ * slot successfully, and it could not be refined into 5a or 5b.
181
215
  * - case 4 `checkpoint-invalid` — the local node re-executed a checkpoint proposal for this
182
216
  * slot and rejected it.
183
217
  * - case 3 `checkpoint-unvalidated` — the local node observed a checkpoint proposal for this
@@ -185,12 +219,33 @@ export declare class Sentinel extends Sentinel_base implements Watcher {
185
219
  * - case 2 `checkpoint-missed` — block proposals seen on P2P but no checkpoint proposal.
186
220
  * - case 1 `blocks-missed` — no block proposals seen for this slot.
187
221
  *
188
- * Missing-attestor penalties apply only in cases 5 and 6, where the local node has positive
189
- * evidence the checkpoint was valid or has been canonicalised on L1.
222
+ * Missing-attestor penalties apply only in cases 5 (including 5a and 5b) and 6, where the local
223
+ * node has positive evidence the checkpoint was valid or has been canonicalised on L1, and never when
224
+ * the proposal conflicts with a checkpoint that landed in its parent's position or its own.
190
225
  */
191
226
  protected getSlotActivity(slot: SlotNumber, epoch: EpochNumber, proposer: EthAddress, committee: EthAddress[]): Promise<{
192
227
  [k: string]: ValidatorStatusInSlot | undefined;
193
228
  }>;
229
+ /**
230
+ * Refines case 5 (a checkpoint proposal for the slot re-executed as valid, with no checkpoint for the slot on L1)
231
+ * into whose fault it was that the proposal did not land:
232
+ *
233
+ * - `checkpoint-orphaned` (5b) when other proposers prevented it: the parent it built on never landed on L1 (it is
234
+ * missing, or a different checkpoint holds the parent's position), or, with a quorum of attestations, an earlier
235
+ * slot's checkpoint landed with its number after it built on the previous one (`unexpected-parent-appeared`).
236
+ * The latter happens when the earlier slot's proposal reached this proposer too late to build on.
237
+ * - `checkpoint-unpublished` (5a) when its parent is on L1 and a quorum of the committee attested, so the proposer
238
+ * had everything it needed to publish. A later slot that lands the same checkpoint number does not exempt it:
239
+ * that slot only got the number because this proposer failed to land it.
240
+ * - `checkpoint-valid` (5) otherwise: the slot had an equivocation, the tracker has no parent or payload data to
241
+ * judge by, or attestations fell short of quorum. Short of quorum is a fault of the attestors, who are tagged
242
+ * `attestation-missed`, except after an unexpected parent appeared (`unexpected-parent-appeared`), when
243
+ * refusing to attest was correct.
244
+ *
245
+ * Only attestations for the proposal's own payload count toward quorum, since those are the only ones its proposer
246
+ * could have published.
247
+ */
248
+ protected classifyUnpublishedProposal(slot: SlotNumber, proposer: EthAddress, committee: EthAddress[], p2pAttested: CheckpointAttestation[]): Promise<UnpublishedProposalClassification>;
194
249
  /** Push the status for each slot for each validator. */
195
250
  protected updateValidators(slot: SlotNumber, stats: Record<`0x${string}`, ValidatorStatusInSlot | undefined>): Promise<void>;
196
251
  /** Computes stats to be returned based on stored data. */
@@ -217,4 +272,4 @@ export declare class Sentinel extends Sentinel_base implements Watcher {
217
272
  } | undefined;
218
273
  }
219
274
  export {};
220
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VudGluZWwuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZW50aW5lbC9zZW50aW5lbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsS0FBSyxVQUFVLEVBQTBCLE1BQU0seUJBQXlCLENBQUM7QUFDbEYsT0FBTyxFQUNMLGdCQUFnQixFQUNoQixzQkFBc0IsRUFDdEIsV0FBVyxFQUNYLFVBQVUsRUFDWCxNQUFNLHNDQUFzQyxDQUFDO0FBRTlDLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxvQ0FBb0MsQ0FBQztBQUVoRSxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sd0NBQXdDLENBQUM7QUFDeEUsT0FBTyxLQUFLLEVBQUUsU0FBUyxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDakQsT0FBTyxFQUlMLEtBQUssT0FBTyxFQUNaLEtBQUssY0FBYyxFQUVwQixNQUFNLHFCQUFxQixDQUFDO0FBQzdCLE9BQU8sS0FBSyxFQUFFLGFBQWEsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQ2hFLE9BQU8sRUFBRSxLQUFLLGFBQWEsRUFBNkMsTUFBTSwwQkFBMEIsQ0FBQztBQUN6RyxPQUFPLEtBQUssRUFBRSw0QkFBNEIsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBQ2xGLE9BQU8sS0FBSyxFQUFFLFdBQVcsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBRzdELE9BQU8sS0FBSyxFQUNWLG9CQUFvQixFQUNwQixjQUFjLEVBQ2Qsc0JBQXNCLEVBQ3RCLHFCQUFxQixFQUNyQixtQkFBbUIsRUFDbkIsMEJBQTBCLEVBQzFCLGVBQWUsRUFDaEIsTUFBTSwrQkFBK0IsQ0FBQztBQUd2QyxPQUFPLEtBQUssRUFBRSxjQUFjLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDbEQsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUUzQyxNQUFNLE1BQU0scUJBQXFCLEdBQUcsSUFBSSxDQUN0QyxhQUFhLEVBQ2Is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
275
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VudGluZWwuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZW50aW5lbC9zZW50aW5lbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQUUsS0FBSyxVQUFVLEVBQTBCLE1BQU0seUJBQXlCLENBQUM7QUFDbEYsT0FBTyxFQUNMLGdCQUFnQixFQUNoQixzQkFBc0IsRUFDdEIsV0FBVyxFQUNYLFVBQVUsRUFDWCxNQUFNLHNDQUFzQyxDQUFDO0FBRTlDLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxvQ0FBb0MsQ0FBQztBQUVoRSxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sd0NBQXdDLENBQUM7QUFDeEUsT0FBTyxLQUFLLEVBQUUsU0FBUyxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDakQsT0FBTyxFQUlMLEtBQUssT0FBTyxFQUNaLEtBQUssY0FBYyxFQUVwQixNQUFNLHFCQUFxQixDQUFDO0FBQzdCLE9BQU8sS0FBSyxFQUFFLGFBQWEsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQ2hFLE9BQU8sRUFBRSxLQUFLLGFBQWEsRUFBNkMsTUFBTSwwQkFBMEIsQ0FBQztBQUN6RyxPQUFPLEtBQUssRUFBRSw0QkFBNEIsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBQ2xGLE9BQU8sS0FBSyxFQUFFLFdBQVcsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBTzdELE9BQU8sRUFDTCxLQUFLLHFCQUFxQixFQUczQixNQUFNLHdCQUF3QixDQUFDO0FBQ2hDLE9BQU8sS0FBSyxFQUNWLG9CQUFvQixFQUNwQixjQUFjLEVBQ2Qsc0JBQXNCLEVBQ3RCLHFCQUFxQixFQUNyQixtQkFBbUIsRUFDbkIsMEJBQTBCLEVBQzFCLGVBQWUsRUFDaEIsTUFBTSwrQkFBK0IsQ0FBQztBQUd2QyxPQUFPLEtBQUssRUFBRSxjQUFjLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDbEQsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLFlBQVksQ0FB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
@@ -1 +1 @@
1
- {"version":3,"file":"sentinel.d.ts","sourceRoot":"","sources":["../../src/sentinel/sentinel.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sentinel.d.ts","sourceRoot":"","sources":["../../src/sentinel/sentinel.ts"],"names":[],"mappings":"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"}
@@ -1,14 +1,27 @@
1
+ import { INITIAL_CHECKPOINT_NUMBER } from '@aztec-labs/constants';
1
2
  import { requireStableCommittee } from '@aztec-labs/epoch-cache';
2
- import { CheckpointProposalHash, EpochNumber, SlotNumber } from '@aztec-labs/foundation/branded-types';
3
+ import { CheckpointNumber, CheckpointProposalHash, EpochNumber, SlotNumber } from '@aztec-labs/foundation/branded-types';
3
4
  import { countWhile, filterAsync, fromEntries, getEntries, mapValues } from '@aztec-labs/foundation/collection';
4
5
  import { EthAddress } from '@aztec-labs/foundation/eth-address';
5
6
  import { createLogger } from '@aztec-labs/foundation/log';
6
7
  import { RunningPromise } from '@aztec-labs/foundation/running-promise';
7
8
  import { OffenseType, WANT_TO_SLASH_EVENT, getOffenseTypeName } from '@aztec-labs/slasher';
8
9
  import { getAttestationInfoFromPublishedCheckpoint } from '@aztec-labs/stdlib/block';
9
- import { getEpochAtSlot, getSlotRangeForEpoch, getTimestampForSlot } from '@aztec-labs/stdlib/epoch-helpers';
10
+ import { computeQuorum, getEpochAtSlot, getSlotRangeForEpoch, getTimestampForSlot } from '@aztec-labs/stdlib/epoch-helpers';
10
11
  import { ConsensusPayload } from '@aztec-labs/stdlib/p2p';
11
12
  import EventEmitter from 'node:events';
13
+ const ORPHANED_PROPOSAL_REASON_DESCRIPTIONS = {
14
+ 'parent-not-on-l1': 'the parent checkpoint it built on never landed on L1',
15
+ 'parent-hash-mismatch': 'a different checkpoint landed on L1 in place of the parent it built on',
16
+ 'unexpected-parent-appeared': 'a checkpoint from an earlier slot landed on L1 with its checkpoint number after it was built on the previous one'
17
+ };
18
+ /**
19
+ * Whether the classification shows the proposal conflicts with a checkpoint that landed on L1, either in its parent's
20
+ * position or in its own. A validator that already held the landed checkpoint's blocks rejects the proposal's blocks as
21
+ * conflicting, so it is right not to attest.
22
+ */ function excusesMissingAttestations(classification) {
23
+ return classification !== undefined && 'reason' in classification && (classification.reason === 'parent-hash-mismatch' || classification.reason === 'unexpected-parent-appeared');
24
+ }
12
25
  /** Maps a validator status to its category: proposer or attestation. */ function statusToCategory(status) {
13
26
  switch(status){
14
27
  case 'attestation-sent':
@@ -47,12 +60,19 @@ import EventEmitter from 'node:events';
47
60
  * ## Six-case taxonomy in `getSlotActivity`
48
61
  *
49
62
  * For each slot, the sentinel assigns the proposer one of six statuses, ranked highest-confidence
50
- * first:
63
+ * first, with case 5 refined into 5a and 5b:
51
64
  *
52
65
  * - `checkpoint-mined` — a checkpoint covering this slot has landed on L1
53
66
  * (fetched on demand via `archiver.getCheckpoint({ slot })`).
67
+ * - `checkpoint-unpublished` — (5a) a valid checkpoint proposal built on a parent that is on L1
68
+ * reached quorum, yet the proposer did not land it.
69
+ * - `checkpoint-orphaned` — (5b) a valid checkpoint proposal could not land because the parent
70
+ * it built on never reached L1 (the proposer pipelining cascade), or
71
+ * because an earlier slot's checkpoint landed with its number after it
72
+ * had built on the previous one.
54
73
  * - `checkpoint-valid` — the local node re-executed a checkpoint proposal for this slot
55
- * successfully (consulted via `CheckpointReexecutionTracker`).
74
+ * successfully (consulted via `CheckpointReexecutionTracker`), and it
75
+ * could not be refined into 5a or 5b.
56
76
  * - `checkpoint-invalid` — the local node re-executed a checkpoint proposal for this slot
57
77
  * and rejected it (e.g. header/archive/out-hash mismatch, limit
58
78
  * breach). Proposer-fault.
@@ -62,9 +82,15 @@ import EventEmitter from 'node:events';
62
82
  * - `checkpoint-missed` — block proposals seen on P2P but no checkpoint proposal at all.
63
83
  * - `blocks-missed` — no block proposals seen for this slot.
64
84
  *
65
- * Missing-attestor faults are recorded only in `checkpoint-mined` and `checkpoint-valid`, where
66
- * the local node has positive evidence the checkpoint was canonical or valid. In the other four
67
- * cases the proposer is at fault and no attestor penalty applies.
85
+ * Missing-attestor faults are recorded only in `checkpoint-mined` and the three valid-proposal
86
+ * statuses (`checkpoint-unpublished`, `checkpoint-orphaned`, `checkpoint-valid`), where the local
87
+ * node has positive evidence the checkpoint was canonical or valid. In the other four cases the
88
+ * proposer is at fault and no attestor penalty applies. A valid proposal that conflicts with a checkpoint
89
+ * that landed on L1, in its parent's position or its own, is also exempt: a validator holding the landed
90
+ * checkpoint's blocks rightly refuses it.
91
+ *
92
+ * `checkpoint-unpublished` and `checkpoint-orphaned` are neutral for the proposer, like
93
+ * `checkpoint-valid`: neither counts toward `missedProposals` or inactivity slashing.
68
94
  *
69
95
  * ## Re-execution tracker
70
96
  *
@@ -391,8 +417,12 @@ import EventEmitter from 'node:events';
391
417
  *
392
418
  * Proposer status:
393
419
  * - case 6 `checkpoint-mined` — a checkpoint covering this slot has landed on L1.
420
+ * - case 5a `checkpoint-unpublished` — a valid proposal with its parent on L1 reached quorum but
421
+ * was not landed by the proposer.
422
+ * - case 5b `checkpoint-orphaned` — a valid proposal could not land because its parent never
423
+ * reached L1, or because an earlier slot took its checkpoint number.
394
424
  * - case 5 `checkpoint-valid` — the local node re-executed a checkpoint proposal for this
395
- * slot successfully.
425
+ * slot successfully, and it could not be refined into 5a or 5b.
396
426
  * - case 4 `checkpoint-invalid` — the local node re-executed a checkpoint proposal for this
397
427
  * slot and rejected it.
398
428
  * - case 3 `checkpoint-unvalidated` — the local node observed a checkpoint proposal for this
@@ -400,8 +430,9 @@ import EventEmitter from 'node:events';
400
430
  * - case 2 `checkpoint-missed` — block proposals seen on P2P but no checkpoint proposal.
401
431
  * - case 1 `blocks-missed` — no block proposals seen for this slot.
402
432
  *
403
- * Missing-attestor penalties apply only in cases 5 and 6, where the local node has positive
404
- * evidence the checkpoint was valid or has been canonicalised on L1.
433
+ * Missing-attestor penalties apply only in cases 5 (including 5a and 5b) and 6, where the local
434
+ * node has positive evidence the checkpoint was valid or has been canonicalised on L1, and never when
435
+ * the proposal conflicts with a checkpoint that landed in its parent's position or its own.
405
436
  */ async getSlotActivity(slot, epoch, proposer, committee) {
406
437
  this.logger.debug(`Computing stats for slot ${slot} at epoch ${epoch}`, {
407
438
  slot,
@@ -419,13 +450,16 @@ import EventEmitter from 'node:events';
419
450
  ...p2pAttestors.map((a)=>a.toString()),
420
451
  ...checkpoint?.attestors.map((a)=>a.toString()) ?? []
421
452
  ].filter((addr)=>proposer.toString() !== addr));
422
- // Determine the proposer status from the six-case taxonomy.
453
+ // Determine the proposer status from the six-case taxonomy. A valid proposal that never reached L1 (case 5) is
454
+ // refined into `checkpoint-unpublished` (5a) or `checkpoint-orphaned` (5b) when the evidence allows it.
423
455
  const reexecutionOutcome = this.reexecutionTracker.getOutcomeForSlot(slot);
424
456
  let status;
457
+ let classification;
425
458
  if (checkpoint) {
426
459
  status = 'checkpoint-mined';
427
460
  } else if (reexecutionOutcome === 'valid') {
428
- status = 'checkpoint-valid';
461
+ classification = await this.classifyUnpublishedProposal(slot, proposer, committee, p2pAttested);
462
+ status = classification.status;
429
463
  } else if (reexecutionOutcome === 'invalid') {
430
464
  status = 'checkpoint-invalid';
431
465
  } else if (reexecutionOutcome === 'unvalidated') {
@@ -444,9 +478,10 @@ import EventEmitter from 'node:events';
444
478
  // A local `invalid` verdict vetoes them even for a mined checkpoint (an honest validator refuses
445
479
  // to sign what it re-executed as invalid), and so does a proposal equivocation in the slot: with
446
480
  // two conflicting proposals an honest attestor may have seen an invalid one and correctly
447
- // declined, so neither must count as a missed attestor (inactivity slashing).
481
+ // declined, so neither must count as a missed attestor (inactivity slashing). Nor does a proposal that conflicts
482
+ // with a checkpoint that landed on L1 (see excusesMissingAttestations).
448
483
  const hasEquivocation = this.reexecutionTracker.hasEquivocation(slot);
449
- const attestorsExpected = (status === 'checkpoint-mined' || status === 'checkpoint-valid') && reexecutionOutcome !== 'invalid' && !hasEquivocation;
484
+ const attestorsExpected = (status === 'checkpoint-mined' || status === 'checkpoint-unpublished' || status === 'checkpoint-orphaned' || status === 'checkpoint-valid') && reexecutionOutcome !== 'invalid' && !hasEquivocation && !excusesMissingAttestations(classification);
450
485
  const missedAttestors = new Set(attestorsExpected ? committee.filter((v)=>!attestors.has(v.toString()) && !proposer.equals(v)).map((v)=>v.toString()) : []);
451
486
  this.logger.debug(`Retrieved ${attestors.size} attestors out of ${committee.length} for slot ${slot}`, {
452
487
  status,
@@ -478,6 +513,114 @@ import EventEmitter from 'node:events';
478
513
  statusFor(who)
479
514
  ]));
480
515
  }
516
+ /**
517
+ * Refines case 5 (a checkpoint proposal for the slot re-executed as valid, with no checkpoint for the slot on L1)
518
+ * into whose fault it was that the proposal did not land:
519
+ *
520
+ * - `checkpoint-orphaned` (5b) when other proposers prevented it: the parent it built on never landed on L1 (it is
521
+ * missing, or a different checkpoint holds the parent's position), or, with a quorum of attestations, an earlier
522
+ * slot's checkpoint landed with its number after it built on the previous one (`unexpected-parent-appeared`).
523
+ * The latter happens when the earlier slot's proposal reached this proposer too late to build on.
524
+ * - `checkpoint-unpublished` (5a) when its parent is on L1 and a quorum of the committee attested, so the proposer
525
+ * had everything it needed to publish. A later slot that lands the same checkpoint number does not exempt it:
526
+ * that slot only got the number because this proposer failed to land it.
527
+ * - `checkpoint-valid` (5) otherwise: the slot had an equivocation, the tracker has no parent or payload data to
528
+ * judge by, or attestations fell short of quorum. Short of quorum is a fault of the attestors, who are tagged
529
+ * `attestation-missed`, except after an unexpected parent appeared (`unexpected-parent-appeared`), when
530
+ * refusing to attest was correct.
531
+ *
532
+ * Only attestations for the proposal's own payload count toward quorum, since those are the only ones its proposer
533
+ * could have published.
534
+ */ async classifyUnpublishedProposal(slot, proposer, committee, p2pAttested) {
535
+ if (this.reexecutionTracker.hasEquivocation(slot)) {
536
+ return {
537
+ status: 'checkpoint-valid',
538
+ reason: 'equivocation'
539
+ };
540
+ }
541
+ const record = this.reexecutionTracker.getRecordForSlot(slot);
542
+ const checkpointNumber = record?.checkpointNumber;
543
+ const lastArchiveRoot = record?.lastArchiveRoot;
544
+ const payloadHash = record?.payloadHash;
545
+ if (checkpointNumber === undefined || lastArchiveRoot === undefined || payloadHash === undefined) {
546
+ return {
547
+ status: 'checkpoint-valid',
548
+ reason: 'no-proposal-data'
549
+ };
550
+ }
551
+ const logData = {
552
+ slot,
553
+ proposer: proposer.toString(),
554
+ checkpointNumber
555
+ };
556
+ const orphaned = (reason, extra = {})=>{
557
+ this.logger.verbose(`Valid checkpoint proposal for slot ${slot} was orphaned (${reason}): ${ORPHANED_PROPOSAL_REASON_DESCRIPTIONS[reason]}`, {
558
+ ...logData,
559
+ ...extra,
560
+ reason
561
+ });
562
+ return {
563
+ status: 'checkpoint-orphaned',
564
+ reason
565
+ };
566
+ };
567
+ // The first checkpoint builds on genesis, which is always on L1.
568
+ const parentNumber = checkpointNumber - 1;
569
+ if (parentNumber >= INITIAL_CHECKPOINT_NUMBER) {
570
+ const parent = await this.archiver.getCheckpointData({
571
+ number: CheckpointNumber(parentNumber)
572
+ });
573
+ if (!parent) {
574
+ return orphaned('parent-not-on-l1');
575
+ }
576
+ if (parent.archive.root.toString() !== lastArchiveRoot) {
577
+ return orphaned('parent-hash-mismatch');
578
+ }
579
+ }
580
+ // The proposer's own attestation counts, as it does when its sequencer collects attestations for L1.
581
+ const committeeMembers = new Set(committee.map((member)=>member.toString()));
582
+ const attestationCount = new Set(p2pAttested.filter((attestation)=>attestation.getPayloadHash() === payloadHash).map((attestation)=>attestation.getSender()?.toString()).filter((attestor)=>attestor !== undefined && committeeMembers.has(attestor))).size;
583
+ const quorum = computeQuorum(committee.length);
584
+ // A checkpoint landed for the slot itself would have made this case 6, so a holder of the same number comes from
585
+ // an earlier or a later slot. Only an earlier one can have stopped this proposal from landing.
586
+ const sameNumber = await this.archiver.getCheckpointData({
587
+ number: checkpointNumber
588
+ });
589
+ const sameNumberSlot = sameNumber?.header.slotNumber;
590
+ if (sameNumberSlot !== undefined && sameNumberSlot < slot) {
591
+ const conflictData = {
592
+ takenBySlot: sameNumberSlot,
593
+ attestationCount,
594
+ quorum
595
+ };
596
+ if (attestationCount < quorum) {
597
+ this.logger.verbose(`Valid checkpoint proposal for slot ${slot} fell short of quorum after checkpoint ${checkpointNumber} landed ` + `from earlier slot ${sameNumberSlot} (unexpected-parent-appeared), so missing attestations are not counted`, {
598
+ ...logData,
599
+ ...conflictData,
600
+ reason: 'unexpected-parent-appeared'
601
+ });
602
+ return {
603
+ status: 'checkpoint-valid',
604
+ reason: 'unexpected-parent-appeared'
605
+ };
606
+ }
607
+ return orphaned('unexpected-parent-appeared', conflictData);
608
+ }
609
+ if (attestationCount < quorum) {
610
+ return {
611
+ status: 'checkpoint-valid',
612
+ reason: 'below-quorum'
613
+ };
614
+ }
615
+ this.logger.info(`Proposer did not publish checkpoint ${checkpointNumber} for slot ${slot} despite quorum`, {
616
+ ...logData,
617
+ attestationCount,
618
+ quorum
619
+ });
620
+ return {
621
+ status: 'checkpoint-unpublished'
622
+ };
623
+ }
481
624
  /** Push the status for each slot for each validator. */ updateValidators(slot, stats) {
482
625
  return this.store.updateValidators(slot, stats);
483
626
  }
@@ -551,7 +694,7 @@ import EventEmitter from 'node:events';
551
694
  computeStatsForValidator(address, allHistory, fromSlot, toSlot) {
552
695
  let history = fromSlot ? allHistory.filter((h)=>BigInt(h.slot) >= fromSlot) : allHistory;
553
696
  history = toSlot ? history.filter((h)=>BigInt(h.slot) <= toSlot) : history;
554
- const lastProposal = history.filter((h)=>h.status === 'checkpoint-valid' || h.status === 'checkpoint-mined').at(-1);
697
+ const lastProposal = history.filter((h)=>h.status === 'checkpoint-valid' || h.status === 'checkpoint-unpublished' || h.status === 'checkpoint-orphaned' || h.status === 'checkpoint-mined').at(-1);
555
698
  const lastAttestation = history.filter((h)=>h.status === 'attestation-sent').at(-1);
556
699
  return {
557
700
  address: EthAddress.fromString(address),
@@ -40,4 +40,4 @@ export declare class SentinelStore {
40
40
  private statusToNumber;
41
41
  private statusFromNumber;
42
42
  }
43
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RvcmUuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZW50aW5lbC9zdG9yZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsV0FBVyxFQUFFLFVBQVUsRUFBRSxNQUFNLHNDQUFzQyxDQUFDO0FBQy9FLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxvQ0FBb0MsQ0FBQztBQUVoRSxPQUFPLEtBQUssRUFBRSxpQkFBaUIsRUFBaUIsTUFBTSxzQkFBc0IsQ0FBQztBQUM3RSxPQUFPLEtBQUssRUFDVixzQkFBc0IsRUFDdEIscUJBQXFCLEVBQ3JCLDBCQUEwQixFQUMzQixNQUFNLCtCQUErQixDQUFDO0FBRXZDLHFCQUFhLGFBQWE7SUFXdEIsT0FBTyxDQUFDLEtBQUs7SUFDYixPQUFPLENBQUMsTUFBTTtJQVhoQixnQkFBdUIsY0FBYyxLQUFLO0lBRzFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUF1QztJQUlsRSxPQUFPLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBdUM7SUFFaEUsWUFDVSxLQUFLLEVBQUUsaUJBQWlCLEVBQ3hCLE1BQU0sRUFBRTtRQUFFLGFBQWEsRUFBRSxNQUFNLENBQUM7UUFBQyw4QkFBOEIsRUFBRSxNQUFNLENBQUE7S0FBRSxFQUlsRjtJQUVNLGdCQUFnQixXQUV0QjtJQUVNLGlDQUFpQyxXQUV2QztJQUVZLHNCQUFzQixDQUFDLEtBQUssRUFBRSxXQUFXLEVBQUUsV0FBVyxFQUFFLDBCQUEwQixpQkFNOUY7SUFFWSxtQkFBbUIsQ0FBQyxHQUFHLEVBQUUsVUFBVSxHQUFHLE9BQU8sQ0FBQztRQUFFLE1BQU0sRUFBRSxNQUFNLENBQUM7UUFBQyxLQUFLLEVBQUUsTUFBTSxDQUFDO1FBQUMsS0FBSyxFQUFFLFdBQVcsQ0FBQTtLQUFFLEVBQUUsQ0FBQyxDQUdsSDtJQUVELDhHQUE4RztJQUNqRyx3QkFBd0IsQ0FDbkMsU0FBUyxFQUFFLFVBQVUsRUFBRSxHQUN0QixPQUFPLENBQUM7UUFBRSxNQUFNLEVBQUUsTUFBTSxDQUFDO1FBQUMsS0FBSyxFQUFFLE1BQU0sQ0FBQztRQUFDLEtBQUssRUFBRSxXQUFXLENBQUE7S0FBRSxFQUFFLEVBQUUsQ0FBQyxDQUdwRTtZQUVhLDZCQUE2QjtJQTZCOUIsZ0JBQWdCLENBQUMsSUFBSSxFQUFFLFVBQVUsRUFBRSxRQUFRLEVBQUUsTUFBTSxDQUFDLEtBQUssTUFBTSxFQUFFLEVBQUUscUJBQXFCLEdBQUcsU0FBUyxDQUFDLGlCQVFqSDtZQUVhLDBCQUEwQjtJQVEzQixZQUFZLElBQUksT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLE1BQU0sRUFBRSxFQUFFLHNCQUFzQixDQUFDLENBQUMsQ0FNbEY7SUFFWSxVQUFVLENBQUMsT0FBTyxFQUFFLFVBQVUsR0FBRyxPQUFPLENBQUMsc0JBQXNCLEdBQUcsU0FBUyxDQUFDLENBR3hGO0lBRUQsMEZBQTBGO0lBQzdFLGVBQWUsQ0FBQyxTQUFTLEVBQUUsVUFBVSxFQUFFLEdBQUcsT0FBTyxDQUFDLENBQUMsc0JBQXNCLEdBQUcsU0FBUyxDQUFDLEVBQUUsQ0FBQyxDQUdyRztJQUVELE9BQU8sQ0FBQyxvQkFBb0I7SUFNNUIsT0FBTyxDQUFDLHNCQUFzQjtJQWE5QixPQUFPLENBQUMsZ0JBQWdCO0lBTXhCLE9BQU8sQ0FBQyxrQkFBa0I7SUFXMUIsT0FBTyxDQUFDLGNBQWM7SUF5QnRCLE9BQU8sQ0FBQyxnQkFBZ0I7Q0FzQnpCIn0=
43
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RvcmUuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZW50aW5lbC9zdG9yZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsV0FBVyxFQUFFLFVBQVUsRUFBRSxNQUFNLHNDQUFzQyxDQUFDO0FBQy9FLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxvQ0FBb0MsQ0FBQztBQUVoRSxPQUFPLEtBQUssRUFBRSxpQkFBaUIsRUFBaUIsTUFBTSxzQkFBc0IsQ0FBQztBQUM3RSxPQUFPLEtBQUssRUFDVixzQkFBc0IsRUFDdEIscUJBQXFCLEVBQ3JCLDBCQUEwQixFQUMzQixNQUFNLCtCQUErQixDQUFDO0FBRXZDLHFCQUFhLGFBQWE7SUFXdEIsT0FBTyxDQUFDLEtBQUs7SUFDYixPQUFPLENBQUMsTUFBTTtJQVhoQixnQkFBdUIsY0FBYyxLQUFLO0lBRzFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUF1QztJQUlsRSxPQUFPLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBdUM7SUFFaEUsWUFDVSxLQUFLLEVBQUUsaUJBQWlCLEVBQ3hCLE1BQU0sRUFBRTtRQUFFLGFBQWEsRUFBRSxNQUFNLENBQUM7UUFBQyw4QkFBOEIsRUFBRSxNQUFNLENBQUE7S0FBRSxFQUlsRjtJQUVNLGdCQUFnQixXQUV0QjtJQUVNLGlDQUFpQyxXQUV2QztJQUVZLHNCQUFzQixDQUFDLEtBQUssRUFBRSxXQUFXLEVBQUUsV0FBVyxFQUFFLDBCQUEwQixpQkFNOUY7SUFFWSxtQkFBbUIsQ0FBQyxHQUFHLEVBQUUsVUFBVSxHQUFHLE9BQU8sQ0FBQztRQUFFLE1BQU0sRUFBRSxNQUFNLENBQUM7UUFBQyxLQUFLLEVBQUUsTUFBTSxDQUFDO1FBQUMsS0FBSyxFQUFFLFdBQVcsQ0FBQTtLQUFFLEVBQUUsQ0FBQyxDQUdsSDtJQUVELDhHQUE4RztJQUNqRyx3QkFBd0IsQ0FDbkMsU0FBUyxFQUFFLFVBQVUsRUFBRSxHQUN0QixPQUFPLENBQUM7UUFBRSxNQUFNLEVBQUUsTUFBTSxDQUFDO1FBQUMsS0FBSyxFQUFFLE1BQU0sQ0FBQztRQUFDLEtBQUssRUFBRSxXQUFXLENBQUE7S0FBRSxFQUFFLEVBQUUsQ0FBQyxDQUdwRTtZQUVhLDZCQUE2QjtJQTZCOUIsZ0JBQWdCLENBQUMsSUFBSSxFQUFFLFVBQVUsRUFBRSxRQUFRLEVBQUUsTUFBTSxDQUFDLEtBQUssTUFBTSxFQUFFLEVBQUUscUJBQXFCLEdBQUcsU0FBUyxDQUFDLGlCQVFqSDtZQUVhLDBCQUEwQjtJQVEzQixZQUFZLElBQUksT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLE1BQU0sRUFBRSxFQUFFLHNCQUFzQixDQUFDLENBQUMsQ0FNbEY7SUFFWSxVQUFVLENBQUMsT0FBTyxFQUFFLFVBQVUsR0FBRyxPQUFPLENBQUMsc0JBQXNCLEdBQUcsU0FBUyxDQUFDLENBR3hGO0lBRUQsMEZBQTBGO0lBQzdFLGVBQWUsQ0FBQyxTQUFTLEVBQUUsVUFBVSxFQUFFLEdBQUcsT0FBTyxDQUFDLENBQUMsc0JBQXNCLEdBQUcsU0FBUyxDQUFDLEVBQUUsQ0FBQyxDQUdyRztJQUVELE9BQU8sQ0FBQyxvQkFBb0I7SUFNNUIsT0FBTyxDQUFDLHNCQUFzQjtJQWE5QixPQUFPLENBQUMsZ0JBQWdCO0lBTXhCLE9BQU8sQ0FBQyxrQkFBa0I7SUFXMUIsT0FBTyxDQUFDLGNBQWM7SUE2QnRCLE9BQU8sQ0FBQyxnQkFBZ0I7Q0EwQnpCIn0=
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/sentinel/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAC;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAEhE,OAAO,KAAK,EAAE,iBAAiB,EAAiB,MAAM,sBAAsB,CAAC;AAC7E,OAAO,KAAK,EACV,sBAAsB,EACtB,qBAAqB,EACrB,0BAA0B,EAC3B,MAAM,+BAA+B,CAAC;AAEvC,qBAAa,aAAa;IAWtB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,MAAM;IAXhB,gBAAuB,cAAc,KAAK;IAG1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuC;IAIlE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuC;IAEhE,YACU,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,8BAA8B,EAAE,MAAM,CAAA;KAAE,EAIlF;IAEM,gBAAgB,WAEtB;IAEM,iCAAiC,WAEvC;IAEY,sBAAsB,CAAC,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,0BAA0B,iBAM9F;IAEY,mBAAmB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,WAAW,CAAA;KAAE,EAAE,CAAC,CAGlH;IAED,8GAA8G;IACjG,wBAAwB,CACnC,SAAS,EAAE,UAAU,EAAE,GACtB,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,WAAW,CAAA;KAAE,EAAE,EAAE,CAAC,CAGpE;YAEa,6BAA6B;IA6B9B,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,KAAK,MAAM,EAAE,EAAE,qBAAqB,GAAG,SAAS,CAAC,iBAQjH;YAEa,0BAA0B;IAQ3B,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,MAAM,EAAE,EAAE,sBAAsB,CAAC,CAAC,CAMlF;IAEY,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC,CAGxF;IAED,0FAA0F;IAC7E,eAAe,CAAC,SAAS,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,CAAC,sBAAsB,GAAG,SAAS,CAAC,EAAE,CAAC,CAGrG;IAED,OAAO,CAAC,oBAAoB;IAM5B,OAAO,CAAC,sBAAsB;IAa9B,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,cAAc;IAyBtB,OAAO,CAAC,gBAAgB;CAsBzB"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/sentinel/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAC;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAEhE,OAAO,KAAK,EAAE,iBAAiB,EAAiB,MAAM,sBAAsB,CAAC;AAC7E,OAAO,KAAK,EACV,sBAAsB,EACtB,qBAAqB,EACrB,0BAA0B,EAC3B,MAAM,+BAA+B,CAAC;AAEvC,qBAAa,aAAa;IAWtB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,MAAM;IAXhB,gBAAuB,cAAc,KAAK;IAG1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuC;IAIlE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuC;IAEhE,YACU,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,8BAA8B,EAAE,MAAM,CAAA;KAAE,EAIlF;IAEM,gBAAgB,WAEtB;IAEM,iCAAiC,WAEvC;IAEY,sBAAsB,CAAC,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,0BAA0B,iBAM9F;IAEY,mBAAmB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,WAAW,CAAA;KAAE,EAAE,CAAC,CAGlH;IAED,8GAA8G;IACjG,wBAAwB,CACnC,SAAS,EAAE,UAAU,EAAE,GACtB,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,WAAW,CAAA;KAAE,EAAE,EAAE,CAAC,CAGpE;YAEa,6BAA6B;IA6B9B,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,KAAK,MAAM,EAAE,EAAE,qBAAqB,GAAG,SAAS,CAAC,iBAQjH;YAEa,0BAA0B;IAQ3B,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,MAAM,EAAE,EAAE,sBAAsB,CAAC,CAAC,CAMlF;IAEY,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC,CAGxF;IAED,0FAA0F;IAC7E,eAAe,CAAC,SAAS,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,CAAC,sBAAsB,GAAG,SAAS,CAAC,EAAE,CAAC,CAGrG;IAED,OAAO,CAAC,oBAAoB;IAM5B,OAAO,CAAC,sBAAsB;IAa9B,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,cAAc;IA6BtB,OAAO,CAAC,gBAAgB;CA0BzB"}
@@ -158,6 +158,10 @@ export class SentinelStore {
158
158
  return 7;
159
159
  case 'checkpoint-unvalidated':
160
160
  return 8;
161
+ case 'checkpoint-unpublished':
162
+ return 9;
163
+ case 'checkpoint-orphaned':
164
+ return 10;
161
165
  default:
162
166
  {
163
167
  const _exhaustive = status;
@@ -183,6 +187,10 @@ export class SentinelStore {
183
187
  return 'checkpoint-invalid';
184
188
  case 8:
185
189
  return 'checkpoint-unvalidated';
190
+ case 9:
191
+ return 'checkpoint-unpublished';
192
+ case 10:
193
+ return 'checkpoint-orphaned';
186
194
  default:
187
195
  throw new Error(`Unknown status: ${status}`);
188
196
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec-labs/aztec-node",
3
- "version": "6.0.0-nightly.20261021",
3
+ "version": "6.0.0-nightly.20261022",
4
4
  "main": "dest/index.js",
5
5
  "type": "module",
6
6
  "exports": {
@@ -65,38 +65,38 @@
65
65
  ]
66
66
  },
67
67
  "dependencies": {
68
- "@aztec-foundation/bb.js": "6.0.0-rc.6",
69
- "@aztec-labs/archiver": "6.0.0-nightly.20261021",
70
- "@aztec-labs/bb-prover": "6.0.0-nightly.20261021",
71
- "@aztec-labs/blob-client": "6.0.0-nightly.20261021",
72
- "@aztec-labs/blob-lib": "6.0.0-nightly.20261021",
73
- "@aztec-labs/constants": "6.0.0-nightly.20261021",
74
- "@aztec-labs/epoch-cache": "6.0.0-nightly.20261021",
75
- "@aztec-labs/ethereum": "6.0.0-nightly.20261021",
76
- "@aztec-labs/foundation": "6.0.0-nightly.20261021",
77
- "@aztec-labs/kv-store": "6.0.0-nightly.20261021",
78
- "@aztec-labs/node-keystore": "6.0.0-nightly.20261021",
79
- "@aztec-labs/node-lib": "6.0.0-nightly.20261021",
80
- "@aztec-labs/p2p": "6.0.0-nightly.20261021",
81
- "@aztec-labs/protocol-contracts": "6.0.0-nightly.20261021",
82
- "@aztec-labs/prover-client": "6.0.0-nightly.20261021",
83
- "@aztec-labs/prover-node": "6.0.0-nightly.20261021",
84
- "@aztec-labs/sequencer-client": "6.0.0-nightly.20261021",
85
- "@aztec-labs/simulator": "6.0.0-nightly.20261021",
86
- "@aztec-labs/slasher": "6.0.0-nightly.20261021",
87
- "@aztec-labs/standard-contracts": "6.0.0-nightly.20261021",
88
- "@aztec-labs/stdlib": "6.0.0-nightly.20261021",
89
- "@aztec-labs/telemetry-client": "6.0.0-nightly.20261021",
90
- "@aztec-labs/validator-client": "6.0.0-nightly.20261021",
91
- "@aztec-labs/validator-ha-signer": "6.0.0-nightly.20261021",
92
- "@aztec-labs/world-state": "6.0.0-nightly.20261021",
68
+ "@aztec-foundation/bb.js": "6.0.0-rc.7",
69
+ "@aztec-labs/archiver": "6.0.0-nightly.20261022",
70
+ "@aztec-labs/bb-prover": "6.0.0-nightly.20261022",
71
+ "@aztec-labs/blob-client": "6.0.0-nightly.20261022",
72
+ "@aztec-labs/blob-lib": "6.0.0-nightly.20261022",
73
+ "@aztec-labs/constants": "6.0.0-nightly.20261022",
74
+ "@aztec-labs/epoch-cache": "6.0.0-nightly.20261022",
75
+ "@aztec-labs/ethereum": "6.0.0-nightly.20261022",
76
+ "@aztec-labs/foundation": "6.0.0-nightly.20261022",
77
+ "@aztec-labs/kv-store": "6.0.0-nightly.20261022",
78
+ "@aztec-labs/node-keystore": "6.0.0-nightly.20261022",
79
+ "@aztec-labs/node-lib": "6.0.0-nightly.20261022",
80
+ "@aztec-labs/p2p": "6.0.0-nightly.20261022",
81
+ "@aztec-labs/protocol-contracts": "6.0.0-nightly.20261022",
82
+ "@aztec-labs/prover-client": "6.0.0-nightly.20261022",
83
+ "@aztec-labs/prover-node": "6.0.0-nightly.20261022",
84
+ "@aztec-labs/sequencer-client": "6.0.0-nightly.20261022",
85
+ "@aztec-labs/simulator": "6.0.0-nightly.20261022",
86
+ "@aztec-labs/slasher": "6.0.0-nightly.20261022",
87
+ "@aztec-labs/standard-contracts": "6.0.0-nightly.20261022",
88
+ "@aztec-labs/stdlib": "6.0.0-nightly.20261022",
89
+ "@aztec-labs/telemetry-client": "6.0.0-nightly.20261022",
90
+ "@aztec-labs/validator-client": "6.0.0-nightly.20261022",
91
+ "@aztec-labs/validator-ha-signer": "6.0.0-nightly.20261022",
92
+ "@aztec-labs/world-state": "6.0.0-nightly.20261022",
93
93
  "koa": "^2.16.1",
94
94
  "koa-router": "^13.1.1",
95
95
  "tslib": "^2.4.0",
96
96
  "viem": "2.57.1"
97
97
  },
98
98
  "devDependencies": {
99
- "@aztec-labs/noir-protocol-circuits-types": "6.0.0-nightly.20261021",
99
+ "@aztec-labs/noir-protocol-circuits-types": "6.0.0-nightly.20261022",
100
100
  "@jest/globals": "^30.0.0",
101
101
  "@types/jest": "^30.0.0",
102
102
  "@types/node": "^22.15.17",
@@ -45,23 +45,39 @@ The aggregator catches up if multiple epochs become eligible at once (e.g. after
45
45
 
46
46
  ## Six-case taxonomy
47
47
 
48
- For each slot, the proposer is assigned one of six statuses, ranked highest-confidence first:
48
+ For each slot, the proposer is assigned one of six statuses, ranked highest-confidence first. Case 5 is refined into 5a and 5b when the evidence allows it:
49
49
 
50
50
  | # | Status | Trigger | Inactive party |
51
51
  |---|---|---|---|
52
52
  | 6 | `checkpoint-mined` | `archiver.getCheckpoint({ slot })` returns a checkpoint (one covering this slot has landed on L1) | Attestors who didn't attest |
53
- | 5 | `checkpoint-valid` | `tracker.getOutcomeForSlot(slot) === 'valid'` | Attestors who didn't attest |
53
+ | 5a | `checkpoint-unpublished` | Case 5, and the proposal's parent is on L1 and a quorum of the committee attested | Attestors who didn't attest (proposer fault recorded, not yet penalized) |
54
+ | 5b | `checkpoint-orphaned` | Case 5, and the parent the proposal built on never landed on L1 (missing or different archive root), or an earlier slot landed the proposal's checkpoint number after a quorum attested it | Attestors who didn't attest, unless a different checkpoint landed in the parent's position or an earlier slot took the checkpoint number |
55
+ | 5 | `checkpoint-valid` | `tracker.getOutcomeForSlot(slot) === 'valid'`, not refined into 5a or 5b | Attestors who didn't attest |
54
56
  | 4 | `checkpoint-invalid` | `tracker.getOutcomeForSlot(slot) === 'invalid'` (re-executed and rejected) | Proposer |
55
57
  | 3 | `checkpoint-unvalidated` | `tracker.getOutcomeForSlot(slot) === 'unvalidated'` (validation aborted: missing data, timeout, etc.) | Proposer |
56
58
  | 2 | `checkpoint-missed` | `p2p.hasBlockProposalsForSlot(slot)` true (blocks seen but no checkpoint proposal observed) | Proposer |
57
59
  | 1 | `blocks-missed` | None of the above (no block proposals observed) | Proposer |
58
60
 
59
- Missing-attestor faults are only recorded in cases 5 and 6 — where the local node has positive evidence the checkpoint was valid or canonical. In cases 1–4 the proposer is at fault and no attestor penalty applies.
61
+ Missing-attestor faults are only recorded in cases 5 (including 5a and 5b) and 6 — where the local node has positive evidence the checkpoint was valid or canonical. In cases 1–4 the proposer is at fault and no attestor penalty applies. Nor does one apply when a different checkpoint landed in the parent's position or an earlier slot landed the proposal's checkpoint number: a validator that already held the landed checkpoint's blocks rejects the proposal (or the parent it builds on) as conflicting, and is right to.
62
+
63
+ ### Refining case 5: unpublished and orphaned proposals
64
+
65
+ Under proposer pipelining, the proposer for slot N+1 builds on slot N's gossiped proposal. If slot N never lands, slot N+1 cannot land either, through no fault of its own. Cases 5a and 5b separate that cascade from a proposer that simply failed to publish. When a slot has a valid re-execution outcome but no checkpoint on L1, the sentinel reads the tracker's record for the slot (`getRecordForSlot`) and checks, in order:
66
+
67
+ 1. If the slot had a proposal equivocation, or the record has no checkpoint number, parent archive root or payload hash, the status stays `checkpoint-valid`.
68
+ 2. If the parent checkpoint (`checkpointNumber - 1`) is not on L1, or its archive root differs from the proposal's `lastArchiveRoot`, the status is `checkpoint-orphaned`. The first checkpoint builds on genesis, which always counts as on L1. When a different checkpoint holds the parent's position (`parent-hash-mismatch`), no attestor is tagged `attestation-missed`: a validator that held that checkpoint's blocks rightly rejects the proposal's parent as conflicting.
69
+ 3. If a checkpoint from an earlier slot landed on L1 with the proposal's checkpoint number (`unexpected-parent-appeared`, the reason the sequencer gives when it discards its own pipelined checkpoint for this cause), the earlier slot's proposal reached the proposer too late to build on, and once it landed this proposal could not. With a quorum of attestations the status is `checkpoint-orphaned`. Below quorum it stays `checkpoint-valid`, and no attestor is tagged `attestation-missed`. Both are logged at `verbose` with the reason and the slot that took the number.
70
+ 4. If fewer than `computeQuorum(committee.length)` distinct committee members attested the proposal's payload on P2P (the proposer's own attestation counts, as it does for the sequencer; attestations for another payload in the slot do not, since the proposer could not publish them), the status stays `checkpoint-valid`: the proposer could not publish, and the attestors are at fault.
71
+ 5. Otherwise the status is `checkpoint-unpublished`, logged at `info` with the attestation count and quorum.
72
+
73
+ A checkpoint from a later slot that holds the proposal's checkpoint number does not exempt the proposer: that slot only got the number because this proposer failed to land it.
74
+
75
+ Neither `checkpoint-unpublished` nor `checkpoint-orphaned` counts toward `missedProposals` or inactivity slashing yet. Like `checkpoint-valid`, both only add to the proposer's slot total.
60
76
 
61
77
  Each non-proposer committee member is tagged:
62
78
 
63
79
  - `attestation-sent` — attestation seen on P2P (with valid signature) or in the L1 checkpoint's attestor set
64
- - `attestation-missed` — only when proposer status is case 5 or 6 and the validator's attestation was not seen
80
+ - `attestation-missed` — only when proposer status is case 5 (including 5a and 5b) or 6 and the validator's attestation was not seen, unless a different checkpoint landed in the parent's position or an earlier slot landed the proposal's checkpoint number
65
81
  - none — otherwise
66
82
 
67
83
  ## Inactivity slashing
@@ -1,3 +1,4 @@
1
+ import { INITIAL_CHECKPOINT_NUMBER } from '@aztec-labs/constants';
1
2
  import { type EpochCache, requireStableCommittee } from '@aztec-labs/epoch-cache';
2
3
  import {
3
4
  CheckpointNumber,
@@ -22,8 +23,17 @@ import type { SlasherConfig } from '@aztec-labs/slasher/config';
22
23
  import { type L2BlockSource, getAttestationInfoFromPublishedCheckpoint } from '@aztec-labs/stdlib/block';
23
24
  import type { CheckpointReexecutionTracker } from '@aztec-labs/stdlib/checkpoint';
24
25
  import type { ChainConfig } from '@aztec-labs/stdlib/config';
25
- import { getEpochAtSlot, getSlotRangeForEpoch, getTimestampForSlot } from '@aztec-labs/stdlib/epoch-helpers';
26
- import { ConsensusPayload, type CoordinationSignatureContext } from '@aztec-labs/stdlib/p2p';
26
+ import {
27
+ computeQuorum,
28
+ getEpochAtSlot,
29
+ getSlotRangeForEpoch,
30
+ getTimestampForSlot,
31
+ } from '@aztec-labs/stdlib/epoch-helpers';
32
+ import {
33
+ type CheckpointAttestation,
34
+ ConsensusPayload,
35
+ type CoordinationSignatureContext,
36
+ } from '@aztec-labs/stdlib/p2p';
27
37
  import type {
28
38
  SingleValidatorStats,
29
39
  ValidatorStats,
@@ -45,6 +55,42 @@ export type SentinelRuntimeConfig = Pick<
45
55
  Pick<SentinelConfig, 'sentinelEpochEndBufferSlots'> &
46
56
  Pick<ChainConfig, 'l1ChainId' | 'rollupAddress'>;
47
57
 
58
+ /**
59
+ * Why a valid checkpoint proposal that never reached L1 could not have landed, regardless of its proposer.
60
+ * `unexpected-parent-appeared` matches the reason the sequencer gives when it discards its own pipelined checkpoint
61
+ * for the same cause.
62
+ */
63
+ type OrphanedProposalReason = 'parent-not-on-l1' | 'parent-hash-mismatch' | 'unexpected-parent-appeared';
64
+
65
+ const ORPHANED_PROPOSAL_REASON_DESCRIPTIONS: Record<OrphanedProposalReason, string> = {
66
+ 'parent-not-on-l1': 'the parent checkpoint it built on never landed on L1',
67
+ 'parent-hash-mismatch': 'a different checkpoint landed on L1 in place of the parent it built on',
68
+ 'unexpected-parent-appeared':
69
+ 'a checkpoint from an earlier slot landed on L1 with its checkpoint number after it was built on the previous one',
70
+ };
71
+
72
+ /** Refined status of a valid checkpoint proposal for a slot whose checkpoint never reached L1. */
73
+ type UnpublishedProposalClassification =
74
+ | { status: 'checkpoint-unpublished' }
75
+ | { status: 'checkpoint-orphaned'; reason: OrphanedProposalReason }
76
+ | {
77
+ status: 'checkpoint-valid';
78
+ reason: 'equivocation' | 'no-proposal-data' | 'below-quorum' | 'unexpected-parent-appeared';
79
+ };
80
+
81
+ /**
82
+ * Whether the classification shows the proposal conflicts with a checkpoint that landed on L1, either in its parent's
83
+ * position or in its own. A validator that already held the landed checkpoint's blocks rejects the proposal's blocks as
84
+ * conflicting, so it is right not to attest.
85
+ */
86
+ function excusesMissingAttestations(classification: UnpublishedProposalClassification | undefined): boolean {
87
+ return (
88
+ classification !== undefined &&
89
+ 'reason' in classification &&
90
+ (classification.reason === 'parent-hash-mismatch' || classification.reason === 'unexpected-parent-appeared')
91
+ );
92
+ }
93
+
48
94
  /** Maps a validator status to its category: proposer or attestation. */
49
95
  function statusToCategory(status: ValidatorStatusInSlot): ValidatorStatusType {
50
96
  switch (status) {
@@ -85,12 +131,19 @@ function statusToCategory(status: ValidatorStatusInSlot): ValidatorStatusType {
85
131
  * ## Six-case taxonomy in `getSlotActivity`
86
132
  *
87
133
  * For each slot, the sentinel assigns the proposer one of six statuses, ranked highest-confidence
88
- * first:
134
+ * first, with case 5 refined into 5a and 5b:
89
135
  *
90
136
  * - `checkpoint-mined` — a checkpoint covering this slot has landed on L1
91
137
  * (fetched on demand via `archiver.getCheckpoint({ slot })`).
138
+ * - `checkpoint-unpublished` — (5a) a valid checkpoint proposal built on a parent that is on L1
139
+ * reached quorum, yet the proposer did not land it.
140
+ * - `checkpoint-orphaned` — (5b) a valid checkpoint proposal could not land because the parent
141
+ * it built on never reached L1 (the proposer pipelining cascade), or
142
+ * because an earlier slot's checkpoint landed with its number after it
143
+ * had built on the previous one.
92
144
  * - `checkpoint-valid` — the local node re-executed a checkpoint proposal for this slot
93
- * successfully (consulted via `CheckpointReexecutionTracker`).
145
+ * successfully (consulted via `CheckpointReexecutionTracker`), and it
146
+ * could not be refined into 5a or 5b.
94
147
  * - `checkpoint-invalid` — the local node re-executed a checkpoint proposal for this slot
95
148
  * and rejected it (e.g. header/archive/out-hash mismatch, limit
96
149
  * breach). Proposer-fault.
@@ -100,9 +153,15 @@ function statusToCategory(status: ValidatorStatusInSlot): ValidatorStatusType {
100
153
  * - `checkpoint-missed` — block proposals seen on P2P but no checkpoint proposal at all.
101
154
  * - `blocks-missed` — no block proposals seen for this slot.
102
155
  *
103
- * Missing-attestor faults are recorded only in `checkpoint-mined` and `checkpoint-valid`, where
104
- * the local node has positive evidence the checkpoint was canonical or valid. In the other four
105
- * cases the proposer is at fault and no attestor penalty applies.
156
+ * Missing-attestor faults are recorded only in `checkpoint-mined` and the three valid-proposal
157
+ * statuses (`checkpoint-unpublished`, `checkpoint-orphaned`, `checkpoint-valid`), where the local
158
+ * node has positive evidence the checkpoint was canonical or valid. In the other four cases the
159
+ * proposer is at fault and no attestor penalty applies. A valid proposal that conflicts with a checkpoint
160
+ * that landed on L1, in its parent's position or its own, is also exempt: a validator holding the landed
161
+ * checkpoint's blocks rightly refuses it.
162
+ *
163
+ * `checkpoint-unpublished` and `checkpoint-orphaned` are neutral for the proposer, like
164
+ * `checkpoint-valid`: neither counts toward `missedProposals` or inactivity slashing.
106
165
  *
107
166
  * ## Re-execution tracker
108
167
  *
@@ -490,8 +549,12 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
490
549
  *
491
550
  * Proposer status:
492
551
  * - case 6 `checkpoint-mined` — a checkpoint covering this slot has landed on L1.
552
+ * - case 5a `checkpoint-unpublished` — a valid proposal with its parent on L1 reached quorum but
553
+ * was not landed by the proposer.
554
+ * - case 5b `checkpoint-orphaned` — a valid proposal could not land because its parent never
555
+ * reached L1, or because an earlier slot took its checkpoint number.
493
556
  * - case 5 `checkpoint-valid` — the local node re-executed a checkpoint proposal for this
494
- * slot successfully.
557
+ * slot successfully, and it could not be refined into 5a or 5b.
495
558
  * - case 4 `checkpoint-invalid` — the local node re-executed a checkpoint proposal for this
496
559
  * slot and rejected it.
497
560
  * - case 3 `checkpoint-unvalidated` — the local node observed a checkpoint proposal for this
@@ -499,8 +562,9 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
499
562
  * - case 2 `checkpoint-missed` — block proposals seen on P2P but no checkpoint proposal.
500
563
  * - case 1 `blocks-missed` — no block proposals seen for this slot.
501
564
  *
502
- * Missing-attestor penalties apply only in cases 5 and 6, where the local node has positive
503
- * evidence the checkpoint was valid or has been canonicalised on L1.
565
+ * Missing-attestor penalties apply only in cases 5 (including 5a and 5b) and 6, where the local
566
+ * node has positive evidence the checkpoint was valid or has been canonicalised on L1, and never when
567
+ * the proposal conflicts with a checkpoint that landed in its parent's position or its own.
504
568
  */
505
569
  protected async getSlotActivity(slot: SlotNumber, epoch: EpochNumber, proposer: EthAddress, committee: EthAddress[]) {
506
570
  this.logger.debug(`Computing stats for slot ${slot} at epoch ${epoch}`, { slot, epoch, proposer, committee });
@@ -517,19 +581,24 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
517
581
  ),
518
582
  );
519
583
 
520
- // Determine the proposer status from the six-case taxonomy.
584
+ // Determine the proposer status from the six-case taxonomy. A valid proposal that never reached L1 (case 5) is
585
+ // refined into `checkpoint-unpublished` (5a) or `checkpoint-orphaned` (5b) when the evidence allows it.
521
586
  const reexecutionOutcome = this.reexecutionTracker.getOutcomeForSlot(slot);
522
587
  let status:
523
588
  | 'checkpoint-mined'
589
+ | 'checkpoint-unpublished'
590
+ | 'checkpoint-orphaned'
524
591
  | 'checkpoint-valid'
525
592
  | 'checkpoint-invalid'
526
593
  | 'checkpoint-unvalidated'
527
594
  | 'checkpoint-missed'
528
595
  | 'blocks-missed';
596
+ let classification: UnpublishedProposalClassification | undefined;
529
597
  if (checkpoint) {
530
598
  status = 'checkpoint-mined';
531
599
  } else if (reexecutionOutcome === 'valid') {
532
- status = 'checkpoint-valid';
600
+ classification = await this.classifyUnpublishedProposal(slot, proposer, committee, p2pAttested);
601
+ status = classification.status;
533
602
  } else if (reexecutionOutcome === 'invalid') {
534
603
  status = 'checkpoint-invalid';
535
604
  } else if (reexecutionOutcome === 'unvalidated') {
@@ -546,12 +615,17 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
546
615
  // A local `invalid` verdict vetoes them even for a mined checkpoint (an honest validator refuses
547
616
  // to sign what it re-executed as invalid), and so does a proposal equivocation in the slot: with
548
617
  // two conflicting proposals an honest attestor may have seen an invalid one and correctly
549
- // declined, so neither must count as a missed attestor (inactivity slashing).
618
+ // declined, so neither must count as a missed attestor (inactivity slashing). Nor does a proposal that conflicts
619
+ // with a checkpoint that landed on L1 (see excusesMissingAttestations).
550
620
  const hasEquivocation = this.reexecutionTracker.hasEquivocation(slot);
551
621
  const attestorsExpected =
552
- (status === 'checkpoint-mined' || status === 'checkpoint-valid') &&
622
+ (status === 'checkpoint-mined' ||
623
+ status === 'checkpoint-unpublished' ||
624
+ status === 'checkpoint-orphaned' ||
625
+ status === 'checkpoint-valid') &&
553
626
  reexecutionOutcome !== 'invalid' &&
554
- !hasEquivocation;
627
+ !hasEquivocation &&
628
+ !excusesMissingAttestations(classification);
555
629
  const missedAttestors = new Set(
556
630
  attestorsExpected
557
631
  ? committee.filter(v => !attestors.has(v.toString()) && !proposer.equals(v)).map(v => v.toString())
@@ -584,6 +658,105 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
584
658
  return Object.fromEntries(committee.map(v => v.toString()).map(who => [who, statusFor(who)]));
585
659
  }
586
660
 
661
+ /**
662
+ * Refines case 5 (a checkpoint proposal for the slot re-executed as valid, with no checkpoint for the slot on L1)
663
+ * into whose fault it was that the proposal did not land:
664
+ *
665
+ * - `checkpoint-orphaned` (5b) when other proposers prevented it: the parent it built on never landed on L1 (it is
666
+ * missing, or a different checkpoint holds the parent's position), or, with a quorum of attestations, an earlier
667
+ * slot's checkpoint landed with its number after it built on the previous one (`unexpected-parent-appeared`).
668
+ * The latter happens when the earlier slot's proposal reached this proposer too late to build on.
669
+ * - `checkpoint-unpublished` (5a) when its parent is on L1 and a quorum of the committee attested, so the proposer
670
+ * had everything it needed to publish. A later slot that lands the same checkpoint number does not exempt it:
671
+ * that slot only got the number because this proposer failed to land it.
672
+ * - `checkpoint-valid` (5) otherwise: the slot had an equivocation, the tracker has no parent or payload data to
673
+ * judge by, or attestations fell short of quorum. Short of quorum is a fault of the attestors, who are tagged
674
+ * `attestation-missed`, except after an unexpected parent appeared (`unexpected-parent-appeared`), when
675
+ * refusing to attest was correct.
676
+ *
677
+ * Only attestations for the proposal's own payload count toward quorum, since those are the only ones its proposer
678
+ * could have published.
679
+ */
680
+ protected async classifyUnpublishedProposal(
681
+ slot: SlotNumber,
682
+ proposer: EthAddress,
683
+ committee: EthAddress[],
684
+ p2pAttested: CheckpointAttestation[],
685
+ ): Promise<UnpublishedProposalClassification> {
686
+ if (this.reexecutionTracker.hasEquivocation(slot)) {
687
+ return { status: 'checkpoint-valid', reason: 'equivocation' };
688
+ }
689
+ const record = this.reexecutionTracker.getRecordForSlot(slot);
690
+ const checkpointNumber = record?.checkpointNumber;
691
+ const lastArchiveRoot = record?.lastArchiveRoot;
692
+ const payloadHash = record?.payloadHash;
693
+ if (checkpointNumber === undefined || lastArchiveRoot === undefined || payloadHash === undefined) {
694
+ return { status: 'checkpoint-valid', reason: 'no-proposal-data' };
695
+ }
696
+
697
+ const logData = { slot, proposer: proposer.toString(), checkpointNumber };
698
+ const orphaned = (
699
+ reason: OrphanedProposalReason,
700
+ extra: Record<string, unknown> = {},
701
+ ): UnpublishedProposalClassification => {
702
+ this.logger.verbose(
703
+ `Valid checkpoint proposal for slot ${slot} was orphaned (${reason}): ${ORPHANED_PROPOSAL_REASON_DESCRIPTIONS[reason]}`,
704
+ { ...logData, ...extra, reason },
705
+ );
706
+ return { status: 'checkpoint-orphaned', reason };
707
+ };
708
+
709
+ // The first checkpoint builds on genesis, which is always on L1.
710
+ const parentNumber = checkpointNumber - 1;
711
+ if (parentNumber >= INITIAL_CHECKPOINT_NUMBER) {
712
+ const parent = await this.archiver.getCheckpointData({ number: CheckpointNumber(parentNumber) });
713
+ if (!parent) {
714
+ return orphaned('parent-not-on-l1');
715
+ }
716
+ if (parent.archive.root.toString() !== lastArchiveRoot) {
717
+ return orphaned('parent-hash-mismatch');
718
+ }
719
+ }
720
+
721
+ // The proposer's own attestation counts, as it does when its sequencer collects attestations for L1.
722
+ const committeeMembers = new Set(committee.map(member => member.toString()));
723
+ const attestationCount = new Set(
724
+ p2pAttested
725
+ .filter(attestation => attestation.getPayloadHash() === payloadHash)
726
+ .map(attestation => attestation.getSender()?.toString())
727
+ .filter(attestor => attestor !== undefined && committeeMembers.has(attestor)),
728
+ ).size;
729
+ const quorum = computeQuorum(committee.length);
730
+
731
+ // A checkpoint landed for the slot itself would have made this case 6, so a holder of the same number comes from
732
+ // an earlier or a later slot. Only an earlier one can have stopped this proposal from landing.
733
+ const sameNumber = await this.archiver.getCheckpointData({ number: checkpointNumber });
734
+ const sameNumberSlot = sameNumber?.header.slotNumber;
735
+ if (sameNumberSlot !== undefined && sameNumberSlot < slot) {
736
+ const conflictData = { takenBySlot: sameNumberSlot, attestationCount, quorum };
737
+ if (attestationCount < quorum) {
738
+ this.logger.verbose(
739
+ `Valid checkpoint proposal for slot ${slot} fell short of quorum after checkpoint ${checkpointNumber} landed ` +
740
+ `from earlier slot ${sameNumberSlot} (unexpected-parent-appeared), so missing attestations are not counted`,
741
+ { ...logData, ...conflictData, reason: 'unexpected-parent-appeared' },
742
+ );
743
+ return { status: 'checkpoint-valid', reason: 'unexpected-parent-appeared' };
744
+ }
745
+ return orphaned('unexpected-parent-appeared', conflictData);
746
+ }
747
+
748
+ if (attestationCount < quorum) {
749
+ return { status: 'checkpoint-valid', reason: 'below-quorum' };
750
+ }
751
+
752
+ this.logger.info(`Proposer did not publish checkpoint ${checkpointNumber} for slot ${slot} despite quorum`, {
753
+ ...logData,
754
+ attestationCount,
755
+ quorum,
756
+ });
757
+ return { status: 'checkpoint-unpublished' };
758
+ }
759
+
587
760
  /** Push the status for each slot for each validator. */
588
761
  protected updateValidators(slot: SlotNumber, stats: Record<`0x${string}`, ValidatorStatusInSlot | undefined>) {
589
762
  return this.store.updateValidators(slot, stats);
@@ -685,7 +858,15 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
685
858
  ): ValidatorStats {
686
859
  let history = fromSlot ? allHistory.filter(h => BigInt(h.slot) >= fromSlot) : allHistory;
687
860
  history = toSlot ? history.filter(h => BigInt(h.slot) <= toSlot) : history;
688
- const lastProposal = history.filter(h => h.status === 'checkpoint-valid' || h.status === 'checkpoint-mined').at(-1);
861
+ const lastProposal = history
862
+ .filter(
863
+ h =>
864
+ h.status === 'checkpoint-valid' ||
865
+ h.status === 'checkpoint-unpublished' ||
866
+ h.status === 'checkpoint-orphaned' ||
867
+ h.status === 'checkpoint-mined',
868
+ )
869
+ .at(-1);
689
870
  const lastAttestation = history.filter(h => h.status === 'attestation-sent').at(-1);
690
871
  return {
691
872
  address: EthAddress.fromString(address),
@@ -175,6 +175,10 @@ export class SentinelStore {
175
175
  return 7;
176
176
  case 'checkpoint-unvalidated':
177
177
  return 8;
178
+ case 'checkpoint-unpublished':
179
+ return 9;
180
+ case 'checkpoint-orphaned':
181
+ return 10;
178
182
  default: {
179
183
  const _exhaustive: never = status;
180
184
  throw new Error(`Unknown status: ${status}`);
@@ -200,6 +204,10 @@ export class SentinelStore {
200
204
  return 'checkpoint-invalid';
201
205
  case 8:
202
206
  return 'checkpoint-unvalidated';
207
+ case 9:
208
+ return 'checkpoint-unpublished';
209
+ case 10:
210
+ return 'checkpoint-orphaned';
203
211
  default:
204
212
  throw new Error(`Unknown status: ${status}`);
205
213
  }