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

SSZ v2 #223

Merged
merged 19 commits into from
Mar 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- uses: actions/setup-node@v2-beta
with:
node-version: "14.16.0"
- run: yarn
- run: yarn && yarn build

- name: Run benchmarks
run: yarn benchmark
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ jobs:
- uses: actions/checkout@v1
- name: Bootstrap
run: yarn
- name: Check types
run: yarn check-types
- name: Build
run: yarn build
- name: Check types
run: yarn check-types
- name: Lint
run: yarn lint
- name: Tests
Expand Down Expand Up @@ -47,9 +47,9 @@ jobs:
- name: Spec tests altair-mainnet
run: LODESTAR_PRESET=mainnet LODESTAR_FORK=altair ../../node_modules/.bin/mocha test/spec/ssz_static.test.ts
working-directory: packages/ssz
- name: Spec tests merge-minimal
run: LODESTAR_PRESET=minimal LODESTAR_FORK=merge ../../node_modules/.bin/mocha test/spec/ssz_static.test.ts
- name: Spec tests bellatrix-minimal
run: LODESTAR_PRESET=minimal LODESTAR_FORK=bellatrix ../../node_modules/.bin/mocha test/spec/ssz_static.test.ts
working-directory: packages/ssz
- name: Spec tests merge-mainnet
run: LODESTAR_PRESET=mainnet LODESTAR_FORK=merge ../../node_modules/.bin/mocha test/spec/ssz_static.test.ts
- name: Spec tests bellatrix-mainnet
run: LODESTAR_PRESET=mainnet LODESTAR_FORK=bellatrix ../../node_modules/.bin/mocha test/spec/ssz_static.test.ts
working-directory: packages/ssz
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ yarn-error.log
.idea/
.vscode

coverage
lib
packages/*/package-lock.json
packages/ssz/spec-tests

benchmark_data/
.failedTest.txt
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
"packages/*"
],
"scripts": {
"postinstall": "yarn build",
"clean": "rm -rf ./packages/*/lib ./packages/*/*.tsbuildinfo",
"build": "yarn clean && lerna run build",
"lint": "lerna run lint --no-bail",
"check-types": "lerna run check-types --no-bail",
"coverage": "lerna run coverage --no-bail",
"test": "lerna run test --no-bail",
"benchmark": "NODE_OPTIONS=--max_old_space_size=4096 benchmark --config .benchrc.yaml 'packages/*/test/perf/**/*.test.ts'",
"benchmark:files": "NODE_OPTIONS=--max_old_space_size=4096 benchmark --config .benchrc.yaml",
"benchmark": "yarn benchmark:files 'packages/*/test/perf/**/*.test.ts'",
"benchmark:local": "yarn benchmark --local",
"publish:release": "lerna publish from-package --yes --no-verify-access",
"release": "lerna version --no-push --sign-git-commit",
Expand Down
46 changes: 43 additions & 3 deletions packages/persistent-merkle-tree/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ A binary merkle tree implemented as a [persistent data structure](https://en.wik

import {LeafNode, BranchNode} from "@chainsafe/persistent-merkle-tree";

const leaf = new LeafNode(Uint8Array.from([...]));
const otherLeaf = new LeafNode(Uint8Array.from([...]));
const leaf = LeafNode.fromRoot(Buffer.alloc(32, 0xaa));
const otherLeaf = LeafNode.fromRoot(Buffer.alloc(32, 0xbb));

const branch = new BranchNode(leaf, otherLeaf);

Expand Down Expand Up @@ -61,7 +61,7 @@ const gindex = BigInt(...);

const n: Node = tree.getNode(gindex); // the Node at gindex
const rrr: Uint8Array = tree.getRoot(gindex); // the Uint8Array root at gindex
const subtree: Tree = tree.getSubtree(gindex); // the Tree wrapping the Node at gindex
const subtree: Tree = tree.getSubtree(gindex); // the Tree wrapping the Node at gindex. Updates to `subtree` will be propagated to `tree`

// A merkle proof for a gindex can be generated

Expand Down Expand Up @@ -115,6 +115,46 @@ Any update to a tree (either to a leaf or intermediate node) is performed as a r

A `Tree` object wraps `Node` and provides an API for tree navigation and transparent rebinding on updates.

#### Navigation by gindex or (depth, index)

Many tree methods allow navigation with a gindex. A gindex (or generalized index) describes a path through the tree, starting from the root and nagivating downwards.

```
1
/ \
2 3
/ \ / \
4 5 6 7
```

It can also be interpreted as a bitstring, starting with "1", then appending "0" for each navigation left, or "1" for each navigation right.

```
1
/ \
10 11
/ \ / \
100 101 110 111
```

Alternatively, several tree methods, with names ending with `AtDepth`, allow navigation by (depth, index). Depth and index navigation works by first navigating down levels into the tree from the top, starting at 0 (depth), and indexing nodes from the left, starting at 0 (index).

```
0 <- depth 0
/ \
0 1 <- depth 1
/ \ / \
0 1 2 3 <- depth 2
```

#### Memory efficiency

As an implementation detail, nodes hold their values as a `HashObject` (a javascript object with `h0`, ... `h7` uint32 `number` values) internally, rather than as a 32 byte `Uint8Array`. Memory benchmarking shows that this results in a ~3x reduction in memory over `Uint8Array`. This optimization allows this library to practically scale to trees with millions of nodes.

#### Navigation efficiency

In performance-critical applications performing many reads and writes to trees, being smart with tree navigation is crucial. This library correctly provides tree navigation methods that handle several important optimized cases: multi-node get and set, and get-then-set operations.

## See also:

https://github.com/protolambda/remerkleable
Expand Down
8 changes: 3 additions & 5 deletions packages/persistent-merkle-tree/src/gindex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,17 @@ export function toGindexBitstring(depth: number, index: number): GindexBitstring
}

export function convertGindexToBitstring(gindex: Gindex | GindexBitstring): GindexBitstring {
let bitstring: string;
if (typeof gindex === "string") {
if (!gindex.length) {
if (gindex.length === 0) {
throw new Error(ERR_INVALID_GINDEX);
}
bitstring = gindex;
return gindex;
} else {
if (gindex < 1) {
throw new Error(ERR_INVALID_GINDEX);
}
bitstring = gindex.toString(2);
return gindex.toString(2);
}
return bitstring;
}

// Get the depth (root starting at 0) necessary to cover a subtree of `count` elements.
Expand Down
5 changes: 3 additions & 2 deletions packages/persistent-merkle-tree/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
export * from "./gindex";
export * from "./hash";
export * from "./node";
export * from "./zeroNode";
export * from "./packedNode";
export * from "./proof";
export * from "./subtree";
export * from "./tree";
export * from "./proof";
export * from "./zeroNode";
dapplion marked this conversation as resolved.
Show resolved Hide resolved
Loading