diff --git a/packages/core/src/sync-historical/index.ts b/packages/core/src/sync-historical/index.ts index 0d2f555cf..310225812 100644 --- a/packages/core/src/sync-historical/index.ts +++ b/packages/core/src/sync-historical/index.ts @@ -424,21 +424,23 @@ export const createHistoricalSync = async ( }; /** Extract and insert the log-based addresses that match `filter` + `interval`. */ - const syncLogFactory = async (filter: LogFactory, interval: Interval) => { + const syncLogFactory = async (factory: LogFactory, interval: Interval) => { const logs = await syncLogsDynamic({ - filter, + filter: factory, interval, - address: filter.address, + address: factory.address, }); const childAddresses = new Map(); for (const log of logs) { - const address = getChildAddress({ log, factory: filter }); + const address = getChildAddress({ log, factory }); childAddresses.set(address, hexToNumber(log.blockNumber)); } + // Note: `factory` must refer to the same original `factory` in `filter` + // and not be a recovered factory from `recoverFilter`. await args.syncStore.insertChildAddresses({ - childAddresses: new Map([[filter, childAddresses]]), + childAddresses: new Map([[factory, childAddresses]]), chainId: args.network.chainId, }); }; @@ -449,11 +451,13 @@ export const createHistoricalSync = async ( * child addresses is above the limit. */ const syncAddressFactory = async ( - filter: Factory, + factory: Factory, interval: Interval, ): Promise> => { - await syncLogFactory(filter, interval); - return args.syncStore.getChildAddresses({ filter }); + await syncLogFactory(factory, interval); + // Note: `factory` must refer to the same original `factory` in `filter` + // and not be a recovered factory from `recoverFilter`. + return args.syncStore.getChildAddresses({ factory }); }; //////// diff --git a/packages/core/src/sync-store/index.test.ts b/packages/core/src/sync-store/index.test.ts index b88ddbaa7..8ffdfe9b0 100644 --- a/packages/core/src/sync-store/index.test.ts +++ b/packages/core/src/sync-store/index.test.ts @@ -485,7 +485,7 @@ test("getChildAddresses()", async (context) => { }); const addresses = await syncStore.getChildAddresses({ - filter: filter.address, + factory: filter.address, }); expect(addresses).toMatchInlineSnapshot(` @@ -512,7 +512,7 @@ test("getChildAddresses() empty", async (context) => { const filter = sources[0]!.filter as LogFilter; const addresses = await syncStore.getChildAddresses({ - filter: filter.address, + factory: filter.address, }); expect(addresses).toMatchInlineSnapshot("Map {}"); @@ -544,7 +544,7 @@ test("getChildAddresses() distinct", async (context) => { }); const addresses = await syncStore.getChildAddresses({ - filter: filter.address, + factory: filter.address, }); expect(addresses).toMatchInlineSnapshot(` diff --git a/packages/core/src/sync-store/index.ts b/packages/core/src/sync-store/index.ts index 340a9ab0a..305c011e4 100644 --- a/packages/core/src/sync-store/index.ts +++ b/packages/core/src/sync-store/index.ts @@ -19,7 +19,12 @@ import type { SyncTransactionReceipt, } from "@/internal/types.js"; import { shouldGetTransactionReceipt } from "@/sync/filter.js"; -import { fragmentToId, getFragments } from "@/sync/fragments.js"; +import { + fragmentAddressToId, + fragmentToId, + getAddressFragments, + getFragments, +} from "@/sync/fragments.js"; import type { Interval } from "@/utils/interval.js"; import { type SelectQueryBuilder, sql as ksql, sql } from "kysely"; import type { InsertObject } from "kysely"; @@ -50,7 +55,7 @@ export type SyncStore = { childAddresses: Map>; chainId: number; }): Promise; - getChildAddresses(args: { filter: Factory }): Promise>; + getChildAddresses(args: { factory: Factory }): Promise>; insertLogs(args: { logs: SyncLog[]; chainId: number }): Promise; insertBlocks(args: { blocks: SyncBlock[]; chainId: number }): Promise; insertTransactions(args: { @@ -231,17 +236,32 @@ export const createSyncStore = ({ return result; }, ), - insertChildAddresses: ({ childAddresses, chainId }) => - database.wrap( + insertChildAddresses: async ({ childAddresses, chainId }) => { + if ( + childAddresses.size === 0 || + Array.from(childAddresses.values()).every( + (addresses) => addresses.size === 0, + ) + ) { + return; + } + await database.wrap( { method: "insertChildAddresses", includeTraceLogs: true }, async () => { const values: InsertObject[] = []; for (const [factory, addresses] of childAddresses) { - const filterId = JSON.stringify(factory); + const fragmentIds = getAddressFragments(factory) + .map(({ fragment }) => fragmentAddressToId(fragment)) + .sort((a, b) => + a === null ? -1 : b === null ? 1 : a < b ? -1 : 1, + ); + const factoryId = fragmentIds.join("_"); + + // Note: factories must be keyed by fragment, then how do we know which address belongs to which fragment for (const [address, blockNumber] of addresses) { values.push({ - factory_hash: sql`MD5(${filterId})`, + factory_hash: sql`MD5(${factoryId})`, chain_id: chainId, block_number: blockNumber, address: address, @@ -249,24 +269,23 @@ export const createSyncStore = ({ } } - if (values.length > 0) { - await database.qb.sync - .insertInto("factories") - .values(values) - .execute(); - } + await database.qb.sync.insertInto("factories").values(values).execute(); }, - ), - getChildAddresses: ({ filter }) => + ); + }, + getChildAddresses: ({ factory }) => database.wrap( { method: "getChildAddresses", includeTraceLogs: true }, () => { - const filterId = JSON.stringify(filter); + const fragmentIds = getAddressFragments(factory) + .map(({ fragment }) => fragmentAddressToId(fragment)) + .sort((a, b) => (a === null ? -1 : b === null ? 1 : a < b ? -1 : 1)); + const factoryId = fragmentIds.join("_"); return database.qb.sync .selectFrom("factories") .select(["address", sql`block_number`.as("blockNumber")]) - .where("factory_hash", "=", sql`MD5(${filterId})`) + .where("factory_hash", "=", sql`MD5(${factoryId})`) .execute() .then((rows) => { const result = new Map(); diff --git a/packages/core/src/sync/filter.ts b/packages/core/src/sync/filter.ts index 0ff692055..c591d195a 100644 --- a/packages/core/src/sync/filter.ts +++ b/packages/core/src/sync/filter.ts @@ -69,8 +69,8 @@ export const isAddressMatched = ({ }) => { if (address === undefined) return false; if ( - childAddresses.has(address) && - childAddresses.get(address)! <= blockNumber + childAddresses.has(toLowerCase(address)) && + childAddresses.get(toLowerCase(address))! <= blockNumber ) { return true; } diff --git a/packages/core/src/sync/fragments.ts b/packages/core/src/sync/fragments.ts index e71e869c3..6043f886b 100644 --- a/packages/core/src/sync/fragments.ts +++ b/packages/core/src/sync/fragments.ts @@ -38,7 +38,7 @@ type FragmentReturnType = { adjacentIds: FragmentId[]; }[]; -const getAddressFragments = ( +export const getAddressFragments = ( address: Address | Address[] | Factory | undefined, ) => { const fragments: { @@ -302,7 +302,7 @@ export const getTransferFilterFragments = ({ return fragments; }; -const fragmentAddressToId = ( +export const fragmentAddressToId = ( fragmentAddress: FragmentAddress, ): FragmentAddressId => { if (fragmentAddress === null) return null; @@ -332,23 +332,12 @@ const recoverAddress = ( if (baseAddress === undefined) return undefined; if (typeof baseAddress === "string") return baseAddress; if (Array.isArray(baseAddress)) return dedupe(fragmentAddresses) as Address[]; - if (typeof baseAddress.address === "string") return baseAddress; - - const address = { - type: "log", - chainId: baseAddress.chainId, - address: [] as Address[], - eventSelector: baseAddress.eventSelector, - childAddressLocation: baseAddress.childAddressLocation, - } satisfies Factory; - - address.address = dedupe( - (fragmentAddresses as Extract[]).map( - ({ address }) => address, - ), - ); - - return address; + + // Note: At this point, `baseAddress` is a factory. We explicitly don't try to recover the factory + // address from the fragments because we want a `insertChildAddresses` and `getChildAddresses` to + // use the factory as a stable key. + + return baseAddress; }; const recoverSelector = ( diff --git a/packages/core/src/sync/index.ts b/packages/core/src/sync/index.ts index 2c45aaa7b..98c7718bc 100644 --- a/packages/core/src/sync/index.ts +++ b/packages/core/src/sync/index.ts @@ -877,7 +877,7 @@ export const createSync = async (params: { if (isAddressFactory(filter.address)) { const childAddresses = await params.syncStore.getChildAddresses({ - filter: filter.address, + factory: filter.address, }); initialChildAddresses.set( @@ -893,7 +893,7 @@ export const createSync = async (params: { if (isAddressFactory(filter.fromAddress)) { const childAddresses = await params.syncStore.getChildAddresses({ - filter: filter.fromAddress, + factory: filter.fromAddress, }); initialChildAddresses.set( @@ -905,7 +905,7 @@ export const createSync = async (params: { if (isAddressFactory(filter.toAddress)) { const childAddresses = await params.syncStore.getChildAddresses({ - filter: filter.toAddress, + factory: filter.toAddress, }); initialChildAddresses.set( @@ -1153,7 +1153,7 @@ export async function* getLocalEventGenerator(params: { case "log": if (isAddressFactory(filter.address)) { const childAddresses = await params.syncStore.getChildAddresses({ - filter: filter.address, + factory: filter.address, }); initialChildAddresses.set(filter.address, new Map(childAddresses)); @@ -1165,7 +1165,7 @@ export async function* getLocalEventGenerator(params: { case "trace": if (isAddressFactory(filter.fromAddress)) { const childAddresses = await params.syncStore.getChildAddresses({ - filter: filter.fromAddress, + factory: filter.fromAddress, }); initialChildAddresses.set( @@ -1176,7 +1176,7 @@ export async function* getLocalEventGenerator(params: { if (isAddressFactory(filter.toAddress)) { const childAddresses = await params.syncStore.getChildAddresses({ - filter: filter.toAddress, + factory: filter.toAddress, }); initialChildAddresses.set(