Skip to content

fix(server): resolve the dotNS controller whether the gateway stores a dispatcher or the controller - #564

Open
re-gius wants to merge 3 commits into
mainfrom
re-gius/dotns-controller-discovery-fallback
Open

fix(server): resolve the dotNS controller whether the gateway stores a dispatcher or the controller#564
re-gius wants to merge 3 commits into
mainfrom
re-gius/dotns-controller-discovery-fallback

Conversation

@re-gius

@re-gius re-gius commented Sep 1, 2026

Copy link
Copy Markdown

Description

This is a transitional step, deliberately shaped to avoid breaking changes after paritytech/dotns#258 .

DotnsGateway.DispatcherAddress is about to change meaning. Today it holds a RootGatewayDispatcher, and discover_pop_controller calls TARGET() on it to reach the DotnsPopController. paritytech/dotns#258 moves the substrate Root check into the controller itself, after which the dispatcher has no purpose and the gateway pallet gets repointed at the controller proxy via set_dispatcher_address. From that moment the stored address has no TARGET(), discovery errors, and host username resolution stops working.

Switching outright to "the stored address is the controller" would have to land in the same instant as that extrinsic, on every chain at once. Instead discovery now works out which contract it is looking at, so one binary serves both states.

TARGET() exists only on the dispatcher and protocolRegistry() only on the controller, so a revert from one identifies the other. A revert is an answer about the contract; a node failure is not an answer at all.

TARGET() then protocolRegistry() Discovery concludes
returns an address not needed it is a dispatcher — use the address it returned (every chain today)
reverts returns an address it is the controller — use the stored address (after the repoint)
reverts reverts it is neither — error naming the address
the node fails to answer or fails here no conclusion — return the transport error, never an address

Not a breaking change. On every chain as it stands the stored address is still a dispatcher, TARGET() still answers, and the resolved controller is identical so the new branch is never reached. No configuration changes are needed, and HOST_CLI_DOTNS_POP_CONTROLLER still overrides discovery unchanged. Nothing here waits on dotns#258 either.

Temporary by design. Once every chain stores the controller, the TARGET() hop and the protocolRegistry() confirmation both become dead weight and discovery collapses to reading the address and using it. That cleanup is a follow-up, not part of this PR, because until then the dispatcher path is the live one on previewnet and paseo-next-v2. The cost of carrying both in the meantime is one extra reverting view per resolution after a repoint,.

The identity.rs test mock still scripts the dispatcher shape (:341, :440), matching today's chains; the new unit tests cover the repointed shape. Point it at a controller later and it panics unscripted view rather than returning a wrong address.

@re-gius
re-gius requested review from a team, TarikGul and pgherveou September 1, 2026 13:23
decode_address(&output)
.map(Some)
.map_err(|err| format!("RootGatewayDispatcher.TARGET(): {err}"))
match transport.view(&stored, call_no_args("TARGET()")).await {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CLI caches the controller for the run, but the core doesnt. lookup_dotns_identity builds a fresh DotnsLookup and calls discover_pop_controller on every lookup, so once chains are repointed every username resolution pays the reverting TARGET() first.

Each step carries OPERATION_TIMEOUT (10s) inside a 45s LOOKUP_BUDGET, so on a slow node that revert can eat a fifth of the budget for no information. Worth caching the controller in the core, or flipping the probe order when the repoint lands?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With e8ea25a we have protocolRegistry() first now, so we moved the delay to the outdated path. Caching would be ideal, but this is just a temporary change and we should move to the controller-only path by the end of the week

/// The stored address is either a `RootGatewayDispatcher`, whose `TARGET()` is the
/// controller, or the controller itself. Both are in service: a chain keeps its
/// dispatcher until the gateway pallet is repointed. `TARGET()` decides which, and a
/// revert means the address is not a dispatcher, since the contract executed and had no

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whole scheme depends on TARGET() existing only on the dispatcher. Everywhere else here fails closed, but this one is different: if the controller ever gains a TARGET(), discovery takes the dispatcher branch and decode_address hands back whatever it returned. Thats a wrong address, not an error.

Does dotns#258 guarantee the controller has no TARGET(), and is that guaranteed to hold after it, or is it just true today?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was just true today and we cannot say anything about the controller's future form, so I fixed it in e8ea25a by inverting the probe: protocolRegistry() goes first now, so discovery keys on a function the controller has rather than one it lacks.

@re-gius
re-gius requested a review from TarikGul September 1, 2026 15:01
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.

2 participants