Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
da3a47a
light-base: add lifecycle_unstable_follow RPC subscription
BigTava Jul 31, 2026
b7a44d3
light-base: fix rustdoc links + add lifecycle_service and commit_boot…
BigTava Aug 3, 2026
cec486c
light-base: guarantee Connecting<FirstPeer order and add WarpNoProgre…
BigTava Aug 3, 2026
b75d3b8
light-base: remove semicolons from lifecycle-events docstrings and co…
BigTava Aug 4, 2026
665b1ea
light-base: add Given/When/Then markers to lifecycle-events tests
BigTava Aug 4, 2026
80d7745
Merge remote-tracking branch 'origin/main' into feat/lifecycle-events
BigTava Aug 5, 2026
4a3364b
light-base: extract duplicate StallReason match into a local closure …
BigTava Aug 5, 2026
da1d5ae
light-base: cargo fmt lifecycle-follow dispatch closure
BigTava Aug 5, 2026
539cebb
Merge remote-tracking branch 'origin/main' into feat/lifecycle-events
BigTava Aug 6, 2026
671f014
Merge remote-tracking branch 'origin/main' into feat/lifecycle-events
BigTava Aug 7, 2026
271465a
Merge remote-tracking branch 'origin/main' into feat/lifecycle-events
BigTava Aug 10, 2026
a6cc8e5
Merge remote-tracking branch 'origin/main' into feat/lifecycle-events
BigTava Aug 13, 2026
e979680
Merge remote-tracking branch 'origin/main' into feat/lifecycle-events
BigTava Aug 17, 2026
2f50b55
light-base: emit ModeDecision, WarpSyncProgress, WarpSyncFinished fro…
BigTava Aug 21, 2026
a875a7c
light-base: trim redundant comments and docstrings on the bootstrap-s…
BigTava Aug 21, 2026
82b50eb
json-rpc: add lifecycle_unstable_follow arm to SubscriptionStartProce…
BigTava Aug 21, 2026
5483d98
light-base: poll peer count instead of subscribing to avoid back-pres…
BigTava Aug 21, 2026
281cda9
light-base: emit Lagged terminal reason via a subscription sideband a…
BigTava Aug 21, 2026
67fbafc
light-base: reword hyphenated compounds in lifecycle subscription com…
BigTava Aug 21, 2026
4db6b7c
lib: refresh Lagged docstring now that the variant is actually emitted
BigTava Aug 21, 2026
cbc83db
light-base: resubscribe if the subscribe_all channel closes before a …
BigTava Aug 21, 2026
7acedfe
light-base: derive watchdog signals from the BootstrapStatus stream a…
BigTava Aug 21, 2026
56f1130
light-base: abort the lifecycle_unstable_follow pump on unfollow so t…
BigTava Aug 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions lib/src/json_rpc/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,12 @@ define_methods! {
sudo_network_unstable_unwatch(subscription: Cow<'a, str>) -> (),
chainHead_unstable_finalizedDatabase(#[rename = "maxSizeBytes"] max_size_bytes: Option<u64>) -> Cow<'a, str>,

/// Subscribe to typed chain lifecycle events. Returns the subscription id used to
/// match notifications and to `_unfollow`. Schema is unstable. See
/// <https://github.com/paritytech/smoldot/issues/3301>.
lifecycle_unstable_follow() -> Cow<'a, str>,
/// Cancel a `lifecycle_unstable_follow` subscription. No-op if the id is unknown.
lifecycle_unstable_unfollow(subscription: Cow<'a, str>) -> (),
}

define_methods! {
Expand All @@ -566,6 +572,9 @@ define_methods! {

// Statement notification sent when statements matching subscribed topics are received.
statement_statement(subscription: Cow<'a, str>, result: StatementEvent) -> (),

/// Lifecycle notification. See `lifecycle_unstable_follow` for the schema.
lifecycle_unstable_followEvent(subscription: Cow<'a, str>, result: LifecycleEvent) -> (),
}

#[derive(Clone, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -1142,6 +1151,86 @@ pub enum StatementEvent {
},
}

/// Notification event for `lifecycle_unstable_follow` subscriptions.
///
/// Schema is unstable. Unknown `kind` values should be ignored. See
/// <https://github.com/paritytech/smoldot/issues/3301>.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "kind", rename_all = "camelCase")]
pub enum LifecycleEvent {
Comment thread
BigTava marked this conversation as resolved.
/// Chain added, starting up.
Connecting,
/// First peer seen since the chain was added.
FirstPeer,
/// Bootstrap mode committed. Emitted once, before any warp or bootstrap-complete
/// event. Relay chains report `warpSync` if warp commits before the deadline and
/// `allForks` otherwise. Parachains are always `allForks`.
ModeDecision { mode: LifecycleSyncMode },
/// Warp sync progress. Only emitted while `mode` is `warpSync`. Polled at ~500 ms
/// cadence and re-emitted only when `(at, target)` changes. Both fields are
/// monotonically non-decreasing during warp. `target` is clamped so it never
/// drops below `at`.
#[serde(rename_all = "camelCase")]
WarpSyncProgress {
/// Highest block whose finality is proven by the warp fragments verified so far.
at: u64,
/// Highest best-block height advertised by any currently-connected peer.
target: u64,
},
/// Warp sync finished. Only emitted while `mode` is `warpSync`.
#[serde(rename_all = "camelCase")]
WarpSyncFinished {
/// Height decoded from the initial finalized-block header, or `0` if decode fails.
finalized: u64,
},
/// Initial bootstrap complete and the chain is ready to serve queries. Emitted at
/// most once per instance, after warp (if any) and once new-block streaming begins.
BootstrapComplete,
/// The health watchdog considers the chain stalled. Re-arms after emitting: when
/// the condition clears, a matching `recovered` event is emitted.
Stalled { reason: LifecycleStallReason },
/// The condition that produced the most recent `stalled` has cleared. `previously`
/// echoes the reason from that `stalled` so clients can correlate the pair.
#[serde(rename_all = "camelCase")]
Recovered { previously: LifecycleStallReason },
/// Terminal event. No further events will be delivered for this subscription.
Stopped { reason: LifecycleStopReason },
}

/// Sync mode reported by [`LifecycleEvent::ModeDecision`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum LifecycleSyncMode {
/// Skip forward via GRANDPA authority-set proofs to a recent finalized block.
WarpSync,
/// Verify every block from the checkpoint upward.
AllForks,
}

/// Reason for a [`LifecycleEvent::Stalled`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum LifecycleStallReason {
/// No peer connected within the watchdog window.
NoPeers,
/// Warp sync did not advance within the watchdog window.
WarpNoProgress,
/// Post-bootstrap sync produced no new-block notifications within the watchdog window.
SyncNoProgress,
/// Bootstrap did not complete within the watchdog window.
BootstrapTimeout,
}

/// Reason for a [`LifecycleEvent::Stopped`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum LifecycleStopReason {
/// Subscriber fell behind and its channel was dropped. Reserved. Not currently emitted.
Lagged,
/// The broadcaster is gone, typically because the chain was removed.
ChainRemoved,
}

/// Filter for subscribing to statements based on topics.
///
/// JSON format is compatible with polkadot-sdk's `TopicFilter`:
Expand Down
12 changes: 11 additions & 1 deletion lib/src/json_rpc/service/client_main_task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,8 @@ impl ClientMainTask {
| methods::MethodCall::transactionWatch_v1_submitAndWatch { .. }
| methods::MethodCall::sudo_network_unstable_watch { .. }
| methods::MethodCall::bitswap_unstable_stream { .. }
| methods::MethodCall::chainHead_v1_follow { .. } => {
| methods::MethodCall::chainHead_v1_follow { .. }
| methods::MethodCall::lifecycle_unstable_follow { .. } => {
Comment thread
BigTava marked this conversation as resolved.
// Subscription starting requests.

// We must check the maximum number of subscriptions.
Expand Down Expand Up @@ -550,6 +551,7 @@ impl ClientMainTask {
| methods::MethodCall::transactionWatch_v1_unwatch { subscription, .. }
| methods::MethodCall::sudo_network_unstable_unwatch { subscription, .. }
| methods::MethodCall::bitswap_unstable_unstream { subscription, .. }
| methods::MethodCall::lifecycle_unstable_unfollow { subscription, .. }
| methods::MethodCall::chainHead_v1_unfollow {
follow_subscription: subscription,
..
Expand Down Expand Up @@ -586,6 +588,9 @@ impl ClientMainTask {
methods::MethodCall::chainHead_v1_unfollow { .. } => {
methods::Response::chainHead_v1_unfollow(())
}
methods::MethodCall::lifecycle_unstable_unfollow { .. } => {
methods::Response::lifecycle_unstable_unfollow(())
}
_ => unreachable!(),
}
.to_json_response(request_id),
Expand Down Expand Up @@ -613,6 +618,11 @@ impl ClientMainTask {
methods::Response::bitswap_unstable_unstream(())
.to_json_response(request_id)
}
methods::MethodCall::lifecycle_unstable_unfollow { .. } => {
// Per spec: no error if subscription is unknown or already-completed.
methods::Response::lifecycle_unstable_unfollow(())
.to_json_response(request_id)
}
_ => parse::build_error_response(
request_id,
ErrorResponse::InvalidParams(None),
Expand Down
5 changes: 5 additions & 0 deletions light-base/src/json_rpc_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ pub struct Config<TPlat: PlatformRef> {
/// Service that fulfills IPFS CID requests.
pub bitswap_service: Arc<bitswap_service::BitswapService>,

/// Broadcaster of typed lifecycle events for the chain. Used by
/// `lifecycle_unstable_follow`.
pub lifecycle_service: Arc<crate::lifecycle_service::LifecycleService>,

/// Name of the chain, as found in the chain specification.
pub chain_name: String,
/// Type of chain, as found in the chain specification.
Expand Down Expand Up @@ -156,6 +160,7 @@ pub fn service<TPlat: PlatformRef>(config: Config<TPlat>) -> Frontend<TPlat> {
transactions_service: config.transactions_service,
runtime_service: config.runtime_service,
bitswap_service: config.bitswap_service,
lifecycle_service: config.lifecycle_service,
chain_name: config.chain_name,
chain_ty: config.chain_ty,
chain_properties_json: config.chain_properties_json,
Expand Down
Loading
Loading