-
Notifications
You must be signed in to change notification settings - Fork 15
feat(ensindexer): leverage SWR caches to achieve goals related to LocalPonderClient
#1652
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
tk-o
wants to merge
15
commits into
feat/indexing-status-builder-3
Choose a base branch
from
feat/indexing-status-builder-3-with-cache
base: feat/indexing-status-builder-3
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
c7b6db4
Extract business-layer for `ChainIndexingMetadataImmutable`
tk-o 4cf211a
Extract business-layer for `ChainIndexingMetadataDynamic`
tk-o 748ce5d
Create `PonderClientCache`
tk-o 05f512c
Update `LocalPonderClient` data fetching approach
tk-o 9b274d7
Create `ChainsIndexingMetadataImmutableCache`
tk-o 84005d7
Update `LocalPonderClient` data fetching approach
tk-o 37f4b44
Refactor `LocalPonderClient` into simplicity
tk-o f3414c1
Make `getLocalPonderClient` sync function
tk-o 2264126
Use `LocalPonderClient` in Indexer Status API handler
tk-o 65ee979
Fix typo
tk-o 817f1cc
docs(changeset): Implements `LocalPonderClient` allowing interactions…
tk-o d0f5326
Merge remote-tracking branch 'origin/feat/indexing-status-builder-3' …
tk-o 7559fb8
Further simplify `LocalPonderClient` implementation
tk-o 6fbed64
Apply AI PR feedback
tk-o f3ab127
Apply code formatting
tk-o File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "ensindexer": minor | ||
| --- | ||
|
|
||
| Implements `LocalPonderClient` allowing interactions with Ponder app configuration and data. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
66 changes: 66 additions & 0 deletions
66
apps/ensindexer/ponder/src/api/lib/cache/ponder-client.cache.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| import config from "@/config"; | ||
|
|
||
| import { type Duration, SWRCache } from "@ensnode/ensnode-sdk"; | ||
| import { | ||
| PonderClient, | ||
| type PonderIndexingMetrics, | ||
| type PonderIndexingStatus, | ||
| } from "@ensnode/ponder-sdk"; | ||
|
|
||
| /** | ||
| * Result of the Ponder Client cache. | ||
| */ | ||
| export interface PonderClientCacheResult { | ||
| ponderIndexingMetrics: PonderIndexingMetrics; | ||
| ponderIndexingStatus: PonderIndexingStatus; | ||
| } | ||
|
|
||
| /** | ||
| * SWR Cache for Ponder Client data | ||
| */ | ||
| export type PonderClientCache = SWRCache<PonderClientCacheResult>; | ||
|
|
||
| const ponderClient = new PonderClient(config.ensIndexerUrl); | ||
|
|
||
| /** | ||
| * Cache for Ponder Client data | ||
| * | ||
| * In case of using multiple Ponder API endpoints, it is optimal for data | ||
| * consistency to call all endpoints at once. This cache loads both | ||
| * Ponder Indexing Metrics and Ponder Indexing Status together, and provides | ||
| * them as a single cached result. This way, we ensure that the metrics and | ||
| * status data are always from the same point in time, and avoid potential | ||
| * inconsistencies that could arise if they were loaded separately. | ||
| * | ||
| * Ponder Client may sometimes fail to load data, i.e. due to network issues. | ||
| * The cache is designed to be resilient to loading failures, and will keep data | ||
| * in the cache indefinitely until it can be successfully reloaded. | ||
| * See `ttl` option below. | ||
| * | ||
| * Ponder Indexing Metrics and Ponder Indexing Status can both change frequently, | ||
| * so the cache is designed to proactively revalidate data to ensure freshness. | ||
| * See `proactiveRevalidationInterval` option below. | ||
| * | ||
| * Note, that Ponder app needs a while at startup to populate indexing metrics, | ||
| * and indexing status, so a few of the initial attempts to load this cache may | ||
| * fail until required data is made available by the Ponder app. | ||
| */ | ||
| export const ponderClientCache = new SWRCache({ | ||
| fn: async function loadPonderClientCache() { | ||
| try { | ||
| const [ponderIndexingMetrics, ponderIndexingStatus] = await Promise.all([ | ||
| ponderClient.metrics(), | ||
| ponderClient.status(), | ||
| ]); | ||
|
|
||
| return { ponderIndexingMetrics, ponderIndexingStatus }; | ||
| } catch (error) { | ||
| const errorMessage = error instanceof Error ? error.message : String(error); | ||
| console.error(`[PonderClientCache]: an error occurred while loading data: ${errorMessage}`); | ||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| throw new Error(`Failed to load Ponder Client cache: ${errorMessage}`); | ||
| } | ||
| }, | ||
| ttl: Number.POSITIVE_INFINITY, | ||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| proactiveRevalidationInterval: 10 satisfies Duration, | ||
| }) satisfies PonderClientCache; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 48 additions & 0 deletions
48
apps/ensindexer/ponder/src/api/lib/chains-indexing-metadata-dynamic.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import type { ChainId, PonderIndexingMetrics, PonderIndexingStatus } from "@ensnode/ponder-sdk"; | ||
|
|
||
| import type { ChainIndexingMetadataDynamic } from "@/lib/indexing-status-builder/chain-indexing-metadata"; | ||
|
|
||
| /** | ||
| * Build a map of chain ID to its dynamic indexing metadata. | ||
| * | ||
| * The dynamic metadata is based on the current indexing metrics and status | ||
| * of the chain, which can change over time as the chain is being indexed. | ||
| * | ||
| * @param indexedChainIds A set of chain IDs that are being indexed. | ||
| * @param ponderIndexingMetrics The current indexing metrics for all chains. | ||
| * @param ponderIndexingStatus The current indexing status for all chains. | ||
| * | ||
| * @returns A map of chain ID to its dynamic indexing metadata. | ||
| * | ||
| * @throws Error if any invariants are violated. | ||
| */ | ||
| export function buildChainsIndexingMetadataDynamic( | ||
| indexedChainIds: Set<ChainId>, | ||
| ponderIndexingMetrics: PonderIndexingMetrics, | ||
| ponderIndexingStatus: PonderIndexingStatus, | ||
| ): Map<ChainId, ChainIndexingMetadataDynamic> { | ||
| const chainsIndexingMetadataDynamic = new Map<ChainId, ChainIndexingMetadataDynamic>(); | ||
|
|
||
| for (const chainId of indexedChainIds) { | ||
| const chainIndexingMetrics = ponderIndexingMetrics.chains.get(chainId); | ||
| const chainIndexingStatus = ponderIndexingStatus.chains.get(chainId); | ||
|
|
||
| // Invariants: indexing metrics and indexing status must exist in proper state for the indexed chain. | ||
| if (!chainIndexingMetrics) { | ||
| throw new Error(`Indexing metrics must be available for indexed chain ID ${chainId}`); | ||
| } | ||
|
|
||
| if (!chainIndexingStatus) { | ||
| throw new Error(`Indexing status must be available for indexed chain ID ${chainId}`); | ||
| } | ||
|
|
||
| const metadataDynamic = { | ||
| indexingMetrics: chainIndexingMetrics, | ||
| indexingStatus: chainIndexingStatus, | ||
| } satisfies ChainIndexingMetadataDynamic; | ||
|
|
||
| chainsIndexingMetadataDynamic.set(chainId, metadataDynamic); | ||
| } | ||
|
|
||
| return chainsIndexingMetadataDynamic; | ||
| } |
114 changes: 114 additions & 0 deletions
114
apps/ensindexer/ponder/src/api/lib/chains-indexing-metadata-immutable.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| import type { PublicClient } from "viem"; | ||
|
|
||
| import { createIndexingConfig } from "@ensnode/ensnode-sdk"; | ||
| import { | ||
| type BlockRef, | ||
| type BlockrangeWithStartBlock, | ||
| type ChainId, | ||
| ChainIndexingStates, | ||
| type PonderIndexingMetrics, | ||
| } from "@ensnode/ponder-sdk"; | ||
|
|
||
| import type { ChainIndexingMetadataImmutable } from "@/lib/indexing-status-builder/chain-indexing-metadata"; | ||
|
|
||
| import { fetchBlockRef } from "./fetch-block-ref"; | ||
|
|
||
| /** | ||
| * Build an immutable indexing metadata for a chain. | ||
| * | ||
| * Some of the metadata fields are based on RPC calls to fetch block references. | ||
| * | ||
| * @param startBlock Chain's start block. | ||
| * @param endBlock Chain's end block (optional). | ||
| * @param backfillEndBlock Chain's backfill end block. | ||
| * | ||
| * @returns The immutable indexing metadata for the chain. | ||
| */ | ||
| function buildChainIndexingMetadataImmutable( | ||
| startBlock: BlockRef, | ||
| endBlock: BlockRef | null, | ||
| backfillEndBlock: BlockRef, | ||
| ): ChainIndexingMetadataImmutable { | ||
| const chainIndexingConfig = createIndexingConfig(startBlock, endBlock); | ||
|
|
||
| return { | ||
| backfillScope: { | ||
| startBlock, | ||
| endBlock: backfillEndBlock, | ||
| }, | ||
| indexingConfig: chainIndexingConfig, | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Map of chain ID to its immutable indexing metadata. | ||
| */ | ||
| export type ChainsIndexingMetadataImmutable = Map<ChainId, ChainIndexingMetadataImmutable>; | ||
|
|
||
| /** | ||
| * Build a map of chain ID to its immutable indexing metadata. | ||
| * | ||
| * @param indexedChainIds Set of indexed chain IDs. | ||
| * @param chainsConfigBlockrange Map of chain ID to its configured blockrange. | ||
| * @param publicClients Map of chain ID to its cached public client. | ||
| * @param ponderIndexingMetrics Ponder indexing metrics for each chain. | ||
| * @returns A map of chain ID to its immutable indexing metadata. | ||
| * @throws Error if any of the required data cannot be fetched or is invalid, | ||
| * or if invariants are violated. | ||
| */ | ||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| export async function buildChainsIndexingMetadataImmutable( | ||
| indexedChainIds: Set<ChainId>, | ||
| chainsConfigBlockrange: Map<ChainId, BlockrangeWithStartBlock>, | ||
| publicClients: Map<ChainId, PublicClient>, | ||
| ponderIndexingMetrics: PonderIndexingMetrics, | ||
| ): Promise<Map<ChainId, ChainIndexingMetadataImmutable>> { | ||
| const chainsIndexingMetadataImmutable = new Map<ChainId, ChainIndexingMetadataImmutable>(); | ||
|
|
||
| for (const chainId of indexedChainIds) { | ||
| const chainConfigBlockrange = chainsConfigBlockrange.get(chainId); | ||
| const chainIndexingMetrics = ponderIndexingMetrics.chains.get(chainId); | ||
| const publicClient = publicClients.get(chainId); | ||
|
|
||
| // Invariants: chain config blockrange, indexing metrics, and public client | ||
| // must exist in proper state for the indexed chain. | ||
| if (!chainConfigBlockrange) { | ||
| throw new Error(`Chain config blockrange must be available for indexed chain ID ${chainId}`); | ||
| } | ||
|
|
||
| if (!chainIndexingMetrics) { | ||
| throw new Error(`Indexing metrics must be available for indexed chain ID ${chainId}`); | ||
| } | ||
|
|
||
| if (chainIndexingMetrics.state !== ChainIndexingStates.Historical) { | ||
| throw new Error( | ||
| `Chain indexing state must be "historical" for indexed chain ID ${chainId}, but got "${chainIndexingMetrics.state}"`, | ||
| ); | ||
| } | ||
|
|
||
| if (!publicClient) { | ||
| throw new Error(`Public client must be available for indexed chain ID ${chainId}`); | ||
| } | ||
|
|
||
| const backfillEndBlockNumber = | ||
| chainConfigBlockrange.startBlock + chainIndexingMetrics.historicalTotalBlocks - 1; | ||
|
|
||
| // Fetch required block references in parallel. | ||
| const [startBlock, endBlock, backfillEndBlock] = await Promise.all([ | ||
| fetchBlockRef(publicClient, chainConfigBlockrange.startBlock), | ||
| chainConfigBlockrange.endBlock | ||
| ? fetchBlockRef(publicClient, chainConfigBlockrange.endBlock) | ||
| : null, | ||
| fetchBlockRef(publicClient, backfillEndBlockNumber), | ||
| ]); | ||
|
|
||
| const metadataImmutable = buildChainIndexingMetadataImmutable( | ||
| startBlock, | ||
| endBlock, | ||
| backfillEndBlock, | ||
| ); | ||
|
|
||
| chainsIndexingMetadataImmutable.set(chainId, metadataImmutable); | ||
| } | ||
|
|
||
| return chainsIndexingMetadataImmutable; | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.