@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.
- package/dest/aztec-node/block_response_helpers.d.ts +21 -0
- package/dest/aztec-node/block_response_helpers.d.ts.map +1 -0
- package/dest/aztec-node/block_response_helpers.js +88 -0
- package/dest/aztec-node/config.d.ts +8 -5
- package/dest/aztec-node/config.d.ts.map +1 -1
- package/dest/aztec-node/config.js +11 -3
- package/dest/aztec-node/server.d.ts +60 -80
- package/dest/aztec-node/server.d.ts.map +1 -1
- package/dest/aztec-node/server.js +855 -367
- package/dest/sentinel/factory.d.ts +4 -3
- package/dest/sentinel/factory.d.ts.map +1 -1
- package/dest/sentinel/sentinel.d.ts +6 -3
- package/dest/sentinel/sentinel.d.ts.map +1 -1
- package/dest/sentinel/sentinel.js +31 -18
- package/package.json +26 -25
- package/src/aztec-node/block_response_helpers.ts +131 -0
- package/src/aztec-node/config.ts +25 -9
- package/src/aztec-node/server.ts +1018 -466
- package/src/sentinel/factory.ts +3 -2
- package/src/sentinel/sentinel.ts +41 -20
package/src/sentinel/factory.ts
CHANGED
|
@@ -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) {
|
package/src/sentinel/sentinel.ts
CHANGED
|
@@ -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:
|
|
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.
|
|
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
|
|
143
|
-
if (!
|
|
144
|
-
this.logger.error(`Failed to get block ${blockNumber}
|
|
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(
|
|
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
|
|
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
|
|
309
|
-
if (
|
|
310
|
-
this.logger.debug(`Waiting for archiver to sync with L2 slot ${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.
|
|
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.
|
|
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;
|