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

ZIP-221: integrate MMR tree from librustcash (without Orchard) #2227

Merged
merged 13 commits into from
Jun 11, 2021

Conversation

conradoplg
Copy link
Collaborator

@conradoplg conradoplg commented May 31, 2021

Motivation

ZIP-221 specifies that the hashFinalSaplingRoot field of the header is a commitment to the root of a MMR tree, therefore Zebra must support it.

Solution

librustzcash has code for that in zcash_history, though not for the whole logic. It has a Tree class that contains the logic for adding / removing nodes to the tree, but it's not supposed to be used as a long-term data structure - zcashd instantiates it every time a node is added or removed.

This PR creates a wrapper over it.

The code in this pull request has:

  • Documentation Comments
  • Unit Tests and Property Tests

(will add tests after moving from draft)

Review

@teor2345 will probably want to review it.

This draft PR only adds the wrapping over librustzcash; it still does not handle the logic to keep track of what should be stored.

Draft PR points that I'd like feedback on:

  • Are some of the expects OK? If an error should be returned, how should I wrap it? (I guess I shouldn't return a librustzcash error. I tried using thiserror but couldn't get it to work, so I thought it would be better to confirm which approach to use beforehand)
  • Is the librustzcash wrapping enough for this PR? (i.e. should I add the remaining logic in a separate PR?)
  • Are the method names OK? I copied librustzcash, but I considered using push/pop (similar to Vec)
  • Should the remaining logic be added in the same file / class, or maybe in other place of zebra_chain?

Questions about the design in #2132

  • "use HashMap<u32, Entry>": this is what would be persisted/serialized, and I'd add / remove nodes on it based on the Tree operations, right? Are there any guidelines on how that should be serialized?

Related Issues

Part of #2132

Follow Up Work

Needs Orchard support which is blocked by librustzcash: zcash/librustzcash#368

@conradoplg conradoplg requested a review from teor2345 May 31, 2021 20:14
Copy link
Contributor

@teor2345 teor2345 left a comment

Choose a reason for hiding this comment

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

This all looks good so far, just a bunch of tweaks and suggestions.

Copy link
Collaborator Author

@conradoplg conradoplg left a comment

Choose a reason for hiding this comment

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

I've addressed most of the issues; still need to change to return Result where needed

Copy link
Contributor

@teor2345 teor2345 left a comment

Choose a reason for hiding this comment

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

Just some quick suggestions before I tackle the larger questions on the PR.

@teor2345
Copy link
Contributor

teor2345 commented Jun 2, 2021

Draft PR points that I'd like feedback on:

  • Are some of the expects OK? If an error should be returned, how should I wrap it? (I guess I shouldn't return a librustzcash error. I tried using thiserror but couldn't get it to work, so I thought it would be better to confirm which approach to use beforehand)

In the primitives module, it's ok to return a librustzcash error type.

We'll wrap it in our own error types in the calling code in zebra_state.

  • Is the librustzcash wrapping enough for this PR? (i.e. should I add the remaining logic in a separate PR?)

Yes, let's do separate PRs for each ticket. And any changes within a ticket that are easily split up.

But if there are any tests you can run on this code, let's add them in this PR. The zcash_test crate has block test vectors for Heartwood and Canopy, for both Mainnet and Testnet:
https://github.com/ZcashFoundation/zebra/blob/main/zebra-test/src/vectors/block.rs#L78

Here are some tests you can do:

  1. The root of a new tree, after adding mainnet heartwood activation 903_000, matches the commitment field in the header of 903_001
  2. Similarly for mainnet canopy, testnet heartwood, and testnet canopy
  • Are the method names OK? I copied librustzcash, but I considered using push/pop (similar to Vec)

For primitives, let's match the method names from the library we're wrapping.

  • Should the remaining logic be added in the same file / class, or maybe in other place of zebra_chain?

Let's put primitive operations in zebra_chain, so they're easier to upstream:

  • MMR data type
  • Pruning old blocks before serialization

The other changes should go in zebra_state, because they are tightly integrated with the NonFinalizedState and FinalizedState implementations.

Questions about the design in #2132

  • "use HashMap<u32, Entry>": this is what would be persisted/serialized, and I'd add / remove nodes on it based on the Tree operations, right?

This is the type that will be cloned for each chain in the NonFinalizedState, and then serialized for the best chain in the FinalizedState. (Multiple chains happen around every 1 in 300 blocks. They are typically very short.)

We might want a tuple struct wrapper around HashMap<u32, Entry>. That will make it self-documenting, and easier to implement serialization on.

Are there any guidelines on how that should be serialized?

Here's our current serialization design, you should update it in the serialization PR:
https://github.com/ZcashFoundation/zebra/blob/main/book/src/dev/rfcs/0005-state-updates.md#rocksdb-data-structures

You'll probably want to serialize it with BE32(height) keys, and [u8] values from Entry.write. If we use height, then all our indexes will be unique, because the finalized state only stores a single block at each height.

We can calculate height using index + network_upgrade.activation_height(network). So you might want to store the network_upgrade in the Zebra Tree, and add an activation_height method.

@teor2345

This comment has been minimized.

@teor2345

This comment has been minimized.

Copy link
Collaborator Author

@conradoplg conradoplg left a comment

Choose a reason for hiding this comment

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

Another round of fixes. I had to make a small refactoring because I was mixing up Node and Entry. An Entry contains a Node and the indices of the left/right children, and Tree::new requires Entrys and not Nodes.

I've created a wrapper for Entry that stores it encoded as a buffer vector. However that may be not needed (you can derive the children indices, no need to store them), but for simplicity I chose this route for now.

Tests are not complete either - see next comment I'll post

@conradoplg
Copy link
Collaborator Author

  1. The root of a new tree, after adding mainnet heartwood activation 903_000, matches the commitment field in the header of 903_001

To do that I'll need the Sapling root for that block, not sure how I'll get that. I'll ask in the chat.

@teor2345 teor2345 mentioned this pull request Jun 3, 2021
3 tasks
@teor2345
Copy link
Contributor

teor2345 commented Jun 3, 2021

  1. The root of a new tree, after adding mainnet heartwood activation 903_000, matches the commitment field in the header of 903_001

To do that I'll need the Sapling root for that block, not sure how I'll get that. I'll ask in the chat.

The final sapling roots for all post-sapling test vectors are in PR #2243

@conradoplg conradoplg marked this pull request as ready for review June 4, 2021 17:55
Copy link
Contributor

@teor2345 teor2345 left a comment

Choose a reason for hiding this comment

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

This looks good, just a few minor changes to tidy up the code.

@mpguerra mpguerra linked an issue Jun 7, 2021 that may be closed by this pull request
9 tasks
Copy link
Collaborator Author

@conradoplg conradoplg left a comment

Choose a reason for hiding this comment

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

New round of improvements!

@str4d
Copy link
Contributor

str4d commented Jun 11, 2021

FYI I've implemented the NU5 tree format: zcash/librustzcash#401 (feedback welcome, particularly re: how it interacts with this PR).

Copy link
Contributor

@teor2345 teor2345 left a comment

Choose a reason for hiding this comment

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

This looks like a good API, let's merge so we can start work on the state changes.

@conradoplg do you think we need to make any changes now to prepare for the librustzcash Orchard changes?
https://github.com/zcash/librustzcash/pull/401/files

We can leave all the changes to the Orchard-specific PR, unless they impact the state design.

@conradoplg
Copy link
Collaborator Author

@teor2345 I believe that the only changes to the API of this module will be adding a type parameter to Tree and adding the Orchard tree root hash parameter wherever the Sapling root is being passed. Also I'll probably have a better understanding of any required API adjustments after starting working on the state part. So I'll open an issue for the Orchard part and we can prioritize it if any bigger changes are required

@conradoplg conradoplg merged commit 5c08808 into main Jun 11, 2021
@conradoplg conradoplg deleted the zip221-history branch June 11, 2021 14:25
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.

ZIP-221: Integrate history merkle mountain range from librustzcash
3 participants