Skip to content
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

Handle empty block id #66

Conversation

yihuang
Copy link
Contributor

@yihuang yihuang commented Nov 13, 2019

Fix panic when dealing with empty block id in the first block.

@@ -34,7 +34,7 @@ impl block::ParseId for BlockId {

impl From<&block::Id> for BlockId {
fn from(bid: &block::Id) -> Self {
let bid_hash = bid.hash.as_bytes().unwrap().to_vec();
let bid_hash = bid.hash.as_bytes().unwrap_or(&[]).to_vec();
Copy link
Contributor

@tarcieri tarcieri Nov 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a weird way of addressing the problem... doesn't it result in an invalid BlockId?

I'd suggest changing this whole impl to either:

impl From<&block::Id> for Option<BlockId>

or

impl TryFrom<&block::Id> for BlockId

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how golang tendermint handled this. I agree we should do better. But we have to handle the serialization identical to golang version to compute signing bytes, what do you think of that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Rust I think it's more idiomatic to reflect the distinction using the type system

Copy link
Contributor Author

@yihuang yihuang Nov 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can also remove the Option in the parts_header field. I think when hash is not empty, parts_header can't be empty.

@yihuang
Copy link
Contributor Author

yihuang commented Nov 19, 2019

I've changed From trait to TryFrom, represent empty block id as Err.
Also fixed block hash calculation when block_id is empty, make it identical to golang version.

@yihuang yihuang force-pushed the lite_impl_simple_merkle_merged branch 2 times, most recently from 1508abb to 23b0a08 Compare November 25, 2019 07:23
@yihuang
Copy link
Contributor Author

yihuang commented Nov 25, 2019

I've removed branch Hash::Null, represent both optional Hash and block::Id as Option.

@yihuang yihuang force-pushed the lite_impl_simple_merkle_merged branch from 23b0a08 to e6d188d Compare November 25, 2019 07:27
and correctly compute header hash when block id is empty.

Represent empty Hash with Option
@yihuang yihuang force-pushed the lite_impl_simple_merkle_merged branch from e6d188d to 8b1d3dc Compare November 25, 2019 07:45
Copy link
Member

@liamsi liamsi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Copy link
Member

@ebuchman ebuchman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this, though it seems a bit unfortunate. I opened an issue on the Tendermint repo to consider if we should address this more fundamentally: tendermint/tendermint#4241

Copy link
Member

@ebuchman ebuchman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest I don't fully understand this. Why couldn't we somehow utilize the fact that Hash is an enum with a Null variant to deal with this? We changed all the header fields to Options but Options are themselves just enum types so I'd imagine we could do this more cleanly using the Hash type with a Null variant ...

@tarcieri
Copy link
Contributor

@ebuchman it's definitely another option (there and back again). It looks like Hash::Null was removed in #84 as well (possibly at my suggestion).

In retrospect, I can see how Hash::Null might be a better fit for how Tendermint serialization works today. Option is generally the preferred way to represent nullable values in Rust, but it seems like trying to do so might be causing some undue complexity here.

@ebuchman
Copy link
Member

#84 included this PR so that's why it was removed there too. I think for this case where we have these possibly null types where the possibility of being null is almost always only related to the first block (rather than normal operation) we could have a cleaner solution by encapsulating the possibility of null in the underlying types rather than in their wrappers. Still not sure, but it does seem it'd be a bit cleaner and simpler.

@ebuchman
Copy link
Member

Option is generally the preferred way to represent nullable values in Rust ... is this also true for enum values? Ie. Option<MyEnum> is preferred to a MyEnum::Null variant ?

@tarcieri
Copy link
Contributor

Could go either way on that, but I don't frequently see Null variants of enums. It might make sense here though.

@yihuang
Copy link
Contributor Author

yihuang commented Dec 18, 2019

Put a Null variant into Hash force all the code handling Hash needs to consider Null variant. with Option<Hash>, the block related logic needs to consider the None variant. So I think the later one makes more sense.
And it's not only Hash could be empty, things like BlockId, LastCommit also could be empty. And unfortunately, tendermint serialize the empty values as, arbitrary empty values, rather than null, I guess because of protobuf behavior. That's why rust deserialization code needs to take special care for it.
It would be great if we can have some fundamental solution in the tendermint, like trying to find valid values that make sense to the first block.

@ebuchman
Copy link
Member

Would it be better to explicitly code the type of the first block without all the fields that will be empty and then use a BlockEnum that can be either FirstBlock or Block? That would probably best represent what to expect and when fields can actually be empty!

@ebuchman
Copy link
Member

Ok opened an issue: #101

@yihuang
Copy link
Contributor Author

yihuang commented Dec 21, 2019

@ebuchman it's definitely another option (there and back again). It looks like Hash::Null was removed in #84 as well (possibly at my suggestion).

In retrospect, I can see how Hash::Null might be a better fit for how Tendermint serialization works today. Option is generally the preferred way to represent nullable values in Rust, but it seems like trying to do so might be causing some undue complexity here.

I think it's only the deserialisation code needs some extra care here, other than that, Option<Hash> should be similar as Hash with Null variant if not a little bit easier, for example, with Option<Hash>, as_bytes and algorithm methods don't have to return an Option, the internal code who only care about valid Hash don't need to handle Option or Null variant.

@yihuang
Copy link
Contributor Author

yihuang commented Dec 21, 2019

What's the problem with all zero hashes then? It seems that's what rust version of cardano did:
(https://github.com/input-output-hk/chain-libs/blob/master/chain-impl-mockchain/src/key.rs#L225).

@tarcieri
Copy link
Contributor

@yihuang IMO an all-zero hash is about the worst thing you can do... the outputs of digest functions are supposed to be random oracles, and all zeroes are anything but.

The risk is an all-zero hash value is inadvertently included in something like a Merkle tree computation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants