Add multiQueryFilter function

This commit is contained in:
Tornado Contrib 2024-11-18 02:52:03 +00:00
parent 092989ebaa
commit b65998fca5
Signed by: tornadocontrib
GPG Key ID: 60B4DF1A076C64B1
8 changed files with 866 additions and 317 deletions

16
dist/batch.d.ts vendored

@ -1,4 +1,14 @@
import type { Provider, BlockTag, Block, TransactionResponse, BaseContract, ContractEventName, EventLog, TransactionReceipt } from 'ethers';
import { Provider, BlockTag, Block, TransactionResponse, BaseContract, ContractEventName, EventLog, TransactionReceipt, EventFragment, TopicFilter, Interface, Log } from 'ethers';
/**
* Copied from ethers.js as they don't export this function
* https://github.com/ethers-io/ethers.js/blob/main/src.ts/contract/contract.ts#L464
*/
export declare function getSubInfo(abiInterface: Interface, event: ContractEventName): Promise<{
fragment: null | EventFragment;
tag: string;
topics: TopicFilter;
}>;
export declare function multiQueryFilter(address: string | string[], contract: BaseContract, event: ContractEventName, fromBlock?: BlockTag, toBlock?: BlockTag): Promise<Log[]>;
export interface BatchBlockServiceConstructor {
provider: Provider;
onProgress?: BatchBlockOnProgress;
@ -50,6 +60,7 @@ export declare class BatchTransactionService {
export interface BatchEventServiceConstructor {
provider: Provider;
contract: BaseContract;
address?: string | string[];
onProgress?: BatchEventOnProgress;
concurrencySize?: number;
blocksPerRequest?: number;
@ -75,13 +86,14 @@ export interface EventInput {
export declare class BatchEventsService {
provider: Provider;
contract: BaseContract;
address?: string | string[];
onProgress?: BatchEventOnProgress;
concurrencySize: number;
blocksPerRequest: number;
shouldRetry: boolean;
retryMax: number;
retryOn: number;
constructor({ provider, contract, onProgress, concurrencySize, blocksPerRequest, shouldRetry, retryMax, retryOn, }: BatchEventServiceConstructor);
constructor({ provider, contract, address, onProgress, concurrencySize, blocksPerRequest, shouldRetry, retryMax, retryOn, }: BatchEventServiceConstructor);
getPastEvents({ fromBlock, toBlock, type }: EventInput): Promise<EventLog[]>;
createBatchRequest(batchArray: EventInput[]): Promise<EventLog[]>[];
getBatchEvents({ fromBlock, toBlock, type }: EventInput): Promise<EventLog[]>;

@ -50,8 +50,8 @@ export declare class BaseEventsService<EventType extends MinimalEvents> {
getLatestEvents({ fromBlock }: {
fromBlock: number;
}): Promise<BaseEvents<EventType>>;
validateEvents<S>({ events, lastBlock, hasNewEvents, }: BaseEvents<EventType> & {
hasNewEvents?: boolean;
validateEvents<S>({ events, newEvents, lastBlock, }: BaseEvents<EventType> & {
newEvents: EventType[];
}): Promise<S>;
/**
* Handle saving events
@ -83,8 +83,8 @@ export declare class BaseTornadoService extends BaseEventsService<DepositsEvents
constructor(serviceConstructor: BaseTornadoServiceConstructor);
getInstanceName(): string;
formatEvents(events: EventLog[]): Promise<(DepositsEvents | WithdrawalsEvents)[]>;
validateEvents<S>({ events, hasNewEvents, }: BaseEvents<DepositsEvents | WithdrawalsEvents> & {
hasNewEvents?: boolean;
validateEvents<S>({ events, newEvents, }: BaseEvents<DepositsEvents | WithdrawalsEvents> & {
newEvents: (DepositsEvents | WithdrawalsEvents)[];
}): Promise<S>;
getLatestEvents({ fromBlock, }: {
fromBlock: number;

133
dist/index.js vendored

@ -154,6 +154,107 @@ function fromContentHash(contentHash) {
return contentHashUtils__namespace.decode(contentHash);
}
function isDeferred(value) {
return value && typeof value === "object" && "getTopicFilter" in value && typeof value.getTopicFilter === "function" && value.fragment;
}
async function getSubInfo(abiInterface, event) {
let topics;
let fragment = null;
if (Array.isArray(event)) {
const topicHashify = function(name) {
if (ethers.isHexString(name, 32)) {
return name;
}
const fragment2 = abiInterface.getEvent(name);
ethers.assertArgument(fragment2, "unknown fragment", "name", name);
return fragment2.topicHash;
};
topics = event.map((e) => {
if (e == null) {
return null;
}
if (Array.isArray(e)) {
return e.map(topicHashify);
}
return topicHashify(e);
});
} else if (event === "*") {
topics = [null];
} else if (typeof event === "string") {
if (ethers.isHexString(event, 32)) {
topics = [event];
} else {
fragment = abiInterface.getEvent(event);
ethers.assertArgument(fragment, "unknown fragment", "event", event);
topics = [fragment.topicHash];
}
} else if (isDeferred(event)) {
topics = await event.getTopicFilter();
} else if ("fragment" in event) {
fragment = event.fragment;
topics = [fragment.topicHash];
} else {
ethers.assertArgument(false, "unknown event name", "event", event);
}
topics = topics.map((t) => {
if (t == null) {
return null;
}
if (Array.isArray(t)) {
const items = Array.from(new Set(t.map((t2) => t2.toLowerCase())).values());
if (items.length === 1) {
return items[0];
}
items.sort();
return items;
}
return t.toLowerCase();
});
const tag = topics.map((t) => {
if (t == null) {
return "null";
}
if (Array.isArray(t)) {
return t.join("|");
}
return t;
}).join("&");
return { fragment, tag, topics };
}
async function multiQueryFilter(address, contract, event, fromBlock, toBlock) {
if (fromBlock == null) {
fromBlock = 0;
}
if (toBlock == null) {
toBlock = "latest";
}
const { fragment, topics } = await getSubInfo(contract.interface, event);
const filter = {
address: address === "*" ? void 0 : address,
topics,
fromBlock,
toBlock
};
const provider = contract.runner;
ethers.assert(provider, "contract runner does not have a provider", "UNSUPPORTED_OPERATION", { operation: "queryFilter" });
return (await provider.getLogs(filter)).map((log) => {
let foundFragment = fragment;
if (foundFragment == null) {
try {
foundFragment = contract.interface.getEvent(log.topics[0]);
} catch {
}
}
if (foundFragment) {
try {
return new ethers.EventLog(log, contract.interface, foundFragment);
} catch (error) {
return new ethers.UndecodedEventLog(log, error);
}
}
return new ethers.Log(log, provider);
});
}
class BatchBlockService {
provider;
onProgress;
@ -326,6 +427,7 @@ class BatchTransactionService {
class BatchEventsService {
provider;
contract;
address;
onProgress;
concurrencySize;
blocksPerRequest;
@ -335,6 +437,7 @@ class BatchEventsService {
constructor({
provider,
contract,
address,
onProgress,
concurrencySize = 10,
blocksPerRequest = 5e3,
@ -344,6 +447,7 @@ class BatchEventsService {
}) {
this.provider = provider;
this.contract = contract;
this.address = address;
this.onProgress = onProgress;
this.concurrencySize = concurrencySize;
this.blocksPerRequest = blocksPerRequest;
@ -356,6 +460,15 @@ class BatchEventsService {
let retries = 0;
while (!this.shouldRetry && retries === 0 || this.shouldRetry && retries < this.retryMax) {
try {
if (this.address) {
return await multiQueryFilter(
this.address,
this.contract,
type,
fromBlock,
toBlock
);
}
return await this.contract.queryFilter(type, fromBlock, toBlock);
} catch (e) {
err = e;
@ -2086,7 +2199,7 @@ class BaseEventsService {
}
}
async getLatestEvents({ fromBlock }) {
if (this.tovarishClient?.selectedRelayer && !["Deposit", "Withdrawal"].includes(this.type)) {
if (this.tovarishClient?.selectedRelayer) {
const { events, lastSyncBlock: lastBlock } = await this.tovarishClient.getEvents({
type: this.getTovarishType(),
fromBlock
@ -2103,8 +2216,8 @@ class BaseEventsService {
/* eslint-disable @typescript-eslint/no-unused-vars */
async validateEvents({
events,
lastBlock,
hasNewEvents
newEvents,
lastBlock
}) {
return void 0;
}
@ -2140,8 +2253,8 @@ class BaseEventsService {
const lastBlock = newEvents.lastBlock || allEvents[allEvents.length - 1]?.blockNumber;
const validateResult = await this.validateEvents({
events: allEvents,
lastBlock,
hasNewEvents: Boolean(newEvents.events.length)
newEvents: newEvents.events,
lastBlock
});
if (savedEvents.fromCache || newEvents.events.length) {
await this.saveEvents({ events: allEvents, lastBlock });
@ -2220,7 +2333,7 @@ class BaseTornadoService extends BaseEventsService {
}
async validateEvents({
events,
hasNewEvents
newEvents
}) {
if (events.length && this.getType() === "Deposit") {
const depositEvents = events;
@ -2229,7 +2342,7 @@ class BaseTornadoService extends BaseEventsService {
const errMsg = `Deposit events invalid wants ${depositEvents.length - 1} leafIndex have ${lastEvent.leafIndex}`;
throw new Error(errMsg);
}
if (this.merkleTreeService && (!this.optionalTree || hasNewEvents)) {
if (this.merkleTreeService && (!this.optionalTree || newEvents.length)) {
return await this.merkleTreeService.verifyTree(depositEvents);
}
}
@ -2250,7 +2363,9 @@ class BaseTornadoService extends BaseEventsService {
lastBlock
};
}
return super.getLatestEvents({ fromBlock });
return await this.getEventsFromRpc({
fromBlock
});
}
}
class BaseEchoService extends BaseEventsService {
@ -10396,6 +10511,7 @@ exports.getProvider = getProvider;
exports.getProviderWithNetId = getProviderWithNetId;
exports.getRelayerEnsSubdomains = getRelayerEnsSubdomains;
exports.getStatusSchema = getStatusSchema;
exports.getSubInfo = getSubInfo;
exports.getSupportedInstances = getSupportedInstances;
exports.getTokenBalances = getTokenBalances;
exports.getTovarishNetworks = getTovarishNetworks;
@ -10415,6 +10531,7 @@ exports.loadDBEvents = loadDBEvents;
exports.loadRemoteEvents = loadRemoteEvents;
exports.makeLabelNodeAndParent = makeLabelNodeAndParent;
exports.mimc = mimc;
exports.multiQueryFilter = multiQueryFilter;
exports.multicall = multicall;
exports.numberFormatter = numberFormatter;
exports.packEncryptedMessage = packEncryptedMessage;

135
dist/index.mjs vendored

@ -1,4 +1,4 @@
import { FetchRequest, JsonRpcProvider, Network, EnsPlugin, GasCostPlugin, Wallet, HDNodeWallet, VoidSigner, JsonRpcSigner, BrowserProvider, isAddress, parseEther, getAddress, AbiCoder, formatEther, namehash, dataSlice, dataLength, Interface, Contract, computeAddress, keccak256, EnsResolver, parseUnits, Transaction, Signature, MaxUint256, solidityPackedKeccak256, TypedDataEncoder, ZeroAddress } from 'ethers';
import { isHexString, assertArgument, assert, EventLog, UndecodedEventLog, Log, FetchRequest, JsonRpcProvider, Network, EnsPlugin, GasCostPlugin, Wallet, HDNodeWallet, VoidSigner, JsonRpcSigner, BrowserProvider, isAddress, parseEther, getAddress, AbiCoder, formatEther, namehash, dataSlice, dataLength, Interface, Contract, computeAddress, keccak256, EnsResolver, parseUnits, Transaction, Signature, MaxUint256, solidityPackedKeccak256, TypedDataEncoder, ZeroAddress } from 'ethers';
import { Tornado__factory } from '@tornado/contracts';
import { webcrypto } from 'crypto';
import BN from 'bn.js';
@ -132,6 +132,107 @@ function fromContentHash(contentHash) {
return contentHashUtils.decode(contentHash);
}
function isDeferred(value) {
return value && typeof value === "object" && "getTopicFilter" in value && typeof value.getTopicFilter === "function" && value.fragment;
}
async function getSubInfo(abiInterface, event) {
let topics;
let fragment = null;
if (Array.isArray(event)) {
const topicHashify = function(name) {
if (isHexString(name, 32)) {
return name;
}
const fragment2 = abiInterface.getEvent(name);
assertArgument(fragment2, "unknown fragment", "name", name);
return fragment2.topicHash;
};
topics = event.map((e) => {
if (e == null) {
return null;
}
if (Array.isArray(e)) {
return e.map(topicHashify);
}
return topicHashify(e);
});
} else if (event === "*") {
topics = [null];
} else if (typeof event === "string") {
if (isHexString(event, 32)) {
topics = [event];
} else {
fragment = abiInterface.getEvent(event);
assertArgument(fragment, "unknown fragment", "event", event);
topics = [fragment.topicHash];
}
} else if (isDeferred(event)) {
topics = await event.getTopicFilter();
} else if ("fragment" in event) {
fragment = event.fragment;
topics = [fragment.topicHash];
} else {
assertArgument(false, "unknown event name", "event", event);
}
topics = topics.map((t) => {
if (t == null) {
return null;
}
if (Array.isArray(t)) {
const items = Array.from(new Set(t.map((t2) => t2.toLowerCase())).values());
if (items.length === 1) {
return items[0];
}
items.sort();
return items;
}
return t.toLowerCase();
});
const tag = topics.map((t) => {
if (t == null) {
return "null";
}
if (Array.isArray(t)) {
return t.join("|");
}
return t;
}).join("&");
return { fragment, tag, topics };
}
async function multiQueryFilter(address, contract, event, fromBlock, toBlock) {
if (fromBlock == null) {
fromBlock = 0;
}
if (toBlock == null) {
toBlock = "latest";
}
const { fragment, topics } = await getSubInfo(contract.interface, event);
const filter = {
address: address === "*" ? void 0 : address,
topics,
fromBlock,
toBlock
};
const provider = contract.runner;
assert(provider, "contract runner does not have a provider", "UNSUPPORTED_OPERATION", { operation: "queryFilter" });
return (await provider.getLogs(filter)).map((log) => {
let foundFragment = fragment;
if (foundFragment == null) {
try {
foundFragment = contract.interface.getEvent(log.topics[0]);
} catch {
}
}
if (foundFragment) {
try {
return new EventLog(log, contract.interface, foundFragment);
} catch (error) {
return new UndecodedEventLog(log, error);
}
}
return new Log(log, provider);
});
}
class BatchBlockService {
provider;
onProgress;
@ -304,6 +405,7 @@ class BatchTransactionService {
class BatchEventsService {
provider;
contract;
address;
onProgress;
concurrencySize;
blocksPerRequest;
@ -313,6 +415,7 @@ class BatchEventsService {
constructor({
provider,
contract,
address,
onProgress,
concurrencySize = 10,
blocksPerRequest = 5e3,
@ -322,6 +425,7 @@ class BatchEventsService {
}) {
this.provider = provider;
this.contract = contract;
this.address = address;
this.onProgress = onProgress;
this.concurrencySize = concurrencySize;
this.blocksPerRequest = blocksPerRequest;
@ -334,6 +438,15 @@ class BatchEventsService {
let retries = 0;
while (!this.shouldRetry && retries === 0 || this.shouldRetry && retries < this.retryMax) {
try {
if (this.address) {
return await multiQueryFilter(
this.address,
this.contract,
type,
fromBlock,
toBlock
);
}
return await this.contract.queryFilter(type, fromBlock, toBlock);
} catch (e) {
err = e;
@ -2064,7 +2177,7 @@ class BaseEventsService {
}
}
async getLatestEvents({ fromBlock }) {
if (this.tovarishClient?.selectedRelayer && !["Deposit", "Withdrawal"].includes(this.type)) {
if (this.tovarishClient?.selectedRelayer) {
const { events, lastSyncBlock: lastBlock } = await this.tovarishClient.getEvents({
type: this.getTovarishType(),
fromBlock
@ -2081,8 +2194,8 @@ class BaseEventsService {
/* eslint-disable @typescript-eslint/no-unused-vars */
async validateEvents({
events,
lastBlock,
hasNewEvents
newEvents,
lastBlock
}) {
return void 0;
}
@ -2118,8 +2231,8 @@ class BaseEventsService {
const lastBlock = newEvents.lastBlock || allEvents[allEvents.length - 1]?.blockNumber;
const validateResult = await this.validateEvents({
events: allEvents,
lastBlock,
hasNewEvents: Boolean(newEvents.events.length)
newEvents: newEvents.events,
lastBlock
});
if (savedEvents.fromCache || newEvents.events.length) {
await this.saveEvents({ events: allEvents, lastBlock });
@ -2198,7 +2311,7 @@ class BaseTornadoService extends BaseEventsService {
}
async validateEvents({
events,
hasNewEvents
newEvents
}) {
if (events.length && this.getType() === "Deposit") {
const depositEvents = events;
@ -2207,7 +2320,7 @@ class BaseTornadoService extends BaseEventsService {
const errMsg = `Deposit events invalid wants ${depositEvents.length - 1} leafIndex have ${lastEvent.leafIndex}`;
throw new Error(errMsg);
}
if (this.merkleTreeService && (!this.optionalTree || hasNewEvents)) {
if (this.merkleTreeService && (!this.optionalTree || newEvents.length)) {
return await this.merkleTreeService.verifyTree(depositEvents);
}
}
@ -2228,7 +2341,9 @@ class BaseTornadoService extends BaseEventsService {
lastBlock
};
}
return super.getLatestEvents({ fromBlock });
return await this.getEventsFromRpc({
fromBlock
});
}
}
class BaseEchoService extends BaseEventsService {
@ -10270,4 +10385,4 @@ async function calculateSnarkProof(input, circuit, provingKey) {
return { proof, args };
}
export { BaseEchoService, BaseEncryptedNotesService, BaseEventsService, BaseGovernanceService, BaseRegistryService, BaseRevenueService, BaseTornadoService, BatchBlockService, BatchEventsService, BatchTransactionService, DBEchoService, DBEncryptedNotesService, DBGovernanceService, DBRegistryService, DBRevenueService, DBTornadoService, Deposit, ENSNameWrapper__factory, ENSRegistry__factory, ENSResolver__factory, ENSUtils, ENS__factory, ERC20__factory, EnsContracts, INDEX_DB_ERROR, IndexedDB, Invoice, MAX_FEE, MAX_TOVARISH_EVENTS, MIN_FEE, MIN_STAKE_BALANCE, MerkleTreeService, Mimc, Multicall__factory, NetId, NoteAccount, OffchainOracle__factory, OvmGasPriceOracle__factory, Pedersen, RelayerClient, ReverseRecords__factory, TokenPriceOracle, TornadoBrowserProvider, TornadoFeeOracle, TornadoRpcSigner, TornadoVoidSigner, TornadoWallet, TovarishClient, addNetwork, addressSchemaType, ajv, base64ToBytes, bigIntReplacer, bnSchemaType, bnToBytes, buffPedersenHash, bufferToBytes, bytes32BNSchemaType, bytes32SchemaType, bytesToBN, bytesToBase64, bytesToHex, calculateScore, calculateSnarkProof, chunk, concatBytes, convertETHToTokenAmount, createDeposit, crypto, customConfig, defaultConfig, defaultUserAgent, deployHasher, depositsEventsSchema, digest, downloadZip, echoEventsSchema, enabledChains, encodedLabelToLabelhash, encryptedNotesSchema, index as factories, fetchData, fetchGetUrlFunc, fetchIp, fromContentHash, gasZipID, gasZipInbounds, gasZipInput, gasZipMinMax, getActiveTokenInstances, getActiveTokens, getConfig, getEventsSchemaValidator, getHttpAgent, getIndexedDB, getInstanceByAddress, getNetworkConfig, getPermit2CommitmentsSignature, getPermit2Signature, getPermitCommitmentsSignature, getPermitSignature, getProvider, getProviderWithNetId, getRelayerEnsSubdomains, getStatusSchema, getSupportedInstances, getTokenBalances, getTovarishNetworks, getWeightRandom, governanceEventsSchema, hasherBytecode, hexToBytes, initGroth16, isHex, isNode, jobRequestSchema, jobsSchema, labelhash, leBuff2Int, leInt2Buff, loadDBEvents, loadRemoteEvents, makeLabelNodeAndParent, mimc, multicall, numberFormatter, packEncryptedMessage, parseInvoice, parseNote, pedersen, permit2Address, pickWeightedRandomRelayer, populateTransaction, proofSchemaType, proposalState, rBigInt, rHex, relayerRegistryEventsSchema, saveDBEvents, sleep, stakeBurnedEventsSchema, substring, toContentHash, toFixedHex, toFixedLength, unpackEncryptedMessage, unzipAsync, validateUrl, withdrawalsEventsSchema, zipAsync };
export { BaseEchoService, BaseEncryptedNotesService, BaseEventsService, BaseGovernanceService, BaseRegistryService, BaseRevenueService, BaseTornadoService, BatchBlockService, BatchEventsService, BatchTransactionService, DBEchoService, DBEncryptedNotesService, DBGovernanceService, DBRegistryService, DBRevenueService, DBTornadoService, Deposit, ENSNameWrapper__factory, ENSRegistry__factory, ENSResolver__factory, ENSUtils, ENS__factory, ERC20__factory, EnsContracts, INDEX_DB_ERROR, IndexedDB, Invoice, MAX_FEE, MAX_TOVARISH_EVENTS, MIN_FEE, MIN_STAKE_BALANCE, MerkleTreeService, Mimc, Multicall__factory, NetId, NoteAccount, OffchainOracle__factory, OvmGasPriceOracle__factory, Pedersen, RelayerClient, ReverseRecords__factory, TokenPriceOracle, TornadoBrowserProvider, TornadoFeeOracle, TornadoRpcSigner, TornadoVoidSigner, TornadoWallet, TovarishClient, addNetwork, addressSchemaType, ajv, base64ToBytes, bigIntReplacer, bnSchemaType, bnToBytes, buffPedersenHash, bufferToBytes, bytes32BNSchemaType, bytes32SchemaType, bytesToBN, bytesToBase64, bytesToHex, calculateScore, calculateSnarkProof, chunk, concatBytes, convertETHToTokenAmount, createDeposit, crypto, customConfig, defaultConfig, defaultUserAgent, deployHasher, depositsEventsSchema, digest, downloadZip, echoEventsSchema, enabledChains, encodedLabelToLabelhash, encryptedNotesSchema, index as factories, fetchData, fetchGetUrlFunc, fetchIp, fromContentHash, gasZipID, gasZipInbounds, gasZipInput, gasZipMinMax, getActiveTokenInstances, getActiveTokens, getConfig, getEventsSchemaValidator, getHttpAgent, getIndexedDB, getInstanceByAddress, getNetworkConfig, getPermit2CommitmentsSignature, getPermit2Signature, getPermitCommitmentsSignature, getPermitSignature, getProvider, getProviderWithNetId, getRelayerEnsSubdomains, getStatusSchema, getSubInfo, getSupportedInstances, getTokenBalances, getTovarishNetworks, getWeightRandom, governanceEventsSchema, hasherBytecode, hexToBytes, initGroth16, isHex, isNode, jobRequestSchema, jobsSchema, labelhash, leBuff2Int, leInt2Buff, loadDBEvents, loadRemoteEvents, makeLabelNodeAndParent, mimc, multiQueryFilter, multicall, numberFormatter, packEncryptedMessage, parseInvoice, parseNote, pedersen, permit2Address, pickWeightedRandomRelayer, populateTransaction, proofSchemaType, proposalState, rBigInt, rHex, relayerRegistryEventsSchema, saveDBEvents, sleep, stakeBurnedEventsSchema, substring, toContentHash, toFixedHex, toFixedLength, unpackEncryptedMessage, unzipAsync, validateUrl, withdrawalsEventsSchema, zipAsync };

694
dist/tornado.umd.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -1,4 +1,4 @@
import type {
import {
Provider,
BlockTag,
Block,
@ -7,9 +7,171 @@ import type {
ContractEventName,
EventLog,
TransactionReceipt,
isHexString,
assert,
assertArgument,
DeferredTopicFilter,
EventFragment,
TopicFilter,
Interface,
UndecodedEventLog,
Log,
} from 'ethers';
import { chunk, sleep } from './utils';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function isDeferred(value: any): value is DeferredTopicFilter {
return (
value &&
typeof value === 'object' &&
'getTopicFilter' in value &&
typeof value.getTopicFilter === 'function' &&
value.fragment
);
}
/**
* Copied from ethers.js as they don't export this function
* https://github.com/ethers-io/ethers.js/blob/main/src.ts/contract/contract.ts#L464
*/
export async function getSubInfo(
abiInterface: Interface,
event: ContractEventName,
): Promise<{
fragment: null | EventFragment;
tag: string;
topics: TopicFilter;
}> {
let topics: Array<null | string | Array<string>>;
let fragment: null | EventFragment = null;
// Convert named events to topicHash and get the fragment for
// events which need deconstructing.
if (Array.isArray(event)) {
const topicHashify = function (name: string): string {
if (isHexString(name, 32)) {
return name;
}
const fragment = abiInterface.getEvent(name);
assertArgument(fragment, 'unknown fragment', 'name', name);
return fragment.topicHash;
};
// Array of Topics and Names; e.g. `[ "0x1234...89ab", "Transfer(address)" ]`
topics = event.map((e) => {
if (e == null) {
return null;
}
if (Array.isArray(e)) {
return e.map(topicHashify);
}
return topicHashify(e);
});
} else if (event === '*') {
topics = [null];
} else if (typeof event === 'string') {
if (isHexString(event, 32)) {
// Topic Hash
topics = [event];
} else {
// Name or Signature; e.g. `"Transfer", `"Transfer(address)"`
fragment = abiInterface.getEvent(event);
assertArgument(fragment, 'unknown fragment', 'event', event);
topics = [fragment.topicHash];
}
} else if (isDeferred(event)) {
// Deferred Topic Filter; e.g. `contract.filter.Transfer(from)`
topics = await event.getTopicFilter();
} else if ('fragment' in event) {
// ContractEvent; e.g. `contract.filter.Transfer`
fragment = event.fragment;
topics = [fragment.topicHash];
} else {
assertArgument(false, 'unknown event name', 'event', event);
}
// Normalize topics and sort TopicSets
topics = topics.map((t) => {
if (t == null) {
return null;
}
if (Array.isArray(t)) {
const items = Array.from(new Set(t.map((t) => t.toLowerCase())).values());
if (items.length === 1) {
return items[0];
}
items.sort();
return items;
}
return t.toLowerCase();
});
const tag = topics
.map((t) => {
if (t == null) {
return 'null';
}
if (Array.isArray(t)) {
return t.join('|');
}
return t;
})
.join('&');
return { fragment, tag, topics };
}
export async function multiQueryFilter(
// Single address will scan for a single contract, array for multiple, and * for all contracts with event topic
address: string | string[],
contract: BaseContract,
event: ContractEventName,
fromBlock?: BlockTag,
toBlock?: BlockTag,
) {
if (fromBlock == null) {
fromBlock = 0;
}
if (toBlock == null) {
toBlock = 'latest';
}
const { fragment, topics } = await getSubInfo(contract.interface, event);
const filter = {
address: address === '*' ? undefined : address,
topics,
fromBlock,
toBlock,
};
const provider = contract.runner as Provider | null;
assert(provider, 'contract runner does not have a provider', 'UNSUPPORTED_OPERATION', { operation: 'queryFilter' });
return (await provider.getLogs(filter)).map((log) => {
let foundFragment = fragment;
if (foundFragment == null) {
try {
foundFragment = contract.interface.getEvent(log.topics[0]);
// eslint-disable-next-line no-empty
} catch {}
}
if (foundFragment) {
try {
return new EventLog(log, contract.interface, foundFragment);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
return new UndecodedEventLog(log, error);
}
}
return new Log(log, provider);
});
}
export interface BatchBlockServiceConstructor {
provider: Provider;
onProgress?: BatchBlockOnProgress;
@ -260,6 +422,7 @@ export class BatchTransactionService {
export interface BatchEventServiceConstructor {
provider: Provider;
contract: BaseContract;
address?: string | string[];
onProgress?: BatchEventOnProgress;
concurrencySize?: number;
blocksPerRequest?: number;
@ -295,6 +458,7 @@ export interface EventInput {
export class BatchEventsService {
provider: Provider;
contract: BaseContract;
address?: string | string[];
onProgress?: BatchEventOnProgress;
concurrencySize: number;
blocksPerRequest: number;
@ -304,6 +468,7 @@ export class BatchEventsService {
constructor({
provider,
contract,
address,
onProgress,
concurrencySize = 10,
blocksPerRequest = 5000,
@ -313,6 +478,7 @@ export class BatchEventsService {
}: BatchEventServiceConstructor) {
this.provider = provider;
this.contract = contract;
this.address = address;
this.onProgress = onProgress;
this.concurrencySize = concurrencySize;
this.blocksPerRequest = blocksPerRequest;
@ -328,6 +494,15 @@ export class BatchEventsService {
// eslint-disable-next-line no-unmodified-loop-condition
while ((!this.shouldRetry && retries === 0) || (this.shouldRetry && retries < this.retryMax)) {
try {
if (this.address) {
return (await multiQueryFilter(
this.address,
this.contract,
type,
fromBlock,
toBlock,
)) as EventLog[];
}
return (await this.contract.queryFilter(type, fromBlock, toBlock)) as EventLog[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {

@ -205,7 +205,7 @@ export class BaseEventsService<EventType extends MinimalEvents> {
}
async getLatestEvents({ fromBlock }: { fromBlock: number }): Promise<BaseEvents<EventType>> {
if (this.tovarishClient?.selectedRelayer && !['Deposit', 'Withdrawal'].includes(this.type)) {
if (this.tovarishClient?.selectedRelayer) {
const { events, lastSyncBlock: lastBlock } = await this.tovarishClient.getEvents<EventType>({
type: this.getTovarishType(),
fromBlock,
@ -225,9 +225,9 @@ export class BaseEventsService<EventType extends MinimalEvents> {
/* eslint-disable @typescript-eslint/no-unused-vars */
async validateEvents<S>({
events,
newEvents,
lastBlock,
hasNewEvents,
}: BaseEvents<EventType> & { hasNewEvents?: boolean }): Promise<S> {
}: BaseEvents<EventType> & { newEvents: EventType[] }): Promise<S> {
return undefined as S;
}
/* eslint-enable @typescript-eslint/no-unused-vars */
@ -274,8 +274,8 @@ export class BaseEventsService<EventType extends MinimalEvents> {
const validateResult = await this.validateEvents<S>({
events: allEvents,
newEvents: newEvents.events,
lastBlock,
hasNewEvents: Boolean(newEvents.events.length),
});
// If the events are loaded from cache or we have found new events, save them
@ -380,9 +380,9 @@ export class BaseTornadoService extends BaseEventsService<DepositsEvents | Withd
async validateEvents<S>({
events,
hasNewEvents,
newEvents,
}: BaseEvents<DepositsEvents | WithdrawalsEvents> & {
hasNewEvents?: boolean;
newEvents: (DepositsEvents | WithdrawalsEvents)[];
}) {
if (events.length && this.getType() === 'Deposit') {
const depositEvents = events as DepositsEvents[];
@ -394,7 +394,7 @@ export class BaseTornadoService extends BaseEventsService<DepositsEvents | Withd
throw new Error(errMsg);
}
if (this.merkleTreeService && (!this.optionalTree || hasNewEvents)) {
if (this.merkleTreeService && (!this.optionalTree || newEvents.length)) {
return (await this.merkleTreeService.verifyTree(depositEvents)) as S;
}
}
@ -423,7 +423,9 @@ export class BaseTornadoService extends BaseEventsService<DepositsEvents | Withd
};
}
return super.getLatestEvents({ fromBlock });
return await this.getEventsFromRpc({
fromBlock,
});
}
}