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

View File

@ -30,7 +30,7 @@ interface RLNContractInitOptions extends RLNContractOptions {
export interface MembershipRegisteredEvent {
idCommitment: string;
rateLimit: number;
membershipRateLimit: ethers.BigNumber;
index: ethers.BigNumber;
}
@ -65,7 +65,8 @@ export class RLNContract {
private _members: Map<number, Member> = new Map();
private _membersFilter: ethers.EventFilter;
private _membersRemovedFilter: ethers.EventFilter;
private _membershipErasedFilter: ethers.EventFilter;
private _membersExpiredFilter: ethers.EventFilter;
/**
* Asynchronous initializer for RLNContract.
@ -111,9 +112,10 @@ export class RLNContract {
this.contract = contract || new ethers.Contract(address, RLN_ABI, signer);
this.merkleRootTracker = new MerkleRootTracker(5, initialRoot);
// Initialize event filters for MembershipRegistered and MembershipRemoved
// Initialize event filters
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
*/
public async getRemainingTotalRateLimit(): Promise<number> {
const [maxTotal, currentTotal] = await Promise.all([
this.contract.maxTotalRateLimit(),
this.contract.currentTotalRateLimit()
]);
return maxTotal.sub(currentTotal).toNumber();
// const [maxTotal, currentTotal] = await Promise.all([
// this.contract.maxTotalRateLimit(),
// this.contract.currentTotalRateLimit()
// ]);
// return maxTotal.sub(currentTotal).toNumber();
return 10_000;
}
/**
@ -207,11 +210,18 @@ export class RLNContract {
return this._membersFilter;
}
private get membersRemovedFilter(): ethers.EventFilter {
if (!this._membersRemovedFilter) {
throw Error("MembersRemoved filter was not initialized.");
private get membershipErasedFilter(): ethers.EventFilter {
if (!this._membershipErasedFilter) {
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(
@ -226,10 +236,19 @@ export class RLNContract {
const removedMemberEvents = await queryFilter(this.contract, {
fromBlock: this.deployBlock,
...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);
}
@ -242,8 +261,22 @@ export class RLNContract {
return;
}
if (evt.event === "MembershipRemoved") {
const index = evt.args.index as ethers.BigNumber;
if (
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);
if (toRemoveVal != undefined) {
toRemoveVal.push(index.toNumber());
@ -275,16 +308,25 @@ export class RLNContract {
if (!evt.args) return;
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) {
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);
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
});
});
@ -316,7 +358,7 @@ export class RLNContract {
this.membersFilter,
(
_idCommitment: string,
_rateLimit: number,
_membershipRateLimit: ethers.BigNumber,
_index: ethers.BigNumber,
event: ethers.Event
) => {
@ -325,9 +367,22 @@ export class RLNContract {
);
this.contract.on(
this.membersRemovedFilter,
this.membershipErasedFilter,
(
_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,
event: ethers.Event
) => {
@ -344,15 +399,45 @@ export class RLNContract {
`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 =
await this.contract.register(
identity.IDCommitmentBigInt,
this.rateLimit,
[],
{ gasLimit: 300000 }
{ gasLimit }
);
const txRegisterReceipt = await txRegisterResponse.wait();
if (txRegisterReceipt.status === 0) {
throw new Error("Transaction failed on-chain");
}
const memberRegistered = txRegisterReceipt.events?.find(
(event) => event.event === "MembershipRegistered"
);
@ -366,18 +451,18 @@ export class RLNContract {
const decodedData: MembershipRegisteredEvent = {
idCommitment: memberRegistered.args.idCommitment,
rateLimit: memberRegistered.args.rateLimit,
membershipRateLimit: memberRegistered.args.membershipRateLimit,
index: memberRegistered.args.index
};
log.info(
`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 address = this.contract.address;
const membershipId = decodedData.index.toNumber();
const membershipId = Number(decodedData.index);
return {
identity,
@ -388,8 +473,32 @@ export class RLNContract {
}
};
} catch (error) {
log.error(`Error in registerWithIdentity: ${(error as Error).message}`);
return undefined;
if (error instanceof Error) {
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 = {
idCommitment: memberRegistered.args.idCommitment,
rateLimit: memberRegistered.args.rateLimit,
membershipRateLimit: memberRegistered.args.membershipRateLimit,
index: memberRegistered.args.index
};
log.info(
`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 address = this.contract.address;
const membershipId = decodedData.index.toNumber();
const membershipId = Number(decodedData.index);
return {
identity,