@waku/rln 0.0.2-8a6571f.0 → 0.0.2-950aefb.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/packages/rln/dist/contract/rln_contract.js +220 -29
- package/bundle/packages/rln/dist/rln.js +2 -8
- package/dist/.tsbuildinfo +1 -1
- package/dist/contract/rln_contract.d.ts +11 -3
- package/dist/contract/rln_contract.js +220 -29
- package/dist/contract/rln_contract.js.map +1 -1
- package/dist/rln.js +2 -8
- package/dist/rln.js.map +1 -1
- package/package.json +1 -1
- package/src/contract/rln_contract.ts +321 -36
- package/src/rln.ts +3 -9
@@ -10,6 +10,7 @@ import { RATE_LIMIT_PARAMS, DEFAULT_RATE_LIMIT } from './constants.js';
|
|
10
10
|
import { Contract } from '../../../../node_modules/@ethersproject/contracts/lib.esm/index.js';
|
11
11
|
import { BigNumber } from '../../../../node_modules/@ethersproject/bignumber/lib.esm/bignumber.js';
|
12
12
|
|
13
|
+
/* eslint-disable no-console */
|
13
14
|
const log = new Logger("waku:rln:contract");
|
14
15
|
var MembershipState;
|
15
16
|
(function (MembershipState) {
|
@@ -26,6 +27,7 @@ class RLNContract {
|
|
26
27
|
_members = new Map();
|
27
28
|
_membersFilter;
|
28
29
|
_membersRemovedFilter;
|
30
|
+
_membersExpiredFilter;
|
29
31
|
/**
|
30
32
|
* Asynchronous initializer for RLNContract.
|
31
33
|
* Allows injecting a mocked contract for testing purposes.
|
@@ -47,9 +49,10 @@ class RLNContract {
|
|
47
49
|
// Use the injected contract if provided; otherwise, instantiate a new one.
|
48
50
|
this.contract = contract || new Contract(address, RLN_ABI, signer);
|
49
51
|
this.merkleRootTracker = new MerkleRootTracker(5, initialRoot);
|
50
|
-
// Initialize event filters
|
52
|
+
// Initialize event filters
|
51
53
|
this._membersFilter = this.contract.filters.MembershipRegistered();
|
52
54
|
this._membersRemovedFilter = this.contract.filters.MembershipErased();
|
55
|
+
this._membersExpiredFilter = this.contract.filters.MembershipExpired();
|
53
56
|
}
|
54
57
|
/**
|
55
58
|
* Gets the current rate limit for this contract instance
|
@@ -106,11 +109,12 @@ class RLNContract {
|
|
106
109
|
* @returns Promise<number> The remaining rate limit that can be allocated
|
107
110
|
*/
|
108
111
|
async getRemainingTotalRateLimit() {
|
109
|
-
const [maxTotal, currentTotal] = await Promise.all([
|
110
|
-
|
111
|
-
|
112
|
-
]);
|
113
|
-
return maxTotal.sub(currentTotal).toNumber();
|
112
|
+
// const [maxTotal, currentTotal] = await Promise.all([
|
113
|
+
// this.contract.maxTotalRateLimit(),
|
114
|
+
// this.contract.currentTotalRateLimit()
|
115
|
+
// ]);
|
116
|
+
// return maxTotal.sub(currentTotal).toNumber();
|
117
|
+
return 10_000;
|
114
118
|
}
|
115
119
|
/**
|
116
120
|
* Updates the rate limit for future registrations
|
@@ -135,6 +139,12 @@ class RLNContract {
|
|
135
139
|
}
|
136
140
|
return this._membersRemovedFilter;
|
137
141
|
}
|
142
|
+
get membersExpiredFilter() {
|
143
|
+
if (!this._membersExpiredFilter) {
|
144
|
+
throw Error("MembersExpired filter was not initialized.");
|
145
|
+
}
|
146
|
+
return this._membersExpiredFilter;
|
147
|
+
}
|
138
148
|
async fetchMembers(rlnInstance, options = {}) {
|
139
149
|
const registeredMemberEvents = await queryFilter(this.contract, {
|
140
150
|
fromBlock: this.deployBlock,
|
@@ -146,7 +156,16 @@ class RLNContract {
|
|
146
156
|
...options,
|
147
157
|
membersFilter: this.membersRemovedFilter
|
148
158
|
});
|
149
|
-
const
|
159
|
+
const expiredMemberEvents = await queryFilter(this.contract, {
|
160
|
+
fromBlock: this.deployBlock,
|
161
|
+
...options,
|
162
|
+
membersFilter: this.membersExpiredFilter
|
163
|
+
});
|
164
|
+
const events = [
|
165
|
+
...registeredMemberEvents,
|
166
|
+
...removedMemberEvents,
|
167
|
+
...expiredMemberEvents
|
168
|
+
];
|
150
169
|
this.processEvents(rlnInstance, events);
|
151
170
|
}
|
152
171
|
processEvents(rlnInstance, events) {
|
@@ -156,8 +175,17 @@ class RLNContract {
|
|
156
175
|
if (!evt.args) {
|
157
176
|
return;
|
158
177
|
}
|
159
|
-
if (evt.event === "MembershipErased"
|
160
|
-
|
178
|
+
if (evt.event === "MembershipErased" ||
|
179
|
+
evt.event === "MembershipExpired") {
|
180
|
+
// Both MembershipErased and MembershipExpired events should remove members
|
181
|
+
let index = evt.args.index;
|
182
|
+
if (!index) {
|
183
|
+
return;
|
184
|
+
}
|
185
|
+
// Convert index to ethers.BigNumber if it's not already
|
186
|
+
if (typeof index === "number" || typeof index === "string") {
|
187
|
+
index = BigNumber.from(index);
|
188
|
+
}
|
161
189
|
const toRemoveVal = toRemoveTable.get(evt.blockNumber);
|
162
190
|
if (toRemoveVal != undefined) {
|
163
191
|
toRemoveVal.push(index.toNumber());
|
@@ -185,14 +213,21 @@ class RLNContract {
|
|
185
213
|
if (!evt.args)
|
186
214
|
return;
|
187
215
|
const _idCommitment = evt.args.idCommitment;
|
188
|
-
|
216
|
+
let index = evt.args.index;
|
217
|
+
// Ensure index is an ethers.BigNumber
|
189
218
|
if (!_idCommitment || !index) {
|
190
219
|
return;
|
191
220
|
}
|
221
|
+
// Convert index to ethers.BigNumber if it's not already
|
222
|
+
if (typeof index === "number" || typeof index === "string") {
|
223
|
+
index = BigNumber.from(index);
|
224
|
+
}
|
192
225
|
const idCommitment = zeroPadLE(hexToBytes(_idCommitment), 32);
|
193
226
|
rlnInstance.zerokit.insertMember(idCommitment);
|
194
|
-
|
195
|
-
|
227
|
+
// Always store the numeric index as the key, but the BigNumber as the value
|
228
|
+
const numericIndex = index.toNumber();
|
229
|
+
this._members.set(numericIndex, {
|
230
|
+
index, // This is always a BigNumber
|
196
231
|
idCommitment: _idCommitment
|
197
232
|
});
|
198
233
|
});
|
@@ -213,34 +248,81 @@ class RLNContract {
|
|
213
248
|
});
|
214
249
|
}
|
215
250
|
subscribeToMembers(rlnInstance) {
|
216
|
-
this.contract.on(this.membersFilter, (_idCommitment,
|
251
|
+
this.contract.on(this.membersFilter, (_idCommitment, _membershipRateLimit, _index, event) => {
|
252
|
+
this.processEvents(rlnInstance, [event]);
|
253
|
+
});
|
254
|
+
this.contract.on(this.membersRemovedFilter, (_idCommitment, _membershipRateLimit, _index, event) => {
|
217
255
|
this.processEvents(rlnInstance, [event]);
|
218
256
|
});
|
219
|
-
this.contract.on(this.
|
257
|
+
this.contract.on(this.membersExpiredFilter, (_idCommitment, _membershipRateLimit, _index, event) => {
|
220
258
|
this.processEvents(rlnInstance, [event]);
|
221
259
|
});
|
222
260
|
}
|
223
261
|
async registerWithIdentity(identity) {
|
224
262
|
try {
|
263
|
+
console.log("registerWithIdentity - starting registration process");
|
264
|
+
console.log("registerWithIdentity - identity:", identity);
|
265
|
+
console.log("registerWithIdentity - IDCommitmentBigInt:", identity.IDCommitmentBigInt.toString());
|
266
|
+
console.log("registerWithIdentity - rate limit:", this.rateLimit);
|
225
267
|
log.info(`Registering identity with rate limit: ${this.rateLimit} messages/epoch`);
|
226
|
-
|
268
|
+
// Check if the ID commitment is already registered
|
269
|
+
const existingIndex = await this.getMemberIndex(identity.IDCommitmentBigInt.toString());
|
270
|
+
if (existingIndex) {
|
271
|
+
console.error(`ID commitment is already registered with index ${existingIndex}`);
|
272
|
+
throw new Error(`ID commitment is already registered with index ${existingIndex}`);
|
273
|
+
}
|
274
|
+
// Check if there's enough remaining rate limit
|
275
|
+
const remainingRateLimit = await this.getRemainingTotalRateLimit();
|
276
|
+
if (remainingRateLimit < this.rateLimit) {
|
277
|
+
console.error(`Not enough remaining rate limit. Requested: ${this.rateLimit}, Available: ${remainingRateLimit}`);
|
278
|
+
throw new Error(`Not enough remaining rate limit. Requested: ${this.rateLimit}, Available: ${remainingRateLimit}`);
|
279
|
+
}
|
280
|
+
console.log("registerWithIdentity - calling contract.register");
|
281
|
+
// Estimate gas for the transaction
|
282
|
+
const estimatedGas = await this.contract.estimateGas.register(identity.IDCommitmentBigInt, this.rateLimit, []);
|
283
|
+
const gasLimit = estimatedGas.add(10000);
|
284
|
+
const txRegisterResponse = await this.contract.register(identity.IDCommitmentBigInt, this.rateLimit, [], { gasLimit });
|
285
|
+
console.log("registerWithIdentity - txRegisterResponse:", txRegisterResponse);
|
286
|
+
console.log("registerWithIdentity - hash:", txRegisterResponse.hash);
|
287
|
+
console.log("registerWithIdentity - waiting for transaction confirmation...");
|
227
288
|
const txRegisterReceipt = await txRegisterResponse.wait();
|
289
|
+
console.log("registerWithIdentity - txRegisterReceipt:", txRegisterReceipt);
|
290
|
+
console.log("registerWithIdentity - transaction status:", txRegisterReceipt.status);
|
291
|
+
console.log("registerWithIdentity - block number:", txRegisterReceipt.blockNumber);
|
292
|
+
console.log("registerWithIdentity - gas used:", txRegisterReceipt.gasUsed.toString());
|
293
|
+
// Check transaction status
|
294
|
+
if (txRegisterReceipt.status === 0) {
|
295
|
+
console.error("Transaction failed on-chain");
|
296
|
+
throw new Error("Transaction failed on-chain");
|
297
|
+
}
|
228
298
|
const memberRegistered = txRegisterReceipt.events?.find((event) => event.event === "MembershipRegistered");
|
299
|
+
console.log("registerWithIdentity - memberRegistered event:", memberRegistered);
|
229
300
|
if (!memberRegistered || !memberRegistered.args) {
|
230
|
-
log
|
301
|
+
console.log("registerWithIdentity - ERROR: no memberRegistered event found");
|
302
|
+
console.log("registerWithIdentity - all events:", txRegisterReceipt.events);
|
303
|
+
console.error("Failed to register membership: No MembershipRegistered event found");
|
231
304
|
return undefined;
|
232
305
|
}
|
306
|
+
console.log("registerWithIdentity - memberRegistered args:", memberRegistered.args);
|
233
307
|
const decodedData = {
|
234
308
|
idCommitment: memberRegistered.args.idCommitment,
|
235
|
-
|
309
|
+
membershipRateLimit: memberRegistered.args.membershipRateLimit,
|
236
310
|
index: memberRegistered.args.index
|
237
311
|
};
|
312
|
+
console.log("registerWithIdentity - decodedData:", decodedData);
|
313
|
+
console.log("registerWithIdentity - index:", decodedData.index.toString());
|
314
|
+
console.log("registerWithIdentity - membershipRateLimit:", decodedData.membershipRateLimit.toString());
|
238
315
|
log.info(`Successfully registered membership with index ${decodedData.index} ` +
|
239
|
-
`and rate limit ${decodedData.
|
316
|
+
`and rate limit ${decodedData.membershipRateLimit}`);
|
317
|
+
console.log("registerWithIdentity - getting network information");
|
240
318
|
const network = await this.contract.provider.getNetwork();
|
319
|
+
console.log("registerWithIdentity - network:", network);
|
320
|
+
console.log("registerWithIdentity - chainId:", network.chainId);
|
241
321
|
const address = this.contract.address;
|
322
|
+
console.log("registerWithIdentity - contract address:", address);
|
242
323
|
const membershipId = decodedData.index.toNumber();
|
243
|
-
|
324
|
+
console.log("registerWithIdentity - membershipId:", membershipId);
|
325
|
+
const result = {
|
244
326
|
identity,
|
245
327
|
membership: {
|
246
328
|
address,
|
@@ -248,10 +330,39 @@ class RLNContract {
|
|
248
330
|
chainId: network.chainId
|
249
331
|
}
|
250
332
|
};
|
333
|
+
console.log("registerWithIdentity - returning result:", result);
|
334
|
+
return result;
|
251
335
|
}
|
252
336
|
catch (error) {
|
253
|
-
log
|
254
|
-
|
337
|
+
console.log("registerWithIdentity - ERROR:", error);
|
338
|
+
// Improved error handling to decode contract errors
|
339
|
+
if (error instanceof Error) {
|
340
|
+
const errorMessage = error.message;
|
341
|
+
console.log("registerWithIdentity - error message:", errorMessage);
|
342
|
+
console.log("registerWithIdentity - error stack:", error.stack);
|
343
|
+
// Try to extract more specific error information
|
344
|
+
if (errorMessage.includes("CannotExceedMaxTotalRateLimit")) {
|
345
|
+
console.error("Registration failed: Cannot exceed maximum total rate limit");
|
346
|
+
}
|
347
|
+
else if (errorMessage.includes("InvalidIdCommitment")) {
|
348
|
+
console.error("Registration failed: Invalid ID commitment");
|
349
|
+
}
|
350
|
+
else if (errorMessage.includes("InvalidMembershipRateLimit")) {
|
351
|
+
console.error("Registration failed: Invalid membership rate limit");
|
352
|
+
}
|
353
|
+
else if (errorMessage.includes("execution reverted")) {
|
354
|
+
// Generic revert
|
355
|
+
console.error("Contract execution reverted. Check contract requirements.");
|
356
|
+
}
|
357
|
+
else {
|
358
|
+
console.error(`Error in registerWithIdentity: ${errorMessage}`);
|
359
|
+
}
|
360
|
+
}
|
361
|
+
else {
|
362
|
+
console.error("Unknown error in registerWithIdentity");
|
363
|
+
}
|
364
|
+
// Re-throw the error to allow callers to handle it
|
365
|
+
throw error;
|
255
366
|
}
|
256
367
|
}
|
257
368
|
/**
|
@@ -287,11 +398,11 @@ class RLNContract {
|
|
287
398
|
}
|
288
399
|
const decodedData = {
|
289
400
|
idCommitment: memberRegistered.args.idCommitment,
|
290
|
-
|
401
|
+
membershipRateLimit: memberRegistered.args.membershipRateLimit,
|
291
402
|
index: memberRegistered.args.index
|
292
403
|
};
|
293
404
|
log.info(`Successfully registered membership with permit. Index: ${decodedData.index}, ` +
|
294
|
-
`Rate limit: ${decodedData.
|
405
|
+
`Rate limit: ${decodedData.membershipRateLimit}, Erased ${idCommitmentsToErase.length} commitments`);
|
295
406
|
const network = await this.contract.provider.getNetwork();
|
296
407
|
const address = this.contract.address;
|
297
408
|
const membershipId = decodedData.index.toNumber();
|
@@ -352,17 +463,29 @@ class RLNContract {
|
|
352
463
|
}
|
353
464
|
}
|
354
465
|
async extendMembership(idCommitment) {
|
355
|
-
|
466
|
+
const tx = await this.contract.extendMemberships([idCommitment]);
|
467
|
+
return await tx.wait();
|
356
468
|
}
|
357
469
|
async eraseMembership(idCommitment, eraseFromMembershipSet = true) {
|
358
|
-
|
470
|
+
const tx = await this.contract.eraseMemberships([idCommitment], eraseFromMembershipSet);
|
471
|
+
return await tx.wait();
|
359
472
|
}
|
360
473
|
async registerMembership(idCommitment, rateLimit = DEFAULT_RATE_LIMIT) {
|
361
|
-
|
362
|
-
rateLimit
|
363
|
-
|
474
|
+
try {
|
475
|
+
if (rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
|
476
|
+
rateLimit > RATE_LIMIT_PARAMS.MAX_RATE) {
|
477
|
+
throw new Error(`Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}`);
|
478
|
+
}
|
479
|
+
// Try to register
|
480
|
+
return this.contract.register(idCommitment, rateLimit, []);
|
481
|
+
}
|
482
|
+
catch (error) {
|
483
|
+
console.error("Error in registerMembership:", error);
|
484
|
+
// Run debug to help diagnose the issue
|
485
|
+
await this.debugRegistration(idCommitment, rateLimit);
|
486
|
+
// Re-throw the error
|
487
|
+
throw error;
|
364
488
|
}
|
365
|
-
return this.contract.register(idCommitment, rateLimit, []);
|
366
489
|
}
|
367
490
|
async getMemberIndex(idCommitment) {
|
368
491
|
try {
|
@@ -377,6 +500,74 @@ class RLNContract {
|
|
377
500
|
return undefined;
|
378
501
|
}
|
379
502
|
}
|
503
|
+
/**
|
504
|
+
* Debug function to check why a registration might fail
|
505
|
+
* @param idCommitment The ID commitment to check
|
506
|
+
* @param rateLimit The rate limit to check
|
507
|
+
*/
|
508
|
+
async debugRegistration(idCommitment, rateLimit) {
|
509
|
+
console.log("=== DEBUG REGISTRATION ===");
|
510
|
+
console.log(`ID Commitment: ${idCommitment}`);
|
511
|
+
console.log(`Rate Limit: ${rateLimit}`);
|
512
|
+
// Check if ID commitment is already registered
|
513
|
+
try {
|
514
|
+
const existingIndex = await this.getMemberIndex(idCommitment);
|
515
|
+
if (existingIndex) {
|
516
|
+
console.error(`ERROR: ID commitment is already registered with index ${existingIndex}`);
|
517
|
+
}
|
518
|
+
else {
|
519
|
+
console.log("ID commitment is not yet registered ✓");
|
520
|
+
}
|
521
|
+
}
|
522
|
+
catch (error) {
|
523
|
+
console.error("Error checking if ID commitment is registered:", error);
|
524
|
+
}
|
525
|
+
// Check rate limit constraints
|
526
|
+
try {
|
527
|
+
const minRateLimit = await this.getMinRateLimit();
|
528
|
+
const maxRateLimit = await this.getMaxRateLimit();
|
529
|
+
const maxTotalRateLimit = await this.getMaxTotalRateLimit();
|
530
|
+
const currentTotalRateLimit = await this.getCurrentTotalRateLimit();
|
531
|
+
const remainingRateLimit = await this.getRemainingTotalRateLimit();
|
532
|
+
console.log(`Min Rate Limit: ${minRateLimit}`);
|
533
|
+
console.log(`Max Rate Limit: ${maxRateLimit}`);
|
534
|
+
console.log(`Max Total Rate Limit: ${maxTotalRateLimit}`);
|
535
|
+
console.log(`Current Total Rate Limit: ${currentTotalRateLimit}`);
|
536
|
+
console.log(`Remaining Rate Limit: ${remainingRateLimit}`);
|
537
|
+
if (rateLimit < minRateLimit) {
|
538
|
+
console.error(`ERROR: Rate limit ${rateLimit} is below minimum ${minRateLimit}`);
|
539
|
+
}
|
540
|
+
if (rateLimit > maxRateLimit) {
|
541
|
+
console.error(`ERROR: Rate limit ${rateLimit} exceeds maximum ${maxRateLimit}`);
|
542
|
+
}
|
543
|
+
if (rateLimit > remainingRateLimit) {
|
544
|
+
console.error(`ERROR: Rate limit ${rateLimit} exceeds remaining capacity ${remainingRateLimit}`);
|
545
|
+
}
|
546
|
+
}
|
547
|
+
catch (error) {
|
548
|
+
console.error("Error checking rate limit constraints:", error);
|
549
|
+
}
|
550
|
+
// Try to estimate gas for the transaction to see if it would revert
|
551
|
+
try {
|
552
|
+
await this.contract.estimateGas.register(idCommitment, rateLimit, []);
|
553
|
+
console.log("Transaction gas estimation succeeded ✓");
|
554
|
+
}
|
555
|
+
catch (error) {
|
556
|
+
console.error("Transaction would revert with error:", error);
|
557
|
+
// Try to extract more specific error information
|
558
|
+
const errorMessage = error.message;
|
559
|
+
if (errorMessage.includes("CannotExceedMaxTotalRateLimit")) {
|
560
|
+
console.error("Cannot exceed maximum total rate limit");
|
561
|
+
}
|
562
|
+
else if (errorMessage.includes("InvalidIdCommitment")) {
|
563
|
+
console.error("Invalid ID commitment format");
|
564
|
+
}
|
565
|
+
else if (errorMessage.includes("InvalidMembershipRateLimit")) {
|
566
|
+
console.error("Invalid membership rate limit");
|
567
|
+
}
|
568
|
+
}
|
569
|
+
console.log("=== END DEBUG ===");
|
570
|
+
}
|
380
571
|
}
|
381
572
|
// These values should be tested on other networks
|
382
573
|
const FETCH_CHUNK = 5;
|
@@ -100,8 +100,6 @@ class RLNInstance {
|
|
100
100
|
return this._signer;
|
101
101
|
}
|
102
102
|
async start(options = {}) {
|
103
|
-
// eslint-disable-next-line no-console
|
104
|
-
console.log("starting", options);
|
105
103
|
if (this.started || this.starting) {
|
106
104
|
return;
|
107
105
|
}
|
@@ -133,12 +131,6 @@ class RLNInstance {
|
|
133
131
|
if (address === SEPOLIA_CONTRACT.address) {
|
134
132
|
chainId = SEPOLIA_CONTRACT.chainId;
|
135
133
|
}
|
136
|
-
// eslint-disable-next-line no-console
|
137
|
-
console.log({
|
138
|
-
chainId,
|
139
|
-
address,
|
140
|
-
SEPOLIA_CONTRACT
|
141
|
-
});
|
142
134
|
const signer = options.signer || (await extractMetaMaskSigner());
|
143
135
|
const currentChainId = await signer.getChainId();
|
144
136
|
if (chainId && chainId !== currentChainId) {
|
@@ -174,6 +166,8 @@ class RLNInstance {
|
|
174
166
|
if ("signature" in options) {
|
175
167
|
identity = this.zerokit.generateSeededIdentityCredential(options.signature);
|
176
168
|
}
|
169
|
+
// eslint-disable-next-line no-console
|
170
|
+
console.log("registering membership", identity);
|
177
171
|
if (!identity) {
|
178
172
|
throw Error("Missing signature or identity to register membership.");
|
179
173
|
}
|