@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 +3 -2
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +2 -1
- package/dest/epoch_cache.d.ts +45 -39
- package/dest/epoch_cache.d.ts.map +1 -1
- package/dest/epoch_cache.js +56 -24
- package/dest/index.d.ts +1 -1
- package/package.json +10 -9
- package/src/config.ts +2 -6
- package/src/epoch_cache.ts +95 -46
package/dest/config.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type L1ContractsConfig
|
|
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=
|
|
5
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvY29uZmlnLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxLQUFLLGlCQUFpQixFQUErQixNQUFNLHdCQUF3QixDQUFDO0FBQzdGLE9BQU8sRUFBRSxLQUFLLGNBQWMsRUFBNEIsTUFBTSwyQkFBMkIsQ0FBQztBQUUxRixNQUFNLE1BQU0sZ0JBQWdCLEdBQUcsSUFBSSxDQUNqQyxjQUFjLEdBQUcsaUJBQWlCLEVBQ2xDLFdBQVcsR0FBRyxXQUFXLEdBQUcsdUJBQXVCLEdBQUcsc0JBQXNCLENBQzdFLENBQUM7QUFFRix3QkFBZ0IsMEJBQTBCLElBQUksZ0JBQWdCLENBRTdEIn0=
|
package/dest/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
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
|
|
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(),
|
package/dest/epoch_cache.d.ts
CHANGED
|
@@ -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:
|
|
8
|
-
slot:
|
|
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:
|
|
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' |
|
|
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:
|
|
24
|
-
computeProposerIndex(slot:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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<
|
|
54
|
+
protected cache: Map<EpochNumber, EpochCommitteeInfo>;
|
|
53
55
|
private allValidators;
|
|
54
56
|
private lastValidatorRefresh;
|
|
55
57
|
private readonly log;
|
|
56
|
-
constructor(rollup: RollupContract, l1constants
|
|
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:
|
|
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:
|
|
86
|
-
computeProposerIndex(slot:
|
|
87
|
-
/**
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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:
|
|
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:
|
|
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=
|
|
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":"
|
|
1
|
+
{"version":3,"file":"epoch_cache.d.ts","sourceRoot":"","sources":["../src/epoch_cache.ts"],"names":[],"mappings":"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"}
|
package/dest/epoch_cache.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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
|
|
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 [
|
|
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
|
|
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=
|
|
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.
|
|
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
|
|
19
|
-
"build:dev": "tsc
|
|
18
|
+
"build": "yarn clean && ../scripts/tsc.sh",
|
|
19
|
+
"build:dev": "../scripts/tsc.sh --watch",
|
|
20
20
|
"clean": "rm -rf ./dest .tsbuildinfo",
|
|
21
|
-
"start:dev": "
|
|
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.
|
|
30
|
-
"@aztec/foundation": "0.0.1-commit.
|
|
31
|
-
"@aztec/l1-artifacts": "0.0.1-commit.
|
|
32
|
-
"@aztec/stdlib": "0.0.1-commit.
|
|
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:@
|
|
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
|
-
|
|
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,
|
package/src/epoch_cache.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
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:
|
|
22
|
-
slot:
|
|
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:
|
|
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' |
|
|
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:
|
|
39
|
-
computeProposerIndex(slot:
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
|
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 [
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
241
|
+
private async computeCommittee(when: { epoch: EpochNumber; ts: bigint }): Promise<EpochCommitteeInfo> {
|
|
195
242
|
const { ts, epoch } = when;
|
|
196
|
-
const [
|
|
197
|
-
|
|
198
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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(
|
|
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
|
}
|