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