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
This commit is contained in:
Sasha 2023-04-28 01:20:29 +02:00 committed by GitHub
parent 88a28a11e3
commit 7e0966aef7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -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<void> {
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<ethers.Event[]> {
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<T>(array: T[], size: number): Iterable<T[]> {
let start = 0;
let skip = size;
while (skip < array.length) {
const portion = array.slice(start, skip);
yield portion;
start = skip;
skip += size;
}
}
function ignoreErrors<T>(promise: Promise<T>, defaultValue: T): Promise<T> {
return promise.catch((err) => {
console.error(`Ignoring an error during query: ${err?.message}`);
return defaultValue;
});
}