Skip to content

Commit 34e8976

Browse files
committed
feat: implement ListUintNum64Type
1 parent 14c4457 commit 34e8976

File tree

4 files changed

+286
-76
lines changed

4 files changed

+286
-76
lines changed

packages/persistent-merkle-tree/src/packedNode.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,45 @@
11
import {subtreeFillToContents} from "./subtree";
22
import {Node, LeafNode, getNodeH, setNodeH} from "./node";
33

4+
const NUMBER_2_POW_32 = 2 ** 32;
5+
46
export function packedRootsBytesToNode(depth: number, dataView: DataView, start: number, end: number): Node {
57
const leafNodes = packedRootsBytesToLeafNodes(dataView, start, end);
68
return subtreeFillToContents(leafNodes, depth);
79
}
810

11+
/**
12+
* Pack a list of uint64 numbers into a list of LeafNodes.
13+
* Each value is UintNum64, which is 8 bytes long, which is 2 h values.
14+
* Each 4 of them forms a LeafNode.
15+
*
16+
* v0 v1 v2 v3
17+
* |-------------|-------------|-------------|-------------|
18+
*
19+
* h0 h1 h2 h3 h4 h5 h6 h7
20+
* |------|------|------|------|------|------|------|------|
21+
*/
22+
export function packedUintNum64sToLeafNodes(values: number[]): LeafNode[] {
23+
const leafNodes = new Array<LeafNode>(Math.ceil(values.length / 4));
24+
for (let i = 0; i < values.length; i++) {
25+
const nodeIndex = Math.floor(i / 4);
26+
const leafNode = leafNodes[nodeIndex] ?? new LeafNode(0, 0, 0, 0, 0, 0, 0, 0);
27+
const vIndex = i % 4;
28+
const hIndex = 2 * vIndex;
29+
const value = values[i];
30+
// same logic to UintNumberType.value_serializeToBytes() for 8 bytes
31+
if (value === Infinity) {
32+
setNodeH(leafNode, hIndex, 0xffffffff);
33+
setNodeH(leafNode, hIndex + 1, 0xffffffff);
34+
} else {
35+
setNodeH(leafNode, hIndex, value & 0xffffffff);
36+
setNodeH(leafNode, hIndex + 1, (value / NUMBER_2_POW_32) & 0xffffffff);
37+
}
38+
leafNodes[nodeIndex] = leafNode;
39+
}
40+
return leafNodes;
41+
}
42+
943
/**
1044
* Optimized deserialization of linear bytes to consecutive leaf nodes
1145
*/

packages/persistent-merkle-tree/test/unit/packedNode.test.ts

Lines changed: 124 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import {HashObject} from "@chainsafe/as-sha256";
22
import {expect} from "chai";
33
import {LeafNode, Node} from "../../src";
4-
import {packedNodeRootsToBytes, packedRootsBytesToLeafNodes} from "../../src/packedNode";
4+
import {packedNodeRootsToBytes, packedRootsBytesToLeafNodes, packedUintNum64sToLeafNodes} from "../../src/packedNode";
55

66
describe("subtree / packedNode single node", () => {
77
const testCases: {
88
id: string;
99
size: number;
1010
nodes: Node[];
1111
outStr: string;
12+
testPackedNumbers?: boolean;
1213
}[] = [
1314
{
1415
id: "One byte",
@@ -48,11 +49,37 @@ describe("subtree / packedNode single node", () => {
4849
nodes: [LeafNode.fromHashObject({h0: 0x0708090a, h1: 0x01020304, h2: 0, h3: 0, h4: 0, h5: 0, h6: 0, h7: 0})],
4950
outStr: "0x0a09080704030201",
5051
},
52+
{
53+
id: "2 h values fits uint64 number",
54+
size: 8,
55+
nodes: [LeafNode.fromHashObject({h0: 0x0708090a, h1: 0x0102030, h2: 0, h3: 0, h4: 0, h5: 0, h6: 0, h7: 0})],
56+
outStr: "0x0a09080730201000",
57+
testPackedNumbers: true,
58+
},
5159
{
5260
id: "32 bytes zero",
5361
size: 32,
5462
nodes: [LeafNode.fromHashObject({h0: 0, h1: 0, h2: 0, h3: 0, h4: 0, h5: 0, h6: 0, h7: 0})],
5563
outStr: "0x0000000000000000000000000000000000000000000000000000000000000000",
64+
testPackedNumbers: true,
65+
},
66+
{
67+
id: "32 bytes max",
68+
size: 32,
69+
nodes: [
70+
LeafNode.fromHashObject({
71+
h0: 0xffffffff,
72+
h1: 0xffffffff,
73+
h2: 0xffffffff,
74+
h3: 0xffffffff,
75+
h4: 0xffffffff,
76+
h5: 0xffffffff,
77+
h6: 0xffffffff,
78+
h7: 0xffffffff,
79+
}),
80+
],
81+
outStr: "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
82+
testPackedNumbers: true,
5683
},
5784
{
5885
id: "32 bytes same",
@@ -77,9 +104,82 @@ describe("subtree / packedNode single node", () => {
77104
],
78105
outStr: "0x18735c375d8e7b2922ef64f165d10b9ace103f326627f0a21d0fe8a0a573f83f",
79106
},
107+
// same to random tests but trim h1, h3, h5, h7 to make h{2*i} + h{2*i + 1} fits uint64
108+
{
109+
id: "8 bytes random fits unit64 number",
110+
size: 32,
111+
nodes: [
112+
LeafNode.fromHashObject({
113+
h0: 928805656,
114+
h1: 6959632,
115+
h2: 0,
116+
h3: 0,
117+
h4: 0,
118+
h5: 0,
119+
h6: 0,
120+
h7: 0,
121+
}),
122+
],
123+
outStr: "0x18735c3710326a00000000000000000000000000000000000000000000000000",
124+
testPackedNumbers: true,
125+
},
126+
{
127+
id: "16 bytes random fits unit64 number",
128+
size: 32,
129+
nodes: [
130+
LeafNode.fromHashObject({
131+
h0: 928805656,
132+
h1: 6959632,
133+
h2: 4049923874,
134+
h3: 258446,
135+
h4: 0,
136+
h5: 0,
137+
h6: 0,
138+
h7: 0,
139+
}),
140+
],
141+
outStr: "0x18735c3710326a0022ef64f18ef1030000000000000000000000000000000000",
142+
testPackedNumbers: true,
143+
},
144+
{
145+
id: "24 bytes random fits unit64 number",
146+
size: 32,
147+
nodes: [
148+
LeafNode.fromHashObject({
149+
h0: 928805656,
150+
h1: 6959632,
151+
h2: 4049923874,
152+
h3: 258446,
153+
h4: 842993870,
154+
h5: 273364,
155+
h6: 0,
156+
h7: 0,
157+
}),
158+
],
159+
outStr: "0x18735c3710326a0022ef64f18ef10300ce103f32d42b04000000000000000000",
160+
testPackedNumbers: true,
161+
},
162+
{
163+
id: "32 bytes random fits unit64 number",
164+
size: 32,
165+
nodes: [
166+
LeafNode.fromHashObject({
167+
h0: 928805656,
168+
h1: 6959632,
169+
h2: 4049923874,
170+
h3: 258446,
171+
h4: 842993870,
172+
h5: 273364,
173+
h6: 2699562781,
174+
h7: 107324,
175+
}),
176+
],
177+
outStr: "0x18735c3710326a0022ef64f18ef10300ce103f32d42b04001d0fe8a03ca30100",
178+
testPackedNumbers: true,
179+
},
80180
];
81181

82-
for (const {id, size, nodes, outStr} of testCases) {
182+
for (const {id, size, nodes, outStr, testPackedNumbers} of testCases) {
83183
it(`${id} - packedNodeRootsToBytes`, () => {
84184
const uint8Array = new Uint8Array(size);
85185
const dataView = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength);
@@ -93,6 +193,28 @@ describe("subtree / packedNode single node", () => {
93193
const nodesRes = packedRootsBytesToLeafNodes(dataView, 0, size);
94194
expect(onlyHashObject(nodesRes[0].rootHashObject)).to.deep.equal(onlyHashObject(nodes[0].rootHashObject));
95195
});
196+
197+
// 1 UintNum64 = 8 bytes
198+
if (testPackedNumbers) {
199+
const NUMBER_2_POW_32 = 2 ** 32;
200+
it(`${id} - packedUintNum64sToLeafNodes, value size=${Math.floor(size / 8)}`, () => {
201+
const values: number[] = [];
202+
const uint8Array = new Uint8Array(Buffer.from(outStr.replace("0x", ""), "hex"));
203+
const dataView = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength);
204+
for (let i = 0; i < size; i += 8) {
205+
const a = dataView.getUint32(i, true);
206+
const b = dataView.getUint32(i + 4, true);
207+
if (a === 0xffffffff && b === 0xffffffff) {
208+
values.push(Infinity);
209+
} else {
210+
values.push(b * NUMBER_2_POW_32 + a);
211+
}
212+
}
213+
214+
const nodesRes = packedUintNum64sToLeafNodes(values);
215+
expect(onlyHashObject(nodesRes[0].rootHashObject)).to.deep.equal(onlyHashObject(nodes[0].rootHashObject));
216+
});
217+
}
96218
}
97219
});
98220

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import {LeafNode, Node, packedUintNum64sToLeafNodes, subtreeFillToContents} from "@chainsafe/persistent-merkle-tree";
2+
3+
import {ListBasicTreeViewDU} from "../viewDU/listBasic";
4+
import {ListBasicOpts, ListBasicType} from "./listBasic";
5+
import {UintNumberType} from "./uint";
6+
import {addLengthNode} from "./arrayBasic";
7+
8+
/**
9+
* Specific implementation of ListBasicType for UintNumberType with some optimizations.
10+
*/
11+
export class ListUintNum64Type extends ListBasicType<UintNumberType> {
12+
constructor(limit: number, opts?: ListBasicOpts) {
13+
super(new UintNumberType(8), limit, opts);
14+
}
15+
16+
/**
17+
* Return a ListBasicTreeViewDU with nodes populated
18+
*/
19+
toViewDU(value: number[]): ListBasicTreeViewDU<UintNumberType> {
20+
// no need to serialize and deserialize like in the abstract class
21+
const {treeNode, leafNodes} = this.packedUintNum64sToNode(value);
22+
// cache leaf nodes in the ViewDU
23+
return this.getViewDU(treeNode, {
24+
nodes: leafNodes,
25+
length: value.length,
26+
nodesPopulated: true,
27+
});
28+
}
29+
30+
/**
31+
* No need to serialize and deserialize like in the abstract class
32+
*/
33+
value_toTree(value: number[]): Node {
34+
const {treeNode} = this.packedUintNum64sToNode(value);
35+
return treeNode;
36+
}
37+
38+
private packedUintNum64sToNode(value: number[]): {treeNode: Node; leafNodes: LeafNode[]} {
39+
if (value.length > this.limit) {
40+
throw new Error(`Exceeds limit: ${value.length} > ${this.limit}`);
41+
}
42+
43+
const leafNodes = packedUintNum64sToLeafNodes(value);
44+
// subtreeFillToContents mutates the leafNodes array
45+
const rootNode = subtreeFillToContents([...leafNodes], this.chunkDepth);
46+
const treeNode = addLengthNode(rootNode, value.length);
47+
return {treeNode, leafNodes};
48+
}
49+
}

0 commit comments

Comments
 (0)