-
Notifications
You must be signed in to change notification settings - Fork 2
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
feat(ponder-ens): introduce plugin architecture #3
Conversation
This change allows defining simple ponder plugins per supported ENS blockchain which can be loaded conditionally in the runtime. Requesting a chain to be indexed requires passing a chain ID in the `CHAINS_TO_INDEX` env var and comma-separated chain ID values.
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.
@tk-o Hey thanks for all the efforts on this 🙌 Excited to see the "plugins" start to come to life! Will defer to @shrugs on more detailed review for the proposed plugin architecture.
A more general feedback from my side. For each PR we make, let's please also consider what documentation updates might be relevant and include them in the PR. Appreciate this takes time, but good documentation for other devs in the ENS community will be key to our success.
For example, ideas that would be great to include as documentation in the README.md with this PR might include:
- Overall introduction and framing for what we're working to do here. For example: enable any permutation of chain-specific ENS indexers to be dynamically activated via environment variables.
- Communicate known oversimplifications in our current approach and identify that our roadmap will remove these oversimplifications in time. For example:
- At the moment we're making oversimplifications where indexing "mainnet" represents indexing the legacy ENS Subgraph, indexing "base" represents only indexing subnames of "base.eth" on Base, and indexing "linea" represents only indexing subnames of "linea.eth" on Linea. Etc.
- At the moment we're making oversimplifications where we assume that all the delegation relationships of subname issuance from an L1 resolver (using ENSIP-10 and EIP-3668) to an offchain gateway server that reads state from a L2 subname registry is immutable. In other words, we're currently assuming that the current L2 subname registry for subnames of "base.eth" on Base is guaranteed to continue serving in that role.
- Perhaps an .env.example file that includes comments documenting the environment variables we use and different example values that might be set. Am I correct to assume that there's also some ponder-related environment variables that we should be documenting in an .env.example file too? For example: RPC endpoints for different chains? Let's write our documentation under the assumption that the reader has never used Ponder before and should not need to familiarize themselves with Ponder if they just want to get started and run their own instance.
- A "getting started" section that explains step-by-step how to go from cloning the repo to having a running instance of our indexer.
- Any other ideas you suggest will be useful 👍 😄
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.
@tk-o Hey nice updates! I like the direction you're moving in 🚀 Reviewed and shared feedback 👍
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.
an abi diff between mainnet and base would be helpful for identifying any logic discrepancies
please configure your editor to use biome to format files — the columnWidth being changed from 80 to 100 is causing a lot of formatting-related changes
Refactor/plugin logic
This event is emitted by the `BaseRegistrar` during a registration from Bases RegistrarControllers
This one includes a fix for duplicated log entries error
Make the root domain name based of the env var
8c25e59
to
75cd66b
Compare
renames NAMEHASH to NODE
75cd66b
to
0e35dd8
Compare
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.
@tk-o Hey great to see the updates here. 👍 Reviewed and shared feedback 😄
export const ns = createNs(nestedNamespace); | ||
export type NsType<T extends string> = NsReturnType<T, typeof nestedNamespace>; | ||
|
||
const START_BLOCK = undefined; // 17607350; |
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.
Can we please document the idea here? Why setting these to undefined?
i much prefer the |
The generic handlers have now the inlined args types to avoid importing subname-specifc event types. Also, we now use the `INDEX_SUBNAME` env var to specify the subname to index, i.e. `eth` or `base.eth` (note: no starting dot character).
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.
excellent documentation & typescript type work! i only have nitpicks in here, i think, so it's looking very close to merge
src/handlers/Registrar.ts
Outdated
/** | ||
* A factory function that returns Ponder indexing handlers for a specified index name/subname. | ||
*/ | ||
export const makeRegistryHandlers = (indexedSubname: `${string}eth`) => { |
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.
now that i understand the nomenclature better, let's call this indexedSubname
variable managedSubname
(and managedSubnameNode
) so that people know that these Registrar handlers are in the context of a registrar that manages the provided subname
src/handlers/Registrar.ts
Outdated
} | ||
|
||
await context.db.update(registrations, { id: label }).set({ labelName: name, cost }); | ||
} | ||
|
||
return { | ||
get baseNameNode() { | ||
return baseNameNode; | ||
get indexedSubnameNode() { |
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.
why provide this back, it will be known to the caller because they provided it as an input agument
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.
We need this value in the caller for some inline indexing handlers:
https://github.com/namehash/ens-multichain-indexer/blob/aad651cf532f1d89b214695994bb2d8e2574b7e3/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts#L8-L17
https://github.com/namehash/ens-multichain-indexer/blob/aad651cf532f1d89b214695994bb2d8e2574b7e3/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts#L31
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.
ah, hmm. i don't love this but it's ok for the moment 👍 we'll refactor this in v2
@@ -114,12 +119,16 @@ export const makeRegistryHandlers = (baseName: `.${`${string}`}eth`) => { | |||
args: { tokenId, from, to }, | |||
}: { | |||
context: Context; | |||
args: Event<NsType<"BaseRegistrar:Transfer">>["args"]; | |||
args: { |
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.
and i think for each of these functions in Registrar.ts
we should standardize the api to be like a ponder handler: it always accepts context and event, and args
is within the event. we need not do this in this pr, i can manage it once we've merged if you'd like
src/handlers/Registry.ts
Outdated
/** | ||
* Initialize the ENS root node with the zeroAddress as the owner. | ||
*/ | ||
export async function handleRootNodeCreation({ context }: { context: Context }) { |
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.
agreed with lightwalker's naming concern, and apologies for the nitpick, but how about calling this setupRootNode
src/lib/ponder-plugin-utils.ts
Outdated
|
||
type PonderNsPath<T extends PonderNsPath = "/"> = `` | `/${string}` | `/${string}${T}`; | ||
|
||
type EthSubname = `${string}eth`; |
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.
very nice type work in this file 🫡
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.
@tk-o Yeah nice work here! 💪
When referring to a variable holding any of: `eth`, `base.eth`, `linea.eth`
looks like biome got misconfigured or something — some line width formatting related changes in the recent commits |
Allows to automatically catch code-related issues on the CI
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.
@tk-o Thanks for your updates here! Very happy to see docs improving. Let's continue to push hard on making our docs and use of language properly aligned.
Reviewed and shared feedback 👍
.env.local.example
Outdated
PONDER_RPC_URL_8453=https://base-rpc.publicnode.com | ||
PONDER_RPC_URL_59144=https://linea-rpc.publicnode.com | ||
|
||
# ponder indexer ens deployment configuration |
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.
@tk-o This is still an open feedback
.env.local.example
Outdated
# ponder indexer database configuration | ||
|
||
## one schema name per chain ID, i.e. ponder_ens_${INDEX_BASENAME} | ||
DATABASE_SCHEMA=ponder_ens_base.eth |
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.
@tk-o This remains an open feedback item.
I'm also concerned to read "chain id" here. I'm very worried we're getting key ideas confused. In my understanding, "chain id" should not be THE key consideration in our configurations. The INDEX_SUBNAME
should be THE number 1 consideration, and then everything else should flow downstream from that.
For example, the chain to index is determined by the subnames to be indexed. Not the other way around.
import { BaseRegistrar } from "./abis/BaseRegistrar"; | ||
import { EarlyAccessRegistrarController } from "./abis/EARegistrarController"; | ||
import { L2Resolver } from "./abis/L2Resolver"; | ||
import { RegistrarController } from "./abis/RegistrarController"; | ||
import { Registry } from "./abis/Registry"; | ||
|
||
export const baseName = ".base.eth" as const; | ||
export const managedSubname = "base.eth" as const; |
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.
Could you help me understand the use of the word "managed" here? Is there a special reason to use that and not "indexed"?
What does it mean to be "managed"?
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.
@shrugs could you share your view here?
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.
@lightwalker-eth it got lost in the github comments but: the registrar handlers are in the context of the subname that the registrar 'manages' — the ETH Registrar manages .eth, base's l2 registrar deployment manages .base.eth subnames, etc. perhaps there's a better word but i don't like 'indexed' in this case, since the argument is specifying which parent node all of the events in this registrar are relative to
|
||
const START_BLOCK = undefined; // 17607350; | ||
const END_BLOCK = undefined; // 17607351; | ||
export const ponderNamespace = createPonderNamespace(managedSubname); |
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.
Is there a special reason for us to put lots of emphasis on "ponder" when we name things?
It's true that we're building on the Ponder framework, but I feel attracted to more general naming of these ideas. For example:
- "ponderNamespace" -> "pluginNamespace"
- "createPonderNamespace" -> "createPluginNamespace"
Very open to other suggestions. Perhaps I'm missing something?
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.
I might suggest contractNamespace
@@ -12,11 +12,9 @@ import { Resolver } from "./abis/Resolver"; | |||
|
|||
const RESOLVER_ABI = mergeAbis([LegacyPublicResolver, Resolver]); | |||
|
|||
export const baseName = ".eth" as const; | |||
export const managedSubname = "eth"; |
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.
Please see my other comment about the use of the word "managed" here.
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.
cc @shrugs
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.
I've reverted back to indexedSubname
src/lib/ponder-plugin-utils.ts
Outdated
export function createPonderNamespace<Subname extends EthSubname>(subname: Subname) { | ||
const path = transformDomain(subname) satisfies PonderNsPath; | ||
|
||
/** Creates a name-spaced contract name */ |
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.
/** Creates a name-spaced contract name */ | |
/** Creates a namespaced contract name */ |
namespace / namespaced is one word, no hyphen.
Please search for other instances of "name-spaced" and replace them. Thanks.
src/lib/ponder-plugin-utils.ts
Outdated
* | ||
* However, in some cases, we may want to create a name-spaced contract name to distinguish between contracts having the same name, but handling different implementations. | ||
* | ||
* Let's say we have two contracts named `Registry`. One handles the `eth` name, and the other handles the `base.eth` subname. We need to create a name-spaced contract name to avoid conflicts. |
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.
* Let's say we have two contracts named `Registry`. One handles the `eth` name, and the other handles the `base.eth` subname. We need to create a name-spaced contract name to avoid conflicts. | |
* Let's say we have two contracts named `Registry`. One handles `eth` subnames, and the other handles `base.eth` subnames. We need to create a namespaced contract name to avoid conflicts. |
src/lib/ponder-plugin-utils.ts
Outdated
@@ -0,0 +1,64 @@ | |||
/** |
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.
@tk-o Great job on the docs here! 🚀 🚀 This makes a big impact for our goals! Docs like this do a lot to help encourage other devs to adopt our indexer because they can more easily make sense of what we're doing and why 😄
One small suggestion is to break to a new line around approximately 80ish characters. This helps with line wrapping when viewing code on platforms such as GitHub.
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.
What do you think about renaming the ponder-ens-plugins
directory to just plugins
? Seems more clean 😄
Assuming we do rename, please be sure to do it in the way that it's properly tracked by git as just renames, and not a bunch of file deletes / creations.
@@ -0,0 +1,31 @@ | |||
name: Static Analysis |
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.
Great to see this!
Parser is not required, as we do the validation in the ponder.config.ts and throw an error if the provided value was not expected.
This allows to define a name managed by the given plugin.
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.
@tk-o Hey super awesome updates! 🚀 🚀 🚀
This change allows defining simple ponder plugins per supported subnames. Only one plugin can be active during indexing, and to select it, please set the
ACTIVE_PLUGIN
env var to the name of one of the available plugins (seesrc/plugins
directory). For example:Also, please make sure to know how to use the
DATABASE_SCHEMA
env var.Examples
Running indexer with the requested subname plugin for ponder: