Skip to content

Refactor bitcoin_merkle_root functions#710

Merged
RCasatta merged 2 commits intorust-bitcoin:masterfrom
tcharding:merkel-root
Dec 16, 2021
Merged

Refactor bitcoin_merkle_root functions#710
RCasatta merged 2 commits intorust-bitcoin:masterfrom
tcharding:merkel-root

Conversation

@tcharding
Copy link
Copy Markdown
Member

@tcharding tcharding commented Nov 18, 2021

Do two minor refactorings to the `bitcoin_merkle_root[_inline] functions.

This PR has grown, is no longer a refactoring because the two functions have been changed to return an Option.

First patch is cleanup. Here is the commit message for the second patch

The merkle_root of an empty tree is undefined, this is the only error
case we have for the two `bitcoin_merkle_root*` functions. We can fully
describe this error case by returning an `Option` if args are found to
be empty.

While we are at it, refactor out a recursive helper function to make
reading the code between the two functions easier.

@Kixunil Kixunil added code quality Makes code easier to understand and less likely to lead to problems P-low Low priority labels Nov 19, 2021
Kixunil
Kixunil previously approved these changes Nov 19, 2021
Copy link
Copy Markdown
Collaborator

@Kixunil Kixunil left a comment

Choose a reason for hiding this comment

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

Very good!

ACK 150d9559f164164a5fb30897bec9336a54af0e05

Copy link
Copy Markdown
Collaborator

@dr-orlovsky dr-orlovsky left a comment

Choose a reason for hiding this comment

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

Yes, this makes code much more readable. However it seems like there is a way to get rid from unnecessary unwrap as well

@tcharding
Copy link
Copy Markdown
Member Author

I've added two more patches that quite heavily refactor the two functions in question. Please re-review at your leisure, this work is really just for the pure fun of beautiful code.

(Includes fix to typo that I recently introduced 'merkel' -> 'merkle' :(

Copy link
Copy Markdown
Collaborator

@dr-orlovsky dr-orlovsky left a comment

Choose a reason for hiding this comment

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

I think that we should return a error and not panic if the tree is empty, since it may be quite a common situation

src/util/hash.rs Outdated
Comment on lines 36 to 49
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This can be improved even more by

match data.len() {
    O => panic!(),
    1 => data[1],
    2 => merkle_root_r(data),
}

Copy link
Copy Markdown
Member Author

@tcharding tcharding Nov 24, 2021

Choose a reason for hiding this comment

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

I deleted my original thoughts because they turned out to be wrong and were really long. PR now includes this suggestion.

src/util/hash.rs Outdated
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This same condition is in markle_root_r, so probably not needed here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I added it there for two reasons:

  1. It gives a line of code that can carry the code comment
  2. In merkle_root_r this call is the recursion base case, here it is not, so slightly different

I was trying to separate the recursion from the two 'higher' functions.

src/util/hash.rs Outdated
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Unrelated but I think ExactSIzeIterator is not actually required. Just allocate Vec based on size_hint and let the Vec resize itself if the hint is incorrect (that's actually what happens today - len is not strictly guaranteed to be correct).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

But then we cannot call iter.len()?

Copy link
Copy Markdown
Collaborator

@Kixunil Kixunil Nov 30, 2021

Choose a reason for hiding this comment

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

I don't think we need to:

let first = iter.next()?;
let second = match iter.next() {
    Some(second) => second,
    // A single hash is by definition the merkle root.
    None => return Some(first),
};
let (min, max) = iter.size_hint();
let mut hashes = Vec::with_capacity(max.unwrap_or(min) / 2 + 1);
let iter = [first, second].iter().chain(iter);
// Continue using `hashes` and `iter` as before.

Note: s/hashes/alloc/g, s/iter/hashes per latest change.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

let iter = [first, second].iter().chain(iter);

I couldn't get this line to build, I used instead:

let mut hashes = iter::once(first).chain(iter::once(second)).chain(hashes);

src/util/hash.rs Outdated
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think it'd be nice to highlight the word "hashes" here and above to make it more obvious it's not data.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Nice, more descriptive and further unifies the functions!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I believe you forgot about highlighting.

Copy link
Copy Markdown
Collaborator

@Kixunil Kixunil left a comment

Choose a reason for hiding this comment

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

Looks good, had a few more ideas. I'm unsure if panic! is better than Option but I think Result would be overkill.

@tcharding
Copy link
Copy Markdown
Member Author

I think that we should return a error and not panic if the tree is empty, since it may be quite a common situation

Thanks for the review @dr-orlovsky, I'm leaning towards @Kixunil suggestion of using an Option. I'll outline the reasoning here to make sure I'm not missing something in your suggestion of returning an error.

  • No panic because if we panic we are placing a burden on the caller to check their input
  • If the merkle root of an empty tree is undefined then its not really an error to try and calculate it it just does not exist, implies Option
  • There is only one error case with no additional information other than 'input was empty', Option fully describes this case

I'll push this change, please comment though if you'd like me to re-consider using an error.

@dr-orlovsky
Copy link
Copy Markdown
Collaborator

I think the Option is fine. I was not that much about error as much about not panicking

@tcharding
Copy link
Copy Markdown
Member Author

tcharding commented Nov 24, 2021

I'm not sure what is the accepted protocol re force pushing in rust-bitcoin? I've tried to find a balance between keeping the PR consisting of sane, easily reviewed patches and making reviewers have to re-review everything again. If there are any strong opinions on force pushing, please let me know. Thanks again for all the reviews.

@tcharding
Copy link
Copy Markdown
Member Author

I think the Option is fine. I was not that much about error as much about not panicking

Cool, thanks. Easy consensus :)

@dr-orlovsky
Copy link
Copy Markdown
Collaborator

@tcharding from my experience here it seems that force-pushes are more preferable than bloating commit log with fix commits. As for re-ACKs, the normal solution for that is using git diff (if no rebase happened), git range-diff master <old-commit> <new-commit> so this does not make a big problem

dr-orlovsky
dr-orlovsky previously approved these changes Nov 25, 2021
Copy link
Copy Markdown
Collaborator

@dr-orlovsky dr-orlovsky left a comment

Choose a reason for hiding this comment

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

utACK e0e708b1a4af00fa735c2fb16cb895499ebbef12

@tcharding
Copy link
Copy Markdown
Member Author

This PR was getting out of control, I've squashed all the hash.rs changes into a single commit. Please re-review at your leisure :)

Copy link
Copy Markdown
Collaborator

@Kixunil Kixunil left a comment

Choose a reason for hiding this comment

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

I believe at least undocumented panics should be resolved.

src/util/hash.rs Outdated
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I believe you forgot about highlighting.

@tcharding
Copy link
Copy Markdown
Member Author

I believe you forgot about highlighting.

I don't understand this comment @Kixunil, can you repeat please? (There is no response box under the original comment for some reason.)

@Kixunil
Copy link
Copy Markdown
Collaborator

Kixunil commented Dec 3, 2021

I suggested adding asterisks around the word "hashes" in the docs, so it becomes hashes. This will make it clear it's not data. I guess you misunderstood what I meant by "highlighting".

Kixunil
Kixunil previously approved these changes Dec 3, 2021
Copy link
Copy Markdown
Collaborator

@Kixunil Kixunil left a comment

Choose a reason for hiding this comment

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

ACK 086f4807bad8cb5d06f2b8bbc9d1c506d4822328

Nice improvement! I consider my comments to be optional.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Maybe this is nicer, I'm not sure, do as you wish.

self.merkle_root()
    .map(|merkle_root| self.header.merkle_root == merkle_root)
    .unwrap_or(false)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I played with the combinator here as suggested and also on the call to self.witness_root(). It wasn't clearly better either way to me so I ended going with the code as it is since seemed slightly easier to read.

@Kixunil Kixunil added the one ack PRs that have one ACK, so one more can progress them label Dec 3, 2021
@tcharding
Copy link
Copy Markdown
Member Author

I suggested adding asterisks around the word "hashes" in the docs, so it becomes hashes. This will make it clear it's not data. I guess you misunderstood what I meant by "highlighting".

Ooooh, my bad, good idea. Will add.

Fix typo in test function name to use the correct spelling of
'merkle' (not 'merkel').
The merkle_root of an empty tree is undefined, this is the only error
case we have for the two `bitcoin_merkle_root*` functions. We can fully
describe this error case by returning an `Option` if args are found to
be empty. We can do the same for the wrapper functions in `block`
module.

While we are at it, refactor out a recursive helper function to make
reading the code between the two functions easier.
@tcharding
Copy link
Copy Markdown
Member Author

Added italics to 'hashes' as suggested, rebased, force pushed.

Copy link
Copy Markdown
Collaborator

@Kixunil Kixunil left a comment

Choose a reason for hiding this comment

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

ACK b454cf8

@Kixunil Kixunil added the API break This PR requires a version bump for the next release label Dec 10, 2021
Copy link
Copy Markdown
Collaborator

@dr-orlovsky dr-orlovsky left a comment

Choose a reason for hiding this comment

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

Seems like we have a bug in the implementation - pls see my comment

Copy link
Copy Markdown
Collaborator

@dr-orlovsky dr-orlovsky left a comment

Choose a reason for hiding this comment

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

ACK b454cf8

@tcharding
Copy link
Copy Markdown
Member Author

'one-ack' badge is stale :)

Thanks for all the reviews fellas.

@Kixunil Kixunil removed the one ack PRs that have one ACK, so one more can progress them label Dec 13, 2021
@Kixunil
Copy link
Copy Markdown
Collaborator

Kixunil commented Dec 13, 2021

Would merge it but I still didn't setup commit signing properly. My understanding is it's not nice to merge without it.

@RCasatta RCasatta merged commit fe43e3c into rust-bitcoin:master Dec 16, 2021
@tcharding tcharding deleted the merkel-root branch August 17, 2023 02:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API break This PR requires a version bump for the next release code quality Makes code easier to understand and less likely to lead to problems P-low Low priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants