UNPKG

@atomiqlabs/chain-evm

Version:

EVM specific base implementation

184 lines (164 loc) 7.66 kB
import {EVMModule} from "../EVMModule"; import {Log} from "ethers"; export class EVMEvents extends EVMModule<any> { /** * Wrapper for provider.getLogs(), automatically retries with smaller ranges if limits are reached * * @param contract * @param topics * @param startBlock * @param endBlock * @private */ private async getLogs( contract: string, topics: (string[] | string | null)[], startBlock: number, endBlock: number ): Promise<Log[]> { try { return await this.root.provider.getLogs({ address: contract, fromBlock: startBlock, toBlock: endBlock, topics }); } catch (e) { if( (e.error?.code===-32602 && e.error?.message?.startsWith("query exceeds max results")) || //Query exceeds max results e.error?.code===-32008 || //Response is too big e.error?.code===-32005 //Limit exceeded ) { if(startBlock===endBlock) throw e; const difference = (endBlock - startBlock)/2; const midpoint = startBlock + Math.floor(difference); this.logger.warn(`getLogs(): Error getting logs, limits reached, splitting to 2 ranges: ${startBlock}..${midpoint} & ${midpoint+1}..${endBlock}: `, e); return [ ...await this.getLogs(contract, topics, startBlock, midpoint), ...await this.getLogs(contract, topics, midpoint+1, endBlock), ] } throw e; } } /** * Returns the all the events occuring in a block range as identified by the contract and keys * * @param contract * @param topics * @param startBlock * @param endBlock * @param abortSignal */ public async getBlockEvents( contract: string, topics: (string[] | string | null)[], startBlock: number, endBlock: number = startBlock, abortSignal?: AbortSignal ): Promise<Log[]> { let events: Log[] = []; if(startBlock===endBlock) { events = await this.root.provider.getLogs({ address: contract, fromBlock: startBlock, toBlock: endBlock==null ? this.root.config.safeBlockTag : endBlock, topics }); } else if(endBlock==null) { const safeBlock = await this.root.provider.getBlock(this.root.config.safeBlockTag); if(safeBlock.number - startBlock > this.root.config.maxLogsBlockRange) { for(let i = startBlock + this.root.config.maxLogsBlockRange; i < safeBlock.number; i += this.root.config.maxLogsBlockRange) { events.push(...await this.getLogs(contract, topics, i - this.root.config.maxLogsBlockRange, i)); startBlock = i; } } events.push(...await this.getLogs(contract, topics, startBlock, safeBlock.number)); } else { //Both numeric if(endBlock - startBlock > this.root.config.maxLogsBlockRange) { for(let i = startBlock + this.root.config.maxLogsBlockRange; i < endBlock; i += this.root.config.maxLogsBlockRange) { events.push(...await this.getLogs(contract, topics, i - this.root.config.maxLogsBlockRange, i)); startBlock = i; } } events.push(...await this.getLogs(contract, topics, startBlock, endBlock)); } return events.filter(val => !val.removed); } /** * Runs a search backwards in time, processing events from a specific contract and keys * * @param contract * @param topics * @param processor called for every batch of returned signatures, should return a value if the correct signature * was found, or null if the search should continue * @param abortSignal * @param genesisHeight Height when the contract was deployed */ public async findInEvents<T>( contract: string, topics: (string[] | string | null)[], processor: (signatures: Log[]) => Promise<T>, abortSignal?: AbortSignal, genesisHeight?: number ): Promise<T> { const {number: latestBlockNumber} = await this.provider.getBlock(this.root.config.safeBlockTag); let promises: Promise<Log[]>[] = []; for(let blockNumber = latestBlockNumber; blockNumber >= (genesisHeight ?? 0); blockNumber-=this.root.config.maxLogsBlockRange) { promises.push(this.getLogs( contract, topics, Math.max(blockNumber-this.root.config.maxLogsBlockRange, 0), blockNumber )); if(promises.length>=this.root.config.maxParallelLogRequests) { const eventsResult = (await Promise.all(promises)).map( arr => arr.reverse() //Oldest events first ).flat(); promises = []; if(abortSignal!=null) abortSignal.throwIfAborted(); const result: T = await processor(eventsResult); if(result!=null) return result; } } const eventsResult = (await Promise.all(promises)).map( arr => arr.reverse() //Oldest events first ).flat(); if(abortSignal!=null) abortSignal.throwIfAborted(); const result: T = await processor(eventsResult); //Oldest events first if(result!=null) return result; return null; } /** * Runs a search forwards in time, processing events from a specific contract and keys * * @param contract * @param topics * @param processor called for every batch of returned signatures, should return a value if the correct signature * was found, or null if the search should continue * @param abortSignal * @param startHeight Blockheight at which to start */ public async findInEventsForward<T>( contract: string, topics: (string[] | string | null)[], processor: (signatures: Log[]) => Promise<T>, abortSignal?: AbortSignal, startHeight?: number ): Promise<T> { const {number: latestBlockNumber} = await this.provider.getBlock(this.root.config.safeBlockTag); let promises: Promise<Log[]>[] = []; for(let blockNumber = startHeight ?? 0; blockNumber < latestBlockNumber; blockNumber += this.root.config.maxLogsBlockRange) { promises.push(this.getLogs( contract, topics, blockNumber, Math.min(blockNumber + this.root.config.maxLogsBlockRange, latestBlockNumber) )); if(promises.length>=this.root.config.maxParallelLogRequests) { const eventsResult = (await Promise.all(promises)).flat(); promises = []; if(abortSignal!=null) abortSignal.throwIfAborted(); const result: T = await processor(eventsResult); //Oldest events first if(result!=null) return result; } } const eventsResult = (await Promise.all(promises)).flat(); if(abortSignal!=null) abortSignal.throwIfAborted(); const result: T = await processor(eventsResult); //Oldest events first if(result!=null) return result; return null; } }