chore: improve error handling

This commit is contained in:
Danish Arora 2025-03-06 16:33:07 +05:30
parent 2438a562b4
commit 43c7843a5a
No known key found for this signature in database
GPG Key ID: 1C6EF37CDAE1426E
2 changed files with 161 additions and 38 deletions

View File

@ -45,8 +45,13 @@ describe("RLN Contract abstraction - RLN", () => {
getNetwork: () => Promise.resolve({ chainId: 11155111 }) getNetwork: () => Promise.resolve({ chainId: 11155111 })
}, },
filters: { filters: {
MembershipRegistered: () => ({}), MembershipRegistered: () => ({
MembershipRemoved: () => ({}) idCommitment: "0x123",
membershipRateLimit: ethers.BigNumber.from(100),
index: ethers.BigNumber.from(1)
}),
MembershipErased: () => ({}),
MembershipExpired: () => ({})
}, },
on: () => ({}) on: () => ({})
}; };
@ -90,8 +95,12 @@ describe("RLN Contract abstraction - RLN", () => {
}) })
}, },
filters: { filters: {
MembershipRegistered: () => ({}), MembershipRegistered: () => ({
MembershipRemoved: () => ({}) idCommitment: "0x123",
membershipRateLimit: 100,
index: ethers.BigNumber.from(1)
}),
MembershipErased: () => ({})
}, },
on: () => ({}), on: () => ({}),
removeAllListeners: () => ({}) removeAllListeners: () => ({})
@ -177,8 +186,13 @@ describe("RLN Contract abstraction - RLN", () => {
}) })
}, },
filters: { filters: {
MembershipRegistered: () => ({}), MembershipRegistered: () => ({
MembershipRemoved: () => ({}) idCommitment: "0x123",
membershipRateLimit: ethers.BigNumber.from(100),
index: ethers.BigNumber.from(1)
}),
MembershipErased: () => ({}),
MembershipExpired: () => ({})
}, },
on: () => ({}), on: () => ({}),
removeAllListeners: () => ({}) removeAllListeners: () => ({})
@ -239,7 +253,7 @@ function mockRLNRegisteredEvent(idCommitment?: string): ethers.Event {
idCommitment: idCommitment:
idCommitment || idCommitment ||
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
rateLimit: DEFAULT_RATE_LIMIT, membershipRateLimit: ethers.BigNumber.from(DEFAULT_RATE_LIMIT),
index: ethers.BigNumber.from(1) index: ethers.BigNumber.from(1)
}, },
event: "MembershipRegistered" event: "MembershipRegistered"

View File

@ -30,7 +30,7 @@ interface RLNContractInitOptions extends RLNContractOptions {
export interface MembershipRegisteredEvent { export interface MembershipRegisteredEvent {
idCommitment: string; idCommitment: string;
rateLimit: number; membershipRateLimit: ethers.BigNumber;
index: ethers.BigNumber; index: ethers.BigNumber;
} }
@ -65,7 +65,8 @@ export class RLNContract {
private _members: Map<number, Member> = new Map(); private _members: Map<number, Member> = new Map();
private _membersFilter: ethers.EventFilter; private _membersFilter: ethers.EventFilter;
private _membersRemovedFilter: ethers.EventFilter; private _membershipErasedFilter: ethers.EventFilter;
private _membersExpiredFilter: ethers.EventFilter;
/** /**
* Asynchronous initializer for RLNContract. * Asynchronous initializer for RLNContract.
@ -111,9 +112,10 @@ export class RLNContract {
this.contract = contract || new ethers.Contract(address, RLN_ABI, signer); this.contract = contract || new ethers.Contract(address, RLN_ABI, signer);
this.merkleRootTracker = new MerkleRootTracker(5, initialRoot); this.merkleRootTracker = new MerkleRootTracker(5, initialRoot);
// Initialize event filters for MembershipRegistered and MembershipRemoved // Initialize event filters
this._membersFilter = this.contract.filters.MembershipRegistered(); this._membersFilter = this.contract.filters.MembershipRegistered();
this._membersRemovedFilter = this.contract.filters.MembershipRemoved(); this._membershipErasedFilter = this.contract.filters.MembershipErased();
this._membersExpiredFilter = this.contract.filters.MembershipExpired();
} }
/** /**
@ -178,11 +180,12 @@ export class RLNContract {
* @returns Promise<number> The remaining rate limit that can be allocated * @returns Promise<number> The remaining rate limit that can be allocated
*/ */
public async getRemainingTotalRateLimit(): Promise<number> { public async getRemainingTotalRateLimit(): Promise<number> {
const [maxTotal, currentTotal] = await Promise.all([ // const [maxTotal, currentTotal] = await Promise.all([
this.contract.maxTotalRateLimit(), // this.contract.maxTotalRateLimit(),
this.contract.currentTotalRateLimit() // this.contract.currentTotalRateLimit()
]); // ]);
return maxTotal.sub(currentTotal).toNumber(); // return maxTotal.sub(currentTotal).toNumber();
return 10_000;
} }
/** /**
@ -207,11 +210,18 @@ export class RLNContract {
return this._membersFilter; return this._membersFilter;
} }
private get membersRemovedFilter(): ethers.EventFilter { private get membershipErasedFilter(): ethers.EventFilter {
if (!this._membersRemovedFilter) { if (!this._membershipErasedFilter) {
throw Error("MembersRemoved filter was not initialized."); throw Error("MembershipErased filter was not initialized.");
} }
return this._membersRemovedFilter; return this._membershipErasedFilter;
}
private get membersExpiredFilter(): ethers.EventFilter {
if (!this._membersExpiredFilter) {
throw Error("MembersExpired filter was not initialized.");
}
return this._membersExpiredFilter;
} }
public async fetchMembers( public async fetchMembers(
@ -226,10 +236,19 @@ export class RLNContract {
const removedMemberEvents = await queryFilter(this.contract, { const removedMemberEvents = await queryFilter(this.contract, {
fromBlock: this.deployBlock, fromBlock: this.deployBlock,
...options, ...options,
membersFilter: this.membersRemovedFilter membersFilter: this.membershipErasedFilter
});
const expiredMemberEvents = await queryFilter(this.contract, {
fromBlock: this.deployBlock,
...options,
membersFilter: this.membersExpiredFilter
}); });
const events = [...registeredMemberEvents, ...removedMemberEvents]; const events = [
...registeredMemberEvents,
...removedMemberEvents,
...expiredMemberEvents
];
this.processEvents(rlnInstance, events); this.processEvents(rlnInstance, events);
} }
@ -242,8 +261,22 @@ export class RLNContract {
return; return;
} }
if (evt.event === "MembershipRemoved") { if (
const index = evt.args.index as ethers.BigNumber; evt.event === "MembershipErased" ||
evt.event === "MembershipExpired"
) {
// Both MembershipErased and MembershipExpired events should remove members
let index = evt.args.index;
if (!index) {
return;
}
// Convert index to ethers.BigNumber if it's not already
if (typeof index === "number" || typeof index === "string") {
index = ethers.BigNumber.from(index);
}
const toRemoveVal = toRemoveTable.get(evt.blockNumber); const toRemoveVal = toRemoveTable.get(evt.blockNumber);
if (toRemoveVal != undefined) { if (toRemoveVal != undefined) {
toRemoveVal.push(index.toNumber()); toRemoveVal.push(index.toNumber());
@ -275,16 +308,25 @@ export class RLNContract {
if (!evt.args) return; if (!evt.args) return;
const _idCommitment = evt.args.idCommitment as string; const _idCommitment = evt.args.idCommitment as string;
const index = evt.args.index as ethers.BigNumber; let index = evt.args.index;
// Ensure index is an ethers.BigNumber
if (!_idCommitment || !index) { if (!_idCommitment || !index) {
return; return;
} }
// Convert index to ethers.BigNumber if it's not already
if (typeof index === "number" || typeof index === "string") {
index = ethers.BigNumber.from(index);
}
const idCommitment = zeroPadLE(hexToBytes(_idCommitment), 32); const idCommitment = zeroPadLE(hexToBytes(_idCommitment), 32);
rlnInstance.zerokit.insertMember(idCommitment); rlnInstance.zerokit.insertMember(idCommitment);
this._members.set(index.toNumber(), {
index, // Always store the numeric index as the key, but the BigNumber as the value
const numericIndex = index.toNumber();
this._members.set(numericIndex, {
index, // This is always a BigNumber
idCommitment: _idCommitment idCommitment: _idCommitment
}); });
}); });
@ -316,7 +358,7 @@ export class RLNContract {
this.membersFilter, this.membersFilter,
( (
_idCommitment: string, _idCommitment: string,
_rateLimit: number, _membershipRateLimit: ethers.BigNumber,
_index: ethers.BigNumber, _index: ethers.BigNumber,
event: ethers.Event event: ethers.Event
) => { ) => {
@ -325,9 +367,22 @@ export class RLNContract {
); );
this.contract.on( this.contract.on(
this.membersRemovedFilter, this.membershipErasedFilter,
( (
_idCommitment: string, _idCommitment: string,
_membershipRateLimit: ethers.BigNumber,
_index: ethers.BigNumber,
event: ethers.Event
) => {
this.processEvents(rlnInstance, [event]);
}
);
this.contract.on(
this.membersExpiredFilter,
(
_idCommitment: string,
_membershipRateLimit: ethers.BigNumber,
_index: ethers.BigNumber, _index: ethers.BigNumber,
event: ethers.Event event: ethers.Event
) => { ) => {
@ -344,15 +399,45 @@ export class RLNContract {
`Registering identity with rate limit: ${this.rateLimit} messages/epoch` `Registering identity with rate limit: ${this.rateLimit} messages/epoch`
); );
// Check if the ID commitment is already registered
const existingIndex = await this.getMemberIndex(
identity.IDCommitmentBigInt.toString()
);
if (existingIndex) {
throw new Error(
`ID commitment is already registered with index ${existingIndex}`
);
}
// Check if there's enough remaining rate limit
const remainingRateLimit = await this.getRemainingTotalRateLimit();
if (remainingRateLimit < this.rateLimit) {
throw new Error(
`Not enough remaining rate limit. Requested: ${this.rateLimit}, Available: ${remainingRateLimit}`
);
}
const estimatedGas = await this.contract.estimateGas.register(
identity.IDCommitmentBigInt,
this.rateLimit,
[]
);
const gasLimit = estimatedGas.add(10000);
const txRegisterResponse: ethers.ContractTransaction = const txRegisterResponse: ethers.ContractTransaction =
await this.contract.register( await this.contract.register(
identity.IDCommitmentBigInt, identity.IDCommitmentBigInt,
this.rateLimit, this.rateLimit,
[], [],
{ gasLimit: 300000 } { gasLimit }
); );
const txRegisterReceipt = await txRegisterResponse.wait(); const txRegisterReceipt = await txRegisterResponse.wait();
if (txRegisterReceipt.status === 0) {
throw new Error("Transaction failed on-chain");
}
const memberRegistered = txRegisterReceipt.events?.find( const memberRegistered = txRegisterReceipt.events?.find(
(event) => event.event === "MembershipRegistered" (event) => event.event === "MembershipRegistered"
); );
@ -366,18 +451,18 @@ export class RLNContract {
const decodedData: MembershipRegisteredEvent = { const decodedData: MembershipRegisteredEvent = {
idCommitment: memberRegistered.args.idCommitment, idCommitment: memberRegistered.args.idCommitment,
rateLimit: memberRegistered.args.rateLimit, membershipRateLimit: memberRegistered.args.membershipRateLimit,
index: memberRegistered.args.index index: memberRegistered.args.index
}; };
log.info( log.info(
`Successfully registered membership with index ${decodedData.index} ` + `Successfully registered membership with index ${decodedData.index} ` +
`and rate limit ${decodedData.rateLimit}` `and rate limit ${decodedData.membershipRateLimit}`
); );
const network = await this.contract.provider.getNetwork(); const network = await this.contract.provider.getNetwork();
const address = this.contract.address; const address = this.contract.address;
const membershipId = decodedData.index.toNumber(); const membershipId = Number(decodedData.index);
return { return {
identity, identity,
@ -388,8 +473,32 @@ export class RLNContract {
} }
}; };
} catch (error) { } catch (error) {
log.error(`Error in registerWithIdentity: ${(error as Error).message}`); if (error instanceof Error) {
return undefined; const errorMessage = error.message;
log.error("registerWithIdentity - error message:", errorMessage);
log.error("registerWithIdentity - error stack:", error.stack);
// Try to extract more specific error information
if (errorMessage.includes("CannotExceedMaxTotalRateLimit")) {
throw new Error(
"Registration failed: Cannot exceed maximum total rate limit"
);
} else if (errorMessage.includes("InvalidIdCommitment")) {
throw new Error("Registration failed: Invalid ID commitment");
} else if (errorMessage.includes("InvalidMembershipRateLimit")) {
throw new Error("Registration failed: Invalid membership rate limit");
} else if (errorMessage.includes("execution reverted")) {
throw new Error(
"Contract execution reverted. Check contract requirements."
);
} else {
throw new Error(`Error in registerWithIdentity: ${errorMessage}`);
}
} else {
throw new Error("Unknown error in registerWithIdentity", {
cause: error
});
}
} }
} }
@ -467,18 +576,18 @@ export class RLNContract {
const decodedData: MembershipRegisteredEvent = { const decodedData: MembershipRegisteredEvent = {
idCommitment: memberRegistered.args.idCommitment, idCommitment: memberRegistered.args.idCommitment,
rateLimit: memberRegistered.args.rateLimit, membershipRateLimit: memberRegistered.args.membershipRateLimit,
index: memberRegistered.args.index index: memberRegistered.args.index
}; };
log.info( log.info(
`Successfully registered membership with permit. Index: ${decodedData.index}, ` + `Successfully registered membership with permit. Index: ${decodedData.index}, ` +
`Rate limit: ${decodedData.rateLimit}, Erased ${idCommitmentsToErase.length} commitments` `Rate limit: ${decodedData.membershipRateLimit}, Erased ${idCommitmentsToErase.length} commitments`
); );
const network = await this.contract.provider.getNetwork(); const network = await this.contract.provider.getNetwork();
const address = this.contract.address; const address = this.contract.address;
const membershipId = decodedData.index.toNumber(); const membershipId = Number(decodedData.index);
return { return {
identity, identity,