paritytech / paritytech/web3-storage
`CommitmentCollection::agreeing_providers` conflates root-agreement with checkpoint signers
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 12
- Forks
- 3
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 33
Description
Introduced by Illia's comment in #318
Summary
CommitmentCollection exposes two fields that read like the same set but no longer are:
signatures- the providers whose signature actually goes into the on-chaincheckpointcallagreeing_providers- every provider that reported the majority MMR root, including those whose signature was dropped by payload grouping
Until payload grouping was introduced (modal_payload_group, clients/storage/src/checkpoint.rs:1177-1201), signatures was derived 1:1 from the agreeing set, so the two were always the same providers and every consumer could read either one interchangeably. That invariant is now gone, but the field name, the type, and the consumers were not updated - the only thing distinguishing the two sets is a doc comment.
Why the sets can diverge
Same root does not imply same committed range. Providers can agree on mmr_root while disagreeing on (start_seq, leaf_count), and the pallet verifies every submitted signature against a single encoded (bucket_id, commitment, nonce) payload - so only one payload group can be submitted. collect_commitments groups by root first, then picks the modal payload group within it, and drops the rest (with a tracing::warn!).
Worked example - providers A, B, C, D all report the same mmr_root; A, B, C report (start_seq=0, leaf_count=100), D reports (0, 120):
agreeing_providers = [A, B, C, D] // everyone who matched the root
signatures = [(A,..), (B,..), (C,..)] // what's actually in the checkpoint
D is in the first list but not the second. D carries no on-chain liability for that snapshot, yet reads as having backed it.
Impact
submit_checkpoint gets this right - it thresholds on signatures.len() and derives signers from signatures (checkpoint.rs:1433-1447). The happy path is correct.
The problem is the public struct: CommitmentCollection is re-exported from storage_client, so persistence, health tracking, monitoring, dashboards, and any external consumer that reads agreeing_providers as "who backed this checkpoint" is silently wrong.
There is already one such consumer in-tree. analyze_conflicts (checkpoint.rs:1590-1662) computes both its consensus decision and its reported count from agreeing_providers:
let majority_percentage =
collection.agreeing_providers.len() as f64 / total_providers as f64 * 100.0;
let resolution = if majority_percentage >= self.config.consensus_threshold_percent as f64 {
ConflictResolution::ProceedWithMajority
With the default 51% threshold and four providers - A, B on root R payload P1; C on root R payload P2; D on root R':
| value | |
|---|---|
agreeing_providers |
[A, B, C] (3) |
signatures |
[A, B] (2) |
disagreeing_providers |
[(D, R')] (1) |
total_providers |
4, required = ceil(4 * 0.51) = 3 |
submit_checkpoint |
InsufficientConsensus { agreeing: 2, required: 3 } - correct |
analyze_conflicts |
ProceedWithMajority, majority_count: 3 - wrong |
So collect_commitments_with_conflicts hands the caller a recommendation to proceed on a collection that cannot meet the submission threshold, and a majority_count that overstates coverage by the number of dropped providers. majority_count is also what gets serialized into PersistedConflict (clients/storage/src/checkpoint_persistence.rs:299), so the overcount outlives the process.
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.
Research direction
Start in clients/storage/src/checkpoint.rs, especially collect_commitments, analyze_conflicts, and submit_checkpoint, then inspect the CommitmentCollection definition and clients/storage/src/checkpoint_persistence.rs:299. Trace how agreeing_providers and signatures are used; done means conflict analysis and persisted majority_count reflect providers whose signatures can actually be submitted and do not recommend proceeding below the submission threshold.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- distributed-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100