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

Add SimpleMerkleTree #36

Merged
merged 38 commits into from
Feb 27, 2024
Merged

Add SimpleMerkleTree #36

merged 38 commits into from
Feb 27, 2024

Conversation

ernestognw
Copy link
Member

Description

Alternative to #31 with less repeated code.
The SimpleMerkleTree also receives BytesLikes leaves so that the input is consistent with StandardMerkleTree and we can use inheritance to avoid code repetition.

I removed the T extends BytesLike[] for simplicity and because BytesLike[] is the only input supported.

Note that StandardMerkleTree overloaded functions are for backwards compatibility. We can get rid of them if we release a new non-backwards compatible version

@Amxx Amxx changed the base branch from master to support-raw-leaves February 23, 2024 09:30
@Amxx Amxx changed the base branch from support-raw-leaves to master February 23, 2024 09:37
src/bytes.ts Outdated Show resolved Hide resolved
src/bytes.ts Outdated Show resolved Hide resolved
src/standard.ts Outdated Show resolved Hide resolved
src/standard.ts Outdated Show resolved Hide resolved
@Amxx
Copy link
Contributor

Amxx commented Feb 23, 2024

We would need to find a way to support any[] values. Elements of the leafs are not all BytesLike.
For example, we must be able to do

    const leaves = [
      [0, []],
      [1, ['openzeppelin']],
      [2, ['hello', 'world']],
      [3, ['merkle', 'tree']],
    ];
    const types = [ 'uint256', 'string[]' ];
    const tree = StandardMerkleTree.of(leaves, types);

We should also be able to load dumps that have been produced with older version of the library

EDIT: solved in this comment

@Amxx
Copy link
Contributor

Amxx commented Feb 23, 2024

There is also something that is bothering me with the way options are stored in the trees.

If possible I'd like to avoid that. I get that its necessary to set the options in SimpleMerkleTree so that hashleaf in the StandardMerkleTree works as part of the SimpleMerkleTree's construction (so before we can set a leafEncoding value in the standard tree ... but maybe there is another way

EDIT: solved in this comment

src/simple.ts Outdated Show resolved Hide resolved
@Amxx
Copy link
Contributor

Amxx commented Feb 23, 2024

Another think I just realized is broken in this PR is the interface of getMultiProof(leaves: (number | T)[]): MultiProof<string, T>

@Amxx
Copy link
Contributor

Amxx commented Feb 23, 2024

I iterated on this PR in https://github.com/OpenZeppelin/merkle-tree/tree/support-raw-leaves-inheritance-amxx

Main differences:

  • Interface MerkleTree<T> and class MerkleTree<T> provide a generic base for all tree types (T is the leaf type)
  • SimpleMerkleTree extends MerkleTree<BytesLike>
  • StandardMerkleTree<T extends any[]> extends MerkleTree<T>
  • Re-introduce support for non BytesLike element in the leaf (with corresponding tests)
  • Reduce the number of variants to reduce code (this includes removing putting leafEncoding in the options)
  • Re-add the generic multiproof to keep support for Multiproof<T> in StandardMerkleTree

I think this is looking better than #31

Diff can be seen here

index.test.ts Outdated Show resolved Hide resolved
src/simple.ts Outdated Show resolved Hide resolved
src/standard.ts Show resolved Hide resolved
src/standard.ts Outdated
this.hashLookup = Object.fromEntries(
values.map(({ value }, valueIndex) => [hex(standardLeafHash(value, leafEncoding)), valueIndex]),
);
super(tree, values, standardLeafHasher.bind(null, leafEncoding));
Copy link
Member Author

Choose a reason for hiding this comment

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

Is there any reason to use .bind? I think it's more confusing than:

Suggested change
super(tree, values, standardLeafHasher.bind(null, leafEncoding));
super(tree, values, v => standardLeafHasher(leafEncoding, v));

src/standard.ts Outdated
leafEncoding: string[],
options: MerkleTreeOptions = {},
): StandardMerkleTree<T> {
const [tree, indexedValues] = MerkleTreeImpl.prepare(values, options, standardLeafHasher.bind(null, leafEncoding));
Copy link
Member Author

Choose a reason for hiding this comment

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

Same as the other

Suggested change
const [tree, indexedValues] = MerkleTreeImpl.prepare(values, options, standardLeafHasher.bind(null, leafEncoding));
const [tree, indexedValues] = MerkleTreeImpl.prepare(values, options, v => standardLeafHasher(leafEncoding, v));

src/merkletree.ts Outdated Show resolved Hide resolved
src/merkletree.ts Outdated Show resolved Hide resolved
src/simple.ts Outdated Show resolved Hide resolved
src/standard.test.ts Outdated Show resolved Hide resolved
src/standard.test.ts Outdated Show resolved Hide resolved
src/simple.ts Show resolved Hide resolved
src/merkletree.ts Outdated Show resolved Hide resolved
src/simple.ts Outdated Show resolved Hide resolved
@ernestognw
Copy link
Member Author

I reviewed the updates and generally addressed comments because I think they're not too controversial. Added explanations accordingly.

The main differences are:

  1. Each StandardMerkleTree and SimpleMerkleTree now define their format on their own data types. Also MerkleTree was marked as abstract so that dump() remains virtual until implemented.
  2. Changed the leafHasher function in the StandardMerkleTree to an arrow function. I think the alternative bind is confusing because it specifies this as null.
  3. Specialized the errors into InvalidArgumentError and InvariantError. I noticed there was a lot of new Error around when we already had a throwError helper.

At this point, the PR looks good to me. I left a couple of open comments that need comments. Also, appreciate feedback on point number 3 regarding errors. If that's too much to discuss here let's revert to ada28f1

values: { value: T; treeIndex: number }[];
};

export interface MerkleTree<T> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Ideally we would have a way to prevent T from being number, as this leads to confusion between leaf value and leaf index.

I tried doing:

export abstract class MerkleTreeImpl<T, _ = Exclude<T, number>> implements MerkleTree<T> {

But it doesn't work as I intended.

Copy link
Member Author

Choose a reason for hiding this comment

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

Couldn't make it work. I think the best we can do is restrict it at the MerkleTree constructor.

I added an update, it'd be the last thing imo, otherwise LGTM.

Copy link
Contributor

Choose a reason for hiding this comment

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

LGTM

Copy link
Contributor

Choose a reason for hiding this comment

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

For your curiosity, a way in which it's possible to achieve something like Exclude<any, number> is to use conditional types.

function foo<T>(n : T extends number ? never : T): void {}

foo(1) // error
foo("a") // ok

But this is obscure and I would not recommend it. I'm not even sure I understand why it works. 😛

@Amxx Amxx mentioned this pull request Feb 26, 2024
8 tasks
@ernestognw
Copy link
Member Author

The new "snapshot" files don't convince me but I think it's not a big deal given we don't have too many variations.

I guess we can merge this but I guess is another strong argument in favor of Ava as a testing framework.

@ernestognw ernestognw changed the title Simple MerkleTree alternative Add SimpleMerkleTree Feb 26, 2024
src/bytes.ts Show resolved Hide resolved
Copy link
Contributor

@frangio frangio left a comment

Choose a reason for hiding this comment

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

Mostly minor comments but also I think one blocker. Feel free to ignore any nitpicks.

src/core.ts Outdated Show resolved Hide resolved
src/core.ts Outdated Show resolved Hide resolved
src/core.ts Outdated Show resolved Hide resolved
src/core.ts Outdated Show resolved Hide resolved
src/simple.ts Outdated Show resolved Hide resolved
src/merkletree.ts Outdated Show resolved Hide resolved
src/merkletree.ts Outdated Show resolved Hide resolved
src/bytes.ts Show resolved Hide resolved
@Amxx Amxx merged commit 952fd9f into master Feb 27, 2024
3 checks passed
@Amxx Amxx deleted the support-raw-leaves-inheritance branch February 27, 2024 19:57
@Amxx Amxx mentioned this pull request Feb 27, 2024
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.

3 participants