@aztec/aztec-node 0.0.1-commit.0c875d939 → 0.0.1-commit.0ec55a70b

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.
@@ -1,10 +1,11 @@
1
1
  import type { EpochCache } from '@aztec/epoch-cache';
2
2
  import { createLogger } from '@aztec/foundation/log';
3
- import type { DataStoreConfig } from '@aztec/kv-store/config';
4
3
  import { createStore } from '@aztec/kv-store/lmdb-v2';
5
4
  import type { P2PClient } from '@aztec/p2p';
6
5
  import type { L2BlockSource } from '@aztec/stdlib/block';
6
+ import type { ChainConfig } from '@aztec/stdlib/config';
7
7
  import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
8
+ import type { DataStoreConfig } from '@aztec/stdlib/kv-store';
8
9
 
9
10
  import type { SentinelConfig } from './config.js';
10
11
  import { Sentinel } from './sentinel.js';
@@ -14,7 +15,7 @@ export async function createSentinel(
14
15
  epochCache: EpochCache,
15
16
  archiver: L2BlockSource,
16
17
  p2p: P2PClient,
17
- config: SentinelConfig & DataStoreConfig & SlasherConfig,
18
+ config: SentinelConfig & DataStoreConfig & SlasherConfig & Pick<ChainConfig, 'l1ChainId' | 'l1Contracts'>,
18
19
  logger = createLogger('node:sentinel'),
19
20
  ): Promise<Sentinel | undefined> {
20
21
  if (!config.sentinelEnabled) {
@@ -21,7 +21,9 @@ import {
21
21
  type L2BlockStreamEventHandler,
22
22
  getAttestationInfoFromPublishedCheckpoint,
23
23
  } from '@aztec/stdlib/block';
24
+ import type { ChainConfig } from '@aztec/stdlib/config';
24
25
  import { getEpochAtSlot, getSlotRangeForEpoch, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
26
+ import type { CoordinationSignatureContext } from '@aztec/stdlib/p2p';
25
27
  import type {
26
28
  SingleValidatorStats,
27
29
  ValidatorStats,
@@ -36,6 +38,12 @@ import EventEmitter from 'node:events';
36
38
 
37
39
  import { SentinelStore } from './store.js';
38
40
 
41
+ export type SentinelRuntimeConfig = Pick<
42
+ SlasherConfig,
43
+ 'slashInactivityTargetPercentage' | 'slashInactivityPenalty' | 'slashInactivityConsecutiveEpochThreshold'
44
+ > &
45
+ Pick<ChainConfig, 'l1ChainId' | 'l1Contracts'>;
46
+
39
47
  /** Maps a validator status to its category: proposer or attestation. */
40
48
  function statusToCategory(status: ValidatorStatusInSlot): ValidatorStatusType {
41
49
  switch (status) {
@@ -65,10 +73,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
65
73
  protected archiver: L2BlockSource,
66
74
  protected p2p: P2PClient,
67
75
  protected store: SentinelStore,
68
- protected config: Pick<
69
- SlasherConfig,
70
- 'slashInactivityTargetPercentage' | 'slashInactivityPenalty' | 'slashInactivityConsecutiveEpochThreshold'
71
- >,
76
+ protected config: SentinelRuntimeConfig,
72
77
  protected logger = createLogger('node:sentinel'),
73
78
  ) {
74
79
  super();
@@ -77,6 +82,13 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
77
82
  this.runningPromise = new RunningPromise(this.work.bind(this), logger, interval);
78
83
  }
79
84
 
85
+ private getSignatureContext(): CoordinationSignatureContext {
86
+ return {
87
+ chainId: this.config.l1ChainId,
88
+ rollupAddress: this.config.l1Contracts.rollupAddress,
89
+ };
90
+ }
91
+
80
92
  public updateConfig(config: Partial<SlasherConfig>) {
81
93
  this.config = { ...this.config, ...config };
82
94
  }
@@ -88,7 +100,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
88
100
 
89
101
  /** Loads initial slot and initializes blockstream. We will not process anything at or before the initial slot. */
90
102
  protected async init() {
91
- this.initialSlot = this.epochCache.getEpochAndSlotNow().slot;
103
+ this.initialSlot = this.epochCache.getSlotNow();
92
104
  const startingBlock = BlockNumber(await this.archiver.getBlockNumber());
93
105
  this.logger.info(`Starting validator sentinel with initial slot ${this.initialSlot} and block ${startingBlock}`);
94
106
  this.blockStream = new L2BlockStream(this.archiver, this.l2TipsStore, this, this.logger, { startingBlock });
@@ -117,7 +129,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
117
129
  this.slotNumberToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, {
118
130
  checkpointNumber: checkpoint.checkpoint.number,
119
131
  archive: checkpoint.checkpoint.archive.root.toString(),
120
- attestors: getAttestationInfoFromPublishedCheckpoint(checkpoint)
132
+ attestors: getAttestationInfoFromPublishedCheckpoint(checkpoint, this.getSignatureContext())
121
133
  .filter(a => a.status === 'recovered-from-signature')
122
134
  .map(a => a.address!),
123
135
  });
@@ -139,15 +151,15 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
139
151
  return;
140
152
  }
141
153
  const blockNumber = event.block.number;
142
- const block = await this.archiver.getL2Block(blockNumber);
143
- if (!block) {
144
- this.logger.error(`Failed to get block ${blockNumber}`, { block });
154
+ const header = await this.archiver.getBlockHeader(blockNumber);
155
+ if (!header) {
156
+ this.logger.error(`Failed to get block header ${blockNumber}`);
145
157
  return;
146
158
  }
147
159
 
148
160
  // TODO(palla/slash): We should only be computing proven performance if this is
149
161
  // a full proof epoch and not a partial one, otherwise we'll end up with skewed stats.
150
- const epoch = getEpochAtSlot(block.header.getSlot(), this.epochCache.getL1Constants());
162
+ const epoch = getEpochAtSlot(header.getSlot(), this.epochCache.getL1Constants());
151
163
  this.logger.debug(`Computing proven performance for epoch ${epoch}`);
152
164
  const performance = await this.computeProvenPerformance(epoch);
153
165
  this.logger.info(`Computed proven performance for epoch ${epoch}`, performance);
@@ -158,7 +170,11 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
158
170
 
159
171
  protected async computeProvenPerformance(epoch: EpochNumber): Promise<ValidatorsEpochPerformance> {
160
172
  const [fromSlot, toSlot] = getSlotRangeForEpoch(epoch, this.epochCache.getL1Constants());
161
- const { committee } = await this.epochCache.getCommittee(fromSlot);
173
+ const { committee, isEscapeHatchOpen } = await this.epochCache.getCommittee(fromSlot);
174
+ if (isEscapeHatchOpen) {
175
+ this.logger.info(`Skipping proven performance for epoch ${epoch} - escape hatch is open`);
176
+ return {};
177
+ }
162
178
  if (!committee) {
163
179
  this.logger.trace(`No committee found for slot ${fromSlot}`);
164
180
  return {};
@@ -215,7 +231,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
215
231
  // Check that we have at least requiredConsecutiveEpochs and that all of them are above the inactivity threshold
216
232
  return pastEpochs
217
233
  .slice(0, requiredConsecutiveEpochs)
218
- .every(p => p.missed / p.total >= this.config.slashInactivityTargetPercentage);
234
+ .every(p => (p.total === 0 ? false : p.missed / p.total >= this.config.slashInactivityTargetPercentage));
219
235
  }
220
236
 
221
237
  protected async handleProvenPerformance(epoch: EpochNumber, performance: ValidatorsEpochPerformance) {
@@ -224,7 +240,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
224
240
  }
225
241
 
226
242
  const inactiveValidators = getEntries(performance)
227
- .filter(([_, { missed, total }]) => missed / total >= this.config.slashInactivityTargetPercentage)
243
+ .filter(([_, { missed, total }]) => total > 0 && missed / total >= this.config.slashInactivityTargetPercentage)
228
244
  .map(([address]) => address);
229
245
 
230
246
  this.logger.debug(`Found ${inactiveValidators.length} inactive validators in epoch ${epoch}`, {
@@ -260,7 +276,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
260
276
  * and we don't have that data if we were offline during the period.
261
277
  */
262
278
  public async work() {
263
- const { slot: currentSlot } = this.epochCache.getEpochAndSlotNow();
279
+ const currentSlot = this.epochCache.getSlotNow();
264
280
  try {
265
281
  // Manually sync the block stream to ensure we have the latest data.
266
282
  // Note we never `start` the blockstream, so it loops at the same pace as we do.
@@ -305,9 +321,9 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
305
321
  return false;
306
322
  }
307
323
 
308
- const archiverSlot = await this.archiver.getL2SlotNumber();
309
- if (archiverSlot === undefined || archiverSlot < targetSlot) {
310
- this.logger.debug(`Waiting for archiver to sync with L2 slot ${targetSlot}`, { archiverSlot, targetSlot });
324
+ const syncedSlot = await this.archiver.getSyncedL2SlotNumber();
325
+ if (syncedSlot === undefined || syncedSlot < targetSlot) {
326
+ this.logger.debug(`Waiting for archiver to sync with L2 slot ${targetSlot}`, { syncedSlot, targetSlot });
311
327
  return false;
312
328
  }
313
329
 
@@ -327,7 +343,12 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
327
343
  * and updates overall stats.
328
344
  */
329
345
  protected async processSlot(slot: SlotNumber) {
330
- const { epoch, seed, committee } = await this.epochCache.getCommittee(slot);
346
+ const { epoch, seed, committee, isEscapeHatchOpen } = await this.epochCache.getCommittee(slot);
347
+ if (isEscapeHatchOpen) {
348
+ this.logger.info(`Skipping slot ${slot} at epoch ${epoch} - escape hatch is open`);
349
+ this.lastProcessedSlot = slot;
350
+ return;
351
+ }
331
352
  if (!committee || committee.length === 0) {
332
353
  this.logger.trace(`No committee found for slot ${slot} at epoch ${epoch}`);
333
354
  this.lastProcessedSlot = slot;
@@ -427,7 +448,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
427
448
  ? fromEntries(await Promise.all(validators.map(async v => [v.toString(), await this.store.getHistory(v)])))
428
449
  : await this.store.getHistories();
429
450
 
430
- const slotNow = this.epochCache.getEpochAndSlotNow().slot;
451
+ const slotNow = this.epochCache.getSlotNow();
431
452
  fromSlot ??= SlotNumber(Math.max((this.lastProcessedSlot ?? slotNow) - this.store.getHistoryLength(), 0));
432
453
  toSlot ??= this.lastProcessedSlot ?? slotNow;
433
454
 
@@ -455,7 +476,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
455
476
  return undefined;
456
477
  }
457
478
 
458
- const slotNow = this.epochCache.getEpochAndSlotNow().slot;
479
+ const slotNow = this.epochCache.getSlotNow();
459
480
  const effectiveFromSlot =
460
481
  fromSlot ?? SlotNumber(Math.max((this.lastProcessedSlot ?? slotNow) - this.store.getHistoryLength(), 0));
461
482
  const effectiveToSlot = toSlot ?? this.lastProcessedSlot ?? slotNow;