feat(contract): introduce generic voting structs#2739
Conversation
5bf1d50 to
b6fec1b
Compare
|
Code Review: feat(contract): introduce generic voting structs Reviewed the full diff: new No critical issues found. The implementation is solid:
Minor nits (non-blocking):
✅ Approved |
netrome
left a comment
There was a problem hiding this comment.
Just started looking. Noticed a lot of trait bounds on structs, which should be avoided. I'll continue reviewing tomorrow.
netrome
left a comment
There was a problem hiding this comment.
Nice stuff overall, only a minor hard blocker about toe TODO in threshold votes - it's unclear to me if this is something intended for this PR that was overlooked or a planned follow-up. If it's the latter we should reference the issue in the TODO.
| votes_by_voter: BTreeMap<V, ProposalId>, | ||
| votes_by_proposal: BTreeMap<ProposalId, VoterSet<V>>, |
There was a problem hiding this comment.
No strong opinion but I'm curious of the choice of BTreeMap here, when we use HashMap and IterableMap in the proposal registry. They all seem very similar to me so I'd expect us to be able to converge to a single map type.
There was a problem hiding this comment.
These votes will have at most num_voters entries each and contain pretty small data (type u64 and AccountId, both of which are rather small-ish).
This is fine until we exceed 100 participants, which I don't think will happen anytime soon.
There was a problem hiding this comment.
After 1c306a8, we no longer use HashMap. Instead, we have the following BTreeMaps:
Voter <-> ProposalId, containing at mostnum_votersentriesProposalId <-> VoterSet<Voter>. The map contains at mostnum_votersentries and its valuesVoterSetsare disjoint, with their union containing at mostnum_voterselements.ProposalHash <-> ProposalId, containing at mostnum_votersentries, likely much fewer.
And we have one IterableMap: proposal_by_id: IterableMap<ProposalId, (ProposalHash, Proposal)>.
While proposal_by_id also contains less than num_voters entries and thus, is still below the threshold for which native collection types are recommended (taking this guide as reference), we prefer an IterableMap here, because Proposal can be potentially quite large.
An IterableMap ensures we only deserialize the proposal when truly needed.
1c4ae34 to
632b1ea
Compare
There was a problem hiding this comment.
note: this file was votes.rs before and was just renamed. This is the diff to the file currently on main:
< use super::thresholds::ThresholdParameters;
< use super::{key_state::AuthenticatedAccountId, participants::Participants};
---
> use crate::primitives::thresholds::ThresholdParameters;
> use crate::primitives::{key_state::AuthenticatedAccountId, participants::Participants};
7a8,9
> // TODO(#2825): Replace with Votes<AuthenticatedAccountId, ThresholdParameters> from generic_votes.rs
> // once this type is moved out of RunningContractState (which requires Clone + PartialEq + JSON).
DSharifi
left a comment
There was a problem hiding this comment.
Thanks, for this change. It looks quite good, but I haven't fully understood why some of the APIs are designed as they are, but I assume there's a good reason for it.
I think it would be easier to understand the API if you could migrate one of the existing voting patterns to use these new structs as a PR stacked on top of this one.
| P: ProposalBounds, | ||
| { | ||
| id_by_proposal: BTreeMap<ProposalHash, ProposalId>, | ||
| proposals_by_id: IterableMap<ProposalId, (ProposalHash, P)>, |
There was a problem hiding this comment.
note for myself:
is ProposalHash used here?
| } | ||
|
|
||
| /// Returns the registry in a form fit for json serialization. | ||
| pub(super) fn all(&self) -> BTreeMap<ProposalId, (ProposalHash, P)> { |
There was a problem hiding this comment.
Note:
This function is currently dead code (only used in tests)
| pub(super) fn next(&self) -> Self { | ||
| let Some(next) = self.0.checked_add(1) else { | ||
| near_sdk::env::panic_str("overflow in proposal id") | ||
| }; | ||
| ProposalId(next) |
There was a problem hiding this comment.
Wonder if a mutable increment would be nicer to have, so we don't have to increment and assign.
| // counts all the votes for which `predicate` returns true | ||
| pub fn count_for(&self, predicate: impl Fn(&V) -> bool) -> usize { |
There was a problem hiding this comment.
How do you envision this being used?
Currently I only see it used in the unit tests.
There was a problem hiding this comment.
Some of our current voting structs are only comparing the number of collected votes to the required threshold for accepting a proposal. The issue with this is that we risk counting votes of former participants, who have since left the network. We rely on votes getting cleaned up after each resharing.
Now, the downside of course is that calling this method is more expensive than simply comparing two integers. But I have not yet seen any indication that this would result in intolerable gas costs.
1c306a8 to
143e074
Compare
Thanks for the review! Regarding usage of this API, please take a look at #2930. |
0efb84e to
6a579df
Compare
|
Putting this on hold for now. Using the hash allows us to save some code bytes and simplify the structs. Take a look at this comment: #2930 (comment). I will open another PR with the simplified voting structs such that we can compare. c.f. #2934 |
|
closing this in favor of #2934 |
resolves #1573