Skip to content

Challenge-saturation stress prototype#271

Draft
x3c41a wants to merge 1 commit into
feat/storage-backed-timeoutsfrom
feat/challenge-saturation
Draft

Challenge-saturation stress prototype#271
x3c41a wants to merge 1 commit into
feat/storage-backed-timeoutsfrom
feat/challenge-saturation

Conversation

@x3c41a

@x3c41a x3c41a commented Jul 7, 2026

Copy link
Copy Markdown

Second slice of #267: a prototype that stresses the challenge-slash sweep at scale and measures it.

Stacked on #269 (storage-backed timeouts + lower-timeouts.ts), which this needs so ChallengeTimeout can be lowered to put the deadline minutes away instead of two days. Base retargets to dev once #269 merges.

What it does

examples/papi/challenge-saturation.ts creates N challenges in a single block (one Utility.force_batch of challenge_offchain, all reusing one uploaded commitment so they share a deadline), leaves them undefended, waits for the deadline block, then measures both the creation block and the on_finalize sweep block: on-chain System.BlockWeight per class, block time, ChallengeSlashed count, provider stake trajectory, and residual Challenges / NextChallengeIndex state after the drain. It prints a pre-flight estimate from the benchmarked weights and emits a JSON result (--json).

This is the "provider ignores all" flavor (the on_finalize mass-slash path). The "provider defends all" flavor (per-challenge proof generation + respond_to_challenge) is a follow-up.

Findings

Measured on local zombienet, ChallengeTimeout=30, count 800:

value
challenges created (one block) 800 / 800
creation block PoV (recorded) 0.14 MiB normal (5% of block PoV)
sweep block PoV 4.11 MiB (82% of the 5 MiB block budget), all mandatory
sweep block ref_time 773 ms of the 2000 ms budget (39%)
sweep block time 6000 ms (normal, no stall)
slashes at deadline 800 (== created)
provider stake 2000 → 0 UNIT
residual challenges / NextChallengeIndex 0 / 0

Two things this answers for the issue:

  • The deadline block is PoV-bound, not time-bound. At 800 the sweep already eats 82% of the PoV budget while using only 39% of ref_time. The actual sweep PoV is ~5.4 KB/challenge (close to the benchmark, since the drain touches 800 distinct entries), so 1,000 extrapolates to ~5.1 MiB, at or over the whole 5 MiB block PoV budget. A full deadline block would be almost entirely the sweep, with little PoV left for anything else.
  • MaxChallengesPerDeadline = 1000 is unreachable through the create path. All challenges sharing a deadline must be created in one block, and block inclusion is gated by the benchmarked challenge_offchain weight (~4148 bytes PoV each), which caps a single block at ~947. So you cannot actually line up 1,000 at one deadline via extrinsics. (Recorded creation PoV is far lower because the creates hit mostly the same storage; inclusion still gates on the benchmark.)

These feed the #177 hardening decision (is the sweep a real DoS surface?) and #113 (2s blocks halve the per-block budget, so re-run after). Numbers reconcile end to end: created == slashed, stake fully slashed, no residual state.

Usage

just start-chain
just start-provider inmemory 3333 ./provider-data "" true   # auth off, as CI runs it
pnpm --filter web3-storage-papi-demo run lower-timeouts -- --set ChallengeTimeout=30
pnpm --filter web3-storage-papi-demo run challenge-saturation -- --count 800 --json out.json

Tested

Ran end to end on local zombienet + provider node at counts 25 and 800; both reconcile clean. tsc --noEmit passes. The script tops the provider stake back up on each run (every run slashes it to 0) so it is re-runnable on the same chain.

Create N challenges in one block (Utility.force_batch of challenge_offchain
reusing a single uploaded commitment) so they share a deadline, leave them
undefended, then measure the on_finalize slash sweep at that deadline block:
System.BlockWeight per class, block time, ChallengeSlashed count, provider
stake trajectory, and residual Challenges/NextChallengeIndex state. Emits a
JSON result and a pre-flight estimate from the benchmarked weights.

Second slice of #267 (challenge saturation). The provider-defends-all flavor
is a follow-up.
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.

1 participant