Measure pallet timeouts in relay chain blocks, not parachain blocks - #268
Merged
Conversation
Add a BlockNumberProvider associated type to pallet-storage-provider's Config and route every block-number read in the pallet (and the drive/s3 registries, which reuse it via their Config supertrait) through it. Production runtimes supply RelaychainDataProvider, so all timeouts, expiries, checkpoint windows and nonce-recency checks are measured in relay chain blocks (6s) and keep their wall-clock meaning when the parachain block time changes (#113 / PR #131). Mocks supply System. The challenge slash sweep can no longer probe the single key n in on_finalize(n): relay numbers advance by a variable amount (including zero) between consecutive parachain blocks. Replace it with a drain-range sweep in on_initialize that tracks progress in a new LastSweptChallengeBlock cursor, drains all deadline keys strictly below the previous block's relay parent (provably unrespondable), and returns the exact weight consumed instead of pre-reserving it. Two independent bounds keep the block budget safe: a 32-key span cap on probing and a MaxChallengesPerDeadline slash budget per block (the old single-key worst case), with mid-key carry-over via the cursor. Runtime timing constants now derive from a new relay_time module based on RELAY_CHAIN_SLOT_DURATION_MILLIS instead of the parachain MILLISECS_PER_BLOCK; values are numerically unchanged today. Benchmarks advance both clocks via a set_block_number helper (the provider implements set_block_number under runtime-benchmarks), and the paseo runtime tests gain a set_clocks helper for the same reason. Closes #233
The pallet now measures every duration against the relay chain clock, so off-chain actors must snapshot ParachainSystem.LastRelayChainBlockNumber instead of the parachain height wherever a value meets an on-chain check. Provider node: chain_state.current_block now tracks the relay block anchored to each finalized parachain block (one extra storage read per block), feeding /negotiate's valid_until and the checkpoint window math; the fetch/decode is shared via a new storage_client::substrate::fetch_last_relay_block_number helper. JS SDK: new currentRelayBlock/waitForRelayBlock helpers in @web3-storage/layer0. Demos and e2e suites switch their CommitmentPayload nonce snapshots off System.Number (which would be rejected on any network where relay numbers dwarf parachain heights) and wait for agreement expiry / checkpoint windows on the relay clock. full-flow's demo agreement duration grows 15 -> 40 relay blocks since the relay clock keeps ticking while the parachain onboards.
Coverage-gated (tests/*_integration.rs) additions: - /negotiate signs valid_until = relay current_block + RequestTimeout, asserted with a Paseo-scale relay number, and returns 503 chain_state_not_ready when either the clock or the constant is still unknown (both refusal branches were untested). Plus checkpoint-coordinator duty tests (coordinators target): windows derive from the relay-scale block number and move only when the relay clock crosses an interval boundary, never on a repeated relay parent (velocity > 1) or an intra-window advance. Provider coverage: 64.79% locally vs 64.59% base (the dip came from the coordinator's relay-fetch lines, which need a live chain; the gate ignores subxt_client.rs).
ilchu
added a commit
that referenced
this pull request
Jul 7, 2026
PR #131 now targets #268 (relay-block denomination) instead of dev. Conflict resolutions: - runtimes/*/storage.rs: took the relay-block-provider side wholesale. This PR's DefaultCheckpointInterval/Grace rework (10*MINUTES/2*MINUTES of parachain time) is obsolete — the constants are relay-denominated now (100/20 relay blocks = the same ~10 min / ~2 min wall-clock) and no longer rescale with parachain block time, which was the whole point of that rework. - runtimes/*/lib.rs spec_version: both sides bumped independently from the merge base, so take the union: local runtime 2 vs 3 -> 4, paseo 4_002 vs 4_003 -> 4_004, with lineage comments. - Everything else (2s consensus constants, velocity 3, RelayParentOffset, zombienet topology, assign-cores) merged clean.
bkontur
reviewed
Jul 8, 2026
#275 extracted the wire types into provider-negotiation and removed the provider node's storage-client dependency, but two callers still reached into storage_client::substrate::fetch_last_relay_block_number. Inline the one-shot ParachainSystem::LastRelayChainBlockNumber query as a pub(crate) helper in subxt_client so the node reads the relay clock over its own subxt connection without depending on the client SDK.
The relay_time module's MINUTES/HOURS are name-twins of the parachain time module's MINUTES/HOURS, so a call site like 48 * HOURS gave no hint which clock it measured. Rename the relay-chain units to RC_MINUTES / RC_HOURS in both runtimes (constants + storage.rs timeout params) so the relay-chain denomination is explicit at every use. Values unchanged; the parachain time::* units (Session Period/SessionLength) keep their names.
bkontur
reviewed
Jul 14, 2026
danielbui12
requested changes
Jul 14, 2026
| StorageProvider::<T>::on_finalize(deadline); | ||
| } | ||
| } | ||
|
|
Member
There was a problem hiding this comment.
Since you dropped on_finalize in pallet/src/lib.rs. This should be updated as well
danielbui12
reviewed
Jul 14, 2026
danielbui12
left a comment
Member
There was a problem hiding this comment.
Make sure you wire client (rust + ts) with new changes. My assistance feedback:
1. client/src/challenger.rs:375-405 - Missed relay-clock wiring in the Rust SDK. analyze_provider computes last_checkpoint_age as parachain head minus snapshot.checkpoint_block, but the pallet now writes checkpoint_block on the relay clock. On Paseo the subtraction saturates to 0, so every provider reads as freshly checkpointed and the Challenge/Monitor/Skip recommendation runs on a fake age; on local zombienet the clocks nearly coincide so nothing fails. This also means the fetch_last_relay_block_number helper the PR added to client/src/substrate.rs:919 has zero callers in the repo (the provider node uses its own inlined copy per #275).
Fix: swap the blocks().at_latest().number() read for fetch_last_relay_block_number, which simultaneously gives the helper a real caller. (flagged by 3 agents, all high confidence)
2. Provider dashboard and s3-ui compare parachain height against relay-denominated on-chain values in five places - this is the direct answer to your "missed client wiring" question. All silently pass on zombienet and all break on Paseo, and two of them misinform providers about penalty-bearing obligations: - user-interfaces/provider/src/state/provider.state.ts:300-310 - checkpoint-overdue detection never fires, so the dashboard never warns about the window that now costs CheckpointMissPenalty.
- user-interfaces/provider/src/pages/Overview.tsx:112,249 - deregister cooldown shows ~29M blocks remaining and the complete button never unlocks.
- user-interfaces/provider/src/lib/chain-client.ts:343 - every agreement reads active forever, which also wrongly gates the deregister UI.
- user-interfaces/provider/src/lib/chain-client.ts:502 - every challenge reads pending forever.
- user-interfaces/s3-ui/src/components/CheckpointPanel.tsx:183-184 - challenge countdowns show tens of millions of blocks left and never expire.
Fix: add a relayBlockNumber$ (reading ParachainSystem.LastRelayChainBlockNumber, mirroring waitForRelayBlock) to each UI's chain state and use it for all pallet-clock comparisons. Same pattern fixes Earnings.tsx:32-34 (progress/earned-so-far clamps to 0 on Paseo).
danielbui12
reviewed
Jul 14, 2026
bkontur
reviewed
Jul 14, 2026
bkontur
reviewed
Jul 14, 2026
bkontur
reviewed
Jul 14, 2026
bkontur
reviewed
Jul 14, 2026
danielbui12
requested changes
Jul 15, 2026
Rewrite the on_initialize sweep docs as bullets with the safety formula (keys < previous relay parent are final), tighten MAX_SWEEP_SPAN and LastSweptChallengeBlock, and fix stale on_finalize references left over from the on_initialize move. Addresses review comments on #268.
The benchmark still called the dropped on_finalize hook, which is now the Hooks default no-op, so it measured nothing and under-charged the slash sweep. Anchor the cursor and relay clock so on_initialize drains exactly the loaded deadline key, and add a verify guard asserting the drain actually happened. Addresses review comment on #268.
The Automatic Slashing sequence still showed the old on_finalize(block_number) single-key take. Reflect the relay-block range drain tracked by LastSweptChallengeBlock. Addresses review comment on #268.
A single deadline can hold up to MaxChallengesPerDeadline (1000) challenges; slashing all of them in one block costs ~5 MB PoV — the whole block budget. Introduce MAX_SWEEP_SLASH_BUDGET (100) and cap the per-block budget at min(MaxChallengesPerDeadline, MAX_SWEEP_SLASH_BUDGET), letting a full deadline drain over several blocks via the existing carry-over cursor. Tighten the benchmark component to the effective budget so weight regeneration stays within the sweep's real ceiling. Addresses review comment on #268.
The field holds the relay-chain block anchored to the latest finalized parachain block, not the parachain height. Rename it (and the local it feeds in /negotiate) so the relay-clock semantics are self-evident, resolving the ambiguity flagged in review. The separate get_current_block trait methods are untouched. Addresses review comment on #268.
Assert no unresolved challenge sits at or below LastSweptChallengeBlock: the sweep drains everything up to its cursor and new challenges always land above it, so a violation flags a stranded-unslashed challenge (e.g. an un-migrated upgrade leaving keys below the anchor). Addresses review comment on #268.
Locals holding Pallet::current_anchor_block() were still named current_block, reading as parachain height. Rename them (and the checkpoint helpers' parameters) to anchor_block, matching the negotiate handler's naming; fix the stale Pallet::current_block doc link and the ChainStateNotReady messages along the way.
current_anchor_block tells clients where the pallet clock is but not how fast it ticks, so the provider dashboard humanized anchor-denominated durations (agreement duration, checkpoint interval/grace, min/max duration) with Aura.SlotDuration — identical today, silently wrong once the parachain block time changes. Add the paired runtime API (both runtimes return RELAY_CHAIN_SLOT_DURATION_MILLIS) and read it in the dashboard via the unsafe API (no descriptor regeneration; older runtimes keep the 6s default), renaming blockTimeMs to anchorBlockTimeMs so parachain block time can't be rewired in by accident.
The anchor_block_time_millis runtime API hardcoded RELAY_CHAIN_SLOT_DURATION_MILLIS in each runtime's impl block, away from the BlockNumberProvider wiring it describes. Make it a #[pallet::constant] instead: the clock and its tick are now declared side by side in each runtime's storage.rs, the runtime API delegates to the pallet like current_anchor_block does, integrity_test rejects a zero value, and the constant lands in metadata for free.
…arisons The provider dashboard and s3-ui compared the parachain height against anchor-denominated on-chain values, which silently passes on zombienet and breaks on any network where relay numbers dwarf parachain heights: agreement expiry and challenge status in chain-client.ts (whose local blockNumber$ was additionally never fed, so both always read 0), checkpoint-overdue detection, the deregister cooldown, earnings progress, and the s3-ui challenge countdown. Add an anchorBlock$ to each UI's chain state, refreshed per finalized block from the current_anchor_block runtime API via the unsafe API (live metadata, no descriptor regeneration; pre-anchor runtimes fall back to the parachain height, which is their pallet clock), and point all six comparisons at it.
Collaborator
Thank you, should be fixed :) |
The per-block anchor refresh is a fire-and-forget promise, so a result resolving after disconnect() (or a network switch) could write a stale anchor from the previous chain into the fresh state. Publish only if the client the call was made on is still the current client.
danielbui12
approved these changes
Jul 20, 2026
Per review: distinguish values on the pallet's anchor clock from the parachain height at the type level. The Config bound pins BlockNumberProvider::BlockNumber equal to BlockNumberFor<T>, so this is a readability marker rather than a distinct compile-time type — a genuine newtype would break the arithmetic that mixes anchor and parachain values and buys nothing while RelaychainDataProvider's BlockNumber is u32. Applied to the purest anchor surfaces (current_anchor_block return, the LastSweptChallengeBlock cursor) plus doc notes on challenges_at; the runtime API keeps BlockNumber (no T in scope for the alias).
Per review: the on_initialize hook body had five early returns and mixed range-resolution with the per-key drain. Split it into sweep_expired_challenges (the budget loop), challenge_sweep_range (clock check, first-run cursor anchoring, span cap -> Option<range>), and slash_expired_at (drain + slash one deadline key). The hook is now a one-line call; behavior is unchanged (all five sweep tests + the try-state invariant pass). Also refreshes the stale on_finalize mentions left in challenges.rs comments.
Per review: adjust the design mds around current_anchor_block and block-number/deadline/timeout handling. Add an anchor-clock note to the implementation doc (all durations measured against the relay-sourced anchor via current_anchor_block, not frame_system::block_number), fix the stale 'in blocks' / 'block number at sign time' lines, and note in the checkpoint doc that its window/leader pseudocode block numbers are anchor blocks. EXECUTION_FLOWS.md was already updated in this PR.
ChallengeTimeout, MaxNonceAge, SettlementTimeout, RequestTimeout, DefaultCheckpointInterval/Grace and DeregisterAnnouncementPeriod are all durations added to or compared against the anchor clock, so type them AnchorBlockNumberFor<Self> to match current_anchor_block()'s return rather than the parachain BlockNumberFor. Same concrete type (the Config bound pins them equal), so no runtime, metadata or migration change. Also tightens the alias / sweep-helper / challenges_at doc comments.
Follow-through on the alias: query_challenges_at and challenge_to_response take anchor-denominated deadline keys, so type them AnchorBlockNumberFor<T> like the sweep helpers rather than the parachain BlockNumberFor<T>. Same concrete type; drops the now-unused import.
query_challenges_at took AnchorBlockNumberFor but the Challenges map it indexes was still keyed by BlockNumberFor. Since a deadline is an anchor value (current_anchor_block + ChallengeTimeout, both anchor-typed), type the whole challenge-deadline surface consistently: the Challenges / NextChallengeIndex keys, every ChallengeId<_> (storage, events, respond_to_challenge arg, benchmarks), and the ChallengeCreated.respond_by / ChallengeDefended.response_time_blocks event fields. Same concrete type (== BlockNumberFor<T>), so no SCALE, metadata or migration change. Agreement/bucket/replica block fields stay BlockNumberFor for now — they live in cross-crate storage_primitives structs the alias can't reach.
bkontur
requested changes
Jul 20, 2026
bkontur
left a comment
Collaborator
There was a problem hiding this comment.
sorry, I was wrong with this AnchorBlockNumberFor, polkadot-sdk uses different pattern - BlockNumberFor and alias for SystemBlockNumber - I am aligning with this
Replace the explicit AnchorBlockNumberFor marker with pallet_treasury's pattern: the pallet redefines BlockNumberFor<T> as the BlockNumberProvider's block number, so the habitual spelling yields the anchor clock everywhere — ProviderInfo, ProviderSettings, Bucket, StorageAgreement, events, extrinsic args included, with no per-site churn. The parachain height is only reachable as SystemBlockNumberFor (re-exported from this pallet so dependents get the canonical pair from one place) and appears only where FRAME requires it: the Hooks impls, whose on_initialize arg adopts treasury's _do_not_use_local_block_number. The Config bound still pins the two types equal (now spelled BlockNumber = SystemBlockNumberFor<Self>, no longer a tautology), so nothing changes at the SCALE/metadata level. The drive/s3 registries import both names from pallet_storage_provider.
bkontur
approved these changes
Jul 20, 2026
The API's BlockNumber slot only carries anchor-clock values (challenges_at deadlines, current_anchor_block), so spell the instantiation with the pallet's shadowed alias. Same concrete type as the runtime's BlockNumber, per the pallet's Config pin — purely self-documenting.
anchor_block + storage_period was raw arithmetic on a provider-influenced duration; with anchor values in the millions on Paseo, a duration near u32::MAX would wrap in release and store an expires_at in the past while the Layer-0 agreement saturates to u32::MAX. Match Layer 0's saturating_add.
The Automatic Slashing diagram still showed the pre-Treasury behavior: challenger reward, bucket removal, agreement teardown and a ProviderSlashed event. The sweep does none of that — it slashes the whole stake to the Treasury, refunds the deposit (no reward), updates stats and emits ChallengeSlashed; teardown is the separate permissionless remove_slashed extrinsic. Also fix the drain range to be exclusive of the current anchor block (challenges at the anchor are still respondable) and correct the same stale 10%-reward claim in the slash helper's rustdoc.
The advance_to helper existed to fire the pallet's on_finalize, which no longer exists — its call resolved to the trait's default no-op, and mock's run_to_block now drives the same hooks. Drop it, use run_to_block everywhere, and reword the comments (and one test name) that still described timeout slashing as an on_finalize event.
The follow test mocked the runtime-API anchor equal to the header number (42), so it could not catch a regression to storing the parachain height; mock it as 4242 and assert that. Stop seeding the provider UI's anchor with a fabricated block 1 — on a pre-anchor runtime the fallback would publish it as a real anchor value; staying at 0 until the first finalized block is the safe direction. Also say anchor (not relay-chain) block in the CommitRequest nonce doc, matching the provider's clock-agnostic abstraction.
Fixes the check-fmt failure on CI: the Saturating import was added after the local fmt pass and sorts below the cfg-gated TryRuntimeError import.
Catch the surrounding docs up with the runtime and the anchor-clock change: - Constants: DeregisterAnnouncementPeriod is 54 * RC_HOURS (not 48h) and strictly > ChallengeTimeout; MinStakePerByte is 1_000 (1 token per GB, not 1_000_000 per MB); ChallengeTimeout is 48 * RC_HOURS everywhere the old '100 blocks' or '48 * HOURS' survived (CLAUDE.md, marketplace.md, EXTRINSICS_REFERENCE, implementation doc), with stake examples recomputed. - Denomination: every quoted duration now says anchor (relay-chain) blocks — parameter tables, the payment formula and its 6s-per-block arithmetic, and a blanket note in EXECUTION_FLOWS covering its pseudocode (whose challenge-creation flow now shows current_anchor_block + ChallengeTimeout and the double-map insert). - Mechanism: the implementation doc's Config snippet gains the missing BlockNumberProvider, AnchorBlockTimeMillis and MaxChallengesPerDeadline entries; its storage section now shows Challenges as the (deadline, index) double map plus NextChallengeIndex and the sweep cursor LastSweptChallengeBlock; the challenge deposit doc no longer promises compensation on a slash (deposit refund only, slash to Treasury). File pointers move from lib.rs to storage.rs where the parameters actually live.
bkontur
enabled auto-merge
July 20, 2026 09:37
bkontur
added a commit
that referenced
this pull request
Jul 20, 2026
Resolves conflicts with #268 (anchor-clock rename + relay-block nonces): - provider-node: keep ProviderDeps-based construction, adopt the current_anchor_block field name and doc wording - examples/papi: keep the signer argument on uploadChunk, take dev's currentRelayBlock(api) nonce source and relay-denominated duration - negotiate_integration.rs: port dev's three new anchor-clock tests to the ProviderDeps constructor signature
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
Implements #233: every duration in
pallet-storage-providerand the registry pallets —ChallengeTimeout,SettlementTimeout,RequestTimeout, checkpoint interval/grace,DeregisterAnnouncementPeriod,MaxNonceAge, agreement durations/extensions, drive expiry, replicamin_sync_interval— is now measured in relay chain blocks instead of parachain blocks, so nothing silently rescales when the parachain moves to 2s blocks (#113 / #131).Closes #233.
On-chain
Config::BlockNumberProvideronpallet-storage-provider; all block-number reads route throughPallet::current_anchor_block(). The drive/s3 registries reuse it via theirConfigsupertrait — no new Config items there. Runtimes wirecumulus_pallet_parachain_system::RelaychainDataProvider; mocks wireSystem, so existing tests drive the clock unchanged.BlockNumberForshadowing: likepallet_treasury, the pallet redefinesBlockNumberFor<T>as itsBlockNumberProvider's block number, so the habitual spelling yields the anchor clock everywhere — storage, structs, events, extrinsic args, constants — with the parachain height only reachable asSystemBlockNumberFor(re-exported for the registries), and only in the FRAMEHooksimpls (whoseon_initializearg adopts treasury's_do_not_use_local_block_number). TheConfigbound pins the two names to one concrete type, so nothing changes at the SCALE/metadata level. (Evolved in-branch from an explicitAnchorBlockNumberFor<T>alias — superseded by the shadowing, which inverts the defaults so new code gets the correct clock by habit.)current_anchor_block()(the clock) andanchor_block_time_millis()(its tick, backed by#[pallet::constant] Config::AnchorBlockTimeMillis, declared in each runtime right next to theBlockNumberProviderit describes;integrity_testrejects zero).on_finalize(n)point-lookup breaks because relay numbers advance by a variable amount (including zero) between consecutive parachain blocks. Replaced with a drain-range sweep inon_initializetracking aLastSweptChallengeBlockcursor. It drains only deadline keys strictly below the previous block's relay parent (provably unrespondable — the validation-data inherent hasn't run yet inon_initialize, and every later block's relay parent is ≥ that value), and returns the exact weight consumed instead of the old reserve-in-on_init/ work-in-on_finalizesplit, which cannot be kept sound under the two-clock asymmetry. Two independent bounds protect the block budget: a 32-key span cap on probing, and a per-block slash budget ofmin(MaxChallengesPerDeadline, MAX_SWEEP_SLASH_BUDGET = 100)so one fully-loaded deadline cannot eat the block's PoV. On budget exhaustion the cursor parks below the partially drained key and carries over. The hook body is a one-line call intosweep_expired_challenges/challenge_sweep_range/slash_expired_atso range-resolution and the per-key drain read and test separately.relay_timemodule anchored toRELAY_CHAIN_SLOT_DURATION_MILLISrather thanMILLISECS_PER_BLOCK— numerically identical today (6s == 6s), but Support for 3 async cores #131's 2s switch no longer touches them.RelaychainDataProvider::set_block_numberis available underruntime-benchmarks); the paseo runtime tests gained aset_clockshelper for the same reason.Off-chain (same PR to keep e2e green)
chain_state.current_anchor_blocktracks the pallet's clock at each finalized block via thecurrent_anchor_blockruntime API (rawstate_call+ manual decode, so the API needn't be in the node's metadata snapshot), so/negotiate'svalid_untiland the checkpoint window math stay consistent with the pallet.ChallengerClient::analyze_providermeasureslast_checkpoint_ageon the anchor clock (sharedfetch_current_anchor_blockhelper inclient/src/substrate.rs) instead of subtracting a relay-denominatedcheckpoint_blockfrom the parachain head. Against a pre-anchor runtime this now errors loudly rather than producing a silently wrong Challenge/Monitor/Skip recommendation.currentRelayBlock/waitForRelayBlockin@web3-storage/layer0. Demos and e2e suites snapshot the relay block forCommitmentPayloadnonces (aSystem.Numbernonce would be rejected as stale on any network where relay numbers dwarf parachain heights — e.g. Paseo) and wait out expiries/windows on the relay clock. The terms replay nonce is counter-based (ReplayWindow) and needed no change.anchorBlock$, refreshed per finalized block from thecurrent_anchor_blockruntime API via PAPI'sgetUnsafeApi()(live metadata — no descriptor regeneration; pre-anchor runtimes fall back to the parachain height, which is their pallet clock). All pallet-clock comparisons read it: agreement expiry and challenge status (whose previous source was additionally a never-fed subject, so both always compared against 0), checkpoint-overdue detection, the deregister cooldown, earnings progress, and the s3-ui challenge countdown.formatDurationconverts anchor-denominated durations withAnchorBlockTimeMillisinstead ofAura.SlotDuration, which would have silently skewed when Support for 3 async cores #131 changes the parachain tick.Ordering vs #131
Not a hard prerequisite, but landing this first is the better order: #131's rebase then no longer needs to touch storage timing constants (its checkpoint-interval rework becomes unnecessary), off-chain consumers re-denominate once instead of twice, and shipping #131 to PreviewNet first would force a live-state migration here later.
Migration & deployment notes
Fresh chains need nothing (
LastSweptChallengeBlockself-anchors on first block). Upgrading a live chain with in-flight state is NOT covered: existing parachain-denominatedChallengeskeys would sit below the cursor (never swept) and old expiries would read as long past — for testnets, reset; otherwise a one-shotOnRuntimeUpgradere-inserting pending challenges atrelay_now + ChallengeTimeout(and clearing their strandedNextChallengeIndexentries) is the minimum. On live networks the runtime and provider node must ship together: an old runtime with a new provider node 503s on/negotiate(missing runtime API), a new runtime with an old provider node signs instantly-expired terms.Remaining follow-up per the issue: user-facing duration inputs (duration pickers, payment-per-block display), and a
pnpm papi:generatedescriptor refresh so the UIs can move fromgetUnsafeApi()to typed runtime-API calls.Docs
current_anchor_block, notframe_system::block_number), the stale "in blocks" / "block number at sign time" lines fixed,EXECUTION_FLOWS.mdupdated to theon_initializesweep, and the checkpoint doc's window/leader pseudocode flagged as anchor-denominated.Verified
runtime-benchmarks/try-runtimefeature checks; clippy-D warnings; JS typecheck + unit tests.just demo(all 8 steps — relay-denominated challenge deadlines, both defenses, expiry + payout),just fs-demo-ci,just s3-demo-ci.anchor_blockrename, anchor-tick API, UI wiring, sweep refactor,BlockNumberForshadowing): workspace clippy-D warnings, pallet/registry/client test suites (289 pallet+registry tests),tsc -b+ vitest for the provider and s3 UIs.