@aztec/aztec-node 0.0.1-commit.6d3c34e → 0.0.1-commit.7cf39cb55
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/node_metrics.d.ts +1 -1
- package/dest/aztec-node/node_metrics.d.ts.map +1 -1
- package/dest/aztec-node/node_metrics.js +8 -4
- package/dest/aztec-node/server.d.ts +25 -92
- package/dest/aztec-node/server.d.ts.map +1 -1
- package/dest/aztec-node/server.js +151 -159
- 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 +2 -2
- package/dest/sentinel/sentinel.d.ts.map +1 -1
- package/dest/sentinel/sentinel.js +39 -20
- 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 +26 -25
- package/src/aztec-node/node_metrics.ts +12 -5
- package/src/aztec-node/server.ts +193 -221
- package/src/sentinel/factory.ts +1 -6
- package/src/sentinel/sentinel.ts +42 -18
- 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
|
@@ -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;
|
|
@@ -128,7 +139,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
128
139
|
return;
|
|
129
140
|
}
|
|
130
141
|
const blockNumber = event.block.number;
|
|
131
|
-
const block = await this.archiver.
|
|
142
|
+
const block = await this.archiver.getL2Block(blockNumber);
|
|
132
143
|
if (!block) {
|
|
133
144
|
this.logger.error(`Failed to get block ${blockNumber}`, { block });
|
|
134
145
|
return;
|
|
@@ -336,16 +347,16 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
336
347
|
|
|
337
348
|
// Check if there is an L2 block in L1 for this L2 slot
|
|
338
349
|
|
|
339
|
-
// Here we get all attestations for the
|
|
340
|
-
// or all attestations for all proposals in the slot if no
|
|
350
|
+
// Here we get all checkpoint attestations for the checkpoint at the given slot,
|
|
351
|
+
// or all checkpoint attestations for all proposals in the slot if no checkpoint was mined.
|
|
341
352
|
// We gather from both p2p (contains the ones seen on the p2p layer) and archiver
|
|
342
|
-
// (contains the ones synced from mined
|
|
343
|
-
const
|
|
344
|
-
const p2pAttested = await this.p2p.getCheckpointAttestationsForSlot(slot,
|
|
353
|
+
// (contains the ones synced from mined checkpoints, which we may have missed from p2p).
|
|
354
|
+
const checkpoint = this.slotNumberToCheckpoint.get(slot);
|
|
355
|
+
const p2pAttested = await this.p2p.getCheckpointAttestationsForSlot(slot, checkpoint?.archive);
|
|
345
356
|
// Filter out attestations with invalid signatures
|
|
346
357
|
const p2pAttestors = p2pAttested.map(a => a.getSender()).filter((s): s is EthAddress => s !== undefined);
|
|
347
358
|
const attestors = new Set(
|
|
348
|
-
[...p2pAttestors.map(a => a.toString()), ...(
|
|
359
|
+
[...p2pAttestors.map(a => a.toString()), ...(checkpoint?.attestors.map(a => a.toString()) ?? [])].filter(
|
|
349
360
|
addr => proposer.toString() !== addr, // Exclude the proposer from the attestors
|
|
350
361
|
),
|
|
351
362
|
);
|
|
@@ -356,20 +367,29 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
356
367
|
// But we'll leave that corner case out to reduce pressure on the node.
|
|
357
368
|
// TODO(palla/slash): This breaks if a given node has more than one validator in the current committee,
|
|
358
369
|
// since they will attest to their own proposal it even if it's not re-executable.
|
|
359
|
-
|
|
360
|
-
|
|
370
|
+
let status: 'checkpoint-mined' | 'checkpoint-proposed' | 'checkpoint-missed' | 'blocks-missed';
|
|
371
|
+
if (checkpoint) {
|
|
372
|
+
status = 'checkpoint-mined';
|
|
373
|
+
} else if (attestors.size > 0) {
|
|
374
|
+
status = 'checkpoint-proposed';
|
|
375
|
+
} else {
|
|
376
|
+
// No checkpoint on L1 and no checkpoint attestations seen. Check if block proposals were sent for this slot.
|
|
377
|
+
const hasBlockProposals = await this.p2p.hasBlockProposalsForSlot(slot);
|
|
378
|
+
status = hasBlockProposals ? 'checkpoint-missed' : 'blocks-missed';
|
|
379
|
+
}
|
|
380
|
+
this.logger.debug(`Checkpoint status for slot ${slot}: ${status}`, { ...checkpoint, slot });
|
|
361
381
|
|
|
362
|
-
// Get attestors that failed their
|
|
382
|
+
// Get attestors that failed their checkpoint attestation duties, but only if there was a checkpoint proposed or mined
|
|
363
383
|
const missedAttestors = new Set(
|
|
364
|
-
|
|
384
|
+
status === 'blocks-missed' || status === 'checkpoint-missed'
|
|
365
385
|
? []
|
|
366
386
|
: committee.filter(v => !attestors.has(v.toString()) && !proposer.equals(v)).map(v => v.toString()),
|
|
367
387
|
);
|
|
368
388
|
|
|
369
389
|
this.logger.debug(`Retrieved ${attestors.size} attestors out of ${committee.length} for slot ${slot}`, {
|
|
370
|
-
|
|
390
|
+
status,
|
|
371
391
|
proposer: proposer.toString(),
|
|
372
|
-
...
|
|
392
|
+
...checkpoint,
|
|
373
393
|
slot,
|
|
374
394
|
attestors: [...attestors],
|
|
375
395
|
missedAttestors: [...missedAttestors],
|
|
@@ -379,7 +399,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
379
399
|
// Compute the status for each validator in the committee
|
|
380
400
|
const statusFor = (who: `0x${string}`): ValidatorStatusInSlot | undefined => {
|
|
381
401
|
if (who === proposer.toString()) {
|
|
382
|
-
return
|
|
402
|
+
return status;
|
|
383
403
|
} else if (attestors.has(who)) {
|
|
384
404
|
return 'attestation-sent';
|
|
385
405
|
} else if (missedAttestors.has(who)) {
|
|
@@ -472,14 +492,16 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
472
492
|
): ValidatorStats {
|
|
473
493
|
let history = fromSlot ? allHistory.filter(h => BigInt(h.slot) >= fromSlot) : allHistory;
|
|
474
494
|
history = toSlot ? history.filter(h => BigInt(h.slot) <= toSlot) : history;
|
|
475
|
-
const lastProposal = history
|
|
495
|
+
const lastProposal = history
|
|
496
|
+
.filter(h => h.status === 'checkpoint-proposed' || h.status === 'checkpoint-mined')
|
|
497
|
+
.at(-1);
|
|
476
498
|
const lastAttestation = history.filter(h => h.status === 'attestation-sent').at(-1);
|
|
477
499
|
return {
|
|
478
500
|
address: EthAddress.fromString(address),
|
|
479
501
|
lastProposal: this.computeFromSlot(lastProposal?.slot),
|
|
480
502
|
lastAttestation: this.computeFromSlot(lastAttestation?.slot),
|
|
481
503
|
totalSlots: history.length,
|
|
482
|
-
missedProposals: this.computeMissed(history, '
|
|
504
|
+
missedProposals: this.computeMissed(history, 'proposer', ['checkpoint-missed', 'blocks-missed']),
|
|
483
505
|
missedAttestations: this.computeMissed(history, 'attestation', ['attestation-missed']),
|
|
484
506
|
history,
|
|
485
507
|
};
|
|
@@ -487,10 +509,12 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
487
509
|
|
|
488
510
|
protected computeMissed(
|
|
489
511
|
history: ValidatorStatusHistory,
|
|
490
|
-
|
|
512
|
+
computeOverCategory: ValidatorStatusType | undefined,
|
|
491
513
|
filter: ValidatorStatusInSlot[],
|
|
492
514
|
) {
|
|
493
|
-
const relevantHistory = history.filter(
|
|
515
|
+
const relevantHistory = history.filter(
|
|
516
|
+
h => !computeOverCategory || statusToCategory(h.status) === computeOverCategory,
|
|
517
|
+
);
|
|
494
518
|
const filteredHistory = relevantHistory.filter(h => filter.includes(h.status));
|
|
495
519
|
return {
|
|
496
520
|
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
|
}
|