@aztec/aztec-node 0.0.1-commit.b655e406 → 0.0.1-commit.d1f2d6c
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 +5 -2
- package/dest/aztec-node/config.d.ts.map +1 -1
- package/dest/aztec-node/config.js +7 -1
- 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 +5 -16
- package/dest/aztec-node/server.d.ts +54 -121
- package/dest/aztec-node/server.d.ts.map +1 -1
- package/dest/aztec-node/server.js +645 -199
- 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 +21 -19
- package/dest/sentinel/sentinel.d.ts.map +1 -1
- package/dest/sentinel/sentinel.js +51 -39
- package/dest/sentinel/store.d.ts +5 -4
- 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 +29 -28
- package/src/aztec-node/config.ts +12 -8
- package/src/aztec-node/node_metrics.ts +5 -23
- package/src/aztec-node/server.ts +322 -240
- package/src/sentinel/sentinel.ts +83 -62
- 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, CheckpointNumber, 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';
|
|
@@ -18,7 +19,7 @@ import {
|
|
|
18
19
|
L2BlockStream,
|
|
19
20
|
type L2BlockStreamEvent,
|
|
20
21
|
type L2BlockStreamEventHandler,
|
|
21
|
-
|
|
22
|
+
getAttestationInfoFromPublishedCheckpoint,
|
|
22
23
|
} from '@aztec/stdlib/block';
|
|
23
24
|
import { getEpochAtSlot, getSlotRangeForEpoch, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
24
25
|
import type {
|
|
@@ -40,10 +41,13 @@ 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
|
-
|
|
46
|
-
|
|
44
|
+
protected initialSlot: SlotNumber | undefined;
|
|
45
|
+
protected lastProcessedSlot: SlotNumber | undefined;
|
|
46
|
+
// eslint-disable-next-line aztec-custom/no-non-primitive-in-collections
|
|
47
|
+
protected slotNumberToCheckpoint: Map<
|
|
48
|
+
SlotNumber,
|
|
49
|
+
{ checkpointNumber: CheckpointNumber; archive: string; attestors: EthAddress[] }
|
|
50
|
+
> = new Map();
|
|
47
51
|
|
|
48
52
|
constructor(
|
|
49
53
|
protected epochCache: EpochCache,
|
|
@@ -74,7 +78,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
74
78
|
/** Loads initial slot and initializes blockstream. We will not process anything at or before the initial slot. */
|
|
75
79
|
protected async init() {
|
|
76
80
|
this.initialSlot = this.epochCache.getEpochAndSlotNow().slot;
|
|
77
|
-
const startingBlock = await this.archiver.getBlockNumber();
|
|
81
|
+
const startingBlock = BlockNumber(await this.archiver.getBlockNumber());
|
|
78
82
|
this.logger.info(`Starting validator sentinel with initial slot ${this.initialSlot} and block ${startingBlock}`);
|
|
79
83
|
this.blockStream = new L2BlockStream(this.archiver, this.l2TipsStore, this, this.logger, { startingBlock });
|
|
80
84
|
}
|
|
@@ -85,39 +89,46 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
85
89
|
|
|
86
90
|
public async handleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void> {
|
|
87
91
|
await this.l2TipsStore.handleBlockStreamEvent(event);
|
|
88
|
-
if (event.type === '
|
|
89
|
-
|
|
90
|
-
for (const block of event.blocks) {
|
|
91
|
-
this.slotNumberToBlock.set(block.block.header.getSlot(), {
|
|
92
|
-
blockNumber: block.block.number,
|
|
93
|
-
archive: block.block.archive.root.toString(),
|
|
94
|
-
attestors: getAttestationInfoFromPublishedL2Block(block)
|
|
95
|
-
.filter(a => a.status === 'recovered-from-signature')
|
|
96
|
-
.map(a => a.address!),
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Prune the archive map to only keep at most N entries
|
|
101
|
-
const historyLength = this.store.getHistoryLength();
|
|
102
|
-
if (this.slotNumberToBlock.size > historyLength) {
|
|
103
|
-
const toDelete = Array.from(this.slotNumberToBlock.keys())
|
|
104
|
-
.sort((a, b) => Number(a - b))
|
|
105
|
-
.slice(0, this.slotNumberToBlock.size - historyLength);
|
|
106
|
-
for (const key of toDelete) {
|
|
107
|
-
this.slotNumberToBlock.delete(key);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
92
|
+
if (event.type === 'chain-checkpointed') {
|
|
93
|
+
this.handleCheckpoint(event);
|
|
110
94
|
} else if (event.type === 'chain-proven') {
|
|
111
95
|
await this.handleChainProven(event);
|
|
112
96
|
}
|
|
113
97
|
}
|
|
114
98
|
|
|
99
|
+
protected handleCheckpoint(event: L2BlockStreamEvent) {
|
|
100
|
+
if (event.type !== 'chain-checkpointed') {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const checkpoint = event.checkpoint;
|
|
104
|
+
|
|
105
|
+
// Store mapping from slot to archive, checkpoint number, and attestors
|
|
106
|
+
this.slotNumberToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, {
|
|
107
|
+
checkpointNumber: checkpoint.checkpoint.number,
|
|
108
|
+
archive: checkpoint.checkpoint.archive.root.toString(),
|
|
109
|
+
attestors: getAttestationInfoFromPublishedCheckpoint(checkpoint)
|
|
110
|
+
.filter(a => a.status === 'recovered-from-signature')
|
|
111
|
+
.map(a => a.address!),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Prune the archive map to only keep at most N entries
|
|
115
|
+
const historyLength = this.store.getHistoryLength();
|
|
116
|
+
if (this.slotNumberToCheckpoint.size > historyLength) {
|
|
117
|
+
const toDelete = Array.from(this.slotNumberToCheckpoint.keys())
|
|
118
|
+
.sort((a, b) => Number(a - b))
|
|
119
|
+
.slice(0, this.slotNumberToCheckpoint.size - historyLength);
|
|
120
|
+
for (const key of toDelete) {
|
|
121
|
+
this.slotNumberToCheckpoint.delete(key);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
115
126
|
protected async handleChainProven(event: L2BlockStreamEvent) {
|
|
116
127
|
if (event.type !== 'chain-proven') {
|
|
117
128
|
return;
|
|
118
129
|
}
|
|
119
130
|
const blockNumber = event.block.number;
|
|
120
|
-
const block = await this.archiver.
|
|
131
|
+
const block = await this.archiver.getL2Block(blockNumber);
|
|
121
132
|
if (!block) {
|
|
122
133
|
this.logger.error(`Failed to get block ${blockNumber}`, { block });
|
|
123
134
|
return;
|
|
@@ -134,7 +145,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
134
145
|
await this.handleProvenPerformance(epoch, performance);
|
|
135
146
|
}
|
|
136
147
|
|
|
137
|
-
protected async computeProvenPerformance(epoch:
|
|
148
|
+
protected async computeProvenPerformance(epoch: EpochNumber): Promise<ValidatorsEpochPerformance> {
|
|
138
149
|
const [fromSlot, toSlot] = getSlotRangeForEpoch(epoch, this.epochCache.getL1Constants());
|
|
139
150
|
const { committee } = await this.epochCache.getCommittee(fromSlot);
|
|
140
151
|
if (!committee) {
|
|
@@ -142,7 +153,11 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
142
153
|
return {};
|
|
143
154
|
}
|
|
144
155
|
|
|
145
|
-
const stats = await this.computeStats({
|
|
156
|
+
const stats = await this.computeStats({
|
|
157
|
+
fromSlot,
|
|
158
|
+
toSlot,
|
|
159
|
+
validators: committee,
|
|
160
|
+
});
|
|
146
161
|
this.logger.debug(`Stats for epoch ${epoch}`, { ...stats, fromSlot, toSlot, epoch });
|
|
147
162
|
|
|
148
163
|
// Note that we are NOT using the total slots in the epoch as `total` here, since we only
|
|
@@ -165,7 +180,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
165
180
|
*/
|
|
166
181
|
protected async checkPastInactivity(
|
|
167
182
|
validator: EthAddress,
|
|
168
|
-
currentEpoch:
|
|
183
|
+
currentEpoch: EpochNumber,
|
|
169
184
|
requiredConsecutiveEpochs: number,
|
|
170
185
|
): Promise<boolean> {
|
|
171
186
|
if (requiredConsecutiveEpochs === 0) {
|
|
@@ -175,23 +190,24 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
175
190
|
// Get all historical performance for this validator
|
|
176
191
|
const allPerformance = await this.store.getProvenPerformance(validator);
|
|
177
192
|
|
|
193
|
+
// Sort by epoch descending to get most recent first, keep only epochs strictly before the current one, and get the first N
|
|
194
|
+
const pastEpochs = allPerformance.sort((a, b) => Number(b.epoch - a.epoch)).filter(p => p.epoch < currentEpoch);
|
|
195
|
+
|
|
178
196
|
// If we don't have enough historical data, don't slash
|
|
179
|
-
if (
|
|
197
|
+
if (pastEpochs.length < requiredConsecutiveEpochs) {
|
|
180
198
|
this.logger.debug(
|
|
181
199
|
`Not enough historical data for slashing ${validator} for inactivity (${allPerformance.length} epochs < ${requiredConsecutiveEpochs} required)`,
|
|
182
200
|
);
|
|
183
201
|
return false;
|
|
184
202
|
}
|
|
185
203
|
|
|
186
|
-
//
|
|
187
|
-
return
|
|
188
|
-
.sort((a, b) => Number(b.epoch - a.epoch))
|
|
189
|
-
.filter(p => p.epoch < currentEpoch)
|
|
204
|
+
// Check that we have at least requiredConsecutiveEpochs and that all of them are above the inactivity threshold
|
|
205
|
+
return pastEpochs
|
|
190
206
|
.slice(0, requiredConsecutiveEpochs)
|
|
191
207
|
.every(p => p.missed / p.total >= this.config.slashInactivityTargetPercentage);
|
|
192
208
|
}
|
|
193
209
|
|
|
194
|
-
protected async handleProvenPerformance(epoch:
|
|
210
|
+
protected async handleProvenPerformance(epoch: EpochNumber, performance: ValidatorsEpochPerformance) {
|
|
195
211
|
if (this.config.slashInactivityPenalty === 0n) {
|
|
196
212
|
return;
|
|
197
213
|
}
|
|
@@ -215,7 +231,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
215
231
|
validator: EthAddress.fromString(address),
|
|
216
232
|
amount: this.config.slashInactivityPenalty,
|
|
217
233
|
offenseType: OffenseType.INACTIVITY,
|
|
218
|
-
epochOrSlot: epoch,
|
|
234
|
+
epochOrSlot: BigInt(epoch),
|
|
219
235
|
}));
|
|
220
236
|
|
|
221
237
|
if (criminals.length > 0) {
|
|
@@ -256,8 +272,13 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
256
272
|
* 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
273
|
* Last, we check the p2p is synced with the archiver, so it has pulled all attestations from it.
|
|
258
274
|
*/
|
|
259
|
-
protected async isReadyToProcess(currentSlot:
|
|
260
|
-
|
|
275
|
+
protected async isReadyToProcess(currentSlot: SlotNumber): Promise<SlotNumber | false> {
|
|
276
|
+
if (currentSlot < 2) {
|
|
277
|
+
this.logger.trace(`Current slot ${currentSlot} too early.`);
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const targetSlot = SlotNumber(currentSlot - 2);
|
|
261
282
|
if (this.lastProcessedSlot && this.lastProcessedSlot >= targetSlot) {
|
|
262
283
|
this.logger.trace(`Already processed slot ${targetSlot}`, { lastProcessedSlot: this.lastProcessedSlot });
|
|
263
284
|
return false;
|
|
@@ -279,8 +300,8 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
279
300
|
return false;
|
|
280
301
|
}
|
|
281
302
|
|
|
282
|
-
const archiverLastBlockHash = await this.l2TipsStore.getL2Tips().then(tip => tip.
|
|
283
|
-
const p2pLastBlockHash = await this.p2p.getL2Tips().then(tips => tips.
|
|
303
|
+
const archiverLastBlockHash = await this.l2TipsStore.getL2Tips().then(tip => tip.proposed.hash);
|
|
304
|
+
const p2pLastBlockHash = await this.p2p.getL2Tips().then(tips => tips.proposed.hash);
|
|
284
305
|
const isP2pSynced = archiverLastBlockHash === p2pLastBlockHash;
|
|
285
306
|
if (!isP2pSynced) {
|
|
286
307
|
this.logger.debug(`Waiting for P2P client to sync with archiver`, { archiverLastBlockHash, p2pLastBlockHash });
|
|
@@ -294,7 +315,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
294
315
|
* Gathers committee and proposer data for a given slot, computes slot stats,
|
|
295
316
|
* and updates overall stats.
|
|
296
317
|
*/
|
|
297
|
-
protected async processSlot(slot:
|
|
318
|
+
protected async processSlot(slot: SlotNumber) {
|
|
298
319
|
const { epoch, seed, committee } = await this.epochCache.getCommittee(slot);
|
|
299
320
|
if (!committee || committee.length === 0) {
|
|
300
321
|
this.logger.trace(`No committee found for slot ${slot} at epoch ${epoch}`);
|
|
@@ -310,7 +331,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
310
331
|
}
|
|
311
332
|
|
|
312
333
|
/** Computes activity for a given slot. */
|
|
313
|
-
protected async getSlotActivity(slot:
|
|
334
|
+
protected async getSlotActivity(slot: SlotNumber, epoch: EpochNumber, proposer: EthAddress, committee: EthAddress[]) {
|
|
314
335
|
this.logger.debug(`Computing stats for slot ${slot} at epoch ${epoch}`, { slot, epoch, proposer, committee });
|
|
315
336
|
|
|
316
337
|
// Check if there is an L2 block in L1 for this L2 slot
|
|
@@ -319,8 +340,8 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
319
340
|
// or all attestations for all proposals in the slot if no block was mined.
|
|
320
341
|
// We gather from both p2p (contains the ones seen on the p2p layer) and archiver
|
|
321
342
|
// (contains the ones synced from mined blocks, which we may have missed from p2p).
|
|
322
|
-
const block = this.
|
|
323
|
-
const p2pAttested = await this.p2p.
|
|
343
|
+
const block = this.slotNumberToCheckpoint.get(slot);
|
|
344
|
+
const p2pAttested = await this.p2p.getCheckpointAttestationsForSlot(slot, block?.archive);
|
|
324
345
|
// Filter out attestations with invalid signatures
|
|
325
346
|
const p2pAttestors = p2pAttested.map(a => a.getSender()).filter((s): s is EthAddress => s !== undefined);
|
|
326
347
|
const attestors = new Set(
|
|
@@ -372,7 +393,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
372
393
|
}
|
|
373
394
|
|
|
374
395
|
/** Push the status for each slot for each validator. */
|
|
375
|
-
protected updateValidators(slot:
|
|
396
|
+
protected updateValidators(slot: SlotNumber, stats: Record<`0x${string}`, ValidatorStatusInSlot | undefined>) {
|
|
376
397
|
return this.store.updateValidators(slot, stats);
|
|
377
398
|
}
|
|
378
399
|
|
|
@@ -381,13 +402,13 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
381
402
|
fromSlot,
|
|
382
403
|
toSlot,
|
|
383
404
|
validators,
|
|
384
|
-
}: { fromSlot?:
|
|
405
|
+
}: { fromSlot?: SlotNumber; toSlot?: SlotNumber; validators?: EthAddress[] } = {}): Promise<ValidatorsStats> {
|
|
385
406
|
const histories = validators
|
|
386
407
|
? fromEntries(await Promise.all(validators.map(async v => [v.toString(), await this.store.getHistory(v)])))
|
|
387
408
|
: await this.store.getHistories();
|
|
388
409
|
|
|
389
410
|
const slotNow = this.epochCache.getEpochAndSlotNow().slot;
|
|
390
|
-
fromSlot ??= (this.lastProcessedSlot ?? slotNow) -
|
|
411
|
+
fromSlot ??= SlotNumber(Math.max((this.lastProcessedSlot ?? slotNow) - this.store.getHistoryLength(), 0));
|
|
391
412
|
toSlot ??= this.lastProcessedSlot ?? slotNow;
|
|
392
413
|
|
|
393
414
|
const stats = mapValues(histories, (history, address) =>
|
|
@@ -405,8 +426,8 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
405
426
|
/** Computes stats for a single validator. */
|
|
406
427
|
public async getValidatorStats(
|
|
407
428
|
validatorAddress: EthAddress,
|
|
408
|
-
fromSlot?:
|
|
409
|
-
toSlot?:
|
|
429
|
+
fromSlot?: SlotNumber,
|
|
430
|
+
toSlot?: SlotNumber,
|
|
410
431
|
): Promise<SingleValidatorStats | undefined> {
|
|
411
432
|
const history = await this.store.getHistory(validatorAddress);
|
|
412
433
|
|
|
@@ -415,13 +436,14 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
415
436
|
}
|
|
416
437
|
|
|
417
438
|
const slotNow = this.epochCache.getEpochAndSlotNow().slot;
|
|
418
|
-
const effectiveFromSlot =
|
|
439
|
+
const effectiveFromSlot =
|
|
440
|
+
fromSlot ?? SlotNumber(Math.max((this.lastProcessedSlot ?? slotNow) - this.store.getHistoryLength(), 0));
|
|
419
441
|
const effectiveToSlot = toSlot ?? this.lastProcessedSlot ?? slotNow;
|
|
420
442
|
|
|
421
443
|
const historyLength = BigInt(this.store.getHistoryLength());
|
|
422
|
-
if (effectiveToSlot - effectiveFromSlot > historyLength) {
|
|
444
|
+
if (BigInt(effectiveToSlot) - BigInt(effectiveFromSlot) > historyLength) {
|
|
423
445
|
throw new Error(
|
|
424
|
-
`Slot range (${effectiveToSlot - effectiveFromSlot}) exceeds history length (${historyLength}). ` +
|
|
446
|
+
`Slot range (${BigInt(effectiveToSlot) - BigInt(effectiveFromSlot)}) exceeds history length (${historyLength}). ` +
|
|
425
447
|
`Requested range: ${effectiveFromSlot} to ${effectiveToSlot}.`,
|
|
426
448
|
);
|
|
427
449
|
}
|
|
@@ -432,11 +454,10 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
432
454
|
effectiveFromSlot,
|
|
433
455
|
effectiveToSlot,
|
|
434
456
|
);
|
|
435
|
-
const allTimeProvenPerformance = await this.store.getProvenPerformance(validatorAddress);
|
|
436
457
|
|
|
437
458
|
return {
|
|
438
459
|
validator,
|
|
439
|
-
allTimeProvenPerformance,
|
|
460
|
+
allTimeProvenPerformance: await this.store.getProvenPerformance(validatorAddress),
|
|
440
461
|
lastProcessedSlot: this.lastProcessedSlot,
|
|
441
462
|
initialSlot: this.initialSlot,
|
|
442
463
|
slotWindow: this.store.getHistoryLength(),
|
|
@@ -446,11 +467,11 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
446
467
|
protected computeStatsForValidator(
|
|
447
468
|
address: `0x${string}`,
|
|
448
469
|
allHistory: ValidatorStatusHistory,
|
|
449
|
-
fromSlot?:
|
|
450
|
-
toSlot?:
|
|
470
|
+
fromSlot?: SlotNumber,
|
|
471
|
+
toSlot?: SlotNumber,
|
|
451
472
|
): ValidatorStats {
|
|
452
|
-
let history = fromSlot ? allHistory.filter(h => h.slot >= fromSlot) : allHistory;
|
|
453
|
-
history = toSlot ? history.filter(h => h.slot <= toSlot) : history;
|
|
473
|
+
let history = fromSlot ? allHistory.filter(h => BigInt(h.slot) >= fromSlot) : allHistory;
|
|
474
|
+
history = toSlot ? history.filter(h => BigInt(h.slot) <= toSlot) : history;
|
|
454
475
|
const lastProposal = history.filter(h => h.status === 'block-proposed' || h.status === 'block-mined').at(-1);
|
|
455
476
|
const lastAttestation = history.filter(h => h.status === 'attestation-sent').at(-1);
|
|
456
477
|
return {
|
|
@@ -479,7 +500,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
479
500
|
};
|
|
480
501
|
}
|
|
481
502
|
|
|
482
|
-
protected computeFromSlot(slot:
|
|
503
|
+
protected computeFromSlot(slot: SlotNumber | undefined) {
|
|
483
504
|
if (slot === undefined) {
|
|
484
505
|
return undefined;
|
|
485
506
|
}
|
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
|
}
|