@aztec/aztec-node 0.0.1-commit.fce3e4f → 0.0.1-commit.ff7989d6c
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.
- package/dest/aztec-node/config.d.ts +11 -5
- package/dest/aztec-node/config.d.ts.map +1 -1
- package/dest/aztec-node/config.js +17 -3
- package/dest/aztec-node/node_metrics.d.ts +1 -1
- package/dest/aztec-node/node_metrics.d.ts.map +1 -1
- package/dest/aztec-node/node_metrics.js +9 -16
- package/dest/aztec-node/server.d.ts +65 -122
- package/dest/aztec-node/server.d.ts.map +1 -1
- package/dest/aztec-node/server.js +769 -216
- package/dest/sentinel/factory.d.ts +1 -1
- package/dest/sentinel/factory.d.ts.map +1 -1
- package/dest/sentinel/factory.js +1 -1
- package/dest/sentinel/sentinel.d.ts +6 -5
- package/dest/sentinel/sentinel.d.ts.map +1 -1
- package/dest/sentinel/sentinel.js +82 -51
- package/dest/sentinel/store.d.ts +2 -2
- package/dest/sentinel/store.d.ts.map +1 -1
- package/dest/sentinel/store.js +11 -7
- package/package.json +30 -28
- package/src/aztec-node/config.ts +34 -14
- package/src/aztec-node/node_metrics.ts +6 -17
- package/src/aztec-node/server.ts +488 -288
- package/src/sentinel/factory.ts +1 -6
- package/src/sentinel/sentinel.ts +94 -52
- package/src/sentinel/store.ts +12 -12
package/src/sentinel/factory.ts
CHANGED
|
@@ -20,12 +20,7 @@ export async function createSentinel(
|
|
|
20
20
|
if (!config.sentinelEnabled) {
|
|
21
21
|
return undefined;
|
|
22
22
|
}
|
|
23
|
-
const kvStore = await createStore(
|
|
24
|
-
'sentinel',
|
|
25
|
-
SentinelStore.SCHEMA_VERSION,
|
|
26
|
-
config,
|
|
27
|
-
createLogger('node:sentinel:lmdb'),
|
|
28
|
-
);
|
|
23
|
+
const kvStore = await createStore('sentinel', SentinelStore.SCHEMA_VERSION, config, logger.getBindings());
|
|
29
24
|
const storeHistoryLength = config.sentinelHistoryLengthInEpochs * epochCache.getL1Constants().epochDuration;
|
|
30
25
|
const storeHistoricProvenPerformanceLength = config.sentinelHistoricProvenPerformanceLengthInEpochs;
|
|
31
26
|
const sentinelStore = new SentinelStore(kvStore, {
|
package/src/sentinel/sentinel.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
-
import { EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
2
|
+
import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
3
3
|
import { countWhile, filterAsync, fromEntries, getEntries, mapValues } from '@aztec/foundation/collection';
|
|
4
4
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
5
5
|
import { createLogger } from '@aztec/foundation/log';
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
L2BlockStream,
|
|
20
20
|
type L2BlockStreamEvent,
|
|
21
21
|
type L2BlockStreamEventHandler,
|
|
22
|
-
|
|
22
|
+
getAttestationInfoFromPublishedCheckpoint,
|
|
23
23
|
} from '@aztec/stdlib/block';
|
|
24
24
|
import { getEpochAtSlot, getSlotRangeForEpoch, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
25
25
|
import type {
|
|
@@ -36,6 +36,17 @@ import EventEmitter from 'node:events';
|
|
|
36
36
|
|
|
37
37
|
import { SentinelStore } from './store.js';
|
|
38
38
|
|
|
39
|
+
/** Maps a validator status to its category: proposer or attestation. */
|
|
40
|
+
function statusToCategory(status: ValidatorStatusInSlot): ValidatorStatusType {
|
|
41
|
+
switch (status) {
|
|
42
|
+
case 'attestation-sent':
|
|
43
|
+
case 'attestation-missed':
|
|
44
|
+
return 'attestation';
|
|
45
|
+
default:
|
|
46
|
+
return 'proposer';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
39
50
|
export class Sentinel extends (EventEmitter as new () => WatcherEmitter) implements L2BlockStreamEventHandler, Watcher {
|
|
40
51
|
protected runningPromise: RunningPromise;
|
|
41
52
|
protected blockStream!: L2BlockStream;
|
|
@@ -44,8 +55,10 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
44
55
|
protected initialSlot: SlotNumber | undefined;
|
|
45
56
|
protected lastProcessedSlot: SlotNumber | undefined;
|
|
46
57
|
// eslint-disable-next-line aztec-custom/no-non-primitive-in-collections
|
|
47
|
-
protected
|
|
48
|
-
|
|
58
|
+
protected slotNumberToCheckpoint: Map<
|
|
59
|
+
SlotNumber,
|
|
60
|
+
{ checkpointNumber: CheckpointNumber; archive: string; attestors: EthAddress[] }
|
|
61
|
+
> = new Map();
|
|
49
62
|
|
|
50
63
|
constructor(
|
|
51
64
|
protected epochCache: EpochCache,
|
|
@@ -76,7 +89,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
76
89
|
/** Loads initial slot and initializes blockstream. We will not process anything at or before the initial slot. */
|
|
77
90
|
protected async init() {
|
|
78
91
|
this.initialSlot = this.epochCache.getEpochAndSlotNow().slot;
|
|
79
|
-
const startingBlock = await this.archiver.getBlockNumber();
|
|
92
|
+
const startingBlock = BlockNumber(await this.archiver.getBlockNumber());
|
|
80
93
|
this.logger.info(`Starting validator sentinel with initial slot ${this.initialSlot} and block ${startingBlock}`);
|
|
81
94
|
this.blockStream = new L2BlockStream(this.archiver, this.l2TipsStore, this, this.logger, { startingBlock });
|
|
82
95
|
}
|
|
@@ -87,47 +100,54 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
87
100
|
|
|
88
101
|
public async handleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void> {
|
|
89
102
|
await this.l2TipsStore.handleBlockStreamEvent(event);
|
|
90
|
-
if (event.type === '
|
|
91
|
-
|
|
92
|
-
for (const block of event.blocks) {
|
|
93
|
-
this.slotNumberToBlock.set(block.block.header.getSlot(), {
|
|
94
|
-
blockNumber: block.block.number,
|
|
95
|
-
archive: block.block.archive.root.toString(),
|
|
96
|
-
attestors: getAttestationInfoFromPublishedL2Block(block)
|
|
97
|
-
.filter(a => a.status === 'recovered-from-signature')
|
|
98
|
-
.map(a => a.address!),
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Prune the archive map to only keep at most N entries
|
|
103
|
-
const historyLength = this.store.getHistoryLength();
|
|
104
|
-
if (this.slotNumberToBlock.size > historyLength) {
|
|
105
|
-
const toDelete = Array.from(this.slotNumberToBlock.keys())
|
|
106
|
-
.sort((a, b) => Number(a - b))
|
|
107
|
-
.slice(0, this.slotNumberToBlock.size - historyLength);
|
|
108
|
-
for (const key of toDelete) {
|
|
109
|
-
this.slotNumberToBlock.delete(key);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
103
|
+
if (event.type === 'chain-checkpointed') {
|
|
104
|
+
this.handleCheckpoint(event);
|
|
112
105
|
} else if (event.type === 'chain-proven') {
|
|
113
106
|
await this.handleChainProven(event);
|
|
114
107
|
}
|
|
115
108
|
}
|
|
116
109
|
|
|
110
|
+
protected handleCheckpoint(event: L2BlockStreamEvent) {
|
|
111
|
+
if (event.type !== 'chain-checkpointed') {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const checkpoint = event.checkpoint;
|
|
115
|
+
|
|
116
|
+
// Store mapping from slot to archive, checkpoint number, and attestors
|
|
117
|
+
this.slotNumberToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, {
|
|
118
|
+
checkpointNumber: checkpoint.checkpoint.number,
|
|
119
|
+
archive: checkpoint.checkpoint.archive.root.toString(),
|
|
120
|
+
attestors: getAttestationInfoFromPublishedCheckpoint(checkpoint)
|
|
121
|
+
.filter(a => a.status === 'recovered-from-signature')
|
|
122
|
+
.map(a => a.address!),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Prune the archive map to only keep at most N entries
|
|
126
|
+
const historyLength = this.store.getHistoryLength();
|
|
127
|
+
if (this.slotNumberToCheckpoint.size > historyLength) {
|
|
128
|
+
const toDelete = Array.from(this.slotNumberToCheckpoint.keys())
|
|
129
|
+
.sort((a, b) => Number(a - b))
|
|
130
|
+
.slice(0, this.slotNumberToCheckpoint.size - historyLength);
|
|
131
|
+
for (const key of toDelete) {
|
|
132
|
+
this.slotNumberToCheckpoint.delete(key);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
117
137
|
protected async handleChainProven(event: L2BlockStreamEvent) {
|
|
118
138
|
if (event.type !== 'chain-proven') {
|
|
119
139
|
return;
|
|
120
140
|
}
|
|
121
141
|
const blockNumber = event.block.number;
|
|
122
|
-
const
|
|
123
|
-
if (!
|
|
124
|
-
this.logger.error(`Failed to get block ${blockNumber}
|
|
142
|
+
const header = await this.archiver.getBlockHeader(blockNumber);
|
|
143
|
+
if (!header) {
|
|
144
|
+
this.logger.error(`Failed to get block header ${blockNumber}`);
|
|
125
145
|
return;
|
|
126
146
|
}
|
|
127
147
|
|
|
128
148
|
// TODO(palla/slash): We should only be computing proven performance if this is
|
|
129
149
|
// a full proof epoch and not a partial one, otherwise we'll end up with skewed stats.
|
|
130
|
-
const epoch = getEpochAtSlot(
|
|
150
|
+
const epoch = getEpochAtSlot(header.getSlot(), this.epochCache.getL1Constants());
|
|
131
151
|
this.logger.debug(`Computing proven performance for epoch ${epoch}`);
|
|
132
152
|
const performance = await this.computeProvenPerformance(epoch);
|
|
133
153
|
this.logger.info(`Computed proven performance for epoch ${epoch}`, performance);
|
|
@@ -138,7 +158,11 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
138
158
|
|
|
139
159
|
protected async computeProvenPerformance(epoch: EpochNumber): Promise<ValidatorsEpochPerformance> {
|
|
140
160
|
const [fromSlot, toSlot] = getSlotRangeForEpoch(epoch, this.epochCache.getL1Constants());
|
|
141
|
-
const { committee } = await this.epochCache.getCommittee(fromSlot);
|
|
161
|
+
const { committee, isEscapeHatchOpen } = await this.epochCache.getCommittee(fromSlot);
|
|
162
|
+
if (isEscapeHatchOpen) {
|
|
163
|
+
this.logger.info(`Skipping proven performance for epoch ${epoch} - escape hatch is open`);
|
|
164
|
+
return {};
|
|
165
|
+
}
|
|
142
166
|
if (!committee) {
|
|
143
167
|
this.logger.trace(`No committee found for slot ${fromSlot}`);
|
|
144
168
|
return {};
|
|
@@ -291,8 +315,8 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
291
315
|
return false;
|
|
292
316
|
}
|
|
293
317
|
|
|
294
|
-
const archiverLastBlockHash = await this.l2TipsStore.getL2Tips().then(tip => tip.
|
|
295
|
-
const p2pLastBlockHash = await this.p2p.getL2Tips().then(tips => tips.
|
|
318
|
+
const archiverLastBlockHash = await this.l2TipsStore.getL2Tips().then(tip => tip.proposed.hash);
|
|
319
|
+
const p2pLastBlockHash = await this.p2p.getL2Tips().then(tips => tips.proposed.hash);
|
|
296
320
|
const isP2pSynced = archiverLastBlockHash === p2pLastBlockHash;
|
|
297
321
|
if (!isP2pSynced) {
|
|
298
322
|
this.logger.debug(`Waiting for P2P client to sync with archiver`, { archiverLastBlockHash, p2pLastBlockHash });
|
|
@@ -307,7 +331,12 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
307
331
|
* and updates overall stats.
|
|
308
332
|
*/
|
|
309
333
|
protected async processSlot(slot: SlotNumber) {
|
|
310
|
-
const { epoch, seed, committee } = await this.epochCache.getCommittee(slot);
|
|
334
|
+
const { epoch, seed, committee, isEscapeHatchOpen } = await this.epochCache.getCommittee(slot);
|
|
335
|
+
if (isEscapeHatchOpen) {
|
|
336
|
+
this.logger.info(`Skipping slot ${slot} at epoch ${epoch} - escape hatch is open`);
|
|
337
|
+
this.lastProcessedSlot = slot;
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
311
340
|
if (!committee || committee.length === 0) {
|
|
312
341
|
this.logger.trace(`No committee found for slot ${slot} at epoch ${epoch}`);
|
|
313
342
|
this.lastProcessedSlot = slot;
|
|
@@ -327,16 +356,16 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
327
356
|
|
|
328
357
|
// Check if there is an L2 block in L1 for this L2 slot
|
|
329
358
|
|
|
330
|
-
// Here we get all attestations for the
|
|
331
|
-
// or all attestations for all proposals in the slot if no
|
|
359
|
+
// Here we get all checkpoint attestations for the checkpoint at the given slot,
|
|
360
|
+
// or all checkpoint attestations for all proposals in the slot if no checkpoint was mined.
|
|
332
361
|
// We gather from both p2p (contains the ones seen on the p2p layer) and archiver
|
|
333
|
-
// (contains the ones synced from mined
|
|
334
|
-
const
|
|
335
|
-
const p2pAttested = await this.p2p.
|
|
362
|
+
// (contains the ones synced from mined checkpoints, which we may have missed from p2p).
|
|
363
|
+
const checkpoint = this.slotNumberToCheckpoint.get(slot);
|
|
364
|
+
const p2pAttested = await this.p2p.getCheckpointAttestationsForSlot(slot, checkpoint?.archive);
|
|
336
365
|
// Filter out attestations with invalid signatures
|
|
337
366
|
const p2pAttestors = p2pAttested.map(a => a.getSender()).filter((s): s is EthAddress => s !== undefined);
|
|
338
367
|
const attestors = new Set(
|
|
339
|
-
[...p2pAttestors.map(a => a.toString()), ...(
|
|
368
|
+
[...p2pAttestors.map(a => a.toString()), ...(checkpoint?.attestors.map(a => a.toString()) ?? [])].filter(
|
|
340
369
|
addr => proposer.toString() !== addr, // Exclude the proposer from the attestors
|
|
341
370
|
),
|
|
342
371
|
);
|
|
@@ -347,20 +376,29 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
347
376
|
// But we'll leave that corner case out to reduce pressure on the node.
|
|
348
377
|
// TODO(palla/slash): This breaks if a given node has more than one validator in the current committee,
|
|
349
378
|
// since they will attest to their own proposal it even if it's not re-executable.
|
|
350
|
-
|
|
351
|
-
|
|
379
|
+
let status: 'checkpoint-mined' | 'checkpoint-proposed' | 'checkpoint-missed' | 'blocks-missed';
|
|
380
|
+
if (checkpoint) {
|
|
381
|
+
status = 'checkpoint-mined';
|
|
382
|
+
} else if (attestors.size > 0) {
|
|
383
|
+
status = 'checkpoint-proposed';
|
|
384
|
+
} else {
|
|
385
|
+
// No checkpoint on L1 and no checkpoint attestations seen. Check if block proposals were sent for this slot.
|
|
386
|
+
const hasBlockProposals = await this.p2p.hasBlockProposalsForSlot(slot);
|
|
387
|
+
status = hasBlockProposals ? 'checkpoint-missed' : 'blocks-missed';
|
|
388
|
+
}
|
|
389
|
+
this.logger.debug(`Checkpoint status for slot ${slot}: ${status}`, { ...checkpoint, slot });
|
|
352
390
|
|
|
353
|
-
// Get attestors that failed their
|
|
391
|
+
// Get attestors that failed their checkpoint attestation duties, but only if there was a checkpoint proposed or mined
|
|
354
392
|
const missedAttestors = new Set(
|
|
355
|
-
|
|
393
|
+
status === 'blocks-missed' || status === 'checkpoint-missed'
|
|
356
394
|
? []
|
|
357
395
|
: committee.filter(v => !attestors.has(v.toString()) && !proposer.equals(v)).map(v => v.toString()),
|
|
358
396
|
);
|
|
359
397
|
|
|
360
398
|
this.logger.debug(`Retrieved ${attestors.size} attestors out of ${committee.length} for slot ${slot}`, {
|
|
361
|
-
|
|
399
|
+
status,
|
|
362
400
|
proposer: proposer.toString(),
|
|
363
|
-
...
|
|
401
|
+
...checkpoint,
|
|
364
402
|
slot,
|
|
365
403
|
attestors: [...attestors],
|
|
366
404
|
missedAttestors: [...missedAttestors],
|
|
@@ -370,7 +408,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
370
408
|
// Compute the status for each validator in the committee
|
|
371
409
|
const statusFor = (who: `0x${string}`): ValidatorStatusInSlot | undefined => {
|
|
372
410
|
if (who === proposer.toString()) {
|
|
373
|
-
return
|
|
411
|
+
return status;
|
|
374
412
|
} else if (attestors.has(who)) {
|
|
375
413
|
return 'attestation-sent';
|
|
376
414
|
} else if (missedAttestors.has(who)) {
|
|
@@ -463,14 +501,16 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
463
501
|
): ValidatorStats {
|
|
464
502
|
let history = fromSlot ? allHistory.filter(h => BigInt(h.slot) >= fromSlot) : allHistory;
|
|
465
503
|
history = toSlot ? history.filter(h => BigInt(h.slot) <= toSlot) : history;
|
|
466
|
-
const lastProposal = history
|
|
504
|
+
const lastProposal = history
|
|
505
|
+
.filter(h => h.status === 'checkpoint-proposed' || h.status === 'checkpoint-mined')
|
|
506
|
+
.at(-1);
|
|
467
507
|
const lastAttestation = history.filter(h => h.status === 'attestation-sent').at(-1);
|
|
468
508
|
return {
|
|
469
509
|
address: EthAddress.fromString(address),
|
|
470
510
|
lastProposal: this.computeFromSlot(lastProposal?.slot),
|
|
471
511
|
lastAttestation: this.computeFromSlot(lastAttestation?.slot),
|
|
472
512
|
totalSlots: history.length,
|
|
473
|
-
missedProposals: this.computeMissed(history, '
|
|
513
|
+
missedProposals: this.computeMissed(history, 'proposer', ['checkpoint-missed', 'blocks-missed']),
|
|
474
514
|
missedAttestations: this.computeMissed(history, 'attestation', ['attestation-missed']),
|
|
475
515
|
history,
|
|
476
516
|
};
|
|
@@ -478,10 +518,12 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
478
518
|
|
|
479
519
|
protected computeMissed(
|
|
480
520
|
history: ValidatorStatusHistory,
|
|
481
|
-
|
|
521
|
+
computeOverCategory: ValidatorStatusType | undefined,
|
|
482
522
|
filter: ValidatorStatusInSlot[],
|
|
483
523
|
) {
|
|
484
|
-
const relevantHistory = history.filter(
|
|
524
|
+
const relevantHistory = history.filter(
|
|
525
|
+
h => !computeOverCategory || statusToCategory(h.status) === computeOverCategory,
|
|
526
|
+
);
|
|
485
527
|
const filteredHistory = relevantHistory.filter(h => filter.includes(h.status));
|
|
486
528
|
return {
|
|
487
529
|
currentStreak: countWhile([...relevantHistory].reverse(), h => filter.includes(h.status)),
|
package/src/sentinel/store.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
} from '@aztec/stdlib/validators';
|
|
10
10
|
|
|
11
11
|
export class SentinelStore {
|
|
12
|
-
public static readonly SCHEMA_VERSION =
|
|
12
|
+
public static readonly SCHEMA_VERSION = 3;
|
|
13
13
|
|
|
14
14
|
// a map from validator address to their ValidatorStatusHistory
|
|
15
15
|
private readonly historyMap: AztecAsyncMap<`0x${string}`, Buffer>;
|
|
@@ -86,11 +86,7 @@ export class SentinelStore {
|
|
|
86
86
|
});
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
private async pushValidatorStatusForSlot(
|
|
90
|
-
who: EthAddress,
|
|
91
|
-
slot: SlotNumber,
|
|
92
|
-
status: 'block-mined' | 'block-proposed' | 'block-missed' | 'attestation-sent' | 'attestation-missed',
|
|
93
|
-
) {
|
|
89
|
+
private async pushValidatorStatusForSlot(who: EthAddress, slot: SlotNumber, status: ValidatorStatusInSlot) {
|
|
94
90
|
await this.store.transactionAsync(async () => {
|
|
95
91
|
const currentHistory = (await this.getHistory(who)) ?? [];
|
|
96
92
|
const newHistory = [...currentHistory, { slot, status }].slice(-this.config.historyLength);
|
|
@@ -149,16 +145,18 @@ export class SentinelStore {
|
|
|
149
145
|
|
|
150
146
|
private statusToNumber(status: ValidatorStatusInSlot): number {
|
|
151
147
|
switch (status) {
|
|
152
|
-
case '
|
|
148
|
+
case 'checkpoint-mined':
|
|
153
149
|
return 1;
|
|
154
|
-
case '
|
|
150
|
+
case 'checkpoint-proposed':
|
|
155
151
|
return 2;
|
|
156
|
-
case '
|
|
152
|
+
case 'checkpoint-missed':
|
|
157
153
|
return 3;
|
|
158
154
|
case 'attestation-sent':
|
|
159
155
|
return 4;
|
|
160
156
|
case 'attestation-missed':
|
|
161
157
|
return 5;
|
|
158
|
+
case 'blocks-missed':
|
|
159
|
+
return 6;
|
|
162
160
|
default: {
|
|
163
161
|
const _exhaustive: never = status;
|
|
164
162
|
throw new Error(`Unknown status: ${status}`);
|
|
@@ -169,15 +167,17 @@ export class SentinelStore {
|
|
|
169
167
|
private statusFromNumber(status: number): ValidatorStatusInSlot {
|
|
170
168
|
switch (status) {
|
|
171
169
|
case 1:
|
|
172
|
-
return '
|
|
170
|
+
return 'checkpoint-mined';
|
|
173
171
|
case 2:
|
|
174
|
-
return '
|
|
172
|
+
return 'checkpoint-proposed';
|
|
175
173
|
case 3:
|
|
176
|
-
return '
|
|
174
|
+
return 'checkpoint-missed';
|
|
177
175
|
case 4:
|
|
178
176
|
return 'attestation-sent';
|
|
179
177
|
case 5:
|
|
180
178
|
return 'attestation-missed';
|
|
179
|
+
case 6:
|
|
180
|
+
return 'blocks-missed';
|
|
181
181
|
default:
|
|
182
182
|
throw new Error(`Unknown status: ${status}`);
|
|
183
183
|
}
|