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