From 2608e53ac2091956a24055e5ad41ffc9d3503cd3 Mon Sep 17 00:00:00 2001 From: Scott Twiname Date: Wed, 28 Aug 2024 16:09:30 +1200 Subject: [PATCH] Fixes and optimisations to dictionary queries (#344) * Fixes and optimisations to dictionary queries * Fix build issue, tidy up * Update changelog --- package.json | 2 +- packages/node/CHANGELOG.md | 3 + .../node/src/indexer/dictionary/utils.spec.ts | 33 +++- packages/node/src/indexer/dictionary/utils.ts | 72 ++++---- .../dictionary/v1/ethDictionaryV1.spec.ts | 81 +++++++-- .../indexer/dictionary/v1/ethDictionaryV1.ts | 155 +++++++++--------- .../dictionary/v2/ethDictionaryV2.spec.ts | 44 ++++- .../indexer/dictionary/v2/ethDictionaryV2.ts | 142 +++++++--------- yarn.lock | 18 +- 9 files changed, 319 insertions(+), 231 deletions(-) diff --git a/package.json b/package.json index 9cebaf0015..cb17d32844 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "ts-loader": "^9.2.6", "ts-node": "^10.4.0", "tsconfig-paths": "^3.12.0", - "typescript": "^4.9.5" + "typescript": "^5.5.4" }, "resolutions": { "node-fetch": "2.6.7" diff --git a/packages/node/CHANGELOG.md b/packages/node/CHANGELOG.md index 19c8e9a7f8..2b3da9faae 100644 --- a/packages/node/CHANGELOG.md +++ b/packages/node/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Fixed +- Dictionary grouping optimisation regression (#344) +- Dictionary query not being used if address specified but no other filters (#344) ## [5.1.2] - 2024-08-27 ### Changed diff --git a/packages/node/src/indexer/dictionary/utils.spec.ts b/packages/node/src/indexer/dictionary/utils.spec.ts index ceff072e76..46758105fb 100644 --- a/packages/node/src/indexer/dictionary/utils.spec.ts +++ b/packages/node/src/indexer/dictionary/utils.spec.ts @@ -7,7 +7,7 @@ import { SubqlRuntimeDatasource, } from '@subql/types-ethereum'; import { EthereumProjectDsTemplate } from '../../configure/SubqueryProject'; -import { ethFilterDs } from './utils'; +import { groupedDataSources } from './utils'; describe('Dictionary utils', () => { it('can filter eth ds with multiple dynamic ds/templates', () => { @@ -80,12 +80,31 @@ describe('Dictionary utils', () => { // Runtime + ERC721 + ERC721 + ERC1155 expect(dataSources.length).toBe(4); - const filteredDs = ethFilterDs(dataSources); - // Runtime + ERC721 (groupedOptions) + ERC1155 - expect(filteredDs.length).toBe(3); - expect((filteredDs[1] as any).groupedOptions).toStrictEqual([ - { address: 'address1' }, - { address: 'address2' }, + const grouped = groupedDataSources(dataSources); + + expect(grouped).toEqual([ + [ + { + handler: 'handleDyanmicDs', + kind: 'ethereum/LogHandler', + filter: { + topics: [ + 'TransferSingle(address, address, address, uint256, uint256)', + ], + }, + }, + [undefined, 'address3'], + ], + [ + { + handler: 'handleERC721', + kind: 'ethereum/LogHandler', + filter: { + topics: ['Transfer(address, address, uint256)'], + }, + }, + ['address1', 'address2'], + ], ]); }); }); diff --git a/packages/node/src/indexer/dictionary/utils.ts b/packages/node/src/indexer/dictionary/utils.ts index 760b8a92a4..1de313e680 100644 --- a/packages/node/src/indexer/dictionary/utils.ts +++ b/packages/node/src/indexer/dictionary/utils.ts @@ -1,46 +1,44 @@ // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors // SPDX-License-Identifier: GPL-3.0 -import { SubqlDatasource } from '@subql/types-ethereum'; -import { groupBy, partition } from 'lodash'; -import { - EthereumProjectDsTemplate, - EthereumProjectDs, -} from '../../configure/SubqueryProject'; +import { SubqlRuntimeHandler } from '@subql/types-ethereum'; +import { groupBy } from 'lodash'; +import { EthereumProjectDs } from '../../configure/SubqueryProject'; -function isTemplateDs( - ds: EthereumProjectDs | EthereumProjectDsTemplate, -): ds is EthereumProjectDsTemplate { - return !!(ds as EthereumProjectDsTemplate)?.name; -} - -export function ethFilterDs( - dataSources: (EthereumProjectDs | EthereumProjectDsTemplate)[], -): SubqlDatasource[] { - const [normalDataSources, templateDataSources] = partition( - dataSources, - (ds) => !isTemplateDs(ds), - ); - - // Group templ - const groupedDataSources = Object.values( - groupBy( - templateDataSources as EthereumProjectDsTemplate[], - (ds) => ds.name, - ), - ).map((grouped) => { - if (grouped.length === 1) { - return grouped[0]; - } +// Gets all the unique handlers and the contract addresses that go with them +export function groupedDataSources( + dataSources: EthereumProjectDs[], +): [SubqlRuntimeHandler, Array][] { + const addressHandlers: [string | undefined, SubqlRuntimeHandler][] = + dataSources + .map((ds) => + ds.mapping.handlers.map( + (h) => + [ds.options?.address, h] as [ + string | undefined, + SubqlRuntimeHandler, + ], + ), + ) + .flat(); - const options = grouped.map((ds) => ds.options); - const ref = grouped[0]; + const grouped = groupBy(addressHandlers, ([, handler]) => { + return `${handler.kind}:${JSON.stringify(handler.filter)}`; + }); - return { - ...ref, - groupedOptions: options, - }; + const res = Object.values(grouped).map((grouped) => { + const addresses = [...new Set(grouped.map(([address]) => address))]; // Make unique, could be duplicate handler in the same DS + return [grouped[0][1], addresses] as [ + SubqlRuntimeHandler, + Array, + ]; }); - return [...normalDataSources, ...groupedDataSources]; + return res; +} + +export function validAddresses( + addresses?: (string | undefined | null)[], +): string[] { + return (addresses ?? []).filter((a) => !!a) as string[]; } diff --git a/packages/node/src/indexer/dictionary/v1/ethDictionaryV1.spec.ts b/packages/node/src/indexer/dictionary/v1/ethDictionaryV1.spec.ts index bc9bce24da..c44a9056c5 100644 --- a/packages/node/src/indexer/dictionary/v1/ethDictionaryV1.spec.ts +++ b/packages/node/src/indexer/dictionary/v1/ethDictionaryV1.spec.ts @@ -336,6 +336,55 @@ describe('buildDictionaryV1QueryEntries', () => { ]); }); + it('Creates a valid filter with a single event handler that has 0 filters but a contract address', () => { + const ds: SubqlRuntimeDatasource = { + kind: EthereumDatasourceKind.Runtime, + assets: new Map(), + options: { + abi: 'erc20', + address: '0x7ceb23fd6bc0add59e62ac25578270cff1b9f619', + }, + startBlock: 1, + mapping: { + file: '', + handlers: [ + { + handler: 'handleTransfer', + kind: EthereumHandlerKind.Event, + }, + { + handler: 'handleTransfer', + kind: EthereumHandlerKind.Call, + }, + ], + }, + }; + + const result = buildDictionaryV1QueryEntries([ds]); + expect(result).toEqual([ + { + entity: 'evmLogs', + conditions: [ + { + field: 'address', + matcher: 'equalTo', + value: '0x7ceb23fd6bc0add59e62ac25578270cff1b9f619', + }, + ], + }, + { + entity: 'evmTransactions', + conditions: [ + { + field: 'to', + matcher: 'equalTo', + value: '0x7ceb23fd6bc0add59e62ac25578270cff1b9f619', + }, + ], + }, + ]); + }); + it('builds a filter when theres a block handler with modulo filter', () => { const ds: SubqlRuntimeDatasource = { kind: EthereumDatasourceKind.Runtime, @@ -433,7 +482,7 @@ describe('buildDictionaryV1QueryEntries', () => { kind: EthereumHandlerKind.Event, filter: { topics: [ - 'TransferSingle(address, address, address, uint256, uint256)', + 'TransferMultiple(address, address, address, uint256, uint256)', ], }, }, @@ -458,7 +507,7 @@ describe('buildDictionaryV1QueryEntries', () => { field: 'topics0', matcher: 'equalTo', value: - '0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62', + '0xeb9b7dd0d144caae51d14067d0d112bc1839fbf62e856fca78f6b4d9bfb51962', }, ], entity: 'evmLogs', @@ -467,8 +516,8 @@ describe('buildDictionaryV1QueryEntries', () => { conditions: [ { field: 'address', - matcher: 'equalTo', - value: 'address1', + matcher: 'in', + value: ['address1', 'address2'], }, { field: 'topics0', @@ -484,29 +533,37 @@ describe('buildDictionaryV1QueryEntries', () => { { field: 'address', matcher: 'equalTo', - value: 'address2', + value: 'address3', }, { field: 'topics0', matcher: 'equalTo', value: - '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62', }, ], entity: 'evmLogs', }, + ]); + }); + + it('Drops the address if multiple datasources have the same filters, but one without an address', () => { + const duplicateDataSources = [ + { ...mockTempDs[0], options: { address: 'address1' } }, + { ...mockTempDs[0], options: { address: 'address2' } }, + { ...mockTempDs[0], options: { address: undefined } }, + ] as SubqlRuntimeDatasource[]; + + const queryEntry = buildDictionaryV1QueryEntries(duplicateDataSources); + + expect(queryEntry).toEqual([ { conditions: [ - { - field: 'address', - matcher: 'equalTo', - value: 'address3', - }, { field: 'topics0', matcher: 'equalTo', value: - '0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62', + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', }, ], entity: 'evmLogs', diff --git a/packages/node/src/indexer/dictionary/v1/ethDictionaryV1.ts b/packages/node/src/indexer/dictionary/v1/ethDictionaryV1.ts index fa4284048e..83da05fd01 100644 --- a/packages/node/src/indexer/dictionary/v1/ethDictionaryV1.ts +++ b/packages/node/src/indexer/dictionary/v1/ethDictionaryV1.ts @@ -12,7 +12,6 @@ import { EthereumLogFilter, EthereumTransactionFilter, SubqlDatasource, - SubqlEthereumProcessorOptions, } from '@subql/types-ethereum'; import JSON5 from 'json5'; import { sortBy, uniqBy } from 'lodash'; @@ -24,57 +23,53 @@ import { } from '../../../configure/SubqueryProject'; import { eventToTopic, functionToSighash } from '../../../utils/string'; import { yargsOptions } from '../../../yargs'; -import { ethFilterDs } from '../utils'; +import { groupedDataSources, validAddresses } from '../utils'; const CHAIN_ALIASES_URL = 'https://raw.githubusercontent.com/subquery/templates/main/chainAliases.json5'; const logger = getLogger('dictionary-v1'); -export function appendDsOptions( - dsOptions: - | SubqlEthereumProcessorOptions - | SubqlEthereumProcessorOptions[] - | undefined, +// Adds the addresses to the query conditions if valid +function applyAddresses( conditions: DictionaryQueryCondition[], + addresses?: (string | undefined | null)[], ): void { + // Don't do anything if theres something that requires no filters const queryAddressLimit = yargsOptions.argv['query-address-limit']; - if (Array.isArray(dsOptions)) { - const addresses = dsOptions - .map((option) => option.address) - .filter((v): v is string => Boolean(v)); + if ( + !addresses || + !addresses.length || + addresses.length > queryAddressLimit || + addresses.filter((v) => !v).length // DONT use find because 'undefined' and 'null' as falsey + ) { + return; + } - if (addresses.length > queryAddressLimit) { - logger.debug( - `Addresses length: ${addresses.length} is exceeding limit: ${queryAddressLimit}. Consider increasing this value with the flag --query-address-limit `, - ); - } + const filterAddresses = validAddresses(addresses).map((a) => a.toLowerCase()); - if (addresses.length !== 0 && addresses.length <= queryAddressLimit) { - conditions.push({ - field: 'address', - value: addresses, - matcher: 'in', - }); - } + if (addresses.length === 1) { + conditions.push({ + field: 'address', + value: filterAddresses[0], + matcher: 'equalTo', + }); } else { - if (dsOptions?.address) { - conditions.push({ - field: 'address', - value: dsOptions.address.toLowerCase(), - matcher: 'equalTo', - }); - } + conditions.push({ + field: 'address', + value: filterAddresses, + matcher: 'in', + }); } } function eventFilterToQueryEntry( - filter: EthereumLogFilter, - dsOptions?: SubqlEthereumProcessorOptions | SubqlEthereumProcessorOptions[], + filter?: EthereumLogFilter, + addresses?: (string | undefined | null)[], ): DictionaryV1QueryEntry { const conditions: DictionaryQueryCondition[] = []; - appendDsOptions(dsOptions, conditions); - if (filter.topics) { + applyAddresses(conditions, addresses); + if (filter?.topics) { for (let i = 0; i < Math.min(filter.topics.length, 4); i++) { const topic = filter.topics[i]; if (!topic) { @@ -85,7 +80,7 @@ function eventFilterToQueryEntry( if (topic === NOT_NULL_FILTER) { conditions.push({ field, - value: false as any, // TODO update types to allow boolean + value: false, matcher: 'isNull', }); } else { @@ -104,17 +99,25 @@ function eventFilterToQueryEntry( } function callFilterToQueryEntry( - filter: EthereumTransactionFilter, - dsOptions?: SubqlEthereumProcessorOptions | SubqlEthereumProcessorOptions[], + filter?: EthereumTransactionFilter, + addresses?: (string | undefined | null)[], ): DictionaryV1QueryEntry { const conditions: DictionaryQueryCondition[] = []; - appendDsOptions(dsOptions, conditions); + applyAddresses(conditions, addresses); for (const condition of conditions) { if (condition.field === 'address') { condition.field = 'to'; } } + + if (!filter) { + return { + entity: 'evmTransactions', + conditions, + }; + } + if (filter.from) { conditions.push({ field: 'from', @@ -162,54 +165,45 @@ function callFilterToQueryEntry( }; } -export type GroupedEthereumProjectDs = SubqlDatasource & { - groupedOptions?: SubqlEthereumProcessorOptions[]; -}; - +// eslint-disable-next-line complexity export function buildDictionaryV1QueryEntries( - dataSources: GroupedEthereumProjectDs[], + dataSources: SubqlDatasource[], ): DictionaryV1QueryEntry[] { const queryEntries: DictionaryV1QueryEntry[] = []; - for (const ds of dataSources) { - for (const handler of ds.mapping.handlers) { - // No filters, cant use dictionary - if (!handler.filter) return []; + const groupedHandlers = groupedDataSources(dataSources); + for (const [handler, addresses] of groupedHandlers) { + // No filters, cant use dictionary + if (!handler.filter && !addresses?.length) return []; - switch (handler.kind) { - case EthereumHandlerKind.Block: - if (handler.filter.modulo === undefined) { - return []; - } - break; - case EthereumHandlerKind.Call: { - const filter = handler.filter as EthereumTransactionFilter; - if ( - filter.from !== undefined || - filter.to !== undefined || - filter.function !== undefined - ) { - queryEntries.push(callFilterToQueryEntry(filter, ds.options)); - } else { - return []; - } - break; + switch (handler.kind) { + case EthereumHandlerKind.Block: + if (handler.filter?.modulo === undefined) { + return []; } - case EthereumHandlerKind.Event: { - const filter = handler.filter as EthereumLogFilter; - if (ds.groupedOptions) { - queryEntries.push( - eventFilterToQueryEntry(filter, ds.groupedOptions), - ); - } else if (ds.options?.address || filter.topics) { - queryEntries.push(eventFilterToQueryEntry(filter, ds.options)); - } else { - return []; - } - break; + break; + case EthereumHandlerKind.Call: { + if ( + (!handler.filter || + !Object.values(handler.filter).filter((v) => v !== undefined) + .length) && + !validAddresses(addresses).length + ) { + return []; } - default: + queryEntries.push(callFilterToQueryEntry(handler.filter, addresses)); + break; } + case EthereumHandlerKind.Event: + if ( + !handler.filter?.topics?.length && + !validAddresses(addresses).length + ) { + return []; + } + queryEntries.push(eventFilterToQueryEntry(handler.filter, addresses)); + break; + default: } } @@ -222,7 +216,7 @@ export function buildDictionaryV1QueryEntries( ); } -export class EthDictionaryV1 extends DictionaryV1 { +export class EthDictionaryV1 extends DictionaryV1 { private constructor( project: SubqueryProject, nodeConfig: NodeConfig, @@ -266,7 +260,6 @@ export class EthDictionaryV1 extends DictionaryV1 { // Add name to datasource as templates have this set dataSources: (EthereumProjectDs | EthereumProjectDsTemplate)[], ): DictionaryV1QueryEntry[] { - const filteredDs = ethFilterDs(dataSources); - return buildDictionaryV1QueryEntries(filteredDs); + return buildDictionaryV1QueryEntries(dataSources); } } diff --git a/packages/node/src/indexer/dictionary/v2/ethDictionaryV2.spec.ts b/packages/node/src/indexer/dictionary/v2/ethDictionaryV2.spec.ts index 71766b2d48..e9ec13ff42 100644 --- a/packages/node/src/indexer/dictionary/v2/ethDictionaryV2.spec.ts +++ b/packages/node/src/indexer/dictionary/v2/ethDictionaryV2.spec.ts @@ -22,7 +22,6 @@ import { SubqueryProject, } from '../../../configure/SubqueryProject'; import { EthereumApi } from '../../../ethereum'; -import { ethFilterDs } from '../utils'; import { buildDictionaryV2QueryEntry, EthDictionaryV2, @@ -396,6 +395,45 @@ describe('buildDictionaryV2QueryEntry', () => { }); }); + it('Creates a valid filter with a single event handler that has 0 filters but a contract address', () => { + const ds: SubqlRuntimeDatasource = { + kind: EthereumDatasourceKind.Runtime, + assets: new Map(), + options: { + abi: 'erc20', + address: '0x7ceb23fd6bc0add59e62ac25578270cff1b9f619', + }, + startBlock: 1, + mapping: { + file: '', + handlers: [ + { + handler: 'handleTransfer', + kind: EthereumHandlerKind.Event, + }, + { + handler: 'handleTransfer', + kind: EthereumHandlerKind.Call, + }, + ], + }, + }; + const result = buildDictionaryV2QueryEntry([ds]); + + expect(result).toEqual({ + transactions: [ + { + to: ['0x7ceb23fd6bc0add59e62ac25578270cff1b9f619'], + }, + ], + logs: [ + { + address: ['0x7ceb23fd6bc0add59e62ac25578270cff1b9f619'], + }, + ], + }); + }); + it('build query entries for multiple ds', () => { const ds: SubqlRuntimeDatasource[] = [ { @@ -557,7 +595,7 @@ describe('buildDictionaryV2QueryEntry', () => { (tmp.options.address = `0x${i}`), ds.push(tmp); } - const result = buildDictionaryV2QueryEntry(ethFilterDs(ds)); + const result = buildDictionaryV2QueryEntry(ds); expect(result).toEqual({ logs: [ @@ -591,7 +629,7 @@ describe('buildDictionaryV2QueryEntry', () => { (tmp.options.address = `0x${i}`), ds.push(tmp); } - const result = buildDictionaryV2QueryEntry(ethFilterDs(ds)); + const result = buildDictionaryV2QueryEntry(ds); expect(result).toEqual({ logs: [ diff --git a/packages/node/src/indexer/dictionary/v2/ethDictionaryV2.ts b/packages/node/src/indexer/dictionary/v2/ethDictionaryV2.ts index e769a7d6ea..9e4f3a6674 100644 --- a/packages/node/src/indexer/dictionary/v2/ethDictionaryV2.ts +++ b/packages/node/src/indexer/dictionary/v2/ethDictionaryV2.ts @@ -1,7 +1,6 @@ // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors // SPDX-License-Identifier: GPL-3.0 -import { BigNumber } from '@ethersproject/bignumber'; import { NOT_NULL_FILTER } from '@subql/common-ethereum'; import { NodeConfig, @@ -17,7 +16,6 @@ import { EthereumLogFilter, EthereumTransactionFilter, SubqlDatasource, - SubqlEthereumProcessorOptions, } from '@subql/types-ethereum'; import { uniqBy } from 'lodash'; import { EthereumNodeConfig } from '../../../configure/NodeConfig'; @@ -29,8 +27,7 @@ import { import { EthereumApi } from '../../../ethereum'; import { eventToTopic, functionToSighash } from '../../../utils/string'; import { yargsOptions } from '../../../yargs'; -import { ethFilterDs } from '../utils'; -import { GroupedEthereumProjectDs } from '../v1'; +import { groupedDataSources, validAddresses } from '../utils'; import { RawEthBlock, EthDictionaryV2QueryEntry, @@ -43,42 +40,32 @@ const MIN_FETCH_LIMIT = 200; const logger = getLogger('dictionary-v2'); -function extractOptionAddresses( - dsOptions?: SubqlEthereumProcessorOptions | SubqlEthereumProcessorOptions[], -): string[] { +function applyAddresses( + addresses?: (string | undefined | null)[], +): string[] | undefined { const queryAddressLimit = yargsOptions.argv['query-address-limit']; - const addressArray: string[] = []; - if (Array.isArray(dsOptions)) { - const addresses = dsOptions - .map((option) => option.address) - .filter((address): address is string => Boolean(address)); - - if (addresses.length > queryAddressLimit) { - logger.debug( - `Addresses length: ${addresses.length} is exceeding limit: ${queryAddressLimit}. Consider increasing this value with the flag --query-address-limit `, - ); - } - if (addresses.length !== 0 && addresses.length <= queryAddressLimit) { - addressArray.push(...addresses); - } - } else { - if (dsOptions?.address) { - addressArray.push(dsOptions.address.toLowerCase()); - } + if ( + !addresses || + !addresses.length || + addresses.length > queryAddressLimit || + addresses.filter((v) => !v).length // DONT use find because 'undefined' and 'null' as falsey + ) { + return []; } - return addressArray; + + return validAddresses(addresses).map((a) => a.toLowerCase()); } function callFilterToDictionaryCondition( - filter: EthereumTransactionFilter, - dsOptions?: SubqlEthereumProcessorOptions, + filter?: EthereumTransactionFilter, + addresses?: (string | undefined | null)[], ): EthDictionaryTxConditions { const txConditions: EthDictionaryTxConditions = {}; const toArray: (string | null)[] = []; const fromArray: string[] = []; const funcArray: string[] = []; - if (filter.from) { + if (filter?.from) { fromArray.push(filter.from.toLowerCase()); } @@ -90,11 +77,11 @@ function callFilterToDictionaryCondition( } }; - const optionsAddresses = extractOptionAddresses(dsOptions); - if (!optionsAddresses.length) { - assignTo(filter.to); + const optionsAddresses = applyAddresses(addresses); + if (!optionsAddresses?.length) { + assignTo(filter?.to); } else { - if (filter.to || filter.to === null) { + if (filter?.to || filter?.to === null) { logger.warn( `TransactionFilter 'to' conflicts with 'address' in data source options, using data source option`, ); @@ -102,7 +89,7 @@ function callFilterToDictionaryCondition( optionsAddresses.forEach(assignTo); } - if (filter.function) { + if (filter?.function) { funcArray.push(functionToSighash(filter.function)); } @@ -121,12 +108,12 @@ function callFilterToDictionaryCondition( } function eventFilterToDictionaryCondition( - filter: EthereumLogFilter, - dsOptions?: SubqlEthereumProcessorOptions | SubqlEthereumProcessorOptions[], + filter?: EthereumLogFilter, + addresses?: (string | undefined | null)[], ): EthDictionaryLogConditions { const logConditions: EthDictionaryLogConditions = {}; - logConditions.address = extractOptionAddresses(dsOptions); - if (filter.topics) { + logConditions.address = applyAddresses(addresses); + if (filter?.topics) { for (let i = 0; i < Math.min(filter.topics.length, 4); i++) { const topic = filter.topics[i]; if (!topic) { @@ -171,58 +158,52 @@ function sanitiseDictionaryConditions( } export function buildDictionaryV2QueryEntry( - dataSources: GroupedEthereumProjectDs[], + dataSources: EthereumProjectDs[], ): EthDictionaryV2QueryEntry { const dictionaryConditions: EthDictionaryV2QueryEntry = { logs: [], transactions: [], }; - for (const ds of dataSources) { - for (const handler of ds.mapping.handlers) { - // No filters, cant use dictionary - if (!handler.filter) return {}; + const groupedHandlers = groupedDataSources(dataSources); + for (const [handler, addresses] of groupedHandlers) { + // No filters, cant use dictionary + if (!handler.filter && !addresses?.length) return {}; - switch (handler.kind) { - case EthereumHandlerKind.Block: - if (handler.filter.modulo === undefined) { - return {}; - } - break; - case EthereumHandlerKind.Call: { - const filter = handler.filter as EthereumTransactionFilter; - if ( - filter.from !== undefined || - filter.to !== undefined || - filter.function !== undefined - ) { - dictionaryConditions.transactions ??= []; - dictionaryConditions.transactions.push( - callFilterToDictionaryCondition(filter, ds.options), - ); - } else { - // do nothing; - } - break; + switch (handler.kind) { + case EthereumHandlerKind.Block: + if (handler.filter?.modulo === undefined) { + return {}; + } + break; + case EthereumHandlerKind.Call: { + if ( + (handler.filter && + Object.values(handler.filter).filter((v) => v !== undefined) + .length) || + validAddresses(addresses).length + ) { + dictionaryConditions.transactions ??= []; + dictionaryConditions.transactions.push( + callFilterToDictionaryCondition(handler.filter, addresses), + ); } - case EthereumHandlerKind.Event: { - const filter = handler.filter as EthereumLogFilter; + break; + } + case EthereumHandlerKind.Event: { + if ( + handler.filter?.topics?.length || + validAddresses(addresses).length + ) { dictionaryConditions.logs ??= []; - if (ds.groupedOptions) { - dictionaryConditions.logs.push( - eventFilterToDictionaryCondition(filter, ds.groupedOptions), - ); - } else if (ds.options?.address || filter.topics) { - dictionaryConditions.logs.push( - eventFilterToDictionaryCondition(filter, ds.options), - ); - } else { - // do nothing; - } - break; + dictionaryConditions.logs.push( + eventFilterToDictionaryCondition(handler.filter, addresses), + ); } - default: + + break; } + default: } } @@ -261,8 +242,7 @@ export class EthDictionaryV2 extends DictionaryV2< buildDictionaryQueryEntries( dataSources: (EthereumProjectDs | EthereumProjectDsTemplate)[], ): EthDictionaryV2QueryEntry { - const filteredDs = ethFilterDs(dataSources); - return buildDictionaryV2QueryEntry(filteredDs); + return buildDictionaryV2QueryEntry(dataSources); } // eslint-disable-next-line @typescript-eslint/require-await diff --git a/yarn.lock b/yarn.lock index a9b33c55b7..e04eec938a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11596,7 +11596,7 @@ __metadata: ts-loader: ^9.2.6 ts-node: ^10.4.0 tsconfig-paths: ^3.12.0 - typescript: ^4.9.5 + typescript: ^5.5.4 web3-providers-http: ^1.7.5 web3-providers-ws: ^1.7.5 languageName: unknown @@ -12113,23 +12113,23 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^4.9.5": - version: 4.9.5 - resolution: "typescript@npm:4.9.5" +"typescript@npm:^5.5.4": + version: 5.5.4 + resolution: "typescript@npm:5.5.4" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: ee000bc26848147ad423b581bd250075662a354d84f0e06eb76d3b892328d8d4440b7487b5a83e851b12b255f55d71835b008a66cbf8f255a11e4400159237db + checksum: b309040f3a1cd91c68a5a58af6b9fdd4e849b8c42d837b2c2e73f9a4f96a98c4f1ed398a9aab576ee0a4748f5690cf594e6b99dbe61de7839da748c41e6d6ca8 languageName: node linkType: hard -"typescript@patch:typescript@^4.9.5#~builtin": - version: 4.9.5 - resolution: "typescript@patch:typescript@npm%3A4.9.5#~builtin::version=4.9.5&hash=493e53" +"typescript@patch:typescript@^5.5.4#~builtin": + version: 5.5.4 + resolution: "typescript@patch:typescript@npm%3A5.5.4#~builtin::version=5.5.4&hash=493e53" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 2eee5c37cad4390385db5db5a8e81470e42e8f1401b0358d7390095d6f681b410f2c4a0c496c6ff9ebd775423c7785cdace7bcdad76c7bee283df3d9718c0f20 + checksum: fc52962f31a5bcb716d4213bef516885e4f01f30cea797a831205fc9ef12b405a40561c40eae3127ab85ba1548e7df49df2bcdee6b84a94bfbe3a0d7eff16b14 languageName: node linkType: hard