feat: network endpoints overridable from remote config - #632
Open
n13 wants to merge 2 commits into
Open
Conversation
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
commented
Sep 5, 2026
n13
left a comment
Collaborator
Author
There was a problem hiding this comment.
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 anawait. 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 throwsConcurrentModificationErrorinstead of failing over. This is especially unsafe for a cross-network move becauseclearChainCaches()runs before those requests settle: an in-flightgetRuntimeVersion()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_sdknon-native suite: 481 passed.mobile-appsuite: 427 passed.- Local analysis passed for SDK, cold-wallet, and miner; mobile analysis was stopped at the required 10-second ceiling. GitHub's
Analyzecheck is green on this head.
No other blocking findings found.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
endpointsblock:Absent keys keep the
AppConstantsdefaults. A malformed block (empty list, non-URL, wrong scheme) throws aFormatException, 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 fromAppConstants, validatedfromJson, value equality.NetworkEndpointsService(quantus_sdk): holds the endpoints in effect.apply()pushes the RPC and GraphQL lists intoRpcEndpointService/GraphQlEndpointService(newsetEndpoints, keeps measured latency for URLs that stay) and clearsSubstrateServicechain caches, since genesis hash and runtime version belong to one chain.RemoteConfigNotifierapplies the cached endpoints in its constructor (before any widget can hit the network) and again when a remote sync changes them.SenotiServiceand the three explorer-link builders read fromNetworkEndpointsService().currentinstead ofAppConstants.RemoteConfigModeldrops thematch/compareboilerplate for value equality viaDeepCollectionEquality, soremote != statein 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:
AppConstantsdefaults on first run or when the cache is unreadable). The remote fetch runs unawaited in the background.endpoints) is logged and yields no remote config; the current flags and endpoints stay in effect. Nothing is surfaced to the user.mobile-app/test/unit/remote_config_service_test.dart: an unreachable server leavesRemoteConfigModel.defaultsand 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.websiteBaseUrl,techSupportUrl,termsOfServiceUrl, quest pages,communityUrl,faucetUrl,miningSetupGuideUrl,shareUrl, Keystone store URL)..env.MinerConfig.availableChains(dev/dirac/planck RPC + subsquid): the miner has its own chain picker and does not use remote config.wss://a1-planck.quantus.catinquantus_sdk/pubspec.yaml,scripts/debug_subsquid.sh,test_qr_payload.sh.Test plan
flutter analyze --fatal-infosclean on quantus_sdk, mobile-app, cold-wallet-app, miner-app.flutter test --exclude-tags=nativein quantus_sdk: 481 passed (new:network_endpoints_test,remote_config_model_test,network_endpoints_service_test).flutter testin mobile-app: 424 passed.endpoints.rpcand confirm the log lineSwitching network endpoints to …and that subsequent RPC calls hit the new host.