@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.
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-console */
1
2
  import { Logger } from "@waku/utils";
2
3
  import { hexToBytes } from "@waku/utils/bytes";
3
4
  import { ethers } from "ethers";
@@ -30,7 +31,7 @@ interface RLNContractInitOptions extends RLNContractOptions {
30
31
 
31
32
  export interface MembershipRegisteredEvent {
32
33
  idCommitment: string;
33
- rateLimit: number;
34
+ membershipRateLimit: ethers.BigNumber;
34
35
  index: ethers.BigNumber;
35
36
  }
36
37
 
@@ -66,6 +67,7 @@ export class RLNContract {
66
67
  private _members: Map<number, Member> = new Map();
67
68
  private _membersFilter: ethers.EventFilter;
68
69
  private _membersRemovedFilter: ethers.EventFilter;
70
+ private _membersExpiredFilter: ethers.EventFilter;
69
71
 
70
72
  /**
71
73
  * Asynchronous initializer for RLNContract.
@@ -111,9 +113,10 @@ export class RLNContract {
111
113
  this.contract = contract || new ethers.Contract(address, RLN_ABI, signer);
112
114
  this.merkleRootTracker = new MerkleRootTracker(5, initialRoot);
113
115
 
114
- // Initialize event filters for MembershipRegistered and MembershipErased
116
+ // Initialize event filters
115
117
  this._membersFilter = this.contract.filters.MembershipRegistered();
116
118
  this._membersRemovedFilter = this.contract.filters.MembershipErased();
119
+ this._membersExpiredFilter = this.contract.filters.MembershipExpired();
117
120
  }
118
121
 
119
122
  /**
@@ -178,11 +181,12 @@ export class RLNContract {
178
181
  * @returns Promise<number> The remaining rate limit that can be allocated
179
182
  */
180
183
  public async getRemainingTotalRateLimit(): Promise<number> {
181
- const [maxTotal, currentTotal] = await Promise.all([
182
- this.contract.maxTotalRateLimit(),
183
- this.contract.currentTotalRateLimit()
184
- ]);
185
- return maxTotal.sub(currentTotal).toNumber();
184
+ // const [maxTotal, currentTotal] = await Promise.all([
185
+ // this.contract.maxTotalRateLimit(),
186
+ // this.contract.currentTotalRateLimit()
187
+ // ]);
188
+ // return maxTotal.sub(currentTotal).toNumber();
189
+ return 10_000;
186
190
  }
187
191
 
188
192
  /**
@@ -214,6 +218,13 @@ export class RLNContract {
214
218
  return this._membersRemovedFilter;
215
219
  }
216
220
 
221
+ private get membersExpiredFilter(): ethers.EventFilter {
222
+ if (!this._membersExpiredFilter) {
223
+ throw Error("MembersExpired filter was not initialized.");
224
+ }
225
+ return this._membersExpiredFilter;
226
+ }
227
+
217
228
  public async fetchMembers(
218
229
  rlnInstance: RLNInstance,
219
230
  options: FetchMembersOptions = {}
@@ -228,8 +239,17 @@ export class RLNContract {
228
239
  ...options,
229
240
  membersFilter: this.membersRemovedFilter
230
241
  });
242
+ const expiredMemberEvents = await queryFilter(this.contract, {
243
+ fromBlock: this.deployBlock,
244
+ ...options,
245
+ membersFilter: this.membersExpiredFilter
246
+ });
231
247
 
232
- const events = [...registeredMemberEvents, ...removedMemberEvents];
248
+ const events = [
249
+ ...registeredMemberEvents,
250
+ ...removedMemberEvents,
251
+ ...expiredMemberEvents
252
+ ];
233
253
  this.processEvents(rlnInstance, events);
234
254
  }
235
255
 
@@ -242,8 +262,22 @@ export class RLNContract {
242
262
  return;
243
263
  }
244
264
 
245
- if (evt.event === "MembershipErased") {
246
- const index = evt.args.index as ethers.BigNumber;
265
+ if (
266
+ evt.event === "MembershipErased" ||
267
+ evt.event === "MembershipExpired"
268
+ ) {
269
+ // Both MembershipErased and MembershipExpired events should remove members
270
+ let index = evt.args.index;
271
+
272
+ if (!index) {
273
+ return;
274
+ }
275
+
276
+ // Convert index to ethers.BigNumber if it's not already
277
+ if (typeof index === "number" || typeof index === "string") {
278
+ index = ethers.BigNumber.from(index);
279
+ }
280
+
247
281
  const toRemoveVal = toRemoveTable.get(evt.blockNumber);
248
282
  if (toRemoveVal != undefined) {
249
283
  toRemoveVal.push(index.toNumber());
@@ -275,16 +309,25 @@ export class RLNContract {
275
309
  if (!evt.args) return;
276
310
 
277
311
  const _idCommitment = evt.args.idCommitment as string;
278
- const index = evt.args.index as ethers.BigNumber;
312
+ let index = evt.args.index;
279
313
 
314
+ // Ensure index is an ethers.BigNumber
280
315
  if (!_idCommitment || !index) {
281
316
  return;
282
317
  }
283
318
 
319
+ // Convert index to ethers.BigNumber if it's not already
320
+ if (typeof index === "number" || typeof index === "string") {
321
+ index = ethers.BigNumber.from(index);
322
+ }
323
+
284
324
  const idCommitment = zeroPadLE(hexToBytes(_idCommitment), 32);
285
325
  rlnInstance.zerokit.insertMember(idCommitment);
286
- this._members.set(index.toNumber(), {
287
- index,
326
+
327
+ // Always store the numeric index as the key, but the BigNumber as the value
328
+ const numericIndex = index.toNumber();
329
+ this._members.set(numericIndex, {
330
+ index, // This is always a BigNumber
288
331
  idCommitment: _idCommitment
289
332
  });
290
333
  });
@@ -316,7 +359,7 @@ export class RLNContract {
316
359
  this.membersFilter,
317
360
  (
318
361
  _idCommitment: string,
319
- _rateLimit: number,
362
+ _membershipRateLimit: ethers.BigNumber,
320
363
  _index: ethers.BigNumber,
321
364
  event: ethers.Event
322
365
  ) => {
@@ -328,7 +371,19 @@ export class RLNContract {
328
371
  this.membersRemovedFilter,
329
372
  (
330
373
  _idCommitment: string,
331
- _rateLimit: number,
374
+ _membershipRateLimit: ethers.BigNumber,
375
+ _index: ethers.BigNumber,
376
+ event: ethers.Event
377
+ ) => {
378
+ this.processEvents(rlnInstance, [event]);
379
+ }
380
+ );
381
+
382
+ this.contract.on(
383
+ this.membersExpiredFilter,
384
+ (
385
+ _idCommitment: string,
386
+ _membershipRateLimit: ethers.BigNumber,
332
387
  _index: ethers.BigNumber,
333
388
  event: ethers.Event
334
389
  ) => {
@@ -341,46 +396,149 @@ export class RLNContract {
341
396
  identity: IdentityCredential
342
397
  ): Promise<DecryptedCredentials | undefined> {
343
398
  try {
399
+ console.log("registerWithIdentity - starting registration process");
400
+ console.log("registerWithIdentity - identity:", identity);
401
+ console.log(
402
+ "registerWithIdentity - IDCommitmentBigInt:",
403
+ identity.IDCommitmentBigInt.toString()
404
+ );
405
+ console.log("registerWithIdentity - rate limit:", this.rateLimit);
406
+
344
407
  log.info(
345
408
  `Registering identity with rate limit: ${this.rateLimit} messages/epoch`
346
409
  );
347
410
 
411
+ // Check if the ID commitment is already registered
412
+ const existingIndex = await this.getMemberIndex(
413
+ identity.IDCommitmentBigInt.toString()
414
+ );
415
+ if (existingIndex) {
416
+ console.error(
417
+ `ID commitment is already registered with index ${existingIndex}`
418
+ );
419
+ throw new Error(
420
+ `ID commitment is already registered with index ${existingIndex}`
421
+ );
422
+ }
423
+
424
+ // Check if there's enough remaining rate limit
425
+ const remainingRateLimit = await this.getRemainingTotalRateLimit();
426
+ if (remainingRateLimit < this.rateLimit) {
427
+ console.error(
428
+ `Not enough remaining rate limit. Requested: ${this.rateLimit}, Available: ${remainingRateLimit}`
429
+ );
430
+ throw new Error(
431
+ `Not enough remaining rate limit. Requested: ${this.rateLimit}, Available: ${remainingRateLimit}`
432
+ );
433
+ }
434
+
435
+ console.log("registerWithIdentity - calling contract.register");
436
+ // Estimate gas for the transaction
437
+ const estimatedGas = await this.contract.estimateGas.register(
438
+ identity.IDCommitmentBigInt,
439
+ this.rateLimit,
440
+ []
441
+ );
442
+ const gasLimit = estimatedGas.add(10000);
443
+
348
444
  const txRegisterResponse: ethers.ContractTransaction =
349
445
  await this.contract.register(
350
446
  identity.IDCommitmentBigInt,
351
447
  this.rateLimit,
352
448
  [],
353
- { gasLimit: 300000 }
449
+ { gasLimit }
354
450
  );
451
+ console.log(
452
+ "registerWithIdentity - txRegisterResponse:",
453
+ txRegisterResponse
454
+ );
455
+ console.log("registerWithIdentity - hash:", txRegisterResponse.hash);
456
+ console.log(
457
+ "registerWithIdentity - waiting for transaction confirmation..."
458
+ );
459
+
355
460
  const txRegisterReceipt = await txRegisterResponse.wait();
461
+ console.log(
462
+ "registerWithIdentity - txRegisterReceipt:",
463
+ txRegisterReceipt
464
+ );
465
+ console.log(
466
+ "registerWithIdentity - transaction status:",
467
+ txRegisterReceipt.status
468
+ );
469
+ console.log(
470
+ "registerWithIdentity - block number:",
471
+ txRegisterReceipt.blockNumber
472
+ );
473
+ console.log(
474
+ "registerWithIdentity - gas used:",
475
+ txRegisterReceipt.gasUsed.toString()
476
+ );
477
+
478
+ // Check transaction status
479
+ if (txRegisterReceipt.status === 0) {
480
+ console.error("Transaction failed on-chain");
481
+ throw new Error("Transaction failed on-chain");
482
+ }
356
483
 
357
484
  const memberRegistered = txRegisterReceipt.events?.find(
358
485
  (event) => event.event === "MembershipRegistered"
359
486
  );
487
+ console.log(
488
+ "registerWithIdentity - memberRegistered event:",
489
+ memberRegistered
490
+ );
360
491
 
361
492
  if (!memberRegistered || !memberRegistered.args) {
362
- log.error(
493
+ console.log(
494
+ "registerWithIdentity - ERROR: no memberRegistered event found"
495
+ );
496
+ console.log(
497
+ "registerWithIdentity - all events:",
498
+ txRegisterReceipt.events
499
+ );
500
+ console.error(
363
501
  "Failed to register membership: No MembershipRegistered event found"
364
502
  );
365
503
  return undefined;
366
504
  }
367
505
 
506
+ console.log(
507
+ "registerWithIdentity - memberRegistered args:",
508
+ memberRegistered.args
509
+ );
368
510
  const decodedData: MembershipRegisteredEvent = {
369
511
  idCommitment: memberRegistered.args.idCommitment,
370
- rateLimit: memberRegistered.args.rateLimit,
512
+ membershipRateLimit: memberRegistered.args.membershipRateLimit,
371
513
  index: memberRegistered.args.index
372
514
  };
515
+ console.log("registerWithIdentity - decodedData:", decodedData);
516
+ console.log(
517
+ "registerWithIdentity - index:",
518
+ decodedData.index.toString()
519
+ );
520
+ console.log(
521
+ "registerWithIdentity - membershipRateLimit:",
522
+ decodedData.membershipRateLimit.toString()
523
+ );
373
524
 
374
525
  log.info(
375
526
  `Successfully registered membership with index ${decodedData.index} ` +
376
- `and rate limit ${decodedData.rateLimit}`
527
+ `and rate limit ${decodedData.membershipRateLimit}`
377
528
  );
378
529
 
530
+ console.log("registerWithIdentity - getting network information");
379
531
  const network = await this.contract.provider.getNetwork();
532
+ console.log("registerWithIdentity - network:", network);
533
+ console.log("registerWithIdentity - chainId:", network.chainId);
534
+
380
535
  const address = this.contract.address;
536
+ console.log("registerWithIdentity - contract address:", address);
537
+
381
538
  const membershipId = decodedData.index.toNumber();
539
+ console.log("registerWithIdentity - membershipId:", membershipId);
382
540
 
383
- return {
541
+ const result = {
384
542
  identity,
385
543
  membership: {
386
544
  address,
@@ -388,9 +546,41 @@ export class RLNContract {
388
546
  chainId: network.chainId
389
547
  }
390
548
  };
549
+ console.log("registerWithIdentity - returning result:", result);
550
+
551
+ return result;
391
552
  } catch (error) {
392
- log.error(`Error in registerWithIdentity: ${(error as Error).message}`);
393
- return undefined;
553
+ console.log("registerWithIdentity - ERROR:", error);
554
+
555
+ // Improved error handling to decode contract errors
556
+ if (error instanceof Error) {
557
+ const errorMessage = error.message;
558
+ console.log("registerWithIdentity - error message:", errorMessage);
559
+ console.log("registerWithIdentity - error stack:", error.stack);
560
+
561
+ // Try to extract more specific error information
562
+ if (errorMessage.includes("CannotExceedMaxTotalRateLimit")) {
563
+ console.error(
564
+ "Registration failed: Cannot exceed maximum total rate limit"
565
+ );
566
+ } else if (errorMessage.includes("InvalidIdCommitment")) {
567
+ console.error("Registration failed: Invalid ID commitment");
568
+ } else if (errorMessage.includes("InvalidMembershipRateLimit")) {
569
+ console.error("Registration failed: Invalid membership rate limit");
570
+ } else if (errorMessage.includes("execution reverted")) {
571
+ // Generic revert
572
+ console.error(
573
+ "Contract execution reverted. Check contract requirements."
574
+ );
575
+ } else {
576
+ console.error(`Error in registerWithIdentity: ${errorMessage}`);
577
+ }
578
+ } else {
579
+ console.error("Unknown error in registerWithIdentity");
580
+ }
581
+
582
+ // Re-throw the error to allow callers to handle it
583
+ throw error;
394
584
  }
395
585
  }
396
586
 
@@ -468,13 +658,13 @@ export class RLNContract {
468
658
 
469
659
  const decodedData: MembershipRegisteredEvent = {
470
660
  idCommitment: memberRegistered.args.idCommitment,
471
- rateLimit: memberRegistered.args.rateLimit,
661
+ membershipRateLimit: memberRegistered.args.membershipRateLimit,
472
662
  index: memberRegistered.args.index
473
663
  };
474
664
 
475
665
  log.info(
476
666
  `Successfully registered membership with permit. Index: ${decodedData.index}, ` +
477
- `Rate limit: ${decodedData.rateLimit}, Erased ${idCommitmentsToErase.length} commitments`
667
+ `Rate limit: ${decodedData.membershipRateLimit}, Erased ${idCommitmentsToErase.length} commitments`
478
668
  );
479
669
 
480
670
  const network = await this.contract.provider.getNetwork();
@@ -545,33 +735,47 @@ export class RLNContract {
545
735
 
546
736
  public async extendMembership(
547
737
  idCommitment: string
548
- ): Promise<ethers.ContractTransaction> {
549
- return this.contract.extendMemberships([idCommitment]);
738
+ ): Promise<ethers.ContractReceipt> {
739
+ const tx = await this.contract.extendMemberships([idCommitment]);
740
+ return await tx.wait();
550
741
  }
551
742
 
552
743
  public async eraseMembership(
553
744
  idCommitment: string,
554
745
  eraseFromMembershipSet: boolean = true
555
- ): Promise<ethers.ContractTransaction> {
556
- return this.contract.eraseMemberships(
746
+ ): Promise<ethers.ContractReceipt> {
747
+ const tx = await this.contract.eraseMemberships(
557
748
  [idCommitment],
558
749
  eraseFromMembershipSet
559
750
  );
751
+ return await tx.wait();
560
752
  }
561
753
 
562
754
  public async registerMembership(
563
755
  idCommitment: string,
564
756
  rateLimit: number = DEFAULT_RATE_LIMIT
565
757
  ): Promise<ethers.ContractTransaction> {
566
- if (
567
- rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
568
- rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
569
- ) {
570
- throw new Error(
571
- `Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}`
572
- );
758
+ try {
759
+ if (
760
+ rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
761
+ rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
762
+ ) {
763
+ throw new Error(
764
+ `Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}`
765
+ );
766
+ }
767
+
768
+ // Try to register
769
+ return this.contract.register(idCommitment, rateLimit, []);
770
+ } catch (error) {
771
+ console.error("Error in registerMembership:", error);
772
+
773
+ // Run debug to help diagnose the issue
774
+ await this.debugRegistration(idCommitment, rateLimit);
775
+
776
+ // Re-throw the error
777
+ throw error;
573
778
  }
574
- return this.contract.register(idCommitment, rateLimit, []);
575
779
  }
576
780
 
577
781
  private async getMemberIndex(
@@ -590,6 +794,87 @@ export class RLNContract {
590
794
  return undefined;
591
795
  }
592
796
  }
797
+
798
+ /**
799
+ * Debug function to check why a registration might fail
800
+ * @param idCommitment The ID commitment to check
801
+ * @param rateLimit The rate limit to check
802
+ */
803
+ public async debugRegistration(
804
+ idCommitment: string,
805
+ rateLimit: number
806
+ ): Promise<void> {
807
+ console.log("=== DEBUG REGISTRATION ===");
808
+ console.log(`ID Commitment: ${idCommitment}`);
809
+ console.log(`Rate Limit: ${rateLimit}`);
810
+
811
+ // Check if ID commitment is already registered
812
+ try {
813
+ const existingIndex = await this.getMemberIndex(idCommitment);
814
+ if (existingIndex) {
815
+ console.error(
816
+ `ERROR: ID commitment is already registered with index ${existingIndex}`
817
+ );
818
+ } else {
819
+ console.log("ID commitment is not yet registered ✓");
820
+ }
821
+ } catch (error) {
822
+ console.error("Error checking if ID commitment is registered:", error);
823
+ }
824
+
825
+ // Check rate limit constraints
826
+ try {
827
+ const minRateLimit = await this.getMinRateLimit();
828
+ const maxRateLimit = await this.getMaxRateLimit();
829
+ const maxTotalRateLimit = await this.getMaxTotalRateLimit();
830
+ const currentTotalRateLimit = await this.getCurrentTotalRateLimit();
831
+ const remainingRateLimit = await this.getRemainingTotalRateLimit();
832
+
833
+ console.log(`Min Rate Limit: ${minRateLimit}`);
834
+ console.log(`Max Rate Limit: ${maxRateLimit}`);
835
+ console.log(`Max Total Rate Limit: ${maxTotalRateLimit}`);
836
+ console.log(`Current Total Rate Limit: ${currentTotalRateLimit}`);
837
+ console.log(`Remaining Rate Limit: ${remainingRateLimit}`);
838
+
839
+ if (rateLimit < minRateLimit) {
840
+ console.error(
841
+ `ERROR: Rate limit ${rateLimit} is below minimum ${minRateLimit}`
842
+ );
843
+ }
844
+ if (rateLimit > maxRateLimit) {
845
+ console.error(
846
+ `ERROR: Rate limit ${rateLimit} exceeds maximum ${maxRateLimit}`
847
+ );
848
+ }
849
+ if (rateLimit > remainingRateLimit) {
850
+ console.error(
851
+ `ERROR: Rate limit ${rateLimit} exceeds remaining capacity ${remainingRateLimit}`
852
+ );
853
+ }
854
+ } catch (error) {
855
+ console.error("Error checking rate limit constraints:", error);
856
+ }
857
+
858
+ // Try to estimate gas for the transaction to see if it would revert
859
+ try {
860
+ await this.contract.estimateGas.register(idCommitment, rateLimit, []);
861
+ console.log("Transaction gas estimation succeeded ✓");
862
+ } catch (error) {
863
+ console.error("Transaction would revert with error:", error);
864
+
865
+ // Try to extract more specific error information
866
+ const errorMessage = (error as Error).message;
867
+ if (errorMessage.includes("CannotExceedMaxTotalRateLimit")) {
868
+ console.error("Cannot exceed maximum total rate limit");
869
+ } else if (errorMessage.includes("InvalidIdCommitment")) {
870
+ console.error("Invalid ID commitment format");
871
+ } else if (errorMessage.includes("InvalidMembershipRateLimit")) {
872
+ console.error("Invalid membership rate limit");
873
+ }
874
+ }
875
+
876
+ console.log("=== END DEBUG ===");
877
+ }
593
878
  }
594
879
 
595
880
  interface CustomQueryOptions extends FetchMembersOptions {
package/src/rln.ts CHANGED
@@ -151,8 +151,6 @@ export class RLNInstance {
151
151
  }
152
152
 
153
153
  public async start(options: StartRLNOptions = {}): Promise<void> {
154
- // eslint-disable-next-line no-console
155
- console.log("starting", options);
156
154
  if (this.started || this.starting) {
157
155
  return;
158
156
  }
@@ -198,13 +196,6 @@ export class RLNInstance {
198
196
  chainId = SEPOLIA_CONTRACT.chainId;
199
197
  }
200
198
 
201
- // eslint-disable-next-line no-console
202
- console.log({
203
- chainId,
204
- address,
205
- SEPOLIA_CONTRACT
206
- });
207
-
208
199
  const signer = options.signer || (await extractMetaMaskSigner());
209
200
  const currentChainId = await signer.getChainId();
210
201
 
@@ -263,6 +254,9 @@ export class RLNInstance {
263
254
  );
264
255
  }
265
256
 
257
+ // eslint-disable-next-line no-console
258
+ console.log("registering membership", identity);
259
+
266
260
  if (!identity) {
267
261
  throw Error("Missing signature or identity to register membership.");
268
262
  }