paritytech / paritytech/web3-storage
Challenge responder: `--challenge-poll-interval 0` makes a decode-drift miss unrecoverable
@ilchu is already working on this.
Since Jul 27, 2026.
- Dominant language
- Rust
- Stars
- 12
- Forks
- 3
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 33
Description
A provider can disable the challenge responder's reconciliation scan with --challenge-poll-interval 0 (ChallengeResponderParams, provider-node/src/cli.rs), which sets safety_net = false in ChallengeResponder::run_loop and makes the periodic scan_and_respond arm a permanent no-op. The node is then purely event-driven, and one class of missed event has no recovery path at all.
The gap
chain_events::decode_block_events decodes each block's events through the static storage-subxt bindings. When an event matches by pallet and name but its field shape has drifted from the bindings — the normal consequence of a runtime upgrade that changes ChallengeCreated — decode logs a warn! and returns None. No BlockEvent is constructed, so nothing is broadcast and no coordinator ever sees it.
Nothing re-announces that event. The other ways an event can be missed do self-heal:
| Gap | Recovery |
|---|---|
Receiver falls behind EVENT_CHANNEL_CAPACITY |
Lagged → full scan |
| Reconnect or node restart | Resubscribed → bootstrap scan |
| Stale or duplicated event replayed | point-read returns Ok(None), no-op |
| Event fails to decode against the bindings | none |
With the safety net enabled (the 300s default) the periodic Challenges scan covers it. With poll_interval = 0 the provider silently stops responding to challenges, with only a log line to show for it, and gets slashed.
Why this is worth a guard rather than documentation
The failure is silent, the trigger is a routine runtime upgrade, and the penalty is stake. An operator setting 0 is opting out of the backstop for the one duty where missing it is slashable — most likely without realising that a decode failure is unrecoverable, since the flag's help text presents the scan as covering "events lost to edge cases".
Options
- Reject or clamp
0for--challenge-poll-intervalspecifically, keeping it allowed for--replica-poll-interval(a missed sync costs revenue, not stake). - Keep
0accepted butwarn!loudly at startup that challenge responses then depend entirely on event decoding. - Escalate decode failures instead: have
decodetrigger a reconciliation scan when an event matched by name but failed to decode, which closes the gap independently ofpoll_interval.
The third is the most targeted — it turns the one unrecoverable case into a recoverable one — but it needs decode_block_events to report drift back to the coordinators rather than just logging.
Context
Came out of review discussion on #291, where the same poll_interval = 0 config also made dropped-while-paused events unrecoverable. That half is fixed in #291 (events now stay queued while paused); this issue is the remaining, independent gap.
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.
Assessment
This issue has not been assessed yet.