paritytech / paritytech/host-rust-core
Core invocation lifecycle: the core has no mechanism for being called cyclically
Nobody has claimed this yet.
- Dominant language
- Swift
- Stars
- 10
- Forks
- 3
- Avg merge
- 1d 10h
- Merged PRs (30d)
- 148
Description
Placeholder for a design discussion that several subsystems already need and none can proceed without. Not scheduled — this exists so the context is not lost.
The gap
The Rust core has no mechanism for being invoked cyclically. Everything in it acts only when called by a product request. Nothing in truapi-platform provides a timer, a scheduler, or a tick: the trait surface is ProductStorage, Navigation, Notifications, Permissions, CoreAdmin, PairingHostAdmin, Features, ChainProvider, JsonRpcConnection, CoreStorage, AuthPresenter, UserConfirmation, ThemeHost, PreimageHost. None of them is about when.
Two subsystems need this today, for unrelated reasons, which is what makes it platform work rather than either subsystem's problem:
- Statement-allowance renewal (#308). Allowances die at a UTC-day boundary and must be re-registered. #308 already states the rule this design should generalise: "core owns what to renew and how; the host owns only when." Not implemented — there is no
renewal.rsand notickanywhere intruapi-server. - Coinage maintenance (
docs/design/coinage-layer.md§6.4, §8.7, and the deferral at line 915). Two periodic passes, both of which protect against permanent loss of funds:- Recycling sweep — a coin past
MaximumAgecan no longer be transferred or split. Coins approaching it must be converted to recycler entries first, or they become unusable. - Ring-expiration sweep — once a recycler entry's ring becomes immutable and
RecyclerExpirationTime(90 days) passes, the chain destroys the entry's backing value. Entries near that deadline must be unloaded back into coins.
- Recycling sweep — a coin past
Both coinage sweeps are implemented and tested. Nothing calls them on a clock. CoinageLayer::refresh_subscriptions likewise has no caller anywhere in the crate.
Why this is not hypothetical
This is the middle of the three conditions required to fix a live loss-of-funds bug in polkadot-app-ios-v2 (CoinageRecyclingService recycles coins into recycler entries and never unloads them out; a user who tops up and does not open the app within 90 days loses the value). The three conditions are: the core implements the rescue sweep (done), the host schedules it in the background (this issue), and the app routes its coinage through the core (separate work).
The failure mode is precisely "the user did not open the app", so a foreground-only or session-scoped tick narrows the window without closing it. That constraint should drive the design rather than be discovered by it.
Questions to settle
High level, before any code:
- Who decides when — adopt #308's rule as the general contract, or something else?
- What does the core expose — one tick per subsystem, or one global tick that fans out internally?
- How does the core say when it next wants waking — return a deadline, a duration, or nothing and let the host pick an interval?
- What happens if the host never ticks? The spec must state, per subsystem, whether the consequence is degraded freshness or destroyed data. Coinage is the latter.
- Session-scoped or true background? See above — session-scoped does not close the coinage window.
- What if the tick needs network and there is none?
- Overlap — what if a tick fires while the previous one is still running?
- Ownership.
CoinageLayeris&mut selfthroughout. A long-lived driver needs an ownership model the crate has not chosen: an actor loop owning the layer, orArc<Mutex<CoinageLayer>>driven by the existingcrate::subscription::Spawner. An actor avoids a mutex around every operation and is the current recommendation, but nothing is decided.
Questions 4 and 5 are product-visible, not just engineering detail.
Related, and deliberately out of scope here
Reactive observation. coinage-layer.md §6.1 requires continuous subscriptions to the chain storage backing local records — "The layer does not pull-poll" — and this is unbuilt for the same underlying reason: nothing in the core stays alive. It also needs state_subscribeStorage added to RpcClient, which currently exposes only call, get_storage, get_storage_at, finalized_head, submit_and_watch, submit_and_watch_inclusion (the inner subxt subscribe is used only by submit_and_watch_inclusion). Observation shares question 8's ownership decision, so it should be designed alongside this rather than separately.
Expected shape of the outcome
A design document covering the model, then an RFC for the truapi-platform trait addition — since that surface change is something every host (iOS, Android, web, CLI) must then implement.
Not blocked on this
Coinage layer 1 supplies the core-side half — an entry point that answers "it is time T, do what is due" and reports when it next wants waking. That is buildable without settling any question above, and does not prejudge them. Only the mechanism that calls it is blocked.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the issue's design questions, #308, and docs/design/coinage-layer.md sections 6.4 and 8.7. Read the truapi-platform trait surface and the existing CoinageLayer sweeps and refresh_subscriptions entry points. Done means a design document and an RFC defining the scheduling contract, ownership model, failure behavior, and required host trait changes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- backend-api-design
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100