@aztec/aztec-node 3.0.0-devnet.2 → 3.0.0-devnet.20251212
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 +7 -2
- package/dest/aztec-node/config.d.ts.map +1 -1
- package/dest/aztec-node/config.js +12 -1
- package/dest/aztec-node/node_metrics.d.ts +5 -1
- package/dest/aztec-node/node_metrics.d.ts.map +1 -1
- package/dest/aztec-node/node_metrics.js +21 -0
- package/dest/aztec-node/server.d.ts +30 -36
- package/dest/aztec-node/server.d.ts.map +1 -1
- package/dest/aztec-node/server.js +64 -44
- package/dest/bin/index.d.ts +1 -1
- package/dest/index.d.ts +1 -1
- package/dest/sentinel/config.d.ts +1 -1
- package/dest/sentinel/factory.d.ts +1 -1
- package/dest/sentinel/index.d.ts +1 -1
- package/dest/sentinel/sentinel.d.ts +20 -19
- package/dest/sentinel/sentinel.d.ts.map +1 -1
- package/dest/sentinel/sentinel.js +24 -17
- package/dest/sentinel/store.d.ts +6 -5
- package/dest/sentinel/store.d.ts.map +1 -1
- package/dest/sentinel/store.js +3 -2
- package/dest/test/index.d.ts +1 -1
- package/package.json +28 -27
- package/src/aztec-node/config.ts +17 -6
- package/src/aztec-node/node_metrics.ts +28 -0
- package/src/aztec-node/server.ts +118 -93
- package/src/sentinel/sentinel.ts +48 -36
- package/src/sentinel/store.ts +11 -10
package/src/sentinel/sentinel.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
+
import { BlockNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
2
3
|
import { countWhile, filterAsync, fromEntries, getEntries, mapValues } from '@aztec/foundation/collection';
|
|
3
4
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
4
5
|
import { createLogger } from '@aztec/foundation/log';
|
|
@@ -40,9 +41,10 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
40
41
|
protected blockStream!: L2BlockStream;
|
|
41
42
|
protected l2TipsStore: L2TipsStore;
|
|
42
43
|
|
|
43
|
-
protected initialSlot:
|
|
44
|
-
protected lastProcessedSlot:
|
|
45
|
-
|
|
44
|
+
protected initialSlot: SlotNumber | undefined;
|
|
45
|
+
protected lastProcessedSlot: SlotNumber | undefined;
|
|
46
|
+
// eslint-disable-next-line aztec-custom/no-non-primitive-in-collections
|
|
47
|
+
protected slotNumberToBlock: Map<SlotNumber, { blockNumber: BlockNumber; archive: string; attestors: EthAddress[] }> =
|
|
46
48
|
new Map();
|
|
47
49
|
|
|
48
50
|
constructor(
|
|
@@ -74,7 +76,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
74
76
|
/** Loads initial slot and initializes blockstream. We will not process anything at or before the initial slot. */
|
|
75
77
|
protected async init() {
|
|
76
78
|
this.initialSlot = this.epochCache.getEpochAndSlotNow().slot;
|
|
77
|
-
const startingBlock = await this.archiver.getBlockNumber();
|
|
79
|
+
const startingBlock = BlockNumber(await this.archiver.getBlockNumber());
|
|
78
80
|
this.logger.info(`Starting validator sentinel with initial slot ${this.initialSlot} and block ${startingBlock}`);
|
|
79
81
|
this.blockStream = new L2BlockStream(this.archiver, this.l2TipsStore, this, this.logger, { startingBlock });
|
|
80
82
|
}
|
|
@@ -89,7 +91,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
89
91
|
// Store mapping from slot to archive, block number, and attestors
|
|
90
92
|
for (const block of event.blocks) {
|
|
91
93
|
this.slotNumberToBlock.set(block.block.header.getSlot(), {
|
|
92
|
-
blockNumber: block.block.number,
|
|
94
|
+
blockNumber: BlockNumber(block.block.number),
|
|
93
95
|
archive: block.block.archive.root.toString(),
|
|
94
96
|
attestors: getAttestationInfoFromPublishedL2Block(block)
|
|
95
97
|
.filter(a => a.status === 'recovered-from-signature')
|
|
@@ -116,7 +118,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
116
118
|
if (event.type !== 'chain-proven') {
|
|
117
119
|
return;
|
|
118
120
|
}
|
|
119
|
-
const blockNumber = event.block.number;
|
|
121
|
+
const blockNumber = BlockNumber(event.block.number);
|
|
120
122
|
const block = await this.archiver.getBlock(blockNumber);
|
|
121
123
|
if (!block) {
|
|
122
124
|
this.logger.error(`Failed to get block ${blockNumber}`, { block });
|
|
@@ -134,7 +136,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
134
136
|
await this.handleProvenPerformance(epoch, performance);
|
|
135
137
|
}
|
|
136
138
|
|
|
137
|
-
protected async computeProvenPerformance(epoch:
|
|
139
|
+
protected async computeProvenPerformance(epoch: EpochNumber): Promise<ValidatorsEpochPerformance> {
|
|
138
140
|
const [fromSlot, toSlot] = getSlotRangeForEpoch(epoch, this.epochCache.getL1Constants());
|
|
139
141
|
const { committee } = await this.epochCache.getCommittee(fromSlot);
|
|
140
142
|
if (!committee) {
|
|
@@ -142,7 +144,11 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
142
144
|
return {};
|
|
143
145
|
}
|
|
144
146
|
|
|
145
|
-
const stats = await this.computeStats({
|
|
147
|
+
const stats = await this.computeStats({
|
|
148
|
+
fromSlot,
|
|
149
|
+
toSlot,
|
|
150
|
+
validators: committee,
|
|
151
|
+
});
|
|
146
152
|
this.logger.debug(`Stats for epoch ${epoch}`, { ...stats, fromSlot, toSlot, epoch });
|
|
147
153
|
|
|
148
154
|
// Note that we are NOT using the total slots in the epoch as `total` here, since we only
|
|
@@ -165,7 +171,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
165
171
|
*/
|
|
166
172
|
protected async checkPastInactivity(
|
|
167
173
|
validator: EthAddress,
|
|
168
|
-
currentEpoch:
|
|
174
|
+
currentEpoch: EpochNumber,
|
|
169
175
|
requiredConsecutiveEpochs: number,
|
|
170
176
|
): Promise<boolean> {
|
|
171
177
|
if (requiredConsecutiveEpochs === 0) {
|
|
@@ -175,23 +181,24 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
175
181
|
// Get all historical performance for this validator
|
|
176
182
|
const allPerformance = await this.store.getProvenPerformance(validator);
|
|
177
183
|
|
|
184
|
+
// Sort by epoch descending to get most recent first, keep only epochs strictly before the current one, and get the first N
|
|
185
|
+
const pastEpochs = allPerformance.sort((a, b) => Number(b.epoch - a.epoch)).filter(p => p.epoch < currentEpoch);
|
|
186
|
+
|
|
178
187
|
// If we don't have enough historical data, don't slash
|
|
179
|
-
if (
|
|
188
|
+
if (pastEpochs.length < requiredConsecutiveEpochs) {
|
|
180
189
|
this.logger.debug(
|
|
181
190
|
`Not enough historical data for slashing ${validator} for inactivity (${allPerformance.length} epochs < ${requiredConsecutiveEpochs} required)`,
|
|
182
191
|
);
|
|
183
192
|
return false;
|
|
184
193
|
}
|
|
185
194
|
|
|
186
|
-
//
|
|
187
|
-
return
|
|
188
|
-
.sort((a, b) => Number(b.epoch - a.epoch))
|
|
189
|
-
.filter(p => p.epoch < currentEpoch)
|
|
195
|
+
// Check that we have at least requiredConsecutiveEpochs and that all of them are above the inactivity threshold
|
|
196
|
+
return pastEpochs
|
|
190
197
|
.slice(0, requiredConsecutiveEpochs)
|
|
191
198
|
.every(p => p.missed / p.total >= this.config.slashInactivityTargetPercentage);
|
|
192
199
|
}
|
|
193
200
|
|
|
194
|
-
protected async handleProvenPerformance(epoch:
|
|
201
|
+
protected async handleProvenPerformance(epoch: EpochNumber, performance: ValidatorsEpochPerformance) {
|
|
195
202
|
if (this.config.slashInactivityPenalty === 0n) {
|
|
196
203
|
return;
|
|
197
204
|
}
|
|
@@ -215,7 +222,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
215
222
|
validator: EthAddress.fromString(address),
|
|
216
223
|
amount: this.config.slashInactivityPenalty,
|
|
217
224
|
offenseType: OffenseType.INACTIVITY,
|
|
218
|
-
epochOrSlot: epoch,
|
|
225
|
+
epochOrSlot: BigInt(epoch),
|
|
219
226
|
}));
|
|
220
227
|
|
|
221
228
|
if (criminals.length > 0) {
|
|
@@ -256,8 +263,13 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
256
263
|
* We also don't move past the archiver last synced L2 slot, as we don't want to process data that is not yet available.
|
|
257
264
|
* Last, we check the p2p is synced with the archiver, so it has pulled all attestations from it.
|
|
258
265
|
*/
|
|
259
|
-
protected async isReadyToProcess(currentSlot:
|
|
260
|
-
|
|
266
|
+
protected async isReadyToProcess(currentSlot: SlotNumber): Promise<SlotNumber | false> {
|
|
267
|
+
if (currentSlot < 2) {
|
|
268
|
+
this.logger.trace(`Current slot ${currentSlot} too early.`);
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const targetSlot = SlotNumber(currentSlot - 2);
|
|
261
273
|
if (this.lastProcessedSlot && this.lastProcessedSlot >= targetSlot) {
|
|
262
274
|
this.logger.trace(`Already processed slot ${targetSlot}`, { lastProcessedSlot: this.lastProcessedSlot });
|
|
263
275
|
return false;
|
|
@@ -274,7 +286,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
274
286
|
}
|
|
275
287
|
|
|
276
288
|
const archiverSlot = await this.archiver.getL2SlotNumber();
|
|
277
|
-
if (archiverSlot < targetSlot) {
|
|
289
|
+
if (archiverSlot === undefined || archiverSlot < targetSlot) {
|
|
278
290
|
this.logger.debug(`Waiting for archiver to sync with L2 slot ${targetSlot}`, { archiverSlot, targetSlot });
|
|
279
291
|
return false;
|
|
280
292
|
}
|
|
@@ -294,7 +306,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
294
306
|
* Gathers committee and proposer data for a given slot, computes slot stats,
|
|
295
307
|
* and updates overall stats.
|
|
296
308
|
*/
|
|
297
|
-
protected async processSlot(slot:
|
|
309
|
+
protected async processSlot(slot: SlotNumber) {
|
|
298
310
|
const { epoch, seed, committee } = await this.epochCache.getCommittee(slot);
|
|
299
311
|
if (!committee || committee.length === 0) {
|
|
300
312
|
this.logger.trace(`No committee found for slot ${slot} at epoch ${epoch}`);
|
|
@@ -310,7 +322,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
310
322
|
}
|
|
311
323
|
|
|
312
324
|
/** Computes activity for a given slot. */
|
|
313
|
-
protected async getSlotActivity(slot:
|
|
325
|
+
protected async getSlotActivity(slot: SlotNumber, epoch: EpochNumber, proposer: EthAddress, committee: EthAddress[]) {
|
|
314
326
|
this.logger.debug(`Computing stats for slot ${slot} at epoch ${epoch}`, { slot, epoch, proposer, committee });
|
|
315
327
|
|
|
316
328
|
// Check if there is an L2 block in L1 for this L2 slot
|
|
@@ -372,7 +384,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
372
384
|
}
|
|
373
385
|
|
|
374
386
|
/** Push the status for each slot for each validator. */
|
|
375
|
-
protected updateValidators(slot:
|
|
387
|
+
protected updateValidators(slot: SlotNumber, stats: Record<`0x${string}`, ValidatorStatusInSlot | undefined>) {
|
|
376
388
|
return this.store.updateValidators(slot, stats);
|
|
377
389
|
}
|
|
378
390
|
|
|
@@ -381,13 +393,13 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
381
393
|
fromSlot,
|
|
382
394
|
toSlot,
|
|
383
395
|
validators,
|
|
384
|
-
}: { fromSlot?:
|
|
396
|
+
}: { fromSlot?: SlotNumber; toSlot?: SlotNumber; validators?: EthAddress[] } = {}): Promise<ValidatorsStats> {
|
|
385
397
|
const histories = validators
|
|
386
398
|
? fromEntries(await Promise.all(validators.map(async v => [v.toString(), await this.store.getHistory(v)])))
|
|
387
399
|
: await this.store.getHistories();
|
|
388
400
|
|
|
389
401
|
const slotNow = this.epochCache.getEpochAndSlotNow().slot;
|
|
390
|
-
fromSlot ??= (this.lastProcessedSlot ?? slotNow) -
|
|
402
|
+
fromSlot ??= SlotNumber(Math.max((this.lastProcessedSlot ?? slotNow) - this.store.getHistoryLength(), 0));
|
|
391
403
|
toSlot ??= this.lastProcessedSlot ?? slotNow;
|
|
392
404
|
|
|
393
405
|
const stats = mapValues(histories, (history, address) =>
|
|
@@ -405,8 +417,8 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
405
417
|
/** Computes stats for a single validator. */
|
|
406
418
|
public async getValidatorStats(
|
|
407
419
|
validatorAddress: EthAddress,
|
|
408
|
-
fromSlot?:
|
|
409
|
-
toSlot?:
|
|
420
|
+
fromSlot?: SlotNumber,
|
|
421
|
+
toSlot?: SlotNumber,
|
|
410
422
|
): Promise<SingleValidatorStats | undefined> {
|
|
411
423
|
const history = await this.store.getHistory(validatorAddress);
|
|
412
424
|
|
|
@@ -415,13 +427,14 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
415
427
|
}
|
|
416
428
|
|
|
417
429
|
const slotNow = this.epochCache.getEpochAndSlotNow().slot;
|
|
418
|
-
const effectiveFromSlot =
|
|
430
|
+
const effectiveFromSlot =
|
|
431
|
+
fromSlot ?? SlotNumber(Math.max((this.lastProcessedSlot ?? slotNow) - this.store.getHistoryLength(), 0));
|
|
419
432
|
const effectiveToSlot = toSlot ?? this.lastProcessedSlot ?? slotNow;
|
|
420
433
|
|
|
421
434
|
const historyLength = BigInt(this.store.getHistoryLength());
|
|
422
|
-
if (effectiveToSlot - effectiveFromSlot > historyLength) {
|
|
435
|
+
if (BigInt(effectiveToSlot) - BigInt(effectiveFromSlot) > historyLength) {
|
|
423
436
|
throw new Error(
|
|
424
|
-
`Slot range (${effectiveToSlot - effectiveFromSlot}) exceeds history length (${historyLength}). ` +
|
|
437
|
+
`Slot range (${BigInt(effectiveToSlot) - BigInt(effectiveFromSlot)}) exceeds history length (${historyLength}). ` +
|
|
425
438
|
`Requested range: ${effectiveFromSlot} to ${effectiveToSlot}.`,
|
|
426
439
|
);
|
|
427
440
|
}
|
|
@@ -432,11 +445,10 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
432
445
|
effectiveFromSlot,
|
|
433
446
|
effectiveToSlot,
|
|
434
447
|
);
|
|
435
|
-
const allTimeProvenPerformance = await this.store.getProvenPerformance(validatorAddress);
|
|
436
448
|
|
|
437
449
|
return {
|
|
438
450
|
validator,
|
|
439
|
-
allTimeProvenPerformance,
|
|
451
|
+
allTimeProvenPerformance: await this.store.getProvenPerformance(validatorAddress),
|
|
440
452
|
lastProcessedSlot: this.lastProcessedSlot,
|
|
441
453
|
initialSlot: this.initialSlot,
|
|
442
454
|
slotWindow: this.store.getHistoryLength(),
|
|
@@ -446,11 +458,11 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
446
458
|
protected computeStatsForValidator(
|
|
447
459
|
address: `0x${string}`,
|
|
448
460
|
allHistory: ValidatorStatusHistory,
|
|
449
|
-
fromSlot?:
|
|
450
|
-
toSlot?:
|
|
461
|
+
fromSlot?: SlotNumber,
|
|
462
|
+
toSlot?: SlotNumber,
|
|
451
463
|
): ValidatorStats {
|
|
452
|
-
let history = fromSlot ? allHistory.filter(h => h.slot >= fromSlot) : allHistory;
|
|
453
|
-
history = toSlot ? history.filter(h => h.slot <= toSlot) : history;
|
|
464
|
+
let history = fromSlot ? allHistory.filter(h => BigInt(h.slot) >= fromSlot) : allHistory;
|
|
465
|
+
history = toSlot ? history.filter(h => BigInt(h.slot) <= toSlot) : history;
|
|
454
466
|
const lastProposal = history.filter(h => h.status === 'block-proposed' || h.status === 'block-mined').at(-1);
|
|
455
467
|
const lastAttestation = history.filter(h => h.status === 'attestation-sent').at(-1);
|
|
456
468
|
return {
|
|
@@ -479,7 +491,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
479
491
|
};
|
|
480
492
|
}
|
|
481
493
|
|
|
482
|
-
protected computeFromSlot(slot:
|
|
494
|
+
protected computeFromSlot(slot: SlotNumber | undefined) {
|
|
483
495
|
if (slot === undefined) {
|
|
484
496
|
return undefined;
|
|
485
497
|
}
|
package/src/sentinel/store.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
1
2
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
2
3
|
import { BufferReader, numToUInt8, numToUInt32BE, serializeToBuffer } from '@aztec/foundation/serialize';
|
|
3
4
|
import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store';
|
|
@@ -33,7 +34,7 @@ export class SentinelStore {
|
|
|
33
34
|
return this.config.historicProvenPerformanceLength;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
public async updateProvenPerformance(epoch:
|
|
37
|
+
public async updateProvenPerformance(epoch: EpochNumber, performance: ValidatorsEpochPerformance) {
|
|
37
38
|
await this.store.transactionAsync(async () => {
|
|
38
39
|
for (const [who, { missed, total }] of Object.entries(performance)) {
|
|
39
40
|
await this.pushValidatorProvenPerformanceForEpoch({ who: EthAddress.fromString(who), missed, total, epoch });
|
|
@@ -41,7 +42,7 @@ export class SentinelStore {
|
|
|
41
42
|
});
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
public async getProvenPerformance(who: EthAddress): Promise<{ missed: number; total: number; epoch:
|
|
45
|
+
public async getProvenPerformance(who: EthAddress): Promise<{ missed: number; total: number; epoch: EpochNumber }[]> {
|
|
45
46
|
const currentPerformanceBuffer = await this.provenMap.getAsync(who.toString());
|
|
46
47
|
return currentPerformanceBuffer ? this.deserializePerformance(currentPerformanceBuffer) : [];
|
|
47
48
|
}
|
|
@@ -55,7 +56,7 @@ export class SentinelStore {
|
|
|
55
56
|
who: EthAddress;
|
|
56
57
|
missed: number;
|
|
57
58
|
total: number;
|
|
58
|
-
epoch:
|
|
59
|
+
epoch: EpochNumber;
|
|
59
60
|
}) {
|
|
60
61
|
const currentPerformance = await this.getProvenPerformance(who);
|
|
61
62
|
const existingIndex = currentPerformance.findIndex(p => p.epoch === epoch);
|
|
@@ -75,7 +76,7 @@ export class SentinelStore {
|
|
|
75
76
|
await this.provenMap.set(who.toString(), this.serializePerformance(performanceToKeep));
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
public async updateValidators(slot:
|
|
79
|
+
public async updateValidators(slot: SlotNumber, statuses: Record<`0x${string}`, ValidatorStatusInSlot | undefined>) {
|
|
79
80
|
await this.store.transactionAsync(async () => {
|
|
80
81
|
for (const [who, status] of Object.entries(statuses)) {
|
|
81
82
|
if (status) {
|
|
@@ -87,7 +88,7 @@ export class SentinelStore {
|
|
|
87
88
|
|
|
88
89
|
private async pushValidatorStatusForSlot(
|
|
89
90
|
who: EthAddress,
|
|
90
|
-
slot:
|
|
91
|
+
slot: SlotNumber,
|
|
91
92
|
status: 'block-mined' | 'block-proposed' | 'block-missed' | 'attestation-sent' | 'attestation-missed',
|
|
92
93
|
) {
|
|
93
94
|
await this.store.transactionAsync(async () => {
|
|
@@ -110,18 +111,18 @@ export class SentinelStore {
|
|
|
110
111
|
return data && this.deserializeHistory(data);
|
|
111
112
|
}
|
|
112
113
|
|
|
113
|
-
private serializePerformance(performance: { missed: number; total: number; epoch:
|
|
114
|
+
private serializePerformance(performance: { missed: number; total: number; epoch: EpochNumber }[]): Buffer {
|
|
114
115
|
return serializeToBuffer(
|
|
115
116
|
performance.map(p => [numToUInt32BE(Number(p.epoch)), numToUInt32BE(p.missed), numToUInt32BE(p.total)]),
|
|
116
117
|
);
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
private deserializePerformance(buffer: Buffer): { missed: number; total: number; epoch:
|
|
120
|
+
private deserializePerformance(buffer: Buffer): { missed: number; total: number; epoch: EpochNumber }[] {
|
|
120
121
|
const reader = new BufferReader(buffer);
|
|
121
|
-
const performance: { missed: number; total: number; epoch:
|
|
122
|
+
const performance: { missed: number; total: number; epoch: EpochNumber }[] = [];
|
|
122
123
|
while (!reader.isEmpty()) {
|
|
123
124
|
performance.push({
|
|
124
|
-
epoch:
|
|
125
|
+
epoch: EpochNumber(reader.readNumber()),
|
|
125
126
|
missed: reader.readNumber(),
|
|
126
127
|
total: reader.readNumber(),
|
|
127
128
|
});
|
|
@@ -139,7 +140,7 @@ export class SentinelStore {
|
|
|
139
140
|
const reader = new BufferReader(buffer);
|
|
140
141
|
const history: ValidatorStatusHistory = [];
|
|
141
142
|
while (!reader.isEmpty()) {
|
|
142
|
-
const slot =
|
|
143
|
+
const slot = SlotNumber(reader.readNumber());
|
|
143
144
|
const status = this.statusFromNumber(reader.readUInt8());
|
|
144
145
|
history.push({ slot, status });
|
|
145
146
|
}
|