-
Notifications
You must be signed in to change notification settings - Fork 228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix VotingPowerCalculator::voting_power_in
#306
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
commit: &Commit, | ||
) -> Option<Vote> { | ||
let (validator_address, timestamp, signature, block_id) = match commit_sig { | ||
CommitSig::BlockIDFlagAbsent { .. } => return None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't we panic here because method assumes non_absent signature? then we won't need the Option<>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is actually the one doing the filtering for non absent signatures, hence the need for the Option. I guess the name isn't great, I am open to suggestions :)
74786f4
to
e973dd9
Compare
- Provide two helper methods for checking validators overlap and if there is enough trust w.r.t. the trusted state. - Only count `Commit` signatures towards the voting power. - Do not rely on `signed_votes` method to get the votes.
e973dd9
to
208cf97
Compare
VotingPowerCalculator::voting_power_in
VotingPowerCalculator::voting_power_in
I pushed a new version which should now be doing the right thing w.r.t. checking trust vs validators signers, but still relies on validator addresses. I guess we can address the latter in a future PR once we are convinced that this version is correct. |
It would be great if someone could run the tests of the
I already did so myself, and I am not too sure about this test:
This test is passing because we expect an error, but I don't think that that is the right one, except if there's perhaps more than one error encoded in the JSON test. In particular, I would have expected the test to result in either @Shivani912 Is that something you could look into? |
let mut tallied_voting_power = 0_u64; | ||
let mut seen_validators = HashSet::new(); | ||
|
||
for (idx, signature) in signatures.into_iter().enumerate() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense to reference the test vector which exercises this particular edge case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What edge case are you referring to here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops perhaps I did not comment the correct line and actually there is an informative comment further down Ensure we only count a validator's power once
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, we currently do not have a test vector which covers this edge case.
cc @Shivani912
Edit: typo
let mut seen_validators = HashSet::new(); | ||
|
||
for (idx, signature) in signatures.into_iter().enumerate() { | ||
let vote = vote_from_non_absent_signature(signature, idx as u64, &signed_header.commit); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar comment as above, IIRC this was a bug in the past.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean counting absent signatures? I don't think that's possible since a CommitSig::BlockIDFlagAbsent
does not contain any of the data required to craft a Vote
(ie. validator_address, timestamp and signature).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True!
@romac There's a bug in this one (issue #290) I'm working on it and will do a PR once I fix it! I feel we really need to have checks (in the test itself) on errors even if the tests pass. I tried to do this at the end of Q1/beginning of Q2 but it seemed it was a little too early to be able to do so. Think this should be on priority for next quarter? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@romac thanks for asking me to review. I wrote what I noticed about differences between the go implementation and the one here. Also, I wrote some comments mostly around VotingPower
, however I might be missing some context so those could just be wrong.
let voting_power = | ||
self.voting_power_of(untrusted_header, untrusted_validators, trust_threshold)?; | ||
|
||
if trust_threshold.is_enough_power(voting_power.tallied, voting_power.total) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mentioned in one of the comments above, I would probably make more sense to just check this in the voting_power_of
and instead of a value just return a boolean value or something else to tell us if everything is fine or there is some error.
|
||
pub trait VotingPowerCalculator: Send { | ||
fn total_power_of(&self, validators: &ValidatorSet) -> u64; | ||
fn voting_power_in( | ||
|
||
fn check_enough_trust( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name and the result of this function were for me a bit misleading as the name would suggest a boolean value (or in this case Ok()
or Error(...)
) and it is more or less used as such since the result value in the Ok case is just ignored.
use tendermint::vote::{SignedVote, Vote}; | ||
|
||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] | ||
pub struct VotingPower { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me this is a bit misleading as the struct has more information than what I would expect by reading just the name. Also, I think it would be beneficial to implement the TODO about the breaking out of the loop as than this would go probably go away.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed that the name is perhaps not the best, but we still need this information to be able to yield a meaningful error in case of lack of trust/signers overlap.
if seen_validators.contains(&vote.validator_address) { | ||
continue; | ||
} else { | ||
seen_validators.insert(vote.validator_address); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One interesting difference between go code and the implementation here is this if
. (go) In case of 2/3 threshold this check is done by comparing the size of the signers validator set and the expected validator set, if both are the same length everything is fine and we don't even do these checks. However in the case where we are interested in trust between two non adjacent headers, then
branch should raise an exception.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@melekes What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess raising an error is probably the proper behavior there. Will already change that, and we can always revert it back if that was wrong.
Awesome, thanks :)
Yes, that would definitely increase our confidence in the tests and thus the correctness of implementation :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is good to go with follow up unit tests coming when we have the fixtures lined up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! I think we still want to break the voting_power_in into two functions, one for check_signers and one for check_enough_trust, and neither should depend on vote.validator_address, but I'll follow up in another issue about that
fn check_enough_trust( | ||
&self, | ||
untrusted_header: &SignedHeader, | ||
untrusted_validators: &ValidatorSet, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are trusted validators
It would be great if you could describe the differences between the two versions somewhere.
I initially tried to fix that as well but eventually chose to focus on fixing the computation itself, and improve it later. |
Done in #377 |
Fix #282
Fix #335
Commit
signatures towards the voting power (BlockIdFlagNil
votes counted when verifying a commit #282)signed_votes
method to get the votesNotes
Most of logic and comments are taken from the Go code, so perhaps some attribution should be added as wellTests are failing because the signatures currently fail to verify, likely because of mismatch with their validator