Skip to content

Measure pallet timeouts in relay chain blocks, not parachain blocks - #268

Merged
bkontur merged 42 commits into
devfrom
ic/relay-block-provider
Jul 20, 2026
Merged

Measure pallet timeouts in relay chain blocks, not parachain blocks#268
bkontur merged 42 commits into
devfrom
ic/relay-block-provider

Conversation

@ilchu

@ilchu ilchu commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements #233: every duration in pallet-storage-provider and the registry pallets — ChallengeTimeout, SettlementTimeout, RequestTimeout, checkpoint interval/grace, DeregisterAnnouncementPeriod, MaxNonceAge, agreement durations/extensions, drive expiry, replica min_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

  • New Config::BlockNumberProvider on pallet-storage-provider; all block-number reads route through Pallet::current_anchor_block(). The drive/s3 registries reuse it via their Config supertrait — no new Config items there. Runtimes wire cumulus_pallet_parachain_system::RelaychainDataProvider; mocks wire System, so existing tests drive the clock unchanged.
  • Treasury-style BlockNumberFor shadowing: like pallet_treasury, the pallet redefines BlockNumberFor<T> as its BlockNumberProvider's block number, so the habitual spelling yields the anchor clock everywhere — storage, structs, events, extrinsic args, constants — with the parachain height only reachable as SystemBlockNumberFor (re-exported for the registries), and only in the FRAME Hooks impls (whose on_initialize arg adopts treasury's _do_not_use_local_block_number). The Config bound pins the two names to one concrete type, so nothing changes at the SCALE/metadata level. (Evolved in-branch from an explicit AnchorBlockNumberFor<T> alias — superseded by the shadowing, which inverts the defaults so new code gets the correct clock by habit.)
  • The pallet exposes the "anchor block" abstraction to off-chain consumers via two runtime APIs, so nothing outside the runtime needs to know which block notion the pallet uses (relay today, whatever later): current_anchor_block() (the clock) and anchor_block_time_millis() (its tick, backed by #[pallet::constant] Config::AnchorBlockTimeMillis, declared in each runtime right next to the BlockNumberProvider it describes; integrity_test rejects zero).
  • Challenge slash sweep redesign (the one non-mechanical part, flagged in the issue comments): the old 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 in on_initialize tracking a LastSweptChallengeBlock cursor. It drains only deadline keys strictly below the previous block's relay parent (provably unrespondable — the validation-data inherent hasn't run yet in on_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_finalize split, 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 of min(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 into sweep_expired_challenges / challenge_sweep_range / slash_expired_at so range-resolution and the per-key drain read and test separately.
  • Timing constants in both runtimes now derive from a new relay_time module anchored to RELAY_CHAIN_SLOT_DURATION_MILLIS rather than MILLISECS_PER_BLOCK — numerically identical today (6s == 6s), but Support for 3 async cores #131's 2s switch no longer touches them.
  • Benchmarks advance both clocks (RelaychainDataProvider::set_block_number is available under runtime-benchmarks); the paseo runtime tests gained a set_clocks helper for the same reason.

Off-chain (same PR to keep e2e green)

  • Provider node: chain_state.current_anchor_block tracks the pallet's clock at each finalized block via the current_anchor_block runtime API (raw state_call + manual decode, so the API needn't be in the node's metadata snapshot), so /negotiate's valid_until and the checkpoint window math stay consistent with the pallet.
  • Rust SDK: ChallengerClient::analyze_provider measures last_checkpoint_age on the anchor clock (shared fetch_current_anchor_block helper in client/src/substrate.rs) instead of subtracting a relay-denominated checkpoint_block from the parachain head. Against a pre-anchor runtime this now errors loudly rather than producing a silently wrong Challenge/Monitor/Skip recommendation.
  • JS SDK: new currentRelayBlock / waitForRelayBlock in @web3-storage/layer0. Demos and e2e suites snapshot the relay block for CommitmentPayload nonces (a System.Number nonce 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.
  • UIs: the provider dashboard and s3-ui track anchorBlock$, refreshed per finalized block from the current_anchor_block runtime API via PAPI's getUnsafeApi() (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. formatDuration converts anchor-denominated durations with AnchorBlockTimeMillis instead of Aura.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 (LastSweptChallengeBlock self-anchors on first block). Upgrading a live chain with in-flight state is NOT covered: existing parachain-denominated Challenges keys would sit below the cursor (never swept) and old expiries would read as long past — for testnets, reset; otherwise a one-shot OnRuntimeUpgrade re-inserting pending challenges at relay_now + ChallengeTimeout (and clearing their stranded NextChallengeIndex entries) 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:generate descriptor refresh so the UIs can move from getUnsafeApi() to typed runtime-API calls.

Docs

  • Design docs denominated on the anchor clock: an "anchor clock" note in the implementation doc (all durations measured via current_anchor_block, not frame_system::block_number), the stale "in blocks" / "block number at sign time" lines fixed, EXECUTION_FLOWS.md updated to the on_initialize sweep, and the checkpoint doc's window/leader pseudocode flagged as anchor-denominated.

Verified

  • 243+ pallet tests (5 new sweep tests: range gap, span-cap carry-over, budget mid-key carry-over, same-block idempotence, cursor anchoring) plus registries, paseo runtime integration tests, provider-node, client — all green; runtime-benchmarks / try-runtime feature checks; clippy -D warnings; JS typecheck + unit tests.
  • Live zombienet: just demo (all 8 steps — relay-denominated challenge deadlines, both defenses, expiry + payout), just fs-demo-ci, just s3-demo-ci.
  • Post-review round (challenger fix, anchor_block rename, anchor-tick API, UI wiring, sweep refactor, BlockNumberFor shadowing): workspace clippy -D warnings, pallet/registry/client test suites (289 pallet+registry tests), tsc -b + vitest for the provider and s3 UIs.

ilchu added 2 commits July 7, 2026 13:52
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.
Comment thread runtimes/web3-storage-paseo/src/storage.rs Outdated
ilchu added 5 commits July 10, 2026 19:22
#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.
Comment thread provider-node/src/subxt_client.rs Outdated
StorageProvider::<T>::on_finalize(deadline);
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since you dropped on_finalize in pallet/src/lib.rs. This should be updated as well

@danielbui12 danielbui12 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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).

Comment thread pallet/src/lib.rs Outdated
Comment thread pallet/src/lib.rs Outdated
Comment thread provider-node/src/chain_state_coordinator.rs Outdated
Comment thread pallet/src/lib.rs Outdated
Comment thread pallet/src/lib.rs
Comment thread pallet/src/lib.rs
ilchu added 6 commits July 16, 2026 15:29
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.
bkontur added 4 commits July 17, 2026 14:06
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.
@bkontur

bkontur commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

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).

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.
bkontur added 6 commits July 20, 2026 09:32
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 bkontur left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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 added 8 commits July 20, 2026 10:57
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
bkontur enabled auto-merge July 20, 2026 09:37
@bkontur
bkontur added this pull request to the merge queue Jul 20, 2026
Merged via the queue into dev with commit 39e43e2 Jul 20, 2026
42 checks passed
@bkontur
bkontur deleted the ic/relay-block-provider branch July 20, 2026 10:42
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
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.

Measure pallet timeouts in relay chain blocks, not parachain blocks

3 participants