@aztec/epoch-cache 0.0.1-commit.24de95ac → 0.0.1-commit.3469e52

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';
1
+ import { type L1ContractsConfig } from '@aztec/ethereum/config';
2
+ import { type L1ReaderConfig } from '@aztec/ethereum/l1-reader';
2
3
  export type EpochCacheConfig = Pick<L1ReaderConfig & L1ContractsConfig, 'l1RpcUrls' | 'l1ChainId' | 'viemPollingIntervalMS' | 'ethereumSlotDuration'>;
3
4
  export declare function getEpochCacheConfigEnvVars(): EpochCacheConfig;
4
- //# sourceMappingURL=config.d.ts.map
5
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvY29uZmlnLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxLQUFLLGlCQUFpQixFQUErQixNQUFNLHdCQUF3QixDQUFDO0FBQzdGLE9BQU8sRUFBRSxLQUFLLGNBQWMsRUFBNEIsTUFBTSwyQkFBMkIsQ0FBQztBQUUxRixNQUFNLE1BQU0sZ0JBQWdCLEdBQUcsSUFBSSxDQUNqQyxjQUFjLEdBQUcsaUJBQWlCLEVBQ2xDLFdBQVcsR0FBRyxXQUFXLEdBQUcsdUJBQXVCLEdBQUcsc0JBQXNCLENBQzdFLENBQUM7QUFFRix3QkFBZ0IsMEJBQTBCLElBQUksZ0JBQWdCLENBRTdEIn0=
@@ -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,EAClC,WAAW,GAAG,WAAW,GAAG,uBAAuB,GAAG,sBAAsB,CAC7E,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,sBAAsB,CAC7E,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,33 +1,35 @@
1
- import { RollupContract } from '@aztec/ethereum';
1
+ import { RollupContract } from '@aztec/ethereum/contracts';
2
+ import { EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
2
3
  import { EthAddress } from '@aztec/foundation/eth-address';
3
4
  import { DateProvider } from '@aztec/foundation/timer';
4
5
  import { type L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
5
6
  import { type EpochCacheConfig } from './config.js';
6
7
  export type EpochAndSlot = {
7
- epoch: bigint;
8
- slot: bigint;
8
+ epoch: EpochNumber;
9
+ slot: SlotNumber;
9
10
  ts: bigint;
10
11
  };
11
12
  export type EpochCommitteeInfo = {
12
13
  committee: EthAddress[] | undefined;
13
14
  seed: bigint;
14
- epoch: bigint;
15
+ epoch: EpochNumber;
16
+ /** True if the epoch is within an open escape hatch window. */
17
+ isEscapeHatchOpen: boolean;
15
18
  };
16
- export type SlotTag = 'now' | 'next' | bigint;
19
+ export type SlotTag = 'now' | 'next' | SlotNumber;
17
20
  export interface EpochCacheInterface {
18
21
  getCommittee(slot: SlotTag | undefined): Promise<EpochCommitteeInfo>;
19
22
  getEpochAndSlotNow(): EpochAndSlot;
20
23
  getEpochAndSlotInNextL1Slot(): EpochAndSlot & {
21
24
  now: bigint;
22
25
  };
23
- getProposerIndexEncoding(epoch: bigint, slot: bigint, seed: bigint): `0x${string}`;
24
- computeProposerIndex(slot: bigint, epoch: bigint, seed: bigint, size: bigint): bigint;
25
- getProposerAttesterAddressInCurrentOrNextSlot(): Promise<{
26
- currentProposer: EthAddress | undefined;
27
- nextProposer: EthAddress | undefined;
28
- currentSlot: bigint;
29
- nextSlot: bigint;
30
- }>;
26
+ getProposerIndexEncoding(epoch: EpochNumber, slot: SlotNumber, seed: bigint): `0x${string}`;
27
+ computeProposerIndex(slot: SlotNumber, epoch: EpochNumber, seed: bigint, size: bigint): bigint;
28
+ getCurrentAndNextSlot(): {
29
+ currentSlot: SlotNumber;
30
+ nextSlot: SlotNumber;
31
+ };
32
+ getProposerAttesterAddressInSlot(slot: SlotNumber): Promise<EthAddress | undefined>;
31
33
  getRegisteredValidators(): Promise<EthAddress[]>;
32
34
  isInCommittee(slot: SlotTag, validator: EthAddress): Promise<boolean>;
33
35
  filterInCommittee(slot: SlotTag, validators: EthAddress[]): Promise<EthAddress[]>;
@@ -49,11 +51,14 @@ export declare class EpochCache implements EpochCacheInterface {
49
51
  cacheSize: number;
50
52
  validatorRefreshIntervalSeconds: number;
51
53
  };
52
- protected cache: Map<bigint, EpochCommitteeInfo>;
54
+ protected cache: Map<EpochNumber, EpochCommitteeInfo>;
53
55
  private allValidators;
54
56
  private lastValidatorRefresh;
55
57
  private readonly log;
56
- constructor(rollup: RollupContract, l1constants?: L1RollupConstants, dateProvider?: DateProvider, config?: {
58
+ constructor(rollup: RollupContract, l1constants: L1RollupConstants & {
59
+ lagInEpochsForValidatorSet: number;
60
+ lagInEpochsForRandao: number;
61
+ }, dateProvider?: DateProvider, config?: {
57
62
  cacheSize: number;
58
63
  validatorRefreshIntervalSeconds: number;
59
64
  });
@@ -70,7 +75,21 @@ export declare class EpochCache implements EpochCacheInterface {
70
75
  now: bigint;
71
76
  };
72
77
  private getEpochAndSlotAtTimestamp;
73
- getCommitteeForEpoch(epoch: bigint): Promise<EpochCommitteeInfo>;
78
+ getCommitteeForEpoch(epoch: EpochNumber): Promise<EpochCommitteeInfo>;
79
+ /**
80
+ * Returns whether the escape hatch is open for the given epoch.
81
+ *
82
+ * Uses the already-cached EpochCommitteeInfo when available. If not cached, it will fetch
83
+ * the epoch committee info (which includes the escape hatch flag) and return it.
84
+ */
85
+ isEscapeHatchOpen(epoch: EpochNumber): Promise<boolean>;
86
+ /**
87
+ * Returns whether the escape hatch is open for the epoch containing the given slot.
88
+ *
89
+ * This is a lightweight helper intended for callers that already have a slot number and only
90
+ * need the escape hatch flag (without pulling full committee info).
91
+ */
92
+ isEscapeHatchOpenAtSlot(slot?: SlotTag): Promise<boolean>;
74
93
  /**
75
94
  * Get the current validator set
76
95
  * @param nextSlot - If true, get the validator set for the next slot.
@@ -82,44 +101,31 @@ export declare class EpochCache implements EpochCacheInterface {
82
101
  /**
83
102
  * Get the ABI encoding of the proposer index - see ValidatorSelectionLib.sol computeProposerIndex
84
103
  */
85
- getProposerIndexEncoding(epoch: bigint, slot: bigint, seed: bigint): `0x${string}`;
86
- computeProposerIndex(slot: bigint, epoch: bigint, seed: bigint, size: bigint): bigint;
87
- /**
88
- * Returns the current and next proposer's attester address
89
- *
90
- * We return the next proposer's attester address as the node will check if it is the proposer at the next ethereum block,
91
- * which can be the next slot. If this is the case, then it will send proposals early.
92
- */
93
- getProposerAttesterAddressInCurrentOrNextSlot(): Promise<{
94
- currentSlot: bigint;
95
- nextSlot: bigint;
96
- currentProposer: EthAddress | undefined;
97
- nextProposer: EthAddress | undefined;
98
- }>;
104
+ getProposerIndexEncoding(epoch: EpochNumber, slot: SlotNumber, seed: bigint): `0x${string}`;
105
+ computeProposerIndex(slot: SlotNumber, epoch: EpochNumber, seed: bigint, size: bigint): bigint;
106
+ /** Returns the current and next L2 slot numbers. */
107
+ getCurrentAndNextSlot(): {
108
+ currentSlot: SlotNumber;
109
+ nextSlot: SlotNumber;
110
+ };
99
111
  /**
100
112
  * Get the proposer attester address in the given L2 slot
101
113
  * @returns The proposer attester address. If the committee does not exist, we throw a NoCommitteeError.
102
114
  * If the committee is empty (i.e. target committee size is 0, and anyone can propose), we return undefined.
103
115
  */
104
- getProposerAttesterAddressInSlot(slot: bigint): Promise<EthAddress | undefined>;
116
+ getProposerAttesterAddressInSlot(slot: SlotNumber): Promise<EthAddress | undefined>;
105
117
  /**
106
118
  * Get the proposer attester address in the next slot
107
119
  * @returns The proposer attester address. If the committee does not exist, we throw a NoCommitteeError.
108
120
  * If the committee is empty (i.e. target committee size is 0, and anyone can propose), we return undefined.
109
121
  */
110
122
  getProposerAttesterAddressInNextSlot(): Promise<EthAddress | undefined>;
111
- /**
112
- * Get the proposer attester address at a given epoch and slot
113
- * @param when - The epoch and slot to get the proposer attester address at
114
- * @returns The proposer attester address. If the committee does not exist, we throw a NoCommitteeError.
115
- * If the committee is empty (i.e. target committee size is 0, and anyone can propose), we return undefined.
116
- */
117
123
  private getProposerAttesterAddressAt;
118
- getProposerFromEpochCommittee(epochCommitteeInfo: EpochCommitteeInfo, slot: bigint): EthAddress | undefined;
124
+ getProposerFromEpochCommittee(epochCommitteeInfo: EpochCommitteeInfo, slot: SlotNumber): EthAddress | undefined;
119
125
  /** Check if a validator is in the given slot's committee */
120
126
  isInCommittee(slot: SlotTag, validator: EthAddress): Promise<boolean>;
121
127
  /** From the set of given addresses, return all that are on the committee for the given slot */
122
128
  filterInCommittee(slot: SlotTag, validators: EthAddress[]): Promise<EthAddress[]>;
123
129
  getRegisteredValidators(): Promise<EthAddress[]>;
124
130
  }
125
- //# sourceMappingURL=epoch_cache.d.ts.map
131
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXBvY2hfY2FjaGUuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9lcG9jaF9jYWNoZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQW9CLGNBQWMsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBQzdFLE9BQU8sRUFBRSxXQUFXLEVBQUUsVUFBVSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDMUUsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBRTNELE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUN2RCxPQUFPLEVBQ0wsS0FBSyxpQkFBaUIsRUFPdkIsTUFBTSw2QkFBNkIsQ0FBQztBQUlyQyxPQUFPLEVBQUUsS0FBSyxnQkFBZ0IsRUFBOEIsTUFBTSxhQUFhLENBQUM7QUFFaEYsTUFBTSxNQUFNLFlBQVksR0FBRztJQUN6QixLQUFLLEVBQUUsV0FBVyxDQUFDO0lBQ25CLElBQUksRUFBRSxVQUFVLENBQUM7SUFDakIsRUFBRSxFQUFFLE1BQU0sQ0FBQztDQUNaLENBQUM7QUFFRixNQUFNLE1BQU0sa0JBQWtCLEdBQUc7SUFDL0IsU0FBUyxFQUFFLFVBQVUsRUFBRSxHQUFHLFNBQVMsQ0FBQztJQUNwQyxJQUFJLEVBQUUsTUFBTSxDQUFDO0lBQ2IsS0FBSyxFQUFFLFdBQVcsQ0FBQztJQUNuQiwrREFBK0Q7SUFDL0QsaUJBQWlCLEVBQUUsT0FBTyxDQUFDO0NBQzVCLENBQUM7QUFFRixNQUFNLE1BQU0sT0FBTyxHQUFHLEtBQUssR0FBRyxNQUFNLEdBQUcsVUFBVSxDQUFDO0FBRWxELE1BQU0sV0FBVyxtQkFBbUI7SUFDbEMsWUFBWSxDQUFDLElBQUksRUFBRSxPQUFPLEdBQUcsU0FBUyxHQUFHLE9BQU8sQ0FBQyxrQkFBa0IsQ0FBQyxDQUFDO0lBQ3JFLGtCQUFrQixJQUFJLFlBQVksQ0FBQztJQUNuQywyQkFBMkIsSUFBSSxZQUFZLEdBQUc7UUFBRSxHQUFHLEVB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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,8 +1,9 @@
1
- import { NoCommitteeError, RollupContract, createEthereumChain } from '@aztec/ethereum';
1
+ import { createEthereumChain } from '@aztec/ethereum/chain';
2
+ import { NoCommitteeError, RollupContract } from '@aztec/ethereum/contracts';
2
3
  import { EthAddress } from '@aztec/foundation/eth-address';
3
4
  import { createLogger } from '@aztec/foundation/log';
4
5
  import { DateProvider } from '@aztec/foundation/timer';
5
- import { EmptyL1RollupConstants, getEpochAtSlot, getEpochNumberAtTimestamp, getSlotAtTimestamp, getSlotRangeForEpoch, getTimestampForSlot, getTimestampRangeForEpoch } from '@aztec/stdlib/epoch-helpers';
6
+ import { getEpochAtSlot, getEpochNumberAtTimestamp, getSlotAtTimestamp, getSlotRangeForEpoch, getTimestampForSlot, getTimestampRangeForEpoch } from '@aztec/stdlib/epoch-helpers';
6
7
  import { createPublicClient, encodeAbiParameters, fallback, http, keccak256 } from 'viem';
7
8
  import { getEpochCacheConfigEnvVars } from './config.js';
8
9
  /**
@@ -18,11 +19,12 @@ import { getEpochCacheConfigEnvVars } from './config.js';
18
19
  l1constants;
19
20
  dateProvider;
20
21
  config;
22
+ // eslint-disable-next-line aztec-custom/no-non-primitive-in-collections
21
23
  cache;
22
24
  allValidators;
23
25
  lastValidatorRefresh;
24
26
  log;
25
- constructor(rollup, l1constants = EmptyL1RollupConstants, dateProvider = new DateProvider(), config = {
27
+ constructor(rollup, l1constants, dateProvider = new DateProvider(), config = {
26
28
  cacheSize: 12,
27
29
  validatorRefreshIntervalSeconds: 60
28
30
  }){
@@ -48,17 +50,21 @@ import { getEpochCacheConfigEnvVars } from './config.js';
48
50
  const chain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
49
51
  const publicClient = createPublicClient({
50
52
  chain: chain.chainInfo,
51
- transport: fallback(config.l1RpcUrls.map((url)=>http(url))),
53
+ transport: fallback(config.l1RpcUrls.map((url)=>http(url, {
54
+ batch: false
55
+ }))),
52
56
  pollingInterval: config.viemPollingIntervalMS
53
57
  });
54
58
  rollup = new RollupContract(publicClient, rollupOrAddress.toString());
55
59
  }
56
- const [l1StartBlock, l1GenesisTime, proofSubmissionEpochs, slotDuration, epochDuration] = await Promise.all([
60
+ const [l1StartBlock, l1GenesisTime, proofSubmissionEpochs, slotDuration, epochDuration, lagInEpochsForValidatorSet, lagInEpochsForRandao] = await Promise.all([
57
61
  rollup.getL1StartBlock(),
58
62
  rollup.getL1GenesisTime(),
59
63
  rollup.getProofSubmissionEpochs(),
60
64
  rollup.getSlotDuration(),
61
- rollup.getEpochDuration()
65
+ rollup.getEpochDuration(),
66
+ rollup.getLagInEpochsForValidatorSet(),
67
+ rollup.getLagInEpochsForRandao()
62
68
  ]);
63
69
  const l1RollupConstants = {
64
70
  l1StartBlock,
@@ -66,7 +72,9 @@ import { getEpochCacheConfigEnvVars } from './config.js';
66
72
  proofSubmissionEpochs: Number(proofSubmissionEpochs),
67
73
  slotDuration: Number(slotDuration),
68
74
  epochDuration: Number(epochDuration),
69
- ethereumSlotDuration: config.ethereumSlotDuration
75
+ ethereumSlotDuration: config.ethereumSlotDuration,
76
+ lagInEpochsForValidatorSet: Number(lagInEpochsForValidatorSet),
77
+ lagInEpochsForRandao: Number(lagInEpochsForRandao)
70
78
  };
71
79
  return new EpochCache(rollup, l1RollupConstants, deps.dateProvider);
72
80
  }
@@ -113,6 +121,28 @@ import { getEpochCacheConfigEnvVars } from './config.js';
113
121
  return this.getCommittee(startSlot);
114
122
  }
115
123
  /**
124
+ * Returns whether the escape hatch is open for the given epoch.
125
+ *
126
+ * Uses the already-cached EpochCommitteeInfo when available. If not cached, it will fetch
127
+ * the epoch committee info (which includes the escape hatch flag) and return it.
128
+ */ async isEscapeHatchOpen(epoch) {
129
+ const cached = this.cache.get(epoch);
130
+ if (cached) {
131
+ return cached.isEscapeHatchOpen;
132
+ }
133
+ const info = await this.getCommitteeForEpoch(epoch);
134
+ return info.isEscapeHatchOpen;
135
+ }
136
+ /**
137
+ * Returns whether the escape hatch is open for the epoch containing the given slot.
138
+ *
139
+ * This is a lightweight helper intended for callers that already have a slot number and only
140
+ * need the escape hatch flag (without pulling full committee info).
141
+ */ async isEscapeHatchOpenAtSlot(slot = 'now') {
142
+ const epoch = slot === 'now' ? this.getEpochAndSlotNow().epoch : slot === 'next' ? this.getEpochAndSlotInNextL1Slot().epoch : getEpochAtSlot(slot, this.l1constants);
143
+ return await this.isEscapeHatchOpen(epoch);
144
+ }
145
+ /**
116
146
  * Get the current validator set
117
147
  * @param nextSlot - If true, get the validator set for the next slot.
118
148
  * @returns The current validator set.
@@ -145,15 +175,24 @@ import { getEpochCacheConfigEnvVars } from './config.js';
145
175
  }
146
176
  async computeCommittee(when) {
147
177
  const { ts, epoch } = when;
148
- const [committeeHex, seed] = await Promise.all([
178
+ const [committee, seedBuffer, l1Timestamp, isEscapeHatchOpen] = await Promise.all([
149
179
  this.rollup.getCommitteeAt(ts),
150
- this.rollup.getSampleSeedAt(ts)
180
+ this.rollup.getSampleSeedAt(ts),
181
+ this.rollup.client.getBlock({
182
+ includeTransactions: false
183
+ }).then((b)=>b.timestamp),
184
+ this.rollup.isEscapeHatchOpen(epoch)
151
185
  ]);
152
- const committee = committeeHex?.map((v)=>EthAddress.fromString(v));
186
+ const { lagInEpochsForValidatorSet, epochDuration, slotDuration } = this.l1constants;
187
+ const sub = BigInt(lagInEpochsForValidatorSet) * BigInt(epochDuration) * BigInt(slotDuration);
188
+ if (ts - sub > l1Timestamp) {
189
+ throw new Error(`Cannot query committee for future epoch ${epoch} with timestamp ${ts} (current L1 time is ${l1Timestamp}). Check your Ethereum node is synced.`);
190
+ }
153
191
  return {
154
192
  committee,
155
- seed,
156
- epoch
193
+ seed: seedBuffer.toBigInt(),
194
+ epoch,
195
+ isEscapeHatchOpen
157
196
  };
158
197
  }
159
198
  /**
@@ -173,8 +212,8 @@ import { getEpochCacheConfigEnvVars } from './config.js';
173
212
  name: 'seed'
174
213
  }
175
214
  ], [
176
- epoch,
177
- slot,
215
+ BigInt(epoch),
216
+ BigInt(slot),
178
217
  seed
179
218
  ]);
180
219
  }
@@ -185,17 +224,10 @@ import { getEpochCacheConfigEnvVars } from './config.js';
185
224
  }
186
225
  return BigInt(keccak256(this.getProposerIndexEncoding(epoch, slot, seed))) % size;
187
226
  }
188
- /**
189
- * Returns the current and next proposer's attester address
190
- *
191
- * We return the next proposer's attester address as the node will check if it is the proposer at the next ethereum block,
192
- * which can be the next slot. If this is the case, then it will send proposals early.
193
- */ async getProposerAttesterAddressInCurrentOrNextSlot() {
227
+ /** Returns the current and next L2 slot numbers. */ getCurrentAndNextSlot() {
194
228
  const current = this.getEpochAndSlotNow();
195
229
  const next = this.getEpochAndSlotInNextL1Slot();
196
230
  return {
197
- currentProposer: await this.getProposerAttesterAddressAt(current),
198
- nextProposer: await this.getProposerAttesterAddressAt(next),
199
231
  currentSlot: current.slot,
200
232
  nextSlot: next.slot
201
233
  };
@@ -259,9 +291,9 @@ import { getEpochCacheConfigEnvVars } from './config.js';
259
291
  const validatorRefreshTime = this.lastValidatorRefresh + validatorRefreshIntervalMs;
260
292
  if (validatorRefreshTime < this.dateProvider.now()) {
261
293
  const currentSet = await this.rollup.getAttesters();
262
- this.allValidators = new Set(currentSet);
294
+ this.allValidators = new Set(currentSet.map((v)=>v.toString()));
263
295
  this.lastValidatorRefresh = this.dateProvider.now();
264
296
  }
265
- return Array.from(this.allValidators.keys().map((v)=>EthAddress.fromString(v)));
297
+ return Array.from(this.allValidators.keys()).map((v)=>EthAddress.fromString(v));
266
298
  }
267
299
  }
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=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/epoch-cache",
3
- "version": "0.0.1-commit.24de95ac",
3
+ "version": "0.0.1-commit.3469e52",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dest/index.js",
@@ -15,10 +15,10 @@
15
15
  "tsconfig": "./tsconfig.json"
16
16
  },
17
17
  "scripts": {
18
- "build": "yarn clean && tsc -b",
19
- "build:dev": "tsc -b --watch",
18
+ "build": "yarn clean && ../scripts/tsc.sh",
19
+ "build:dev": "../scripts/tsc.sh --watch",
20
20
  "clean": "rm -rf ./dest .tsbuildinfo",
21
- "start:dev": "tsc-watch -p tsconfig.json --onSuccess 'yarn start'",
21
+ "start:dev": "concurrently -k \"../scripts/tsc.sh --watch\" \"nodemon --watch dest --exec yarn start\"",
22
22
  "start": "node ./dest/index.js",
23
23
  "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=${JEST_MAX_WORKERS:-8}"
24
24
  },
@@ -26,22 +26,23 @@
26
26
  "../package.common.json"
27
27
  ],
28
28
  "dependencies": {
29
- "@aztec/ethereum": "0.0.1-commit.24de95ac",
30
- "@aztec/foundation": "0.0.1-commit.24de95ac",
31
- "@aztec/l1-artifacts": "0.0.1-commit.24de95ac",
32
- "@aztec/stdlib": "0.0.1-commit.24de95ac",
29
+ "@aztec/ethereum": "0.0.1-commit.3469e52",
30
+ "@aztec/foundation": "0.0.1-commit.3469e52",
31
+ "@aztec/l1-artifacts": "0.0.1-commit.3469e52",
32
+ "@aztec/stdlib": "0.0.1-commit.3469e52",
33
33
  "@viem/anvil": "^0.0.10",
34
34
  "dotenv": "^16.0.3",
35
35
  "get-port": "^7.1.0",
36
36
  "jest-mock-extended": "^4.0.0",
37
37
  "tslib": "^2.4.0",
38
- "viem": "npm:@spalladino/viem@2.38.2-eip7594.0",
38
+ "viem": "npm:@aztec/viem@2.38.2",
39
39
  "zod": "^3.23.8"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@jest/globals": "^30.0.0",
43
43
  "@types/jest": "^30.0.0",
44
44
  "@types/node": "^22.15.17",
45
+ "@typescript/native-preview": "7.0.0-dev.20260113.1",
45
46
  "jest": "^30.0.0",
46
47
  "ts-node": "^10.9.1",
47
48
  "typescript": "^5.3.3"
package/src/config.ts CHANGED
@@ -1,9 +1,5 @@
1
- import {
2
- type L1ContractsConfig,
3
- type L1ReaderConfig,
4
- getL1ContractsConfigEnvVars,
5
- getL1ReaderConfigFromEnv,
6
- } from '@aztec/ethereum';
1
+ import { type L1ContractsConfig, getL1ContractsConfigEnvVars } from '@aztec/ethereum/config';
2
+ import { type L1ReaderConfig, getL1ReaderConfigFromEnv } from '@aztec/ethereum/l1-reader';
7
3
 
8
4
  export type EpochCacheConfig = Pick<
9
5
  L1ReaderConfig & L1ContractsConfig,
@@ -1,9 +1,10 @@
1
- import { NoCommitteeError, RollupContract, createEthereumChain } from '@aztec/ethereum';
1
+ import { createEthereumChain } from '@aztec/ethereum/chain';
2
+ import { NoCommitteeError, RollupContract } from '@aztec/ethereum/contracts';
3
+ import { EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
2
4
  import { EthAddress } from '@aztec/foundation/eth-address';
3
5
  import { type Logger, createLogger } from '@aztec/foundation/log';
4
6
  import { DateProvider } from '@aztec/foundation/timer';
5
7
  import {
6
- EmptyL1RollupConstants,
7
8
  type L1RollupConstants,
8
9
  getEpochAtSlot,
9
10
  getEpochNumberAtTimestamp,
@@ -18,31 +19,29 @@ import { createPublicClient, encodeAbiParameters, fallback, http, keccak256 } fr
18
19
  import { type EpochCacheConfig, getEpochCacheConfigEnvVars } from './config.js';
19
20
 
20
21
  export type EpochAndSlot = {
21
- epoch: bigint;
22
- slot: bigint;
22
+ epoch: EpochNumber;
23
+ slot: SlotNumber;
23
24
  ts: bigint;
24
25
  };
25
26
 
26
27
  export type EpochCommitteeInfo = {
27
28
  committee: EthAddress[] | undefined;
28
29
  seed: bigint;
29
- epoch: bigint;
30
+ epoch: EpochNumber;
31
+ /** True if the epoch is within an open escape hatch window. */
32
+ isEscapeHatchOpen: boolean;
30
33
  };
31
34
 
32
- export type SlotTag = 'now' | 'next' | bigint;
35
+ export type SlotTag = 'now' | 'next' | SlotNumber;
33
36
 
34
37
  export interface EpochCacheInterface {
35
38
  getCommittee(slot: SlotTag | undefined): Promise<EpochCommitteeInfo>;
36
39
  getEpochAndSlotNow(): EpochAndSlot;
37
40
  getEpochAndSlotInNextL1Slot(): EpochAndSlot & { now: bigint };
38
- getProposerIndexEncoding(epoch: bigint, slot: bigint, seed: bigint): `0x${string}`;
39
- computeProposerIndex(slot: bigint, epoch: bigint, seed: bigint, size: bigint): bigint;
40
- getProposerAttesterAddressInCurrentOrNextSlot(): Promise<{
41
- currentProposer: EthAddress | undefined;
42
- nextProposer: EthAddress | undefined;
43
- currentSlot: bigint;
44
- nextSlot: bigint;
45
- }>;
41
+ getProposerIndexEncoding(epoch: EpochNumber, slot: SlotNumber, seed: bigint): `0x${string}`;
42
+ computeProposerIndex(slot: SlotNumber, epoch: EpochNumber, seed: bigint, size: bigint): bigint;
43
+ getCurrentAndNextSlot(): { currentSlot: SlotNumber; nextSlot: SlotNumber };
44
+ getProposerAttesterAddressInSlot(slot: SlotNumber): Promise<EthAddress | undefined>;
46
45
  getRegisteredValidators(): Promise<EthAddress[]>;
47
46
  isInCommittee(slot: SlotTag, validator: EthAddress): Promise<boolean>;
48
47
  filterInCommittee(slot: SlotTag, validators: EthAddress[]): Promise<EthAddress[]>;
@@ -58,14 +57,18 @@ export interface EpochCacheInterface {
58
57
  * Note: This class is very dependent on the system clock being in sync.
59
58
  */
60
59
  export class EpochCache implements EpochCacheInterface {
61
- protected cache: Map<bigint, EpochCommitteeInfo> = new Map();
60
+ // eslint-disable-next-line aztec-custom/no-non-primitive-in-collections
61
+ protected cache: Map<EpochNumber, EpochCommitteeInfo> = new Map();
62
62
  private allValidators: Set<string> = new Set();
63
63
  private lastValidatorRefresh = 0;
64
64
  private readonly log: Logger = createLogger('epoch-cache');
65
65
 
66
66
  constructor(
67
67
  private rollup: RollupContract,
68
- private readonly l1constants: L1RollupConstants = EmptyL1RollupConstants,
68
+ private readonly l1constants: L1RollupConstants & {
69
+ lagInEpochsForValidatorSet: number;
70
+ lagInEpochsForRandao: number;
71
+ },
69
72
  private readonly dateProvider: DateProvider = new DateProvider(),
70
73
  protected readonly config = { cacheSize: 12, validatorRefreshIntervalSeconds: 60 },
71
74
  ) {
@@ -89,27 +92,39 @@ export class EpochCache implements EpochCacheInterface {
89
92
  const chain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
90
93
  const publicClient = createPublicClient({
91
94
  chain: chain.chainInfo,
92
- transport: fallback(config.l1RpcUrls.map(url => http(url))),
95
+ transport: fallback(config.l1RpcUrls.map(url => http(url, { batch: false }))),
93
96
  pollingInterval: config.viemPollingIntervalMS,
94
97
  });
95
98
  rollup = new RollupContract(publicClient, rollupOrAddress.toString());
96
99
  }
97
100
 
98
- const [l1StartBlock, l1GenesisTime, proofSubmissionEpochs, slotDuration, epochDuration] = await Promise.all([
101
+ const [
102
+ l1StartBlock,
103
+ l1GenesisTime,
104
+ proofSubmissionEpochs,
105
+ slotDuration,
106
+ epochDuration,
107
+ lagInEpochsForValidatorSet,
108
+ lagInEpochsForRandao,
109
+ ] = await Promise.all([
99
110
  rollup.getL1StartBlock(),
100
111
  rollup.getL1GenesisTime(),
101
112
  rollup.getProofSubmissionEpochs(),
102
113
  rollup.getSlotDuration(),
103
114
  rollup.getEpochDuration(),
115
+ rollup.getLagInEpochsForValidatorSet(),
116
+ rollup.getLagInEpochsForRandao(),
104
117
  ] as const);
105
118
 
106
- const l1RollupConstants: L1RollupConstants = {
119
+ const l1RollupConstants = {
107
120
  l1StartBlock,
108
121
  l1GenesisTime,
109
122
  proofSubmissionEpochs: Number(proofSubmissionEpochs),
110
123
  slotDuration: Number(slotDuration),
111
124
  epochDuration: Number(epochDuration),
112
125
  ethereumSlotDuration: config.ethereumSlotDuration,
126
+ lagInEpochsForValidatorSet: Number(lagInEpochsForValidatorSet),
127
+ lagInEpochsForRandao: Number(lagInEpochsForRandao),
113
128
  };
114
129
 
115
130
  return new EpochCache(rollup, l1RollupConstants, deps.dateProvider);
@@ -128,7 +143,7 @@ export class EpochCache implements EpochCacheInterface {
128
143
  return BigInt(Math.floor(this.dateProvider.now() / 1000));
129
144
  }
130
145
 
131
- private getEpochAndSlotAtSlot(slot: bigint): EpochAndSlot {
146
+ private getEpochAndSlotAtSlot(slot: SlotNumber): EpochAndSlot {
132
147
  const epoch = getEpochAtSlot(slot, this.l1constants);
133
148
  const ts = getTimestampRangeForEpoch(epoch, this.l1constants)[0];
134
149
  return { epoch, ts, slot };
@@ -149,11 +164,43 @@ export class EpochCache implements EpochCacheInterface {
149
164
  };
150
165
  }
151
166
 
152
- public getCommitteeForEpoch(epoch: bigint): Promise<EpochCommitteeInfo> {
167
+ public getCommitteeForEpoch(epoch: EpochNumber): Promise<EpochCommitteeInfo> {
153
168
  const [startSlot] = getSlotRangeForEpoch(epoch, this.l1constants);
154
169
  return this.getCommittee(startSlot);
155
170
  }
156
171
 
172
+ /**
173
+ * Returns whether the escape hatch is open for the given epoch.
174
+ *
175
+ * Uses the already-cached EpochCommitteeInfo when available. If not cached, it will fetch
176
+ * the epoch committee info (which includes the escape hatch flag) and return it.
177
+ */
178
+ public async isEscapeHatchOpen(epoch: EpochNumber): Promise<boolean> {
179
+ const cached = this.cache.get(epoch);
180
+ if (cached) {
181
+ return cached.isEscapeHatchOpen;
182
+ }
183
+ const info = await this.getCommitteeForEpoch(epoch);
184
+ return info.isEscapeHatchOpen;
185
+ }
186
+
187
+ /**
188
+ * Returns whether the escape hatch is open for the epoch containing the given slot.
189
+ *
190
+ * This is a lightweight helper intended for callers that already have a slot number and only
191
+ * need the escape hatch flag (without pulling full committee info).
192
+ */
193
+ public async isEscapeHatchOpenAtSlot(slot: SlotTag = 'now'): Promise<boolean> {
194
+ const epoch =
195
+ slot === 'now'
196
+ ? this.getEpochAndSlotNow().epoch
197
+ : slot === 'next'
198
+ ? this.getEpochAndSlotInNextL1Slot().epoch
199
+ : getEpochAtSlot(slot, this.l1constants);
200
+
201
+ return await this.isEscapeHatchOpen(epoch);
202
+ }
203
+
157
204
  /**
158
205
  * Get the current validator set
159
206
  * @param nextSlot - If true, get the validator set for the next slot.
@@ -191,28 +238,39 @@ export class EpochCache implements EpochCacheInterface {
191
238
  }
192
239
  }
193
240
 
194
- private async computeCommittee(when: { epoch: bigint; ts: bigint }): Promise<EpochCommitteeInfo> {
241
+ private async computeCommittee(when: { epoch: EpochNumber; ts: bigint }): Promise<EpochCommitteeInfo> {
195
242
  const { ts, epoch } = when;
196
- const [committeeHex, seed] = await Promise.all([this.rollup.getCommitteeAt(ts), this.rollup.getSampleSeedAt(ts)]);
197
- const committee = committeeHex?.map((v: `0x${string}`) => EthAddress.fromString(v));
198
- return { committee, seed, epoch };
243
+ const [committee, seedBuffer, l1Timestamp, isEscapeHatchOpen] = await Promise.all([
244
+ this.rollup.getCommitteeAt(ts),
245
+ this.rollup.getSampleSeedAt(ts),
246
+ this.rollup.client.getBlock({ includeTransactions: false }).then(b => b.timestamp),
247
+ this.rollup.isEscapeHatchOpen(epoch),
248
+ ]);
249
+ const { lagInEpochsForValidatorSet, epochDuration, slotDuration } = this.l1constants;
250
+ const sub = BigInt(lagInEpochsForValidatorSet) * BigInt(epochDuration) * BigInt(slotDuration);
251
+ if (ts - sub > l1Timestamp) {
252
+ throw new Error(
253
+ `Cannot query committee for future epoch ${epoch} with timestamp ${ts} (current L1 time is ${l1Timestamp}). Check your Ethereum node is synced.`,
254
+ );
255
+ }
256
+ return { committee, seed: seedBuffer.toBigInt(), epoch, isEscapeHatchOpen };
199
257
  }
200
258
 
201
259
  /**
202
260
  * Get the ABI encoding of the proposer index - see ValidatorSelectionLib.sol computeProposerIndex
203
261
  */
204
- getProposerIndexEncoding(epoch: bigint, slot: bigint, seed: bigint): `0x${string}` {
262
+ getProposerIndexEncoding(epoch: EpochNumber, slot: SlotNumber, seed: bigint): `0x${string}` {
205
263
  return encodeAbiParameters(
206
264
  [
207
265
  { type: 'uint256', name: 'epoch' },
208
266
  { type: 'uint256', name: 'slot' },
209
267
  { type: 'uint256', name: 'seed' },
210
268
  ],
211
- [epoch, slot, seed],
269
+ [BigInt(epoch), BigInt(slot), seed],
212
270
  );
213
271
  }
214
272
 
215
- public computeProposerIndex(slot: bigint, epoch: bigint, seed: bigint, size: bigint): bigint {
273
+ public computeProposerIndex(slot: SlotNumber, epoch: EpochNumber, seed: bigint, size: bigint): bigint {
216
274
  // if committe size is 0, then mod 1 is 0
217
275
  if (size === 0n) {
218
276
  return 0n;
@@ -220,24 +278,12 @@ export class EpochCache implements EpochCacheInterface {
220
278
  return BigInt(keccak256(this.getProposerIndexEncoding(epoch, slot, seed))) % size;
221
279
  }
222
280
 
223
- /**
224
- * Returns the current and next proposer's attester address
225
- *
226
- * We return the next proposer's attester address as the node will check if it is the proposer at the next ethereum block,
227
- * which can be the next slot. If this is the case, then it will send proposals early.
228
- */
229
- public async getProposerAttesterAddressInCurrentOrNextSlot(): Promise<{
230
- currentSlot: bigint;
231
- nextSlot: bigint;
232
- currentProposer: EthAddress | undefined;
233
- nextProposer: EthAddress | undefined;
234
- }> {
281
+ /** Returns the current and next L2 slot numbers. */
282
+ public getCurrentAndNextSlot(): { currentSlot: SlotNumber; nextSlot: SlotNumber } {
235
283
  const current = this.getEpochAndSlotNow();
236
284
  const next = this.getEpochAndSlotInNextL1Slot();
237
285
 
238
286
  return {
239
- currentProposer: await this.getProposerAttesterAddressAt(current),
240
- nextProposer: await this.getProposerAttesterAddressAt(next),
241
287
  currentSlot: current.slot,
242
288
  nextSlot: next.slot,
243
289
  };
@@ -248,7 +294,7 @@ export class EpochCache implements EpochCacheInterface {
248
294
  * @returns The proposer attester address. If the committee does not exist, we throw a NoCommitteeError.
249
295
  * If the committee is empty (i.e. target committee size is 0, and anyone can propose), we return undefined.
250
296
  */
251
- public getProposerAttesterAddressInSlot(slot: bigint): Promise<EthAddress | undefined> {
297
+ public getProposerAttesterAddressInSlot(slot: SlotNumber): Promise<EthAddress | undefined> {
252
298
  const epochAndSlot = this.getEpochAndSlotAtSlot(slot);
253
299
  return this.getProposerAttesterAddressAt(epochAndSlot);
254
300
  }
@@ -282,7 +328,10 @@ export class EpochCache implements EpochCacheInterface {
282
328
  return committee[Number(proposerIndex)];
283
329
  }
284
330
 
285
- public getProposerFromEpochCommittee(epochCommitteeInfo: EpochCommitteeInfo, slot: bigint): EthAddress | undefined {
331
+ public getProposerFromEpochCommittee(
332
+ epochCommitteeInfo: EpochCommitteeInfo,
333
+ slot: SlotNumber,
334
+ ): EthAddress | undefined {
286
335
  if (!epochCommitteeInfo.committee || epochCommitteeInfo.committee.length === 0) {
287
336
  return undefined;
288
337
  }
@@ -320,9 +369,9 @@ export class EpochCache implements EpochCacheInterface {
320
369
  const validatorRefreshTime = this.lastValidatorRefresh + validatorRefreshIntervalMs;
321
370
  if (validatorRefreshTime < this.dateProvider.now()) {
322
371
  const currentSet = await this.rollup.getAttesters();
323
- this.allValidators = new Set(currentSet);
372
+ this.allValidators = new Set(currentSet.map(v => v.toString()));
324
373
  this.lastValidatorRefresh = this.dateProvider.now();
325
374
  }
326
- return Array.from(this.allValidators.keys().map(v => EthAddress.fromString(v)));
375
+ return Array.from(this.allValidators.keys()).map(v => EthAddress.fromString(v));
327
376
  }
328
377
  }