paritytech / paritytech/web3-storage
`checkpoint` counts signature entries, not distinct providers
@ilchu is already working on this.
Since Sep 4, 2026.
- Dominant language
- Rust
- Stars
- 12
- Forks
- 3
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 33
Description
Motivation
min_providers is a bucket's redundancy policy: how many primary providers must
acknowledge a state before it becomes the canonical snapshot. The threshold counts
signature entries, so one provider's signature repeated N times satisfies a
policy of N — the setting is met with zero actual redundancy, the one property it
exists to guarantee.
The design already specifies the mechanism that prevents this; this one path
doesn't use it.
Current state
crates/pallets/storage-provider/src/lib.rs — checkpoint counts entries in
signatures (signing_count += 1 per loop iteration) instead of counting the
primary_signers bitfield it builds alongside. No duplicate guard exists.
Reproduced against the pallet mock — two primaries, min_providers = 2, provider 2
signing twice, provider 3 never signing:
primary_providers = [2, 3]
min_providers = 2
checkpoint with duplicate signer -> Ok(())
count_signers() = 1 (bitfield [1])
The snapshot that lands is self-contradicting: its own bitfield says one signer
against a policy of two. The bucket-freeze path in the same file counts that
bitfield via count_signers() and would reject the snapshot checkpoint just
wrote.
The design (docs/design/scalable-web3-storage-implementation.md) says
min_providers is how many providers must acknowledge, "ensures minimum
redundancy", and specifies primary_signers with count_signers() /
has_provider_signed() helpers — both of which exist and are used elsewhere in the
pallet. Counting set bits is duplicate-free by construction.
Impact is bounded: checkpoint is writer-or-admin, so this is a writer
bypassing an admin's policy. The bitfield stays correct, so no provider is
wrongly slashable, and nothing touches a payment path — policy enforcement and
telemetry, not fund loss.
Pre-existing: identical on dev. Surfaced while reviewing #341, which touches
verify_signature but not this counting.
Needs double-check. Found during review; the reproduction above is
mock-only. Worth confirming on a real runtime, and sanity-checking the impact
claim (that liability and payments are genuinely unaffected) before deciding
priority.
Proposed solutions
- Count the bitfield via
count_signers(), as the freeze path already does. - Reject a repeated signer outright with a new
DuplicateSignererror — also
de-duplicates the event payload.
Either is a small change with no migration and no weight change.
Regression test
Currently returns Ok(()); should fail once fixed. For
crates/pallets/storage-provider/src/tests/checkpoint.rs (needs codec::Encode,
sp_core::{Pair as _, H256}, storage_primitives::{Commitment, CommitmentPayload}):
#[test]
fn checkpoint_rejects_repeated_signer() {
new_test_ext().execute_with(|| {
frame_system::Pallet::<Test>::set_block_number(1);
register_provider(2, 200);
register_provider(3, 200);
let pair2 = provider_signer(2);
let _pair3 = provider_signer(3);
let bucket_id = setup_agreement(2, 1, 50, 200);
add_primary_to_bucket(3, 1, bucket_id, 50);
assert_ok!(StorageProvider::set_min_providers(
RuntimeOrigin::signed(1),
bucket_id,
2
));
let commitment = Commitment {
mmr_root: H256::repeat_byte(0xAA),
start_seq: 0,
leaf_count: 10,
};
let payload = CommitmentPayload::new(bucket_id, commitment).encode();
let sig = sp_runtime::MultiSignature::Sr25519(pair2.sign(&payload));
// Provider 2 signs twice; provider 3 never signs.
let sigs = vec![(2u64, sig.clone()), (2u64, sig)];
assert_noop!(
StorageProvider::checkpoint(
RuntimeOrigin::signed(1),
bucket_id,
commitment,
sigs.try_into().unwrap(),
),
Error::<Test>::DuplicateSigner // option 1: InsufficientSignatures
);
});
}
TODO
- Double-check the finding and the impact assessment
- Pick option 1 or 2
- Implement in
checkpoint; keepcount_signers()as the single counting idiom - Land the regression test above
- Test: the emitted event lists each provider exactly once
- Regression-guard
extend_checkpoint: it ORs with no counter, so it is
already correct — keep it that way if a counter is added - Update
docs/reference/EXTRINSICS_REFERENCE.mdif a new error is added
Related
- #341 — multi-scheme provider keys (where this surfaced; does not touch this path)
docs/design/scalable-web3-storage-implementation.md—min_providerssemantics
and thecount_signers()/has_provider_signed()helpers
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.