Skip to content

feat: network endpoints overridable from remote config - #632

Open
n13 wants to merge 2 commits into
mainfrom
n13/remote-config-endpoints
Open

feat: network endpoints overridable from remote config#632
n13 wants to merge 2 commits into
mainfrom
n13/remote-config-endpoints

Conversation

@n13

@n13 n13 commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Remote config so far only carried feature flags. This lets it also override the URLs that bind the wallet to a network, so the mobile wallet can be moved (e.g. Planck → mainnet) without an app release. Companion server PR: Quantus-Network/quersi#3.

The wallet payload gains an optional endpoints block:

"endpoints": {
  "rpc": ["https://a1-planck.quantus.cat", "https://a2-planck.quantus.cat", "https://matcha-latte.quantus.com"],
  "graphQl": ["https://sub2.quantus.com/v1/graphql"],
  "explorer": "https://explorer.quantus.com",
  "senoti": "https://snt.quantus.com/api"
}

Absent keys keep the AppConstants defaults. A malformed block (empty list, non-URL, wrong scheme) throws a FormatException, so the whole payload is rejected and the last good config stays in effect.

What changes at runtime

  • NetworkEndpoints (quantus_sdk model): the four network-bound URLs, defaults from AppConstants, validated fromJson, value equality.
  • NetworkEndpointsService (quantus_sdk): holds the endpoints in effect. apply() pushes the RPC and GraphQL lists into RpcEndpointService / GraphQlEndpointService (new setEndpoints, keeps measured latency for URLs that stay) and clears SubstrateService chain caches, since genesis hash and runtime version belong to one chain.
  • RemoteConfigNotifier applies the cached endpoints in its constructor (before any widget can hit the network) and again when a remote sync changes them.
  • SenotiService and the three explorer-link builders read from NetworkEndpointsService().current instead of AppConstants.
  • RemoteConfigModel drops the match/compare boilerplate for value equality via DeepCollectionEquality, so remote != state in the notifier is a real comparison and the cache is only rewritten on change.

Bundled polkadart metadata is intentionally untouched: the runtime about to land on Planck is the same one mainnet ships, so only the endpoints differ.

When quersi is unreachable

Remote config never blocks or breaks the wallet:

  • Startup applies the cached config synchronously (last successful fetch; the in-code AppConstants defaults on first run or when the cache is unreadable). The remote fetch runs unawaited in the background.
  • Every quersi request is bounded to 10s, so a black-holed host cannot stall the sync or wedge the refresh guard.
  • Any failure (DNS, timeout, non-200, bad JSON, malformed endpoints) is logged and yields no remote config; the current flags and endpoints stay in effect. Nothing is surfaced to the user.
  • Covered by mobile-app/test/unit/remote_config_service_test.dart: an unreachable server leaves RemoteConfigModel.defaults and the built-in RPC endpoints in place; a reachable one applies its flags and endpoints.

Hard-coded URL inventory

Network-bound, now overridable: rpcEndpoints, graphQlEndpoints, explorerEndpoint, senotiEndpoint.

Left as constants on purpose:

  • quersiEndpoint: the bootstrap URL that fetches the config itself.
  • telemetryUrl (telemetry.quantus.cat): network-bound but only opened as a web link from the mining screen. Easy to add to the block later if wanted.
  • Marketing/support links (websiteBaseUrl, techSupportUrl, termsOfServiceUrl, quest pages, communityUrl, faucetUrl, miningSetupGuideUrl, shareUrl, Keystone store URL).
  • Swap externals (1click.chaindefuser.com tokens, CoinGecko prices and icons), Supabase URL from .env.
  • miner-app MinerConfig.availableChains (dev/dirac/planck RPC + subsquid): the miner has its own chain picker and does not use remote config.
  • Dev tooling: polkadart codegen wss://a1-planck.quantus.cat in quantus_sdk/pubspec.yaml, scripts/debug_subsquid.sh, test_qr_payload.sh.

Test plan

  • flutter analyze --fatal-infos clean on quantus_sdk, mobile-app, cold-wallet-app, miner-app.
  • flutter test --exclude-tags=native in quantus_sdk: 481 passed (new: network_endpoints_test, remote_config_model_test, network_endpoints_service_test).
  • flutter test in mobile-app: 424 passed.
  • Manual: serve a config with a different endpoints.rpc and confirm the log line Switching network endpoints to … and that subsequent RPC calls hit the new host.

Remote config can now move the mobile wallet to another network. The
wallet payload's optional `endpoints` block (rpc, graphQl, explorer,
senoti) replaces the hard-coded AppConstants URLs at runtime; absent
keys keep their defaults and a malformed block rejects the payload so
the last good config stays in effect.

NetworkEndpointsService holds the endpoints in effect and pushes them
into the RPC and GraphQL endpoint services (keeping measured latency for
URLs that stay) and clears the substrate chain caches, since genesis
hash and runtime version belong to one chain. Senoti and the explorer
links read from it instead of AppConstants.

RemoteConfigModel drops the match/compare boilerplate for value
equality, so the notifier only re-caches when the config changed.
Wallets must never depend on quersi being up. The quersi client now
bounds every request to 10s so a black-holed host cannot stall the
background sync, and both quersi calls share one data reader.

RemoteConfigService keeps its quiet failure path (unreachable server or
bad payload -> null, the current config stays in effect) and now also
survives an unreadable cache by resetting to the in-code defaults
instead of throwing out of the provider. Its collaborators are
injectable so the path is covered by tests: an unreachable server
leaves the defaults and the built-in endpoints in place; a reachable one
applies its flags and endpoints.
@n13 n13 added the bot-review Request automated review from review-bot label Sep 4, 2026

@n13 n13 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewer model: GPT Sol

Verdict (advisory): Request changes

Blocking finding:

  • [P1] Make endpoint replacement atomic with in-flight requests (quantus_sdk/lib/src/services/network/redundant_endpoint.dart:141). setEndpoints() clears and repopulates the same mutable list that _executeTask() iterates across an await. Remote config is applied asynchronously while startup/background polling and signing RPCs can already be running. A deterministic suspended-request probe shows both failure modes: a successful request to the old endpoint is returned after the switch, while a failed old request resumes the invalidated iterator and throws ConcurrentModificationError instead of failing over. This is especially unsafe for a cross-network move because clearChainCaches() runs before those requests settle: an in-flight getRuntimeVersion() or _getGenesisHash() can then repopulate the just-cleared cache with old-chain data, and the parallel signing-context reads can mix generations. Replace the endpoint set atomically and track an endpoint generation/snapshot so results from an older generation are discarded or retried; guard chain-cache writes with that generation. Please add a regression test that switches endpoints while a request is suspended.

Validation on exact head b9f19f5ec46093d805372e9e9b4534a40f557e23:

  • git diff --check: passed.
  • Formatting across all four packages: 0 changed files.
  • Added focused endpoint/config tests: 14 passed.
  • quantus_sdk non-native suite: 481 passed.
  • mobile-app suite: 427 passed.
  • Local analysis passed for SDK, cold-wallet, and miner; mobile analysis was stopped at the required 10-second ceiling. GitHub's Analyze check is green on this head.

No other blocking findings found.

@n13 n13 removed the bot-review Request automated review from review-bot label Sep 5, 2026
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.

1 participant