@atomiqlabs/chain-evm
Version:
EVM specific base implementation
184 lines (164 loc) • 7.66 kB
text/typescript
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;
}
}