@aztec/epoch-cache 0.0.0-test.1 → 0.0.1-commit.001888fc

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,231 +1,386 @@
1
- import { RollupContract, createEthereumChain } from '@aztec/ethereum';
1
+ import { createEthereumChain } from '@aztec/ethereum/chain';
2
+ import { makeL1HttpTransport } from '@aztec/ethereum/client';
3
+ import { NoCommitteeError, RollupContract } from '@aztec/ethereum/contracts';
4
+ import { EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
2
5
  import { EthAddress } from '@aztec/foundation/eth-address';
3
6
  import { type Logger, createLogger } from '@aztec/foundation/log';
4
7
  import { DateProvider } from '@aztec/foundation/timer';
5
8
  import {
6
- EmptyL1RollupConstants,
7
9
  type L1RollupConstants,
10
+ getEpochAtSlot,
8
11
  getEpochNumberAtTimestamp,
9
12
  getSlotAtTimestamp,
13
+ getSlotRangeForEpoch,
14
+ getTimestampForSlot,
15
+ getTimestampRangeForEpoch,
10
16
  } from '@aztec/stdlib/epoch-helpers';
11
17
 
12
- import { EventEmitter } from 'node:events';
13
- import { createPublicClient, encodeAbiParameters, fallback, http, keccak256 } from 'viem';
18
+ import { createPublicClient, encodeAbiParameters, keccak256 } from 'viem';
14
19
 
15
20
  import { type EpochCacheConfig, getEpochCacheConfigEnvVars } from './config.js';
16
21
 
17
- type EpochAndSlot = {
18
- epoch: bigint;
19
- slot: bigint;
22
+ export type EpochAndSlot = {
23
+ epoch: EpochNumber;
24
+ slot: SlotNumber;
20
25
  ts: bigint;
21
26
  };
22
27
 
28
+ export type EpochCommitteeInfo = {
29
+ committee: EthAddress[] | undefined;
30
+ seed: bigint;
31
+ epoch: EpochNumber;
32
+ /** True if the epoch is within an open escape hatch window. */
33
+ isEscapeHatchOpen: boolean;
34
+ };
35
+
36
+ export type SlotTag = 'now' | 'next' | SlotNumber;
37
+
23
38
  export interface EpochCacheInterface {
24
- getCommittee(nextSlot: boolean): Promise<EthAddress[]>;
25
- getEpochAndSlotNow(): EpochAndSlot;
26
- getProposerIndexEncoding(epoch: bigint, slot: bigint, seed: bigint): `0x${string}`;
27
- computeProposerIndex(slot: bigint, epoch: bigint, seed: bigint, size: bigint): bigint;
28
- getProposerInCurrentOrNextSlot(): Promise<{
29
- currentProposer: EthAddress;
30
- nextProposer: EthAddress;
31
- currentSlot: bigint;
32
- nextSlot: bigint;
33
- }>;
34
- isInCommittee(validator: EthAddress): Promise<boolean>;
39
+ getCommittee(slot: SlotTag | undefined): Promise<EpochCommitteeInfo>;
40
+ getEpochAndSlotNow(): EpochAndSlot & { nowMs: bigint };
41
+ getEpochAndSlotInNextL1Slot(): EpochAndSlot & { now: bigint };
42
+ getProposerIndexEncoding(epoch: EpochNumber, slot: SlotNumber, seed: bigint): `0x${string}`;
43
+ computeProposerIndex(slot: SlotNumber, epoch: EpochNumber, seed: bigint, size: bigint): bigint;
44
+ getCurrentAndNextSlot(): { currentSlot: SlotNumber; nextSlot: SlotNumber };
45
+ getProposerAttesterAddressInSlot(slot: SlotNumber): Promise<EthAddress | undefined>;
46
+ getRegisteredValidators(): Promise<EthAddress[]>;
47
+ isInCommittee(slot: SlotTag, validator: EthAddress): Promise<boolean>;
48
+ filterInCommittee(slot: SlotTag, validators: EthAddress[]): Promise<EthAddress[]>;
49
+ getL1Constants(): L1RollupConstants;
35
50
  }
36
51
 
37
52
  /**
38
53
  * Epoch cache
39
54
  *
40
55
  * This class is responsible for managing traffic to the l1 node, by caching the validator set.
56
+ * Keeps the last N epochs in cache.
41
57
  * It also provides a method to get the current or next proposer, and to check who is in the current slot.
42
58
  *
43
- * If the epoch changes, then we update the stored validator set.
44
- *
45
59
  * Note: This class is very dependent on the system clock being in sync.
46
60
  */
47
- export class EpochCache
48
- extends EventEmitter<{ committeeChanged: [EthAddress[], bigint] }>
49
- implements EpochCacheInterface
50
- {
51
- private committee: EthAddress[];
52
- private cachedEpoch: bigint;
53
- private cachedSampleSeed: bigint;
61
+ export class EpochCache implements EpochCacheInterface {
62
+ // eslint-disable-next-line aztec-custom/no-non-primitive-in-collections
63
+ protected cache: Map<EpochNumber, EpochCommitteeInfo> = new Map();
64
+ private allValidators: Set<string> = new Set();
65
+ private lastValidatorRefresh = 0;
54
66
  private readonly log: Logger = createLogger('epoch-cache');
55
67
 
56
68
  constructor(
57
69
  private rollup: RollupContract,
58
- initialValidators: EthAddress[] = [],
59
- initialSampleSeed: bigint = 0n,
60
- private readonly l1constants: L1RollupConstants = EmptyL1RollupConstants,
70
+ private readonly l1constants: L1RollupConstants & {
71
+ lagInEpochsForValidatorSet: number;
72
+ lagInEpochsForRandao: number;
73
+ },
61
74
  private readonly dateProvider: DateProvider = new DateProvider(),
75
+ protected readonly config = { cacheSize: 12, validatorRefreshIntervalSeconds: 60 },
62
76
  ) {
63
- super();
64
- this.committee = initialValidators;
65
- this.cachedSampleSeed = initialSampleSeed;
66
-
67
- this.log.debug(`Initialized EpochCache with constants and validators`, { l1constants, initialValidators });
68
-
69
- this.cachedEpoch = getEpochNumberAtTimestamp(this.nowInSeconds(), this.l1constants);
77
+ this.log.debug(`Initialized EpochCache`, {
78
+ l1constants,
79
+ });
70
80
  }
71
81
 
72
82
  static async create(
73
- rollupAddress: EthAddress,
83
+ rollupOrAddress: EthAddress | RollupContract,
74
84
  config?: EpochCacheConfig,
75
85
  deps: { dateProvider?: DateProvider } = {},
76
86
  ) {
77
87
  config = config ?? getEpochCacheConfigEnvVars();
78
88
 
79
- const chain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
80
- const publicClient = createPublicClient({
81
- chain: chain.chainInfo,
82
- transport: fallback(config.l1RpcUrls.map(url => http(url))),
83
- pollingInterval: config.viemPollingIntervalMS,
84
- });
89
+ // Load the rollup contract if we were given an address
90
+ let rollup: RollupContract;
91
+ if ('address' in rollupOrAddress) {
92
+ rollup = rollupOrAddress;
93
+ } else {
94
+ const chain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
95
+ const publicClient = createPublicClient({
96
+ chain: chain.chainInfo,
97
+ transport: makeL1HttpTransport(config.l1RpcUrls, { timeout: config.l1HttpTimeoutMS }),
98
+ pollingInterval: config.viemPollingIntervalMS,
99
+ });
100
+ rollup = new RollupContract(publicClient, rollupOrAddress.toString());
101
+ }
85
102
 
86
- const rollup = new RollupContract(publicClient, rollupAddress.toString());
87
- const [l1StartBlock, l1GenesisTime, initialValidators, sampleSeed] = await Promise.all([
103
+ const [
104
+ l1StartBlock,
105
+ l1GenesisTime,
106
+ proofSubmissionEpochs,
107
+ slotDuration,
108
+ epochDuration,
109
+ lagInEpochsForValidatorSet,
110
+ lagInEpochsForRandao,
111
+ targetCommitteeSize,
112
+ rollupManaLimit,
113
+ ] = await Promise.all([
88
114
  rollup.getL1StartBlock(),
89
115
  rollup.getL1GenesisTime(),
90
- rollup.getCurrentEpochCommittee(),
91
- rollup.getCurrentSampleSeed(),
116
+ rollup.getProofSubmissionEpochs(),
117
+ rollup.getSlotDuration(),
118
+ rollup.getEpochDuration(),
119
+ rollup.getLagInEpochsForValidatorSet(),
120
+ rollup.getLagInEpochsForRandao(),
121
+ rollup.getTargetCommitteeSize(),
122
+ rollup.getManaLimit(),
92
123
  ] as const);
93
124
 
94
- const l1RollupConstants: L1RollupConstants = {
125
+ const l1RollupConstants = {
95
126
  l1StartBlock,
96
127
  l1GenesisTime,
97
- slotDuration: config.aztecSlotDuration,
98
- epochDuration: config.aztecEpochDuration,
128
+ proofSubmissionEpochs: Number(proofSubmissionEpochs),
129
+ slotDuration: Number(slotDuration),
130
+ epochDuration: Number(epochDuration),
99
131
  ethereumSlotDuration: config.ethereumSlotDuration,
132
+ lagInEpochsForValidatorSet: Number(lagInEpochsForValidatorSet),
133
+ lagInEpochsForRandao: Number(lagInEpochsForRandao),
134
+ targetCommitteeSize: Number(targetCommitteeSize),
135
+ rollupManaLimit: Number(rollupManaLimit),
100
136
  };
101
137
 
102
- return new EpochCache(
103
- rollup,
104
- initialValidators.map(v => EthAddress.fromString(v)),
105
- sampleSeed,
106
- l1RollupConstants,
107
- deps.dateProvider,
108
- );
138
+ return new EpochCache(rollup, l1RollupConstants, deps.dateProvider);
139
+ }
140
+
141
+ public getL1Constants(): L1RollupConstants {
142
+ return this.l1constants;
109
143
  }
110
144
 
111
- private nowInSeconds(): bigint {
145
+ public getEpochAndSlotNow(): EpochAndSlot & { nowMs: bigint } {
146
+ const nowMs = BigInt(this.dateProvider.now());
147
+ const nowSeconds = nowMs / 1000n;
148
+ return { ...this.getEpochAndSlotAtTimestamp(nowSeconds), nowMs };
149
+ }
150
+
151
+ public nowInSeconds(): bigint {
112
152
  return BigInt(Math.floor(this.dateProvider.now() / 1000));
113
153
  }
114
154
 
115
- getEpochAndSlotNow(): EpochAndSlot {
116
- return this.getEpochAndSlotAtTimestamp(this.nowInSeconds());
155
+ private getEpochAndSlotAtSlot(slot: SlotNumber): EpochAndSlot {
156
+ const epoch = getEpochAtSlot(slot, this.l1constants);
157
+ const ts = getTimestampRangeForEpoch(epoch, this.l1constants)[0];
158
+ return { epoch, ts, slot };
117
159
  }
118
160
 
119
- private getEpochAndSlotInNextSlot(): EpochAndSlot {
120
- const nextSlotTs = this.nowInSeconds() + BigInt(this.l1constants.slotDuration);
121
- return this.getEpochAndSlotAtTimestamp(nextSlotTs);
161
+ public getEpochAndSlotInNextL1Slot(): EpochAndSlot & { now: bigint } {
162
+ const now = this.nowInSeconds();
163
+ const nextSlotTs = now + BigInt(this.l1constants.ethereumSlotDuration);
164
+ return { ...this.getEpochAndSlotAtTimestamp(nextSlotTs), now };
122
165
  }
123
166
 
124
167
  private getEpochAndSlotAtTimestamp(ts: bigint): EpochAndSlot {
168
+ const slot = getSlotAtTimestamp(ts, this.l1constants);
125
169
  return {
126
170
  epoch: getEpochNumberAtTimestamp(ts, this.l1constants),
127
- slot: getSlotAtTimestamp(ts, this.l1constants),
128
- ts,
171
+ ts: getTimestampForSlot(slot, this.l1constants),
172
+ slot,
129
173
  };
130
174
  }
131
175
 
176
+ public getCommitteeForEpoch(epoch: EpochNumber): Promise<EpochCommitteeInfo> {
177
+ const [startSlot] = getSlotRangeForEpoch(epoch, this.l1constants);
178
+ return this.getCommittee(startSlot);
179
+ }
180
+
132
181
  /**
133
- * Get the current validator set
182
+ * Returns whether the escape hatch is open for the given epoch.
183
+ *
184
+ * Uses the already-cached EpochCommitteeInfo when available. If not cached, it will fetch
185
+ * the epoch committee info (which includes the escape hatch flag) and return it.
186
+ */
187
+ public async isEscapeHatchOpen(epoch: EpochNumber): Promise<boolean> {
188
+ const cached = this.cache.get(epoch);
189
+ if (cached) {
190
+ return cached.isEscapeHatchOpen;
191
+ }
192
+ const info = await this.getCommitteeForEpoch(epoch);
193
+ return info.isEscapeHatchOpen;
194
+ }
195
+
196
+ /**
197
+ * Returns whether the escape hatch is open for the epoch containing the given slot.
134
198
  *
199
+ * This is a lightweight helper intended for callers that already have a slot number and only
200
+ * need the escape hatch flag (without pulling full committee info).
201
+ */
202
+ public async isEscapeHatchOpenAtSlot(slot: SlotTag = 'now'): Promise<boolean> {
203
+ const epoch =
204
+ slot === 'now'
205
+ ? this.getEpochAndSlotNow().epoch
206
+ : slot === 'next'
207
+ ? this.getEpochAndSlotInNextL1Slot().epoch
208
+ : getEpochAtSlot(slot, this.l1constants);
209
+
210
+ return await this.isEscapeHatchOpen(epoch);
211
+ }
212
+
213
+ /**
214
+ * Get the current validator set
135
215
  * @param nextSlot - If true, get the validator set for the next slot.
136
216
  * @returns The current validator set.
137
217
  */
138
- async getCommittee(nextSlot: boolean = false): Promise<EthAddress[]> {
139
- // If the current epoch has changed, then we need to make a request to update the validator set
140
- const { epoch: calculatedEpoch, ts } = nextSlot ? this.getEpochAndSlotInNextSlot() : this.getEpochAndSlotNow();
141
-
142
- if (calculatedEpoch !== this.cachedEpoch) {
143
- this.log.debug(`Updating validator set for new epoch ${calculatedEpoch}`, {
144
- epoch: calculatedEpoch,
145
- previousEpoch: this.cachedEpoch,
146
- });
147
- const [committeeAtTs, sampleSeedAtTs] = await Promise.all([
148
- this.rollup.getCommitteeAt(ts),
149
- this.rollup.getSampleSeedAt(ts),
150
- ]);
151
- this.committee = committeeAtTs.map((v: `0x${string}`) => EthAddress.fromString(v));
152
- this.cachedEpoch = calculatedEpoch;
153
- this.cachedSampleSeed = sampleSeedAtTs;
154
- this.log.debug(`Updated validator set for epoch ${calculatedEpoch}`, { commitee: this.committee });
155
- this.emit('committeeChanged', this.committee, calculatedEpoch);
218
+ public async getCommittee(slot: SlotTag = 'now'): Promise<EpochCommitteeInfo> {
219
+ const { epoch, ts } = this.getEpochAndTimestamp(slot);
220
+
221
+ if (this.cache.has(epoch)) {
222
+ return this.cache.get(epoch)!;
156
223
  }
157
224
 
158
- return this.committee;
225
+ const epochData = await this.computeCommittee({ epoch, ts });
226
+ // If the committee size is 0 or undefined, then do not cache
227
+ if (!epochData.committee || epochData.committee.length === 0) {
228
+ return epochData;
229
+ }
230
+ this.cache.set(epoch, epochData);
231
+
232
+ const toPurge = Array.from(this.cache.keys())
233
+ .sort((a, b) => Number(b - a))
234
+ .slice(this.config.cacheSize);
235
+ toPurge.forEach(key => this.cache.delete(key));
236
+
237
+ return epochData;
238
+ }
239
+
240
+ private getEpochAndTimestamp(slot: SlotTag = 'now') {
241
+ if (slot === 'now') {
242
+ return this.getEpochAndSlotNow();
243
+ } else if (slot === 'next') {
244
+ return this.getEpochAndSlotInNextL1Slot();
245
+ } else {
246
+ return this.getEpochAndSlotAtSlot(slot);
247
+ }
248
+ }
249
+
250
+ private async computeCommittee(when: { epoch: EpochNumber; ts: bigint }): Promise<EpochCommitteeInfo> {
251
+ const { ts, epoch } = when;
252
+ const [committee, seedBuffer, l1Timestamp, isEscapeHatchOpen] = await Promise.all([
253
+ this.rollup.getCommitteeAt(ts),
254
+ this.rollup.getSampleSeedAt(ts),
255
+ this.rollup.client.getBlock({ includeTransactions: false }).then(b => b.timestamp),
256
+ this.rollup.isEscapeHatchOpen(epoch),
257
+ ]);
258
+ const { lagInEpochsForValidatorSet, epochDuration, slotDuration } = this.l1constants;
259
+ const sub = BigInt(lagInEpochsForValidatorSet) * BigInt(epochDuration) * BigInt(slotDuration);
260
+ if (ts - sub > l1Timestamp) {
261
+ throw new Error(
262
+ `Cannot query committee for future epoch ${epoch} with timestamp ${ts} (current L1 time is ${l1Timestamp}). Check your Ethereum node is synced.`,
263
+ );
264
+ }
265
+ return { committee, seed: seedBuffer.toBigInt(), epoch, isEscapeHatchOpen };
159
266
  }
160
267
 
161
268
  /**
162
269
  * Get the ABI encoding of the proposer index - see ValidatorSelectionLib.sol computeProposerIndex
163
270
  */
164
- getProposerIndexEncoding(epoch: bigint, slot: bigint, seed: bigint): `0x${string}` {
271
+ getProposerIndexEncoding(epoch: EpochNumber, slot: SlotNumber, seed: bigint): `0x${string}` {
165
272
  return encodeAbiParameters(
166
273
  [
167
274
  { type: 'uint256', name: 'epoch' },
168
275
  { type: 'uint256', name: 'slot' },
169
276
  { type: 'uint256', name: 'seed' },
170
277
  ],
171
- [epoch, slot, seed],
278
+ [BigInt(epoch), BigInt(slot), seed],
172
279
  );
173
280
  }
174
281
 
175
- computeProposerIndex(slot: bigint, epoch: bigint, seed: bigint, size: bigint): bigint {
282
+ public computeProposerIndex(slot: SlotNumber, epoch: EpochNumber, seed: bigint, size: bigint): bigint {
283
+ // if committe size is 0, then mod 1 is 0
284
+ if (size === 0n) {
285
+ return 0n;
286
+ }
176
287
  return BigInt(keccak256(this.getProposerIndexEncoding(epoch, slot, seed))) % size;
177
288
  }
178
289
 
290
+ /** Returns the current and next L2 slot numbers. */
291
+ public getCurrentAndNextSlot(): { currentSlot: SlotNumber; nextSlot: SlotNumber } {
292
+ const current = this.getEpochAndSlotNow();
293
+ const next = this.getEpochAndSlotInNextL1Slot();
294
+
295
+ return {
296
+ currentSlot: current.slot,
297
+ nextSlot: next.slot,
298
+ };
299
+ }
300
+
179
301
  /**
180
- * Returns the current and next proposer
181
- *
182
- * We return the next proposer as the node will check if it is the proposer at the next ethereum block, which
183
- * can be the next slot. If this is the case, then it will send proposals early.
184
- *
185
- * If we are at an epoch boundary, then we can update the cache for the next epoch, this is the last check
186
- * we do in the validator client, so we can update the cache here.
302
+ * Get the proposer attester address in the given L2 slot
303
+ * @returns The proposer attester address. If the committee does not exist, we throw a NoCommitteeError.
304
+ * If the committee is empty (i.e. target committee size is 0, and anyone can propose), we return undefined.
187
305
  */
188
- async getProposerInCurrentOrNextSlot(): Promise<{
189
- currentProposer: EthAddress;
190
- nextProposer: EthAddress;
191
- currentSlot: bigint;
192
- nextSlot: bigint;
193
- }> {
194
- // Validators are sorted by their index in the committee, and getValidatorSet will cache
195
- const committee = await this.getCommittee();
196
- const { slot: currentSlot, epoch: currentEpoch } = this.getEpochAndSlotNow();
197
- const { slot: nextSlot, epoch: nextEpoch } = this.getEpochAndSlotInNextSlot();
198
-
199
- // Compute the proposer in this and the next slot
200
- const proposerIndex = this.computeProposerIndex(
201
- currentSlot,
202
- this.cachedEpoch,
203
- this.cachedSampleSeed,
204
- BigInt(committee.length),
205
- );
306
+ public getProposerAttesterAddressInSlot(slot: SlotNumber): Promise<EthAddress | undefined> {
307
+ const epochAndSlot = this.getEpochAndSlotAtSlot(slot);
308
+ return this.getProposerAttesterAddressAt(epochAndSlot);
309
+ }
206
310
 
207
- // Check if the next proposer is in the next epoch
208
- if (nextEpoch !== currentEpoch) {
209
- await this.getCommittee(/*next slot*/ true);
311
+ /**
312
+ * Get the proposer attester address in the next slot
313
+ * @returns The proposer attester address. If the committee does not exist, we throw a NoCommitteeError.
314
+ * If the committee is empty (i.e. target committee size is 0, and anyone can propose), we return undefined.
315
+ */
316
+ public getProposerAttesterAddressInNextSlot(): Promise<EthAddress | undefined> {
317
+ const epochAndSlot = this.getEpochAndSlotInNextL1Slot();
318
+ return this.getProposerAttesterAddressAt(epochAndSlot);
319
+ }
320
+
321
+ /**
322
+ * Get the proposer attester address at a given epoch and slot
323
+ * @param when - The epoch and slot to get the proposer attester address at
324
+ * @returns The proposer attester address. If the committee does not exist, we throw a NoCommitteeError.
325
+ * If the committee is empty (i.e. target committee size is 0, and anyone can propose), we return undefined.
326
+ */
327
+ private async getProposerAttesterAddressAt(when: EpochAndSlot) {
328
+ const { epoch, slot } = when;
329
+ const { committee, seed } = await this.getCommittee(slot);
330
+ if (!committee) {
331
+ throw new NoCommitteeError();
332
+ } else if (committee.length === 0) {
333
+ return undefined;
210
334
  }
211
- const nextProposerIndex = this.computeProposerIndex(
212
- nextSlot,
213
- this.cachedEpoch,
214
- this.cachedSampleSeed,
215
- BigInt(committee.length),
216
- );
217
335
 
218
- const currentProposer = committee[Number(proposerIndex)];
219
- const nextProposer = committee[Number(nextProposerIndex)];
336
+ const proposerIndex = this.computeProposerIndex(slot, epoch, seed, BigInt(committee.length));
337
+ return committee[Number(proposerIndex)];
338
+ }
339
+
340
+ public getProposerFromEpochCommittee(
341
+ epochCommitteeInfo: EpochCommitteeInfo,
342
+ slot: SlotNumber,
343
+ ): EthAddress | undefined {
344
+ if (!epochCommitteeInfo.committee || epochCommitteeInfo.committee.length === 0) {
345
+ return undefined;
346
+ }
347
+ const proposerIndex = this.computeProposerIndex(
348
+ slot,
349
+ epochCommitteeInfo.epoch,
350
+ epochCommitteeInfo.seed,
351
+ BigInt(epochCommitteeInfo.committee.length),
352
+ );
220
353
 
221
- return { currentProposer, nextProposer, currentSlot, nextSlot };
354
+ return epochCommitteeInfo.committee[Number(proposerIndex)];
222
355
  }
223
356
 
224
- /**
225
- * Check if a validator is in the current epoch's committee
226
- */
227
- async isInCommittee(validator: EthAddress): Promise<boolean> {
228
- const committee = await this.getCommittee();
357
+ /** Check if a validator is in the given slot's committee */
358
+ async isInCommittee(slot: SlotTag, validator: EthAddress): Promise<boolean> {
359
+ const { committee } = await this.getCommittee(slot);
360
+ if (!committee) {
361
+ return false;
362
+ }
229
363
  return committee.some(v => v.equals(validator));
230
364
  }
365
+
366
+ /** From the set of given addresses, return all that are on the committee for the given slot */
367
+ async filterInCommittee(slot: SlotTag, validators: EthAddress[]): Promise<EthAddress[]> {
368
+ const { committee } = await this.getCommittee(slot);
369
+ if (!committee) {
370
+ return [];
371
+ }
372
+ const committeeSet = new Set(committee.map(v => v.toString()));
373
+ return validators.filter(v => committeeSet.has(v.toString()));
374
+ }
375
+
376
+ async getRegisteredValidators(): Promise<EthAddress[]> {
377
+ const validatorRefreshIntervalMs = this.config.validatorRefreshIntervalSeconds * 1000;
378
+ const validatorRefreshTime = this.lastValidatorRefresh + validatorRefreshIntervalMs;
379
+ if (validatorRefreshTime < this.dateProvider.now()) {
380
+ const currentSet = await this.rollup.getAttesters();
381
+ this.allValidators = new Set(currentSet.map(v => v.toString()));
382
+ this.lastValidatorRefresh = this.dateProvider.now();
383
+ }
384
+ return Array.from(this.allValidators.keys()).map(v => EthAddress.fromString(v));
385
+ }
231
386
  }
@@ -0,0 +1 @@
1
+ export * from './test_epoch_cache.js';