@waku/rln 0.1.5-5e19700.0 → 0.1.5-6997987.0
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/bundle/index.js +2 -2
- package/bundle/packages/rln/dist/contract/{rln_light_contract.js → rln_base_contract.js} +172 -168
- package/bundle/packages/rln/dist/contract/rln_contract.js +9 -419
- package/bundle/packages/rln/dist/contract/types.js +9 -0
- package/bundle/packages/rln/dist/create.js +1 -1
- package/bundle/packages/rln/dist/{rln_light.js → credentials_manager.js} +120 -36
- package/bundle/packages/rln/dist/rln.js +56 -166
- package/dist/.tsbuildinfo +1 -1
- package/dist/contract/{rln_light_contract.d.ts → rln_base_contract.d.ts} +25 -53
- package/dist/contract/{rln_light_contract.js → rln_base_contract.js} +172 -168
- package/dist/contract/rln_base_contract.js.map +1 -0
- package/dist/contract/rln_contract.d.ts +5 -122
- package/dist/contract/rln_contract.js +8 -417
- package/dist/contract/rln_contract.js.map +1 -1
- package/dist/contract/test-utils.js +1 -1
- package/dist/contract/test-utils.js.map +1 -1
- package/dist/contract/types.d.ts +40 -0
- package/dist/contract/types.js +8 -0
- package/dist/contract/types.js.map +1 -0
- package/dist/create.js +1 -1
- package/dist/create.js.map +1 -1
- package/dist/credentials_manager.d.ts +50 -0
- package/dist/{rln_light.js → credentials_manager.js} +118 -47
- package/dist/credentials_manager.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/rln.d.ts +9 -52
- package/dist/rln.js +54 -163
- package/dist/rln.js.map +1 -1
- package/dist/types.d.ts +27 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +1 -1
- package/src/contract/{rln_light_contract.ts → rln_base_contract.ts} +289 -300
- package/src/contract/rln_contract.ts +9 -663
- package/src/contract/test-utils.ts +1 -1
- package/src/contract/types.ts +48 -0
- package/src/create.ts +1 -1
- package/src/credentials_manager.ts +306 -0
- package/src/index.ts +4 -4
- package/src/rln.ts +67 -259
- package/src/types.ts +31 -0
- package/dist/contract/rln_light_contract.js.map +0 -1
- package/dist/rln_light.d.ts +0 -64
- package/dist/rln_light.js.map +0 -1
- package/src/rln_light.ts +0 -235
@@ -1,84 +1,47 @@
|
|
1
1
|
import { Logger } from "@waku/utils";
|
2
2
|
import { ethers } from "ethers";
|
3
3
|
|
4
|
-
import
|
5
|
-
import
|
4
|
+
import { IdentityCredential } from "../identity.js";
|
5
|
+
import { DecryptedCredentials } from "../keystore/types.js";
|
6
6
|
|
7
7
|
import { RLN_ABI } from "./abi.js";
|
8
8
|
import { DEFAULT_RATE_LIMIT, RATE_LIMIT_PARAMS } from "./constants.js";
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
interface RLNContractInitOptions extends RLNContractOptions {
|
24
|
-
contract?: ethers.Contract;
|
25
|
-
}
|
26
|
-
|
27
|
-
export interface MembershipRegisteredEvent {
|
28
|
-
idCommitment: string;
|
29
|
-
membershipRateLimit: ethers.BigNumber;
|
30
|
-
index: ethers.BigNumber;
|
31
|
-
}
|
32
|
-
|
33
|
-
type FetchMembersOptions = {
|
34
|
-
fromBlock?: number;
|
35
|
-
fetchRange?: number;
|
36
|
-
fetchChunks?: number;
|
37
|
-
};
|
38
|
-
|
39
|
-
export interface MembershipInfo {
|
40
|
-
index: ethers.BigNumber;
|
41
|
-
idCommitment: string;
|
42
|
-
rateLimit: number;
|
43
|
-
startBlock: number;
|
44
|
-
endBlock: number;
|
45
|
-
state: MembershipState;
|
46
|
-
}
|
47
|
-
|
48
|
-
export enum MembershipState {
|
49
|
-
Active = "Active",
|
50
|
-
GracePeriod = "GracePeriod",
|
51
|
-
Expired = "Expired",
|
52
|
-
ErasedAwaitsWithdrawal = "ErasedAwaitsWithdrawal"
|
53
|
-
}
|
54
|
-
|
55
|
-
export class RLNLightContract {
|
9
|
+
import {
|
10
|
+
CustomQueryOptions,
|
11
|
+
FetchMembersOptions,
|
12
|
+
Member,
|
13
|
+
MembershipInfo,
|
14
|
+
MembershipRegisteredEvent,
|
15
|
+
MembershipState,
|
16
|
+
RLNContractInitOptions
|
17
|
+
} from "./types.js";
|
18
|
+
|
19
|
+
const log = new Logger("waku:rln:contract:base");
|
20
|
+
|
21
|
+
export class RLNBaseContract {
|
56
22
|
public contract: ethers.Contract;
|
57
|
-
|
58
23
|
private deployBlock: undefined | number;
|
59
24
|
private rateLimit: number;
|
60
25
|
|
61
|
-
|
26
|
+
protected _members: Map<number, Member> = new Map();
|
62
27
|
private _membersFilter: ethers.EventFilter;
|
63
28
|
private _membershipErasedFilter: ethers.EventFilter;
|
64
29
|
private _membersExpiredFilter: ethers.EventFilter;
|
65
30
|
|
66
31
|
/**
|
67
|
-
*
|
32
|
+
* Constructor for RLNBaseContract.
|
68
33
|
* Allows injecting a mocked contract for testing purposes.
|
69
34
|
*/
|
70
|
-
public
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
}
|
35
|
+
public constructor(options: RLNContractInitOptions) {
|
36
|
+
// Initialize members and subscriptions
|
37
|
+
this.fetchMembers()
|
38
|
+
.then(() => {
|
39
|
+
this.subscribeToMembers();
|
40
|
+
})
|
41
|
+
.catch((error) => {
|
42
|
+
log.error("Failed to initialize members", { error });
|
43
|
+
});
|
80
44
|
|
81
|
-
private constructor(options: RLNContractInitOptions) {
|
82
45
|
const {
|
83
46
|
address,
|
84
47
|
signer,
|
@@ -86,19 +49,10 @@ export class RLNLightContract {
|
|
86
49
|
contract
|
87
50
|
} = options;
|
88
51
|
|
89
|
-
|
90
|
-
rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
|
91
|
-
rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
|
92
|
-
) {
|
93
|
-
throw new Error(
|
94
|
-
`Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE} messages per epoch`
|
95
|
-
);
|
96
|
-
}
|
97
|
-
|
98
|
-
this.rateLimit = rateLimit;
|
52
|
+
this.validateRateLimit(rateLimit);
|
99
53
|
|
100
|
-
// Use the injected contract if provided; otherwise, instantiate a new one.
|
101
54
|
this.contract = contract || new ethers.Contract(address, RLN_ABI, signer);
|
55
|
+
this.rateLimit = rateLimit;
|
102
56
|
|
103
57
|
// Initialize event filters
|
104
58
|
this._membersFilter = this.contract.filters.MembershipRegistered();
|
@@ -133,7 +87,7 @@ export class RLNLightContract {
|
|
133
87
|
*/
|
134
88
|
public async getMinRateLimit(): Promise<number> {
|
135
89
|
const minRate = await this.contract.minMembershipRateLimit();
|
136
|
-
return minRate.toNumber();
|
90
|
+
return ethers.BigNumber.from(minRate).toNumber();
|
137
91
|
}
|
138
92
|
|
139
93
|
/**
|
@@ -142,7 +96,7 @@ export class RLNLightContract {
|
|
142
96
|
*/
|
143
97
|
public async getMaxRateLimit(): Promise<number> {
|
144
98
|
const maxRate = await this.contract.maxMembershipRateLimit();
|
145
|
-
return maxRate.toNumber();
|
99
|
+
return ethers.BigNumber.from(maxRate).toNumber();
|
146
100
|
}
|
147
101
|
|
148
102
|
/**
|
@@ -180,6 +134,7 @@ export class RLNLightContract {
|
|
180
134
|
* @param newRateLimit The new rate limit to use
|
181
135
|
*/
|
182
136
|
public async setRateLimit(newRateLimit: number): Promise<void> {
|
137
|
+
this.validateRateLimit(newRateLimit);
|
183
138
|
this.rateLimit = newRateLimit;
|
184
139
|
}
|
185
140
|
|
@@ -190,43 +145,31 @@ export class RLNLightContract {
|
|
190
145
|
return sortedMembers;
|
191
146
|
}
|
192
147
|
|
193
|
-
private get membersFilter(): ethers.EventFilter {
|
194
|
-
if (!this._membersFilter) {
|
195
|
-
throw Error("Members filter was not initialized.");
|
196
|
-
}
|
197
|
-
return this._membersFilter;
|
198
|
-
}
|
199
|
-
|
200
|
-
private get membershipErasedFilter(): ethers.EventFilter {
|
201
|
-
if (!this._membershipErasedFilter) {
|
202
|
-
throw Error("MembershipErased filter was not initialized.");
|
203
|
-
}
|
204
|
-
return this._membershipErasedFilter;
|
205
|
-
}
|
206
|
-
|
207
|
-
private get membersExpiredFilter(): ethers.EventFilter {
|
208
|
-
if (!this._membersExpiredFilter) {
|
209
|
-
throw Error("MembersExpired filter was not initialized.");
|
210
|
-
}
|
211
|
-
return this._membersExpiredFilter;
|
212
|
-
}
|
213
|
-
|
214
148
|
public async fetchMembers(options: FetchMembersOptions = {}): Promise<void> {
|
215
|
-
const registeredMemberEvents = await queryFilter(
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
149
|
+
const registeredMemberEvents = await RLNBaseContract.queryFilter(
|
150
|
+
this.contract,
|
151
|
+
{
|
152
|
+
fromBlock: this.deployBlock,
|
153
|
+
...options,
|
154
|
+
membersFilter: this.membersFilter
|
155
|
+
}
|
156
|
+
);
|
157
|
+
const removedMemberEvents = await RLNBaseContract.queryFilter(
|
158
|
+
this.contract,
|
159
|
+
{
|
160
|
+
fromBlock: this.deployBlock,
|
161
|
+
...options,
|
162
|
+
membersFilter: this.membershipErasedFilter
|
163
|
+
}
|
164
|
+
);
|
165
|
+
const expiredMemberEvents = await RLNBaseContract.queryFilter(
|
166
|
+
this.contract,
|
167
|
+
{
|
168
|
+
fromBlock: this.deployBlock,
|
169
|
+
...options,
|
170
|
+
membersFilter: this.membersExpiredFilter
|
171
|
+
}
|
172
|
+
);
|
230
173
|
|
231
174
|
const events = [
|
232
175
|
...registeredMemberEvents,
|
@@ -236,6 +179,58 @@ export class RLNLightContract {
|
|
236
179
|
this.processEvents(events);
|
237
180
|
}
|
238
181
|
|
182
|
+
public static async queryFilter(
|
183
|
+
contract: ethers.Contract,
|
184
|
+
options: CustomQueryOptions
|
185
|
+
): Promise<ethers.Event[]> {
|
186
|
+
const FETCH_CHUNK = 5;
|
187
|
+
const BLOCK_RANGE = 3000;
|
188
|
+
|
189
|
+
const {
|
190
|
+
fromBlock,
|
191
|
+
membersFilter,
|
192
|
+
fetchRange = BLOCK_RANGE,
|
193
|
+
fetchChunks = FETCH_CHUNK
|
194
|
+
} = options;
|
195
|
+
|
196
|
+
if (fromBlock === undefined) {
|
197
|
+
return contract.queryFilter(membersFilter);
|
198
|
+
}
|
199
|
+
|
200
|
+
if (!contract.provider) {
|
201
|
+
throw Error("No provider found on the contract.");
|
202
|
+
}
|
203
|
+
|
204
|
+
const toBlock = await contract.provider.getBlockNumber();
|
205
|
+
|
206
|
+
if (toBlock - fromBlock < fetchRange) {
|
207
|
+
return contract.queryFilter(membersFilter, fromBlock, toBlock);
|
208
|
+
}
|
209
|
+
|
210
|
+
const events: ethers.Event[][] = [];
|
211
|
+
const chunks = RLNBaseContract.splitToChunks(
|
212
|
+
fromBlock,
|
213
|
+
toBlock,
|
214
|
+
fetchRange
|
215
|
+
);
|
216
|
+
|
217
|
+
for (const portion of RLNBaseContract.takeN<[number, number]>(
|
218
|
+
chunks,
|
219
|
+
fetchChunks
|
220
|
+
)) {
|
221
|
+
const promises = portion.map(([left, right]) =>
|
222
|
+
RLNBaseContract.ignoreErrors(
|
223
|
+
contract.queryFilter(membersFilter, left, right),
|
224
|
+
[]
|
225
|
+
)
|
226
|
+
);
|
227
|
+
const fetchedEvents = await Promise.all(promises);
|
228
|
+
events.push(fetchedEvents.flatMap((v) => v));
|
229
|
+
}
|
230
|
+
|
231
|
+
return events.flatMap((v) => v);
|
232
|
+
}
|
233
|
+
|
239
234
|
public processEvents(events: ethers.Event[]): void {
|
240
235
|
const toRemoveTable = new Map<number, number[]>();
|
241
236
|
const toInsertTable = new Map<number, ethers.Event[]>();
|
@@ -278,6 +273,53 @@ export class RLNLightContract {
|
|
278
273
|
});
|
279
274
|
}
|
280
275
|
|
276
|
+
public static splitToChunks(
|
277
|
+
from: number,
|
278
|
+
to: number,
|
279
|
+
step: number
|
280
|
+
): Array<[number, number]> {
|
281
|
+
const chunks: Array<[number, number]> = [];
|
282
|
+
|
283
|
+
let left = from;
|
284
|
+
while (left < to) {
|
285
|
+
const right = left + step < to ? left + step : to;
|
286
|
+
|
287
|
+
chunks.push([left, right] as [number, number]);
|
288
|
+
|
289
|
+
left = right;
|
290
|
+
}
|
291
|
+
|
292
|
+
return chunks;
|
293
|
+
}
|
294
|
+
|
295
|
+
public static *takeN<T>(array: T[], size: number): Iterable<T[]> {
|
296
|
+
let start = 0;
|
297
|
+
|
298
|
+
while (start < array.length) {
|
299
|
+
const portion = array.slice(start, start + size);
|
300
|
+
|
301
|
+
yield portion;
|
302
|
+
|
303
|
+
start += size;
|
304
|
+
}
|
305
|
+
}
|
306
|
+
|
307
|
+
public static async ignoreErrors<T>(
|
308
|
+
promise: Promise<T>,
|
309
|
+
defaultValue: T
|
310
|
+
): Promise<T> {
|
311
|
+
try {
|
312
|
+
return await promise;
|
313
|
+
} catch (err: unknown) {
|
314
|
+
if (err instanceof Error) {
|
315
|
+
log.info(`Ignoring an error during query: ${err.message}`);
|
316
|
+
} else {
|
317
|
+
log.info(`Ignoring an unknown error during query`);
|
318
|
+
}
|
319
|
+
return defaultValue;
|
320
|
+
}
|
321
|
+
}
|
322
|
+
|
281
323
|
public subscribeToMembers(): void {
|
282
324
|
this.contract.on(
|
283
325
|
this.membersFilter,
|
@@ -316,6 +358,116 @@ export class RLNLightContract {
|
|
316
358
|
);
|
317
359
|
}
|
318
360
|
|
361
|
+
/**
|
362
|
+
* Helper method to get remaining messages in current epoch
|
363
|
+
* @param membershipId The ID of the membership to check
|
364
|
+
* @returns number of remaining messages allowed in current epoch
|
365
|
+
*/
|
366
|
+
public async getRemainingMessages(membershipId: number): Promise<number> {
|
367
|
+
try {
|
368
|
+
const [startTime, , rateLimit] =
|
369
|
+
await this.contract.getMembershipInfo(membershipId);
|
370
|
+
|
371
|
+
// Calculate current epoch
|
372
|
+
const currentTime = Math.floor(Date.now() / 1000);
|
373
|
+
const epochsPassed = Math.floor(
|
374
|
+
(currentTime - startTime) / RATE_LIMIT_PARAMS.EPOCH_LENGTH
|
375
|
+
);
|
376
|
+
const currentEpochStart =
|
377
|
+
startTime + epochsPassed * RATE_LIMIT_PARAMS.EPOCH_LENGTH;
|
378
|
+
|
379
|
+
// Get message count in current epoch using contract's function
|
380
|
+
const messageCount = await this.contract.getMessageCount(
|
381
|
+
membershipId,
|
382
|
+
currentEpochStart
|
383
|
+
);
|
384
|
+
return Math.max(
|
385
|
+
0,
|
386
|
+
ethers.BigNumber.from(rateLimit)
|
387
|
+
.sub(ethers.BigNumber.from(messageCount))
|
388
|
+
.toNumber()
|
389
|
+
);
|
390
|
+
} catch (error) {
|
391
|
+
log.error(
|
392
|
+
`Error getting remaining messages: ${(error as Error).message}`
|
393
|
+
);
|
394
|
+
return 0; // Fail safe: assume no messages remaining on error
|
395
|
+
}
|
396
|
+
}
|
397
|
+
|
398
|
+
public async getMembershipInfo(
|
399
|
+
idCommitment: string
|
400
|
+
): Promise<MembershipInfo | undefined> {
|
401
|
+
try {
|
402
|
+
const [startBlock, endBlock, rateLimit] =
|
403
|
+
await this.contract.getMembershipInfo(idCommitment);
|
404
|
+
const currentBlock = await this.contract.provider.getBlockNumber();
|
405
|
+
|
406
|
+
let state: MembershipState;
|
407
|
+
if (currentBlock < startBlock) {
|
408
|
+
state = MembershipState.Active;
|
409
|
+
} else if (currentBlock < endBlock) {
|
410
|
+
state = MembershipState.GracePeriod;
|
411
|
+
} else {
|
412
|
+
state = MembershipState.Expired;
|
413
|
+
}
|
414
|
+
|
415
|
+
const index = await this.getMemberIndex(idCommitment);
|
416
|
+
if (!index) return undefined;
|
417
|
+
|
418
|
+
return {
|
419
|
+
index,
|
420
|
+
idCommitment,
|
421
|
+
rateLimit: rateLimit.toNumber(),
|
422
|
+
startBlock: startBlock.toNumber(),
|
423
|
+
endBlock: endBlock.toNumber(),
|
424
|
+
state
|
425
|
+
};
|
426
|
+
} catch (error) {
|
427
|
+
return undefined;
|
428
|
+
}
|
429
|
+
}
|
430
|
+
|
431
|
+
public async extendMembership(
|
432
|
+
idCommitment: string
|
433
|
+
): Promise<ethers.ContractTransaction> {
|
434
|
+
return this.contract.extendMemberships([idCommitment]);
|
435
|
+
}
|
436
|
+
|
437
|
+
public async eraseMembership(
|
438
|
+
idCommitment: string,
|
439
|
+
eraseFromMembershipSet: boolean = true
|
440
|
+
): Promise<ethers.ContractTransaction> {
|
441
|
+
return this.contract.eraseMemberships(
|
442
|
+
[idCommitment],
|
443
|
+
eraseFromMembershipSet
|
444
|
+
);
|
445
|
+
}
|
446
|
+
|
447
|
+
public async registerMembership(
|
448
|
+
idCommitment: string,
|
449
|
+
rateLimit: number = DEFAULT_RATE_LIMIT
|
450
|
+
): Promise<ethers.ContractTransaction> {
|
451
|
+
if (
|
452
|
+
rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
|
453
|
+
rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
|
454
|
+
) {
|
455
|
+
throw new Error(
|
456
|
+
`Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}`
|
457
|
+
);
|
458
|
+
}
|
459
|
+
return this.contract.register(idCommitment, rateLimit, []);
|
460
|
+
}
|
461
|
+
|
462
|
+
public async withdraw(token: string, holder: string): Promise<void> {
|
463
|
+
try {
|
464
|
+
const tx = await this.contract.withdraw(token, { from: holder });
|
465
|
+
await tx.wait();
|
466
|
+
} catch (error) {
|
467
|
+
log.error(`Error in withdraw: ${(error as Error).message}`);
|
468
|
+
}
|
469
|
+
}
|
470
|
+
|
319
471
|
public async registerWithIdentity(
|
320
472
|
identity: IdentityCredential
|
321
473
|
): Promise<DecryptedCredentials | undefined> {
|
@@ -428,38 +580,6 @@ export class RLNLightContract {
|
|
428
580
|
}
|
429
581
|
}
|
430
582
|
|
431
|
-
/**
|
432
|
-
* Helper method to get remaining messages in current epoch
|
433
|
-
* @param membershipId The ID of the membership to check
|
434
|
-
* @returns number of remaining messages allowed in current epoch
|
435
|
-
*/
|
436
|
-
public async getRemainingMessages(membershipId: number): Promise<number> {
|
437
|
-
try {
|
438
|
-
const [startTime, , rateLimit] =
|
439
|
-
await this.contract.getMembershipInfo(membershipId);
|
440
|
-
|
441
|
-
// Calculate current epoch
|
442
|
-
const currentTime = Math.floor(Date.now() / 1000);
|
443
|
-
const epochsPassed = Math.floor(
|
444
|
-
(currentTime - startTime) / RATE_LIMIT_PARAMS.EPOCH_LENGTH
|
445
|
-
);
|
446
|
-
const currentEpochStart =
|
447
|
-
startTime + epochsPassed * RATE_LIMIT_PARAMS.EPOCH_LENGTH;
|
448
|
-
|
449
|
-
// Get message count in current epoch using contract's function
|
450
|
-
const messageCount = await this.contract.getMessageCount(
|
451
|
-
membershipId,
|
452
|
-
currentEpochStart
|
453
|
-
);
|
454
|
-
return Math.max(0, rateLimit.sub(messageCount).toNumber());
|
455
|
-
} catch (error) {
|
456
|
-
log.error(
|
457
|
-
`Error getting remaining messages: ${(error as Error).message}`
|
458
|
-
);
|
459
|
-
return 0; // Fail safe: assume no messages remaining on error
|
460
|
-
}
|
461
|
-
}
|
462
|
-
|
463
583
|
public async registerWithPermitAndErase(
|
464
584
|
identity: IdentityCredential,
|
465
585
|
permit: {
|
@@ -532,77 +652,40 @@ export class RLNLightContract {
|
|
532
652
|
}
|
533
653
|
}
|
534
654
|
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
655
|
+
/**
|
656
|
+
* Validates that the rate limit is within the allowed range
|
657
|
+
* @throws Error if the rate limit is outside the allowed range
|
658
|
+
*/
|
659
|
+
private validateRateLimit(rateLimit: number): void {
|
660
|
+
if (
|
661
|
+
rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
|
662
|
+
rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
|
663
|
+
) {
|
664
|
+
throw new Error(
|
665
|
+
`Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE} messages per epoch`
|
666
|
+
);
|
541
667
|
}
|
542
668
|
}
|
543
669
|
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
try {
|
548
|
-
const [startBlock, endBlock, rateLimit] =
|
549
|
-
await this.contract.getMembershipInfo(idCommitment);
|
550
|
-
const currentBlock = await this.contract.provider.getBlockNumber();
|
551
|
-
|
552
|
-
let state: MembershipState;
|
553
|
-
if (currentBlock < startBlock) {
|
554
|
-
state = MembershipState.Active;
|
555
|
-
} else if (currentBlock < endBlock) {
|
556
|
-
state = MembershipState.GracePeriod;
|
557
|
-
} else {
|
558
|
-
state = MembershipState.Expired;
|
559
|
-
}
|
560
|
-
|
561
|
-
const index = await this.getMemberIndex(idCommitment);
|
562
|
-
if (!index) return undefined;
|
563
|
-
|
564
|
-
return {
|
565
|
-
index,
|
566
|
-
idCommitment,
|
567
|
-
rateLimit: rateLimit.toNumber(),
|
568
|
-
startBlock: startBlock.toNumber(),
|
569
|
-
endBlock: endBlock.toNumber(),
|
570
|
-
state
|
571
|
-
};
|
572
|
-
} catch (error) {
|
573
|
-
return undefined;
|
670
|
+
private get membersFilter(): ethers.EventFilter {
|
671
|
+
if (!this._membersFilter) {
|
672
|
+
throw Error("Members filter was not initialized.");
|
574
673
|
}
|
674
|
+
return this._membersFilter;
|
575
675
|
}
|
576
676
|
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
public async eraseMembership(
|
584
|
-
idCommitment: string,
|
585
|
-
eraseFromMembershipSet: boolean = true
|
586
|
-
): Promise<ethers.ContractTransaction> {
|
587
|
-
return this.contract.eraseMemberships(
|
588
|
-
[idCommitment],
|
589
|
-
eraseFromMembershipSet
|
590
|
-
);
|
677
|
+
private get membershipErasedFilter(): ethers.EventFilter {
|
678
|
+
if (!this._membershipErasedFilter) {
|
679
|
+
throw Error("MembershipErased filter was not initialized.");
|
680
|
+
}
|
681
|
+
return this._membershipErasedFilter;
|
591
682
|
}
|
592
683
|
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
): Promise<ethers.ContractTransaction> {
|
597
|
-
if (
|
598
|
-
rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
|
599
|
-
rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
|
600
|
-
) {
|
601
|
-
throw new Error(
|
602
|
-
`Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}`
|
603
|
-
);
|
684
|
+
private get membersExpiredFilter(): ethers.EventFilter {
|
685
|
+
if (!this._membersExpiredFilter) {
|
686
|
+
throw Error("MembersExpired filter was not initialized.");
|
604
687
|
}
|
605
|
-
return this.
|
688
|
+
return this._membersExpiredFilter;
|
606
689
|
}
|
607
690
|
|
608
691
|
private async getMemberIndex(
|
@@ -622,97 +705,3 @@ export class RLNLightContract {
|
|
622
705
|
}
|
623
706
|
}
|
624
707
|
}
|
625
|
-
|
626
|
-
interface CustomQueryOptions extends FetchMembersOptions {
|
627
|
-
membersFilter: ethers.EventFilter;
|
628
|
-
}
|
629
|
-
|
630
|
-
// These values should be tested on other networks
|
631
|
-
const FETCH_CHUNK = 5;
|
632
|
-
const BLOCK_RANGE = 3000;
|
633
|
-
|
634
|
-
async function queryFilter(
|
635
|
-
contract: ethers.Contract,
|
636
|
-
options: CustomQueryOptions
|
637
|
-
): Promise<ethers.Event[]> {
|
638
|
-
const {
|
639
|
-
fromBlock,
|
640
|
-
membersFilter,
|
641
|
-
fetchRange = BLOCK_RANGE,
|
642
|
-
fetchChunks = FETCH_CHUNK
|
643
|
-
} = options;
|
644
|
-
|
645
|
-
if (fromBlock === undefined) {
|
646
|
-
return contract.queryFilter(membersFilter);
|
647
|
-
}
|
648
|
-
|
649
|
-
if (!contract.provider) {
|
650
|
-
throw Error("No provider found on the contract.");
|
651
|
-
}
|
652
|
-
|
653
|
-
const toBlock = await contract.provider.getBlockNumber();
|
654
|
-
|
655
|
-
if (toBlock - fromBlock < fetchRange) {
|
656
|
-
return contract.queryFilter(membersFilter, fromBlock, toBlock);
|
657
|
-
}
|
658
|
-
|
659
|
-
const events: ethers.Event[][] = [];
|
660
|
-
const chunks = splitToChunks(fromBlock, toBlock, fetchRange);
|
661
|
-
|
662
|
-
for (const portion of takeN<[number, number]>(chunks, fetchChunks)) {
|
663
|
-
const promises = portion.map(([left, right]) =>
|
664
|
-
ignoreErrors(contract.queryFilter(membersFilter, left, right), [])
|
665
|
-
);
|
666
|
-
const fetchedEvents = await Promise.all(promises);
|
667
|
-
events.push(fetchedEvents.flatMap((v) => v));
|
668
|
-
}
|
669
|
-
|
670
|
-
return events.flatMap((v) => v);
|
671
|
-
}
|
672
|
-
|
673
|
-
function splitToChunks(
|
674
|
-
from: number,
|
675
|
-
to: number,
|
676
|
-
step: number
|
677
|
-
): Array<[number, number]> {
|
678
|
-
const chunks: Array<[number, number]> = [];
|
679
|
-
|
680
|
-
let left = from;
|
681
|
-
while (left < to) {
|
682
|
-
const right = left + step < to ? left + step : to;
|
683
|
-
|
684
|
-
chunks.push([left, right] as [number, number]);
|
685
|
-
|
686
|
-
left = right;
|
687
|
-
}
|
688
|
-
|
689
|
-
return chunks;
|
690
|
-
}
|
691
|
-
|
692
|
-
function* takeN<T>(array: T[], size: number): Iterable<T[]> {
|
693
|
-
let start = 0;
|
694
|
-
|
695
|
-
while (start < array.length) {
|
696
|
-
const portion = array.slice(start, start + size);
|
697
|
-
|
698
|
-
yield portion;
|
699
|
-
|
700
|
-
start += size;
|
701
|
-
}
|
702
|
-
}
|
703
|
-
|
704
|
-
async function ignoreErrors<T>(
|
705
|
-
promise: Promise<T>,
|
706
|
-
defaultValue: T
|
707
|
-
): Promise<T> {
|
708
|
-
try {
|
709
|
-
return await promise;
|
710
|
-
} catch (err: unknown) {
|
711
|
-
if (err instanceof Error) {
|
712
|
-
log.info(`Ignoring an error during query: ${err.message}`);
|
713
|
-
} else {
|
714
|
-
log.info(`Ignoring an unknown error during query`);
|
715
|
-
}
|
716
|
-
return defaultValue;
|
717
|
-
}
|
718
|
-
}
|