From 7e0966aef7437c2a10e47d65c1d37271ee4611af Mon Sep 17 00:00:00 2001 From: Sasha <118575614+weboko@users.noreply.github.com> Date: Fri, 28 Apr 2023 01:20:29 +0200 Subject: [PATCH] feat: add custom queryFilter (#57) * add custom fetcher * improve fetch in chunks * remove comment * fetch in portions of 5 * allow to configure fetch params * add error log --- src/rln_contract.ts | 103 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 98 insertions(+), 5 deletions(-) diff --git a/src/rln_contract.ts b/src/rln_contract.ts index b1b8f62..9e24035 100644 --- a/src/rln_contract.ts +++ b/src/rln_contract.ts @@ -13,6 +13,12 @@ type ContractOptions = { provider: ethers.Signer | ethers.providers.Provider; }; +type FetchMembersOptions = { + fromBlock?: number; + fetchRange?: number; + fetchChunks?: number; +}; + export class RLNContract { private _contract: ethers.Contract; private membersFilter: ethers.EventFilter; @@ -46,12 +52,12 @@ export class RLNContract { public async fetchMembers( rlnInstance: RLNInstance, - fromBlock?: number + options: FetchMembersOptions = {} ): Promise { - const registeredMemberEvents = await this.contract.queryFilter( - this.membersFilter, - fromBlock - ); + const registeredMemberEvents = await queryFilter(this.contract, { + ...options, + membersFilter: this.membersFilter, + }); for (const event of registeredMemberEvents) { this.addMemberFromEvent(rlnInstance, event); @@ -109,3 +115,90 @@ export class RLNContract { return txRegisterReceipt?.events?.[0]; } } + +type CustomQueryOptions = FetchMembersOptions & { + membersFilter: ethers.EventFilter; +}; + +// these value should be tested on other networks +const FETCH_CHUNK = 5; +const BLOCK_RANGE = 3000; + +async function queryFilter( + contract: ethers.Contract, + options: CustomQueryOptions +): Promise { + const { + fromBlock, + membersFilter, + fetchRange = BLOCK_RANGE, + fetchChunks = FETCH_CHUNK, + } = options; + + if (!fromBlock) { + return contract.queryFilter(membersFilter); + } + + if (!contract.signer.provider) { + throw Error("No provider found on the contract's signer."); + } + + const toBlock = await contract.signer.provider.getBlockNumber(); + + if (toBlock - fromBlock < fetchRange) { + return contract.queryFilter(membersFilter); + } + + const events: ethers.Event[][] = []; + const chunks = splitToChunks(fromBlock, toBlock, fetchRange); + + for (const portion of takeN<[number, number]>(chunks, fetchChunks)) { + const promises = portion.map(([left, right]) => + ignoreErrors(contract.queryFilter(membersFilter, left, right), []) + ); + const fetchedEvents = await Promise.all(promises); + events.push(fetchedEvents.flatMap((v) => v)); + } + + return events.flatMap((v) => v); +} + +function splitToChunks( + from: number, + to: number, + step: number +): Array<[number, number]> { + const chunks = []; + + let left = from; + while (left < to) { + const right = left + step < to ? left + step : to; + + chunks.push([left, right] as [number, number]); + + left = right; + } + + return chunks; +} + +function* takeN(array: T[], size: number): Iterable { + let start = 0; + let skip = size; + + while (skip < array.length) { + const portion = array.slice(start, skip); + + yield portion; + + start = skip; + skip += size; + } +} + +function ignoreErrors(promise: Promise, defaultValue: T): Promise { + return promise.catch((err) => { + console.error(`Ignoring an error during query: ${err?.message}`); + return defaultValue; + }); +}