-
Notifications
You must be signed in to change notification settings - Fork 15
Integration of Indexing Status Builder into ENSIndexer API #1614
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
base: main
Are you sure you want to change the base?
Changes from all commits
9bc4fc5
cb6057d
28f9d31
9986ac8
008c0fa
3b15c47
63275b6
9bad342
afe61a4
34d7920
e9db330
01d336a
8c3addd
d9d7976
91e743a
3d1a32c
f52301e
7234c81
ebf55d0
6b00f45
42b48d2
14ace99
b8b2f19
c51d99e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,185 @@ | ||||||||||||||||||||
| /** | ||||||||||||||||||||
| * This file is about parsing the object that is exported by `ponder.config.ts`. | ||||||||||||||||||||
| * | ||||||||||||||||||||
| * Each Ponder datasource defined in the aforementioned Ponder Config object | ||||||||||||||||||||
| * can include information about startBlock and endBlock. This is to let | ||||||||||||||||||||
| * Ponder know which blockrange to index for a particular Ponder Datasource. | ||||||||||||||||||||
| * | ||||||||||||||||||||
| * ENSIndexer, however, needs a blockrange for each indexed chain. This is why | ||||||||||||||||||||
| * we examine Ponder Config object, looking for the "lowest" startBlock, and | ||||||||||||||||||||
| * the "highest" endBlock defined for each of the indexed chains. | ||||||||||||||||||||
| */ | ||||||||||||||||||||
|
|
||||||||||||||||||||
| import type { AddressConfig, ChainConfig, CreateConfigReturnType } from "ponder"; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| import { | ||||||||||||||||||||
| deserializeBlockNumber, | ||||||||||||||||||||
| deserializeBlockrange, | ||||||||||||||||||||
| deserializeChainId, | ||||||||||||||||||||
| } from "@ensnode/ensnode-sdk"; | ||||||||||||||||||||
| import type { | ||||||||||||||||||||
| BlockNumber, | ||||||||||||||||||||
| Blockrange, | ||||||||||||||||||||
| BlockrangeWithStartBlock, | ||||||||||||||||||||
| ChainId, | ||||||||||||||||||||
| ChainIdString, | ||||||||||||||||||||
| } from "@ensnode/ponder-sdk"; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
vercel[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||
| * Ponder config datasource with a flat `chain` value. | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| export type PonderConfigDatasourceFlat = { | ||||||||||||||||||||
| chain: ChainIdString; | ||||||||||||||||||||
| } & AddressConfig & | ||||||||||||||||||||
| Blockrange; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
| * Ponder config datasource with a nested `chain` value. | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| export type PonderConfigDatasourceNested = { | ||||||||||||||||||||
| chain: Record<ChainIdString, AddressConfig & Blockrange>; | ||||||||||||||||||||
| }; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
| * Ponder config datasource | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| export type PonderConfigDatasource = PonderConfigDatasourceFlat | PonderConfigDatasourceNested; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
| * Ponder config datasource | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| type PonderConfigDatasources = { | ||||||||||||||||||||
| [datasourceId: string]: PonderConfigDatasource; | ||||||||||||||||||||
| }; | ||||||||||||||||||||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
| * Ponder chains config | ||||||||||||||||||||
| * | ||||||||||||||||||||
| * Chain config for each indexed chain. | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| type PonderConfigChains = { | ||||||||||||||||||||
| [chainId: ChainIdString]: ChainConfig; | ||||||||||||||||||||
| }; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
| * Ponder Config | ||||||||||||||||||||
| * | ||||||||||||||||||||
| * A utility type describing Ponder Config. | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| export type PonderConfigType = CreateConfigReturnType< | ||||||||||||||||||||
| PonderConfigChains, | ||||||||||||||||||||
| PonderConfigDatasources, | ||||||||||||||||||||
| PonderConfigDatasources, | ||||||||||||||||||||
| PonderConfigDatasources | ||||||||||||||||||||
| >; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
| * Ensure the `ponderDatasource` is {@link PonderConfigDatasourceFlat}. | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| function isPonderDatasourceFlat( | ||||||||||||||||||||
| ponderDatasource: PonderConfigDatasource, | ||||||||||||||||||||
| ): ponderDatasource is PonderConfigDatasourceFlat { | ||||||||||||||||||||
| return typeof ponderDatasource.chain === "string"; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
| * Ensure the `ponderDatasource` is {@link PonderConfigDatasourceNested}. | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| function isPonderDatasourceNested( | ||||||||||||||||||||
| ponderDatasource: PonderConfigDatasource, | ||||||||||||||||||||
| ): ponderDatasource is PonderConfigDatasourceNested { | ||||||||||||||||||||
| return typeof ponderDatasource.chain === "object"; | ||||||||||||||||||||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||
| } | ||||||||||||||||||||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
| * Build {@link Blockrange} for each indexed chain. | ||||||||||||||||||||
| * | ||||||||||||||||||||
| * Invariants: | ||||||||||||||||||||
| * - every chain include a startBlock, | ||||||||||||||||||||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||
| * - some chains may include an endBlock, | ||||||||||||||||||||
| * - all present startBlock and endBlock values are valid {@link BlockNumber} values. | ||||||||||||||||||||
| * | ||||||||||||||||||||
| * @throws Error if any of the above invariants are violated. | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| export function buildChainsBlockrange( | ||||||||||||||||||||
| ponderConfig: PonderConfigType, | ||||||||||||||||||||
| ): Map<ChainId, BlockrangeWithStartBlock> { | ||||||||||||||||||||
| const chainsBlockrange = new Map<ChainId, BlockrangeWithStartBlock>(); | ||||||||||||||||||||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| // 0. Get all ponder sources (includes chain + startBlock & endBlock) | ||||||||||||||||||||
| const ponderSources = [ | ||||||||||||||||||||
| ...Object.values(ponderConfig.accounts ?? {}), | ||||||||||||||||||||
| ...Object.values(ponderConfig.blocks ?? {}), | ||||||||||||||||||||
| ...Object.values(ponderConfig.contracts ?? {}), | ||||||||||||||||||||
| ] as PonderConfigDatasource[]; | ||||||||||||||||||||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| // 1. For every indexed chain | ||||||||||||||||||||
| for (const serializedChainId of Object.keys(ponderConfig.chains)) { | ||||||||||||||||||||
| const chainStartBlocks: BlockNumber[] = []; | ||||||||||||||||||||
| const chainEndBlocks: BlockNumber[] = []; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // 1.1. For every Ponder source (accounts, blocks, contracts), | ||||||||||||||||||||
| // extract startBlock number (required) and endBlock number (optional). | ||||||||||||||||||||
| for (const ponderSource of ponderSources) { | ||||||||||||||||||||
| let startBlock: Blockrange["startBlock"]; | ||||||||||||||||||||
| let endBlock: Blockrange["endBlock"]; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if (isPonderDatasourceFlat(ponderSource) && ponderSource.chain === serializedChainId) { | ||||||||||||||||||||
| startBlock = ponderSource.startBlock; | ||||||||||||||||||||
| endBlock = ponderSource.endBlock; | ||||||||||||||||||||
| } else if (isPonderDatasourceNested(ponderSource) && ponderSource.chain[serializedChainId]) { | ||||||||||||||||||||
| startBlock = ponderSource.chain[serializedChainId].startBlock; | ||||||||||||||||||||
| endBlock = ponderSource.chain[serializedChainId].endBlock; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if (typeof startBlock === "number") { | ||||||||||||||||||||
| chainStartBlocks.push(deserializeBlockNumber(startBlock)); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if (typeof endBlock === "number") { | ||||||||||||||||||||
| chainEndBlocks.push(deserializeBlockNumber(endBlock)); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // 2. Get the lowest startBlock for the chain. | ||||||||||||||||||||
| const chainLowestStartBlock = | ||||||||||||||||||||
| chainStartBlocks.length > 0 ? Math.min(...chainStartBlocks) : undefined; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // 3.a) The endBlock can only be set for a chain if and only if every | ||||||||||||||||||||
| // ponderSource for that chain has its respective `endBlock` defined. | ||||||||||||||||||||
| const isEndBlockForChainAllowed = chainEndBlocks.length === chainStartBlocks.length; | ||||||||||||||||||||
|
|
||||||||||||||||||||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||
| // 3.b) Get the highest endBLock for the chain. | ||||||||||||||||||||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Typo: "endBLock" → "endBlock". - // 3.b) Get the highest endBLock for the chain.
+ // 3.b) Get the highest endBlock for the chain.📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||
| // 3.b) Get the highest endBLock for the chain. | |
| // 3.b) Get the highest endBlock for the chain. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
The second startBlock guard at lines 175–179 is unreachable at runtime.
After the throw at lines 161–165, chainLowestStartBlock is a valid BlockNumber, so deserializeBlockrange will either throw on schema failure or return with startBlock present. The if (typeof blockrange.startBlock === "undefined") branch can never execute.
The guard exists only to satisfy TypeScript's inferred return type of deserializeBlockrange (Blockrange with optional startBlock) before the cast on line 181. Consider using an assertion comment or a type assertion directly to make the intent clearer without the misleading dead throw:
♻️ Proposed simplification
- // Invariant: the blockrange must include a valid startBlock number.
- if (typeof blockrange.startBlock === "undefined") {
- throw new Error(
- `Invalid blockrange for chain '${serializedChainId}'. The blockrange must include a valid startBlock number.`,
- );
- }
-
- chainsBlockrange.set(chainId, blockrange as BlockrangeWithStartBlock);
+ // startBlock is guaranteed non-undefined: chainLowestStartBlock was validated above.
+ chainsBlockrange.set(chainId, blockrange as BlockrangeWithStartBlock);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (typeof blockrange.startBlock === "undefined") { | |
| throw new Error( | |
| `Invalid blockrange for chain '${serializedChainId}'. The blockrange must include a valid startBlock number.`, | |
| ); | |
| } | |
| chainsBlockrange.set(chainId, blockrange as BlockrangeWithStartBlock); | |
| // startBlock is guaranteed non-undefined: chainLowestStartBlock was validated above. | |
| chainsBlockrange.set(chainId, blockrange as BlockrangeWithStartBlock); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/ensindexer/ponder/src/api/lib/chains-config-blockrange.ts` around lines
175 - 181, The second guard checking typeof blockrange.startBlock ===
"undefined" is unreachable; remove that throw and instead assert the type of the
returned value from deserializeBlockrange so TypeScript knows startBlock is
present. Replace the runtime guard with a type assertion/cast to
BlockrangeWithStartBlock (or add an inline assertion comment) when calling
chainsBlockrange.set(chainId, ...) after
deserializeBlockrange(chainLowestStartBlock, ...), referencing
deserializeBlockrange, blockrange, chainLowestStartBlock,
BlockrangeWithStartBlock, chainsBlockrange.set and serializedChainId to locate
the code.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import type { PublicClient } from "viem"; | ||
|
|
||
| import { bigIntToNumber, deserializeBlockRef } from "@ensnode/ensnode-sdk"; | ||
| import type { BlockNumber, BlockRef } from "@ensnode/ponder-sdk"; | ||
|
|
||
| /** | ||
| * Fetch block reference (number and timestamp) for a given block number on | ||
| * a given chain, using the provided public client. | ||
| * | ||
| * @returns Requested block reference | ||
| * @throws Error if the block cannot be fetched or if the block data is invalid. | ||
| */ | ||
| export async function fetchBlockRef( | ||
| publicClient: PublicClient, | ||
| blockNumber: BlockNumber, | ||
| ): Promise<BlockRef> { | ||
| const chainId = publicClient.chain?.id; | ||
|
|
||
| if (!chainId) { | ||
| throw new Error( | ||
| `Public client is missing chain ID, cannot fetch block ref for block number ${blockNumber}`, | ||
| ); | ||
| } | ||
|
|
||
| try { | ||
| const block = await publicClient.getBlock({ blockNumber: BigInt(blockNumber) }); | ||
|
|
||
| return deserializeBlockRef({ | ||
| timestamp: bigIntToNumber(block.timestamp), | ||
| number: bigIntToNumber(block.number), | ||
| }); | ||
| } catch (error) { | ||
| const errorMessage = error instanceof Error ? error.message : "Unknown error"; | ||
|
|
||
| throw new Error( | ||
| `Error fetching block for chain ID ${chainId} at block number ${blockNumber}: ${errorMessage}`, | ||
| ); | ||
| } | ||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.