PendingPolls + tally write path redesignfeature/polkadot-v1.6.0-vm-bc463-pending-polls-redesignPendingPolls<BoundedVec<…>> into a lightweight counter plus a per-voter StorageDoubleMap. Every vote / remove_vote / update_vote becomes O(1) instead of decode-and-re-encode of a ~2.1 MB BoundedVec. on_idle now drains processed entries in-place, so multi-block batch resumption is implicit. The v2 migration shim added in the first commit was reverted in the second — the chain starts from scratch.
vote() ref_timevote() proof_sizeremove_vote() ref_timePendingPolls entry max
Numbers from the hand-adjusted weights.rs. The proof_size figures are tight (derived from MaxEncodedLen); the ref_time figures are estimates and want a fresh cargo bench sweep before tagging.
| Extrinsic | Before | After | Storage touched (after) |
|---|---|---|---|
vote() |
O(1) push + ~2 MB decode/encode | O(1) double-map insert | PendingPollVoters insert + PendingPolls counter +1 |
remove_vote() |
O(n) retain |
O(1) double-map remove | PendingPollVoters remove + PendingPolls counter −1 |
update_vote() |
O(n) find + mutate | O(1) double-map overwrite | PendingPollVoters overwrite (no counter change) |
on_idle (per voter) |
3R / 4W | 4R / 4W | + PendingPollVoters read+remove inline |
MaxVotersReached is still enforced — but now via a counter compare against MaxVotersPerPoll::get() rather than BoundedVec::try_push. Semantically identical cap; the error now surfaces before any tally mutation.
The core change. Reworks storage, all three extrinsics, the on_idle batch processor, weights, and benchmarks. Originally shipped with a v2 migration (reverted in commit 2).
PendingPolls: CountedStorageMap<PollIndex, u32> — just a voter count. The built-in CounterForPendingPolls still lets on_idle bail out quickly when the queue is empty.PendingPollVoters: StorageDoubleMap<PollIndex, AccountId, VoteDirection> — the actual per-voter records. Iterated via iter_prefix(poll_index).on_idle batch processingprocess_voters_batch now iter_prefix(poll).take(max_voters) and drains processed entries in place. Resumption across blocks is implicit — the next batch just continues the shorter prefix. No more voter_offset arithmetic.ProcessingState drops voter_offset and adds total_voters (captured at first-batch time so the PollProcessed / PollCancelled event still reports the full poll's count after multi-block processing).per_voter_proof_size adds a fourth trie-proof overhead + the new entry's encoded size.Tally::is_passing() removed (only test fixtures called it). Inlined to tally.ayes > tally.nays in mock and tests — this is what required the test-file edits.Pallet::get_tally: defensive unreachable Ongoing arm changed from Some(*tally) to None, matching the sibling defensive arm in is_passing and avoiding a redundant referenda re-entry while a mutable borrow is held.vote() comment Executed → Completed.weights.rs: hand-adjusted on 2026-05-18 to reflect new storage entry sizes (header comment notes a fresh cargo bench pass is still wanted).prefill_voters writes into PendingPollVoters + bumps the counter; documented that prefill no longer drives measured weight for the three extrinsics.Three helpers — pending_voter_count, add_pending_voter, pending_voters_sorted — replace the old PendingPolls::mutate(_, try_push) patterns and abstract over the unordered double-map iteration. Tests that asserted specific voter ordering were rewritten to count processed voters rather than inspect indices (storage prefix iteration order is unspecified). ProcessingState field changes propagate into the cancelled_poll_* and on_idle_batched_processing_with_progress_state tests.
Reverts the migration shim that the first commit originally added.
pallets/reputation-voting/src/migrations.rs.pub mod migrations; from the pallet root.STORAGE_VERSION from StorageVersion::new(2) back to StorageVersion::new(1); reverts its visibility to private.Why: the chain starts from scratch — no v1 state exists in production, so the migration was dead weight. It also tripped SonarCloud rust:S2208 (wildcard imports) on MR !269. Consistent with the standing rule that this branch never carries storage migrations.
PendingPolls value type changes from BoundedVec<…> to u32, and a new PendingPollVoters double-map is introduced. Since the v2 migration was dropped, this is only safe on a fresh chain — which is the explicit assumption here.
Tally::is_passing() is removed. If any out-of-tree consumer (RPC client, off-chain worker) called it, they need to inline tally.ayes > tally.nays.
ProcessingState layout change. Encoded size 10 B → 14 B. No on-disk migration handles this, but ProcessingProgress is transient (only set during multi-block batch processing), so on a fresh-genesis chain this is moot.
MaxVotersReached ordering. Now enforced via a counter compare instead of BoundedVec::try_push. Same cap, but the error surfaces before any tally mutation (previously rolled back, but computed first).
weights.rs says so explicitly. Proof_size numbers are tight (derived from MaxEncodedLen), but the ref_time figures — particularly vote()'s 38_000_000 ps — are estimates. Regenerate via frame-omni-bencher before this lands in a release tag.Vec allocation. process_voters_batch collects the batch into a Vec before iterating, to avoid invalidating the prefix iterator during removal. Bounded by max_voters, so safe — just worth knowing there is a small heap allocation per batch.