-
Notifications
You must be signed in to change notification settings - Fork 225
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
block id in vote could be empty #103
block id in vote could be empty #103
Conversation
737: Problem: block id field in vote could be empty r=devashishdxt a=yihuang Solution: - Fix the problem in tendermint-rs - Upgrade dependency tendermint-rs PR for this: informalsystems/tendermint-rs#103 Co-authored-by: yihuang <[email protected]>
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.
Thanks for including the test.
Somehow, the block id field in vote of precommits of block could also be empty.
Yes, this is when a validator hasn't seen enough justification to precommit for the block, so they "precommit nil". We include it in the commit to signal that the validator is still online and participating, so they don't get potentially punished for being "offline".
block_id: Some(BlockId { | ||
hash: vote.block_id.hash.as_bytes().to_vec(), | ||
parts_header: vote.block_id.parts.as_ref().map(PartsSetHeader::from), | ||
block_id: vote.block_id.as_ref().map(|block_id| BlockId { |
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.
Interesting that the amino_types vote had the Option but the main vote didn't - I would expect that to have been preserved. I'm not sure, but I think that might mean that if we were eg. using this code to feed the KMS, we'd end up signing something incorrect, since we'd end up with a vote for a Some(BlockId) containing an empty hash and a None header instead of a vote with a None block_id.
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.
As far as I know, the rust version of amino proto is like this:
None
is skiped, just the same as go version skipping a default value.Some(value)
encodes the value as is, won't skip it if it's the default value.
And the go version seems to automatically skip default values, so if rust version encodesSome(default_value)
, it won't match the go version, we need to prevent this from happening. And the new version is actually more correct in this regard.
parts_header: vote.block_id.parts.as_ref().map(PartsSetHeader::from), | ||
block_id: vote.block_id.as_ref().map(|block_id| BlockId { | ||
hash: block_id.hash.as_bytes().to_vec(), | ||
parts_header: block_id.parts.as_ref().map(PartsSetHeader::from), |
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.
Is there a reason why this is an Option? Shouldn't it be always there and instead we use Option on the BlockId itself?
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 believe there are situation when block_id has valid hash
, but don't has parts_header
, or have a parts_header
with default values(can't recall the case for now), for the go version of protobuf, default value is skipped automatically when encoding, but rust version won't check the default value automatically, so we need an Option
to keep the encoding exactly the same as the go version.
@@ -144,3 +144,41 @@ where | |||
let v = Option::deserialize(deserializer)?; | |||
Ok(v.map(|Wrapper(a)| a)) | |||
} | |||
|
|||
/// Parse empty block id as None. | |||
pub(crate) fn parse_non_empty_block_id<'de, D>( |
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'm kind of hoping this will go away with #101. In which case maybe we'll want to use an explicit VoteValue enum with variants BlockId and Nil ...
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 actually don't think enum
would help us in these cases, because it's adding a branch just like Option
, you just move the branch from Option
to your own type.
The core problem lies in the protobuf semantic of optional field and default values, and the inconsistency between json and binary version of protobuf encoding.
In the semantic of protobuf, basically, there's no separation between null value and default value, they can be encoded as the same(skip), but in the json version, the default value is encoded as is, not skipped or null, it is actually correct in protobuf's semantic, because a default value is the same as a skipped or null value, but when we use Option
to distingish null value and default value in rust, we need extra effort to handle this.
Let me rephrase our current situation here:
|
Can we somehow enable circleci for PRs from forks? That would be amazing! |
merging in master reverted removing |
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 we should merge this as is and deal with how-to properly deal with serialization when we have a clearer picture!
As always, thanks @yihuang!
Somehow, the block id field in vote of precommits of block could also be empty.
Real world test case included.