Skip to content

feat: load extra chain definitions and method specs at startup#193

Open
robbeverhelst wants to merge 2 commits into
drpcorg:mainfrom
settlemint:feat/external-chains-and-specs
Open

feat: load extra chain definitions and method specs at startup#193
robbeverhelst wants to merge 2 commits into
drpcorg:mainfrom
settlemint:feat/external-chains-and-specs

Conversation

@robbeverhelst
Copy link
Copy Markdown

Closes #186

What

Two opt-in env vars that let deployers extend nodecore's embedded chain
registry and method specs at startup, without forking the image:

Env var Purpose
NODECORE_EXTRA_CHAINS_PATH Path to a YAML file using the same schema as pkg/chains/public/chains.yaml. Merged on top of the embedded registry.
NODECORE_EXTRA_SPECS_PATH Directory of JSON method-spec files (same schema as pkg/methods/specs/*.json). Wires up the already-exported NewMethodSpecLoaderWithFs constructor.

If both vars are unset, behaviour is identical to today.

Why

The motivating use case is documented in #186: running nodecore in
front of EVM-compatible upstreams that aren't in drpcorg/public and
never will be — customer-private Besu / consortium networks with their
own chain ids.

The existing per-upstream disable-chain-validation flag isn't enough
because eth_chainId and net_version are marked "settings": {"local": true}
in eth.json and answered locally from the chain registry. With the
chain spoofed as ethereum, those return 0x1 instead of the real
chain id, which breaks EIP-155 signing in every modern client
(ethers / viem / web3.js / hardhat / foundry).

With this PR, a deployer drops a small extra-chains.yaml containing
their private chain entry, sets NODECORE_EXTRA_CHAINS_PATH, and the
registry returns the correct chain-id end-to-end.

How

pkg/chains/chains.go

  • Renamed configureChainsconfigureChainsFromBytes(raw []byte) so the
    same code path handles the embedded YAML and any extra YAML.
  • New public LoadExtraChains(yamlBytes []byte) error. Conflict checks:
    • Empty input is a no-op.
    • Duplicate short-name against an already-registered chain → error.
    • Non-zero grpcId collision against an already-registered chain → error.
  • For each extra short-name not in chainsMap, allocate a Chain id
    starting at dynamicChainBaseId (1 << 30) so it doesn't collide
    with the codegen-emitted constants, and register it in
    dynamicChainNames so Chain.String() round-trips correctly.

cmd/chains/init_chains.go (code-generator template)

  • Declares const dynamicChainBaseId Chain = 1 << 30 and
    var dynamicChainNames = map[Chain]string{} in the generated file.
  • Chain.String() falls through to dynamicChainNames[c] for ids in
    the dynamic range.

The generated pkg/chains/chains_data.go is gitignored as today; it's
re-generated next time make chains runs.

cmd/nodecore/main.go

  • Reads NODECORE_EXTRA_CHAINS_PATH after flag.Parse() and before
    config.NewAppConfig(); calls chains.LoadExtraChains if set.
  • Reads NODECORE_EXTRA_SPECS_PATH next and uses
    specs.NewMethodSpecLoaderWithFs(os.DirFS(path)) instead of the
    embedded loader if set.

Docs

New subsection in docs/nodecore/05-upstream-config.md covering both
env vars, the schema, and a minimal Besu example.

Tests

pkg/chains/chains_extra_test.go:

  • happy path (registers a new chain, Chain.String() round-trips,
    eth method-spec inherited)
  • empty input is a no-op
  • duplicate short-name rejected
  • grpcId collision rejected
  • malformed YAML returns an error

All existing tests still pass; go vet and go build ./... clean.

Open questions (carried over from #186)

  1. Env var vs config-file field — I went with env var to mirror how
    internal/config/config.go already does ConfigPathVar. Happy to
    move it under app-config if you prefer.
  2. Merge semantics — strict: extras can only add new chains, not
    override existing ones. Felt like the safe default; I can switch to
    "extras win" or make it configurable if you'd rather.
  3. net-version derivation — extras follow the same fall-through
    as embedded entries (explicit net-version else big-int decimal of
    chain-id). Worth calling out in case private chains have weird
    net versions.

Happy to iterate on any of the above.


Recreated from the SettleMint fork because the original fork backing #187 was deleted, which closed that PR.

Adds two opt-in env vars that let deployers extend the embedded
registries without forking, addressing drpcorg#186:

- NODECORE_EXTRA_CHAINS_PATH: path to a YAML file using the same schema
  as pkg/chains/public/chains.yaml. Its entries are merged on top of
  the embedded registry. Each extra short-name not already registered
  is given a dynamically allocated Chain id (>= 1<<30) so Chain.String()
  round-trips correctly and eth_chainId / net_version answer with the
  configured chain-id instead of falling back to the spoofed chain.

- NODECORE_EXTRA_SPECS_PATH: directory of JSON method-spec files. Wires
  up the already-exported NewMethodSpecLoaderWithFs constructor.

Refactor pkg/chains:

- configureChains -> configureChainsFromBytes(rawYaml) so the same code
  path handles the embedded and the extra YAML.
- LoadExtraChains(yamlBytes) public entry-point with conflict checks:
  rejects duplicate short-names or grpcId collisions with chains that
  are already registered.
- Generated chains_data.go gains dynamicChainNames + a Chain.String()
  fallback so dynamically allocated ids resolve to their canonical
  short-name (updated cmd/chains/init_chains.go template).

Docs: new section in docs/nodecore/05-upstream-config.md covering the
two env vars with a minimal Besu-style example.

Tests: pkg/chains/chains_extra_test.go covers the happy path, empty
input, duplicate short-name rejection, grpcId collision rejection,
and malformed YAML.
Guard against accidental misuse:

- sync.Mutex around LoadExtraChains so concurrent callers do not race on the package-level chain registry maps.
- A boolean flag flipped on success only. Failed validation leaves the flag false so the caller can retry with corrected input.
- Second successful load is rejected with a clear error instead of silently mutating the registry while consumers may already be reading from it.

Tests reordered so validation cases run before the happy-path case, plus TestLoadExtraChains_SecondCallIsRejected exercises the lock.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: allow loading external chain definitions at startup (e.g. NODECORE_EXTRA_CHAINS_PATH)

1 participant