Skip to content

Add trait bound to Merkle tree method; fix typing#2864

Merged
apoelstra merged 7 commits intorust-bitcoin:masterfrom
apoelstra:2024-06--merkle-tree
Jun 19, 2024
Merged

Add trait bound to Merkle tree method; fix typing#2864
apoelstra merged 7 commits intorust-bitcoin:masterfrom
apoelstra:2024-06--merkle-tree

Conversation

@apoelstra
Copy link
Copy Markdown
Member

Alternate to #2853. Takes patch 1 from that PR.

@github-actions github-actions bot added the C-bitcoin PRs modifying the bitcoin crate label Jun 13, 2024
@apoelstra apoelstra force-pushed the 2024-06--merkle-tree branch from 60dce23 to 4dec788 Compare June 13, 2024 14:48
@coveralls
Copy link
Copy Markdown

coveralls commented Jun 13, 2024

Pull Request Test Coverage Report for Build 9501833057

Details

  • 34 of 34 (100.0%) changed or added relevant lines in 3 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.007%) to 83.293%

Totals Coverage Status
Change from base Build 9500447857: 0.007%
Covered Lines: 19523
Relevant Lines: 23439

💛 - Coveralls

Copy link
Copy Markdown
Member

@tcharding tcharding 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 putting this up, I would have never got here because I was stuck on the inline conundrum. I particular like the combine function.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This moves the problem I described (that I thought needed unsafe to solve) onto the user.

Said another way, what is the point of providing this function now, presumably the caller has a vector of Txids and needs to convert them to MerkleNode implementors?

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.

We could take the pub away if it worries you. It is only used within the library as a helper function to implement calculate_root, and it exists to theoretically allow people to implement calculate_root without any allocations.

I doubt it has ever been actually used by anybody else in the history of this library.

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.

BTW the algorithm implemented here is super memory-wasteful and bizarrely combines recursion with an explicit Vec, giving us the worst of both worlds (and spread across 3 functions for some reason). I'll do a followup PR that fixes this since I'm looking at it.

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.

Nah, I'll just do it here. If I'm breaking the API I should go all the way. Added two commits.

@coveralls
Copy link
Copy Markdown

coveralls commented Jun 14, 2024

Pull Request Test Coverage Report for Build 9518993748

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 43 of 43 (100.0%) changed or added relevant lines in 3 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage decreased (-0.005%) to 83.281%

Totals Coverage Status
Change from base Build 9500447857: -0.005%
Covered Lines: 19497
Relevant Lines: 23411

💛 - Coveralls

@tcharding
Copy link
Copy Markdown
Member

Looks good, its non-trivial to review for me though. Perhaps we should have some regression tests before we make the change? Currently only one unit test.

@apoelstra
Copy link
Copy Markdown
Member Author

@tcharding there are like a dozen regression tests in merkle_tree/block.rs, and the "one unit test" is also a regression test.

Try changing the tree algorithm and see how much breaks.

@apoelstra apoelstra mentioned this pull request Jun 15, 2024
@apoelstra apoelstra force-pushed the 2024-06--merkle-tree branch 2 times, most recently from 7384679 to 1835605 Compare June 15, 2024 13:14
@coveralls
Copy link
Copy Markdown

coveralls commented Jun 15, 2024

Pull Request Test Coverage Report for Build 9528363460

Details

  • 42 of 42 (100.0%) changed or added relevant lines in 3 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage decreased (-0.006%) to 83.13%

Totals Coverage Status
Change from base Build 9528230836: -0.006%
Covered Lines: 19519
Relevant Lines: 23480

💛 - Coveralls

@coveralls
Copy link
Copy Markdown

coveralls commented Jun 15, 2024

Pull Request Test Coverage Report for Build 9528371517

Details

  • 42 of 42 (100.0%) changed or added relevant lines in 3 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage decreased (-0.006%) to 83.13%

Totals Coverage Status
Change from base Build 9528230836: -0.006%
Covered Lines: 19519
Relevant Lines: 23480

💛 - Coveralls

@apoelstra apoelstra force-pushed the 2024-06--merkle-tree branch from 1835605 to 4cf9752 Compare June 16, 2024 15:00
@coveralls
Copy link
Copy Markdown

coveralls commented Jun 16, 2024

Pull Request Test Coverage Report for Build 9536877191

Details

  • 42 of 42 (100.0%) changed or added relevant lines in 3 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage decreased (-0.006%) to 83.137%

Totals Coverage Status
Change from base Build 9536654741: -0.006%
Covered Lines: 19519
Relevant Lines: 23478

💛 - Coveralls

@coveralls
Copy link
Copy Markdown

coveralls commented Jun 16, 2024

Pull Request Test Coverage Report for Build 9536960269

Details

  • 45 of 45 (100.0%) changed or added relevant lines in 3 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage decreased (-0.01%) to 83.133%

Totals Coverage Status
Change from base Build 9536654741: -0.01%
Covered Lines: 19513
Relevant Lines: 23472

💛 - Coveralls

@tcharding
Copy link
Copy Markdown
Member

My bad, I appreciate you have also probably written this algorithm a dozen times before. I was sick in bed reviewing and had zero confidence in my ability to fully grok the change. I didn't look in block.rs at the time either :(

ACK 66ca2c20519b726e3c5931d6b99eedc3aec60106

tcharding
tcharding previously approved these changes Jun 16, 2024
Comment on lines 43 to 54
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Coming back for a post ack review :)

Perhaps:

    /// Converts a hash to a leaf node of the tree.
    fn from_leaf(leaf: Self::Leaf) -> Self;

    /// Combines two nodes to get a single node.
    ///
    /// The final node of the tree is the Merkle root.
    fn combine(&self, other: &Self) -> Self;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
/// Returns `None` iff the iterator was empty.
/// # Returns
///
/// Returns `None` iff the iterator was empty.

Kixunil
Kixunil previously approved these changes Jun 17, 2024
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 66ca2c20519b726e3c5931d6b99eedc3aec60106

Nice changes overall!

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 find n /= 2 more readable

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.

n + 2 == 1 is more readable, the compiler knows how to optimize it.

@Kixunil
Copy link
Copy Markdown
Collaborator

Kixunil commented Jun 17, 2024

One more thing: this does not address Taproot. I don't remember what the differences are. Can we unify them?

@apoelstra
Copy link
Copy Markdown
Member Author

I don't remember what the differences are. Can we unify them?

Unfortunately not. The differences in Taproot are:

  • The use of a tagged hash rather than sha256 (probably not too hard to handle)
  • Combining hashes in lexicographic order to make an "unordered tree" (can be handled by the new MerkleNode::combine trait function).
  • Hashing leaves properly rather than just pretending like they're nodes (can be handled by MerkleNode::from_leaf)
  • Allowing arbitrarily-shaped binary trees rather than just balaced trees.
  • Treating single branches as leaves rather than doing this dumb doubling-up trick to simulate a balanced tree.

It's the last two points where we have a meaningfully different tree algorithm and I don't think it makes sense to unify them.

@Kixunil
Copy link
Copy Markdown
Collaborator

Kixunil commented Jun 17, 2024

It's the last two points where we have a meaningfully different tree algorithm and I don't think it makes sense to unify them.

I believe you're right then. Might be worth mention in the code.

Currently we are defining the two merkle tree hash types in the `block`
module, a better home for them is the `merkle_tree` module.

This is an API breaking change because the types were public in the
`block` module, however the change should/could be unnoticeable to users
if they use the crate level re-export - which is maintained.
These are pretty noisy but it's just a move.
These changes are nontrivial. They

* Drop the `From` impls from (w)txids to (w)TxMerkleRoots
* Introduce a new trait and bound calculate_root on it...
* ...in such a way that its return value cannot be inferred without
  further hints (though in practice this doesn't matter because usually
  the return value is immediately assigned to something with a known
  type such as a BlockHeader field)
Drop recursion, reduce memory usage to be logarithmic in size of tree
rather than linear, and put it all in one function rather than three.

Also make the method an trait method on MerkleNode which makes it a
easier on type inference, by writing e.g. TxMerkleNode::calculate_root.
@apoelstra apoelstra dismissed stale reviews from Kixunil and tcharding via 90b6d67 June 18, 2024 16:12
@apoelstra apoelstra force-pushed the 2024-06--merkle-tree branch from 66ca2c2 to 90b6d67 Compare June 18, 2024 16:12
@apoelstra
Copy link
Copy Markdown
Member Author

Rebased and expanded comment.

@coveralls
Copy link
Copy Markdown

coveralls commented Jun 18, 2024

Pull Request Test Coverage Report for Build 9568518880

Details

  • 46 of 46 (100.0%) changed or added relevant lines in 3 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage decreased (-0.01%) to 83.119%

Totals Coverage Status
Change from base Build 9551233748: -0.01%
Covered Lines: 19548
Relevant Lines: 23518

💛 - Coveralls

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 90b6d67

Copy link
Copy Markdown
Member

@tcharding tcharding left a comment

Choose a reason for hiding this comment

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

ACK 90b6d67

@apoelstra apoelstra merged commit d063cc1 into rust-bitcoin:master Jun 19, 2024
@apoelstra apoelstra deleted the 2024-06--merkle-tree branch June 19, 2024 14:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C-bitcoin PRs modifying the bitcoin crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants