From febf242b5cd078b0d9a1ba549a86f3ce2ea092be Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 25 Oct 2024 09:18:26 +0100 Subject: [PATCH 001/107] Start implementation --- packages/common/src/trees/LinkedMerkleTree.ts | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/common/src/trees/LinkedMerkleTree.ts diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts new file mode 100644 index 00000000..67bb2801 --- /dev/null +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -0,0 +1 @@ +// Linked Merkle Tree Implementation From 7dcca5371bd33e9f4c4b9472244ae7f6ceb24810 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 25 Oct 2024 10:42:13 +0100 Subject: [PATCH 002/107] Define Leaf --- packages/common/src/trees/LinkedMerkleTree.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 67bb2801..66dcbd71 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -1 +1,5 @@ -// Linked Merkle Tree Implementation +type StoredLeaf = { + readonly value: bigint; + readonly nextIdx: bigint; + readonly nextValue: number; +}; From 11922ac28b17ad10535b94c10b635829dabf491e Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 25 Oct 2024 14:55:25 +0100 Subject: [PATCH 003/107] Start LinkedMerkleTree --- packages/common/src/trees/LinkedMerkleTree.ts | 346 +++++++++++++++++- 1 file changed, 341 insertions(+), 5 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 66dcbd71..5f1990ec 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -1,5 +1,341 @@ -type StoredLeaf = { - readonly value: bigint; - readonly nextIdx: bigint; - readonly nextValue: number; -}; +import { Bool, Field, Poseidon, Provable, Struct } from "o1js"; + +import { range } from "../utils"; +import { TypedClass } from "../types"; + +import { MerkleTreeStore } from "./MerkleTreeStore"; +import { InMemoryMerkleTreeStorage } from "./InMemoryMerkleTreeStorage"; + +class LinkedLeaf extends Struct({ + value: Field, + path: Field, + nextPath: Field, +}) {} + +class StructTemplate extends Struct({ + path: Provable.Array(Field, 0), + isLeft: Provable.Array(Bool, 0), +}) {} + +export interface AbstractLinkedMerkleWitness extends StructTemplate { + height(): number; + + /** + * Calculates a root depending on the leaf value. + * @param leaf Value of the leaf node that belongs to this Witness. + * @returns The calculated root. + */ + calculateRoot(hash: Field): Field; + + /** + * Calculates the index of the leaf node that belongs to this Witness. + * @returns Index of the leaf. + */ + calculateIndex(): Field; + + checkMembership(root: Field, key: Field, value: Field): Bool; + + checkMembershipGetRoots( + root: Field, + key: Field, + value: Field + ): [Bool, Field, Field]; + + toShortenedEntries(): string[]; +} + +export interface AbstractLinkedMerkleTree { + store: MerkleTreeStore; + readonly leafCount: bigint; + + assertIndexRange(index: bigint): void; + + /** + * Returns a node which lives at a given index and level. + * @param level Level of the node. + * @param index Index of the node. + * @returns The data of the node. + */ + getNode(level: number, index: bigint): Field; + + /** + * Returns the root of the [Merkle Tree](https://en.wikipedia.org/wiki/Merkle_tree). + * @returns The root of the Merkle Tree. + */ + getRoot(): Field; + + /** + * Sets the value of a leaf node at a given index to a given value. + * @param index Position of the leaf node. + * @param leaf New value. + */ + setLeaf(index: bigint, leaf: Field): void; + + /** + * Returns the witness (also known as + * [Merkle Proof or Merkle Witness](https://computersciencewiki.org/index.php/Merkle_proof)) + * for the leaf at the given index. + * @param index Position of the leaf node. + * @returns The witness that belongs to the leaf. + */ + getWitness(index: bigint): AbstractLinkedMerkleWitness; + + /** + * Fills all leaves of the tree. + * @param leaves Values to fill the leaves with. + */ + fill(leaves: Field[]): void; +} + +export interface AbstractLinkedMerkleTreeClass { + new (store: MerkleTreeStore): AbstractLinkedMerkleTree; + + WITNESS: TypedClass & + typeof StructTemplate & { dummy: () => AbstractLinkedMerkleWitness }; + + HEIGHT: number; + + EMPTY_ROOT: bigint; + + get leafCount(): bigint; +} + +export function createLinkedMerkleTree( + height: number +): AbstractLinkedMerkleTreeClass { + class LinkedMerkleWitness + extends Struct({ + path: Provable.Array(Field, height - 1), + isLeft: Provable.Array(Bool, height - 1), + }) + implements AbstractLinkedMerkleWitness + { + public static height = height; + + public height(): number { + return LinkedMerkleWitness.height; + } + + /** + * Calculates a root depending on the leaf value. + * @param leaf Value of the leaf node that belongs to this Witness. + * @returns The calculated root. + */ + public calculateRoot(leaf: LinkedLeaf): Field { + let hash = leaf; + const n = this.height(); + + for (let index = 1; index < n; ++index) { + const isLeft = this.isLeft[index - 1]; + + const [left, right] = maybeSwap(isLeft, hash, this.path[index - 1]); + hash = Poseidon.hash([left, right]); + } + + return hash; + } + + /** + * Calculates the index of the leaf node that belongs to this Witness. + * @returns Index of the leaf. + */ + public calculateIndex(): Field { + let powerOfTwo = Field(1); + let index = Field(0); + const n = this.height(); + + for (let i = 1; i < n; ++i) { + index = Provable.if(this.isLeft[i - 1], index, index.add(powerOfTwo)); + powerOfTwo = powerOfTwo.mul(2); + } + + return index; + } + + public checkMembership(root: Field, key: Field, value: Field): Bool { + const calculatedRoot = this.calculateRoot(value); + const calculatedKey = this.calculateIndex(); + // We don't have to range-check the key, because if it would be greater + // than leafCount, it would not match the computedKey + key.assertEquals(calculatedKey, "Keys of MerkleWitness does not match"); + return root.equals(calculatedRoot); + } + + public checkMembershipGetRoots( + root: Field, + key: Field, + value: Field + ): [Bool, Field, Field] { + const calculatedRoot = this.calculateRoot(value); + const calculatedKey = this.calculateIndex(); + key.assertEquals(calculatedKey, "Keys of MerkleWitness does not match"); + return [root.equals(calculatedRoot), root, calculatedRoot]; + } + + public toShortenedEntries() { + return range(0, 5) + .concat(range(this.height() - 4, this.height())) + .map((index) => + [ + this.path[index].toString(), + this.isLeft[index].toString(), + ].toString() + ); + } + + public static dummy() { + return new LinkedMerkleWitness({ + isLeft: Array(height - 1).fill(Bool(false)), + path: Array(height - 1).fill(Field(0)), + }); + } + } + + return class AbstractRollupMerkleTree implements AbstractLinkedMerkleTree { + public static HEIGHT = height; + + public static EMPTY_ROOT = new AbstractRollupMerkleTree( + new InMemoryMerkleTreeStorage() + ) + .getRoot() + .toBigInt(); + + public static get leafCount(): bigint { + return 2n ** BigInt(AbstractRollupMerkleTree.HEIGHT - 1); + } + + public static WITNESS = LinkedMerkleWitness; + + // private in interface + readonly zeroes: bigint[]; + + readonly store: MerkleTreeStore; + + public constructor(store: MerkleTreeStore) { + this.store = store; + this.zeroes = [0n]; + for (let index = 1; index < AbstractRollupMerkleTree.HEIGHT; index += 1) { + const previousLevel = Field(this.zeroes[index - 1]); + this.zeroes.push( + Poseidon.hash([previousLevel, previousLevel]).toBigInt() + ); + } + } + + public assertIndexRange(index: bigint) { + if (index > this.leafCount) { + throw new Error("Index greater than maximum leaf number"); + } + } + + /** + * Returns a node which lives at a given index and level. + * @param level Level of the node. + * @param index Index of the node. + * @returns The data of the node. + */ + public getNode(level: number, index: bigint): Field { + this.assertIndexRange(index); + return Field(this.store.getNode(index, level) ?? this.zeroes[level]); + } + + /** + * Returns the root of the [Merkle Tree](https://en.wikipedia.org/wiki/Merkle_tree). + * @returns The root of the Merkle Tree. + */ + public getRoot(): Field { + return this.getNode(AbstractRollupMerkleTree.HEIGHT - 1, 0n).toConstant(); + } + + // private in interface + private setNode(level: number, index: bigint, value: Field) { + this.store.setNode(index, level, value.toBigInt()); + } + + /** + * Sets the value of a leaf node at a given index to a given value. + * @param index Position of the leaf node. + * @param leaf New value. + */ + public setLeaf(index: bigint, leaf: Field) { + this.assertIndexRange(index); + + this.setNode(0, index, leaf); + let currentIndex = index; + for (let level = 1; level < AbstractRollupMerkleTree.HEIGHT; level += 1) { + currentIndex /= 2n; + + const left = this.getNode(level - 1, currentIndex * 2n); + const right = this.getNode(level - 1, currentIndex * 2n + 1n); + + this.setNode(level, currentIndex, Poseidon.hash([left, right])); + } + } + + /** + * Returns the witness (also known as + * [Merkle Proof or Merkle Witness](https://computersciencewiki.org/index.php/Merkle_proof)) + * for the leaf at the given index. + * @param index Position of the leaf node. + * @returns The witness that belongs to the leaf. + */ + public getWitness(index: bigint): LinkedMerkleWitness { + this.assertIndexRange(index); + + const path = []; + const isLefts = []; + let currentIndex = index; + for ( + let level = 0; + level < AbstractRollupMerkleTree.HEIGHT - 1; + level += 1 + ) { + const isLeft = currentIndex % 2n === 0n; + const sibling = this.getNode( + level, + isLeft ? currentIndex + 1n : currentIndex - 1n + ); + isLefts.push(Bool(isLeft)); + path.push(sibling); + currentIndex /= 2n; + } + return new LinkedMerkleWitness({ + isLeft: isLefts, + path, + }); + } + + // TODO: should this take an optional offset? should it fail if the array is too long? + /** + * Fills all leaves of the tree. + * @param leaves Values to fill the leaves with. + */ + public fill(leaves: Field[]) { + leaves.forEach((value, index) => { + this.setLeaf(BigInt(index), value); + }); + } + + /** + * Returns the amount of leaf nodes. + * @returns Amount of leaf nodes. + */ + public get leafCount(): bigint { + return AbstractRollupMerkleTree.leafCount; + } + }; +} + +export class LinkedMerkleTree extends createLinkedMerkleTree(256) {} +export class LinkedMerkleTreeWitness extends LinkedMerkleTree.WITNESS {} + +/** + * More efficient version of `maybeSwapBad` which + * reuses an intermediate variable + */ +function maybeSwap(b: Bool, x: Field, y: Field): [Field, Field] { + const m = b.toField().mul(x.sub(y)); // b*(x - y) + const x1 = y.add(m); // y + b*(x - y) + const y2 = x.sub(m); // x - b*(x - y) = x + b*(y - x) + return [x1, y2]; +} From e78afb7f8dbaa8bfb2d96e1da192b5b593a08f1c Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 25 Oct 2024 16:32:46 +0100 Subject: [PATCH 004/107] Start LinkedMerkleTree --- packages/common/src/trees/LinkedMerkleTree.ts | 44 +++---------------- packages/common/src/trees/RollupMerkleTree.ts | 2 +- 2 files changed, 8 insertions(+), 38 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 5f1990ec..0d6d57a6 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -5,6 +5,7 @@ import { TypedClass } from "../types"; import { MerkleTreeStore } from "./MerkleTreeStore"; import { InMemoryMerkleTreeStorage } from "./InMemoryMerkleTreeStorage"; +import { AbstractMerkleWitness, StructTemplate } from "./RollupMerkleTree"; class LinkedLeaf extends Struct({ value: Field, @@ -12,38 +13,6 @@ class LinkedLeaf extends Struct({ nextPath: Field, }) {} -class StructTemplate extends Struct({ - path: Provable.Array(Field, 0), - isLeft: Provable.Array(Bool, 0), -}) {} - -export interface AbstractLinkedMerkleWitness extends StructTemplate { - height(): number; - - /** - * Calculates a root depending on the leaf value. - * @param leaf Value of the leaf node that belongs to this Witness. - * @returns The calculated root. - */ - calculateRoot(hash: Field): Field; - - /** - * Calculates the index of the leaf node that belongs to this Witness. - * @returns Index of the leaf. - */ - calculateIndex(): Field; - - checkMembership(root: Field, key: Field, value: Field): Bool; - - checkMembershipGetRoots( - root: Field, - key: Field, - value: Field - ): [Bool, Field, Field]; - - toShortenedEntries(): string[]; -} - export interface AbstractLinkedMerkleTree { store: MerkleTreeStore; readonly leafCount: bigint; @@ -78,7 +47,7 @@ export interface AbstractLinkedMerkleTree { * @param index Position of the leaf node. * @returns The witness that belongs to the leaf. */ - getWitness(index: bigint): AbstractLinkedMerkleWitness; + getWitness(index: bigint): AbstractMerkleWitness; /** * Fills all leaves of the tree. @@ -90,8 +59,8 @@ export interface AbstractLinkedMerkleTree { export interface AbstractLinkedMerkleTreeClass { new (store: MerkleTreeStore): AbstractLinkedMerkleTree; - WITNESS: TypedClass & - typeof StructTemplate & { dummy: () => AbstractLinkedMerkleWitness }; + WITNESS: TypedClass & + typeof StructTemplate & { dummy: () => AbstractMerkleWitness }; HEIGHT: number; @@ -108,7 +77,7 @@ export function createLinkedMerkleTree( path: Provable.Array(Field, height - 1), isLeft: Provable.Array(Bool, height - 1), }) - implements AbstractLinkedMerkleWitness + implements AbstractMerkleWitness { public static height = height; @@ -122,12 +91,13 @@ export function createLinkedMerkleTree( * @returns The calculated root. */ public calculateRoot(leaf: LinkedLeaf): Field { - let hash = leaf; + let hash = Poseidon.hash([leaf.value, leaf.path, leaf.nextPath]); const n = this.height(); for (let index = 1; index < n; ++index) { const isLeft = this.isLeft[index - 1]; + // eslint-disable-next-line @typescript-eslint/no-use-before-define const [left, right] = maybeSwap(isLeft, hash, this.path[index - 1]); hash = Poseidon.hash([left, right]); } diff --git a/packages/common/src/trees/RollupMerkleTree.ts b/packages/common/src/trees/RollupMerkleTree.ts index 44049e80..ff913b39 100644 --- a/packages/common/src/trees/RollupMerkleTree.ts +++ b/packages/common/src/trees/RollupMerkleTree.ts @@ -6,7 +6,7 @@ import { TypedClass } from "../types"; import { MerkleTreeStore } from "./MerkleTreeStore"; import { InMemoryMerkleTreeStorage } from "./InMemoryMerkleTreeStorage"; -class StructTemplate extends Struct({ +export class StructTemplate extends Struct({ path: Provable.Array(Field, 0), isLeft: Provable.Array(Bool, 0), }) {} From 36d07bc5ac9676c828efd5d6153b78e0de708716 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 25 Oct 2024 16:47:27 +0100 Subject: [PATCH 005/107] Create LinkedMerkleTreeStore --- packages/common/src/trees/LinkedMerkleTree.ts | 10 +++++----- packages/common/src/trees/LinkedMerkleTreeStore.ts | 9 +++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 packages/common/src/trees/LinkedMerkleTreeStore.ts diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 0d6d57a6..6d326500 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -3,7 +3,7 @@ import { Bool, Field, Poseidon, Provable, Struct } from "o1js"; import { range } from "../utils"; import { TypedClass } from "../types"; -import { MerkleTreeStore } from "./MerkleTreeStore"; +import { LinkedMerkleTreeStore } from "./LinkedMerkleTreeStore"; import { InMemoryMerkleTreeStorage } from "./InMemoryMerkleTreeStorage"; import { AbstractMerkleWitness, StructTemplate } from "./RollupMerkleTree"; @@ -14,7 +14,7 @@ class LinkedLeaf extends Struct({ }) {} export interface AbstractLinkedMerkleTree { - store: MerkleTreeStore; + store: LinkedMerkleTreeStore; readonly leafCount: bigint; assertIndexRange(index: bigint): void; @@ -57,7 +57,7 @@ export interface AbstractLinkedMerkleTree { } export interface AbstractLinkedMerkleTreeClass { - new (store: MerkleTreeStore): AbstractLinkedMerkleTree; + new (store: LinkedMerkleTreeStore): AbstractLinkedMerkleTree; WITNESS: TypedClass & typeof StructTemplate & { dummy: () => AbstractMerkleWitness }; @@ -179,9 +179,9 @@ export function createLinkedMerkleTree( // private in interface readonly zeroes: bigint[]; - readonly store: MerkleTreeStore; + readonly store: LinkedMerkleTreeStore; - public constructor(store: MerkleTreeStore) { + public constructor(store: LinkedMerkleTreeStore) { this.store = store; this.zeroes = [0n]; for (let index = 1; index < AbstractRollupMerkleTree.HEIGHT; index += 1) { diff --git a/packages/common/src/trees/LinkedMerkleTreeStore.ts b/packages/common/src/trees/LinkedMerkleTreeStore.ts new file mode 100644 index 00000000..f113c8d6 --- /dev/null +++ b/packages/common/src/trees/LinkedMerkleTreeStore.ts @@ -0,0 +1,9 @@ +export interface LinkedMerkleTreeStore { + setNode: ( + key: bigint, + level: number, + node: { value: bigint; path: number; nextPath: number } + ) => void; + + getNode: (key: bigint, level: number) => bigint | undefined; +} From fba3f7c06fc0cfece10a2b0bcd7e5a800035b9d3 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Mon, 28 Oct 2024 08:18:33 +0000 Subject: [PATCH 006/107] Update LinkedMerkleTreeStore --- .../trees/InMemoryLinkedMerkleTreeStorage.ts | 17 +++++++++++++++++ .../common/src/trees/LinkedMerkleTreeStore.ts | 10 ++++------ packages/common/src/trees/RollupMerkleTree.ts | 2 +- 3 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts diff --git a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts new file mode 100644 index 00000000..907d2bcd --- /dev/null +++ b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts @@ -0,0 +1,17 @@ +import { LinkedMerkleTreeStore, LinkedNode } from "./LinkedMerkleTreeStore"; + +export class InMemoryLinkedMerkleTreeStorage implements LinkedMerkleTreeStore { + protected nodes: { + [key: number]: { + [key: string]: LinkedNode; + }; + } = {}; + + public getNode(key: bigint, level: number): LinkedNode | undefined { + return this.nodes[level]?.[key.toString()]; + } + + public setNode(key: bigint, level: number, value: LinkedNode): void { + (this.nodes[level] ??= {})[key.toString()] = value; + } +} diff --git a/packages/common/src/trees/LinkedMerkleTreeStore.ts b/packages/common/src/trees/LinkedMerkleTreeStore.ts index f113c8d6..9d9f4e4f 100644 --- a/packages/common/src/trees/LinkedMerkleTreeStore.ts +++ b/packages/common/src/trees/LinkedMerkleTreeStore.ts @@ -1,9 +1,7 @@ export interface LinkedMerkleTreeStore { - setNode: ( - key: bigint, - level: number, - node: { value: bigint; path: number; nextPath: number } - ) => void; + setNode: (key: bigint, level: number, node: LinkedNode) => void; - getNode: (key: bigint, level: number) => bigint | undefined; + getNode: (key: bigint, level: number) => LinkedNode | undefined; } + +export type LinkedNode = { value: bigint; path: number; nextPath: number }; diff --git a/packages/common/src/trees/RollupMerkleTree.ts b/packages/common/src/trees/RollupMerkleTree.ts index ff913b39..bd61171f 100644 --- a/packages/common/src/trees/RollupMerkleTree.ts +++ b/packages/common/src/trees/RollupMerkleTree.ts @@ -348,7 +348,7 @@ export class RollupMerkleTreeWitness extends RollupMerkleTree.WITNESS {} * More efficient version of `maybeSwapBad` which * reuses an intermediate variable */ -function maybeSwap(b: Bool, x: Field, y: Field): [Field, Field] { +export function maybeSwap(b: Bool, x: Field, y: Field): [Field, Field] { const m = b.toField().mul(x.sub(y)); // b*(x - y) const x1 = y.add(m); // y + b*(x - y) const y2 = x.sub(m); // x - b*(x - y) = x + b*(y - x) From da6d00c7bd01a37e78997d7f8c4bcffe127edd91 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Mon, 28 Oct 2024 15:33:31 +0000 Subject: [PATCH 007/107] Update interface with types. --- packages/common/src/trees/LinkedMerkleTree.ts | 110 +++++++++++------- 1 file changed, 69 insertions(+), 41 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 6d326500..e7405d37 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -4,8 +4,12 @@ import { range } from "../utils"; import { TypedClass } from "../types"; import { LinkedMerkleTreeStore } from "./LinkedMerkleTreeStore"; -import { InMemoryMerkleTreeStorage } from "./InMemoryMerkleTreeStorage"; -import { AbstractMerkleWitness, StructTemplate } from "./RollupMerkleTree"; +import { InMemoryLinkedMerkleTreeStorage } from "./InMemoryLinkedMerkleTreeStorage"; +import { + AbstractMerkleWitness, + StructTemplate, + maybeSwap, +} from "./RollupMerkleTree"; class LinkedLeaf extends Struct({ value: Field, @@ -16,9 +20,6 @@ class LinkedLeaf extends Struct({ export interface AbstractLinkedMerkleTree { store: LinkedMerkleTreeStore; readonly leafCount: bigint; - - assertIndexRange(index: bigint): void; - /** * Returns a node which lives at a given index and level. * @param level Level of the node. @@ -38,7 +39,14 @@ export interface AbstractLinkedMerkleTree { * @param index Position of the leaf node. * @param leaf New value. */ - setLeaf(index: bigint, leaf: Field): void; + setLeaf(index: bigint, leaf: LinkedLeaf): void; + + /** + * Returns a leaf which lives at a given index. + * @param index Index of the node. + * @returns The data of the leaf. + */ + getLeaf(index: bigint): LinkedLeaf; /** * Returns the witness (also known as @@ -48,12 +56,6 @@ export interface AbstractLinkedMerkleTree { * @returns The witness that belongs to the leaf. */ getWitness(index: bigint): AbstractMerkleWitness; - - /** - * Fills all leaves of the tree. - * @param leaves Values to fill the leaves with. - */ - fill(leaves: Field[]): void; } export interface AbstractLinkedMerkleTreeClass { @@ -97,7 +99,6 @@ export function createLinkedMerkleTree( for (let index = 1; index < n; ++index) { const isLeft = this.isLeft[index - 1]; - // eslint-disable-next-line @typescript-eslint/no-use-before-define const [left, right] = maybeSwap(isLeft, hash, this.path[index - 1]); hash = Poseidon.hash([left, right]); } @@ -161,17 +162,19 @@ export function createLinkedMerkleTree( } } - return class AbstractRollupMerkleTree implements AbstractLinkedMerkleTree { + return class AbstractLinkedRollupMerkleTree + implements AbstractLinkedMerkleTree + { public static HEIGHT = height; - public static EMPTY_ROOT = new AbstractRollupMerkleTree( - new InMemoryMerkleTreeStorage() + public static EMPTY_ROOT = new AbstractLinkedRollupMerkleTree( + new InMemoryLinkedMerkleTreeStorage() ) .getRoot() .toBigInt(); public static get leafCount(): bigint { - return 2n ** BigInt(AbstractRollupMerkleTree.HEIGHT - 1); + return 2n ** BigInt(AbstractLinkedRollupMerkleTree.HEIGHT - 1); } public static WITNESS = LinkedMerkleWitness; @@ -184,7 +187,11 @@ export function createLinkedMerkleTree( public constructor(store: LinkedMerkleTreeStore) { this.store = store; this.zeroes = [0n]; - for (let index = 1; index < AbstractRollupMerkleTree.HEIGHT; index += 1) { + for ( + let index = 1; + index < AbstractLinkedRollupMerkleTree.HEIGHT; + index += 1 + ) { const previousLevel = Field(this.zeroes[index - 1]); this.zeroes.push( Poseidon.hash([previousLevel, previousLevel]).toBigInt() @@ -198,15 +205,37 @@ export function createLinkedMerkleTree( } } + public getNode(level: number, index: bigint): Field { + this.assertIndexRange(index); + const node = this.store.getNode(index, level) ?? { + value: 0n, + path: 0, + nextPath: 0, + }; + return { + value: Field(node.value), + path: Field(node.path), + nextPath: Field(node.nextPath), + }; + } + /** * Returns a node which lives at a given index and level. * @param level Level of the node. * @param index Index of the node. * @returns The data of the node. */ - public getNode(level: number, index: bigint): Field { - this.assertIndexRange(index); - return Field(this.store.getNode(index, level) ?? this.zeroes[level]); + public getLeaf(index: bigint): LinkedLeaf { + const node = this.store.getNode(index, 0) ?? { + value: 0n, + path: 0, + nextPath: 0, + }; + return { + value: Field(node.value), + path: Field(node.path), + nextPath: Field(node.nextPath), + }; } /** @@ -214,12 +243,19 @@ export function createLinkedMerkleTree( * @returns The root of the Merkle Tree. */ public getRoot(): Field { - return this.getNode(AbstractRollupMerkleTree.HEIGHT - 1, 0n).toConstant(); + return this.getNode( + AbstractLinkedRollupMerkleTree.HEIGHT - 1, + 0n + ).toConstant(); } // private in interface - private setNode(level: number, index: bigint, value: Field) { - this.store.setNode(index, level, value.toBigInt()); + private setNode(level: number, index: bigint, node: LinkedLeaf) { + this.store.setNode(index, level, { + value: node.value.toBigInt(), + path: node.path.toBigInt(), + nextPath: node.nextPath.toBigInt(), + }); } /** @@ -227,12 +263,16 @@ export function createLinkedMerkleTree( * @param index Position of the leaf node. * @param leaf New value. */ - public setLeaf(index: bigint, leaf: Field) { + public setLeaf(index: bigint, leaf: LinkedLeaf) { this.assertIndexRange(index); this.setNode(0, index, leaf); let currentIndex = index; - for (let level = 1; level < AbstractRollupMerkleTree.HEIGHT; level += 1) { + for ( + let level = 1; + level < AbstractLinkedRollupMerkleTree.HEIGHT; + level += 1 + ) { currentIndex /= 2n; const left = this.getNode(level - 1, currentIndex * 2n); @@ -257,7 +297,7 @@ export function createLinkedMerkleTree( let currentIndex = index; for ( let level = 0; - level < AbstractRollupMerkleTree.HEIGHT - 1; + level < AbstractLinkedRollupMerkleTree.HEIGHT - 1; level += 1 ) { const isLeft = currentIndex % 2n === 0n; @@ -275,7 +315,6 @@ export function createLinkedMerkleTree( }); } - // TODO: should this take an optional offset? should it fail if the array is too long? /** * Fills all leaves of the tree. * @param leaves Values to fill the leaves with. @@ -291,21 +330,10 @@ export function createLinkedMerkleTree( * @returns Amount of leaf nodes. */ public get leafCount(): bigint { - return AbstractRollupMerkleTree.leafCount; + return AbstractLinkedRollupMerkleTree.leafCount; } }; } -export class LinkedMerkleTree extends createLinkedMerkleTree(256) {} +export class LinkedMerkleTree extends createLinkedMerkleTree(40) {} export class LinkedMerkleTreeWitness extends LinkedMerkleTree.WITNESS {} - -/** - * More efficient version of `maybeSwapBad` which - * reuses an intermediate variable - */ -function maybeSwap(b: Bool, x: Field, y: Field): [Field, Field] { - const m = b.toField().mul(x.sub(y)); // b*(x - y) - const x1 = y.add(m); // y + b*(x - y) - const y2 = x.sub(m); // x - b*(x - y) = x + b*(y - x) - return [x1, y2]; -} From da3f1abee362834db640eaa4c2a4fb0cf349e419 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:04:41 +0000 Subject: [PATCH 008/107] Update interface and get implementation for in memory linked merkle tree --- packages/common/src/index.ts | 1 + .../trees/InMemoryLinkedMerkleTreeStorage.ts | 28 +++++++++++++++---- .../common/src/trees/LinkedMerkleTreeStore.ts | 12 ++++++-- packages/sequencer/src/index.ts | 1 + 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 2309ba77..6addb04f 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -15,5 +15,6 @@ export * from "./events/EventEmitter"; export * from "./trees/MerkleTreeStore"; export * from "./trees/InMemoryMerkleTreeStorage"; export * from "./trees/RollupMerkleTree"; +export * from "./trees/InMemoryLinkedMerkleTreeStorage"; export * from "./events/EventEmitterProxy"; export * from "./trees/MockAsyncMerkleStore"; diff --git a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts index 907d2bcd..8fd2b666 100644 --- a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts +++ b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts @@ -1,17 +1,35 @@ -import { LinkedMerkleTreeStore, LinkedNode } from "./LinkedMerkleTreeStore"; +import { LinkedMerkleTreeStore, LinkedLeaf } from "./LinkedMerkleTreeStore"; export class InMemoryLinkedMerkleTreeStorage implements LinkedMerkleTreeStore { protected nodes: { [key: number]: { - [key: string]: LinkedNode; + [key: string]: bigint; }; } = {}; - public getNode(key: bigint, level: number): LinkedNode | undefined { - return this.nodes[level]?.[key.toString()]; + protected leaves: { + [key: string]: LinkedLeaf; + } = {}; + + public getNode(key: number, level: number): bigint | undefined { + return this.nodes[level]?.[key]; } - public setNode(key: bigint, level: number, value: LinkedNode): void { + public setNode(key: number, level: number, value: bigint): void { (this.nodes[level] ??= {})[key.toString()] = value; } + + public getLeaf(key: number): LinkedLeaf | undefined { + return this.leaves[key]; + } + + public setLeaf(key: number, value: LinkedLeaf): void { + this.leaves[key.toString()] = value; + } + + public getIndex(path: number): string | undefined { + return Object.keys(this.leaves).find((key) => { + return this.leaves[key].path === path; + }); + } } diff --git a/packages/common/src/trees/LinkedMerkleTreeStore.ts b/packages/common/src/trees/LinkedMerkleTreeStore.ts index 9d9f4e4f..2df34863 100644 --- a/packages/common/src/trees/LinkedMerkleTreeStore.ts +++ b/packages/common/src/trees/LinkedMerkleTreeStore.ts @@ -1,7 +1,13 @@ export interface LinkedMerkleTreeStore { - setNode: (key: bigint, level: number, node: LinkedNode) => void; + setNode: (key: number, level: number, value: bigint) => void; - getNode: (key: bigint, level: number) => LinkedNode | undefined; + getNode: (key: number, level: number) => bigint | undefined; + + setLeaf: (key: number, value: LinkedLeaf) => void; + + getLeaf: (key: number) => LinkedLeaf | undefined; + + getIndex: (path: number) => string | undefined; } -export type LinkedNode = { value: bigint; path: number; nextPath: number }; +export type LinkedLeaf = { value: bigint; path: number; nextPath: number }; diff --git a/packages/sequencer/src/index.ts b/packages/sequencer/src/index.ts index 87cea4a2..5f78b1c0 100644 --- a/packages/sequencer/src/index.ts +++ b/packages/sequencer/src/index.ts @@ -61,6 +61,7 @@ export * from "./helpers/query/NetworkStateQuery"; export * from "./helpers/query/NetworkStateTransportModule"; export * from "./state/prefilled/PreFilledStateService"; export * from "./state/prefilled/PreFilledWitnessProvider"; +export * from "./state/async/AsyncLinkedMerkleTreeStore"; export * from "./state/async/AsyncMerkleTreeStore"; export * from "./state/async/AsyncStateService"; export * from "./state/merkle/CachedMerkleTreeStore"; From ae09af0997bf7613c5fedbcb8ed2f9d7f39ba987 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Tue, 29 Oct 2024 12:31:15 +0000 Subject: [PATCH 009/107] Update interface --- packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts | 2 +- packages/common/src/trees/LinkedMerkleTreeStore.ts | 2 +- packages/persistance/src/index.ts | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts index 8fd2b666..433a8c80 100644 --- a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts +++ b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts @@ -27,7 +27,7 @@ export class InMemoryLinkedMerkleTreeStorage implements LinkedMerkleTreeStore { this.leaves[key.toString()] = value; } - public getIndex(path: number): string | undefined { + public getLeafIndex(path: number): string | undefined { return Object.keys(this.leaves).find((key) => { return this.leaves[key].path === path; }); diff --git a/packages/common/src/trees/LinkedMerkleTreeStore.ts b/packages/common/src/trees/LinkedMerkleTreeStore.ts index 2df34863..45983b83 100644 --- a/packages/common/src/trees/LinkedMerkleTreeStore.ts +++ b/packages/common/src/trees/LinkedMerkleTreeStore.ts @@ -7,7 +7,7 @@ export interface LinkedMerkleTreeStore { getLeaf: (key: number) => LinkedLeaf | undefined; - getIndex: (path: number) => string | undefined; + getLeafIndex: (path: number) => string | undefined; } export type LinkedLeaf = { value: bigint; path: number; nextPath: number }; diff --git a/packages/persistance/src/index.ts b/packages/persistance/src/index.ts index 20821121..51ef2c6f 100644 --- a/packages/persistance/src/index.ts +++ b/packages/persistance/src/index.ts @@ -15,3 +15,4 @@ export * from "./services/prisma/mappers/StateTransitionMapper"; export * from "./services/prisma/mappers/TransactionMapper"; export * from "./services/prisma/mappers/BlockResultMapper"; export * from "./services/redis/RedisMerkleTreeStore"; +export * from "./services/redis/RedisLinkedMerkleTreeStore"; From 1c1ab2d699b2958729e3d277ff76ab892d027427 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:27:36 +0000 Subject: [PATCH 010/107] Working on LinkedMerkleTree --- .../trees/InMemoryLinkedMerkleTreeStorage.ts | 24 +++--- packages/common/src/trees/LinkedMerkleTree.ts | 84 +++++-------------- .../common/src/trees/LinkedMerkleTreeStore.ts | 10 +-- .../state/async/AsyncLinkedMerkleTreeStore.ts | 21 +++++ 4 files changed, 62 insertions(+), 77 deletions(-) create mode 100644 packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts diff --git a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts index 433a8c80..07d1f146 100644 --- a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts +++ b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts @@ -11,25 +11,29 @@ export class InMemoryLinkedMerkleTreeStorage implements LinkedMerkleTreeStore { [key: string]: LinkedLeaf; } = {}; - public getNode(key: number, level: number): bigint | undefined { - return this.nodes[level]?.[key]; + public getNode(index: bigint, level: number): bigint | undefined { + return this.nodes[level]?.[index.toString()]; } - public setNode(key: number, level: number, value: bigint): void { - (this.nodes[level] ??= {})[key.toString()] = value; + public setNode(index: bigint, level: number, value: bigint): void { + (this.nodes[level] ??= {})[index.toString()] = value; } - public getLeaf(key: number): LinkedLeaf | undefined { - return this.leaves[key]; + public getLeaf(index: bigint): LinkedLeaf | undefined { + return this.leaves[index.toString()]; } - public setLeaf(key: number, value: LinkedLeaf): void { - this.leaves[key.toString()] = value; + public setLeaf(index: bigint, value: LinkedLeaf): void { + this.leaves[index.toString()] = value; } - public getLeafIndex(path: number): string | undefined { - return Object.keys(this.leaves).find((key) => { + public getLeafIndex(path: number): bigint | undefined { + const leafIndex = Object.keys(this.leaves).find((key) => { return this.leaves[key].path === path; }); + if (leafIndex === undefined) { + return undefined; + } + return BigInt(leafIndex); } } diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index e7405d37..a6911eda 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -1,6 +1,5 @@ import { Bool, Field, Poseidon, Provable, Struct } from "o1js"; -import { range } from "../utils"; import { TypedClass } from "../types"; import { LinkedMerkleTreeStore } from "./LinkedMerkleTreeStore"; @@ -42,11 +41,11 @@ export interface AbstractLinkedMerkleTree { setLeaf(index: bigint, leaf: LinkedLeaf): void; /** - * Returns a leaf which lives at a given index. - * @param index Index of the node. + * Returns a leaf which lives at a given path. + * @param path Index of the node. * @returns The data of the leaf. */ - getLeaf(index: bigint): LinkedLeaf; + getLeaf(path: number): LinkedLeaf | undefined; /** * Returns the witness (also known as @@ -92,8 +91,8 @@ export function createLinkedMerkleTree( * @param leaf Value of the leaf node that belongs to this Witness. * @returns The calculated root. */ - public calculateRoot(leaf: LinkedLeaf): Field { - let hash = Poseidon.hash([leaf.value, leaf.path, leaf.nextPath]); + public calculateRoot(leaf: Field): Field { + let hash = leaf; const n = this.height(); for (let index = 1; index < n; ++index) { @@ -142,24 +141,6 @@ export function createLinkedMerkleTree( key.assertEquals(calculatedKey, "Keys of MerkleWitness does not match"); return [root.equals(calculatedRoot), root, calculatedRoot]; } - - public toShortenedEntries() { - return range(0, 5) - .concat(range(this.height() - 4, this.height())) - .map((index) => - [ - this.path[index].toString(), - this.isLeft[index].toString(), - ].toString() - ); - } - - public static dummy() { - return new LinkedMerkleWitness({ - isLeft: Array(height - 1).fill(Bool(false)), - path: Array(height - 1).fill(Field(0)), - }); - } } return class AbstractLinkedRollupMerkleTree @@ -207,34 +188,27 @@ export function createLinkedMerkleTree( public getNode(level: number, index: bigint): Field { this.assertIndexRange(index); - const node = this.store.getNode(index, level) ?? { - value: 0n, - path: 0, - nextPath: 0, - }; - return { - value: Field(node.value), - path: Field(node.path), - nextPath: Field(node.nextPath), - }; + return Field(this.store.getNode(index, level) ?? this.zeroes[level]); } /** - * Returns a node which lives at a given index and level. - * @param level Level of the node. - * @param index Index of the node. + * Returns leaf which lives at a given path + * @param path path of the node. * @returns The data of the node. */ - public getLeaf(index: bigint): LinkedLeaf { - const node = this.store.getNode(index, 0) ?? { - value: 0n, - path: 0, - nextPath: 0, - }; + public getLeaf(path: number): LinkedLeaf | undefined { + const index = this.store.getLeafIndex(path); + if (index === undefined) { + return index; + } + const leaf = this.store.getLeaf(BigInt(index)); + if (leaf === undefined) { + return undefined; + } return { - value: Field(node.value), - path: Field(node.path), - nextPath: Field(node.nextPath), + value: Field(leaf.value), + path: Field(leaf.path), + nextPath: Field(leaf.nextPath), }; } @@ -250,12 +224,8 @@ export function createLinkedMerkleTree( } // private in interface - private setNode(level: number, index: bigint, node: LinkedLeaf) { - this.store.setNode(index, level, { - value: node.value.toBigInt(), - path: node.path.toBigInt(), - nextPath: node.nextPath.toBigInt(), - }); + private setNode(level: number, index: bigint, value: Field) { + this.store.setNode(index, level, value.toBigInt()); } /** @@ -315,16 +285,6 @@ export function createLinkedMerkleTree( }); } - /** - * Fills all leaves of the tree. - * @param leaves Values to fill the leaves with. - */ - public fill(leaves: Field[]) { - leaves.forEach((value, index) => { - this.setLeaf(BigInt(index), value); - }); - } - /** * Returns the amount of leaf nodes. * @returns Amount of leaf nodes. diff --git a/packages/common/src/trees/LinkedMerkleTreeStore.ts b/packages/common/src/trees/LinkedMerkleTreeStore.ts index 45983b83..9127d008 100644 --- a/packages/common/src/trees/LinkedMerkleTreeStore.ts +++ b/packages/common/src/trees/LinkedMerkleTreeStore.ts @@ -1,13 +1,13 @@ export interface LinkedMerkleTreeStore { - setNode: (key: number, level: number, value: bigint) => void; + setNode: (index: bigint, level: number, value: bigint) => void; - getNode: (key: number, level: number) => bigint | undefined; + getNode: (index: bigint, level: number) => bigint | undefined; - setLeaf: (key: number, value: LinkedLeaf) => void; + setLeaf: (index: bigint, value: LinkedLeaf) => void; - getLeaf: (key: number) => LinkedLeaf | undefined; + getLeaf: (index: bigint) => LinkedLeaf | undefined; - getLeafIndex: (path: number) => string | undefined; + getLeafIndex: (path: number) => bigint | undefined; } export type LinkedLeaf = { value: bigint; path: number; nextPath: number }; diff --git a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts new file mode 100644 index 00000000..8de8feb5 --- /dev/null +++ b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts @@ -0,0 +1,21 @@ +// import { LinkedMerkleTreeStore } from "@proto-kit/common"; + +import { MerkleTreeNodeQuery } from "./AsyncMerkleTreeStore"; + +export interface LinkedMerkleTreeNode extends MerkleTreeNodeQuery { + value: bigint; + path: number; + nextPath: number; +} + +export interface AsyncLinkedMerkleTreeStore { + openTransaction: () => Promise; + + commit: () => Promise; + + writeNodes: (nodes: LinkedMerkleTreeNode[]) => void; + + getNodesAsync: ( + nodes: MerkleTreeNodeQuery[] + ) => Promise<(bigint | undefined)[]>; +} From 13f61a1dc7b3821f644f9cadc990c1c79249344d Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Wed, 30 Oct 2024 12:24:42 +0000 Subject: [PATCH 011/107] Add in getClosestPath --- .../src/trees/InMemoryLinkedMerkleTreeStorage.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts index 07d1f146..54ec04df 100644 --- a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts +++ b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts @@ -23,6 +23,19 @@ export class InMemoryLinkedMerkleTreeStorage implements LinkedMerkleTreeStore { return this.leaves[index.toString()]; } + // This gets the leaf with the closest path. + public getClosestPath(path: number): LinkedLeaf { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + let largestLeaf = this.getLeaf(0n) as LinkedLeaf; + while (largestLeaf.nextPath < path) { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const nextIndex = this.getLeafIndex(largestLeaf.nextPath) as bigint; + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + largestLeaf = this.getLeaf(nextIndex) as LinkedLeaf; + } + return largestLeaf; + } + public setLeaf(index: bigint, value: LinkedLeaf): void { this.leaves[index.toString()] = value; } From 69dbaaa46e4ef8194f47c549d0b8a0347ea1a231 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Wed, 30 Oct 2024 13:14:38 +0000 Subject: [PATCH 012/107] Update GetWitness --- packages/common/src/trees/LinkedMerkleTree.ts | 54 ++++++++++++++----- .../common/src/trees/LinkedMerkleTreeStore.ts | 2 + 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index a6911eda..857fe85a 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -1,6 +1,7 @@ import { Bool, Field, Poseidon, Provable, Struct } from "o1js"; import { TypedClass } from "../types"; +import { range } from "../utils"; import { LinkedMerkleTreeStore } from "./LinkedMerkleTreeStore"; import { InMemoryLinkedMerkleTreeStorage } from "./InMemoryLinkedMerkleTreeStorage"; @@ -47,14 +48,21 @@ export interface AbstractLinkedMerkleTree { */ getLeaf(path: number): LinkedLeaf | undefined; + /** + * Returns a leaf which lives at a given path. + * @param path Index of the node. + * @returns The data of the leaf. + */ + getClosestPath(path: number): LinkedLeaf; + /** * Returns the witness (also known as * [Merkle Proof or Merkle Witness](https://computersciencewiki.org/index.php/Merkle_proof)) - * for the leaf at the given index. - * @param index Position of the leaf node. + * for the leaf at the given path. + * @param path Position of the leaf node. * @returns The witness that belongs to the leaf. */ - getWitness(index: bigint): AbstractMerkleWitness; + getWitness(path: number): AbstractMerkleWitness; } export interface AbstractLinkedMerkleTreeClass { @@ -141,6 +149,17 @@ export function createLinkedMerkleTree( key.assertEquals(calculatedKey, "Keys of MerkleWitness does not match"); return [root.equals(calculatedRoot), root, calculatedRoot]; } + + public toShortenedEntries() { + return range(0, 5) + .concat(range(this.height() - 4, this.height())) + .map((index) => + [ + this.path[index].toString(), + this.isLeft[index].toString(), + ].toString() + ); + } } return class AbstractLinkedRollupMerkleTree @@ -192,7 +211,7 @@ export function createLinkedMerkleTree( } /** - * Returns leaf which lives at a given path + * Returns leaf which lives at a given path, or closest path * @param path path of the node. * @returns The data of the node. */ @@ -212,6 +231,16 @@ export function createLinkedMerkleTree( }; } + // This gets the leaf with the closest path. + public getClosestPath(path: number): LinkedLeaf { + const closestLeaf = this.store.getClosestPath(path); + return { + value: Field(closestLeaf.value), + path: Field(closestLeaf.path), + nextPath: Field(closestLeaf.nextPath), + }; + } + /** * Returns the root of the [Merkle Tree](https://en.wikipedia.org/wiki/Merkle_tree). * @returns The root of the Merkle Tree. @@ -233,9 +262,7 @@ export function createLinkedMerkleTree( * @param index Position of the leaf node. * @param leaf New value. */ - public setLeaf(index: bigint, leaf: LinkedLeaf) { - this.assertIndexRange(index); - + public setLeaf(path: bigint, leaf: LinkedLeaf) { this.setNode(0, index, leaf); let currentIndex = index; for ( @@ -259,10 +286,13 @@ export function createLinkedMerkleTree( * @param index Position of the leaf node. * @returns The witness that belongs to the leaf. */ - public getWitness(index: bigint): LinkedMerkleWitness { - this.assertIndexRange(index); + public getWitness(path: number): LinkedMerkleWitness { + const index = this.store.getLeafIndex(path); + if (index === undefined) { + throw new Error("Path does not exist in tree."); + } - const path = []; + const pathArray = []; const isLefts = []; let currentIndex = index; for ( @@ -276,12 +306,12 @@ export function createLinkedMerkleTree( isLeft ? currentIndex + 1n : currentIndex - 1n ); isLefts.push(Bool(isLeft)); - path.push(sibling); + pathArray.push(sibling); currentIndex /= 2n; } return new LinkedMerkleWitness({ isLeft: isLefts, - path, + path: pathArray, }); } diff --git a/packages/common/src/trees/LinkedMerkleTreeStore.ts b/packages/common/src/trees/LinkedMerkleTreeStore.ts index 9127d008..00ae5353 100644 --- a/packages/common/src/trees/LinkedMerkleTreeStore.ts +++ b/packages/common/src/trees/LinkedMerkleTreeStore.ts @@ -8,6 +8,8 @@ export interface LinkedMerkleTreeStore { getLeaf: (index: bigint) => LinkedLeaf | undefined; getLeafIndex: (path: number) => bigint | undefined; + + getClosestPath: (path: number) => LinkedLeaf; } export type LinkedLeaf = { value: bigint; path: number; nextPath: number }; From d8930c0b4758bee1d276dd6f0c20582a36406dcd Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:18:29 +0000 Subject: [PATCH 013/107] Added in code to set leaf --- .../trees/InMemoryLinkedMerkleTreeStorage.ts | 32 ++--- packages/common/src/trees/LinkedMerkleTree.ts | 111 +++++++++++------- .../common/src/trees/LinkedMerkleTreeStore.ts | 2 + 3 files changed, 90 insertions(+), 55 deletions(-) diff --git a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts index 54ec04df..7cb9b344 100644 --- a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts +++ b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts @@ -9,7 +9,7 @@ export class InMemoryLinkedMerkleTreeStorage implements LinkedMerkleTreeStore { protected leaves: { [key: string]: LinkedLeaf; - } = {}; + } = { "0": { value: 0n, path: 0, nextPath: 0 } }; public getNode(index: bigint, level: number): bigint | undefined { return this.nodes[level]?.[index.toString()]; @@ -23,19 +23,6 @@ export class InMemoryLinkedMerkleTreeStorage implements LinkedMerkleTreeStore { return this.leaves[index.toString()]; } - // This gets the leaf with the closest path. - public getClosestPath(path: number): LinkedLeaf { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - let largestLeaf = this.getLeaf(0n) as LinkedLeaf; - while (largestLeaf.nextPath < path) { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const nextIndex = this.getLeafIndex(largestLeaf.nextPath) as bigint; - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - largestLeaf = this.getLeaf(nextIndex) as LinkedLeaf; - } - return largestLeaf; - } - public setLeaf(index: bigint, value: LinkedLeaf): void { this.leaves[index.toString()] = value; } @@ -49,4 +36,21 @@ export class InMemoryLinkedMerkleTreeStorage implements LinkedMerkleTreeStore { } return BigInt(leafIndex); } + + public getMaximumIndex(): bigint { + return BigInt(Object.keys(this.leaves).length) - 1n; + } + + // This gets the leaf with the closest path. + public getClosestPath(path: number): LinkedLeaf { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + let largestLeaf = this.getLeaf(0n) as LinkedLeaf; + while (largestLeaf.nextPath < path) { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const nextIndex = this.getLeafIndex(largestLeaf.nextPath) as bigint; + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + largestLeaf = this.getLeaf(nextIndex) as LinkedLeaf; + } + return largestLeaf; + } } diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 857fe85a..bf27b663 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -19,7 +19,6 @@ class LinkedLeaf extends Struct({ export interface AbstractLinkedMerkleTree { store: LinkedMerkleTreeStore; - readonly leafCount: bigint; /** * Returns a node which lives at a given index and level. * @param level Level of the node. @@ -36,20 +35,20 @@ export interface AbstractLinkedMerkleTree { /** * Sets the value of a leaf node at a given index to a given value. - * @param index Position of the leaf node. - * @param leaf New value. + * @param path of the leaf node. + * @param value New value. */ - setLeaf(index: bigint, leaf: LinkedLeaf): void; + setLeaf(path: number, value: bigint): void; /** * Returns a leaf which lives at a given path. * @param path Index of the node. * @returns The data of the leaf. */ - getLeaf(path: number): LinkedLeaf | undefined; + getLeaf(path: number): LinkedLeaf; /** - * Returns a leaf which lives at a given path. + * Returns a leaf which is closest to a given path. * @param path Index of the node. * @returns The data of the leaf. */ @@ -74,8 +73,6 @@ export interface AbstractLinkedMerkleTreeClass { HEIGHT: number; EMPTY_ROOT: bigint; - - get leafCount(): bigint; } export function createLinkedMerkleTree( @@ -160,6 +157,13 @@ export function createLinkedMerkleTree( ].toString() ); } + + public static dummy() { + return new LinkedMerkleWitness({ + isLeft: Array(height - 1).fill(Bool(false)), + path: Array(height - 1).fill(Field(0)), + }); + } } return class AbstractLinkedRollupMerkleTree @@ -173,10 +177,6 @@ export function createLinkedMerkleTree( .getRoot() .toBigInt(); - public static get leafCount(): bigint { - return 2n ** BigInt(AbstractLinkedRollupMerkleTree.HEIGHT - 1); - } - public static WITNESS = LinkedMerkleWitness; // private in interface @@ -199,14 +199,7 @@ export function createLinkedMerkleTree( } } - public assertIndexRange(index: bigint) { - if (index > this.leafCount) { - throw new Error("Index greater than maximum leaf number"); - } - } - public getNode(level: number, index: bigint): Field { - this.assertIndexRange(index); return Field(this.store.getNode(index, level) ?? this.zeroes[level]); } @@ -215,14 +208,14 @@ export function createLinkedMerkleTree( * @param path path of the node. * @returns The data of the node. */ - public getLeaf(path: number): LinkedLeaf | undefined { + public getLeaf(path: number): LinkedLeaf { const index = this.store.getLeafIndex(path); if (index === undefined) { - return index; + throw new Error("Path does not exist in tree."); } const leaf = this.store.getLeaf(BigInt(index)); if (leaf === undefined) { - return undefined; + throw new Error("Index does not exist in tree."); } return { value: Field(leaf.value), @@ -258,24 +251,68 @@ export function createLinkedMerkleTree( } /** - * Sets the value of a leaf node at a given index to a given value. - * @param index Position of the leaf node. - * @param leaf New value. + * Sets the value of a leaf node at a given path to a given value. + * @param path Position of the leaf node. + * @param value New value. */ - public setLeaf(path: bigint, leaf: LinkedLeaf) { - this.setNode(0, index, leaf); - let currentIndex = index; + public setLeaf(path: number, value: bigint) { + const prevLeaf = this.store.getClosestPath(path); + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + let prevLeafIndex = this.store.getLeafIndex(path) as bigint; + const newPrevLeaf = { + value: prevLeaf.value, + path: prevLeaf.path, + nextPath: path, + }; + this.store.setLeaf(prevLeafIndex, newPrevLeaf); + const prevLeafFields = this.getLeaf(prevLeaf.path); + this.setNode( + 0, + prevLeafIndex, + Poseidon.hash([ + prevLeafFields.value, + prevLeafFields.path, + prevLeafFields.nextPath, + ]) + ); + + const newLeaf = { + value: value, + path: path, + nextPath: prevLeaf.nextPath, + }; + let newLeafIndex = this.store.getMaximumIndex() + 1n; + this.store.setLeaf(newLeafIndex, newLeaf); + const newLeafFields = this.getLeaf(path); + this.setNode( + 0, + newLeafIndex, + Poseidon.hash([ + newLeafFields.value, + newLeafFields.path, + newLeafFields.nextPath, + ]) + ); + for ( let level = 1; level < AbstractLinkedRollupMerkleTree.HEIGHT; level += 1 ) { - currentIndex /= 2n; + prevLeafIndex /= 2n; + newLeafIndex /= 2n; - const left = this.getNode(level - 1, currentIndex * 2n); - const right = this.getNode(level - 1, currentIndex * 2n + 1n); + const leftPrev = this.getNode(level - 1, prevLeafIndex * 2n); + const rightPrev = this.getNode(level - 1, prevLeafIndex * 2n + 1n); + const leftNew = this.getNode(level - 1, newLeafIndex * 2n); + const rightNew = this.getNode(level - 1, newLeafIndex * 2n + 1n); - this.setNode(level, currentIndex, Poseidon.hash([left, right])); + this.setNode( + level, + prevLeafIndex, + Poseidon.hash([leftPrev, rightPrev]) + ); + this.setNode(level, prevLeafIndex, Poseidon.hash([leftNew, rightNew])); } } @@ -283,7 +320,7 @@ export function createLinkedMerkleTree( * Returns the witness (also known as * [Merkle Proof or Merkle Witness](https://computersciencewiki.org/index.php/Merkle_proof)) * for the leaf at the given index. - * @param index Position of the leaf node. + * @param path of the leaf node. * @returns The witness that belongs to the leaf. */ public getWitness(path: number): LinkedMerkleWitness { @@ -314,14 +351,6 @@ export function createLinkedMerkleTree( path: pathArray, }); } - - /** - * Returns the amount of leaf nodes. - * @returns Amount of leaf nodes. - */ - public get leafCount(): bigint { - return AbstractLinkedRollupMerkleTree.leafCount; - } }; } diff --git a/packages/common/src/trees/LinkedMerkleTreeStore.ts b/packages/common/src/trees/LinkedMerkleTreeStore.ts index 00ae5353..fec94874 100644 --- a/packages/common/src/trees/LinkedMerkleTreeStore.ts +++ b/packages/common/src/trees/LinkedMerkleTreeStore.ts @@ -10,6 +10,8 @@ export interface LinkedMerkleTreeStore { getLeafIndex: (path: number) => bigint | undefined; getClosestPath: (path: number) => LinkedLeaf; + + getMaximumIndex: () => bigint; } export type LinkedLeaf = { value: bigint; path: number; nextPath: number }; From 162ab7d1b6c809ddaa7b3da9464158c0e49586ca Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:21:36 +0000 Subject: [PATCH 014/107] Remove deleted reference --- packages/persistance/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/persistance/src/index.ts b/packages/persistance/src/index.ts index 51ef2c6f..20821121 100644 --- a/packages/persistance/src/index.ts +++ b/packages/persistance/src/index.ts @@ -15,4 +15,3 @@ export * from "./services/prisma/mappers/StateTransitionMapper"; export * from "./services/prisma/mappers/TransactionMapper"; export * from "./services/prisma/mappers/BlockResultMapper"; export * from "./services/redis/RedisMerkleTreeStore"; -export * from "./services/redis/RedisLinkedMerkleTreeStore"; From 45270441ec5b9c24cc29f668d372103ab87d89db Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:48:11 +0000 Subject: [PATCH 015/107] Set up constructor --- packages/common/src/trees/LinkedMerkleTree.ts | 84 +++++++++---------- 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index bf27b663..dc938490 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -1,15 +1,35 @@ import { Bool, Field, Poseidon, Provable, Struct } from "o1js"; import { TypedClass } from "../types"; -import { range } from "../utils"; import { LinkedMerkleTreeStore } from "./LinkedMerkleTreeStore"; import { InMemoryLinkedMerkleTreeStorage } from "./InMemoryLinkedMerkleTreeStorage"; -import { - AbstractMerkleWitness, - StructTemplate, - maybeSwap, -} from "./RollupMerkleTree"; +import { StructTemplate, maybeSwap } from "./RollupMerkleTree"; + +export interface AbstractLinkedMerkleWitness extends StructTemplate { + height(): number; + + /** + * Calculates a root depending on the leaf value. + * @param leaf Value of the leaf node that belongs to this Witness. + * @returns The calculated root. + */ + calculateRoot(hash: Field): Field; + + /** + * Calculates the index of the leaf node that belongs to this Witness. + * @returns Index of the leaf. + */ + calculateIndex(): Field; + + checkMembership(root: Field, key: Field, value: Field): Bool; + + checkMembershipGetRoots( + root: Field, + key: Field, + value: Field + ): [Bool, Field, Field]; +} class LinkedLeaf extends Struct({ value: Field, @@ -61,14 +81,13 @@ export interface AbstractLinkedMerkleTree { * @param path Position of the leaf node. * @returns The witness that belongs to the leaf. */ - getWitness(path: number): AbstractMerkleWitness; + getWitness(path: number): AbstractLinkedMerkleWitness; } export interface AbstractLinkedMerkleTreeClass { new (store: LinkedMerkleTreeStore): AbstractLinkedMerkleTree; - WITNESS: TypedClass & - typeof StructTemplate & { dummy: () => AbstractMerkleWitness }; + WITNESS: TypedClass & typeof StructTemplate; HEIGHT: number; @@ -83,7 +102,7 @@ export function createLinkedMerkleTree( path: Provable.Array(Field, height - 1), isLeft: Provable.Array(Bool, height - 1), }) - implements AbstractMerkleWitness + implements AbstractLinkedMerkleWitness { public static height = height; @@ -146,24 +165,6 @@ export function createLinkedMerkleTree( key.assertEquals(calculatedKey, "Keys of MerkleWitness does not match"); return [root.equals(calculatedRoot), root, calculatedRoot]; } - - public toShortenedEntries() { - return range(0, 5) - .concat(range(this.height() - 4, this.height())) - .map((index) => - [ - this.path[index].toString(), - this.isLeft[index].toString(), - ].toString() - ); - } - - public static dummy() { - return new LinkedMerkleWitness({ - isLeft: Array(height - 1).fill(Bool(false)), - path: Array(height - 1).fill(Field(0)), - }); - } } return class AbstractLinkedRollupMerkleTree @@ -179,28 +180,25 @@ export function createLinkedMerkleTree( public static WITNESS = LinkedMerkleWitness; - // private in interface - readonly zeroes: bigint[]; - readonly store: LinkedMerkleTreeStore; public constructor(store: LinkedMerkleTreeStore) { this.store = store; - this.zeroes = [0n]; - for ( - let index = 1; - index < AbstractLinkedRollupMerkleTree.HEIGHT; - index += 1 - ) { - const previousLevel = Field(this.zeroes[index - 1]); - this.zeroes.push( - Poseidon.hash([previousLevel, previousLevel]).toBigInt() - ); - } + this.store.setLeaf(0n, { value: 0n, path: 0, nextPath: 0 }); + const baseLeaf = this.getLeaf(0); + this.setNode( + 0, + 0n, + Poseidon.hash([baseLeaf.value, baseLeaf.path, baseLeaf.nextPath]) + ); } public getNode(level: number, index: bigint): Field { - return Field(this.store.getNode(index, level) ?? this.zeroes[level]); + const node = this.store.getNode(index, level); + if (node === undefined) { + throw new Error("Path does not exist in tree."); + } + return Field(node); } /** From ba198a90b97c4c941dea91d2ebf3a6b7e1d83231 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:35:36 +0000 Subject: [PATCH 016/107] Set up constructor and fix initialisation --- .../trees/InMemoryLinkedMerkleTreeStorage.ts | 2 +- packages/common/src/trees/LinkedMerkleTree.ts | 50 ++++++++++++++++--- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts index 7cb9b344..889b7705 100644 --- a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts +++ b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts @@ -9,7 +9,7 @@ export class InMemoryLinkedMerkleTreeStorage implements LinkedMerkleTreeStore { protected leaves: { [key: string]: LinkedLeaf; - } = { "0": { value: 0n, path: 0, nextPath: 0 } }; + } = {}; public getNode(index: bigint, level: number): bigint | undefined { return this.nodes[level]?.[index.toString()]; diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index dc938490..36458be3 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -180,17 +180,24 @@ export function createLinkedMerkleTree( public static WITNESS = LinkedMerkleWitness; + readonly zeroes: bigint[]; + readonly store: LinkedMerkleTreeStore; public constructor(store: LinkedMerkleTreeStore) { this.store = store; - this.store.setLeaf(0n, { value: 0n, path: 0, nextPath: 0 }); - const baseLeaf = this.getLeaf(0); - this.setNode( - 0, - 0n, - Poseidon.hash([baseLeaf.value, baseLeaf.path, baseLeaf.nextPath]) - ); + this.zeroes = [0n]; + for ( + let index = 1; + index < AbstractLinkedRollupMerkleTree.HEIGHT; + index += 1 + ) { + const previousLevel = Field(this.zeroes[index - 1]); + this.zeroes.push( + Poseidon.hash([previousLevel, previousLevel]).toBigInt() + ); + } + this.setLeafInitialisation(); } public getNode(level: number, index: bigint): Field { @@ -314,6 +321,35 @@ export function createLinkedMerkleTree( } } + public setLeafInitialisation() { + const MAX_FIELD_VALUE = 2 ** 1000000; + this.store.setLeaf(0n, { + value: 0n, + path: 0, + nextPath: MAX_FIELD_VALUE, + }); + const initialLeaf = this.getLeaf(0); + this.setNode( + 0, + 0n, + Poseidon.hash([ + initialLeaf.value, + initialLeaf.path, + initialLeaf.nextPath, + ]) + ); + for ( + let level = 1; + level < AbstractLinkedRollupMerkleTree.HEIGHT; + level += 1 + ) { + const leftNode = this.getNode(level - 1, 0n); + const rightNode = this.getNode(level - 1, 1n); + + this.setNode(level, 0n, Poseidon.hash([leftNode, rightNode])); + } + } + /** * Returns the witness (also known as * [Merkle Proof or Merkle Witness](https://computersciencewiki.org/index.php/Merkle_proof)) From 671f5634d514066907762ef92736f1de0795c005 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 31 Oct 2024 12:04:03 +0000 Subject: [PATCH 017/107] Add in private --- packages/common/src/trees/LinkedMerkleTree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 36458be3..e21e1aaf 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -321,7 +321,7 @@ export function createLinkedMerkleTree( } } - public setLeafInitialisation() { + private setLeafInitialisation() { const MAX_FIELD_VALUE = 2 ** 1000000; this.store.setLeaf(0n, { value: 0n, From 7c7c7916bfd3e974c5e891d4382877851911371a Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 31 Oct 2024 12:47:13 +0000 Subject: [PATCH 018/107] Update comment --- packages/common/src/trees/LinkedMerkleTree.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index e21e1aaf..19bb53f0 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -11,7 +11,7 @@ export interface AbstractLinkedMerkleWitness extends StructTemplate { /** * Calculates a root depending on the leaf value. - * @param leaf Value of the leaf node that belongs to this Witness. + * @param hash Value of the leaf node that belongs to this Witness. * @returns The calculated root. */ calculateRoot(hash: Field): Field; @@ -321,6 +321,10 @@ export function createLinkedMerkleTree( } } + /** + * Sets the value of a leaf node at initialisation, + * i.e. {vale: 0, path: 0, nextPath: Field.Max} + */ private setLeafInitialisation() { const MAX_FIELD_VALUE = 2 ** 1000000; this.store.setLeaf(0n, { From bffd5d48c3232c83ac25c413f87e95666e9b58f0 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 31 Oct 2024 12:55:36 +0000 Subject: [PATCH 019/107] Update comment --- packages/common/src/trees/LinkedMerkleTree.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 19bb53f0..724fd38b 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -229,7 +229,10 @@ export function createLinkedMerkleTree( }; } - // This gets the leaf with the closest path. + /** + * Returns the leaf with a path either equal to or less than the path specified. + * @param path Position of the leaf node. + * */ public getClosestPath(path: number): LinkedLeaf { const closestLeaf = this.store.getClosestPath(path); return { From 743935dc03a5dc7b4299e0c0510bce22ca4e66dc Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 31 Oct 2024 13:02:01 +0000 Subject: [PATCH 020/107] Change confusing method name --- .../common/src/trees/InMemoryLinkedMerkleTreeStorage.ts | 2 +- packages/common/src/trees/LinkedMerkleTree.ts | 8 ++++---- packages/common/src/trees/LinkedMerkleTreeStore.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts index 889b7705..5626c0d1 100644 --- a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts +++ b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts @@ -42,7 +42,7 @@ export class InMemoryLinkedMerkleTreeStorage implements LinkedMerkleTreeStore { } // This gets the leaf with the closest path. - public getClosestPath(path: number): LinkedLeaf { + public getPathLessOrEqual(path: number): LinkedLeaf { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions let largestLeaf = this.getLeaf(0n) as LinkedLeaf; while (largestLeaf.nextPath < path) { diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 724fd38b..1682b1cf 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -72,7 +72,7 @@ export interface AbstractLinkedMerkleTree { * @param path Index of the node. * @returns The data of the leaf. */ - getClosestPath(path: number): LinkedLeaf; + getPathLessOrEqual(path: number): LinkedLeaf; /** * Returns the witness (also known as @@ -233,8 +233,8 @@ export function createLinkedMerkleTree( * Returns the leaf with a path either equal to or less than the path specified. * @param path Position of the leaf node. * */ - public getClosestPath(path: number): LinkedLeaf { - const closestLeaf = this.store.getClosestPath(path); + public getPathLessOrEqual(path: number): LinkedLeaf { + const closestLeaf = this.store.getPathLessOrEqual(path); return { value: Field(closestLeaf.value), path: Field(closestLeaf.path), @@ -264,7 +264,7 @@ export function createLinkedMerkleTree( * @param value New value. */ public setLeaf(path: number, value: bigint) { - const prevLeaf = this.store.getClosestPath(path); + const prevLeaf = this.store.getPathLessOrEqual(path); // eslint-disable-next-line @typescript-eslint/consistent-type-assertions let prevLeafIndex = this.store.getLeafIndex(path) as bigint; const newPrevLeaf = { diff --git a/packages/common/src/trees/LinkedMerkleTreeStore.ts b/packages/common/src/trees/LinkedMerkleTreeStore.ts index fec94874..92e8e11f 100644 --- a/packages/common/src/trees/LinkedMerkleTreeStore.ts +++ b/packages/common/src/trees/LinkedMerkleTreeStore.ts @@ -9,7 +9,7 @@ export interface LinkedMerkleTreeStore { getLeafIndex: (path: number) => bigint | undefined; - getClosestPath: (path: number) => LinkedLeaf; + getPathLessOrEqual: (path: number) => LinkedLeaf; getMaximumIndex: () => bigint; } From 1dfdcb4d7ef64be3a4c34fbb4449a5e4e6937a30 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 31 Oct 2024 13:54:59 +0000 Subject: [PATCH 021/107] Adding setValue --- packages/common/src/trees/LinkedMerkleTree.ts | 84 ++++++++----------- 1 file changed, 36 insertions(+), 48 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 1682b1cf..33bb7000 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -58,7 +58,7 @@ export interface AbstractLinkedMerkleTree { * @param path of the leaf node. * @param value New value. */ - setLeaf(path: number, value: bigint): void; + setValue(path: number, value: bigint): void; /** * Returns a leaf which lives at a given path. @@ -259,69 +259,57 @@ export function createLinkedMerkleTree( } /** - * Sets the value of a leaf node at a given path to a given value. - * @param path Position of the leaf node. - * @param value New value. + * Sets the value of a leaf node at a given index to a given value. + * @param index Position of the leaf node. + * @param leaf New value. */ - public setLeaf(path: number, value: bigint) { + private setLeaf(index: bigint, leaf: LinkedLeaf) { + this.setNode( + 0, + index, + Poseidon.hash([leaf.value, leaf.path, leaf.nextPath]) + ); + let tempIndex = index; + for ( + let level = 1; + level < AbstractLinkedRollupMerkleTree.HEIGHT; + level += 1 + ) { + tempIndex /= 2n; + const leftPrev = this.getNode(level - 1, tempIndex * 2n); + const rightPrev = this.getNode(level - 1, tempIndex * 2n + 1n); + this.setNode(level, tempIndex, Poseidon.hash([leftPrev, rightPrev])); + } + } + + public setValue(path: number, value: bigint) { const prevLeaf = this.store.getPathLessOrEqual(path); // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - let prevLeafIndex = this.store.getLeafIndex(path) as bigint; + const prevLeafIndex = this.store.getLeafIndex(path) as bigint; const newPrevLeaf = { value: prevLeaf.value, path: prevLeaf.path, nextPath: path, }; this.store.setLeaf(prevLeafIndex, newPrevLeaf); - const prevLeafFields = this.getLeaf(prevLeaf.path); - this.setNode( - 0, - prevLeafIndex, - Poseidon.hash([ - prevLeafFields.value, - prevLeafFields.path, - prevLeafFields.nextPath, - ]) - ); + this.setLeaf(prevLeafIndex, { + value: Field(newPrevLeaf.value), + path: Field(newPrevLeaf.path), + nextPath: Field(newPrevLeaf.nextPath), + }); const newLeaf = { value: value, path: path, nextPath: prevLeaf.nextPath, }; - let newLeafIndex = this.store.getMaximumIndex() + 1n; + const newLeafIndex = this.store.getMaximumIndex() + 1n; this.store.setLeaf(newLeafIndex, newLeaf); - const newLeafFields = this.getLeaf(path); - this.setNode( - 0, - newLeafIndex, - Poseidon.hash([ - newLeafFields.value, - newLeafFields.path, - newLeafFields.nextPath, - ]) - ); - - for ( - let level = 1; - level < AbstractLinkedRollupMerkleTree.HEIGHT; - level += 1 - ) { - prevLeafIndex /= 2n; - newLeafIndex /= 2n; - - const leftPrev = this.getNode(level - 1, prevLeafIndex * 2n); - const rightPrev = this.getNode(level - 1, prevLeafIndex * 2n + 1n); - const leftNew = this.getNode(level - 1, newLeafIndex * 2n); - const rightNew = this.getNode(level - 1, newLeafIndex * 2n + 1n); - - this.setNode( - level, - prevLeafIndex, - Poseidon.hash([leftPrev, rightPrev]) - ); - this.setNode(level, prevLeafIndex, Poseidon.hash([leftNew, rightNew])); - } + this.setLeaf(newLeafIndex, { + value: Field(newLeaf.value), + path: Field(newLeaf.path), + nextPath: Field(newLeaf.nextPath), + }); } /** From 2423256c9cfa92aea4d7fbaa8c78ae2129743fd5 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:35:45 +0000 Subject: [PATCH 022/107] Changing setValue to insert and update --- packages/common/src/trees/LinkedMerkleTree.ts | 68 ++++++++----------- 1 file changed, 28 insertions(+), 40 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 33bb7000..138b4233 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -202,10 +202,7 @@ export function createLinkedMerkleTree( public getNode(level: number, index: bigint): Field { const node = this.store.getNode(index, level); - if (node === undefined) { - throw new Error("Path does not exist in tree."); - } - return Field(node); + return Field(node ?? this.zeroes[level]); } /** @@ -282,30 +279,39 @@ export function createLinkedMerkleTree( } } + /** + * Sets the value of a node at a given index to a given value. + * @param path Position of the leaf node. + * @param value New value. + */ public setValue(path: number, value: bigint) { + let index = this.store.getLeafIndex(path); const prevLeaf = this.store.getPathLessOrEqual(path); - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const prevLeafIndex = this.store.getLeafIndex(path) as bigint; - const newPrevLeaf = { - value: prevLeaf.value, - path: prevLeaf.path, - nextPath: path, - }; - this.store.setLeaf(prevLeafIndex, newPrevLeaf); - this.setLeaf(prevLeafIndex, { - value: Field(newPrevLeaf.value), - path: Field(newPrevLeaf.path), - nextPath: Field(newPrevLeaf.nextPath), - }); - + if (index === undefined) { + // The above means the path doesn't already exist and we are inserting, not updating. + // This requires us to update the node with the previous path, as well. + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const prevLeafIndex = this.store.getLeafIndex(prevLeaf.path) as bigint; + const newPrevLeaf = { + value: prevLeaf.value, + path: prevLeaf.path, + nextPath: path, + }; + this.store.setLeaf(prevLeafIndex, newPrevLeaf); + this.setLeaf(prevLeafIndex, { + value: Field(newPrevLeaf.value), + path: Field(newPrevLeaf.path), + nextPath: Field(newPrevLeaf.nextPath), + }); + index = this.store.getMaximumIndex() + 1n; + } const newLeaf = { value: value, path: path, nextPath: prevLeaf.nextPath, }; - const newLeafIndex = this.store.getMaximumIndex() + 1n; - this.store.setLeaf(newLeafIndex, newLeaf); - this.setLeaf(newLeafIndex, { + this.store.setLeaf(index, newLeaf); + this.setLeaf(index, { value: Field(newLeaf.value), path: Field(newLeaf.path), nextPath: Field(newLeaf.nextPath), @@ -324,25 +330,7 @@ export function createLinkedMerkleTree( nextPath: MAX_FIELD_VALUE, }); const initialLeaf = this.getLeaf(0); - this.setNode( - 0, - 0n, - Poseidon.hash([ - initialLeaf.value, - initialLeaf.path, - initialLeaf.nextPath, - ]) - ); - for ( - let level = 1; - level < AbstractLinkedRollupMerkleTree.HEIGHT; - level += 1 - ) { - const leftNode = this.getNode(level - 1, 0n); - const rightNode = this.getNode(level - 1, 1n); - - this.setNode(level, 0n, Poseidon.hash([leftNode, rightNode])); - } + this.setLeaf(0n, initialLeaf); } /** From c1dc6eefed5408da3b88e600ef44b92b014a3d32 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:53:32 +0000 Subject: [PATCH 023/107] Change getPathLessOrEqual to include equal --- packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts index 5626c0d1..b59a8b37 100644 --- a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts +++ b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts @@ -45,7 +45,7 @@ export class InMemoryLinkedMerkleTreeStorage implements LinkedMerkleTreeStore { public getPathLessOrEqual(path: number): LinkedLeaf { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions let largestLeaf = this.getLeaf(0n) as LinkedLeaf; - while (largestLeaf.nextPath < path) { + while (largestLeaf.nextPath <= path) { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const nextIndex = this.getLeafIndex(largestLeaf.nextPath) as bigint; // eslint-disable-next-line @typescript-eslint/consistent-type-assertions From 1c3e27e988e79a08cb028457384a576aa14e6b4f Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:26:16 +0000 Subject: [PATCH 024/107] Change types as path and nextPath are bigint and not number --- .../trees/InMemoryLinkedMerkleTreeStorage.ts | 4 ++-- packages/common/src/trees/LinkedMerkleTree.ts | 22 +++++++++---------- .../common/src/trees/LinkedMerkleTreeStore.ts | 6 ++--- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts index b59a8b37..2628f23c 100644 --- a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts +++ b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts @@ -27,7 +27,7 @@ export class InMemoryLinkedMerkleTreeStorage implements LinkedMerkleTreeStore { this.leaves[index.toString()] = value; } - public getLeafIndex(path: number): bigint | undefined { + public getLeafIndex(path: bigint): bigint | undefined { const leafIndex = Object.keys(this.leaves).find((key) => { return this.leaves[key].path === path; }); @@ -42,7 +42,7 @@ export class InMemoryLinkedMerkleTreeStorage implements LinkedMerkleTreeStore { } // This gets the leaf with the closest path. - public getPathLessOrEqual(path: number): LinkedLeaf { + public getPathLessOrEqual(path: bigint): LinkedLeaf { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions let largestLeaf = this.getLeaf(0n) as LinkedLeaf; while (largestLeaf.nextPath <= path) { diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 138b4233..01da5fd7 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -58,21 +58,21 @@ export interface AbstractLinkedMerkleTree { * @param path of the leaf node. * @param value New value. */ - setValue(path: number, value: bigint): void; + setValue(path: bigint, value: bigint): void; /** * Returns a leaf which lives at a given path. * @param path Index of the node. * @returns The data of the leaf. */ - getLeaf(path: number): LinkedLeaf; + getLeaf(path: bigint): LinkedLeaf; /** * Returns a leaf which is closest to a given path. * @param path Index of the node. * @returns The data of the leaf. */ - getPathLessOrEqual(path: number): LinkedLeaf; + getPathLessOrEqual(path: bigint): LinkedLeaf; /** * Returns the witness (also known as @@ -81,7 +81,7 @@ export interface AbstractLinkedMerkleTree { * @param path Position of the leaf node. * @returns The witness that belongs to the leaf. */ - getWitness(path: number): AbstractLinkedMerkleWitness; + getWitness(path: bigint): AbstractLinkedMerkleWitness; } export interface AbstractLinkedMerkleTreeClass { @@ -210,7 +210,7 @@ export function createLinkedMerkleTree( * @param path path of the node. * @returns The data of the node. */ - public getLeaf(path: number): LinkedLeaf { + public getLeaf(path: bigint): LinkedLeaf { const index = this.store.getLeafIndex(path); if (index === undefined) { throw new Error("Path does not exist in tree."); @@ -230,7 +230,7 @@ export function createLinkedMerkleTree( * Returns the leaf with a path either equal to or less than the path specified. * @param path Position of the leaf node. * */ - public getPathLessOrEqual(path: number): LinkedLeaf { + public getPathLessOrEqual(path: bigint): LinkedLeaf { const closestLeaf = this.store.getPathLessOrEqual(path); return { value: Field(closestLeaf.value), @@ -284,7 +284,7 @@ export function createLinkedMerkleTree( * @param path Position of the leaf node. * @param value New value. */ - public setValue(path: number, value: bigint) { + public setValue(path: bigint, value: bigint) { let index = this.store.getLeafIndex(path); const prevLeaf = this.store.getPathLessOrEqual(path); if (index === undefined) { @@ -323,13 +323,13 @@ export function createLinkedMerkleTree( * i.e. {vale: 0, path: 0, nextPath: Field.Max} */ private setLeafInitialisation() { - const MAX_FIELD_VALUE = 2 ** 1000000; + const MAX_FIELD_VALUE: bigint = BigInt(2 ** 53 - 1); this.store.setLeaf(0n, { value: 0n, - path: 0, + path: 0n, nextPath: MAX_FIELD_VALUE, }); - const initialLeaf = this.getLeaf(0); + const initialLeaf = this.getLeaf(0n); this.setLeaf(0n, initialLeaf); } @@ -340,7 +340,7 @@ export function createLinkedMerkleTree( * @param path of the leaf node. * @returns The witness that belongs to the leaf. */ - public getWitness(path: number): LinkedMerkleWitness { + public getWitness(path: bigint): LinkedMerkleWitness { const index = this.store.getLeafIndex(path); if (index === undefined) { throw new Error("Path does not exist in tree."); diff --git a/packages/common/src/trees/LinkedMerkleTreeStore.ts b/packages/common/src/trees/LinkedMerkleTreeStore.ts index 92e8e11f..8ffe3420 100644 --- a/packages/common/src/trees/LinkedMerkleTreeStore.ts +++ b/packages/common/src/trees/LinkedMerkleTreeStore.ts @@ -7,11 +7,11 @@ export interface LinkedMerkleTreeStore { getLeaf: (index: bigint) => LinkedLeaf | undefined; - getLeafIndex: (path: number) => bigint | undefined; + getLeafIndex: (path: bigint) => bigint | undefined; - getPathLessOrEqual: (path: number) => LinkedLeaf; + getPathLessOrEqual: (path: bigint) => LinkedLeaf; getMaximumIndex: () => bigint; } -export type LinkedLeaf = { value: bigint; path: number; nextPath: number }; +export type LinkedLeaf = { value: bigint; path: bigint; nextPath: bigint }; From af9630711b156138d0d600784f55a556517a3cc0 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 1 Nov 2024 08:18:23 +0000 Subject: [PATCH 025/107] Update state transition prover to use LinkedMerkleTree --- .../src/prover/statetransition/StateTransitionProver.ts | 4 ++-- .../statetransition/StateTransitionWitnessProvider.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index ff271f60..2c6731a4 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -2,11 +2,11 @@ import { AreProofsEnabled, PlainZkProgram, provableMethod, - RollupMerkleTreeWitness, ZkProgrammable, } from "@proto-kit/common"; import { Field, Provable, SelfProof, ZkProgram } from "o1js"; import { injectable } from "tsyringe"; +import { LinkedMerkleTreeWitness } from "@proto-kit/common/dist/trees/LinkedMerkleTree"; import { constants } from "../../Constants"; import { ProvableStateTransition } from "../../model/StateTransition"; @@ -189,7 +189,7 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< type: ProvableStateTransitionType, index = 0 ) { - const witness = Provable.witness(RollupMerkleTreeWitness, () => + const witness = Provable.witness(LinkedMerkleTreeWitness, () => this.witnessProvider.getWitness(transition.path) ); diff --git a/packages/protocol/src/prover/statetransition/StateTransitionWitnessProvider.ts b/packages/protocol/src/prover/statetransition/StateTransitionWitnessProvider.ts index a002c7db..fbe2930e 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionWitnessProvider.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionWitnessProvider.ts @@ -1,6 +1,6 @@ import type { Field } from "o1js"; import { injectable } from "tsyringe"; -import { RollupMerkleTreeWitness } from "@proto-kit/common"; +import { LinkedMerkleTreeWitness } from "@proto-kit/common/dist/trees/LinkedMerkleTree"; /** * Interface for providing merkle witnesses to the state-transition prover @@ -10,14 +10,14 @@ export interface StateTransitionWitnessProvider { * Provides the merkle witness corresponding to the given key * @param key Merkle-tree key */ - getWitness: (key: Field) => RollupMerkleTreeWitness; + getWitness: (key: Field) => LinkedMerkleTreeWitness; } @injectable() export class NoOpStateTransitionWitnessProvider implements StateTransitionWitnessProvider { - public getWitness(): RollupMerkleTreeWitness { - return new RollupMerkleTreeWitness({ path: [], isLeft: [] }); + public getWitness(): LinkedMerkleTreeWitness { + return new LinkedMerkleTreeWitness({ path: [], isLeft: [] }); } } From 396c01fd052466e9a59e2902a1cc51487a5cdb21 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Tue, 5 Nov 2024 16:02:20 +0000 Subject: [PATCH 026/107] Add types --- packages/common/src/trees/LinkedMerkleTree.ts | 148 +++----------- packages/common/src/trees/RollupMerkleTree.ts | 184 +++++++++--------- .../StateTransitionWitnessProvider.ts | 8 +- 3 files changed, 123 insertions(+), 217 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 01da5fd7..d0d2dc08 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -1,35 +1,10 @@ -import { Bool, Field, Poseidon, Provable, Struct } from "o1js"; +import { Bool, Field, Poseidon, Struct } from "o1js"; import { TypedClass } from "../types"; import { LinkedMerkleTreeStore } from "./LinkedMerkleTreeStore"; import { InMemoryLinkedMerkleTreeStorage } from "./InMemoryLinkedMerkleTreeStorage"; -import { StructTemplate, maybeSwap } from "./RollupMerkleTree"; - -export interface AbstractLinkedMerkleWitness extends StructTemplate { - height(): number; - - /** - * Calculates a root depending on the leaf value. - * @param hash Value of the leaf node that belongs to this Witness. - * @returns The calculated root. - */ - calculateRoot(hash: Field): Field; - - /** - * Calculates the index of the leaf node that belongs to this Witness. - * @returns Index of the leaf. - */ - calculateIndex(): Field; - - checkMembership(root: Field, key: Field, value: Field): Bool; - - checkMembershipGetRoots( - root: Field, - key: Field, - value: Field - ): [Bool, Field, Field]; -} +import { RollupMerkleTreeWitness } from "./RollupMerkleTree"; class LinkedLeaf extends Struct({ value: Field, @@ -37,6 +12,13 @@ class LinkedLeaf extends Struct({ nextPath: Field, }) {} +export class LinkedStructTemplate extends Struct({ + leaf: LinkedLeaf, + merkleWitness: RollupMerkleTreeWitness, +}) {} + +export interface AbstractLinkedMerkleWitness extends LinkedStructTemplate {} + export interface AbstractLinkedMerkleTree { store: LinkedMerkleTreeStore; /** @@ -67,13 +49,6 @@ export interface AbstractLinkedMerkleTree { */ getLeaf(path: bigint): LinkedLeaf; - /** - * Returns a leaf which is closest to a given path. - * @param path Index of the node. - * @returns The data of the leaf. - */ - getPathLessOrEqual(path: bigint): LinkedLeaf; - /** * Returns the witness (also known as * [Merkle Proof or Merkle Witness](https://computersciencewiki.org/index.php/Merkle_proof)) @@ -87,7 +62,8 @@ export interface AbstractLinkedMerkleTree { export interface AbstractLinkedMerkleTreeClass { new (store: LinkedMerkleTreeStore): AbstractLinkedMerkleTree; - WITNESS: TypedClass & typeof StructTemplate; + WITNESS: TypedClass & + typeof LinkedStructTemplate; HEIGHT: number; @@ -99,73 +75,10 @@ export function createLinkedMerkleTree( ): AbstractLinkedMerkleTreeClass { class LinkedMerkleWitness extends Struct({ - path: Provable.Array(Field, height - 1), - isLeft: Provable.Array(Bool, height - 1), + leaf: LinkedLeaf, + merkleWitness: RollupMerkleTreeWitness, }) - implements AbstractLinkedMerkleWitness - { - public static height = height; - - public height(): number { - return LinkedMerkleWitness.height; - } - - /** - * Calculates a root depending on the leaf value. - * @param leaf Value of the leaf node that belongs to this Witness. - * @returns The calculated root. - */ - public calculateRoot(leaf: Field): Field { - let hash = leaf; - const n = this.height(); - - for (let index = 1; index < n; ++index) { - const isLeft = this.isLeft[index - 1]; - - const [left, right] = maybeSwap(isLeft, hash, this.path[index - 1]); - hash = Poseidon.hash([left, right]); - } - - return hash; - } - - /** - * Calculates the index of the leaf node that belongs to this Witness. - * @returns Index of the leaf. - */ - public calculateIndex(): Field { - let powerOfTwo = Field(1); - let index = Field(0); - const n = this.height(); - - for (let i = 1; i < n; ++i) { - index = Provable.if(this.isLeft[i - 1], index, index.add(powerOfTwo)); - powerOfTwo = powerOfTwo.mul(2); - } - - return index; - } - - public checkMembership(root: Field, key: Field, value: Field): Bool { - const calculatedRoot = this.calculateRoot(value); - const calculatedKey = this.calculateIndex(); - // We don't have to range-check the key, because if it would be greater - // than leafCount, it would not match the computedKey - key.assertEquals(calculatedKey, "Keys of MerkleWitness does not match"); - return root.equals(calculatedRoot); - } - - public checkMembershipGetRoots( - root: Field, - key: Field, - value: Field - ): [Bool, Field, Field] { - const calculatedRoot = this.calculateRoot(value); - const calculatedKey = this.calculateIndex(); - key.assertEquals(calculatedKey, "Keys of MerkleWitness does not match"); - return [root.equals(calculatedRoot), root, calculatedRoot]; - } - } + implements AbstractLinkedMerkleWitness {} return class AbstractLinkedRollupMerkleTree implements AbstractLinkedMerkleTree @@ -211,26 +124,14 @@ export function createLinkedMerkleTree( * @returns The data of the node. */ public getLeaf(path: bigint): LinkedLeaf { - const index = this.store.getLeafIndex(path); - if (index === undefined) { - throw new Error("Path does not exist in tree."); - } - const leaf = this.store.getLeaf(BigInt(index)); - if (leaf === undefined) { - throw new Error("Index does not exist in tree."); - } - return { - value: Field(leaf.value), - path: Field(leaf.path), - nextPath: Field(leaf.nextPath), - }; + return this.getPathLessOrEqual(path); } /** * Returns the leaf with a path either equal to or less than the path specified. * @param path Position of the leaf node. * */ - public getPathLessOrEqual(path: bigint): LinkedLeaf { + private getPathLessOrEqual(path: bigint): LinkedLeaf { const closestLeaf = this.store.getPathLessOrEqual(path); return { value: Field(closestLeaf.value), @@ -341,14 +242,14 @@ export function createLinkedMerkleTree( * @returns The witness that belongs to the leaf. */ public getWitness(path: bigint): LinkedMerkleWitness { - const index = this.store.getLeafIndex(path); - if (index === undefined) { - throw new Error("Path does not exist in tree."); - } + const leaf = this.getPathLessOrEqual(path); const pathArray = []; const isLefts = []; - let currentIndex = index; + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + let currentIndex = this.store.getLeafIndex( + leaf.path.toBigInt() + ) as bigint; for ( let level = 0; level < AbstractLinkedRollupMerkleTree.HEIGHT - 1; @@ -364,8 +265,11 @@ export function createLinkedMerkleTree( currentIndex /= 2n; } return new LinkedMerkleWitness({ - isLeft: isLefts, - path: pathArray, + merkleWitness: new RollupMerkleTreeWitness({ + path: pathArray, + isLeft: isLefts, + }), + leaf: leaf, }); } }; diff --git a/packages/common/src/trees/RollupMerkleTree.ts b/packages/common/src/trees/RollupMerkleTree.ts index bd61171f..a5541adf 100644 --- a/packages/common/src/trees/RollupMerkleTree.ts +++ b/packages/common/src/trees/RollupMerkleTree.ts @@ -16,7 +16,7 @@ export interface AbstractMerkleWitness extends StructTemplate { /** * Calculates a root depending on the leaf value. - * @param leaf Value of the leaf node that belongs to this Witness. + * @param hash Value of the leaf node that belongs to this Witness. * @returns The calculated root. */ calculateRoot(hash: Field): Field; @@ -94,6 +94,96 @@ export interface AbstractMerkleTreeClass { get leafCount(): bigint; } +const HEIGHT: number = 40; +/** + * The {@link RollupMerkleWitness} class defines a circuit-compatible base class + * for [Merkle Witness'](https://computersciencewiki.org/index.php/Merkle_proof). + */ +class RollupMerkleWitness + extends Struct({ + path: Provable.Array(Field, HEIGHT - 1), + isLeft: Provable.Array(Bool, HEIGHT - 1), + }) + implements AbstractMerkleWitness +{ + public static height = HEIGHT; + + public height(): number { + return RollupMerkleWitness.height; + } + + /** + * Calculates a root depending on the leaf value. + * @param leaf Value of the leaf node that belongs to this Witness. + * @returns The calculated root. + */ + public calculateRoot(leaf: Field): Field { + let hash = leaf; + const n = this.height(); + + for (let index = 1; index < n; ++index) { + const isLeft = this.isLeft[index - 1]; + // eslint-disable-next-line @typescript-eslint/no-use-before-define + const [left, right] = maybeSwap(isLeft, hash, this.path[index - 1]); + hash = Poseidon.hash([left, right]); + } + + return hash; + } + + /** + * Calculates the index of the leaf node that belongs to this Witness. + * @returns Index of the leaf. + */ + public calculateIndex(): Field { + let powerOfTwo = Field(1); + let index = Field(0); + const n = this.height(); + + for (let i = 1; i < n; ++i) { + index = Provable.if(this.isLeft[i - 1], index, index.add(powerOfTwo)); + powerOfTwo = powerOfTwo.mul(2); + } + + return index; + } + + public checkMembership(root: Field, key: Field, value: Field): Bool { + const calculatedRoot = this.calculateRoot(value); + const calculatedKey = this.calculateIndex(); + // We don't have to range-check the key, because if it would be greater + // than leafCount, it would not match the computedKey + key.assertEquals(calculatedKey, "Keys of MerkleWitness does not match"); + return root.equals(calculatedRoot); + } + + public checkMembershipGetRoots( + root: Field, + key: Field, + value: Field + ): [Bool, Field, Field] { + const calculatedRoot = this.calculateRoot(value); + const calculatedKey = this.calculateIndex(); + key.assertEquals(calculatedKey, "Keys of MerkleWitness does not match"); + return [root.equals(calculatedRoot), root, calculatedRoot]; + } + + public toShortenedEntries() { + return range(0, 5) + .concat(range(this.height() - 4, this.height())) + .map((index) => + [this.path[index].toString(), this.isLeft[index].toString()].toString() + ); + } + + public static dummy() { + return new RollupMerkleWitness({ + isLeft: Array(this.height - 1).fill(Bool(false)), + path: Array(this.height - 1).fill(Field(0)), + }); + } +} + /** * A [Merkle Tree](https://en.wikipedia.org/wiki/Merkle_tree) is a binary tree in * which every leaf is the cryptography hash of a piece of data, @@ -114,98 +204,6 @@ export interface AbstractMerkleTreeClass { * It also holds the Witness class under tree.WITNESS */ export function createMerkleTree(height: number): AbstractMerkleTreeClass { - /** - * The {@link BaseMerkleWitness} class defines a circuit-compatible base class - * for [Merkle Witness'](https://computersciencewiki.org/index.php/Merkle_proof). - */ - class RollupMerkleWitness - extends Struct({ - path: Provable.Array(Field, height - 1), - isLeft: Provable.Array(Bool, height - 1), - }) - implements AbstractMerkleWitness - { - public static height = height; - - public height(): number { - return RollupMerkleWitness.height; - } - - /** - * Calculates a root depending on the leaf value. - * @param leaf Value of the leaf node that belongs to this Witness. - * @returns The calculated root. - */ - public calculateRoot(leaf: Field): Field { - let hash = leaf; - const n = this.height(); - - for (let index = 1; index < n; ++index) { - const isLeft = this.isLeft[index - 1]; - // eslint-disable-next-line @typescript-eslint/no-use-before-define - const [left, right] = maybeSwap(isLeft, hash, this.path[index - 1]); - hash = Poseidon.hash([left, right]); - } - - return hash; - } - - /** - * Calculates the index of the leaf node that belongs to this Witness. - * @returns Index of the leaf. - */ - public calculateIndex(): Field { - let powerOfTwo = Field(1); - let index = Field(0); - const n = this.height(); - - for (let i = 1; i < n; ++i) { - index = Provable.if(this.isLeft[i - 1], index, index.add(powerOfTwo)); - powerOfTwo = powerOfTwo.mul(2); - } - - return index; - } - - public checkMembership(root: Field, key: Field, value: Field): Bool { - const calculatedRoot = this.calculateRoot(value); - const calculatedKey = this.calculateIndex(); - // We don't have to range-check the key, because if it would be greater - // than leafCount, it would not match the computedKey - key.assertEquals(calculatedKey, "Keys of MerkleWitness does not match"); - return root.equals(calculatedRoot); - } - - public checkMembershipGetRoots( - root: Field, - key: Field, - value: Field - ): [Bool, Field, Field] { - const calculatedRoot = this.calculateRoot(value); - const calculatedKey = this.calculateIndex(); - key.assertEquals(calculatedKey, "Keys of MerkleWitness does not match"); - return [root.equals(calculatedRoot), root, calculatedRoot]; - } - - public toShortenedEntries() { - return range(0, 5) - .concat(range(this.height() - 4, this.height())) - .map((index) => - [ - this.path[index].toString(), - this.isLeft[index].toString(), - ].toString() - ); - } - - public static dummy() { - return new RollupMerkleWitness({ - isLeft: Array(height - 1).fill(Bool(false)), - path: Array(height - 1).fill(Field(0)), - }); - } - } - return class AbstractRollupMerkleTree implements AbstractMerkleTree { public static HEIGHT = height; diff --git a/packages/protocol/src/prover/statetransition/StateTransitionWitnessProvider.ts b/packages/protocol/src/prover/statetransition/StateTransitionWitnessProvider.ts index fbe2930e..1450f685 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionWitnessProvider.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionWitnessProvider.ts @@ -1,6 +1,7 @@ -import type { Field } from "o1js"; +import { Field } from "o1js"; import { injectable } from "tsyringe"; import { LinkedMerkleTreeWitness } from "@proto-kit/common/dist/trees/LinkedMerkleTree"; +import { RollupMerkleTreeWitness } from "@proto-kit/common/dist/trees/RollupMerkleTree"; /** * Interface for providing merkle witnesses to the state-transition prover @@ -18,6 +19,9 @@ export class NoOpStateTransitionWitnessProvider implements StateTransitionWitnessProvider { public getWitness(): LinkedMerkleTreeWitness { - return new LinkedMerkleTreeWitness({ path: [], isLeft: [] }); + return new LinkedMerkleTreeWitness({ + merkleWitness: new RollupMerkleTreeWitness({ path: [], isLeft: [] }), + leaf: { value: Field(0), path: Field(0), nextPath: Field(0) }, + }); } } From 5f3f6a2bea633bf75ac387a92ddeabc321f63b94 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Tue, 5 Nov 2024 16:11:21 +0000 Subject: [PATCH 027/107] Remove unnecessary code --- packages/common/src/trees/LinkedMerkleTree.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index d0d2dc08..b9bd73f0 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -74,10 +74,7 @@ export function createLinkedMerkleTree( height: number ): AbstractLinkedMerkleTreeClass { class LinkedMerkleWitness - extends Struct({ - leaf: LinkedLeaf, - merkleWitness: RollupMerkleTreeWitness, - }) + extends LinkedStructTemplate implements AbstractLinkedMerkleWitness {} return class AbstractLinkedRollupMerkleTree From 72011b4e7ff9d95e7b7350581acd85b9c96db4eb Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Wed, 6 Nov 2024 08:27:58 +0000 Subject: [PATCH 028/107] Add in-circuit checks for reading --- .../statetransition/StateTransitionProver.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index 2c6731a4..43a2265f 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -4,7 +4,7 @@ import { provableMethod, ZkProgrammable, } from "@proto-kit/common"; -import { Field, Provable, SelfProof, ZkProgram } from "o1js"; +import { Bool, Field, Provable, SelfProof, ZkProgram } from "o1js"; import { injectable } from "tsyringe"; import { LinkedMerkleTreeWitness } from "@proto-kit/common/dist/trees/LinkedMerkleTree"; @@ -193,7 +193,17 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< this.witnessProvider.getWitness(transition.path) ); - const membershipValid = witness.checkMembership( + const checkLeafValue = Provable.if( + transition.from.isSome, + Bool, + witness.leaf.path.equals(transition.path), + witness.leaf.path.lessThan(transition.path) && + witness.leaf.nextPath.greaterThan(transition.path) + ); + + checkLeafValue.assertTrue(); + + const membershipValid = witness.merkleWitness.checkMembership( state.stateRoot, transition.path, transition.from.value @@ -208,7 +218,9 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< ) ); - const newRoot = witness.calculateRoot(transition.to.value); + const newRoot = witness.merkleWitness.calculateRoot(transition.to.value); + + // LEAVE AS IS for below Linked Merkle Tree state.stateRoot = Provable.if( transition.to.isSome, From cb39718c5655651ff9ab2566455522b1e7ad2420 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Wed, 6 Nov 2024 08:32:44 +0000 Subject: [PATCH 029/107] Add in-circuit checks for reading --- .../src/prover/statetransition/StateTransitionProver.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index 43a2265f..f8deae37 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -197,8 +197,9 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< transition.from.isSome, Bool, witness.leaf.path.equals(transition.path), - witness.leaf.path.lessThan(transition.path) && - witness.leaf.nextPath.greaterThan(transition.path) + witness.leaf.path + .lessThan(transition.path) + .and(witness.leaf.nextPath.greaterThan(transition.path)) ); checkLeafValue.assertTrue(); From 2258c694f6391ff77d52cfe21f6b7b8b0b1ef657 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Wed, 6 Nov 2024 14:38:47 +0000 Subject: [PATCH 030/107] Update code for insertion (WIP) --- packages/common/src/trees/LinkedMerkleTree.ts | 2 ++ packages/common/src/trees/RollupMerkleTree.ts | 7 ++++ .../statetransition/StateTransitionProver.ts | 36 ++++++++++++------- .../StateTransitionWitnessProvider.ts | 1 + 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index b9bd73f0..df4b76e8 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -15,6 +15,7 @@ class LinkedLeaf extends Struct({ export class LinkedStructTemplate extends Struct({ leaf: LinkedLeaf, merkleWitness: RollupMerkleTreeWitness, + nextFreeIndex: Field, }) {} export interface AbstractLinkedMerkleWitness extends LinkedStructTemplate {} @@ -267,6 +268,7 @@ export function createLinkedMerkleTree( isLeft: isLefts, }), leaf: leaf, + nextFreeIndex: Field(this.store.getMaximumIndex() + 1n), }); } }; diff --git a/packages/common/src/trees/RollupMerkleTree.ts b/packages/common/src/trees/RollupMerkleTree.ts index a5541adf..57f46067 100644 --- a/packages/common/src/trees/RollupMerkleTree.ts +++ b/packages/common/src/trees/RollupMerkleTree.ts @@ -29,6 +29,8 @@ export interface AbstractMerkleWitness extends StructTemplate { checkMembership(root: Field, key: Field, value: Field): Bool; + checkMembershipSimple(root: Field, value: Field): Bool; + checkMembershipGetRoots( root: Field, key: Field, @@ -157,6 +159,11 @@ class RollupMerkleWitness return root.equals(calculatedRoot); } + public checkMembershipSimple(root: Field, value: Field): Bool { + const calculatedRoot = this.calculateRoot(value); + return root.equals(calculatedRoot); + } + public checkMembershipGetRoots( root: Field, key: Field, diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index f8deae37..946afe06 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -4,7 +4,7 @@ import { provableMethod, ZkProgrammable, } from "@proto-kit/common"; -import { Bool, Field, Provable, SelfProof, ZkProgram } from "o1js"; +import { Bool, Field, Poseidon, Provable, SelfProof, ZkProgram } from "o1js"; import { injectable } from "tsyringe"; import { LinkedMerkleTreeWitness } from "@proto-kit/common/dist/trees/LinkedMerkleTree"; @@ -204,22 +204,34 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< checkLeafValue.assertTrue(); - const membershipValid = witness.merkleWitness.checkMembership( + const membershipValid = witness.merkleWitness.checkMembershipSimple( state.stateRoot, - transition.path, - transition.from.value + Poseidon.hash([ + transition.from.value, + transition.path, + witness.leaf.nextPath, + ]) ); - membershipValid - .or(transition.from.isSome.not()) - .assertTrue( - errors.merkleWitnessNotCorrect( - index, - type.isNormal().toBoolean() ? "normal" : "protocol" - ) - ); + membershipValid.assertTrue( + errors.merkleWitnessNotCorrect( + index, + type.isNormal().toBoolean() ? "normal" : "protocol" + ) + ); + + // Now for inserting. This requires changing the leaf before and inserting a new leaf. + // The new leaf requires a new witness. + const oldRoot = witness.merkleWitness.calculateRoot( + Poseidon.hash([witness.leaf.value, witness.leaf.path, transition.path]) + ); + + const newWitness = Provable.witness(LinkedMerkleTreeWitness, () => + this.witnessProvider.getWitness(transition.path) + ); const newRoot = witness.merkleWitness.calculateRoot(transition.to.value); + // const newRoot = witness.merkleWitness.calculateRoot(transition.to.value); // LEAVE AS IS for below Linked Merkle Tree diff --git a/packages/protocol/src/prover/statetransition/StateTransitionWitnessProvider.ts b/packages/protocol/src/prover/statetransition/StateTransitionWitnessProvider.ts index 1450f685..64558614 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionWitnessProvider.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionWitnessProvider.ts @@ -22,6 +22,7 @@ export class NoOpStateTransitionWitnessProvider return new LinkedMerkleTreeWitness({ merkleWitness: new RollupMerkleTreeWitness({ path: [], isLeft: [] }), leaf: { value: Field(0), path: Field(0), nextPath: Field(0) }, + nextFreeIndex: Field(1), }); } } From 27f0220d47d81bc1515e58d035ec187439025212 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Mon, 11 Nov 2024 08:51:31 +0000 Subject: [PATCH 031/107] Update code to reflect LinkedMerkleWitness --- .../src/model/StateTransitionProvableBatch.ts | 19 ++++---- .../statetransition/StateTransitionProver.ts | 43 +++++++++++-------- 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/packages/protocol/src/model/StateTransitionProvableBatch.ts b/packages/protocol/src/model/StateTransitionProvableBatch.ts index cbd7283b..bb363b6b 100644 --- a/packages/protocol/src/model/StateTransitionProvableBatch.ts +++ b/packages/protocol/src/model/StateTransitionProvableBatch.ts @@ -1,10 +1,9 @@ import { Bool, Provable, Struct } from "o1js"; +import { InMemoryLinkedMerkleTreeStorage, range } from "@proto-kit/common"; import { - InMemoryMerkleTreeStorage, - range, - RollupMerkleTree, - RollupMerkleTreeWitness, -} from "@proto-kit/common"; + LinkedMerkleTree, + LinkedMerkleTreeWitness, +} from "@proto-kit/common/dist/trees/LinkedMerkleTree"; import { constants } from "../Constants"; @@ -67,7 +66,7 @@ export class StateTransitionProvableBatch extends Struct({ ), merkleWitnesses: Provable.Array( - RollupMerkleTreeWitness, + LinkedMerkleTreeWitness, constants.stateTransitionProverBatchSize ), }) { @@ -76,7 +75,7 @@ export class StateTransitionProvableBatch extends Struct({ transition: ProvableStateTransition; type: ProvableStateTransitionType; }[], - merkleWitnesses: RollupMerkleTreeWitness[] + merkleWitnesses: LinkedMerkleTreeWitness[] ): StateTransitionProvableBatch { const batch = transitions.map((entry) => entry.transition); const transitionTypes = transitions.map((entry) => entry.type); @@ -96,7 +95,7 @@ export class StateTransitionProvableBatch extends Struct({ batch.push(ProvableStateTransition.dummy()); transitionTypes.push(ProvableStateTransitionType.normal); witnesses.push( - new RollupMerkleTree(new InMemoryMerkleTreeStorage()).getWitness( + new LinkedMerkleTree(new InMemoryLinkedMerkleTreeStorage()).getWitness( BigInt(0) ) ); @@ -111,7 +110,7 @@ export class StateTransitionProvableBatch extends Struct({ public static fromTransitions( transitions: ProvableStateTransition[], protocolTransitions: ProvableStateTransition[], - merkleWitnesses: RollupMerkleTreeWitness[] + merkleWitnesses: LinkedMerkleTreeWitness[] ): StateTransitionProvableBatch { const array = transitions.slice().concat(protocolTransitions); @@ -138,7 +137,7 @@ export class StateTransitionProvableBatch extends Struct({ private constructor(object: { batch: ProvableStateTransition[]; transitionTypes: ProvableStateTransitionType[]; - merkleWitnesses: RollupMerkleTreeWitness[]; + merkleWitnesses: LinkedMerkleTreeWitness[]; }) { super(object); } diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index 2b55aac4..4e24c6cb 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -3,6 +3,7 @@ import { PlainZkProgram, provableMethod, ZkProgrammable, + RollupMerkleTreeWitness, } from "@proto-kit/common"; import { Bool, Field, Poseidon, Provable, SelfProof, ZkProgram } from "o1js"; import { injectable } from "tsyringe"; @@ -157,7 +158,7 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< const transitions = transitionBatch.batch; const types = transitionBatch.transitionTypes; - const merkleWitness = transitionBatch.merkleWitnesses; + const { merkleWitnesses } = transitionBatch; for ( let index = 0; index < constants.stateTransitionProverBatchSize; @@ -167,7 +168,7 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< state, transitions[index], types[index], - merkleWitness[index], + merkleWitnesses[index], index ); } @@ -183,30 +184,26 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< state: StateTransitionProverExecutionState, transition: ProvableStateTransition, type: ProvableStateTransitionType, - merkleWitness: RollupMerkleTreeWitness, + merkleWitness: LinkedMerkleTreeWitness, index = 0 ) { - const witness = Provable.witness(LinkedMerkleTreeWitness, () => - this.witnessProvider.getWitness(transition.path) - ); - const checkLeafValue = Provable.if( transition.from.isSome, Bool, - witness.leaf.path.equals(transition.path), - witness.leaf.path + merkleWitness.leaf.path.equals(transition.path), + merkleWitness.leaf.path .lessThan(transition.path) - .and(witness.leaf.nextPath.greaterThan(transition.path)) + .and(merkleWitness.leaf.nextPath.greaterThan(transition.path)) ); checkLeafValue.assertTrue(); - const membershipValid = witness.merkleWitness.checkMembershipSimple( + const membershipValid = merkleWitness.merkleWitness.checkMembershipSimple( state.stateRoot, Poseidon.hash([ transition.from.value, transition.path, - witness.leaf.nextPath, + merkleWitness.leaf.nextPath, ]) ); @@ -219,19 +216,27 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< // Now for inserting. This requires changing the leaf before and inserting a new leaf. // The new leaf requires a new witness. - const oldRoot = witness.merkleWitness.calculateRoot( - Poseidon.hash([witness.leaf.value, witness.leaf.path, transition.path]) + const oldRoot = merkleWitness.merkleWitness.calculateRoot( + Poseidon.hash([ + merkleWitness.leaf.value, + merkleWitness.leaf.path, + transition.path, + ]) ); - const newWitness = Provable.witness(LinkedMerkleTreeWitness, () => - this.witnessProvider.getWitness(transition.path) - ); + // const newWitness = Provable.witness(LinkedMerkleTreeWitness, () => + // this.witnessProvider.getWitness(transition.path) + // ); - const newRoot = witness.merkleWitness.calculateRoot(transition.to.value); + const newRoot = merkleWitness.merkleWitness.calculateRoot( + transition.to.value + ); // const newRoot = witness.merkleWitness.calculateRoot(transition.to.value); // LEAVE AS IS for below Linked Merkle Tree - const newRoot = merkleWitness.calculateRoot(transition.to.value); + const newRoot = merkleWitness.merkleWitness.calculateRoot( + transition.to.value + ); state.stateRoot = Provable.if( transition.to.isSome, From 99ec28f787d10c0f771172571e581d977ee29ad5 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Mon, 11 Nov 2024 14:56:57 +0000 Subject: [PATCH 032/107] Update code for witnesses. --- packages/common/src/trees/LinkedMerkleTree.ts | 37 ++++++++++-- .../statetransition/StateTransitionProver.ts | 56 +++++++++++-------- .../production/TransactionTraceService.ts | 14 ++--- .../tasks/StateTransitionTaskParameters.ts | 9 +-- 4 files changed, 75 insertions(+), 41 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index df4b76e8..af80e955 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line max-classes-per-file import { Bool, Field, Poseidon, Struct } from "o1js"; import { TypedClass } from "../types"; @@ -12,10 +13,14 @@ class LinkedLeaf extends Struct({ nextPath: Field, }) {} -export class LinkedStructTemplate extends Struct({ +export class LinkedLeafAndMerkleWitness extends Struct({ leaf: LinkedLeaf, merkleWitness: RollupMerkleTreeWitness, - nextFreeIndex: Field, +}) {} + +class LinkedStructTemplate extends Struct({ + leafPrevious: LinkedLeafAndMerkleWitness, + leafCurrent: LinkedLeafAndMerkleWitness, }) {} export interface AbstractLinkedMerkleWitness extends LinkedStructTemplate {} @@ -57,7 +62,7 @@ export interface AbstractLinkedMerkleTree { * @param path Position of the leaf node. * @returns The witness that belongs to the leaf. */ - getWitness(path: bigint): AbstractLinkedMerkleWitness; + getWitness(path: bigint): LinkedLeafAndMerkleWitness; } export interface AbstractLinkedMerkleTreeClass { @@ -186,6 +191,7 @@ export function createLinkedMerkleTree( public setValue(path: bigint, value: bigint) { let index = this.store.getLeafIndex(path); const prevLeaf = this.store.getPathLessOrEqual(path); + let witness; if (index === undefined) { // The above means the path doesn't already exist and we are inserting, not updating. // This requires us to update the node with the previous path, as well. @@ -202,8 +208,24 @@ export function createLinkedMerkleTree( path: Field(newPrevLeaf.path), nextPath: Field(newPrevLeaf.nextPath), }); + witness = this.getWitness(prevLeaf.path); index = this.store.getMaximumIndex() + 1n; } + // The following sets a default for the previous value + // TODO: How to handle this better. + const witnessPrevious = + witness ?? + new LinkedLeafAndMerkleWitness({ + merkleWitness: new RollupMerkleTreeWitness({ + path: [], + isLeft: [], + }), + leaf: new LinkedLeaf({ + value: Field(0), + path: Field(0), + nextPath: Field(0), + }), + }); const newLeaf = { value: value, path: path, @@ -215,6 +237,10 @@ export function createLinkedMerkleTree( path: Field(newLeaf.path), nextPath: Field(newLeaf.nextPath), }); + return new LinkedMerkleWitness({ + leafPrevious: witnessPrevious, + leafCurrent: this.getWitness(newLeaf.path), + }); } /** @@ -239,7 +265,7 @@ export function createLinkedMerkleTree( * @param path of the leaf node. * @returns The witness that belongs to the leaf. */ - public getWitness(path: bigint): LinkedMerkleWitness { + public getWitness(path: bigint): LinkedLeafAndMerkleWitness { const leaf = this.getPathLessOrEqual(path); const pathArray = []; @@ -262,13 +288,12 @@ export function createLinkedMerkleTree( pathArray.push(sibling); currentIndex /= 2n; } - return new LinkedMerkleWitness({ + return new LinkedLeafAndMerkleWitness({ merkleWitness: new RollupMerkleTreeWitness({ path: pathArray, isLeft: isLefts, }), leaf: leaf, - nextFreeIndex: Field(this.store.getMaximumIndex() + 1n), }); } }; diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index 4e24c6cb..314c9f7e 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -187,25 +187,30 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< merkleWitness: LinkedMerkleTreeWitness, index = 0 ) { + // The following checks if an existing path or non-existing path. + // It won't be an insert (non-existing) if the 'from' is empty. const checkLeafValue = Provable.if( transition.from.isSome, Bool, - merkleWitness.leaf.path.equals(transition.path), - merkleWitness.leaf.path + merkleWitness.leafCurrent.leaf.path.equals(transition.path), + merkleWitness.leafCurrent.leaf.path .lessThan(transition.path) - .and(merkleWitness.leaf.nextPath.greaterThan(transition.path)) + .and( + merkleWitness.leafCurrent.leaf.nextPath.greaterThan(transition.path) + ) ); checkLeafValue.assertTrue(); - const membershipValid = merkleWitness.merkleWitness.checkMembershipSimple( - state.stateRoot, - Poseidon.hash([ - transition.from.value, - transition.path, - merkleWitness.leaf.nextPath, - ]) - ); + const membershipValid = + merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( + state.stateRoot, + Poseidon.hash([ + transition.from.value, + transition.path, + merkleWitness.leafCurrent.leaf.nextPath, + ]) + ); membershipValid.assertTrue( errors.merkleWitnessNotCorrect( @@ -214,27 +219,30 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< ) ); - // Now for inserting. This requires changing the leaf before and inserting a new leaf. - // The new leaf requires a new witness. - const oldRoot = merkleWitness.merkleWitness.calculateRoot( + // Now for inserting. + + const oldRoot = merkleWitness.leafPrevious.merkleWitness.calculateRoot( Poseidon.hash([ - merkleWitness.leaf.value, - merkleWitness.leaf.path, + merkleWitness.leafPrevious.leaf.value, + merkleWitness.leafPrevious.leaf.path, transition.path, ]) ); - // const newWitness = Provable.witness(LinkedMerkleTreeWitness, () => - // this.witnessProvider.getWitness(transition.path) - // ); + const membershipValidInsert = + merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( + oldRoot, + Poseidon.hash([ + merkleWitness.leafCurrent.leaf.value, + merkleWitness.leafCurrent.leaf.path, + merkleWitness.leafCurrent.leaf.nextPath, + ]) + ); - const newRoot = merkleWitness.merkleWitness.calculateRoot( - transition.to.value - ); - // const newRoot = witness.merkleWitness.calculateRoot(transition.to.value); + membershipValidInsert.assertTrue(); // LEAVE AS IS for below Linked Merkle Tree - const newRoot = merkleWitness.merkleWitness.calculateRoot( + const newRoot = merkleWitness.leafCurrent.merkleWitness.calculateRoot( transition.to.value ); diff --git a/packages/sequencer/src/protocol/production/TransactionTraceService.ts b/packages/sequencer/src/protocol/production/TransactionTraceService.ts index f16ea9e3..ad61cce4 100644 --- a/packages/sequencer/src/protocol/production/TransactionTraceService.ts +++ b/packages/sequencer/src/protocol/production/TransactionTraceService.ts @@ -10,9 +10,9 @@ import { StateTransitionProverPublicInput, StateTransitionType, } from "@proto-kit/protocol"; -import { RollupMerkleTree } from "@proto-kit/common"; import { Bool, Field } from "o1js"; import chunk from "lodash/chunk"; +import { LinkedMerkleTree } from "@proto-kit/common/dist/trees/LinkedMerkleTree"; import { distinctByString } from "../../helpers/utils"; import { CachedMerkleTreeStore } from "../../state/merkle/CachedMerkleTreeStore"; @@ -105,8 +105,8 @@ export class TransactionTraceService { await stateServices.merkleStore.preloadKey(0n); fromStateRoot = Field( - stateServices.merkleStore.getNode(0n, RollupMerkleTree.HEIGHT - 1) ?? - RollupMerkleTree.EMPTY_ROOT + stateServices.merkleStore.getNode(0n, LinkedMerkleTree.HEIGHT - 1) ?? + LinkedMerkleTree.EMPTY_ROOT ); stParameters = [ @@ -272,8 +272,8 @@ export class TransactionTraceService { await merkleStore.preloadKeys(keys.map((key) => key.toBigInt())); - const tree = new RollupMerkleTree(merkleStore); - const runtimeTree = new RollupMerkleTree(runtimeSimulationMerkleStore); + const tree = new LinkedMerkleTree(merkleStore); + const runtimeTree = new LinkedMerkleTree(runtimeSimulationMerkleStore); // const runtimeTree = new RollupMerkleTree(merkleStore); const initialRoot = tree.getRoot(); @@ -323,9 +323,9 @@ export class TransactionTraceService { const witness = usedTree.getWitness(provableTransition.path.toBigInt()); if (provableTransition.to.isSome.toBoolean()) { - usedTree.setLeaf( + usedTree.setValue( provableTransition.path.toBigInt(), - provableTransition.to.value + provableTransition.to.value.toBigInt() ); stateRoot = usedTree.getRoot(); diff --git a/packages/sequencer/src/protocol/production/tasks/StateTransitionTaskParameters.ts b/packages/sequencer/src/protocol/production/tasks/StateTransitionTaskParameters.ts index 3408f7b5..2da8d0ee 100644 --- a/packages/sequencer/src/protocol/production/tasks/StateTransitionTaskParameters.ts +++ b/packages/sequencer/src/protocol/production/tasks/StateTransitionTaskParameters.ts @@ -5,6 +5,7 @@ import { } from "@proto-kit/protocol"; import { RollupMerkleTreeWitness } from "@proto-kit/common"; import { Bool } from "o1js"; +import { LinkedMerkleTreeWitness } from "@proto-kit/common/dist/trees/LinkedMerkleTree"; import { TaskSerializer } from "../../../worker/flow/Task"; @@ -14,7 +15,7 @@ export interface StateTransitionProofParameters { transition: ProvableStateTransition; type: ProvableStateTransitionType; }[]; - merkleWitnesses: RollupMerkleTreeWitness[]; + merkleWitnesses: LinkedMerkleTreeWitness[]; } interface StateTransitionParametersJSON { @@ -23,7 +24,7 @@ interface StateTransitionParametersJSON { transition: ReturnType; type: boolean; }[]; - merkleWitnesses: ReturnType[]; + merkleWitnesses: ReturnType[]; } export class StateTransitionParametersSerializer @@ -43,7 +44,7 @@ export class StateTransitionParametersSerializer }), merkleWitnesses: parameters.merkleWitnesses.map((witness) => - RollupMerkleTreeWitness.toJSON(witness) + LinkedMerkleTreeWitness.toJSON(witness) ), } satisfies StateTransitionParametersJSON); } @@ -69,7 +70,7 @@ export class StateTransitionParametersSerializer merkleWitnesses: parsed.merkleWitnesses.map( (witness) => - new RollupMerkleTreeWitness(RollupMerkleTreeWitness.fromJSON(witness)) + new LinkedMerkleTreeWitness(LinkedMerkleTreeWitness.fromJSON(witness)) ), }; } From 09874c3d1e4c82afb0d7295bca7a3f205cf2eea4 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Mon, 11 Nov 2024 14:58:43 +0000 Subject: [PATCH 033/107] Remove unuesed reference --- .../protocol/src/prover/statetransition/StateTransitionProver.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index 314c9f7e..beda35bb 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -3,7 +3,6 @@ import { PlainZkProgram, provableMethod, ZkProgrammable, - RollupMerkleTreeWitness, } from "@proto-kit/common"; import { Bool, Field, Poseidon, Provable, SelfProof, ZkProgram } from "o1js"; import { injectable } from "tsyringe"; From e0d5f6eb243e598f34710a66d35c0722859e5a72 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Mon, 11 Nov 2024 15:34:24 +0000 Subject: [PATCH 034/107] Change order of witnesses. --- packages/common/src/trees/LinkedMerkleTree.ts | 6 ++- .../statetransition/StateTransitionProver.ts | 43 ++++++++++++------- .../StateTransitionWitnessProvider.ts | 0 3 files changed, 31 insertions(+), 18 deletions(-) delete mode 100644 packages/protocol/src/prover/statetransition/StateTransitionWitnessProvider.ts diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index af80e955..93068ab3 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -197,6 +197,7 @@ export function createLinkedMerkleTree( // This requires us to update the node with the previous path, as well. // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const prevLeafIndex = this.store.getLeafIndex(prevLeaf.path) as bigint; + witness = this.getWitness(prevLeaf.path); const newPrevLeaf = { value: prevLeaf.value, path: prevLeaf.path, @@ -208,7 +209,6 @@ export function createLinkedMerkleTree( path: Field(newPrevLeaf.path), nextPath: Field(newPrevLeaf.nextPath), }); - witness = this.getWitness(prevLeaf.path); index = this.store.getMaximumIndex() + 1n; } // The following sets a default for the previous value @@ -226,11 +226,13 @@ export function createLinkedMerkleTree( nextPath: Field(0), }), }); + const newLeaf = { value: value, path: path, nextPath: prevLeaf.nextPath, }; + const witnessNext = this.getWitness(newLeaf.path); this.store.setLeaf(index, newLeaf); this.setLeaf(index, { value: Field(newLeaf.value), @@ -239,7 +241,7 @@ export function createLinkedMerkleTree( }); return new LinkedMerkleWitness({ leafPrevious: witnessPrevious, - leafCurrent: this.getWitness(newLeaf.path), + leafCurrent: witnessNext, }); } diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index beda35bb..04595d3e 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -220,29 +220,40 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< // Now for inserting. - const oldRoot = merkleWitness.leafPrevious.merkleWitness.calculateRoot( - Poseidon.hash([ - merkleWitness.leafPrevious.leaf.value, - merkleWitness.leafPrevious.leaf.path, - transition.path, - ]) - ); - - const membershipValidInsert = - merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( - oldRoot, + merkleWitness.leafPrevious.merkleWitness + .checkMembershipSimple( + state.stateRoot, Poseidon.hash([ - merkleWitness.leafCurrent.leaf.value, - merkleWitness.leafCurrent.leaf.path, - merkleWitness.leafCurrent.leaf.nextPath, + merkleWitness.leafPrevious.leaf.value, + merkleWitness.leafPrevious.leaf.path, + merkleWitness.leafPrevious.leaf.nextPath, + ]) + ) + .assertTrue(); + merkleWitness.leafPrevious.leaf.nextPath.assertGreaterThan(transition.path); + const rootWithLeafChanged = + merkleWitness.leafPrevious.merkleWitness.calculateRoot( + Poseidon.hash([ + merkleWitness.leafPrevious.leaf.value, + merkleWitness.leafPrevious.leaf.path, + transition.path, ]) ); - membershipValidInsert.assertTrue(); + merkleWitness.leafCurrent.merkleWitness + .checkMembershipSimple( + rootWithLeafChanged, + Poseidon.hash([Field(0), Field(0), Field(0)]) + ) + .assertTrue(); // LEAVE AS IS for below Linked Merkle Tree const newRoot = merkleWitness.leafCurrent.merkleWitness.calculateRoot( - transition.to.value + Poseidon.hash([ + transition.to.value, + transition.path, + merkleWitness.leafPrevious.leaf.nextPath, + ]) ); state.stateRoot = Provable.if( diff --git a/packages/protocol/src/prover/statetransition/StateTransitionWitnessProvider.ts b/packages/protocol/src/prover/statetransition/StateTransitionWitnessProvider.ts deleted file mode 100644 index e69de29b..00000000 From 279c5cc7049596fa21d4d7e4e233a332a7ecf019 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Mon, 11 Nov 2024 15:39:12 +0000 Subject: [PATCH 035/107] Add spacing. --- .../src/prover/statetransition/StateTransitionProver.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index 04595d3e..580ff4c0 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -230,7 +230,9 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< ]) ) .assertTrue(); + merkleWitness.leafPrevious.leaf.nextPath.assertGreaterThan(transition.path); + const rootWithLeafChanged = merkleWitness.leafPrevious.merkleWitness.calculateRoot( Poseidon.hash([ From 6c53d7d68848e50f257371f0242015fe2085114e Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Tue, 12 Nov 2024 12:31:58 +0000 Subject: [PATCH 036/107] Fix getWitness --- packages/common/src/trees/LinkedMerkleTree.ts | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 93068ab3..d37f01ab 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -160,7 +160,8 @@ export function createLinkedMerkleTree( } /** - * Sets the value of a leaf node at a given index to a given value. + * Sets the value of a leaf node at a given index to a given value + * and carry the change through to the tree. * @param index Position of the leaf node. * @param leaf New value. */ @@ -268,14 +269,23 @@ export function createLinkedMerkleTree( * @returns The witness that belongs to the leaf. */ public getWitness(path: bigint): LinkedLeafAndMerkleWitness { - const leaf = this.getPathLessOrEqual(path); + let currentIndex = this.store.getLeafIndex(path); + let leaf; + + if (currentIndex === undefined) { + currentIndex = this.store.getMaximumIndex() + 1n; + leaf = new LinkedLeaf({ + value: Field(0), + path: Field(0), + nextPath: Field(0), + }); + } else { + leaf = this.getLeaf(path); + } const pathArray = []; const isLefts = []; - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - let currentIndex = this.store.getLeafIndex( - leaf.path.toBigInt() - ) as bigint; + for ( let level = 0; level < AbstractLinkedRollupMerkleTree.HEIGHT - 1; From 9fedff9a3190f036e6ead666f1dfcd3f26a5b300 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Tue, 12 Nov 2024 12:36:38 +0000 Subject: [PATCH 037/107] Fix getWitness --- packages/common/src/trees/LinkedMerkleTree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index d37f01ab..b1c1ed1f 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -264,7 +264,7 @@ export function createLinkedMerkleTree( /** * Returns the witness (also known as * [Merkle Proof or Merkle Witness](https://computersciencewiki.org/index.php/Merkle_proof)) - * for the leaf at the given index. + * for the leaf at the given path, otherwise returns a witness for the first unused index. * @param path of the leaf node. * @returns The witness that belongs to the leaf. */ From c31d461e6a773277d28a1111b9eebd7cd79eb260 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Tue, 12 Nov 2024 13:10:50 +0000 Subject: [PATCH 038/107] Get code compiling --- packages/common/src/trees/LinkedMerkleTree.ts | 48 +++++++++++-------- .../common/src/trees/LinkedMerkleTreeStore.ts | 4 +- .../src/state/merkle/CachedMerkleTreeStore.ts | 4 +- .../state/merkle/SyncCachedMerkleTreeStore.ts | 4 +- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index b1c1ed1f..c6bbd080 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -62,7 +62,7 @@ export interface AbstractLinkedMerkleTree { * @param path Position of the leaf node. * @returns The witness that belongs to the leaf. */ - getWitness(path: bigint): LinkedLeafAndMerkleWitness; + getWitness(path: bigint): LinkedMerkleTreeWitness; } export interface AbstractLinkedMerkleTreeClass { @@ -192,13 +192,13 @@ export function createLinkedMerkleTree( public setValue(path: bigint, value: bigint) { let index = this.store.getLeafIndex(path); const prevLeaf = this.store.getPathLessOrEqual(path); - let witness; + let witnessPrevious; if (index === undefined) { // The above means the path doesn't already exist and we are inserting, not updating. // This requires us to update the node with the previous path, as well. // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const prevLeafIndex = this.store.getLeafIndex(prevLeaf.path) as bigint; - witness = this.getWitness(prevLeaf.path); + witnessPrevious = this.getWitness(prevLeaf.path).leafCurrent; const newPrevLeaf = { value: prevLeaf.value, path: prevLeaf.path, @@ -211,22 +211,11 @@ export function createLinkedMerkleTree( nextPath: Field(newPrevLeaf.nextPath), }); index = this.store.getMaximumIndex() + 1n; + } else { + witnessPrevious = this.dummy(); } // The following sets a default for the previous value // TODO: How to handle this better. - const witnessPrevious = - witness ?? - new LinkedLeafAndMerkleWitness({ - merkleWitness: new RollupMerkleTreeWitness({ - path: [], - isLeft: [], - }), - leaf: new LinkedLeaf({ - value: Field(0), - path: Field(0), - nextPath: Field(0), - }), - }); const newLeaf = { value: value, @@ -242,7 +231,7 @@ export function createLinkedMerkleTree( }); return new LinkedMerkleWitness({ leafPrevious: witnessPrevious, - leafCurrent: witnessNext, + leafCurrent: witnessNext.leafCurrent, }); } @@ -268,7 +257,7 @@ export function createLinkedMerkleTree( * @param path of the leaf node. * @returns The witness that belongs to the leaf. */ - public getWitness(path: bigint): LinkedLeafAndMerkleWitness { + public getWitness(path: bigint): LinkedMerkleWitness { let currentIndex = this.store.getLeafIndex(path); let leaf; @@ -300,12 +289,29 @@ export function createLinkedMerkleTree( pathArray.push(sibling); currentIndex /= 2n; } + return new LinkedMerkleWitness({ + leafPrevious: this.dummy(), + leafCurrent: new LinkedLeafAndMerkleWitness({ + merkleWitness: new RollupMerkleTreeWitness({ + path: pathArray, + isLeft: isLefts, + }), + leaf: leaf, + }), + }); + } + + private dummy(): LinkedLeafAndMerkleWitness { return new LinkedLeafAndMerkleWitness({ merkleWitness: new RollupMerkleTreeWitness({ - path: pathArray, - isLeft: isLefts, + path: [], + isLeft: [], + }), + leaf: new LinkedLeaf({ + value: Field(0), + path: Field(0), + nextPath: Field(0), }), - leaf: leaf, }); } }; diff --git a/packages/common/src/trees/LinkedMerkleTreeStore.ts b/packages/common/src/trees/LinkedMerkleTreeStore.ts index 8ffe3420..ca295aea 100644 --- a/packages/common/src/trees/LinkedMerkleTreeStore.ts +++ b/packages/common/src/trees/LinkedMerkleTreeStore.ts @@ -1,4 +1,6 @@ -export interface LinkedMerkleTreeStore { +import { MerkleTreeStore } from "./MerkleTreeStore"; + +export interface LinkedMerkleTreeStore extends MerkleTreeStore { setNode: (index: bigint, level: number, value: bigint) => void; getNode: (index: bigint, level: number) => bigint | undefined; diff --git a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts index 27c3ae6e..b7dd2717 100644 --- a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts @@ -1,8 +1,8 @@ import { log, noop, - InMemoryMerkleTreeStorage, RollupMerkleTree, + InMemoryLinkedMerkleTreeStorage, } from "@proto-kit/common"; import { @@ -12,7 +12,7 @@ import { } from "../async/AsyncMerkleTreeStore"; export class CachedMerkleTreeStore - extends InMemoryMerkleTreeStorage + extends InMemoryLinkedMerkleTreeStorage implements AsyncMerkleTreeStore { private writeCache: { diff --git a/packages/sequencer/src/state/merkle/SyncCachedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/SyncCachedMerkleTreeStore.ts index a2e122bc..a028006e 100644 --- a/packages/sequencer/src/state/merkle/SyncCachedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/SyncCachedMerkleTreeStore.ts @@ -1,10 +1,10 @@ import { - InMemoryMerkleTreeStorage, + InMemoryLinkedMerkleTreeStorage, MerkleTreeStore, RollupMerkleTree, } from "@proto-kit/common"; -export class SyncCachedMerkleTreeStore extends InMemoryMerkleTreeStorage { +export class SyncCachedMerkleTreeStore extends InMemoryLinkedMerkleTreeStorage { public constructor(private readonly parent: MerkleTreeStore) { super(); } From 64c38f0b44bb90306ba419deea52f4eae9da365e Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Tue, 12 Nov 2024 13:22:53 +0000 Subject: [PATCH 039/107] Linting --- .../protocol/production/tasks/StateTransitionTaskParameters.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/sequencer/src/protocol/production/tasks/StateTransitionTaskParameters.ts b/packages/sequencer/src/protocol/production/tasks/StateTransitionTaskParameters.ts index 2da8d0ee..5c17bda3 100644 --- a/packages/sequencer/src/protocol/production/tasks/StateTransitionTaskParameters.ts +++ b/packages/sequencer/src/protocol/production/tasks/StateTransitionTaskParameters.ts @@ -3,7 +3,6 @@ import { ProvableStateTransitionType, StateTransitionProverPublicInput, } from "@proto-kit/protocol"; -import { RollupMerkleTreeWitness } from "@proto-kit/common"; import { Bool } from "o1js"; import { LinkedMerkleTreeWitness } from "@proto-kit/common/dist/trees/LinkedMerkleTree"; From 1f97c285ef309114c62bbb830216309ea572a7e8 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Tue, 12 Nov 2024 19:08:30 +0000 Subject: [PATCH 040/107] Code review changes --- .../statetransition/StateTransitionProver.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index 580ff4c0..ad95c16a 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -201,23 +201,6 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< checkLeafValue.assertTrue(); - const membershipValid = - merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( - state.stateRoot, - Poseidon.hash([ - transition.from.value, - transition.path, - merkleWitness.leafCurrent.leaf.nextPath, - ]) - ); - - membershipValid.assertTrue( - errors.merkleWitnessNotCorrect( - index, - type.isNormal().toBoolean() ? "normal" : "protocol" - ) - ); - // Now for inserting. merkleWitness.leafPrevious.merkleWitness From 648a5e3213d411c91e0eefb24016af5e2ca48055 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Tue, 12 Nov 2024 20:01:27 +0000 Subject: [PATCH 041/107] Change STProver to cover update case. --- .../statetransition/StateTransitionProver.ts | 47 ++++++++++++++----- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index ad95c16a..abc48363 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -202,7 +202,6 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< checkLeafValue.assertTrue(); // Now for inserting. - merkleWitness.leafPrevious.merkleWitness .checkMembershipSimple( state.stateRoot, @@ -225,20 +224,46 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< ]) ); - merkleWitness.leafCurrent.merkleWitness - .checkMembershipSimple( + // We now check that whether we have an update or insert. + // If insert then we have the current path would be 0. + const secondWitness = Provable.if( + merkleWitness.leafCurrent.leaf.path.equals(0n), + merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( rootWithLeafChanged, Poseidon.hash([Field(0), Field(0), Field(0)]) + ), + merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( + rootWithLeafChanged, + Poseidon.hash([ + transition.from.value, + transition.path, + merkleWitness.leafCurrent.leaf.nextPath, + ]) ) - .assertTrue(); + ); + + secondWitness.assertTrue(); - // LEAVE AS IS for below Linked Merkle Tree - const newRoot = merkleWitness.leafCurrent.merkleWitness.calculateRoot( - Poseidon.hash([ - transition.to.value, - transition.path, - merkleWitness.leafPrevious.leaf.nextPath, - ]) + // Compute the new final root. + // For an insert we have to hash the new leaf and use the leafPrev's nextPath + // For an update we just use the new value, but keep the leafCurrent.s + // next path the same. + const newRoot = Provable.if( + merkleWitness.leafCurrent.leaf.path.equals(0n), + merkleWitness.leafCurrent.merkleWitness.calculateRoot( + Poseidon.hash([ + transition.to.value, + transition.path, + merkleWitness.leafPrevious.leaf.nextPath, + ]) + ), + merkleWitness.leafCurrent.merkleWitness.calculateRoot( + Poseidon.hash([ + transition.from.value, + transition.path, + merkleWitness.leafCurrent.leaf.nextPath, + ]) + ) ); state.stateRoot = Provable.if( From 7fa56389d1f423f3f92fc7a168ca0c8a97929cc2 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Wed, 13 Nov 2024 15:11:16 +0000 Subject: [PATCH 042/107] Update MerkleTree.test.ts --- packages/common/src/index.ts | 1 + packages/common/src/trees/LinkedMerkleTree.ts | 117 ++++++++++- packages/common/src/trees/RollupMerkleTree.ts | 191 +++++++++--------- packages/common/test/trees/MerkleTree.test.ts | 125 +++++++----- 4 files changed, 284 insertions(+), 150 deletions(-) diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 6addb04f..59886b4a 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -15,6 +15,7 @@ export * from "./events/EventEmitter"; export * from "./trees/MerkleTreeStore"; export * from "./trees/InMemoryMerkleTreeStorage"; export * from "./trees/RollupMerkleTree"; +export * from "./trees/LinkedMerkleTree"; export * from "./trees/InMemoryLinkedMerkleTreeStorage"; export * from "./events/EventEmitterProxy"; export * from "./trees/MockAsyncMerkleStore"; diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index c6bbd080..eb792519 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -1,11 +1,16 @@ // eslint-disable-next-line max-classes-per-file -import { Bool, Field, Poseidon, Struct } from "o1js"; +import { Bool, Field, Poseidon, Provable, Struct } from "o1js"; import { TypedClass } from "../types"; +import { range } from "../utils"; import { LinkedMerkleTreeStore } from "./LinkedMerkleTreeStore"; import { InMemoryLinkedMerkleTreeStorage } from "./InMemoryLinkedMerkleTreeStorage"; -import { RollupMerkleTreeWitness } from "./RollupMerkleTree"; +import { + AbstractMerkleWitness, + maybeSwap, + RollupMerkleTreeWitness, +} from "./RollupMerkleTree"; class LinkedLeaf extends Struct({ value: Field, @@ -13,6 +18,8 @@ class LinkedLeaf extends Struct({ nextPath: Field, }) {} +// We use the RollupMerkleTreeWitness here, although we will actually implement +// the RollupMerkleTreeWitnessV2 defined below when instantiating the class. export class LinkedLeafAndMerkleWitness extends Struct({ leaf: LinkedLeaf, merkleWitness: RollupMerkleTreeWitness, @@ -82,7 +89,106 @@ export function createLinkedMerkleTree( class LinkedMerkleWitness extends LinkedStructTemplate implements AbstractLinkedMerkleWitness {} + /** + * The {@link RollupMerkleWitness} class defines a circuit-compatible base class + * for [Merkle Witness'](https://computersciencewiki.org/index.php/Merkle_proof). + */ + // We define the RollupMerkleWitness again here as we want it to have the same height + // as the tree. If we re-used the Witness from the RollupMerkleTree.ts we wouldn't have + // control, whilst having the overhead of creating the RollupTree since the witness is + // defined from the tree (for the height reason already described). Since we can't + class RollupMerkleWitnessV2 + extends Struct({ + path: Provable.Array(Field, height - 1), + isLeft: Provable.Array(Bool, height - 1), + }) + implements AbstractMerkleWitness + { + public static height = height; + + public height(): number { + return RollupMerkleWitnessV2.height; + } + + /** + * Calculates a root depending on the leaf value. + * @param leaf Value of the leaf node that belongs to this Witness. + * @returns The calculated root. + */ + public calculateRoot(leaf: Field): Field { + let hash = leaf; + const n = this.height(); + for (let index = 1; index < n; ++index) { + const isLeft = this.isLeft[index - 1]; + + const [left, right] = maybeSwap(isLeft, hash, this.path[index - 1]); + hash = Poseidon.hash([left, right]); + } + + return hash; + } + + /** + * Calculates the index of the leaf node that belongs to this Witness. + * @returns Index of the leaf. + */ + public calculateIndex(): Field { + let powerOfTwo = Field(1); + let index = Field(0); + const n = this.height(); + + for (let i = 1; i < n; ++i) { + index = Provable.if(this.isLeft[i - 1], index, index.add(powerOfTwo)); + powerOfTwo = powerOfTwo.mul(2); + } + + return index; + } + + public checkMembership(root: Field, key: Field, value: Field): Bool { + const calculatedRoot = this.calculateRoot(value); + const calculatedKey = this.calculateIndex(); + // We don't have to range-check the key, because if it would be greater + // than leafCount, it would not match the computedKey + key.assertEquals(calculatedKey, "Keys of MerkleWitness does not match"); + return root.equals(calculatedRoot); + } + + public checkMembershipSimple(root: Field, value: Field): Bool { + const calculatedRoot = this.calculateRoot(value); + return root.equals(calculatedRoot); + } + + public checkMembershipGetRoots( + root: Field, + key: Field, + value: Field + ): [Bool, Field, Field] { + const calculatedRoot = this.calculateRoot(value); + const calculatedKey = this.calculateIndex(); + key.assertEquals(calculatedKey, "Keys of MerkleWitness does not match"); + return [root.equals(calculatedRoot), root, calculatedRoot]; + } + + public toShortenedEntries() { + return range(0, 5) + .concat(range(this.height() - 4, this.height())) + .map((index) => + [ + this.path[index].toString(), + this.isLeft[index].toString(), + ].toString() + ); + } + + public static dummy() { + return new RollupMerkleWitnessV2({ + isLeft: Array(this.height - 1).fill(Bool(false)), + path: Array(this.height - 1).fill(Field(0)), + }); + } + } return class AbstractLinkedRollupMerkleTree implements AbstractLinkedMerkleTree { @@ -154,7 +260,6 @@ export function createLinkedMerkleTree( ).toConstant(); } - // private in interface private setNode(level: number, index: bigint, value: Field) { this.store.setNode(index, level, value.toBigInt()); } @@ -196,6 +301,10 @@ export function createLinkedMerkleTree( if (index === undefined) { // The above means the path doesn't already exist and we are inserting, not updating. // This requires us to update the node with the previous path, as well. + + if (this.store.getMaximumIndex() + 1n >= 2 ** height) { + throw new Error("Index greater than maximum leaf number"); + } // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const prevLeafIndex = this.store.getLeafIndex(prevLeaf.path) as bigint; witnessPrevious = this.getWitness(prevLeaf.path).leafCurrent; @@ -292,7 +401,7 @@ export function createLinkedMerkleTree( return new LinkedMerkleWitness({ leafPrevious: this.dummy(), leafCurrent: new LinkedLeafAndMerkleWitness({ - merkleWitness: new RollupMerkleTreeWitness({ + merkleWitness: new RollupMerkleWitnessV2({ path: pathArray, isLeft: isLefts, }), diff --git a/packages/common/src/trees/RollupMerkleTree.ts b/packages/common/src/trees/RollupMerkleTree.ts index 57f46067..b8b245d1 100644 --- a/packages/common/src/trees/RollupMerkleTree.ts +++ b/packages/common/src/trees/RollupMerkleTree.ts @@ -96,101 +96,6 @@ export interface AbstractMerkleTreeClass { get leafCount(): bigint; } -const HEIGHT: number = 40; -/** - * The {@link RollupMerkleWitness} class defines a circuit-compatible base class - * for [Merkle Witness'](https://computersciencewiki.org/index.php/Merkle_proof). - */ -class RollupMerkleWitness - extends Struct({ - path: Provable.Array(Field, HEIGHT - 1), - isLeft: Provable.Array(Bool, HEIGHT - 1), - }) - implements AbstractMerkleWitness -{ - public static height = HEIGHT; - - public height(): number { - return RollupMerkleWitness.height; - } - - /** - * Calculates a root depending on the leaf value. - * @param leaf Value of the leaf node that belongs to this Witness. - * @returns The calculated root. - */ - public calculateRoot(leaf: Field): Field { - let hash = leaf; - const n = this.height(); - - for (let index = 1; index < n; ++index) { - const isLeft = this.isLeft[index - 1]; - // eslint-disable-next-line @typescript-eslint/no-use-before-define - const [left, right] = maybeSwap(isLeft, hash, this.path[index - 1]); - hash = Poseidon.hash([left, right]); - } - - return hash; - } - - /** - * Calculates the index of the leaf node that belongs to this Witness. - * @returns Index of the leaf. - */ - public calculateIndex(): Field { - let powerOfTwo = Field(1); - let index = Field(0); - const n = this.height(); - - for (let i = 1; i < n; ++i) { - index = Provable.if(this.isLeft[i - 1], index, index.add(powerOfTwo)); - powerOfTwo = powerOfTwo.mul(2); - } - - return index; - } - - public checkMembership(root: Field, key: Field, value: Field): Bool { - const calculatedRoot = this.calculateRoot(value); - const calculatedKey = this.calculateIndex(); - // We don't have to range-check the key, because if it would be greater - // than leafCount, it would not match the computedKey - key.assertEquals(calculatedKey, "Keys of MerkleWitness does not match"); - return root.equals(calculatedRoot); - } - - public checkMembershipSimple(root: Field, value: Field): Bool { - const calculatedRoot = this.calculateRoot(value); - return root.equals(calculatedRoot); - } - - public checkMembershipGetRoots( - root: Field, - key: Field, - value: Field - ): [Bool, Field, Field] { - const calculatedRoot = this.calculateRoot(value); - const calculatedKey = this.calculateIndex(); - key.assertEquals(calculatedKey, "Keys of MerkleWitness does not match"); - return [root.equals(calculatedRoot), root, calculatedRoot]; - } - - public toShortenedEntries() { - return range(0, 5) - .concat(range(this.height() - 4, this.height())) - .map((index) => - [this.path[index].toString(), this.isLeft[index].toString()].toString() - ); - } - - public static dummy() { - return new RollupMerkleWitness({ - isLeft: Array(this.height - 1).fill(Bool(false)), - path: Array(this.height - 1).fill(Field(0)), - }); - } -} - /** * A [Merkle Tree](https://en.wikipedia.org/wiki/Merkle_tree) is a binary tree in * which every leaf is the cryptography hash of a piece of data, @@ -211,6 +116,102 @@ class RollupMerkleWitness * It also holds the Witness class under tree.WITNESS */ export function createMerkleTree(height: number): AbstractMerkleTreeClass { + /** + * The {@link RollupMerkleWitness} class defines a circuit-compatible base class + * for [Merkle Witness'](https://computersciencewiki.org/index.php/Merkle_proof). + */ + class RollupMerkleWitness + extends Struct({ + path: Provable.Array(Field, height - 1), + isLeft: Provable.Array(Bool, height - 1), + }) + implements AbstractMerkleWitness + { + public static height = height; + + public height(): number { + return RollupMerkleWitness.height; + } + + /** + * Calculates a root depending on the leaf value. + * @param leaf Value of the leaf node that belongs to this Witness. + * @returns The calculated root. + */ + public calculateRoot(leaf: Field): Field { + let hash = leaf; + const n = this.height(); + + for (let index = 1; index < n; ++index) { + const isLeft = this.isLeft[index - 1]; + // eslint-disable-next-line @typescript-eslint/no-use-before-define + const [left, right] = maybeSwap(isLeft, hash, this.path[index - 1]); + hash = Poseidon.hash([left, right]); + } + + return hash; + } + + /** + * Calculates the index of the leaf node that belongs to this Witness. + * @returns Index of the leaf. + */ + public calculateIndex(): Field { + let powerOfTwo = Field(1); + let index = Field(0); + const n = this.height(); + + for (let i = 1; i < n; ++i) { + index = Provable.if(this.isLeft[i - 1], index, index.add(powerOfTwo)); + powerOfTwo = powerOfTwo.mul(2); + } + + return index; + } + + public checkMembership(root: Field, key: Field, value: Field): Bool { + const calculatedRoot = this.calculateRoot(value); + const calculatedKey = this.calculateIndex(); + // We don't have to range-check the key, because if it would be greater + // than leafCount, it would not match the computedKey + key.assertEquals(calculatedKey, "Keys of MerkleWitness does not match"); + return root.equals(calculatedRoot); + } + + public checkMembershipSimple(root: Field, value: Field): Bool { + const calculatedRoot = this.calculateRoot(value); + return root.equals(calculatedRoot); + } + + public checkMembershipGetRoots( + root: Field, + key: Field, + value: Field + ): [Bool, Field, Field] { + const calculatedRoot = this.calculateRoot(value); + const calculatedKey = this.calculateIndex(); + key.assertEquals(calculatedKey, "Keys of MerkleWitness does not match"); + return [root.equals(calculatedRoot), root, calculatedRoot]; + } + + public toShortenedEntries() { + return range(0, 5) + .concat(range(this.height() - 4, this.height())) + .map((index) => + [ + this.path[index].toString(), + this.isLeft[index].toString(), + ].toString() + ); + } + + public static dummy() { + return new RollupMerkleWitness({ + isLeft: Array(this.height - 1).fill(Bool(false)), + path: Array(this.height - 1).fill(Field(0)), + }); + } + } return class AbstractRollupMerkleTree implements AbstractMerkleTree { public static HEIGHT = height; diff --git a/packages/common/test/trees/MerkleTree.test.ts b/packages/common/test/trees/MerkleTree.test.ts index 2452960e..ce301bb7 100644 --- a/packages/common/test/trees/MerkleTree.test.ts +++ b/packages/common/test/trees/MerkleTree.test.ts @@ -1,106 +1,129 @@ import { beforeEach } from "@jest/globals"; -import { Field } from "o1js"; +import { Field, Poseidon } from "o1js"; -import { createMerkleTree, InMemoryMerkleTreeStorage, log } from "../../src"; +import { + createLinkedMerkleTree, + InMemoryLinkedMerkleTreeStorage, + log, +} from "../../src"; describe.each([4, 16, 256])("cachedMerkleTree - %s", (height) => { - class RollupMerkleTree extends createMerkleTree(height) {} - // eslint-disable-next-line @typescript-eslint/no-unused-vars - class RollupMerkleTreeWitness extends RollupMerkleTree.WITNESS {} + class LinkedMerkleTree extends createLinkedMerkleTree(height) {} - let store: InMemoryMerkleTreeStorage; - let tree: RollupMerkleTree; + let store: InMemoryLinkedMerkleTreeStorage; + let tree: LinkedMerkleTree; beforeEach(() => { log.setLevel("INFO"); - store = new InMemoryMerkleTreeStorage(); - tree = new RollupMerkleTree(store); + store = new InMemoryLinkedMerkleTreeStorage(); + tree = new LinkedMerkleTree(store); }); it("should have the same root when empty", () => { expect.assertions(1); expect(tree.getRoot().toBigInt()).toStrictEqual( - RollupMerkleTree.EMPTY_ROOT + LinkedMerkleTree.EMPTY_ROOT ); }); it("should have a different root when not empty", () => { expect.assertions(1); - tree.setLeaf(1n, Field(1)); + tree.setValue(1n, 1n); expect(tree.getRoot().toBigInt()).not.toStrictEqual( - RollupMerkleTree.EMPTY_ROOT + LinkedMerkleTree.EMPTY_ROOT ); }); - it("should have the same root after adding and removing item", () => { - expect.assertions(1); - - tree.setLeaf(1n, Field(1)); - - const root = tree.getRoot(); - - tree.setLeaf(5n, Field(5)); - tree.setLeaf(5n, Field(0)); - - expect(tree.getRoot().toBigInt()).toStrictEqual(root.toBigInt()); - }); - it("should provide correct witnesses", () => { expect.assertions(1); - tree.setLeaf(1n, Field(1)); - tree.setLeaf(5n, Field(5)); - - const witness = tree.getWitness(5n); - - expect(witness.calculateRoot(Field(5)).toBigInt()).toStrictEqual( - tree.getRoot().toBigInt() - ); + tree.setValue(1n, 1n); + tree.setValue(5n, 5n); + + const witness = tree.getWitness(5n).leafCurrent; + + expect( + witness.merkleWitness + .calculateRoot( + Poseidon.hash([ + witness.leaf.value, + witness.leaf.path, + witness.leaf.nextPath, + ]) + ) + .toBigInt() + ).toStrictEqual(tree.getRoot().toBigInt()); }); it("should have invalid witnesses with wrong values", () => { expect.assertions(1); - tree.setLeaf(1n, Field(1)); - tree.setLeaf(5n, Field(5)); + tree.setValue(1n, 1n); + tree.setValue(5n, 5n); const witness = tree.getWitness(5n); - expect(witness.calculateRoot(Field(6)).toBigInt()).not.toStrictEqual( - tree.getRoot().toBigInt() - ); + expect( + witness.leafCurrent.merkleWitness.calculateRoot(Field(6)).toBigInt() + ).not.toStrictEqual(tree.getRoot().toBigInt()); }); it("should have valid witnesses with changed value on the same leafs", () => { expect.assertions(1); - tree.setLeaf(1n, Field(1)); - tree.setLeaf(5n, Field(5)); + tree.setValue(1n, 1n); + tree.setValue(5n, 5n); - const witness = tree.getWitness(5n); + const witness = tree.getWitness(5n).leafCurrent; - tree.setLeaf(5n, Field(10)); + tree.setValue(5n, 10n); - expect(witness.calculateRoot(Field(10)).toBigInt()).toStrictEqual( - tree.getRoot().toBigInt() - ); + expect( + witness.merkleWitness + .calculateRoot( + Poseidon.hash([Field(10), witness.leaf.path, witness.leaf.nextPath]) + ) + .toBigInt() + ).toStrictEqual(tree.getRoot().toBigInt()); }); - it("should throw for invalid index", () => { - expect.assertions(2); - - const index = 2n ** BigInt(height) + 1n; + it("should return zeroNode ", () => { + expect.assertions(3); + const MAX_FIELD_VALUE: bigint = BigInt(2 ** 53 - 1); + const zeroLeaf = tree.getLeaf(0n); + expect(zeroLeaf.value.toBigInt()).toStrictEqual(0n); + expect(zeroLeaf.path.toBigInt()).toStrictEqual(0n); + expect(zeroLeaf.nextPath.toBigInt()).toStrictEqual(MAX_FIELD_VALUE); + }); + it("throw for invalid index", () => { expect(() => { - tree.setLeaf(index, Field(1)); + for (let i = 0; i < 2n ** BigInt(height) + 1n; i++) { + tree.setValue(BigInt(i), 2n); + } }).toThrow("Index greater than maximum leaf number"); + }); +}); + +// Separate describe here since we only want small trees for this test. +describe("Error check", () => { + class LinkedMerkleTree extends createLinkedMerkleTree(4) {} + let store: InMemoryLinkedMerkleTreeStorage; + let tree: LinkedMerkleTree; + + it("throw for invalid index", () => { + log.setLevel("INFO"); + store = new InMemoryLinkedMerkleTreeStorage(); + tree = new LinkedMerkleTree(store); expect(() => { - tree.getNode(0, index); + for (let i = 0; i < 2n ** BigInt(4) + 1n; i++) { + tree.setValue(BigInt(i), 2n); + } }).toThrow("Index greater than maximum leaf number"); }); }); From 7abadda5085c6f4dced418be7ddbde3f2a3a7065 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Wed, 13 Nov 2024 15:19:28 +0000 Subject: [PATCH 043/107] Update MerkleTree.test.ts --- packages/common/src/trees/LinkedMerkleTree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index eb792519..fe38dee5 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -349,7 +349,7 @@ export function createLinkedMerkleTree( * i.e. {vale: 0, path: 0, nextPath: Field.Max} */ private setLeafInitialisation() { - const MAX_FIELD_VALUE: bigint = BigInt(2 ** 53 - 1); + const MAX_FIELD_VALUE: bigint = BigInt(2 ** height - 1); this.store.setLeaf(0n, { value: 0n, path: 0n, From 97328b031cb1f65729831516e77dcd09549bb0ea Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Wed, 13 Nov 2024 15:37:19 +0000 Subject: [PATCH 044/107] Fix tests --- packages/common/src/trees/LinkedMerkleTree.ts | 3 ++- packages/common/test/trees/MerkleTree.test.ts | 10 +--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index fe38dee5..66f5e220 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -349,7 +349,8 @@ export function createLinkedMerkleTree( * i.e. {vale: 0, path: 0, nextPath: Field.Max} */ private setLeafInitialisation() { - const MAX_FIELD_VALUE: bigint = BigInt(2 ** height - 1); + // This is the maximum value of the hash + const MAX_FIELD_VALUE: bigint = BigInt(2 ** 53 - 1); this.store.setLeaf(0n, { value: 0n, path: 0n, diff --git a/packages/common/test/trees/MerkleTree.test.ts b/packages/common/test/trees/MerkleTree.test.ts index ce301bb7..5d7186e1 100644 --- a/packages/common/test/trees/MerkleTree.test.ts +++ b/packages/common/test/trees/MerkleTree.test.ts @@ -7,7 +7,7 @@ import { log, } from "../../src"; -describe.each([4, 16, 256])("cachedMerkleTree - %s", (height) => { +describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { class LinkedMerkleTree extends createLinkedMerkleTree(height) {} let store: InMemoryLinkedMerkleTreeStorage; @@ -99,14 +99,6 @@ describe.each([4, 16, 256])("cachedMerkleTree - %s", (height) => { expect(zeroLeaf.path.toBigInt()).toStrictEqual(0n); expect(zeroLeaf.nextPath.toBigInt()).toStrictEqual(MAX_FIELD_VALUE); }); - - it("throw for invalid index", () => { - expect(() => { - for (let i = 0; i < 2n ** BigInt(height) + 1n; i++) { - tree.setValue(BigInt(i), 2n); - } - }).toThrow("Index greater than maximum leaf number"); - }); }); // Separate describe here since we only want small trees for this test. From b050d0e1f799b18d9a6aa4343572aec8e90e0696 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Wed, 13 Nov 2024 20:43:33 +0000 Subject: [PATCH 045/107] Rename setValue to setLeaf in MerkleTree and setLeaf to setMerkleLeaf --- packages/common/src/trees/LinkedMerkleTree.ts | 17 +++++++------- packages/common/test/trees/MerkleTree.test.ts | 22 ++++++++++--------- .../production/TransactionTraceService.ts | 2 +- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 66f5e220..64f594db 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -53,7 +53,7 @@ export interface AbstractLinkedMerkleTree { * @param path of the leaf node. * @param value New value. */ - setValue(path: bigint, value: bigint): void; + setLeaf(path: bigint, value: bigint): void; /** * Returns a leaf which lives at a given path. @@ -270,7 +270,7 @@ export function createLinkedMerkleTree( * @param index Position of the leaf node. * @param leaf New value. */ - private setLeaf(index: bigint, leaf: LinkedLeaf) { + private setMerkleLeaf(index: bigint, leaf: LinkedLeaf) { this.setNode( 0, index, @@ -294,14 +294,13 @@ export function createLinkedMerkleTree( * @param path Position of the leaf node. * @param value New value. */ - public setValue(path: bigint, value: bigint) { + public setLeaf(path: bigint, value: bigint) { let index = this.store.getLeafIndex(path); const prevLeaf = this.store.getPathLessOrEqual(path); let witnessPrevious; if (index === undefined) { - // The above means the path doesn't already exist and we are inserting, not updating. + // The above means the path doesn't already exist, and we are inserting, not updating. // This requires us to update the node with the previous path, as well. - if (this.store.getMaximumIndex() + 1n >= 2 ** height) { throw new Error("Index greater than maximum leaf number"); } @@ -314,7 +313,7 @@ export function createLinkedMerkleTree( nextPath: path, }; this.store.setLeaf(prevLeafIndex, newPrevLeaf); - this.setLeaf(prevLeafIndex, { + this.setMerkleLeaf(prevLeafIndex, { value: Field(newPrevLeaf.value), path: Field(newPrevLeaf.path), nextPath: Field(newPrevLeaf.nextPath), @@ -333,7 +332,7 @@ export function createLinkedMerkleTree( }; const witnessNext = this.getWitness(newLeaf.path); this.store.setLeaf(index, newLeaf); - this.setLeaf(index, { + this.setMerkleLeaf(index, { value: Field(newLeaf.value), path: Field(newLeaf.path), nextPath: Field(newLeaf.nextPath), @@ -356,8 +355,10 @@ export function createLinkedMerkleTree( path: 0n, nextPath: MAX_FIELD_VALUE, }); + // We do this to get the Field-ified version of the leaf. const initialLeaf = this.getLeaf(0n); - this.setLeaf(0n, initialLeaf); + // We now set the leafs in the merkle tree. + this.setMerkleLeaf(0n, initialLeaf); } /** diff --git a/packages/common/test/trees/MerkleTree.test.ts b/packages/common/test/trees/MerkleTree.test.ts index 5d7186e1..b0a3ea0e 100644 --- a/packages/common/test/trees/MerkleTree.test.ts +++ b/packages/common/test/trees/MerkleTree.test.ts @@ -31,7 +31,7 @@ describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { it("should have a different root when not empty", () => { expect.assertions(1); - tree.setValue(1n, 1n); + tree.setLeaf(1n, 1n); expect(tree.getRoot().toBigInt()).not.toStrictEqual( LinkedMerkleTree.EMPTY_ROOT @@ -41,8 +41,8 @@ describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { it("should provide correct witnesses", () => { expect.assertions(1); - tree.setValue(1n, 1n); - tree.setValue(5n, 5n); + tree.setLeaf(1n, 1n); + tree.setLeaf(5n, 5n); const witness = tree.getWitness(5n).leafCurrent; @@ -62,8 +62,8 @@ describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { it("should have invalid witnesses with wrong values", () => { expect.assertions(1); - tree.setValue(1n, 1n); - tree.setValue(5n, 5n); + tree.setLeaf(1n, 1n); + tree.setLeaf(5n, 5n); const witness = tree.getWitness(5n); @@ -75,12 +75,12 @@ describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { it("should have valid witnesses with changed value on the same leafs", () => { expect.assertions(1); - tree.setValue(1n, 1n); - tree.setValue(5n, 5n); + tree.setLeaf(1n, 1n); + tree.setLeaf(5n, 5n); const witness = tree.getWitness(5n).leafCurrent; - tree.setValue(5n, 10n); + tree.setLeaf(5n, 10n); expect( witness.merkleWitness @@ -91,7 +91,7 @@ describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { ).toStrictEqual(tree.getRoot().toBigInt()); }); - it("should return zeroNode ", () => { + it("should return zeroNode", () => { expect.assertions(3); const MAX_FIELD_VALUE: bigint = BigInt(2 ** 53 - 1); const zeroLeaf = tree.getLeaf(0n); @@ -99,6 +99,8 @@ describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { expect(zeroLeaf.path.toBigInt()).toStrictEqual(0n); expect(zeroLeaf.nextPath.toBigInt()).toStrictEqual(MAX_FIELD_VALUE); }); + + it("should return zeroNode", () => {}); }); // Separate describe here since we only want small trees for this test. @@ -114,7 +116,7 @@ describe("Error check", () => { tree = new LinkedMerkleTree(store); expect(() => { for (let i = 0; i < 2n ** BigInt(4) + 1n; i++) { - tree.setValue(BigInt(i), 2n); + tree.setLeaf(BigInt(i), 2n); } }).toThrow("Index greater than maximum leaf number"); }); diff --git a/packages/sequencer/src/protocol/production/TransactionTraceService.ts b/packages/sequencer/src/protocol/production/TransactionTraceService.ts index ad61cce4..1a475644 100644 --- a/packages/sequencer/src/protocol/production/TransactionTraceService.ts +++ b/packages/sequencer/src/protocol/production/TransactionTraceService.ts @@ -323,7 +323,7 @@ export class TransactionTraceService { const witness = usedTree.getWitness(provableTransition.path.toBigInt()); if (provableTransition.to.isSome.toBoolean()) { - usedTree.setValue( + usedTree.setLeaf( provableTransition.path.toBigInt(), provableTransition.to.value.toBigInt() ); From 7fbc30da8a13f4eeeafbb5a21522cb20b5a38e5d Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 14 Nov 2024 08:48:50 +0000 Subject: [PATCH 046/107] ImplementAsyncLinkedMerkleTreeStore --- .../state/async/AsyncLinkedMerkleTreeStore.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts index 8de8feb5..9c50a0a5 100644 --- a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts @@ -1,11 +1,9 @@ -// import { LinkedMerkleTreeStore } from "@proto-kit/common"; - import { MerkleTreeNodeQuery } from "./AsyncMerkleTreeStore"; -export interface LinkedMerkleTreeNode extends MerkleTreeNodeQuery { +export interface LinkedMerkleTreeLeaf { value: bigint; - path: number; - nextPath: number; + path: bigint; + nextPath: bigint; } export interface AsyncLinkedMerkleTreeStore { @@ -13,9 +11,17 @@ export interface AsyncLinkedMerkleTreeStore { commit: () => Promise; - writeNodes: (nodes: LinkedMerkleTreeNode[]) => void; + writeNodes: (nodes: MerkleTreeNodeQuery[]) => void; + + writeLeaves: (leaves: LinkedMerkleTreeLeaf[]) => void; getNodesAsync: ( nodes: MerkleTreeNodeQuery[] ) => Promise<(bigint | undefined)[]>; + + getLeavesAsync: ( + leaves: LinkedMerkleTreeLeaf[] + ) => Promise< + ({ value: bigint; path: bigint; nextPath: bigint } | undefined)[] + >; } From 9c36f5d8a2544f76530b210c0cf2471941fefd00 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 14 Nov 2024 09:11:23 +0000 Subject: [PATCH 047/107] Update index to make type available. --- packages/common/src/index.ts | 1 + .../sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 59886b4a..a370acf3 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -13,6 +13,7 @@ export * from "./log"; export * from "./events/EventEmittingComponent"; export * from "./events/EventEmitter"; export * from "./trees/MerkleTreeStore"; +export * from "./trees/LinkedMerkleTreeStore"; export * from "./trees/InMemoryMerkleTreeStorage"; export * from "./trees/RollupMerkleTree"; export * from "./trees/LinkedMerkleTree"; diff --git a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts index 9c50a0a5..c103e848 100644 --- a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts @@ -1,6 +1,6 @@ import { MerkleTreeNodeQuery } from "./AsyncMerkleTreeStore"; -export interface LinkedMerkleTreeLeaf { +export interface LinkedMerkleTreeLeafQuery { value: bigint; path: bigint; nextPath: bigint; @@ -13,14 +13,14 @@ export interface AsyncLinkedMerkleTreeStore { writeNodes: (nodes: MerkleTreeNodeQuery[]) => void; - writeLeaves: (leaves: LinkedMerkleTreeLeaf[]) => void; + writeLeaves: (leaves: LinkedMerkleTreeLeafQuery[]) => void; getNodesAsync: ( nodes: MerkleTreeNodeQuery[] ) => Promise<(bigint | undefined)[]>; getLeavesAsync: ( - leaves: LinkedMerkleTreeLeaf[] + leaves: LinkedMerkleTreeLeafQuery[] ) => Promise< ({ value: bigint; path: bigint; nextPath: bigint } | undefined)[] >; From f2b97462259fae7cd32594fd2eb9e0cb01217d1d Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 14 Nov 2024 14:55:37 +0000 Subject: [PATCH 048/107] Started implementing CachedMerkleTreeStore --- .../state/async/AsyncLinkedMerkleTreeStore.ts | 20 +- .../src/state/merkle/CachedMerkleTreeStore.ts | 218 +++++++++++++----- 2 files changed, 175 insertions(+), 63 deletions(-) diff --git a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts index c103e848..ba6cdea0 100644 --- a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts @@ -1,27 +1,29 @@ -import { MerkleTreeNodeQuery } from "./AsyncMerkleTreeStore"; +import { LinkedLeaf } from "@proto-kit/common"; -export interface LinkedMerkleTreeLeafQuery { - value: bigint; - path: bigint; - nextPath: bigint; -} +import { MerkleTreeNode, MerkleTreeNodeQuery } from "./AsyncMerkleTreeStore"; export interface AsyncLinkedMerkleTreeStore { openTransaction: () => Promise; commit: () => Promise; - writeNodes: (nodes: MerkleTreeNodeQuery[]) => void; + writeNodes: (nodes: MerkleTreeNode[]) => void; - writeLeaves: (leaves: LinkedMerkleTreeLeafQuery[]) => void; + writeLeaves: (leaves: { path: bigint; value: bigint }[]) => void; getNodesAsync: ( nodes: MerkleTreeNodeQuery[] ) => Promise<(bigint | undefined)[]>; getLeavesAsync: ( - leaves: LinkedMerkleTreeLeafQuery[] + paths: bigint[] ) => Promise< ({ value: bigint; path: bigint; nextPath: bigint } | undefined)[] >; + + getLeafIndex: (path: bigint) => bigint | undefined; + + getPathLessOrEqual: (path: bigint) => LinkedLeaf; + + getMaximumIndex: () => bigint; } diff --git a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts index b7dd2717..69f6dc08 100644 --- a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts @@ -3,23 +3,30 @@ import { noop, RollupMerkleTree, InMemoryLinkedMerkleTreeStorage, + LinkedLeaf, } from "@proto-kit/common"; +import { Field, Poseidon } from "o1js"; import { - AsyncMerkleTreeStore, MerkleTreeNode, MerkleTreeNodeQuery, } from "../async/AsyncMerkleTreeStore"; +import { AsyncLinkedMerkleTreeStore } from "../async/AsyncLinkedMerkleTreeStore"; export class CachedMerkleTreeStore extends InMemoryLinkedMerkleTreeStorage - implements AsyncMerkleTreeStore + implements AsyncLinkedMerkleTreeStore { private writeCache: { - [key: number]: { - [key: string]: bigint; + nodes: { + [key: number]: { + [key: string]: bigint; + }; }; - } = {}; + leaves: { + [key: string]: LinkedLeaf; + }; + } = { nodes: {}, leaves: {} }; public async openTransaction(): Promise { noop(); @@ -29,31 +36,171 @@ export class CachedMerkleTreeStore noop(); } - public constructor(private readonly parent: AsyncMerkleTreeStore) { + public constructor(private readonly parent: AsyncLinkedMerkleTreeStore) { super(); } + // This gets the nodes from the in memory store (which looks also to be the cache). public getNode(key: bigint, level: number): bigint | undefined { return super.getNode(key, level); } + // This gets the nodes from the in memory store. + // If the node is not in the in-memory store it goes to the parent (i.e. + // what's put in the constructor). + public async getNodesAsync( + nodes: MerkleTreeNodeQuery[] + ): Promise<(bigint | undefined)[]> { + const results = Array(nodes.length).fill(undefined); + + const toFetch: MerkleTreeNodeQuery[] = []; + + nodes.forEach((node, index) => { + const localResult = this.getNode(node.key, node.level); + if (localResult !== undefined) { + results[index] = localResult; + } else { + toFetch.push(node); + } + }); + + // Reverse here, so that we can use pop() later + const fetchResult = (await this.parent.getNodesAsync(toFetch)).reverse(); + + results.forEach((result, index) => { + if (result === undefined) { + results[index] = fetchResult.pop(); + } + }); + + return results; + } + + // This sets the nodes in the cache and in the in-memory tree. public setNode(key: bigint, level: number, value: bigint) { super.setNode(key, level, value); - (this.writeCache[level] ??= {})[key.toString()] = value; + (this.writeCache.nodes[level] ??= {})[key.toString()] = value; + } + + // This is basically setNode (cache and in-memory) for a list of nodes. + // Looks only to be used in the mergeIntoParent + public writeNodes(nodes: MerkleTreeNode[]) { + nodes.forEach(({ key, level, value }) => { + this.setNode(key, level, value); + }); + } + + // This gets the nodes from the in memory store (which looks also to be the cache). + public getLeaf(path: bigint) { + const index = super.getLeafIndex(path); + if (index !== undefined) { + return super.getLeaf(index); + } + return undefined; + } + + // This gets the leaves and the nodes from the in memory store. + // If the leaf is not in the in-memory store it goes to the parent (i.e. + // what's put in the constructor). + public async getLeavesAsync(paths: bigint[]) { + const results = Array(paths.length).fill(undefined); + + const toFetch: bigint[] = []; + + paths.forEach((path, index) => { + const localResult = this.getLeaf(path); + if (localResult !== undefined) { + results[index] = localResult; + } else { + toFetch.push(path); + } + }); + + // Reverse here, so that we can use pop() later + const fetchResult = (await this.parent.getLeavesAsync(toFetch)).reverse(); + + results.forEach((result, index) => { + if (result === undefined) { + results[index] = fetchResult.pop(); + } + }); + + return results; + } + + // This sets the leaves in the cache and in the in-memory tree. + // It also updates the node. + // Note that setNode doesn't carry the change up the tree (i.e. for siblings etc) + public setLeaf(index: bigint, leaf: LinkedLeaf) { + super.setLeaf(index, leaf); + this.writeCache.leaves[index.toString()] = leaf; + + this.setNode( + index, + 0, + Poseidon.hash([ + Field(leaf.value), + Field(leaf.path), + Field(leaf.nextPath), + ]).toBigInt() + ); + } + + // This is basically setLeaf (cache and in-memory) for a list of leaves. + // This requires updating the nodes as well. + public writeLeaves(leaves: { path: bigint; value: bigint }[]) { + leaves.forEach(({ value, path }) => { + const index = super.getLeafIndex(path); + // The following checks if we have an insert or update. + if (index !== undefined) { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const linkedLeaf = super.getLeaf(index) as LinkedLeaf; + this.setLeaf(index, { + value: value, + path: path, + nextPath: linkedLeaf.nextPath, + }); + } else { + // This is an insert. Need to change two leaves. + const nearestLinkedLeaf = super.getPathLessOrEqual(path); + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const nearestLinkedleafIndex = super.getLeafIndex( + nearestLinkedLeaf.path + ) as bigint; + const lowestUnoccupiedIndex = super.getMaximumIndex() + 1n; + this.setLeaf(nearestLinkedleafIndex, { + value: nearestLinkedLeaf.value, + path: nearestLinkedLeaf.path, + nextPath: path, + }); + this.setLeaf(lowestUnoccupiedIndex, { + value: value, + path: path, + nextPath: nearestLinkedLeaf.path, + }); + } + }); } + // This gets the nodes from the cache. + // Only used in mergeIntoParent public getWrittenNodes(): { [key: number]: { [key: string]: bigint; }; } { - return this.writeCache; + return this.writeCache.nodes; } - public resetWrittenNodes() { - this.writeCache = {}; + // This resets the cache (not the in memory tree). + public resetWrittenTree() { + this.writeCache = { nodes: {}, leaves: {} }; } + // Used only in the preloadKeys + // Basically, gets all of the relevant nodes (and siblings) in the Merkle tree + // at the various levels required to produce a witness for the given index (at level 0). + // But only gets those that aren't in the cache. private collectNodesToFetch(index: bigint) { // Algo from RollupMerkleTree.getWitness() const { leafCount, HEIGHT } = RollupMerkleTree; @@ -91,6 +238,8 @@ export class CachedMerkleTreeStore return nodesToRetrieve; } + // Takes a list of keys and for each key collects the relevant nodes from the + // parent tree and sets the node in the cached tree (and in-memory tree). public async preloadKeys(keys: bigint[]) { const nodesToRetrieve = keys.flatMap((key) => this.collectNodesToFetch(key) @@ -105,13 +254,16 @@ export class CachedMerkleTreeStore }); } + // This is preloadKeys with just one index/key. public async preloadKey(index: bigint): Promise { await this.preloadKeys([index]); } + // This merges the cache into the parent tree and resets the cache, but not the + // in-memory merkle tree. public async mergeIntoParent(): Promise { // In case no state got set we can skip this step - if (Object.keys(this.writeCache).length === 0) { + if (Object.keys(this.writeCache.leaves).length === 0) { return; } @@ -134,48 +286,6 @@ export class CachedMerkleTreeStore this.parent.writeNodes(writes); await this.parent.commit(); - this.resetWrittenNodes(); - } - - public async setNodeAsync( - key: bigint, - level: number, - value: bigint - ): Promise { - this.setNode(key, level, value); - } - - public async getNodesAsync( - nodes: MerkleTreeNodeQuery[] - ): Promise<(bigint | undefined)[]> { - const results = Array(nodes.length).fill(undefined); - - const toFetch: MerkleTreeNodeQuery[] = []; - - nodes.forEach((node, index) => { - const localResult = this.getNode(node.key, node.level); - if (localResult !== undefined) { - results[index] = localResult; - } else { - toFetch.push(node); - } - }); - - // Reverse here, so that we can use pop() later - const fetchResult = (await this.parent.getNodesAsync(toFetch)).reverse(); - - results.forEach((result, index) => { - if (result === undefined) { - results[index] = fetchResult.pop(); - } - }); - - return results; - } - - public writeNodes(nodes: MerkleTreeNode[]): void { - nodes.forEach(({ key, level, value }) => { - this.setNode(key, level, value); - }); + this.resetWrittenTree(); } } From 5aa8e8579bbdc91544361ea59a1cd9efc57ee509 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 14 Nov 2024 15:40:01 +0000 Subject: [PATCH 049/107] Fix preload keys method. --- .../state/async/AsyncLinkedMerkleTreeStore.ts | 6 +---- .../src/state/merkle/CachedMerkleTreeStore.ts | 26 +++++++++++++------ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts index ba6cdea0..bcc1e982 100644 --- a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts @@ -15,11 +15,7 @@ export interface AsyncLinkedMerkleTreeStore { nodes: MerkleTreeNodeQuery[] ) => Promise<(bigint | undefined)[]>; - getLeavesAsync: ( - paths: bigint[] - ) => Promise< - ({ value: bigint; path: bigint; nextPath: bigint } | undefined)[] - >; + getLeavesAsync: (paths: bigint[]) => Promise<(LinkedLeaf | undefined)[]>; getLeafIndex: (path: bigint) => bigint | undefined; diff --git a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts index 69f6dc08..8612fabe 100644 --- a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts @@ -239,15 +239,25 @@ export class CachedMerkleTreeStore } // Takes a list of keys and for each key collects the relevant nodes from the - // parent tree and sets the node in the cached tree (and in-memory tree). - public async preloadKeys(keys: bigint[]) { - const nodesToRetrieve = keys.flatMap((key) => - this.collectNodesToFetch(key) - ); - - const results = await this.parent.getNodesAsync(nodesToRetrieve); + // parent tree and sets the leaf and node in the cached tree (and in-memory tree). + public async preloadKeys(paths: bigint[]) { + const nodesToRetrieve = ( + await Promise.all( + paths.flatMap(async (path) => { + const pathIndex = super.getLeafIndex(path) ?? 0n; + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const resultLeaf = ( + await this.parent.getLeavesAsync([path]) + )[0] as LinkedLeaf; + super.setLeaf(pathIndex, resultLeaf); + this.writeCache.leaves[pathIndex.toString()] = resultLeaf; + return this.collectNodesToFetch(pathIndex); + }) + ) + ).flat(1); + const resultsNode = await this.parent.getNodesAsync(nodesToRetrieve); nodesToRetrieve.forEach(({ key, level }, index) => { - const value = results[index]; + const value = resultsNode[index]; if (value !== undefined) { this.setNode(key, level, value); } From 13f074d12828c30d1cd8a8311f04ecd5bc341c04 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:15:08 +0000 Subject: [PATCH 050/107] Change WriteLeaves method. Commented out old. --- .../state/async/AsyncLinkedMerkleTreeStore.ts | 2 +- .../src/state/merkle/CachedMerkleTreeStore.ts | 91 ++++++++++++------- 2 files changed, 58 insertions(+), 35 deletions(-) diff --git a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts index bcc1e982..c988c54d 100644 --- a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts @@ -9,7 +9,7 @@ export interface AsyncLinkedMerkleTreeStore { writeNodes: (nodes: MerkleTreeNode[]) => void; - writeLeaves: (leaves: { path: bigint; value: bigint }[]) => void; + writeLeaves: (leaves: LinkedLeaf[]) => void; getNodesAsync: ( nodes: MerkleTreeNodeQuery[] diff --git a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts index 8612fabe..61065de9 100644 --- a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts @@ -129,7 +129,7 @@ export class CachedMerkleTreeStore } // This sets the leaves in the cache and in the in-memory tree. - // It also updates the node. + // It also updates the relevant node at the base level. // Note that setNode doesn't carry the change up the tree (i.e. for siblings etc) public setLeaf(index: bigint, leaf: LinkedLeaf) { super.setLeaf(index, leaf); @@ -146,41 +146,54 @@ export class CachedMerkleTreeStore ); } - // This is basically setLeaf (cache and in-memory) for a list of leaves. - // This requires updating the nodes as well. - public writeLeaves(leaves: { path: bigint; value: bigint }[]) { - leaves.forEach(({ value, path }) => { - const index = super.getLeafIndex(path); - // The following checks if we have an insert or update. - if (index !== undefined) { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const linkedLeaf = super.getLeaf(index) as LinkedLeaf; - this.setLeaf(index, { - value: value, - path: path, - nextPath: linkedLeaf.nextPath, - }); - } else { - // This is an insert. Need to change two leaves. - const nearestLinkedLeaf = super.getPathLessOrEqual(path); - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const nearestLinkedleafIndex = super.getLeafIndex( - nearestLinkedLeaf.path - ) as bigint; - const lowestUnoccupiedIndex = super.getMaximumIndex() + 1n; - this.setLeaf(nearestLinkedleafIndex, { - value: nearestLinkedLeaf.value, - path: nearestLinkedLeaf.path, - nextPath: path, - }); - this.setLeaf(lowestUnoccupiedIndex, { - value: value, - path: path, - nextPath: nearestLinkedLeaf.path, - }); - } + // This is just used in the mergeIntoParent. + // It doesn't need any fancy logic and just updates the leaves. + // I don't think we need to coordinate this with the nodes + // or do any calculations. Just a straight copy and paste. + public writeLeaves(leaves: LinkedLeaf[]) { + leaves.forEach((leaf) => { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const index = super.getLeafIndex(leaf.path) as bigint; + this.writeCache.leaves[index.toString()] = leaf; + super.setLeaf(index, leaf); }); } + // This is setLeaf (cache and in-memory) for a list of leaves. + // It checks if it's an insert or update and then updates the relevant + // leaf. + // public writeLeaves(leaves: { path: bigint; value: bigint }[]) { + // leaves.forEach(({ value, path }) => { + // const index = super.getLeafIndex(path); + // // The following checks if we have an insert or update. + // if (index !== undefined) { + // // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + // const linkedLeaf = super.getLeaf(index) as LinkedLeaf; + // this.setLeaf(index, { + // value: value, + // path: path, + // nextPath: linkedLeaf.nextPath, + // }); + // } else { + // // This is an insert. Need to change two leaves. + // const nearestLinkedLeaf = super.getPathLessOrEqual(path); + // // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + // const nearestLinkedleafIndex = super.getLeafIndex( + // nearestLinkedLeaf.path + // ) as bigint; + // const lowestUnoccupiedIndex = super.getMaximumIndex() + 1n; + // this.setLeaf(nearestLinkedleafIndex, { + // value: nearestLinkedLeaf.value, + // path: nearestLinkedLeaf.path, + // nextPath: path, + // }); + // this.setLeaf(lowestUnoccupiedIndex, { + // value: value, + // path: path, + // nextPath: nearestLinkedLeaf.path, + // }); + // } + // }); + // } // This gets the nodes from the cache. // Only used in mergeIntoParent @@ -192,6 +205,14 @@ export class CachedMerkleTreeStore return this.writeCache.nodes; } + // This gets the leaves from the cache. + // Only used in mergeIntoParent + public getWrittenLeaves(): { + [key: string]: LinkedLeaf; + } { + return this.writeCache.leaves; + } + // This resets the cache (not the in memory tree). public resetWrittenTree() { this.writeCache = { nodes: {}, leaves: {} }; @@ -279,7 +300,9 @@ export class CachedMerkleTreeStore await this.parent.openTransaction(); const nodes = this.getWrittenNodes(); + const leaves = this.getWrittenLeaves(); + this.writeLeaves(Object.values(leaves)); const writes = Object.keys(nodes).flatMap((levelString) => { const level = Number(levelString); return Object.entries(nodes[level]).map( From f82b91ddf7c60b5c088400639e83db024804e219 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:19:53 +0000 Subject: [PATCH 051/107] Commented out unneeded code. --- .../src/state/merkle/CachedMerkleTreeStore.ts | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts index 61065de9..e24c1b21 100644 --- a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts @@ -5,7 +5,6 @@ import { InMemoryLinkedMerkleTreeStorage, LinkedLeaf, } from "@proto-kit/common"; -import { Field, Poseidon } from "o1js"; import { MerkleTreeNode, @@ -128,24 +127,6 @@ export class CachedMerkleTreeStore return results; } - // This sets the leaves in the cache and in the in-memory tree. - // It also updates the relevant node at the base level. - // Note that setNode doesn't carry the change up the tree (i.e. for siblings etc) - public setLeaf(index: bigint, leaf: LinkedLeaf) { - super.setLeaf(index, leaf); - this.writeCache.leaves[index.toString()] = leaf; - - this.setNode( - index, - 0, - Poseidon.hash([ - Field(leaf.value), - Field(leaf.path), - Field(leaf.nextPath), - ]).toBigInt() - ); - } - // This is just used in the mergeIntoParent. // It doesn't need any fancy logic and just updates the leaves. // I don't think we need to coordinate this with the nodes @@ -158,6 +139,24 @@ export class CachedMerkleTreeStore super.setLeaf(index, leaf); }); } + + // // This sets the leaves in the cache and in the in-memory tree. + // // It also updates the relevant node at the base level. + // // Note that setNode doesn't carry the change up the tree (i.e. for siblings etc) + // public setLeaf(index: bigint, leaf: LinkedLeaf) { + // super.setLeaf(index, leaf); + // this.writeCache.leaves[index.toString()] = leaf; + // + // this.setNode( + // index, + // 0, + // Poseidon.hash([ + // Field(leaf.value), + // Field(leaf.path), + // Field(leaf.nextPath), + // ]).toBigInt() + // ); + // } // This is setLeaf (cache and in-memory) for a list of leaves. // It checks if it's an insert or update and then updates the relevant // leaf. @@ -221,7 +220,7 @@ export class CachedMerkleTreeStore // Used only in the preloadKeys // Basically, gets all of the relevant nodes (and siblings) in the Merkle tree // at the various levels required to produce a witness for the given index (at level 0). - // But only gets those that aren't in the cache. + // But only gets those that aren't already in the cache. private collectNodesToFetch(index: bigint) { // Algo from RollupMerkleTree.getWitness() const { leafCount, HEIGHT } = RollupMerkleTree; From 94c34c79967e015104f221dead50bf884239b616 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 14 Nov 2024 19:15:00 +0000 Subject: [PATCH 052/107] Move changes to new file and restore original CachedMerkleTreeStore --- .../merkle/CachedLinkedMerkleTreeStore.ts | 323 ++++++++++++++++++ .../src/state/merkle/CachedMerkleTreeStore.ts | 268 ++++----------- 2 files changed, 386 insertions(+), 205 deletions(-) create mode 100644 packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts diff --git a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts new file mode 100644 index 00000000..e24c1b21 --- /dev/null +++ b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts @@ -0,0 +1,323 @@ +import { + log, + noop, + RollupMerkleTree, + InMemoryLinkedMerkleTreeStorage, + LinkedLeaf, +} from "@proto-kit/common"; + +import { + MerkleTreeNode, + MerkleTreeNodeQuery, +} from "../async/AsyncMerkleTreeStore"; +import { AsyncLinkedMerkleTreeStore } from "../async/AsyncLinkedMerkleTreeStore"; + +export class CachedMerkleTreeStore + extends InMemoryLinkedMerkleTreeStorage + implements AsyncLinkedMerkleTreeStore +{ + private writeCache: { + nodes: { + [key: number]: { + [key: string]: bigint; + }; + }; + leaves: { + [key: string]: LinkedLeaf; + }; + } = { nodes: {}, leaves: {} }; + + public async openTransaction(): Promise { + noop(); + } + + public async commit(): Promise { + noop(); + } + + public constructor(private readonly parent: AsyncLinkedMerkleTreeStore) { + super(); + } + + // This gets the nodes from the in memory store (which looks also to be the cache). + public getNode(key: bigint, level: number): bigint | undefined { + return super.getNode(key, level); + } + + // This gets the nodes from the in memory store. + // If the node is not in the in-memory store it goes to the parent (i.e. + // what's put in the constructor). + public async getNodesAsync( + nodes: MerkleTreeNodeQuery[] + ): Promise<(bigint | undefined)[]> { + const results = Array(nodes.length).fill(undefined); + + const toFetch: MerkleTreeNodeQuery[] = []; + + nodes.forEach((node, index) => { + const localResult = this.getNode(node.key, node.level); + if (localResult !== undefined) { + results[index] = localResult; + } else { + toFetch.push(node); + } + }); + + // Reverse here, so that we can use pop() later + const fetchResult = (await this.parent.getNodesAsync(toFetch)).reverse(); + + results.forEach((result, index) => { + if (result === undefined) { + results[index] = fetchResult.pop(); + } + }); + + return results; + } + + // This sets the nodes in the cache and in the in-memory tree. + public setNode(key: bigint, level: number, value: bigint) { + super.setNode(key, level, value); + (this.writeCache.nodes[level] ??= {})[key.toString()] = value; + } + + // This is basically setNode (cache and in-memory) for a list of nodes. + // Looks only to be used in the mergeIntoParent + public writeNodes(nodes: MerkleTreeNode[]) { + nodes.forEach(({ key, level, value }) => { + this.setNode(key, level, value); + }); + } + + // This gets the nodes from the in memory store (which looks also to be the cache). + public getLeaf(path: bigint) { + const index = super.getLeafIndex(path); + if (index !== undefined) { + return super.getLeaf(index); + } + return undefined; + } + + // This gets the leaves and the nodes from the in memory store. + // If the leaf is not in the in-memory store it goes to the parent (i.e. + // what's put in the constructor). + public async getLeavesAsync(paths: bigint[]) { + const results = Array(paths.length).fill(undefined); + + const toFetch: bigint[] = []; + + paths.forEach((path, index) => { + const localResult = this.getLeaf(path); + if (localResult !== undefined) { + results[index] = localResult; + } else { + toFetch.push(path); + } + }); + + // Reverse here, so that we can use pop() later + const fetchResult = (await this.parent.getLeavesAsync(toFetch)).reverse(); + + results.forEach((result, index) => { + if (result === undefined) { + results[index] = fetchResult.pop(); + } + }); + + return results; + } + + // This is just used in the mergeIntoParent. + // It doesn't need any fancy logic and just updates the leaves. + // I don't think we need to coordinate this with the nodes + // or do any calculations. Just a straight copy and paste. + public writeLeaves(leaves: LinkedLeaf[]) { + leaves.forEach((leaf) => { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const index = super.getLeafIndex(leaf.path) as bigint; + this.writeCache.leaves[index.toString()] = leaf; + super.setLeaf(index, leaf); + }); + } + + // // This sets the leaves in the cache and in the in-memory tree. + // // It also updates the relevant node at the base level. + // // Note that setNode doesn't carry the change up the tree (i.e. for siblings etc) + // public setLeaf(index: bigint, leaf: LinkedLeaf) { + // super.setLeaf(index, leaf); + // this.writeCache.leaves[index.toString()] = leaf; + // + // this.setNode( + // index, + // 0, + // Poseidon.hash([ + // Field(leaf.value), + // Field(leaf.path), + // Field(leaf.nextPath), + // ]).toBigInt() + // ); + // } + // This is setLeaf (cache and in-memory) for a list of leaves. + // It checks if it's an insert or update and then updates the relevant + // leaf. + // public writeLeaves(leaves: { path: bigint; value: bigint }[]) { + // leaves.forEach(({ value, path }) => { + // const index = super.getLeafIndex(path); + // // The following checks if we have an insert or update. + // if (index !== undefined) { + // // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + // const linkedLeaf = super.getLeaf(index) as LinkedLeaf; + // this.setLeaf(index, { + // value: value, + // path: path, + // nextPath: linkedLeaf.nextPath, + // }); + // } else { + // // This is an insert. Need to change two leaves. + // const nearestLinkedLeaf = super.getPathLessOrEqual(path); + // // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + // const nearestLinkedleafIndex = super.getLeafIndex( + // nearestLinkedLeaf.path + // ) as bigint; + // const lowestUnoccupiedIndex = super.getMaximumIndex() + 1n; + // this.setLeaf(nearestLinkedleafIndex, { + // value: nearestLinkedLeaf.value, + // path: nearestLinkedLeaf.path, + // nextPath: path, + // }); + // this.setLeaf(lowestUnoccupiedIndex, { + // value: value, + // path: path, + // nextPath: nearestLinkedLeaf.path, + // }); + // } + // }); + // } + + // This gets the nodes from the cache. + // Only used in mergeIntoParent + public getWrittenNodes(): { + [key: number]: { + [key: string]: bigint; + }; + } { + return this.writeCache.nodes; + } + + // This gets the leaves from the cache. + // Only used in mergeIntoParent + public getWrittenLeaves(): { + [key: string]: LinkedLeaf; + } { + return this.writeCache.leaves; + } + + // This resets the cache (not the in memory tree). + public resetWrittenTree() { + this.writeCache = { nodes: {}, leaves: {} }; + } + + // Used only in the preloadKeys + // Basically, gets all of the relevant nodes (and siblings) in the Merkle tree + // at the various levels required to produce a witness for the given index (at level 0). + // But only gets those that aren't already in the cache. + private collectNodesToFetch(index: bigint) { + // Algo from RollupMerkleTree.getWitness() + const { leafCount, HEIGHT } = RollupMerkleTree; + + let currentIndex = index >= leafCount ? index % leafCount : index; + + const nodesToRetrieve: MerkleTreeNodeQuery[] = []; + + for (let level = 0; level < HEIGHT; level++) { + const key = currentIndex; + + const isLeft = key % 2n === 0n; + const siblingKey = isLeft ? key + 1n : key - 1n; + + // Only preload node if it is not already preloaded. + // We also don't want to overwrite because changes will get lost (tracing) + if (this.getNode(key, level) === undefined) { + nodesToRetrieve.push({ + key, + level, + }); + if (level === 0) { + log.trace(`Queued preloading of ${key} @ ${level}`); + } + } + + if (this.getNode(siblingKey, level) === undefined) { + nodesToRetrieve.push({ + key: siblingKey, + level, + }); + } + currentIndex /= 2n; + } + return nodesToRetrieve; + } + + // Takes a list of keys and for each key collects the relevant nodes from the + // parent tree and sets the leaf and node in the cached tree (and in-memory tree). + public async preloadKeys(paths: bigint[]) { + const nodesToRetrieve = ( + await Promise.all( + paths.flatMap(async (path) => { + const pathIndex = super.getLeafIndex(path) ?? 0n; + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const resultLeaf = ( + await this.parent.getLeavesAsync([path]) + )[0] as LinkedLeaf; + super.setLeaf(pathIndex, resultLeaf); + this.writeCache.leaves[pathIndex.toString()] = resultLeaf; + return this.collectNodesToFetch(pathIndex); + }) + ) + ).flat(1); + const resultsNode = await this.parent.getNodesAsync(nodesToRetrieve); + nodesToRetrieve.forEach(({ key, level }, index) => { + const value = resultsNode[index]; + if (value !== undefined) { + this.setNode(key, level, value); + } + }); + } + + // This is preloadKeys with just one index/key. + public async preloadKey(index: bigint): Promise { + await this.preloadKeys([index]); + } + + // This merges the cache into the parent tree and resets the cache, but not the + // in-memory merkle tree. + public async mergeIntoParent(): Promise { + // In case no state got set we can skip this step + if (Object.keys(this.writeCache.leaves).length === 0) { + return; + } + + await this.parent.openTransaction(); + const nodes = this.getWrittenNodes(); + const leaves = this.getWrittenLeaves(); + + this.writeLeaves(Object.values(leaves)); + const writes = Object.keys(nodes).flatMap((levelString) => { + const level = Number(levelString); + return Object.entries(nodes[level]).map( + ([key, value]) => { + return { + key: BigInt(key), + level, + value, + }; + } + ); + }); + + this.parent.writeNodes(writes); + + await this.parent.commit(); + this.resetWrittenTree(); + } +} diff --git a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts index e24c1b21..27c3ae6e 100644 --- a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts @@ -1,31 +1,25 @@ import { log, noop, + InMemoryMerkleTreeStorage, RollupMerkleTree, - InMemoryLinkedMerkleTreeStorage, - LinkedLeaf, } from "@proto-kit/common"; import { + AsyncMerkleTreeStore, MerkleTreeNode, MerkleTreeNodeQuery, } from "../async/AsyncMerkleTreeStore"; -import { AsyncLinkedMerkleTreeStore } from "../async/AsyncLinkedMerkleTreeStore"; export class CachedMerkleTreeStore - extends InMemoryLinkedMerkleTreeStorage - implements AsyncLinkedMerkleTreeStore + extends InMemoryMerkleTreeStorage + implements AsyncMerkleTreeStore { private writeCache: { - nodes: { - [key: number]: { - [key: string]: bigint; - }; - }; - leaves: { - [key: string]: LinkedLeaf; + [key: number]: { + [key: string]: bigint; }; - } = { nodes: {}, leaves: {} }; + } = {}; public async openTransaction(): Promise { noop(); @@ -35,192 +29,31 @@ export class CachedMerkleTreeStore noop(); } - public constructor(private readonly parent: AsyncLinkedMerkleTreeStore) { + public constructor(private readonly parent: AsyncMerkleTreeStore) { super(); } - // This gets the nodes from the in memory store (which looks also to be the cache). public getNode(key: bigint, level: number): bigint | undefined { return super.getNode(key, level); } - // This gets the nodes from the in memory store. - // If the node is not in the in-memory store it goes to the parent (i.e. - // what's put in the constructor). - public async getNodesAsync( - nodes: MerkleTreeNodeQuery[] - ): Promise<(bigint | undefined)[]> { - const results = Array(nodes.length).fill(undefined); - - const toFetch: MerkleTreeNodeQuery[] = []; - - nodes.forEach((node, index) => { - const localResult = this.getNode(node.key, node.level); - if (localResult !== undefined) { - results[index] = localResult; - } else { - toFetch.push(node); - } - }); - - // Reverse here, so that we can use pop() later - const fetchResult = (await this.parent.getNodesAsync(toFetch)).reverse(); - - results.forEach((result, index) => { - if (result === undefined) { - results[index] = fetchResult.pop(); - } - }); - - return results; - } - - // This sets the nodes in the cache and in the in-memory tree. public setNode(key: bigint, level: number, value: bigint) { super.setNode(key, level, value); - (this.writeCache.nodes[level] ??= {})[key.toString()] = value; - } - - // This is basically setNode (cache and in-memory) for a list of nodes. - // Looks only to be used in the mergeIntoParent - public writeNodes(nodes: MerkleTreeNode[]) { - nodes.forEach(({ key, level, value }) => { - this.setNode(key, level, value); - }); - } - - // This gets the nodes from the in memory store (which looks also to be the cache). - public getLeaf(path: bigint) { - const index = super.getLeafIndex(path); - if (index !== undefined) { - return super.getLeaf(index); - } - return undefined; - } - - // This gets the leaves and the nodes from the in memory store. - // If the leaf is not in the in-memory store it goes to the parent (i.e. - // what's put in the constructor). - public async getLeavesAsync(paths: bigint[]) { - const results = Array(paths.length).fill(undefined); - - const toFetch: bigint[] = []; - - paths.forEach((path, index) => { - const localResult = this.getLeaf(path); - if (localResult !== undefined) { - results[index] = localResult; - } else { - toFetch.push(path); - } - }); - - // Reverse here, so that we can use pop() later - const fetchResult = (await this.parent.getLeavesAsync(toFetch)).reverse(); - - results.forEach((result, index) => { - if (result === undefined) { - results[index] = fetchResult.pop(); - } - }); - - return results; - } - - // This is just used in the mergeIntoParent. - // It doesn't need any fancy logic and just updates the leaves. - // I don't think we need to coordinate this with the nodes - // or do any calculations. Just a straight copy and paste. - public writeLeaves(leaves: LinkedLeaf[]) { - leaves.forEach((leaf) => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const index = super.getLeafIndex(leaf.path) as bigint; - this.writeCache.leaves[index.toString()] = leaf; - super.setLeaf(index, leaf); - }); + (this.writeCache[level] ??= {})[key.toString()] = value; } - // // This sets the leaves in the cache and in the in-memory tree. - // // It also updates the relevant node at the base level. - // // Note that setNode doesn't carry the change up the tree (i.e. for siblings etc) - // public setLeaf(index: bigint, leaf: LinkedLeaf) { - // super.setLeaf(index, leaf); - // this.writeCache.leaves[index.toString()] = leaf; - // - // this.setNode( - // index, - // 0, - // Poseidon.hash([ - // Field(leaf.value), - // Field(leaf.path), - // Field(leaf.nextPath), - // ]).toBigInt() - // ); - // } - // This is setLeaf (cache and in-memory) for a list of leaves. - // It checks if it's an insert or update and then updates the relevant - // leaf. - // public writeLeaves(leaves: { path: bigint; value: bigint }[]) { - // leaves.forEach(({ value, path }) => { - // const index = super.getLeafIndex(path); - // // The following checks if we have an insert or update. - // if (index !== undefined) { - // // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - // const linkedLeaf = super.getLeaf(index) as LinkedLeaf; - // this.setLeaf(index, { - // value: value, - // path: path, - // nextPath: linkedLeaf.nextPath, - // }); - // } else { - // // This is an insert. Need to change two leaves. - // const nearestLinkedLeaf = super.getPathLessOrEqual(path); - // // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - // const nearestLinkedleafIndex = super.getLeafIndex( - // nearestLinkedLeaf.path - // ) as bigint; - // const lowestUnoccupiedIndex = super.getMaximumIndex() + 1n; - // this.setLeaf(nearestLinkedleafIndex, { - // value: nearestLinkedLeaf.value, - // path: nearestLinkedLeaf.path, - // nextPath: path, - // }); - // this.setLeaf(lowestUnoccupiedIndex, { - // value: value, - // path: path, - // nextPath: nearestLinkedLeaf.path, - // }); - // } - // }); - // } - - // This gets the nodes from the cache. - // Only used in mergeIntoParent public getWrittenNodes(): { [key: number]: { [key: string]: bigint; }; } { - return this.writeCache.nodes; + return this.writeCache; } - // This gets the leaves from the cache. - // Only used in mergeIntoParent - public getWrittenLeaves(): { - [key: string]: LinkedLeaf; - } { - return this.writeCache.leaves; + public resetWrittenNodes() { + this.writeCache = {}; } - // This resets the cache (not the in memory tree). - public resetWrittenTree() { - this.writeCache = { nodes: {}, leaves: {} }; - } - - // Used only in the preloadKeys - // Basically, gets all of the relevant nodes (and siblings) in the Merkle tree - // at the various levels required to produce a witness for the given index (at level 0). - // But only gets those that aren't already in the cache. private collectNodesToFetch(index: bigint) { // Algo from RollupMerkleTree.getWitness() const { leafCount, HEIGHT } = RollupMerkleTree; @@ -258,50 +91,33 @@ export class CachedMerkleTreeStore return nodesToRetrieve; } - // Takes a list of keys and for each key collects the relevant nodes from the - // parent tree and sets the leaf and node in the cached tree (and in-memory tree). - public async preloadKeys(paths: bigint[]) { - const nodesToRetrieve = ( - await Promise.all( - paths.flatMap(async (path) => { - const pathIndex = super.getLeafIndex(path) ?? 0n; - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const resultLeaf = ( - await this.parent.getLeavesAsync([path]) - )[0] as LinkedLeaf; - super.setLeaf(pathIndex, resultLeaf); - this.writeCache.leaves[pathIndex.toString()] = resultLeaf; - return this.collectNodesToFetch(pathIndex); - }) - ) - ).flat(1); - const resultsNode = await this.parent.getNodesAsync(nodesToRetrieve); + public async preloadKeys(keys: bigint[]) { + const nodesToRetrieve = keys.flatMap((key) => + this.collectNodesToFetch(key) + ); + + const results = await this.parent.getNodesAsync(nodesToRetrieve); nodesToRetrieve.forEach(({ key, level }, index) => { - const value = resultsNode[index]; + const value = results[index]; if (value !== undefined) { this.setNode(key, level, value); } }); } - // This is preloadKeys with just one index/key. public async preloadKey(index: bigint): Promise { await this.preloadKeys([index]); } - // This merges the cache into the parent tree and resets the cache, but not the - // in-memory merkle tree. public async mergeIntoParent(): Promise { // In case no state got set we can skip this step - if (Object.keys(this.writeCache.leaves).length === 0) { + if (Object.keys(this.writeCache).length === 0) { return; } await this.parent.openTransaction(); const nodes = this.getWrittenNodes(); - const leaves = this.getWrittenLeaves(); - this.writeLeaves(Object.values(leaves)); const writes = Object.keys(nodes).flatMap((levelString) => { const level = Number(levelString); return Object.entries(nodes[level]).map( @@ -318,6 +134,48 @@ export class CachedMerkleTreeStore this.parent.writeNodes(writes); await this.parent.commit(); - this.resetWrittenTree(); + this.resetWrittenNodes(); + } + + public async setNodeAsync( + key: bigint, + level: number, + value: bigint + ): Promise { + this.setNode(key, level, value); + } + + public async getNodesAsync( + nodes: MerkleTreeNodeQuery[] + ): Promise<(bigint | undefined)[]> { + const results = Array(nodes.length).fill(undefined); + + const toFetch: MerkleTreeNodeQuery[] = []; + + nodes.forEach((node, index) => { + const localResult = this.getNode(node.key, node.level); + if (localResult !== undefined) { + results[index] = localResult; + } else { + toFetch.push(node); + } + }); + + // Reverse here, so that we can use pop() later + const fetchResult = (await this.parent.getNodesAsync(toFetch)).reverse(); + + results.forEach((result, index) => { + if (result === undefined) { + results[index] = fetchResult.pop(); + } + }); + + return results; + } + + public writeNodes(nodes: MerkleTreeNode[]): void { + nodes.forEach(({ key, level, value }) => { + this.setNode(key, level, value); + }); } } From 02c9c69172022dacf1fa88f5854d536f1508c6c0 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 14 Nov 2024 19:22:45 +0000 Subject: [PATCH 053/107] Update types --- .../src/protocol/production/BatchProducerModule.ts | 11 ++++++----- .../protocol/production/TransactionTraceService.ts | 8 ++++---- .../src/state/merkle/CachedLinkedMerkleTreeStore.ts | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/sequencer/src/protocol/production/BatchProducerModule.ts b/packages/sequencer/src/protocol/production/BatchProducerModule.ts index 967d34ac..3e5a4052 100644 --- a/packages/sequencer/src/protocol/production/BatchProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BatchProducerModule.ts @@ -17,11 +17,12 @@ import { import { BatchStorage } from "../../storage/repositories/BatchStorage"; import { SettleableBatch } from "../../storage/model/Batch"; import { CachedStateService } from "../../state/state/CachedStateService"; -import { CachedMerkleTreeStore } from "../../state/merkle/CachedMerkleTreeStore"; import { AsyncStateService } from "../../state/async/AsyncStateService"; import { AsyncMerkleTreeStore } from "../../state/async/AsyncMerkleTreeStore"; import { BlockResult, BlockWithResult } from "../../storage/model/Block"; import { VerificationKeyService } from "../runtime/RuntimeVerificationKeyService"; +import { CachedLinkedMerkleTreeStore } from "../../state/merkle/CachedLinkedMerkleTreeStore"; +import { AsyncLinkedMerkleTreeStore } from "../../state/async/AsyncLinkedMerkleTreeStore"; import { BlockProverParameters } from "./tasks/BlockProvingTask"; import { StateTransitionProofParameters } from "./tasks/StateTransitionTaskParameters"; @@ -53,7 +54,7 @@ export interface BlockWithPreviousResult { interface BatchMetadata { batch: SettleableBatch; stateService: CachedStateService; - merkleStore: CachedMerkleTreeStore; + merkleStore: CachedLinkedMerkleTreeStore; } const errors = { @@ -77,7 +78,7 @@ export class BatchProducerModule extends SequencerModule { @inject("AsyncStateService") private readonly asyncStateService: AsyncStateService, @inject("AsyncMerkleStore") - private readonly merkleStore: AsyncMerkleTreeStore, + private readonly merkleStore: AsyncLinkedMerkleTreeStore, @inject("BatchStorage") private readonly batchStorage: BatchStorage, @inject("BlockTreeStore") private readonly blockTreeStore: AsyncMerkleTreeStore, @@ -212,7 +213,7 @@ export class BatchProducerModule extends SequencerModule { ): Promise<{ proof: Proof; stateService: CachedStateService; - merkleStore: CachedMerkleTreeStore; + merkleStore: CachedLinkedMerkleTreeStore; fromNetworkState: NetworkState; toNetworkState: NetworkState; }> { @@ -222,7 +223,7 @@ export class BatchProducerModule extends SequencerModule { const stateServices = { stateService: new CachedStateService(this.asyncStateService), - merkleStore: new CachedMerkleTreeStore(this.merkleStore), + merkleStore: new CachedLinkedMerkleTreeStore(this.merkleStore), }; const blockTraces: BlockTrace[] = []; diff --git a/packages/sequencer/src/protocol/production/TransactionTraceService.ts b/packages/sequencer/src/protocol/production/TransactionTraceService.ts index 1a475644..1a717fa3 100644 --- a/packages/sequencer/src/protocol/production/TransactionTraceService.ts +++ b/packages/sequencer/src/protocol/production/TransactionTraceService.ts @@ -15,7 +15,6 @@ import chunk from "lodash/chunk"; import { LinkedMerkleTree } from "@proto-kit/common/dist/trees/LinkedMerkleTree"; import { distinctByString } from "../../helpers/utils"; -import { CachedMerkleTreeStore } from "../../state/merkle/CachedMerkleTreeStore"; import { CachedStateService } from "../../state/state/CachedStateService"; import { SyncCachedMerkleTreeStore } from "../../state/merkle/SyncCachedMerkleTreeStore"; import type { @@ -24,6 +23,7 @@ import type { } from "../../storage/model/Block"; import { AsyncMerkleTreeStore } from "../../state/async/AsyncMerkleTreeStore"; import { VerificationKeyService } from "../runtime/RuntimeVerificationKeyService"; +import { CachedLinkedMerkleTreeStore } from "../../state/merkle/CachedLinkedMerkleTreeStore"; import type { TransactionTrace, BlockTrace } from "./BatchProducerModule"; import { StateTransitionProofParameters } from "./tasks/StateTransitionTaskParameters"; @@ -79,7 +79,7 @@ export class TransactionTraceService { traces: TransactionTrace[], stateServices: { stateService: CachedStateService; - merkleStore: CachedMerkleTreeStore; + merkleStore: CachedLinkedMerkleTreeStore; }, blockHashTreeStore: AsyncMerkleTreeStore, beforeBlockStateRoot: Field, @@ -172,7 +172,7 @@ export class TransactionTraceService { executionResult: TransactionExecutionResult, stateServices: { stateService: CachedStateService; - merkleStore: CachedMerkleTreeStore; + merkleStore: CachedLinkedMerkleTreeStore; }, verificationKeyService: VerificationKeyService, networkState: NetworkState, @@ -256,7 +256,7 @@ export class TransactionTraceService { } private async createMerkleTrace( - merkleStore: CachedMerkleTreeStore, + merkleStore: CachedLinkedMerkleTreeStore, stateTransitions: UntypedStateTransition[], protocolTransitions: UntypedStateTransition[], runtimeSuccess: boolean diff --git a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts index e24c1b21..0900ada8 100644 --- a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts @@ -12,7 +12,7 @@ import { } from "../async/AsyncMerkleTreeStore"; import { AsyncLinkedMerkleTreeStore } from "../async/AsyncLinkedMerkleTreeStore"; -export class CachedMerkleTreeStore +export class CachedLinkedMerkleTreeStore extends InMemoryLinkedMerkleTreeStorage implements AsyncLinkedMerkleTreeStore { From bce82553afd7b2a2b1dfced49ac27e3a5bd2022b Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 15 Nov 2024 09:49:53 +0000 Subject: [PATCH 054/107] Add comments to STProver --- .../statetransition/StateTransitionProver.ts | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index abc48363..1378a8b3 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -4,7 +4,7 @@ import { provableMethod, ZkProgrammable, } from "@proto-kit/common"; -import { Bool, Field, Poseidon, Provable, SelfProof, ZkProgram } from "o1js"; +import { Field, Poseidon, Provable, SelfProof, ZkProgram } from "o1js"; import { injectable } from "tsyringe"; import { LinkedMerkleTreeWitness } from "@proto-kit/common/dist/trees/LinkedMerkleTree"; @@ -186,22 +186,28 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< merkleWitness: LinkedMerkleTreeWitness, index = 0 ) { - // The following checks if an existing path or non-existing path. + // The following checks if this is an existing path or non-existing path. // It won't be an insert (non-existing) if the 'from' is empty. - const checkLeafValue = Provable.if( + // If it's an update then the leafCurrent will be the current leaf, + // rather than the zero leaf if it's an insert. + // If it's an insert then we need to check the leafPrevious is + // a valid leaf, i.e. path is less than transition.path and nextPath + // greater than transition.path. + // Even if we're just reading (rather than writing) then we expect + // the path for the current leaf to be populated. + Provable.if( transition.from.isSome, - Bool, merkleWitness.leafCurrent.leaf.path.equals(transition.path), - merkleWitness.leafCurrent.leaf.path + merkleWitness.leafPrevious.leaf.path .lessThan(transition.path) .and( merkleWitness.leafCurrent.leaf.nextPath.greaterThan(transition.path) ) - ); - - checkLeafValue.assertTrue(); + ).assertTrue(); - // Now for inserting. + // We need to check the sequencer had fetched the correct previousLeaf, + // specifically that the previousLeaf is what is verified. + // We check the stateRoot matches. This doesn't matter merkleWitness.leafPrevious.merkleWitness .checkMembershipSimple( state.stateRoot, @@ -213,8 +219,9 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< ) .assertTrue(); - merkleWitness.leafPrevious.leaf.nextPath.assertGreaterThan(transition.path); - + // Need to calculate the new state root after the previous leaf is changed. + // This is only relevant if it's an insert. If an update, we will just use + // the existing state root. const rootWithLeafChanged = merkleWitness.leafPrevious.merkleWitness.calculateRoot( Poseidon.hash([ @@ -224,25 +231,27 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< ]) ); - // We now check that whether we have an update or insert. - // If insert then we have the current path would be 0. - const secondWitness = Provable.if( + // Need to check the second leaf is correct, i.e. leafCurrent. + // is what the sequencer claims it is. + // Again, we check whether we have an update or insert as the value + // depends on this. If insert then we have the current path would be 0. + // We use the existing state root if it's only an update as the prev leaf + // wouldn't have changed and therefore the state root should be the same. + Provable.if( merkleWitness.leafCurrent.leaf.path.equals(0n), merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( rootWithLeafChanged, Poseidon.hash([Field(0), Field(0), Field(0)]) ), merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( - rootWithLeafChanged, + state.stateRoot, Poseidon.hash([ transition.from.value, transition.path, merkleWitness.leafCurrent.leaf.nextPath, ]) ) - ); - - secondWitness.assertTrue(); + ).assertTrue(); // Compute the new final root. // For an insert we have to hash the new leaf and use the leafPrev's nextPath @@ -266,6 +275,8 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< ) ); + // This is checking if we have a read or write. + // If a read the state root should stay the same. state.stateRoot = Provable.if( transition.to.isSome, newRoot, From 708ef4be560cba4b8e43f0f6d0ecfe5417969da5 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:04:23 +0000 Subject: [PATCH 055/107] Move LinekdMerkleTree test to own file. --- .../test/trees/LinkedMerkleTree.test.ts | 123 ++++++++++++++++++ packages/common/test/trees/MerkleTree.test.ts | 123 ++++++++---------- 2 files changed, 176 insertions(+), 70 deletions(-) create mode 100644 packages/common/test/trees/LinkedMerkleTree.test.ts diff --git a/packages/common/test/trees/LinkedMerkleTree.test.ts b/packages/common/test/trees/LinkedMerkleTree.test.ts new file mode 100644 index 00000000..b0a3ea0e --- /dev/null +++ b/packages/common/test/trees/LinkedMerkleTree.test.ts @@ -0,0 +1,123 @@ +import { beforeEach } from "@jest/globals"; +import { Field, Poseidon } from "o1js"; + +import { + createLinkedMerkleTree, + InMemoryLinkedMerkleTreeStorage, + log, +} from "../../src"; + +describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { + class LinkedMerkleTree extends createLinkedMerkleTree(height) {} + + let store: InMemoryLinkedMerkleTreeStorage; + let tree: LinkedMerkleTree; + + beforeEach(() => { + log.setLevel("INFO"); + + store = new InMemoryLinkedMerkleTreeStorage(); + tree = new LinkedMerkleTree(store); + }); + + it("should have the same root when empty", () => { + expect.assertions(1); + + expect(tree.getRoot().toBigInt()).toStrictEqual( + LinkedMerkleTree.EMPTY_ROOT + ); + }); + + it("should have a different root when not empty", () => { + expect.assertions(1); + + tree.setLeaf(1n, 1n); + + expect(tree.getRoot().toBigInt()).not.toStrictEqual( + LinkedMerkleTree.EMPTY_ROOT + ); + }); + + it("should provide correct witnesses", () => { + expect.assertions(1); + + tree.setLeaf(1n, 1n); + tree.setLeaf(5n, 5n); + + const witness = tree.getWitness(5n).leafCurrent; + + expect( + witness.merkleWitness + .calculateRoot( + Poseidon.hash([ + witness.leaf.value, + witness.leaf.path, + witness.leaf.nextPath, + ]) + ) + .toBigInt() + ).toStrictEqual(tree.getRoot().toBigInt()); + }); + + it("should have invalid witnesses with wrong values", () => { + expect.assertions(1); + + tree.setLeaf(1n, 1n); + tree.setLeaf(5n, 5n); + + const witness = tree.getWitness(5n); + + expect( + witness.leafCurrent.merkleWitness.calculateRoot(Field(6)).toBigInt() + ).not.toStrictEqual(tree.getRoot().toBigInt()); + }); + + it("should have valid witnesses with changed value on the same leafs", () => { + expect.assertions(1); + + tree.setLeaf(1n, 1n); + tree.setLeaf(5n, 5n); + + const witness = tree.getWitness(5n).leafCurrent; + + tree.setLeaf(5n, 10n); + + expect( + witness.merkleWitness + .calculateRoot( + Poseidon.hash([Field(10), witness.leaf.path, witness.leaf.nextPath]) + ) + .toBigInt() + ).toStrictEqual(tree.getRoot().toBigInt()); + }); + + it("should return zeroNode", () => { + expect.assertions(3); + const MAX_FIELD_VALUE: bigint = BigInt(2 ** 53 - 1); + const zeroLeaf = tree.getLeaf(0n); + expect(zeroLeaf.value.toBigInt()).toStrictEqual(0n); + expect(zeroLeaf.path.toBigInt()).toStrictEqual(0n); + expect(zeroLeaf.nextPath.toBigInt()).toStrictEqual(MAX_FIELD_VALUE); + }); + + it("should return zeroNode", () => {}); +}); + +// Separate describe here since we only want small trees for this test. +describe("Error check", () => { + class LinkedMerkleTree extends createLinkedMerkleTree(4) {} + let store: InMemoryLinkedMerkleTreeStorage; + let tree: LinkedMerkleTree; + + it("throw for invalid index", () => { + log.setLevel("INFO"); + + store = new InMemoryLinkedMerkleTreeStorage(); + tree = new LinkedMerkleTree(store); + expect(() => { + for (let i = 0; i < 2n ** BigInt(4) + 1n; i++) { + tree.setLeaf(BigInt(i), 2n); + } + }).toThrow("Index greater than maximum leaf number"); + }); +}); diff --git a/packages/common/test/trees/MerkleTree.test.ts b/packages/common/test/trees/MerkleTree.test.ts index b0a3ea0e..2452960e 100644 --- a/packages/common/test/trees/MerkleTree.test.ts +++ b/packages/common/test/trees/MerkleTree.test.ts @@ -1,123 +1,106 @@ import { beforeEach } from "@jest/globals"; -import { Field, Poseidon } from "o1js"; +import { Field } from "o1js"; -import { - createLinkedMerkleTree, - InMemoryLinkedMerkleTreeStorage, - log, -} from "../../src"; +import { createMerkleTree, InMemoryMerkleTreeStorage, log } from "../../src"; -describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { - class LinkedMerkleTree extends createLinkedMerkleTree(height) {} +describe.each([4, 16, 256])("cachedMerkleTree - %s", (height) => { + class RollupMerkleTree extends createMerkleTree(height) {} + // eslint-disable-next-line @typescript-eslint/no-unused-vars + class RollupMerkleTreeWitness extends RollupMerkleTree.WITNESS {} - let store: InMemoryLinkedMerkleTreeStorage; - let tree: LinkedMerkleTree; + let store: InMemoryMerkleTreeStorage; + let tree: RollupMerkleTree; beforeEach(() => { log.setLevel("INFO"); - store = new InMemoryLinkedMerkleTreeStorage(); - tree = new LinkedMerkleTree(store); + store = new InMemoryMerkleTreeStorage(); + tree = new RollupMerkleTree(store); }); it("should have the same root when empty", () => { expect.assertions(1); expect(tree.getRoot().toBigInt()).toStrictEqual( - LinkedMerkleTree.EMPTY_ROOT + RollupMerkleTree.EMPTY_ROOT ); }); it("should have a different root when not empty", () => { expect.assertions(1); - tree.setLeaf(1n, 1n); + tree.setLeaf(1n, Field(1)); expect(tree.getRoot().toBigInt()).not.toStrictEqual( - LinkedMerkleTree.EMPTY_ROOT + RollupMerkleTree.EMPTY_ROOT ); }); + it("should have the same root after adding and removing item", () => { + expect.assertions(1); + + tree.setLeaf(1n, Field(1)); + + const root = tree.getRoot(); + + tree.setLeaf(5n, Field(5)); + tree.setLeaf(5n, Field(0)); + + expect(tree.getRoot().toBigInt()).toStrictEqual(root.toBigInt()); + }); + it("should provide correct witnesses", () => { expect.assertions(1); - tree.setLeaf(1n, 1n); - tree.setLeaf(5n, 5n); - - const witness = tree.getWitness(5n).leafCurrent; - - expect( - witness.merkleWitness - .calculateRoot( - Poseidon.hash([ - witness.leaf.value, - witness.leaf.path, - witness.leaf.nextPath, - ]) - ) - .toBigInt() - ).toStrictEqual(tree.getRoot().toBigInt()); + tree.setLeaf(1n, Field(1)); + tree.setLeaf(5n, Field(5)); + + const witness = tree.getWitness(5n); + + expect(witness.calculateRoot(Field(5)).toBigInt()).toStrictEqual( + tree.getRoot().toBigInt() + ); }); it("should have invalid witnesses with wrong values", () => { expect.assertions(1); - tree.setLeaf(1n, 1n); - tree.setLeaf(5n, 5n); + tree.setLeaf(1n, Field(1)); + tree.setLeaf(5n, Field(5)); const witness = tree.getWitness(5n); - expect( - witness.leafCurrent.merkleWitness.calculateRoot(Field(6)).toBigInt() - ).not.toStrictEqual(tree.getRoot().toBigInt()); + expect(witness.calculateRoot(Field(6)).toBigInt()).not.toStrictEqual( + tree.getRoot().toBigInt() + ); }); it("should have valid witnesses with changed value on the same leafs", () => { expect.assertions(1); - tree.setLeaf(1n, 1n); - tree.setLeaf(5n, 5n); - - const witness = tree.getWitness(5n).leafCurrent; + tree.setLeaf(1n, Field(1)); + tree.setLeaf(5n, Field(5)); - tree.setLeaf(5n, 10n); + const witness = tree.getWitness(5n); - expect( - witness.merkleWitness - .calculateRoot( - Poseidon.hash([Field(10), witness.leaf.path, witness.leaf.nextPath]) - ) - .toBigInt() - ).toStrictEqual(tree.getRoot().toBigInt()); - }); + tree.setLeaf(5n, Field(10)); - it("should return zeroNode", () => { - expect.assertions(3); - const MAX_FIELD_VALUE: bigint = BigInt(2 ** 53 - 1); - const zeroLeaf = tree.getLeaf(0n); - expect(zeroLeaf.value.toBigInt()).toStrictEqual(0n); - expect(zeroLeaf.path.toBigInt()).toStrictEqual(0n); - expect(zeroLeaf.nextPath.toBigInt()).toStrictEqual(MAX_FIELD_VALUE); + expect(witness.calculateRoot(Field(10)).toBigInt()).toStrictEqual( + tree.getRoot().toBigInt() + ); }); - it("should return zeroNode", () => {}); -}); + it("should throw for invalid index", () => { + expect.assertions(2); -// Separate describe here since we only want small trees for this test. -describe("Error check", () => { - class LinkedMerkleTree extends createLinkedMerkleTree(4) {} - let store: InMemoryLinkedMerkleTreeStorage; - let tree: LinkedMerkleTree; + const index = 2n ** BigInt(height) + 1n; - it("throw for invalid index", () => { - log.setLevel("INFO"); + expect(() => { + tree.setLeaf(index, Field(1)); + }).toThrow("Index greater than maximum leaf number"); - store = new InMemoryLinkedMerkleTreeStorage(); - tree = new LinkedMerkleTree(store); expect(() => { - for (let i = 0; i < 2n ** BigInt(4) + 1n; i++) { - tree.setLeaf(BigInt(i), 2n); - } + tree.getNode(0, index); }).toThrow("Index greater than maximum leaf number"); }); }); From a1d81d280bb7913b060fc0e35882d29ebaf3ead3 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 15 Nov 2024 12:08:04 +0000 Subject: [PATCH 056/107] Created SyncCachedMerkleTree --- .../production/TransactionTraceService.ts | 4 +- .../merkle/SyncCachedLinkedMerkleTreeStore.ts | 47 +++++++++++++++++++ .../state/merkle/SyncCachedMerkleTreeStore.ts | 4 +- 3 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts diff --git a/packages/sequencer/src/protocol/production/TransactionTraceService.ts b/packages/sequencer/src/protocol/production/TransactionTraceService.ts index 1a717fa3..06121170 100644 --- a/packages/sequencer/src/protocol/production/TransactionTraceService.ts +++ b/packages/sequencer/src/protocol/production/TransactionTraceService.ts @@ -16,7 +16,6 @@ import { LinkedMerkleTree } from "@proto-kit/common/dist/trees/LinkedMerkleTree" import { distinctByString } from "../../helpers/utils"; import { CachedStateService } from "../../state/state/CachedStateService"; -import { SyncCachedMerkleTreeStore } from "../../state/merkle/SyncCachedMerkleTreeStore"; import type { TransactionExecutionResult, BlockWithResult, @@ -24,6 +23,7 @@ import type { import { AsyncMerkleTreeStore } from "../../state/async/AsyncMerkleTreeStore"; import { VerificationKeyService } from "../runtime/RuntimeVerificationKeyService"; import { CachedLinkedMerkleTreeStore } from "../../state/merkle/CachedLinkedMerkleTreeStore"; +import { SyncCachedLinkedMerkleTreeStore } from "../../state/merkle/SyncCachedLinkedMerkleTreeStore"; import type { TransactionTrace, BlockTrace } from "./BatchProducerModule"; import { StateTransitionProofParameters } from "./tasks/StateTransitionTaskParameters"; @@ -266,7 +266,7 @@ export class TransactionTraceService { }> { const keys = this.allKeys(protocolTransitions.concat(stateTransitions)); - const runtimeSimulationMerkleStore = new SyncCachedMerkleTreeStore( + const runtimeSimulationMerkleStore = new SyncCachedLinkedMerkleTreeStore( merkleStore ); diff --git a/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts new file mode 100644 index 00000000..fb01e542 --- /dev/null +++ b/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts @@ -0,0 +1,47 @@ +import { + InMemoryLinkedMerkleTreeStorage, + LinkedLeaf, + LinkedMerkleTree, + LinkedMerkleTreeStore, +} from "@proto-kit/common"; + +export class SyncCachedLinkedMerkleTreeStore extends InMemoryLinkedMerkleTreeStorage { + public constructor(private readonly parent: LinkedMerkleTreeStore) { + super(); + } + + public getNode(key: bigint, level: number): bigint | undefined { + return super.getNode(key, level) ?? this.parent.getNode(key, level); + } + + public setNode(key: bigint, level: number, value: bigint) { + super.setNode(key, level, value); + } + + public getLeaf(index: bigint): LinkedLeaf | undefined { + return super.getLeaf(index) ?? this.parent.getLeaf(index); + } + + public setLeaf(index: bigint, value: LinkedLeaf) { + super.setLeaf(index, value); + } + + public mergeIntoParent() { + if (Object.keys(this.leaves).length === 0) { + return; + } + + const { nodes, leaves } = this; + Object.entries(leaves).forEach(([key, leaf]) => + this.setLeaf(BigInt(key), leaf) + ); + Array.from({ length: LinkedMerkleTree.HEIGHT }).forEach((ignored, level) => + Object.entries(nodes[level]).forEach((entry) => { + this.parent.setNode(BigInt(entry[0]), level, entry[1]); + }) + ); + + this.leaves = {}; + this.nodes = {}; + } +} diff --git a/packages/sequencer/src/state/merkle/SyncCachedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/SyncCachedMerkleTreeStore.ts index a028006e..a2e122bc 100644 --- a/packages/sequencer/src/state/merkle/SyncCachedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/SyncCachedMerkleTreeStore.ts @@ -1,10 +1,10 @@ import { - InMemoryLinkedMerkleTreeStorage, + InMemoryMerkleTreeStorage, MerkleTreeStore, RollupMerkleTree, } from "@proto-kit/common"; -export class SyncCachedMerkleTreeStore extends InMemoryLinkedMerkleTreeStorage { +export class SyncCachedMerkleTreeStore extends InMemoryMerkleTreeStorage { public constructor(private readonly parent: MerkleTreeStore) { super(); } From a591f7b49374530a12aa2c85fdbb62ca16b324b7 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 15 Nov 2024 13:42:58 +0000 Subject: [PATCH 057/107] Written InMemoryAsyncLinkedMerkleTreeStore.ts --- .../state/async/AsyncLinkedMerkleTreeStore.ts | 2 +- .../merkle/CachedLinkedMerkleTreeStore.ts | 15 ++--- .../InMemoryAsyncLinkedMerkleTreeStore.ts | 66 +++++++++++++++++++ 3 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts diff --git a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts index c988c54d..01d5b146 100644 --- a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts @@ -9,7 +9,7 @@ export interface AsyncLinkedMerkleTreeStore { writeNodes: (nodes: MerkleTreeNode[]) => void; - writeLeaves: (leaves: LinkedLeaf[]) => void; + writeLeaves: (leaves: [string, LinkedLeaf][]) => void; getNodesAsync: ( nodes: MerkleTreeNodeQuery[] diff --git a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts index 0900ada8..df0b11d7 100644 --- a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts @@ -131,12 +131,10 @@ export class CachedLinkedMerkleTreeStore // It doesn't need any fancy logic and just updates the leaves. // I don't think we need to coordinate this with the nodes // or do any calculations. Just a straight copy and paste. - public writeLeaves(leaves: LinkedLeaf[]) { - leaves.forEach((leaf) => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const index = super.getLeafIndex(leaf.path) as bigint; - this.writeCache.leaves[index.toString()] = leaf; - super.setLeaf(index, leaf); + public writeLeaves(leaves: [string, LinkedLeaf][]) { + leaves.forEach(([key, leaf]) => { + this.writeCache.leaves[key] = leaf; + super.setLeaf(BigInt(key), leaf); }); } @@ -222,7 +220,6 @@ export class CachedLinkedMerkleTreeStore // at the various levels required to produce a witness for the given index (at level 0). // But only gets those that aren't already in the cache. private collectNodesToFetch(index: bigint) { - // Algo from RollupMerkleTree.getWitness() const { leafCount, HEIGHT } = RollupMerkleTree; let currentIndex = index >= leafCount ? index % leafCount : index; @@ -263,7 +260,7 @@ export class CachedLinkedMerkleTreeStore public async preloadKeys(paths: bigint[]) { const nodesToRetrieve = ( await Promise.all( - paths.flatMap(async (path) => { + paths.map(async (path) => { const pathIndex = super.getLeafIndex(path) ?? 0n; // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const resultLeaf = ( @@ -301,7 +298,7 @@ export class CachedLinkedMerkleTreeStore const nodes = this.getWrittenNodes(); const leaves = this.getWrittenLeaves(); - this.writeLeaves(Object.values(leaves)); + this.writeLeaves(Object.entries(leaves)); const writes = Object.keys(nodes).flatMap((levelString) => { const level = Number(levelString); return Object.entries(nodes[level]).map( diff --git a/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts b/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts new file mode 100644 index 00000000..05779d1a --- /dev/null +++ b/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts @@ -0,0 +1,66 @@ +import { + InMemoryLinkedMerkleTreeStorage, + LinkedLeaf, + noop, +} from "@proto-kit/common"; + +import { AsyncLinkedMerkleTreeStore } from "../../state/async/AsyncLinkedMerkleTreeStore"; +import { + MerkleTreeNode, + MerkleTreeNodeQuery, +} from "../../state/async/AsyncMerkleTreeStore"; + +export class InMemoryAsyncLinkedMerkleTreeStore + implements AsyncLinkedMerkleTreeStore +{ + private readonly store = new InMemoryLinkedMerkleTreeStorage(); + + public writeNodes(nodes: MerkleTreeNode[]): void { + nodes.forEach(({ key, level, value }) => + this.store.setNode(key, level, value) + ); + } + + public async commit(): Promise { + noop(); + } + + public async openTransaction(): Promise { + noop(); + } + + public async getNodesAsync( + nodes: MerkleTreeNodeQuery[] + ): Promise<(bigint | undefined)[]> { + return nodes.map(({ key, level }) => this.store.getNode(key, level)); + } + + public async getLeavesAsync(paths: bigint[]) { + return paths.map((path) => { + const index = this.store.getLeafIndex(path); + if (index !== undefined) { + this.store.getLeaf(index); + } + return undefined; + }); + } + + public writeLeaves(leaves: [string, LinkedLeaf][]) { + leaves.forEach(([key, leaf]) => { + this.store.setLeaf(BigInt(key), leaf); + }); + } + + public getLeafIndex(path: bigint) { + return this.store.getLeafIndex(path); + } + + public getMaximumIndex() { + return this.store.getMaximumIndex(); + } + + // This gets the leaf with the closest path. + public getPathLessOrEqual(path: bigint) { + return this.store.getPathLessOrEqual(path); + } +} From fd75b34f3a16c00eae9db55aadad710705a44168 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 15 Nov 2024 14:03:39 +0000 Subject: [PATCH 058/107] Update tsyringe to fetch new types --- .../src/protocol/production/BatchProducerModule.ts | 4 ++-- .../protocol/production/sequencing/BlockProducerModule.ts | 6 +++--- .../sequencer/src/storage/StorageDependencyFactory.ts | 8 ++++---- .../sequencer/src/storage/inmemory/InMemoryDatabase.ts | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/sequencer/src/protocol/production/BatchProducerModule.ts b/packages/sequencer/src/protocol/production/BatchProducerModule.ts index 3e5a4052..186597e7 100644 --- a/packages/sequencer/src/protocol/production/BatchProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BatchProducerModule.ts @@ -8,7 +8,7 @@ import { NetworkState, } from "@proto-kit/protocol"; import { Field, Proof } from "o1js"; -import { log, noop, RollupMerkleTree } from "@proto-kit/common"; +import { LinkedMerkleTree, log, noop } from "@proto-kit/common"; import { sequencerModule, @@ -268,7 +268,7 @@ export class BatchProducerModule extends SequencerModule { this.blockTreeStore, Field( blockWithPreviousResult.lastBlockResult?.stateRoot ?? - RollupMerkleTree.EMPTY_ROOT + LinkedMerkleTree.EMPTY_ROOT ), blockWithPreviousResult.block ); diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts index 106833f4..1c567f4c 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts @@ -16,11 +16,11 @@ import { } from "../../../sequencer/builder/SequencerModule"; import { BlockQueue } from "../../../storage/repositories/BlockStorage"; import { PendingTransaction } from "../../../mempool/PendingTransaction"; -import { AsyncMerkleTreeStore } from "../../../state/async/AsyncMerkleTreeStore"; import { AsyncStateService } from "../../../state/async/AsyncStateService"; import { Block, BlockWithResult } from "../../../storage/model/Block"; import { CachedStateService } from "../../../state/state/CachedStateService"; import { MessageStorage } from "../../../storage/repositories/MessageStorage"; +import { AsyncLinkedMerkleTreeStore } from "../../../state/async/AsyncLinkedMerkleTreeStore"; import { TransactionExecutionService } from "./TransactionExecutionService"; @@ -39,11 +39,11 @@ export class BlockProducerModule extends SequencerModule { @inject("UnprovenStateService") private readonly unprovenStateService: AsyncStateService, @inject("UnprovenMerkleStore") - private readonly unprovenMerkleStore: AsyncMerkleTreeStore, + private readonly unprovenMerkleStore: AsyncLinkedMerkleTreeStore, @inject("BlockQueue") private readonly blockQueue: BlockQueue, @inject("BlockTreeStore") - private readonly blockTreeStore: AsyncMerkleTreeStore, + private readonly blockTreeStore: AsyncLinkedMerkleTreeStore, private readonly executionService: TransactionExecutionService, @inject("MethodIdResolver") private readonly methodIdResolver: MethodIdResolver, diff --git a/packages/sequencer/src/storage/StorageDependencyFactory.ts b/packages/sequencer/src/storage/StorageDependencyFactory.ts index ed660d8f..dfd4ed6c 100644 --- a/packages/sequencer/src/storage/StorageDependencyFactory.ts +++ b/packages/sequencer/src/storage/StorageDependencyFactory.ts @@ -5,7 +5,7 @@ import { } from "@proto-kit/common"; import { AsyncStateService } from "../state/async/AsyncStateService"; -import { AsyncMerkleTreeStore } from "../state/async/AsyncMerkleTreeStore"; +import { AsyncLinkedMerkleTreeStore } from "../state/async/AsyncLinkedMerkleTreeStore"; import { BatchStorage } from "./repositories/BatchStorage"; import { BlockQueue, BlockStorage } from "./repositories/BlockStorage"; @@ -15,13 +15,13 @@ import { TransactionStorage } from "./repositories/TransactionStorage"; export interface StorageDependencyMinimumDependencies extends DependencyRecord { asyncStateService: DependencyDeclaration; - asyncMerkleStore: DependencyDeclaration; + asyncMerkleStore: DependencyDeclaration; batchStorage: DependencyDeclaration; blockQueue: DependencyDeclaration; blockStorage: DependencyDeclaration; unprovenStateService: DependencyDeclaration; - unprovenMerkleStore: DependencyDeclaration; - blockTreeStore: DependencyDeclaration; + unprovenMerkleStore: DependencyDeclaration; + blockTreeStore: DependencyDeclaration; messageStorage: DependencyDeclaration; settlementStorage: DependencyDeclaration; transactionStorage: DependencyDeclaration; diff --git a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts index 96390958..be06030e 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts @@ -9,7 +9,7 @@ import { StorageDependencyMinimumDependencies } from "../StorageDependencyFactor import { Database } from "../Database"; import { InMemoryBlockStorage } from "./InMemoryBlockStorage"; -import { InMemoryAsyncMerkleTreeStore } from "./InMemoryAsyncMerkleTreeStore"; +import { InMemoryAsyncLinkedMerkleTreeStore } from "./InMemoryAsyncLinkedMerkleTreeStore"; import { InMemoryBatchStorage } from "./InMemoryBatchStorage"; import { InMemoryMessageStorage } from "./InMemoryMessageStorage"; import { InMemorySettlementStorage } from "./InMemorySettlementStorage"; @@ -20,7 +20,7 @@ export class InMemoryDatabase extends SequencerModule implements Database { public dependencies(): StorageDependencyMinimumDependencies { return { asyncMerkleStore: { - useClass: InMemoryAsyncMerkleTreeStore, + useClass: InMemoryAsyncLinkedMerkleTreeStore, }, asyncStateService: { useFactory: () => new CachedStateService(undefined), @@ -38,10 +38,10 @@ export class InMemoryDatabase extends SequencerModule implements Database { useFactory: () => new CachedStateService(undefined), }, unprovenMerkleStore: { - useClass: InMemoryAsyncMerkleTreeStore, + useClass: InMemoryAsyncLinkedMerkleTreeStore, }, blockTreeStore: { - useClass: InMemoryAsyncMerkleTreeStore, + useClass: InMemoryAsyncLinkedMerkleTreeStore, }, messageStorage: { useClass: InMemoryMessageStorage, From b33b39330bc2550e3461707e7691c43c18274014 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 15 Nov 2024 16:36:52 +0000 Subject: [PATCH 059/107] Add test suite. Fix error. --- .../merkle/CachedLinkedMerkleTreeStore.ts | 4 +- .../merkle/CachedLinkedMerkleStore.test.ts | 118 ++++++++++++++++++ 2 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts diff --git a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts index df0b11d7..9408013e 100644 --- a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts @@ -90,7 +90,7 @@ export class CachedLinkedMerkleTreeStore } // This gets the nodes from the in memory store (which looks also to be the cache). - public getLeaf(path: bigint) { + private getLeafByPath(path: bigint) { const index = super.getLeafIndex(path); if (index !== undefined) { return super.getLeaf(index); @@ -107,7 +107,7 @@ export class CachedLinkedMerkleTreeStore const toFetch: bigint[] = []; paths.forEach((path, index) => { - const localResult = this.getLeaf(path); + const localResult = this.getLeafByPath(path); if (localResult !== undefined) { results[index] = localResult; } else { diff --git a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts new file mode 100644 index 00000000..2b4903dc --- /dev/null +++ b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts @@ -0,0 +1,118 @@ +import { LinkedMerkleTree } from "@proto-kit/common"; +import { beforeEach, expect } from "@jest/globals"; +import { Field, Poseidon } from "o1js"; + +import { CachedLinkedMerkleTreeStore } from "../../src/state/merkle/CachedLinkedMerkleTreeStore"; +import { InMemoryAsyncLinkedMerkleTreeStore } from "../../src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore"; +import { SyncCachedLinkedMerkleTreeStore } from "../../src/state/merkle/SyncCachedLinkedMerkleTreeStore"; + +describe("cached linked merkle store", () => { + const mainStore = new InMemoryAsyncLinkedMerkleTreeStore(); + + let cache1: CachedLinkedMerkleTreeStore; + let tree1: LinkedMerkleTree; + + beforeEach(async () => { + const cachedStore = new CachedLinkedMerkleTreeStore(mainStore); + + const tmpTree = new LinkedMerkleTree(cachedStore); + tmpTree.setLeaf(5n, 10n); + await cachedStore.mergeIntoParent(); + + cache1 = new CachedLinkedMerkleTreeStore(mainStore); + tree1 = new LinkedMerkleTree(cache1); + }); + + it("should cache multiple keys correctly", async () => { + expect.assertions(3); + + const cache2 = new CachedLinkedMerkleTreeStore(cache1); + const tree2 = new LinkedMerkleTree(cache2); + + tree1.setLeaf(16n, 16n); + tree1.setLeaf(46n, 46n); + + await cache2.preloadKeys([16n, 46n]); + + const leaf1 = tree1.getLeaf(16n); + const leaf2 = tree1.getLeaf(46n); + + expect(tree2.getNode(0, 16n).toBigInt()).toBe( + Poseidon.hash([leaf1.value, leaf1.path, leaf1.nextPath]).toBigInt() + ); + expect(tree2.getNode(0, 46n).toBigInt()).toBe( + Poseidon.hash([leaf2.value, leaf2.path, leaf2.nextPath]).toBigInt() + ); + + expect(tree2.getRoot().toString()).toStrictEqual( + tree1.getRoot().toString() + ); + }); + + it("should preload through multiple levels", async () => { + const cache2 = new CachedLinkedMerkleTreeStore(cache1); + + await cache2.preloadKey(5n); + + const leaf = tree1.getLeaf(5n); + expect(cache2.getNode(5n, 0)).toStrictEqual( + Poseidon.hash([leaf.value, leaf.path, leaf.nextPath]).toBigInt() + ); + }); + + it("should cache correctly", async () => { + expect.assertions(9); + + const cache2 = new SyncCachedLinkedMerkleTreeStore(cache1); + const tree2 = new LinkedMerkleTree(cache2); + + const leaf1 = tree2.getLeaf(5n); + await expect( + mainStore.getNodesAsync([{ key: 5n, level: 0 }]) + ).resolves.toStrictEqual([ + Poseidon.hash([leaf1.value, leaf1.path, leaf1.nextPath]).toBigInt(), + ]); + + await cache1.preloadKey(5n); + + tree1.setLeaf(10n, 20n); + + const leaf2 = tree2.getLeaf(10n); + expect(tree2.getNode(0, 10n).toBigInt()).toBe( + Poseidon.hash([leaf2.value, leaf2.path, leaf2.nextPath]).toBigInt() + ); + + const witness = tree2.getWitness(5n); + + expect( + witness.leafCurrent.merkleWitness.calculateRoot(Field(10)).toString() + ).toBe(tree1.getRoot().toString()); + expect( + witness.leafCurrent.merkleWitness.calculateRoot(Field(11)).toString() + ).not.toBe(tree1.getRoot().toString()); + + const witness2 = tree1.getWitness(10n); + + expect( + witness2.leafCurrent.merkleWitness.calculateRoot(Field(20)).toString() + ).toBe(tree2.getRoot().toString()); + + tree2.setLeaf(15n, 30n); + + expect(tree1.getRoot().toString()).not.toBe(tree2.getRoot().toString()); + + cache2.mergeIntoParent(); + + expect(tree1.getRoot().toString()).toBe(tree2.getRoot().toString()); + expect(tree1.getNode(0, 15n).toString()).toBe("30"); + + await cache1.mergeIntoParent(); + + const cachedStore = new CachedLinkedMerkleTreeStore(mainStore); + await cachedStore.preloadKey(15n); + + expect(new LinkedMerkleTree(cachedStore).getRoot().toString()).toBe( + tree2.getRoot().toString() + ); + }); +}); From 479eaef13ebaab8d0ab6f6b4a2520ad523035248 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 15 Nov 2024 16:37:45 +0000 Subject: [PATCH 060/107] Fix comment --- packages/common/src/trees/LinkedMerkleTree.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 64f594db..bc2debe9 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -95,8 +95,8 @@ export function createLinkedMerkleTree( */ // We define the RollupMerkleWitness again here as we want it to have the same height // as the tree. If we re-used the Witness from the RollupMerkleTree.ts we wouldn't have - // control, whilst having the overhead of creating the RollupTree since the witness is - // defined from the tree (for the height reason already described). Since we can't + // control, whilst having the overhead of creating the RollupTree, since the witness is + // defined from the tree (for the height reason already described). class RollupMerkleWitnessV2 extends Struct({ path: Provable.Array(Field, height - 1), From f8344933a85187c2355d6a01d866e3b932b20e8c Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Mon, 18 Nov 2024 13:10:09 +0000 Subject: [PATCH 061/107] Add tests and fixes. --- .../merkle/CachedLinkedMerkleTreeStore.ts | 42 ++++++++++--------- .../merkle/CachedLinkedMerkleStore.test.ts | 24 ++++++++--- .../test/merkle/CachedMerkleStore.test.ts | 4 +- 3 files changed, 43 insertions(+), 27 deletions(-) diff --git a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts index 9408013e..4f50f678 100644 --- a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts @@ -1,7 +1,6 @@ import { log, noop, - RollupMerkleTree, InMemoryLinkedMerkleTreeStorage, LinkedLeaf, } from "@proto-kit/common"; @@ -133,11 +132,15 @@ export class CachedLinkedMerkleTreeStore // or do any calculations. Just a straight copy and paste. public writeLeaves(leaves: [string, LinkedLeaf][]) { leaves.forEach(([key, leaf]) => { - this.writeCache.leaves[key] = leaf; - super.setLeaf(BigInt(key), leaf); + this.setLeaf(BigInt(key), leaf); }); } + public setLeaf(key: bigint, leaf: LinkedLeaf) { + this.writeCache.leaves[key.toString()] = leaf; + super.setLeaf(BigInt(key), leaf); + } + // // This sets the leaves in the cache and in the in-memory tree. // // It also updates the relevant node at the base level. // // Note that setNode doesn't carry the change up the tree (i.e. for siblings etc) @@ -220,7 +223,9 @@ export class CachedLinkedMerkleTreeStore // at the various levels required to produce a witness for the given index (at level 0). // But only gets those that aren't already in the cache. private collectNodesToFetch(index: bigint) { - const { leafCount, HEIGHT } = RollupMerkleTree; + // This is hardcoded, but should be changed. + const HEIGHT = 40n; + const leafCount = 2n ** (HEIGHT - 1n); let currentIndex = index >= leafCount ? index % leafCount : index; @@ -258,20 +263,19 @@ export class CachedLinkedMerkleTreeStore // Takes a list of keys and for each key collects the relevant nodes from the // parent tree and sets the leaf and node in the cached tree (and in-memory tree). public async preloadKeys(paths: bigint[]) { - const nodesToRetrieve = ( - await Promise.all( - paths.map(async (path) => { - const pathIndex = super.getLeafIndex(path) ?? 0n; - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const resultLeaf = ( - await this.parent.getLeavesAsync([path]) - )[0] as LinkedLeaf; - super.setLeaf(pathIndex, resultLeaf); - this.writeCache.leaves[pathIndex.toString()] = resultLeaf; - return this.collectNodesToFetch(pathIndex); - }) - ) - ).flat(1); + const nodesToRetrieve = paths.flatMap((path) => { + const pathIndex = this.parent.getLeafIndex(path) ?? 0n; + return this.collectNodesToFetch(pathIndex); + }); + + for (const path of paths) { + const pathIndex = this.parent.getLeafIndex(path) ?? 0n; + const resultLeaf = // eslint-disable-next-line no-await-in-loop,@typescript-eslint/consistent-type-assertions + (await this.parent.getLeavesAsync([path]))[0] as LinkedLeaf; + super.setLeaf(pathIndex, resultLeaf); + this.writeCache.leaves[pathIndex.toString()] = resultLeaf; + } + const resultsNode = await this.parent.getNodesAsync(nodesToRetrieve); nodesToRetrieve.forEach(({ key, level }, index) => { const value = resultsNode[index]; @@ -298,7 +302,7 @@ export class CachedLinkedMerkleTreeStore const nodes = this.getWrittenNodes(); const leaves = this.getWrittenLeaves(); - this.writeLeaves(Object.entries(leaves)); + this.parent.writeLeaves(Object.entries(leaves)); const writes = Object.keys(nodes).flatMap((levelString) => { const level = Number(levelString); return Object.entries(nodes[level]).map( diff --git a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts index 2b4903dc..83163bb1 100644 --- a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts +++ b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts @@ -1,4 +1,4 @@ -import { LinkedMerkleTree } from "@proto-kit/common"; +import { expectDefined, LinkedMerkleTree } from "@proto-kit/common"; import { beforeEach, expect } from "@jest/globals"; import { Field, Poseidon } from "o1js"; @@ -24,7 +24,7 @@ describe("cached linked merkle store", () => { }); it("should cache multiple keys correctly", async () => { - expect.assertions(3); + expect.assertions(7); const cache2 = new CachedLinkedMerkleTreeStore(cache1); const tree2 = new LinkedMerkleTree(cache2); @@ -32,15 +32,27 @@ describe("cached linked merkle store", () => { tree1.setLeaf(16n, 16n); tree1.setLeaf(46n, 46n); - await cache2.preloadKeys([16n, 46n]); + // Need to preload 0n, as well since the nextPath of the leaf would have changed + // when other leaves were added. + await cache2.preloadKeys([0n, 16n, 46n]); const leaf1 = tree1.getLeaf(16n); const leaf2 = tree1.getLeaf(46n); - expect(tree2.getNode(0, 16n).toBigInt()).toBe( + const leaf1Index = cache2.getLeafIndex(16n); + const leaf2Index = cache2.getLeafIndex(46n); + + expectDefined(leaf1Index); + expectDefined(leaf2Index); + + // Note that 5n hasn't been loaded so indices are off by 1. + expect(leaf1Index).toStrictEqual(1n); + expect(leaf2Index).toStrictEqual(2n); + + expect(tree2.getNode(0, leaf1Index).toBigInt()).toBe( Poseidon.hash([leaf1.value, leaf1.path, leaf1.nextPath]).toBigInt() ); - expect(tree2.getNode(0, 46n).toBigInt()).toBe( + expect(tree2.getNode(0, leaf2Index).toBigInt()).toBe( Poseidon.hash([leaf2.value, leaf2.path, leaf2.nextPath]).toBigInt() ); @@ -52,7 +64,7 @@ describe("cached linked merkle store", () => { it("should preload through multiple levels", async () => { const cache2 = new CachedLinkedMerkleTreeStore(cache1); - await cache2.preloadKey(5n); + await cache2.preloadKeys([0n, 5n]); const leaf = tree1.getLeaf(5n); expect(cache2.getNode(5n, 0)).toStrictEqual( diff --git a/packages/sequencer/test/merkle/CachedMerkleStore.test.ts b/packages/sequencer/test/merkle/CachedMerkleStore.test.ts index 4d882d7d..fd688426 100644 --- a/packages/sequencer/test/merkle/CachedMerkleStore.test.ts +++ b/packages/sequencer/test/merkle/CachedMerkleStore.test.ts @@ -25,7 +25,7 @@ describe("cached merkle store", () => { tree1 = new RollupMerkleTree(cache1); }); - it("should cache multiple keys corretly", async () => { + it("should cache multiple keys correctly", async () => { expect.assertions(3); const cache2 = new CachedMerkleTreeStore(cache1); @@ -34,7 +34,7 @@ describe("cached merkle store", () => { tree1.setLeaf(16n, Field(16)); tree1.setLeaf(46n, Field(46)); - await cache2.preloadKeys([16n, 46n]); + await cache2.preloadKeys([16n, 46n, 5n]); expect(tree2.getNode(0, 16n).toBigInt()).toBe(16n); expect(tree2.getNode(0, 46n).toBigInt()).toBe(46n); From a28a02b989c62cbcc83c07f3fa7f04ab910a7c84 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Mon, 18 Nov 2024 14:58:11 +0000 Subject: [PATCH 062/107] Don't preload. --- packages/common/src/trees/LinkedMerkleTree.ts | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index bc2debe9..c35a093a 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -349,16 +349,18 @@ export function createLinkedMerkleTree( */ private setLeafInitialisation() { // This is the maximum value of the hash - const MAX_FIELD_VALUE: bigint = BigInt(2 ** 53 - 1); - this.store.setLeaf(0n, { - value: 0n, - path: 0n, - nextPath: MAX_FIELD_VALUE, - }); - // We do this to get the Field-ified version of the leaf. - const initialLeaf = this.getLeaf(0n); - // We now set the leafs in the merkle tree. - this.setMerkleLeaf(0n, initialLeaf); + if (this.store.getMaximumIndex() <= 0n) { + const MAX_FIELD_VALUE: bigint = BigInt(2 ** 53 - 1); + this.store.setLeaf(0n, { + value: 0n, + path: 0n, + nextPath: MAX_FIELD_VALUE, + }); + // We do this to get the Field-ified version of the leaf. + const initialLeaf = this.getLeaf(0n); + // We now set the leafs in the merkle tree. + this.setMerkleLeaf(0n, initialLeaf); + } } /** From 55dda230ef4e08524c45e11ab4d4b6060651cb7b Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Mon, 18 Nov 2024 19:50:10 +0000 Subject: [PATCH 063/107] Don't preload. --- packages/common/src/trees/LinkedMerkleTree.ts | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index c35a093a..2cccf253 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -219,7 +219,12 @@ export function createLinkedMerkleTree( Poseidon.hash([previousLevel, previousLevel]).toBigInt() ); } - this.setLeafInitialisation(); + // We only do the leaf initialisation the store + // has no values. Otherwise, we leave the store + // as is to not overwrite any data. + if (this.store.getMaximumIndex() <= 0n) { + this.setLeafInitialisation(); + } } public getNode(level: number, index: bigint): Field { @@ -349,18 +354,17 @@ export function createLinkedMerkleTree( */ private setLeafInitialisation() { // This is the maximum value of the hash - if (this.store.getMaximumIndex() <= 0n) { - const MAX_FIELD_VALUE: bigint = BigInt(2 ** 53 - 1); - this.store.setLeaf(0n, { - value: 0n, - path: 0n, - nextPath: MAX_FIELD_VALUE, - }); - // We do this to get the Field-ified version of the leaf. - const initialLeaf = this.getLeaf(0n); - // We now set the leafs in the merkle tree. - this.setMerkleLeaf(0n, initialLeaf); - } + const MAX_FIELD_VALUE: bigint = BigInt(2 ** 53 - 1); + this.store.setLeaf(0n, { + value: 0n, + path: 0n, + nextPath: MAX_FIELD_VALUE, + }); + // We do this to get the Field-ified version of the leaf. + const initialLeaf = this.getLeaf(0n); + // We now set the leafs in the merkle tree to cascade the values up + // the tree. + this.setMerkleLeaf(0n, initialLeaf); } /** From d0beafd5750f673e9ad3cc5324150d560a6194d6 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Mon, 18 Nov 2024 21:33:04 +0000 Subject: [PATCH 064/107] Get first few tests passing. --- .../state/async/AsyncLinkedMerkleTreeStore.ts | 3 + .../merkle/CachedLinkedMerkleTreeStore.ts | 89 ++++++------------- .../merkle/CachedLinkedMerkleStore.test.ts | 28 +++--- 3 files changed, 46 insertions(+), 74 deletions(-) diff --git a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts index 01d5b146..dd65a218 100644 --- a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts @@ -22,4 +22,7 @@ export interface AsyncLinkedMerkleTreeStore { getPathLessOrEqual: (path: bigint) => LinkedLeaf; getMaximumIndex: () => bigint; + + // For the preloadedKeys functionality + getLeafByIndex: (index: bigint) => LinkedLeaf | undefined; } diff --git a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts index 4f50f678..ca9d7d79 100644 --- a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts @@ -38,6 +38,18 @@ export class CachedLinkedMerkleTreeStore super(); } + public static async new( + parent: AsyncLinkedMerkleTreeStore + ): Promise { + // do your async stuff here + // now instantiate and return a class + const cachedInstance = new CachedLinkedMerkleTreeStore(parent); + if (parent.getMaximumIndex() > 0) { + await cachedInstance.preloadKey(0n); + } + return cachedInstance; + } + // This gets the nodes from the in memory store (which looks also to be the cache). public getNode(key: bigint, level: number): bigint | undefined { return super.getNode(key, level); @@ -141,59 +153,9 @@ export class CachedLinkedMerkleTreeStore super.setLeaf(BigInt(key), leaf); } - // // This sets the leaves in the cache and in the in-memory tree. - // // It also updates the relevant node at the base level. - // // Note that setNode doesn't carry the change up the tree (i.e. for siblings etc) - // public setLeaf(index: bigint, leaf: LinkedLeaf) { - // super.setLeaf(index, leaf); - // this.writeCache.leaves[index.toString()] = leaf; - // - // this.setNode( - // index, - // 0, - // Poseidon.hash([ - // Field(leaf.value), - // Field(leaf.path), - // Field(leaf.nextPath), - // ]).toBigInt() - // ); - // } - // This is setLeaf (cache and in-memory) for a list of leaves. - // It checks if it's an insert or update and then updates the relevant - // leaf. - // public writeLeaves(leaves: { path: bigint; value: bigint }[]) { - // leaves.forEach(({ value, path }) => { - // const index = super.getLeafIndex(path); - // // The following checks if we have an insert or update. - // if (index !== undefined) { - // // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - // const linkedLeaf = super.getLeaf(index) as LinkedLeaf; - // this.setLeaf(index, { - // value: value, - // path: path, - // nextPath: linkedLeaf.nextPath, - // }); - // } else { - // // This is an insert. Need to change two leaves. - // const nearestLinkedLeaf = super.getPathLessOrEqual(path); - // // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - // const nearestLinkedleafIndex = super.getLeafIndex( - // nearestLinkedLeaf.path - // ) as bigint; - // const lowestUnoccupiedIndex = super.getMaximumIndex() + 1n; - // this.setLeaf(nearestLinkedleafIndex, { - // value: nearestLinkedLeaf.value, - // path: nearestLinkedLeaf.path, - // nextPath: path, - // }); - // this.setLeaf(lowestUnoccupiedIndex, { - // value: value, - // path: path, - // nextPath: nearestLinkedLeaf.path, - // }); - // } - // }); - // } + public getLeafByIndex(index: bigint) { + return super.getLeaf(index); + } // This gets the nodes from the cache. // Only used in mergeIntoParent @@ -268,21 +230,22 @@ export class CachedLinkedMerkleTreeStore return this.collectNodesToFetch(pathIndex); }); - for (const path of paths) { - const pathIndex = this.parent.getLeafIndex(path) ?? 0n; - const resultLeaf = // eslint-disable-next-line no-await-in-loop,@typescript-eslint/consistent-type-assertions - (await this.parent.getLeavesAsync([path]))[0] as LinkedLeaf; - super.setLeaf(pathIndex, resultLeaf); - this.writeCache.leaves[pathIndex.toString()] = resultLeaf; - } - const resultsNode = await this.parent.getNodesAsync(nodesToRetrieve); - nodesToRetrieve.forEach(({ key, level }, index) => { + let index = 0; + for (const retrievedNode of nodesToRetrieve) { + const { key, level } = retrievedNode; const value = resultsNode[index]; if (value !== undefined) { this.setNode(key, level, value); + if (level === 0) { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const resultLeaf = this.parent.getLeafByIndex(key) as LinkedLeaf; + super.setLeaf(key, resultLeaf); + this.writeCache.leaves[key.toString()] = resultLeaf; + } } - }); + index += 1; + } } // This is preloadKeys with just one index/key. diff --git a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts index 83163bb1..7955ad3e 100644 --- a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts +++ b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts @@ -13,25 +13,24 @@ describe("cached linked merkle store", () => { let tree1: LinkedMerkleTree; beforeEach(async () => { - const cachedStore = new CachedLinkedMerkleTreeStore(mainStore); + const cachedStore = await CachedLinkedMerkleTreeStore.new(mainStore); const tmpTree = new LinkedMerkleTree(cachedStore); tmpTree.setLeaf(5n, 10n); await cachedStore.mergeIntoParent(); - cache1 = new CachedLinkedMerkleTreeStore(mainStore); + cache1 = await CachedLinkedMerkleTreeStore.new(mainStore); tree1 = new LinkedMerkleTree(cache1); }); it("should cache multiple keys correctly", async () => { expect.assertions(7); - const cache2 = new CachedLinkedMerkleTreeStore(cache1); - const tree2 = new LinkedMerkleTree(cache2); - tree1.setLeaf(16n, 16n); tree1.setLeaf(46n, 46n); + const cache2 = await CachedLinkedMerkleTreeStore.new(cache1); + const tree2 = new LinkedMerkleTree(cache2); // Need to preload 0n, as well since the nextPath of the leaf would have changed // when other leaves were added. await cache2.preloadKeys([0n, 16n, 46n]); @@ -45,9 +44,12 @@ describe("cached linked merkle store", () => { expectDefined(leaf1Index); expectDefined(leaf2Index); - // Note that 5n hasn't been loaded so indices are off by 1. - expect(leaf1Index).toStrictEqual(1n); - expect(leaf2Index).toStrictEqual(2n); + // The new leaves are at index 2 and 3, as the index 5 is auto-preloaded + // as it is next to 0, and 0 is always preloaded as well as any relevant + // nodes. + + expect(leaf1Index).toStrictEqual(2n); + expect(leaf2Index).toStrictEqual(3n); expect(tree2.getNode(0, leaf1Index).toBigInt()).toBe( Poseidon.hash([leaf1.value, leaf1.path, leaf1.nextPath]).toBigInt() @@ -62,12 +64,16 @@ describe("cached linked merkle store", () => { }); it("should preload through multiple levels", async () => { - const cache2 = new CachedLinkedMerkleTreeStore(cache1); + const cache2 = await CachedLinkedMerkleTreeStore.new(cache1); await cache2.preloadKeys([0n, 5n]); const leaf = tree1.getLeaf(5n); - expect(cache2.getNode(5n, 0)).toStrictEqual( + + const leafIndex = cache2.getLeafIndex(5n); + expectDefined(leafIndex); + expect(leafIndex).toStrictEqual(1n); + expect(cache2.getNode(leafIndex, 0)).toStrictEqual( Poseidon.hash([leaf.value, leaf.path, leaf.nextPath]).toBigInt() ); }); @@ -120,7 +126,7 @@ describe("cached linked merkle store", () => { await cache1.mergeIntoParent(); - const cachedStore = new CachedLinkedMerkleTreeStore(mainStore); + const cachedStore = await CachedLinkedMerkleTreeStore.new(mainStore); await cachedStore.preloadKey(15n); expect(new LinkedMerkleTree(cachedStore).getRoot().toString()).toBe( From fe2ac0e6fe3a3f3c03e2ba565e2090d64da9c01c Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Tue, 19 Nov 2024 09:08:04 +0000 Subject: [PATCH 065/107] Get first test passing --- .../inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts | 4 ++++ .../test/merkle/CachedLinkedMerkleStore.test.ts | 13 +++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts b/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts index 05779d1a..1dedbe48 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts @@ -63,4 +63,8 @@ export class InMemoryAsyncLinkedMerkleTreeStore public getPathLessOrEqual(path: bigint) { return this.store.getPathLessOrEqual(path); } + + public getLeafByIndex(index: bigint) { + return this.store.getLeaf(index); + } } diff --git a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts index 7955ad3e..a0ea3eb6 100644 --- a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts +++ b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts @@ -17,6 +17,7 @@ describe("cached linked merkle store", () => { const tmpTree = new LinkedMerkleTree(cachedStore); tmpTree.setLeaf(5n, 10n); + // tmpTree.setLeaf(6n, 12n); await cachedStore.mergeIntoParent(); cache1 = await CachedLinkedMerkleTreeStore.new(mainStore); @@ -24,7 +25,7 @@ describe("cached linked merkle store", () => { }); it("should cache multiple keys correctly", async () => { - expect.assertions(7); + expect.assertions(10); tree1.setLeaf(16n, 16n); tree1.setLeaf(46n, 46n); @@ -35,6 +36,7 @@ describe("cached linked merkle store", () => { // when other leaves were added. await cache2.preloadKeys([0n, 16n, 46n]); + const leaf0 = tree1.getLeaf(0n); const leaf1 = tree1.getLeaf(16n); const leaf2 = tree1.getLeaf(46n); @@ -47,7 +49,6 @@ describe("cached linked merkle store", () => { // The new leaves are at index 2 and 3, as the index 5 is auto-preloaded // as it is next to 0, and 0 is always preloaded as well as any relevant // nodes. - expect(leaf1Index).toStrictEqual(2n); expect(leaf2Index).toStrictEqual(3n); @@ -58,6 +59,10 @@ describe("cached linked merkle store", () => { Poseidon.hash([leaf2.value, leaf2.path, leaf2.nextPath]).toBigInt() ); + expect(tree2.getLeaf(0n)).toEqual(leaf0); + expect(tree2.getLeaf(16n)).toEqual(leaf1); + expect(tree2.getLeaf(46n)).toEqual(leaf2); + expect(tree2.getRoot().toString()).toStrictEqual( tree1.getRoot().toString() ); @@ -66,8 +71,8 @@ describe("cached linked merkle store", () => { it("should preload through multiple levels", async () => { const cache2 = await CachedLinkedMerkleTreeStore.new(cache1); - await cache2.preloadKeys([0n, 5n]); - + // nodes 0 and 5 should be auto-preloaded when cache2 is created. + // We auto-preload 0 whenever the parent cache is already created. const leaf = tree1.getLeaf(5n); const leafIndex = cache2.getLeafIndex(5n); From fa5a44a7bc302d178ff139cfd84df6cc4a1ede28 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Tue, 19 Nov 2024 10:08:18 +0000 Subject: [PATCH 066/107] Undo test change. --- packages/sequencer/test/merkle/CachedMerkleStore.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sequencer/test/merkle/CachedMerkleStore.test.ts b/packages/sequencer/test/merkle/CachedMerkleStore.test.ts index fd688426..1b6cbf37 100644 --- a/packages/sequencer/test/merkle/CachedMerkleStore.test.ts +++ b/packages/sequencer/test/merkle/CachedMerkleStore.test.ts @@ -34,7 +34,7 @@ describe("cached merkle store", () => { tree1.setLeaf(16n, Field(16)); tree1.setLeaf(46n, Field(46)); - await cache2.preloadKeys([16n, 46n, 5n]); + await cache2.preloadKeys([16n, 46n]); expect(tree2.getNode(0, 16n).toBigInt()).toBe(16n); expect(tree2.getNode(0, 46n).toBigInt()).toBe(46n); From 038205752443774dc626c77d7c2dc9b3280d7945 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Tue, 19 Nov 2024 12:32:16 +0000 Subject: [PATCH 067/107] Always preLoad last index to ensure later nodes are added afterwards --- .../trees/InMemoryLinkedMerkleTreeStorage.ts | 9 +++- .../merkle/CachedLinkedMerkleTreeStore.ts | 19 +++++--- .../merkle/CachedLinkedMerkleStore.test.ts | 48 +++++++++++++++---- 3 files changed, 61 insertions(+), 15 deletions(-) diff --git a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts index 2628f23c..e65333f4 100644 --- a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts +++ b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts @@ -38,7 +38,14 @@ export class InMemoryLinkedMerkleTreeStorage implements LinkedMerkleTreeStore { } public getMaximumIndex(): bigint { - return BigInt(Object.keys(this.leaves).length) - 1n; + let max = 0n; + Object.keys(this.leaves).forEach((x) => { + const key = BigInt(x); + if (key > max) { + max = key; + } + }); + return max; } // This gets the leaf with the closest path. diff --git a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts index ca9d7d79..6b151b58 100644 --- a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts @@ -41,11 +41,18 @@ export class CachedLinkedMerkleTreeStore public static async new( parent: AsyncLinkedMerkleTreeStore ): Promise { - // do your async stuff here - // now instantiate and return a class const cachedInstance = new CachedLinkedMerkleTreeStore(parent); - if (parent.getMaximumIndex() > 0) { - await cachedInstance.preloadKey(0n); + const maxIndex = parent.getMaximumIndex(); + // If the parent is populated then we + // load up the first key and the last key. + // The last key is to ensure we do not overwrite + // any existing paths when we insert a new node/leaf. + if (maxIndex > 0) { + const leaf = parent.getLeafByIndex(maxIndex); + if (leaf === undefined) { + throw Error("Max Path is not defined"); + } + await cachedInstance.preloadKeys([0n, leaf.path]); } return cachedInstance; } @@ -249,8 +256,8 @@ export class CachedLinkedMerkleTreeStore } // This is preloadKeys with just one index/key. - public async preloadKey(index: bigint): Promise { - await this.preloadKeys([index]); + public async preloadKey(path: bigint): Promise { + await this.preloadKeys([path]); } // This merges the cache into the parent tree and resets the cache, but not the diff --git a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts index a0ea3eb6..96f8eda8 100644 --- a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts +++ b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts @@ -68,19 +68,51 @@ describe("cached linked merkle store", () => { ); }); - it("should preload through multiple levels", async () => { + it("should preload through multiple levels and insert correctly at right index", async () => { + tree1.setLeaf(10n, 10n); + tree1.setLeaf(11n, 11n); + tree1.setLeaf(12n, 12n); + tree1.setLeaf(13n, 13n); + + // Nodes 0 and 5 should be auto-preloaded when cache2 is created + // as 0 is the first and 5 is its sibling. Similarly, 12 and 13 + // should be preloaded as 13 is in the maximum index and 12 is its sibling. + // Nodes 10 and 11 shouldn't be preloaded. + // We auto-preload 0 whenever the parent cache is already created. + const cache2 = await CachedLinkedMerkleTreeStore.new(cache1); + const tree2 = new LinkedMerkleTree(cache2); - // nodes 0 and 5 should be auto-preloaded when cache2 is created. - // We auto-preload 0 whenever the parent cache is already created. - const leaf = tree1.getLeaf(5n); + tree2.setLeaf(14n, 14n); - const leafIndex = cache2.getLeafIndex(5n); - expectDefined(leafIndex); - expect(leafIndex).toStrictEqual(1n); - expect(cache2.getNode(leafIndex, 0)).toStrictEqual( + const leaf = tree1.getLeaf(5n); + const leaf2 = tree2.getLeaf(14n); + + const leaf5Index = cache2.getLeafIndex(5n); + const leaf10Index = cache2.getLeafIndex(10n); + const leaf11Index = cache2.getLeafIndex(11n); + const leaf12Index = cache2.getLeafIndex(12n); + const leaf13Index = cache2.getLeafIndex(13n); + const leaf14Index = cache2.getLeafIndex(14n); + + expectDefined(leaf5Index); + expect(leaf10Index).toBeNull(); + expect(leaf11Index).toBeNull(); + expectDefined(leaf12Index); + expectDefined(leaf13Index); + expectDefined(leaf14Index); + + expect(leaf5Index).toStrictEqual(1n); + expect(leaf12Index).toStrictEqual(4n); + expect(leaf13Index).toStrictEqual(5n); + expect(leaf14Index).toStrictEqual(6n); + + expect(cache2.getNode(leaf5Index, 0)).toStrictEqual( Poseidon.hash([leaf.value, leaf.path, leaf.nextPath]).toBigInt() ); + expect(cache2.getNode(leaf14Index, 0)).toStrictEqual( + Poseidon.hash([leaf2.value, leaf2.path, leaf2.nextPath]).toBigInt() + ); }); it("should cache correctly", async () => { From 79c36887c75d3e8dce41afe9db1a25f0d5f78b5a Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Tue, 19 Nov 2024 13:18:42 +0000 Subject: [PATCH 068/107] Make constructor private and change code to accept static new --- .../sequencer/src/protocol/production/BatchProducerModule.ts | 2 +- .../sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sequencer/src/protocol/production/BatchProducerModule.ts b/packages/sequencer/src/protocol/production/BatchProducerModule.ts index 186597e7..95561a0b 100644 --- a/packages/sequencer/src/protocol/production/BatchProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BatchProducerModule.ts @@ -223,7 +223,7 @@ export class BatchProducerModule extends SequencerModule { const stateServices = { stateService: new CachedStateService(this.asyncStateService), - merkleStore: new CachedLinkedMerkleTreeStore(this.merkleStore), + merkleStore: await CachedLinkedMerkleTreeStore.new(this.merkleStore), }; const blockTraces: BlockTrace[] = []; diff --git a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts index 6b151b58..98c4d8e9 100644 --- a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts @@ -34,7 +34,7 @@ export class CachedLinkedMerkleTreeStore noop(); } - public constructor(private readonly parent: AsyncLinkedMerkleTreeStore) { + private constructor(private readonly parent: AsyncLinkedMerkleTreeStore) { super(); } From 00dda9166428686a63466d8c8474d33a7e7d918f Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:18:32 +0000 Subject: [PATCH 069/107] Remove getPathClose in LinkedMerkleTree --- packages/common/src/trees/LinkedMerkleTree.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 2cccf253..577ac2bd 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -238,14 +238,6 @@ export function createLinkedMerkleTree( * @returns The data of the node. */ public getLeaf(path: bigint): LinkedLeaf { - return this.getPathLessOrEqual(path); - } - - /** - * Returns the leaf with a path either equal to or less than the path specified. - * @param path Position of the leaf node. - * */ - private getPathLessOrEqual(path: bigint): LinkedLeaf { const closestLeaf = this.store.getPathLessOrEqual(path); return { value: Field(closestLeaf.value), From 9e3630c685c655e2c78f77e9cac437554652f8c1 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Tue, 19 Nov 2024 17:51:51 +0000 Subject: [PATCH 070/107] Change how getPathLessOrEqual works --- .../trees/InMemoryLinkedMerkleTreeStorage.ts | 4 +- packages/common/src/trees/LinkedMerkleTree.ts | 19 +++- .../common/src/trees/LinkedMerkleTreeStore.ts | 2 +- .../state/async/AsyncLinkedMerkleTreeStore.ts | 2 - .../merkle/CachedLinkedMerkleTreeStore.ts | 21 +++++ .../merkle/CachedLinkedMerkleStore.test.ts | 91 ++++++++++++++++--- 6 files changed, 116 insertions(+), 23 deletions(-) diff --git a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts index e65333f4..af9eff6a 100644 --- a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts +++ b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts @@ -49,7 +49,7 @@ export class InMemoryLinkedMerkleTreeStorage implements LinkedMerkleTreeStore { } // This gets the leaf with the closest path. - public getPathLessOrEqual(path: bigint): LinkedLeaf { + public getLeafLessOrEqual(path: bigint): Promise { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions let largestLeaf = this.getLeaf(0n) as LinkedLeaf; while (largestLeaf.nextPath <= path) { @@ -58,6 +58,6 @@ export class InMemoryLinkedMerkleTreeStorage implements LinkedMerkleTreeStore { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions largestLeaf = this.getLeaf(nextIndex) as LinkedLeaf; } - return largestLeaf; + return Promise.resolve(largestLeaf); } } diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 577ac2bd..9dfe45aa 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -53,10 +53,11 @@ export interface AbstractLinkedMerkleTree { * @param path of the leaf node. * @param value New value. */ - setLeaf(path: bigint, value: bigint): void; + setLeaf(path: bigint, value: bigint): Promise; /** * Returns a leaf which lives at a given path. + * Errors otherwise. * @param path Index of the node. * @returns The data of the leaf. */ @@ -233,12 +234,20 @@ export function createLinkedMerkleTree( } /** - * Returns leaf which lives at a given path, or closest path + * Returns leaf which lives at a given path. + * Errors if the path is not defined. * @param path path of the node. * @returns The data of the node. */ public getLeaf(path: bigint): LinkedLeaf { - const closestLeaf = this.store.getPathLessOrEqual(path); + const index = this.store.getLeafIndex(path); + if (index === undefined) { + throw Error("Path not defined"); + } + const closestLeaf = this.store.getLeaf(index); + if (closestLeaf === undefined) { + throw Error("Leaf not defined"); + } return { value: Field(closestLeaf.value), path: Field(closestLeaf.path), @@ -291,9 +300,9 @@ export function createLinkedMerkleTree( * @param path Position of the leaf node. * @param value New value. */ - public setLeaf(path: bigint, value: bigint) { + public async setLeaf(path: bigint, value: bigint) { let index = this.store.getLeafIndex(path); - const prevLeaf = this.store.getPathLessOrEqual(path); + const prevLeaf = await this.store.getLeafLessOrEqual(path); let witnessPrevious; if (index === undefined) { // The above means the path doesn't already exist, and we are inserting, not updating. diff --git a/packages/common/src/trees/LinkedMerkleTreeStore.ts b/packages/common/src/trees/LinkedMerkleTreeStore.ts index ca295aea..e7e53af3 100644 --- a/packages/common/src/trees/LinkedMerkleTreeStore.ts +++ b/packages/common/src/trees/LinkedMerkleTreeStore.ts @@ -11,7 +11,7 @@ export interface LinkedMerkleTreeStore extends MerkleTreeStore { getLeafIndex: (path: bigint) => bigint | undefined; - getPathLessOrEqual: (path: bigint) => LinkedLeaf; + getLeafLessOrEqual: (path: bigint) => Promise; getMaximumIndex: () => bigint; } diff --git a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts index dd65a218..4d4b8d65 100644 --- a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts @@ -19,8 +19,6 @@ export interface AsyncLinkedMerkleTreeStore { getLeafIndex: (path: bigint) => bigint | undefined; - getPathLessOrEqual: (path: bigint) => LinkedLeaf; - getMaximumIndex: () => bigint; // For the preloadedKeys functionality diff --git a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts index 98c4d8e9..40239f3d 100644 --- a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts @@ -182,6 +182,27 @@ export class CachedLinkedMerkleTreeStore return this.writeCache.leaves; } + // This gets the leaf with the path equal or just less than + // the given path. + public async getLeafLessOrEqual(path: bigint): Promise { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + let largestLeaf = this.getLeaf(0n) as LinkedLeaf; + while (largestLeaf.nextPath <= path) { + let nextLeaf = this.getLeafByPath(largestLeaf.nextPath); + // This means the nextPath wasn't preloaded and we have to load it. + if (nextLeaf === undefined) { + // eslint-disable-next-line no-await-in-loop + await this.preloadKey(largestLeaf.nextPath); + nextLeaf = this.getLeafByPath(largestLeaf.nextPath); + if (nextLeaf === undefined) { + throw Error(" Next Path is defined but not fetched"); + } + } + largestLeaf = nextLeaf; + } + return largestLeaf; + } + // This resets the cache (not the in memory tree). public resetWrittenTree() { this.writeCache = { nodes: {}, leaves: {} }; diff --git a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts index 96f8eda8..d54b233f 100644 --- a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts +++ b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts @@ -16,8 +16,7 @@ describe("cached linked merkle store", () => { const cachedStore = await CachedLinkedMerkleTreeStore.new(mainStore); const tmpTree = new LinkedMerkleTree(cachedStore); - tmpTree.setLeaf(5n, 10n); - // tmpTree.setLeaf(6n, 12n); + await tmpTree.setLeaf(5n, 10n); await cachedStore.mergeIntoParent(); cache1 = await CachedLinkedMerkleTreeStore.new(mainStore); @@ -27,8 +26,8 @@ describe("cached linked merkle store", () => { it("should cache multiple keys correctly", async () => { expect.assertions(10); - tree1.setLeaf(16n, 16n); - tree1.setLeaf(46n, 46n); + await tree1.setLeaf(16n, 16n); + await tree1.setLeaf(46n, 46n); const cache2 = await CachedLinkedMerkleTreeStore.new(cache1); const tree2 = new LinkedMerkleTree(cache2); @@ -69,10 +68,10 @@ describe("cached linked merkle store", () => { }); it("should preload through multiple levels and insert correctly at right index", async () => { - tree1.setLeaf(10n, 10n); - tree1.setLeaf(11n, 11n); - tree1.setLeaf(12n, 12n); - tree1.setLeaf(13n, 13n); + await tree1.setLeaf(10n, 10n); + await tree1.setLeaf(11n, 11n); + await tree1.setLeaf(12n, 12n); + await tree1.setLeaf(13n, 13n); // Nodes 0 and 5 should be auto-preloaded when cache2 is created // as 0 is the first and 5 is its sibling. Similarly, 12 and 13 @@ -83,7 +82,10 @@ describe("cached linked merkle store", () => { const cache2 = await CachedLinkedMerkleTreeStore.new(cache1); const tree2 = new LinkedMerkleTree(cache2); - tree2.setLeaf(14n, 14n); + // When we set this leaf the missing nodes are preloaded + // as when we do a set we have to go through all the leaves to find + // the one with the nextPath that is suitable + await tree2.setLeaf(14n, 14n); const leaf = tree1.getLeaf(5n); const leaf2 = tree2.getLeaf(14n); @@ -96,13 +98,15 @@ describe("cached linked merkle store", () => { const leaf14Index = cache2.getLeafIndex(14n); expectDefined(leaf5Index); - expect(leaf10Index).toBeNull(); - expect(leaf11Index).toBeNull(); + expectDefined(leaf10Index); + expectDefined(leaf11Index); expectDefined(leaf12Index); expectDefined(leaf13Index); expectDefined(leaf14Index); expect(leaf5Index).toStrictEqual(1n); + expect(leaf10Index).toStrictEqual(2n); + expect(leaf11Index).toStrictEqual(3n); expect(leaf12Index).toStrictEqual(4n); expect(leaf13Index).toStrictEqual(5n); expect(leaf14Index).toStrictEqual(6n); @@ -115,6 +119,67 @@ describe("cached linked merkle store", () => { ); }); + it("should preload through multiple levels and insert correctly at right index - harder", async () => { + await tree1.setLeaf(10n, 10n); + await tree1.setLeaf(100n, 100n); + await tree1.setLeaf(200n, 200n); + await tree1.setLeaf(300n, 300n); + await tree1.setLeaf(400n, 400n); + await tree1.setLeaf(500n, 500n); + + // Nodes 0 and 5 should be auto-preloaded when cache2 is created + // as 0 is the first and 5 is its sibling. Similarly, 400 and 500 + // should be preloaded as 500 is in the maximum index and 400 is its sibling. + // Nodes 10 and 100, 300 and 400, shouldn't be preloaded. + // Note We auto-preload 0 whenever the parent cache is already created. + + const cache2 = await CachedLinkedMerkleTreeStore.new(cache1); + const tree2 = new LinkedMerkleTree(cache2); + + // When we set this leaf some of the missing nodes are preloaded + // as when we do a set we have to go through all the leaves to find + // the one with the nextPath that is suitable and this preloads that are missing before. + // This means 10n will be preloaded and since 100n is its sibling this will be preloaded, too. + // Note that the nodes 200n and 300n are not preloaded. + await tree2.setLeaf(14n, 14n); + + const leaf = tree1.getLeaf(5n); + const leaf2 = tree2.getLeaf(14n); + + const leaf5Index = cache2.getLeafIndex(5n); + const leaf10Index = cache2.getLeafIndex(10n); + const leaf100Index = cache2.getLeafIndex(100n); + const leaf200Index = cache2.getLeafIndex(200n); + const leaf300Index = cache2.getLeafIndex(300n); + const leaf400Index = cache2.getLeafIndex(400n); + const leaf500Index = cache2.getLeafIndex(500n); + const leaf14Index = cache2.getLeafIndex(14n); + + expectDefined(leaf5Index); + expectDefined(leaf10Index); + expectDefined(leaf100Index); + expectDefined(leaf400Index); + expectDefined(leaf500Index); + expectDefined(leaf14Index); + + expect(leaf5Index).toStrictEqual(1n); + expect(leaf10Index).toStrictEqual(2n); + expect(leaf100Index).toStrictEqual(3n); + expect(leaf200Index).toStrictEqual(undefined); + expect(leaf300Index).toStrictEqual(undefined); + expect(leaf400Index).toStrictEqual(6n); + expect(leaf500Index).toStrictEqual(7n); + expect(leaf14Index).toStrictEqual(8n); + + expect(cache2.getNode(leaf5Index, 0)).toStrictEqual( + Poseidon.hash([leaf.value, leaf.path, leaf.nextPath]).toBigInt() + ); + expect(cache2.getNode(leaf14Index, 0)).toStrictEqual( + Poseidon.hash([leaf2.value, leaf2.path, leaf2.nextPath]).toBigInt() + ); + expect(tree1.getRoot()).not.toEqual(tree2.getRoot()); + }); + it("should cache correctly", async () => { expect.assertions(9); @@ -130,7 +195,7 @@ describe("cached linked merkle store", () => { await cache1.preloadKey(5n); - tree1.setLeaf(10n, 20n); + await tree1.setLeaf(10n, 20n); const leaf2 = tree2.getLeaf(10n); expect(tree2.getNode(0, 10n).toBigInt()).toBe( @@ -152,7 +217,7 @@ describe("cached linked merkle store", () => { witness2.leafCurrent.merkleWitness.calculateRoot(Field(20)).toString() ).toBe(tree2.getRoot().toString()); - tree2.setLeaf(15n, 30n); + await tree2.setLeaf(15n, 30n); expect(tree1.getRoot().toString()).not.toBe(tree2.getRoot().toString()); From 102f95e3efa007ce6fd0df5e347a8285c712951d Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Tue, 19 Nov 2024 19:01:39 +0000 Subject: [PATCH 071/107] Move changes to getNearestPath to another method on the cache and call separately. --- .../trees/InMemoryLinkedMerkleTreeStorage.ts | 4 +-- packages/common/src/trees/LinkedMerkleTree.ts | 6 ++-- .../common/src/trees/LinkedMerkleTreeStore.ts | 2 +- .../merkle/CachedLinkedMerkleTreeStore.ts | 10 ++++--- .../InMemoryAsyncLinkedMerkleTreeStore.ts | 5 ---- .../merkle/CachedLinkedMerkleStore.test.ts | 28 ++++++++++--------- 6 files changed, 27 insertions(+), 28 deletions(-) diff --git a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts index af9eff6a..892cdc3d 100644 --- a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts +++ b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts @@ -49,7 +49,7 @@ export class InMemoryLinkedMerkleTreeStorage implements LinkedMerkleTreeStore { } // This gets the leaf with the closest path. - public getLeafLessOrEqual(path: bigint): Promise { + public getLeafLessOrEqual(path: bigint): LinkedLeaf { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions let largestLeaf = this.getLeaf(0n) as LinkedLeaf; while (largestLeaf.nextPath <= path) { @@ -58,6 +58,6 @@ export class InMemoryLinkedMerkleTreeStorage implements LinkedMerkleTreeStore { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions largestLeaf = this.getLeaf(nextIndex) as LinkedLeaf; } - return Promise.resolve(largestLeaf); + return largestLeaf; } } diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 9dfe45aa..4fa11643 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -53,7 +53,7 @@ export interface AbstractLinkedMerkleTree { * @param path of the leaf node. * @param value New value. */ - setLeaf(path: bigint, value: bigint): Promise; + setLeaf(path: bigint, value: bigint): LinkedMerkleTreeWitness; /** * Returns a leaf which lives at a given path. @@ -300,9 +300,9 @@ export function createLinkedMerkleTree( * @param path Position of the leaf node. * @param value New value. */ - public async setLeaf(path: bigint, value: bigint) { + public setLeaf(path: bigint, value: bigint) { let index = this.store.getLeafIndex(path); - const prevLeaf = await this.store.getLeafLessOrEqual(path); + const prevLeaf = this.store.getLeafLessOrEqual(path); let witnessPrevious; if (index === undefined) { // The above means the path doesn't already exist, and we are inserting, not updating. diff --git a/packages/common/src/trees/LinkedMerkleTreeStore.ts b/packages/common/src/trees/LinkedMerkleTreeStore.ts index e7e53af3..18b9fe77 100644 --- a/packages/common/src/trees/LinkedMerkleTreeStore.ts +++ b/packages/common/src/trees/LinkedMerkleTreeStore.ts @@ -11,7 +11,7 @@ export interface LinkedMerkleTreeStore extends MerkleTreeStore { getLeafIndex: (path: bigint) => bigint | undefined; - getLeafLessOrEqual: (path: bigint) => Promise; + getLeafLessOrEqual: (path: bigint) => LinkedLeaf; getMaximumIndex: () => bigint; } diff --git a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts index 40239f3d..62668fb0 100644 --- a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts @@ -182,9 +182,12 @@ export class CachedLinkedMerkleTreeStore return this.writeCache.leaves; } - // This gets the leaf with the path equal or just less than - // the given path. - public async getLeafLessOrEqual(path: bigint): Promise { + // This ensures all the keys needed to be loaded + // to find the closest path are loaded. + // A bit repetitive as we basically repeat the process + // (without the loading) when we find the closest leaf. + // TODO: see how we could sue a returned value. + public async loadUpKeysForClosestPath(path: bigint): Promise { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions let largestLeaf = this.getLeaf(0n) as LinkedLeaf; while (largestLeaf.nextPath <= path) { @@ -200,7 +203,6 @@ export class CachedLinkedMerkleTreeStore } largestLeaf = nextLeaf; } - return largestLeaf; } // This resets the cache (not the in memory tree). diff --git a/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts b/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts index 1dedbe48..1b902171 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts @@ -59,11 +59,6 @@ export class InMemoryAsyncLinkedMerkleTreeStore return this.store.getMaximumIndex(); } - // This gets the leaf with the closest path. - public getPathLessOrEqual(path: bigint) { - return this.store.getPathLessOrEqual(path); - } - public getLeafByIndex(index: bigint) { return this.store.getLeaf(index); } diff --git a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts index d54b233f..07c17b3e 100644 --- a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts +++ b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts @@ -26,8 +26,8 @@ describe("cached linked merkle store", () => { it("should cache multiple keys correctly", async () => { expect.assertions(10); - await tree1.setLeaf(16n, 16n); - await tree1.setLeaf(46n, 46n); + tree1.setLeaf(16n, 16n); + tree1.setLeaf(46n, 46n); const cache2 = await CachedLinkedMerkleTreeStore.new(cache1); const tree2 = new LinkedMerkleTree(cache2); @@ -68,10 +68,10 @@ describe("cached linked merkle store", () => { }); it("should preload through multiple levels and insert correctly at right index", async () => { - await tree1.setLeaf(10n, 10n); - await tree1.setLeaf(11n, 11n); - await tree1.setLeaf(12n, 12n); - await tree1.setLeaf(13n, 13n); + tree1.setLeaf(10n, 10n); + tree1.setLeaf(11n, 11n); + tree1.setLeaf(12n, 12n); + tree1.setLeaf(13n, 13n); // Nodes 0 and 5 should be auto-preloaded when cache2 is created // as 0 is the first and 5 is its sibling. Similarly, 12 and 13 @@ -85,7 +85,8 @@ describe("cached linked merkle store", () => { // When we set this leaf the missing nodes are preloaded // as when we do a set we have to go through all the leaves to find // the one with the nextPath that is suitable - await tree2.setLeaf(14n, 14n); + await cache2.loadUpKeysForClosestPath(14n); + tree2.setLeaf(14n, 14n); const leaf = tree1.getLeaf(5n); const leaf2 = tree2.getLeaf(14n); @@ -120,12 +121,12 @@ describe("cached linked merkle store", () => { }); it("should preload through multiple levels and insert correctly at right index - harder", async () => { - await tree1.setLeaf(10n, 10n); - await tree1.setLeaf(100n, 100n); - await tree1.setLeaf(200n, 200n); - await tree1.setLeaf(300n, 300n); - await tree1.setLeaf(400n, 400n); - await tree1.setLeaf(500n, 500n); + tree1.setLeaf(10n, 10n); + tree1.setLeaf(100n, 100n); + tree1.setLeaf(200n, 200n); + tree1.setLeaf(300n, 300n); + tree1.setLeaf(400n, 400n); + tree1.setLeaf(500n, 500n); // Nodes 0 and 5 should be auto-preloaded when cache2 is created // as 0 is the first and 5 is its sibling. Similarly, 400 and 500 @@ -141,6 +142,7 @@ describe("cached linked merkle store", () => { // the one with the nextPath that is suitable and this preloads that are missing before. // This means 10n will be preloaded and since 100n is its sibling this will be preloaded, too. // Note that the nodes 200n and 300n are not preloaded. + await cache2.loadUpKeysForClosestPath(14n); await tree2.setLeaf(14n, 14n); const leaf = tree1.getLeaf(5n); From 35cd1fa751126f589760da52f76de4b407be9f04 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Wed, 20 Nov 2024 13:09:14 +0000 Subject: [PATCH 072/107] Got syncedCachedLinkedMerkleTreeStoreWorking --- packages/common/src/trees/LinkedMerkleTree.ts | 2 +- .../merkle/SyncCachedLinkedMerkleTreeStore.ts | 16 ++++- .../merkle/CachedLinkedMerkleStore.test.ts | 58 +++++++++++++++---- 3 files changed, 62 insertions(+), 14 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 4fa11643..a51d456a 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -220,7 +220,7 @@ export function createLinkedMerkleTree( Poseidon.hash([previousLevel, previousLevel]).toBigInt() ); } - // We only do the leaf initialisation the store + // We only do the leaf initialisation when the store // has no values. Otherwise, we leave the store // as is to not overwrite any data. if (this.store.getMaximumIndex() <= 0n) { diff --git a/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts index fb01e542..4102ff01 100644 --- a/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts @@ -5,6 +5,8 @@ import { LinkedMerkleTreeStore, } from "@proto-kit/common"; +// This is mainly used for supporting the rollbacks we need to do in case a runtimemethod fails +// In this case everything should be preloaded in the parent async service export class SyncCachedLinkedMerkleTreeStore extends InMemoryLinkedMerkleTreeStorage { public constructor(private readonly parent: LinkedMerkleTreeStore) { super(); @@ -26,6 +28,18 @@ export class SyncCachedLinkedMerkleTreeStore extends InMemoryLinkedMerkleTreeSto super.setLeaf(index, value); } + // Need to make sure we call the parent as the super will usually be empty + // The Tree calls this method. + public getLeafIndex(path: bigint): bigint | undefined { + return this.parent.getLeafIndex(path); + } + + // Need to make sure we call the parent as the super will usually be empty + // The tree calls this method. + public getMaximumIndex(): bigint { + return this.parent.getMaximumIndex(); + } + public mergeIntoParent() { if (Object.keys(this.leaves).length === 0) { return; @@ -33,7 +47,7 @@ export class SyncCachedLinkedMerkleTreeStore extends InMemoryLinkedMerkleTreeSto const { nodes, leaves } = this; Object.entries(leaves).forEach(([key, leaf]) => - this.setLeaf(BigInt(key), leaf) + this.parent.setLeaf(BigInt(key), leaf) ); Array.from({ length: LinkedMerkleTree.HEIGHT }).forEach((ignored, level) => Object.entries(nodes[level]).forEach((entry) => { diff --git a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts index 07c17b3e..10c1e7ff 100644 --- a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts +++ b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts @@ -16,7 +16,7 @@ describe("cached linked merkle store", () => { const cachedStore = await CachedLinkedMerkleTreeStore.new(mainStore); const tmpTree = new LinkedMerkleTree(cachedStore); - await tmpTree.setLeaf(5n, 10n); + tmpTree.setLeaf(5n, 10n); await cachedStore.mergeIntoParent(); cache1 = await CachedLinkedMerkleTreeStore.new(mainStore); @@ -183,51 +183,85 @@ describe("cached linked merkle store", () => { }); it("should cache correctly", async () => { - expect.assertions(9); + expect.assertions(12); const cache2 = new SyncCachedLinkedMerkleTreeStore(cache1); const tree2 = new LinkedMerkleTree(cache2); const leaf1 = tree2.getLeaf(5n); + const leaf1Index = cache2.getLeafIndex(5n); + expectDefined(leaf1Index); await expect( - mainStore.getNodesAsync([{ key: 5n, level: 0 }]) + mainStore.getNodesAsync([{ key: leaf1Index, level: 0 }]) ).resolves.toStrictEqual([ Poseidon.hash([leaf1.value, leaf1.path, leaf1.nextPath]).toBigInt(), ]); - await cache1.preloadKey(5n); - - await tree1.setLeaf(10n, 20n); + tree1.setLeaf(10n, 20n); const leaf2 = tree2.getLeaf(10n); - expect(tree2.getNode(0, 10n).toBigInt()).toBe( + const leaf2Index = cache2.getLeafIndex(10n); + expectDefined(leaf2Index); + expect(tree2.getNode(0, leaf2Index).toBigInt()).toBe( Poseidon.hash([leaf2.value, leaf2.path, leaf2.nextPath]).toBigInt() ); const witness = tree2.getWitness(5n); + // We check tree1 and tree2 have same hash roots. + // The witness is from tree2, which comes from cache2, + // but which because of the sync is really just cache1. expect( - witness.leafCurrent.merkleWitness.calculateRoot(Field(10)).toString() + witness.leafCurrent.merkleWitness + .calculateRoot( + Poseidon.hash([ + witness.leafCurrent.leaf.value, + witness.leafCurrent.leaf.path, + witness.leafCurrent.leaf.nextPath, + ]) + ) + .toString() ).toBe(tree1.getRoot().toString()); + expect( - witness.leafCurrent.merkleWitness.calculateRoot(Field(11)).toString() + witness.leafCurrent.merkleWitness + .calculateRoot(Poseidon.hash([Field(11), Field(5n), Field(10n)])) + .toString() ).not.toBe(tree1.getRoot().toString()); const witness2 = tree1.getWitness(10n); expect( - witness2.leafCurrent.merkleWitness.calculateRoot(Field(20)).toString() + witness2.leafCurrent.merkleWitness + .calculateRoot( + Poseidon.hash([ + Field(20), + Field(10n), + witness2.leafCurrent.leaf.nextPath, // This is the maximum as the the leaf 10n should be the last + ]) + ) + .toString() ).toBe(tree2.getRoot().toString()); - await tree2.setLeaf(15n, 30n); + tree2.setLeaf(15n, 30n); + // Won't be the same as the tree2 works on cache2 and these changes don't + // carry up to cache1. Have to merge into parent for this. expect(tree1.getRoot().toString()).not.toBe(tree2.getRoot().toString()); + // After this the changes should be merged into the parents, i.e. cache1, + // which tree1 has access to. cache2.mergeIntoParent(); + const index15 = cache2.getLeafIndex(15n); + const leaf15 = tree2.getLeaf(15n); + expectDefined(index15); expect(tree1.getRoot().toString()).toBe(tree2.getRoot().toString()); - expect(tree1.getNode(0, 15n).toString()).toBe("30"); + expect(tree1.getNode(0, index15).toString()).toBe( + Poseidon.hash([leaf15.value, leaf15.path, leaf15.nextPath]).toString() + ); + // Now the mainstore has the new 15n root. await cache1.mergeIntoParent(); const cachedStore = await CachedLinkedMerkleTreeStore.new(mainStore); From adf4a87fa5c7cb63094d7cea65448e1785a8ac81 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Wed, 20 Nov 2024 13:47:56 +0000 Subject: [PATCH 073/107] Add in template for RedisLinkedMerkleTreeStore --- packages/persistance/src/RedisConnection.ts | 8 +- packages/persistance/src/index.ts | 1 + .../redis/RedisLinkedMerkleTreeStore.ts | 84 +++++++++++++++++++ 3 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 packages/persistance/src/services/redis/RedisLinkedMerkleTreeStore.ts diff --git a/packages/persistance/src/RedisConnection.ts b/packages/persistance/src/RedisConnection.ts index e9bd6a9f..80e10eeb 100644 --- a/packages/persistance/src/RedisConnection.ts +++ b/packages/persistance/src/RedisConnection.ts @@ -5,7 +5,7 @@ import { } from "@proto-kit/sequencer"; import { DependencyFactory } from "@proto-kit/common"; -import { RedisMerkleTreeStore } from "./services/redis/RedisMerkleTreeStore"; +import { RedisLinkedMerkleTreeStore } from "./services/redis/RedisLinkedMerkleTreeStore"; export interface RedisConnectionConfig { host: string; @@ -39,13 +39,13 @@ export class RedisConnectionModule > { return { asyncMerkleStore: { - useFactory: () => new RedisMerkleTreeStore(this), + useFactory: () => new RedisLinkedMerkleTreeStore(this), }, unprovenMerkleStore: { - useFactory: () => new RedisMerkleTreeStore(this, "unproven"), + useFactory: () => new RedisLinkedMerkleTreeStore(this, "unproven"), }, blockTreeStore: { - useFactory: () => new RedisMerkleTreeStore(this, "blockHash"), + useFactory: () => new RedisLinkedMerkleTreeStore(this, "blockHash"), }, }; } diff --git a/packages/persistance/src/index.ts b/packages/persistance/src/index.ts index 20821121..51ef2c6f 100644 --- a/packages/persistance/src/index.ts +++ b/packages/persistance/src/index.ts @@ -15,3 +15,4 @@ export * from "./services/prisma/mappers/StateTransitionMapper"; export * from "./services/prisma/mappers/TransactionMapper"; export * from "./services/prisma/mappers/BlockResultMapper"; export * from "./services/redis/RedisMerkleTreeStore"; +export * from "./services/redis/RedisLinkedMerkleTreeStore"; diff --git a/packages/persistance/src/services/redis/RedisLinkedMerkleTreeStore.ts b/packages/persistance/src/services/redis/RedisLinkedMerkleTreeStore.ts new file mode 100644 index 00000000..139b6a56 --- /dev/null +++ b/packages/persistance/src/services/redis/RedisLinkedMerkleTreeStore.ts @@ -0,0 +1,84 @@ +import { MerkleTreeNode, MerkleTreeNodeQuery } from "@proto-kit/sequencer"; +import { LinkedLeaf, log, noop } from "@proto-kit/common"; +import { AsyncLinkedMerkleTreeStore } from "@proto-kit/sequencer/dist/state/async/AsyncLinkedMerkleTreeStore"; + +import type { RedisConnection } from "../../RedisConnection"; + +export class RedisLinkedMerkleTreeStore implements AsyncLinkedMerkleTreeStore { + private cache: MerkleTreeNode[] = []; + + public constructor( + private readonly connection: RedisConnection, + private readonly mask: string = "base" + ) {} + + private getKey(node: MerkleTreeNodeQuery): string { + return `${this.mask}:${node.level}:${node.key.toString()}`; + } + + public async openTransaction(): Promise { + noop(); + } + + public async commit(): Promise { + const start = Date.now(); + const array: [string, string][] = this.cache.map( + ({ key, level, value }) => [this.getKey({ key, level }), value.toString()] + ); + + if (array.length === 0) { + return; + } + + try { + await this.connection.redisClient.mSet(array.flat(1)); + } catch (error) { + log.error(error); + } + log.trace( + `Committing ${array.length} kv-pairs took ${Date.now() - start} ms` + ); + + this.cache = []; + } + + public async getNodesAsync( + nodes: MerkleTreeNodeQuery[] + ): Promise<(bigint | undefined)[]> { + if (nodes.length === 0) { + return []; + } + + const keys = nodes.map((node) => this.getKey(node)); + + const result = await this.connection.redisClient.mGet(keys); + + return result.map((x) => (x !== null ? BigInt(x) : undefined)); + } + + public writeNodes(nodes: MerkleTreeNode[]): void { + this.cache = this.cache.concat(nodes); + } + + public writeLeaves(leaves: [string, LinkedLeaf][]) {} + + public getLeavesAsync(paths: bigint[]) { + return Promise.resolve([undefined]); + } + + public getLeafIndex(path: bigint) { + return 0n; + } + + public getMaximumIndex() { + return 0n; + } + + public getLeafByIndex(index: bigint) { + return { + value: 0n, + path: 0n, + nextPath: 0n, + }; + } +} From a530f18bc1ae373ac576e19eeb8641fbf58c029f Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 21 Nov 2024 11:42:26 +0000 Subject: [PATCH 074/107] Change Max Field Value and update test --- packages/common/src/trees/LinkedMerkleTree.ts | 2 +- packages/common/test/trees/LinkedMerkleTree.test.ts | 8 ++++---- .../src/state/merkle/CachedLinkedMerkleTreeStore.ts | 2 ++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index a51d456a..8c969667 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -355,7 +355,7 @@ export function createLinkedMerkleTree( */ private setLeafInitialisation() { // This is the maximum value of the hash - const MAX_FIELD_VALUE: bigint = BigInt(2 ** 53 - 1); + const MAX_FIELD_VALUE: bigint = BigInt(2 ** 256); this.store.setLeaf(0n, { value: 0n, path: 0n, diff --git a/packages/common/test/trees/LinkedMerkleTree.test.ts b/packages/common/test/trees/LinkedMerkleTree.test.ts index b0a3ea0e..e21907db 100644 --- a/packages/common/test/trees/LinkedMerkleTree.test.ts +++ b/packages/common/test/trees/LinkedMerkleTree.test.ts @@ -93,14 +93,14 @@ describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { it("should return zeroNode", () => { expect.assertions(3); - const MAX_FIELD_VALUE: bigint = BigInt(2 ** 53 - 1); + const MAX_FIELD_VALUE: bigint = BigInt(2 ** 256); const zeroLeaf = tree.getLeaf(0n); expect(zeroLeaf.value.toBigInt()).toStrictEqual(0n); expect(zeroLeaf.path.toBigInt()).toStrictEqual(0n); - expect(zeroLeaf.nextPath.toBigInt()).toStrictEqual(MAX_FIELD_VALUE); + expect(zeroLeaf.nextPath.toBigInt()).toStrictEqual( + Field(MAX_FIELD_VALUE).toBigInt() + ); }); - - it("should return zeroNode", () => {}); }); // Separate describe here since we only want small trees for this test. diff --git a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts index 62668fb0..60bb46bf 100644 --- a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts @@ -53,6 +53,8 @@ export class CachedLinkedMerkleTreeStore throw Error("Max Path is not defined"); } await cachedInstance.preloadKeys([0n, leaf.path]); + } else { + await cachedInstance.preloadKeys([0n]); } return cachedInstance; } From 8f015bcf480f0b809f57a9c9209d31c6cc2e209f Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 21 Nov 2024 14:51:59 +0000 Subject: [PATCH 075/107] Max field, and other minor changes --- .../trees/InMemoryLinkedMerkleTreeStorage.ts | 5 +- packages/common/src/trees/LinkedMerkleTree.ts | 46 +++++++++++++------ .../common/src/trees/LinkedMerkleTreeStore.ts | 2 +- .../test/trees/LinkedMerkleTree.test.ts | 8 ++-- .../sequencing/TransactionExecutionService.ts | 22 ++++----- .../state/async/AsyncLinkedMerkleTreeStore.ts | 2 +- .../merkle/CachedLinkedMerkleTreeStore.ts | 4 +- .../merkle/SyncCachedLinkedMerkleTreeStore.ts | 10 +++- 8 files changed, 64 insertions(+), 35 deletions(-) diff --git a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts index 892cdc3d..4d1c9313 100644 --- a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts +++ b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts @@ -37,7 +37,10 @@ export class InMemoryLinkedMerkleTreeStorage implements LinkedMerkleTreeStore { return BigInt(leafIndex); } - public getMaximumIndex(): bigint { + public getMaximumIndex(): bigint | undefined { + if (Object.keys(this.leaves).length === 0) { + return undefined; + } let max = 0n; Object.keys(this.leaves).forEach((x) => { const key = BigInt(x); diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 8c969667..aa0878b1 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -61,7 +61,7 @@ export interface AbstractLinkedMerkleTree { * @param path Index of the node. * @returns The data of the leaf. */ - getLeaf(path: bigint): LinkedLeaf; + getLeaf(path: bigint): LinkedLeaf | undefined; /** * Returns the witness (also known as @@ -223,7 +223,7 @@ export function createLinkedMerkleTree( // We only do the leaf initialisation when the store // has no values. Otherwise, we leave the store // as is to not overwrite any data. - if (this.store.getMaximumIndex() <= 0n) { + if (this.store.getMaximumIndex() === undefined) { this.setLeafInitialisation(); } } @@ -239,14 +239,14 @@ export function createLinkedMerkleTree( * @param path path of the node. * @returns The data of the node. */ - public getLeaf(path: bigint): LinkedLeaf { + public getLeaf(path: bigint): LinkedLeaf | undefined { const index = this.store.getLeafIndex(path); if (index === undefined) { - throw Error("Path not defined"); + return undefined; } const closestLeaf = this.store.getLeaf(index); if (closestLeaf === undefined) { - throw Error("Leaf not defined"); + return undefined; } return { value: Field(closestLeaf.value), @@ -300,14 +300,21 @@ export function createLinkedMerkleTree( * @param path Position of the leaf node. * @param value New value. */ - public setLeaf(path: bigint, value: bigint) { + public setLeaf(path: bigint, value?: bigint) { + if (value === undefined) { + return this.getWitness(path); + } let index = this.store.getLeafIndex(path); const prevLeaf = this.store.getLeafLessOrEqual(path); let witnessPrevious; if (index === undefined) { // The above means the path doesn't already exist, and we are inserting, not updating. // This requires us to update the node with the previous path, as well. - if (this.store.getMaximumIndex() + 1n >= 2 ** height) { + const tempIndex = this.store.getMaximumIndex(); + if (tempIndex === undefined) { + throw Error("Store Max Index not defined"); + } + if (tempIndex + 1n >= 2 ** height) { throw new Error("Index greater than maximum leaf number"); } // eslint-disable-next-line @typescript-eslint/consistent-type-assertions @@ -324,7 +331,8 @@ export function createLinkedMerkleTree( path: Field(newPrevLeaf.path), nextPath: Field(newPrevLeaf.nextPath), }); - index = this.store.getMaximumIndex() + 1n; + + index = tempIndex + 1n; } else { witnessPrevious = this.dummy(); } @@ -355,17 +363,22 @@ export function createLinkedMerkleTree( */ private setLeafInitialisation() { // This is the maximum value of the hash - const MAX_FIELD_VALUE: bigint = BigInt(2 ** 256); + const MAX_FIELD_VALUE: bigint = Field.ORDER - 1n; this.store.setLeaf(0n, { value: 0n, path: 0n, nextPath: MAX_FIELD_VALUE, }); - // We do this to get the Field-ified version of the leaf. - const initialLeaf = this.getLeaf(0n); // We now set the leafs in the merkle tree to cascade the values up // the tree. - this.setMerkleLeaf(0n, initialLeaf); + this.setMerkleLeaf( + 0n, + new LinkedLeaf({ + value: Field(0n), + path: Field(0n), + nextPath: Field(MAX_FIELD_VALUE), + }) + ); } /** @@ -380,7 +393,11 @@ export function createLinkedMerkleTree( let leaf; if (currentIndex === undefined) { - currentIndex = this.store.getMaximumIndex() + 1n; + const storeIndex = this.store.getMaximumIndex(); + if (storeIndex === undefined) { + throw new Error("Store Undefined"); + } + currentIndex = storeIndex + 1n; leaf = new LinkedLeaf({ value: Field(0), path: Field(0), @@ -388,6 +405,9 @@ export function createLinkedMerkleTree( }); } else { leaf = this.getLeaf(path); + if (leaf === undefined) { + throw new Error("Leaf is undefined"); + } } const pathArray = []; diff --git a/packages/common/src/trees/LinkedMerkleTreeStore.ts b/packages/common/src/trees/LinkedMerkleTreeStore.ts index 18b9fe77..7bcbc9ca 100644 --- a/packages/common/src/trees/LinkedMerkleTreeStore.ts +++ b/packages/common/src/trees/LinkedMerkleTreeStore.ts @@ -13,7 +13,7 @@ export interface LinkedMerkleTreeStore extends MerkleTreeStore { getLeafLessOrEqual: (path: bigint) => LinkedLeaf; - getMaximumIndex: () => bigint; + getMaximumIndex: () => bigint | undefined; } export type LinkedLeaf = { value: bigint; path: bigint; nextPath: bigint }; diff --git a/packages/common/test/trees/LinkedMerkleTree.test.ts b/packages/common/test/trees/LinkedMerkleTree.test.ts index e21907db..6bc70e08 100644 --- a/packages/common/test/trees/LinkedMerkleTree.test.ts +++ b/packages/common/test/trees/LinkedMerkleTree.test.ts @@ -6,6 +6,7 @@ import { InMemoryLinkedMerkleTreeStorage, log, } from "../../src"; +import { expectDefined } from "../../dist/utils"; describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { class LinkedMerkleTree extends createLinkedMerkleTree(height) {} @@ -93,13 +94,12 @@ describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { it("should return zeroNode", () => { expect.assertions(3); - const MAX_FIELD_VALUE: bigint = BigInt(2 ** 256); + const MAX_FIELD_VALUE: bigint = Field.ORDER - 1n; const zeroLeaf = tree.getLeaf(0n); + expectDefined(zeroLeaf); expect(zeroLeaf.value.toBigInt()).toStrictEqual(0n); expect(zeroLeaf.path.toBigInt()).toStrictEqual(0n); - expect(zeroLeaf.nextPath.toBigInt()).toStrictEqual( - Field(MAX_FIELD_VALUE).toBigInt() - ); + expect(zeroLeaf.nextPath.toBigInt()).toStrictEqual(MAX_FIELD_VALUE); }); }); diff --git a/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts index dae2a7b7..6b5a3c25 100644 --- a/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts @@ -25,8 +25,8 @@ import { Bool, Field, Poseidon } from "o1js"; import { AreProofsEnabled, log, - RollupMerkleTree, mapSequential, + LinkedMerkleTree, } from "@proto-kit/common"; import { MethodParameterEncoder, @@ -38,8 +38,6 @@ import { import { PendingTransaction } from "../../../mempool/PendingTransaction"; import { CachedStateService } from "../../../state/state/CachedStateService"; import { distinctByString } from "../../../helpers/utils"; -import { CachedMerkleTreeStore } from "../../../state/merkle/CachedMerkleTreeStore"; -import { AsyncMerkleTreeStore } from "../../../state/async/AsyncMerkleTreeStore"; import { TransactionExecutionResult, Block, @@ -48,6 +46,8 @@ import { } from "../../../storage/model/Block"; import { UntypedStateTransition } from "../helpers/UntypedStateTransition"; import type { StateRecord } from "../BatchProducerModule"; +import { CachedLinkedMerkleTreeStore } from "../../../state/merkle/CachedLinkedMerkleTreeStore"; +import { AsyncLinkedMerkleTreeStore } from "../../../state/async/AsyncLinkedMerkleTreeStore"; const errors = { methodIdNotFound: (methodId: string) => @@ -322,8 +322,8 @@ export class TransactionExecutionService { public async generateMetadataForNextBlock( block: Block, - merkleTreeStore: AsyncMerkleTreeStore, - blockHashTreeStore: AsyncMerkleTreeStore, + merkleTreeStore: AsyncLinkedMerkleTreeStore, + blockHashTreeStore: AsyncLinkedMerkleTreeStore, modifyTreeStore = true ): Promise { // Flatten diff list into a single diff by applying them over each other @@ -339,11 +339,11 @@ export class TransactionExecutionService { return Object.assign(accumulator, diff); }, {}); - const inMemoryStore = new CachedMerkleTreeStore(merkleTreeStore); - const tree = new RollupMerkleTree(inMemoryStore); - const blockHashInMemoryStore = new CachedMerkleTreeStore( - blockHashTreeStore - ); + const inMemoryStore = + await CachedLinkedMerkleTreeStore.new(merkleTreeStore); + const tree = new LinkedMerkleTree(inMemoryStore); + const blockHashInMemoryStore = + await CachedLinkedMerkleTreeStore.new(blockHashTreeStore); const blockHashTree = new BlockHashMerkleTree(blockHashInMemoryStore); await inMemoryStore.preloadKeys(Object.keys(combinedDiff).map(BigInt)); @@ -359,7 +359,7 @@ export class TransactionExecutionService { Object.entries(combinedDiff).forEach(([key, state]) => { const treeValue = state !== undefined ? Poseidon.hash(state) : Field(0); - tree.setLeaf(BigInt(key), treeValue); + tree.setLeaf(BigInt(key), treeValue.toBigInt()); }); const stateRoot = tree.getRoot(); diff --git a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts index 4d4b8d65..b666b099 100644 --- a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts @@ -19,7 +19,7 @@ export interface AsyncLinkedMerkleTreeStore { getLeafIndex: (path: bigint) => bigint | undefined; - getMaximumIndex: () => bigint; + getMaximumIndex: () => bigint | undefined; // For the preloadedKeys functionality getLeafByIndex: (index: bigint) => LinkedLeaf | undefined; diff --git a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts index 60bb46bf..27d4c0ff 100644 --- a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts @@ -47,7 +47,7 @@ export class CachedLinkedMerkleTreeStore // load up the first key and the last key. // The last key is to ensure we do not overwrite // any existing paths when we insert a new node/leaf. - if (maxIndex > 0) { + if (maxIndex !== undefined) { const leaf = parent.getLeafByIndex(maxIndex); if (leaf === undefined) { throw Error("Max Path is not defined"); @@ -188,7 +188,7 @@ export class CachedLinkedMerkleTreeStore // to find the closest path are loaded. // A bit repetitive as we basically repeat the process // (without the loading) when we find the closest leaf. - // TODO: see how we could sue a returned value. + // TODO: see how we could use a returned value. public async loadUpKeysForClosestPath(path: bigint): Promise { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions let largestLeaf = this.getLeaf(0n) as LinkedLeaf; diff --git a/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts index 4102ff01..a8d4976e 100644 --- a/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts @@ -31,15 +31,21 @@ export class SyncCachedLinkedMerkleTreeStore extends InMemoryLinkedMerkleTreeSto // Need to make sure we call the parent as the super will usually be empty // The Tree calls this method. public getLeafIndex(path: bigint): bigint | undefined { - return this.parent.getLeafIndex(path); + return super.getLeafIndex(path) ?? this.parent.getLeafIndex(path); } // Need to make sure we call the parent as the super will usually be empty // The tree calls this method. - public getMaximumIndex(): bigint { + public getMaximumIndex(): bigint | undefined { return this.parent.getMaximumIndex(); } + public getLeafLessOrEqual(path: bigint): LinkedLeaf { + return ( + super.getLeafLessOrEqual(path) ?? this.parent.getLeafLessOrEqual(path) + ); + } + public mergeIntoParent() { if (Object.keys(this.leaves).length === 0) { return; From 0fb80ecf1f9c4b8ba63d4f97602303bf3e0ea3ba Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 21 Nov 2024 15:21:29 +0000 Subject: [PATCH 076/107] Update tests after code changes. --- .../merkle/CachedLinkedMerkleStore.test.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts index 10c1e7ff..f68df43c 100644 --- a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts +++ b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts @@ -24,7 +24,7 @@ describe("cached linked merkle store", () => { }); it("should cache multiple keys correctly", async () => { - expect.assertions(10); + expect.assertions(13); tree1.setLeaf(16n, 16n); tree1.setLeaf(46n, 46n); @@ -39,6 +39,10 @@ describe("cached linked merkle store", () => { const leaf1 = tree1.getLeaf(16n); const leaf2 = tree1.getLeaf(46n); + expectDefined(leaf0); + expectDefined(leaf1); + expectDefined(leaf2); + const leaf1Index = cache2.getLeafIndex(16n); const leaf2Index = cache2.getLeafIndex(46n); @@ -91,6 +95,9 @@ describe("cached linked merkle store", () => { const leaf = tree1.getLeaf(5n); const leaf2 = tree2.getLeaf(14n); + expectDefined(leaf); + expectDefined(leaf2); + const leaf5Index = cache2.getLeafIndex(5n); const leaf10Index = cache2.getLeafIndex(10n); const leaf11Index = cache2.getLeafIndex(11n); @@ -143,11 +150,14 @@ describe("cached linked merkle store", () => { // This means 10n will be preloaded and since 100n is its sibling this will be preloaded, too. // Note that the nodes 200n and 300n are not preloaded. await cache2.loadUpKeysForClosestPath(14n); - await tree2.setLeaf(14n, 14n); + tree2.setLeaf(14n, 14n); const leaf = tree1.getLeaf(5n); const leaf2 = tree2.getLeaf(14n); + expectDefined(leaf); + expectDefined(leaf2); + const leaf5Index = cache2.getLeafIndex(5n); const leaf10Index = cache2.getLeafIndex(10n); const leaf100Index = cache2.getLeafIndex(100n); @@ -183,13 +193,14 @@ describe("cached linked merkle store", () => { }); it("should cache correctly", async () => { - expect.assertions(12); + expect.assertions(15); const cache2 = new SyncCachedLinkedMerkleTreeStore(cache1); const tree2 = new LinkedMerkleTree(cache2); const leaf1 = tree2.getLeaf(5n); const leaf1Index = cache2.getLeafIndex(5n); + expectDefined(leaf1); expectDefined(leaf1Index); await expect( mainStore.getNodesAsync([{ key: leaf1Index, level: 0 }]) @@ -201,6 +212,7 @@ describe("cached linked merkle store", () => { const leaf2 = tree2.getLeaf(10n); const leaf2Index = cache2.getLeafIndex(10n); + expectDefined(leaf2); expectDefined(leaf2Index); expect(tree2.getNode(0, leaf2Index).toBigInt()).toBe( Poseidon.hash([leaf2.value, leaf2.path, leaf2.nextPath]).toBigInt() @@ -255,6 +267,7 @@ describe("cached linked merkle store", () => { const index15 = cache2.getLeafIndex(15n); const leaf15 = tree2.getLeaf(15n); + expectDefined(leaf15); expectDefined(index15); expect(tree1.getRoot().toString()).toBe(tree2.getRoot().toString()); expect(tree1.getNode(0, index15).toString()).toBe( From 5eb3f5f704a59aad971e262172d48ae73ed96673 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 21 Nov 2024 19:01:54 +0000 Subject: [PATCH 077/107] Add in more tests --- .../merkle/CachedLinkedMerkleStore.test.ts | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts index f68df43c..f56aa93f 100644 --- a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts +++ b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts @@ -192,6 +192,90 @@ describe("cached linked merkle store", () => { expect(tree1.getRoot()).not.toEqual(tree2.getRoot()); }); + it("mimic transaction execution service", async () => { + expect.assertions(20); + + const cache2 = new SyncCachedLinkedMerkleTreeStore(cache1); + const treeCache1 = new LinkedMerkleTree(cache1); + const treeCache2 = new LinkedMerkleTree(cache2); + + treeCache1.setLeaf(10n, 10n); + treeCache1.setLeaf(20n, 20n); + + treeCache2.setLeaf(7n, 7n); + cache2.mergeIntoParent(); + + const leaves = await cache1.getLeavesAsync([0n, 5n, 7n, 10n, 20n]); + expectDefined(leaves[0]); + expectDefined(leaves[1]); + expectDefined(leaves[2]); + expectDefined(leaves[3]); + expectDefined(leaves[4]); + + expect(leaves[0]).toEqual({ + value: 0n, + path: 0n, + nextPath: 5n, + }); + expect(leaves[1]).toEqual({ + value: 10n, + path: 5n, + nextPath: 7n, + }); + expect(leaves[2]).toEqual({ + value: 7n, + path: 7n, + nextPath: 10n, + }); + expect(leaves[3]).toEqual({ + value: 10n, + path: 10n, + nextPath: 20n, + }); + expect(leaves[4]).toEqual({ + value: 20n, + path: 20n, + nextPath: Field.ORDER - 1n, + }); + + const leaf0Index = cache1.getLeafIndex(0n); + const leaf5Index = cache1.getLeafIndex(5n); + const leaf7Index = cache1.getLeafIndex(7n); + const leaf10Index = cache1.getLeafIndex(10n); + const leaf20Index = cache1.getLeafIndex(20n); + + expectDefined(leaf0Index); + await expect( + cache1.getNodesAsync([{ key: leaf0Index, level: 0 }]) + ).resolves.toStrictEqual([ + Poseidon.hash([Field(0), Field(0), Field(5)]).toBigInt(), + ]); + expectDefined(leaf5Index); + await expect( + cache1.getNodesAsync([{ key: leaf5Index, level: 0 }]) + ).resolves.toStrictEqual([ + Poseidon.hash([Field(10), Field(5), Field(7)]).toBigInt(), + ]); + expectDefined(leaf7Index); + await expect( + cache1.getNodesAsync([{ key: leaf7Index, level: 0 }]) + ).resolves.toStrictEqual([ + Poseidon.hash([Field(7), Field(7), Field(10)]).toBigInt(), + ]); + expectDefined(leaf10Index); + await expect( + cache1.getNodesAsync([{ key: leaf10Index, level: 0 }]) + ).resolves.toStrictEqual([ + Poseidon.hash([Field(10), Field(10), Field(20)]).toBigInt(), + ]); + expectDefined(leaf20Index); + await expect( + cache1.getNodesAsync([{ key: leaf20Index, level: 0 }]) + ).resolves.toStrictEqual([ + Poseidon.hash([Field(20), Field(20), Field(Field.ORDER - 1n)]).toBigInt(), + ]); + }); + it("should cache correctly", async () => { expect.assertions(15); @@ -284,4 +368,77 @@ describe("cached linked merkle store", () => { tree2.getRoot().toString() ); }); + + it("mimic transaction execution service further", async () => { + expect.assertions(16); + + const mStore = new InMemoryAsyncLinkedMerkleTreeStore(); + const mCache = await CachedLinkedMerkleTreeStore.new(mStore); + const mCache2 = new SyncCachedLinkedMerkleTreeStore(mCache); + const treeCache1 = new LinkedMerkleTree(mCache); + const treeCache2 = new LinkedMerkleTree(mCache2); + + treeCache1.setLeaf(10n, 10n); + treeCache1.setLeaf(20n, 20n); + + treeCache2.setLeaf(7n, 7n); + mCache2.mergeIntoParent(); + + const leaves = await mCache.getLeavesAsync([0n, 7n, 10n, 20n]); + expectDefined(leaves[0]); + expectDefined(leaves[1]); + expectDefined(leaves[2]); + expectDefined(leaves[3]); + + expect(leaves[0]).toEqual({ + value: 0n, + path: 0n, + nextPath: 7n, + }); + expect(leaves[1]).toEqual({ + value: 7n, + path: 7n, + nextPath: 10n, + }); + expect(leaves[2]).toEqual({ + value: 10n, + path: 10n, + nextPath: 20n, + }); + expect(leaves[3]).toEqual({ + value: 20n, + path: 20n, + nextPath: Field.ORDER - 1n, + }); + + const leaf0Index = mCache.getLeafIndex(0n); + const leaf7Index = mCache.getLeafIndex(7n); + const leaf10Index = mCache.getLeafIndex(10n); + const leaf20Index = mCache.getLeafIndex(20n); + + expectDefined(leaf0Index); + await expect( + mCache.getNodesAsync([{ key: leaf0Index, level: 0 }]) + ).resolves.toStrictEqual([ + Poseidon.hash([Field(0), Field(0), Field(7)]).toBigInt(), + ]); + expectDefined(leaf7Index); + await expect( + mCache.getNodesAsync([{ key: leaf7Index, level: 0 }]) + ).resolves.toStrictEqual([ + Poseidon.hash([Field(7), Field(7), Field(10)]).toBigInt(), + ]); + expectDefined(leaf10Index); + await expect( + mCache.getNodesAsync([{ key: leaf10Index, level: 0 }]) + ).resolves.toStrictEqual([ + Poseidon.hash([Field(10), Field(10), Field(20)]).toBigInt(), + ]); + expectDefined(leaf20Index); + await expect( + mCache.getNodesAsync([{ key: leaf20Index, level: 0 }]) + ).resolves.toStrictEqual([ + Poseidon.hash([Field(20), Field(20), Field(Field.ORDER - 1n)]).toBigInt(), + ]); + }); }); From 14657c87414aa10f2d7f74d5cad395857c161ac2 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 22 Nov 2024 10:56:48 +0000 Subject: [PATCH 078/107] Change STProver --- .../statetransition/StateTransitionProver.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index 1378a8b3..a06db843 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -196,13 +196,11 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< // Even if we're just reading (rather than writing) then we expect // the path for the current leaf to be populated. Provable.if( - transition.from.isSome, - merkleWitness.leafCurrent.leaf.path.equals(transition.path), - merkleWitness.leafPrevious.leaf.path - .lessThan(transition.path) - .and( - merkleWitness.leafCurrent.leaf.nextPath.greaterThan(transition.path) - ) + merkleWitness.leafPrevious.leaf.nextPath.equals(Field(0)), // nextPath equal to 0 only if it's a dummy., which is when we update + merkleWitness.leafCurrent.leaf.path.equals(transition.path), // update + merkleWitness.leafPrevious.leaf.path.lessThan(transition.path).and( + merkleWitness.leafCurrent.leaf.nextPath.greaterThan(transition.path) // insert + ) ).assertTrue(); // We need to check the sequencer had fetched the correct previousLeaf, @@ -238,7 +236,7 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< // We use the existing state root if it's only an update as the prev leaf // wouldn't have changed and therefore the state root should be the same. Provable.if( - merkleWitness.leafCurrent.leaf.path.equals(0n), + merkleWitness.leafCurrent.leaf.nextPath.equals(0n), // this mens an insert merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( rootWithLeafChanged, Poseidon.hash([Field(0), Field(0), Field(0)]) From fc9768f0fc8bc73be5f887762a52ad3e3d9f817b Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 22 Nov 2024 11:15:17 +0000 Subject: [PATCH 079/107] Change STProver --- .../src/prover/statetransition/StateTransitionProver.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index a06db843..f5458dbf 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -198,9 +198,7 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< Provable.if( merkleWitness.leafPrevious.leaf.nextPath.equals(Field(0)), // nextPath equal to 0 only if it's a dummy., which is when we update merkleWitness.leafCurrent.leaf.path.equals(transition.path), // update - merkleWitness.leafPrevious.leaf.path.lessThan(transition.path).and( - merkleWitness.leafCurrent.leaf.nextPath.greaterThan(transition.path) // insert - ) + merkleWitness.leafPrevious.leaf.path.lessThan(transition.path) // insert ).assertTrue(); // We need to check the sequencer had fetched the correct previousLeaf, From 905cb39d713d03098adf1fa69703f8f0bafa437d Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:09:13 +0000 Subject: [PATCH 080/107] Change STProver --- .../statetransition/StateTransitionProver.ts | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index f5458dbf..aba6abba 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -203,9 +203,20 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< // We need to check the sequencer had fetched the correct previousLeaf, // specifically that the previousLeaf is what is verified. - // We check the stateRoot matches. This doesn't matter - merkleWitness.leafPrevious.merkleWitness - .checkMembershipSimple( + // We check the stateRoot matches. + // For an insert we the prev leaf is not a dummy, + // and for an update the prev leaf is a dummy. + Provable.if( + merkleWitness.leafPrevious.leaf.nextPath.equals(Field(0)), // nextPath equal to 0 only if it's a dummy., which is when we update + merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( + state.stateRoot, + Poseidon.hash([ + merkleWitness.leafCurrent.leaf.value, + merkleWitness.leafCurrent.leaf.path, + merkleWitness.leafCurrent.leaf.nextPath, + ]) + ), + merkleWitness.leafPrevious.merkleWitness.checkMembershipSimple( state.stateRoot, Poseidon.hash([ merkleWitness.leafPrevious.leaf.value, @@ -213,7 +224,7 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< merkleWitness.leafPrevious.leaf.nextPath, ]) ) - .assertTrue(); + ).assertTrue(); // Need to calculate the new state root after the previous leaf is changed. // This is only relevant if it's an insert. If an update, we will just use @@ -234,7 +245,7 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< // We use the existing state root if it's only an update as the prev leaf // wouldn't have changed and therefore the state root should be the same. Provable.if( - merkleWitness.leafCurrent.leaf.nextPath.equals(0n), // this mens an insert + merkleWitness.leafCurrent.leaf.nextPath.equals(0n), // this means an insert merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( rootWithLeafChanged, Poseidon.hash([Field(0), Field(0), Field(0)]) @@ -251,7 +262,7 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< // Compute the new final root. // For an insert we have to hash the new leaf and use the leafPrev's nextPath - // For an update we just use the new value, but keep the leafCurrent.s + // For an update we just use the new value, but keep the leafCurrents // next path the same. const newRoot = Provable.if( merkleWitness.leafCurrent.leaf.path.equals(0n), From 5c11506486c4bf7b95a8d608419727980e050541 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:34:34 +0000 Subject: [PATCH 081/107] Change STProver --- .../prover/statetransition/StateTransitionProver.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index aba6abba..9faf1ae7 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -186,10 +186,9 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< merkleWitness: LinkedMerkleTreeWitness, index = 0 ) { - // The following checks if this is an existing path or non-existing path. - // It won't be an insert (non-existing) if the 'from' is empty. + // The following checks if this is an update or insert // If it's an update then the leafCurrent will be the current leaf, - // rather than the zero leaf if it's an insert. + // rather than the zero/dummy leaf if it's an insert. // If it's an insert then we need to check the leafPrevious is // a valid leaf, i.e. path is less than transition.path and nextPath // greater than transition.path. @@ -198,7 +197,11 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< Provable.if( merkleWitness.leafPrevious.leaf.nextPath.equals(Field(0)), // nextPath equal to 0 only if it's a dummy., which is when we update merkleWitness.leafCurrent.leaf.path.equals(transition.path), // update - merkleWitness.leafPrevious.leaf.path.lessThan(transition.path) // insert + merkleWitness.leafPrevious.leaf.path + .lessThan(transition.path) + .and( + merkleWitness.leafPrevious.leaf.nextPath.greaterThan(transition.path) + ) // insert ).assertTrue(); // We need to check the sequencer had fetched the correct previousLeaf, From 95af2e1e5c4f0f6943a49bfef990a65408aab64d Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:48:39 +0000 Subject: [PATCH 082/107] Update TxTraceService to take witnesses from setLeaf --- .../protocol/production/TransactionTraceService.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/sequencer/src/protocol/production/TransactionTraceService.ts b/packages/sequencer/src/protocol/production/TransactionTraceService.ts index 06121170..3734a192 100644 --- a/packages/sequencer/src/protocol/production/TransactionTraceService.ts +++ b/packages/sequencer/src/protocol/production/TransactionTraceService.ts @@ -272,6 +272,14 @@ export class TransactionTraceService { await merkleStore.preloadKeys(keys.map((key) => key.toBigInt())); + // // TODO: Not efficient. Try to cache. + // for (const stateTransition of stateTransitions) { + // // eslint-disable-next-line no-await-in-loop + // await merkleStore.loadUpKeysForClosestPath( + // stateTransition.path.toBigInt() + // ); + // } + const tree = new LinkedMerkleTree(merkleStore); const runtimeTree = new LinkedMerkleTree(runtimeSimulationMerkleStore); // const runtimeTree = new RollupMerkleTree(merkleStore); @@ -320,10 +328,10 @@ export class TransactionTraceService { const provableTransition = transition.toProvable(); - const witness = usedTree.getWitness(provableTransition.path.toBigInt()); + let witness; if (provableTransition.to.isSome.toBoolean()) { - usedTree.setLeaf( + witness = usedTree.setLeaf( provableTransition.path.toBigInt(), provableTransition.to.value.toBigInt() ); @@ -332,6 +340,8 @@ export class TransactionTraceService { if (StateTransitionType.isProtocol(type)) { protocolStateRoot = stateRoot; } + } else { + witness = usedTree.getWitness(provableTransition.path.toBigInt()); } // Push transition to respective hashlist From 7a85f8a35fb66969382549c5fc453dd3ad2be821 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 22 Nov 2024 13:21:53 +0000 Subject: [PATCH 083/107] Update TxTraceService to pass in undefined --- .../src/protocol/production/TransactionTraceService.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/sequencer/src/protocol/production/TransactionTraceService.ts b/packages/sequencer/src/protocol/production/TransactionTraceService.ts index 3734a192..4b5386d7 100644 --- a/packages/sequencer/src/protocol/production/TransactionTraceService.ts +++ b/packages/sequencer/src/protocol/production/TransactionTraceService.ts @@ -341,7 +341,10 @@ export class TransactionTraceService { protocolStateRoot = stateRoot; } } else { - witness = usedTree.getWitness(provableTransition.path.toBigInt()); + witness = usedTree.setLeaf( + provableTransition.path.toBigInt(), + undefined + ); } // Push transition to respective hashlist From 9601c1f30521f754acd4cf5a0eb2bc3fac476eeb Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 22 Nov 2024 14:18:58 +0000 Subject: [PATCH 084/107] Fix error. --- packages/common/src/trees/LinkedMerkleTree.ts | 2 +- .../src/prover/statetransition/StateTransitionProver.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index aa0878b1..9701b623 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -53,7 +53,7 @@ export interface AbstractLinkedMerkleTree { * @param path of the leaf node. * @param value New value. */ - setLeaf(path: bigint, value: bigint): LinkedMerkleTreeWitness; + setLeaf(path: bigint, value?: bigint): LinkedMerkleTreeWitness; /** * Returns a leaf which lives at a given path. diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index 9faf1ae7..861f083d 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -251,7 +251,7 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< merkleWitness.leafCurrent.leaf.nextPath.equals(0n), // this means an insert merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( rootWithLeafChanged, - Poseidon.hash([Field(0), Field(0), Field(0)]) + Field(0n) ), merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( state.stateRoot, From b908f256dc160e860c95fccbb37f120d26ab9261 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 22 Nov 2024 16:23:09 +0000 Subject: [PATCH 085/107] Fix STProver to handle when we have dummies --- packages/common/src/trees/LinkedMerkleTree.ts | 6 +- packages/common/src/trees/RollupMerkleTree.ts | 2 +- .../statetransition/StateTransitionProver.ts | 124 ++++++++++-------- 3 files changed, 74 insertions(+), 58 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 9701b623..ea745f44 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -442,8 +442,10 @@ export function createLinkedMerkleTree( private dummy(): LinkedLeafAndMerkleWitness { return new LinkedLeafAndMerkleWitness({ merkleWitness: new RollupMerkleTreeWitness({ - path: [], - isLeft: [], + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + path: Array(40).fill(Field(0)) as Field[], + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + isLeft: Array(40).fill(new Bool(true)) as Bool[], }), leaf: new LinkedLeaf({ value: Field(0), diff --git a/packages/common/src/trees/RollupMerkleTree.ts b/packages/common/src/trees/RollupMerkleTree.ts index b8b245d1..32d98acc 100644 --- a/packages/common/src/trees/RollupMerkleTree.ts +++ b/packages/common/src/trees/RollupMerkleTree.ts @@ -347,7 +347,7 @@ export function createMerkleTree(height: number): AbstractMerkleTreeClass { }; } -export class RollupMerkleTree extends createMerkleTree(256) {} +export class RollupMerkleTree extends createMerkleTree(40) {} export class RollupMerkleTreeWitness extends RollupMerkleTree.WITNESS {} /** diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index 861f083d..a246b7b0 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -4,7 +4,7 @@ import { provableMethod, ZkProgrammable, } from "@proto-kit/common"; -import { Field, Poseidon, Provable, SelfProof, ZkProgram } from "o1js"; +import { Bool, Field, Poseidon, Provable, SelfProof, ZkProgram } from "o1js"; import { injectable } from "tsyringe"; import { LinkedMerkleTreeWitness } from "@proto-kit/common/dist/trees/LinkedMerkleTree"; @@ -186,47 +186,57 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< merkleWitness: LinkedMerkleTreeWitness, index = 0 ) { - // The following checks if this is an update or insert - // If it's an update then the leafCurrent will be the current leaf, - // rather than the zero/dummy leaf if it's an insert. - // If it's an insert then we need to check the leafPrevious is - // a valid leaf, i.e. path is less than transition.path and nextPath - // greater than transition.path. - // Even if we're just reading (rather than writing) then we expect - // the path for the current leaf to be populated. Provable.if( - merkleWitness.leafPrevious.leaf.nextPath.equals(Field(0)), // nextPath equal to 0 only if it's a dummy., which is when we update - merkleWitness.leafCurrent.leaf.path.equals(transition.path), // update - merkleWitness.leafPrevious.leaf.path - .lessThan(transition.path) - .and( - merkleWitness.leafPrevious.leaf.nextPath.greaterThan(transition.path) - ) // insert + transition.from.isSome, + // The following checks if this is an update or insert + // If it's an update then the leafCurrent will be the current leaf, + // rather than the zero/dummy leaf if it's an insert. + // If it's an insert then we need to check the leafPrevious is + // a valid leaf, i.e. path is less than transition.path and nextPath + // greater than transition.path. + // Even if we're just reading (rather than writing) then we expect + // the path for the current leaf to be populated. + Provable.if( + merkleWitness.leafPrevious.leaf.nextPath.equals(Field(0)), // nextPath equal to 0 only if it's a dummy., which is when we update + merkleWitness.leafCurrent.leaf.path.equals(transition.path), // update + merkleWitness.leafPrevious.leaf.path + .lessThan(transition.path) + .and( + merkleWitness.leafPrevious.leaf.nextPath.greaterThan( + transition.path + ) + ) // insert + ), + new Bool(true) ).assertTrue(); - // We need to check the sequencer had fetched the correct previousLeaf, - // specifically that the previousLeaf is what is verified. - // We check the stateRoot matches. - // For an insert we the prev leaf is not a dummy, - // and for an update the prev leaf is a dummy. Provable.if( - merkleWitness.leafPrevious.leaf.nextPath.equals(Field(0)), // nextPath equal to 0 only if it's a dummy., which is when we update - merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( - state.stateRoot, - Poseidon.hash([ - merkleWitness.leafCurrent.leaf.value, - merkleWitness.leafCurrent.leaf.path, - merkleWitness.leafCurrent.leaf.nextPath, - ]) + transition.from.isSome, + // We need to check the sequencer had fetched the correct previousLeaf, + // specifically that the previousLeaf is what is verified. + // We check the stateRoot matches. + // For an insert we the prev leaf is not a dummy, + // and for an update the prev leaf is a dummy. + Provable.if( + merkleWitness.leafPrevious.leaf.nextPath.equals(Field(0)), // nextPath equal to 0 only if it's a dummy., which is when we update + merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( + state.stateRoot, + Poseidon.hash([ + merkleWitness.leafCurrent.leaf.value, + merkleWitness.leafCurrent.leaf.path, + merkleWitness.leafCurrent.leaf.nextPath, + ]) + ), + merkleWitness.leafPrevious.merkleWitness.checkMembershipSimple( + state.stateRoot, + Poseidon.hash([ + merkleWitness.leafPrevious.leaf.value, + merkleWitness.leafPrevious.leaf.path, + merkleWitness.leafPrevious.leaf.nextPath, + ]) + ) ), - merkleWitness.leafPrevious.merkleWitness.checkMembershipSimple( - state.stateRoot, - Poseidon.hash([ - merkleWitness.leafPrevious.leaf.value, - merkleWitness.leafPrevious.leaf.path, - merkleWitness.leafPrevious.leaf.nextPath, - ]) - ) + new Bool(true) ).assertTrue(); // Need to calculate the new state root after the previous leaf is changed. @@ -241,26 +251,30 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< ]) ); - // Need to check the second leaf is correct, i.e. leafCurrent. - // is what the sequencer claims it is. - // Again, we check whether we have an update or insert as the value - // depends on this. If insert then we have the current path would be 0. - // We use the existing state root if it's only an update as the prev leaf - // wouldn't have changed and therefore the state root should be the same. Provable.if( - merkleWitness.leafCurrent.leaf.nextPath.equals(0n), // this means an insert - merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( - rootWithLeafChanged, - Field(0n) + transition.from.isSome, + // Need to check the second leaf is correct, i.e. leafCurrent. + // is what the sequencer claims it is. + // Again, we check whether we have an update or insert as the value + // depends on this. If insert then we have the current path would be 0. + // We use the existing state root if it's only an update as the prev leaf + // wouldn't have changed and therefore the state root should be the same. + Provable.if( + merkleWitness.leafCurrent.leaf.nextPath.equals(0n), // this means an insert + merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( + rootWithLeafChanged, + Field(0n) + ), + merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( + state.stateRoot, + Poseidon.hash([ + transition.from.value, + transition.path, + merkleWitness.leafCurrent.leaf.nextPath, + ]) + ) ), - merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( - state.stateRoot, - Poseidon.hash([ - transition.from.value, - transition.path, - merkleWitness.leafCurrent.leaf.nextPath, - ]) - ) + new Bool(true) ).assertTrue(); // Compute the new final root. From dac79761d9ea6aa1eb01ca6ac1402aa77e993155 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:36:37 +0000 Subject: [PATCH 086/107] Circuits rewrite --- docs/sequencer/spec.md | 60 ++++++ packages/common/src/trees/LinkedMerkleTree.ts | 52 +++-- .../common/src/trees/LinkedMerkleTreeStore.ts | 6 +- .../test/trees/LinkedMerkleTree.test.ts | 2 +- .../statetransition/StateTransitionProver.ts | 191 +++++++++--------- .../production/TransactionTraceService.ts | 2 + .../state/async/AsyncLinkedMerkleTreeStore.ts | 6 +- 7 files changed, 190 insertions(+), 129 deletions(-) create mode 100644 docs/sequencer/spec.md diff --git a/docs/sequencer/spec.md b/docs/sequencer/spec.md new file mode 100644 index 00000000..7dfea9dd --- /dev/null +++ b/docs/sequencer/spec.md @@ -0,0 +1,60 @@ +## Merkle Tree Stores + +Object we need to store: +(Nodes, Leaves, MaximumIndex) + +Level 1: +Async stores: (InMemory*, Redis*) + +Schema: +Record + +write +getAsync +getMaximumIndexAsync +getLeafIndexAsync (mapping of path -> leaf index) +getLeafLessOrEqualAsync(path) (gives us either our current leaf or previous leaf in case of insert) + +openTransaction() +commit() +mergeIntoParent() + +( getLeafByIndex ) + +Level 2: +CachedStore: implements Sync, parent: Async + +Sync: +set +getNode +getLeaf(path) => { leaf: LinkedLeaf, index: bigint } +getMaximumIndex +getLeafLessOrEqual(path) => { leaf: LinkedLeaf, index: bigint } + +Cached: +preloadMerkleWitness(index) +preloadKeys(paths: string[]) +mergeIntoParent() + +Level 3: +SyncCachedStore: implements Sync, parent: Sync +mergeIntoParent() + +preLoading: +input: path +``` +const leaf = getLeaf(path) +if(leaf !== undefined) { + super.cache(leaf); + // Update + preloadMerkleWitness(leaf.index); +} else { + // Insert + const previousLeaf = parent.getLeafLessOrEqual(path); + super.cache(previousLeaf); + preloadMerkleWitness(previousLeaf.index); + const maximumIndex = this.preloadAndGetMaximumINndex(); // super.getMaximumINdex() ?? await parent.getMaximumIndexASync() + preloadMerkleWitness(maximumIndex); +} + +``` \ No newline at end of file diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index ea745f44..7723eaf1 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -12,16 +12,20 @@ import { RollupMerkleTreeWitness, } from "./RollupMerkleTree"; -class LinkedLeaf extends Struct({ +export class LinkedLeafStruct extends Struct({ value: Field, path: Field, nextPath: Field, -}) {} +}) { + public hash(): Field { + return Poseidon.hash(LinkedLeafStruct.toFields(this)); + } +} // We use the RollupMerkleTreeWitness here, although we will actually implement // the RollupMerkleTreeWitnessV2 defined below when instantiating the class. export class LinkedLeafAndMerkleWitness extends Struct({ - leaf: LinkedLeaf, + leaf: LinkedLeafStruct, merkleWitness: RollupMerkleTreeWitness, }) {} @@ -61,7 +65,7 @@ export interface AbstractLinkedMerkleTree { * @param path Index of the node. * @returns The data of the leaf. */ - getLeaf(path: bigint): LinkedLeaf | undefined; + getLeaf(path: bigint): LinkedLeafStruct | undefined; /** * Returns the witness (also known as @@ -239,7 +243,7 @@ export function createLinkedMerkleTree( * @param path path of the node. * @returns The data of the node. */ - public getLeaf(path: bigint): LinkedLeaf | undefined { + public getLeaf(path: bigint): LinkedLeafStruct | undefined { const index = this.store.getLeafIndex(path); if (index === undefined) { return undefined; @@ -248,11 +252,11 @@ export function createLinkedMerkleTree( if (closestLeaf === undefined) { return undefined; } - return { + return new LinkedLeafStruct({ value: Field(closestLeaf.value), path: Field(closestLeaf.path), nextPath: Field(closestLeaf.nextPath), - }; + }); } /** @@ -276,7 +280,7 @@ export function createLinkedMerkleTree( * @param index Position of the leaf node. * @param leaf New value. */ - private setMerkleLeaf(index: bigint, leaf: LinkedLeaf) { + private setMerkleLeaf(index: bigint, leaf: LinkedLeafStruct) { this.setNode( 0, index, @@ -326,11 +330,14 @@ export function createLinkedMerkleTree( nextPath: path, }; this.store.setLeaf(prevLeafIndex, newPrevLeaf); - this.setMerkleLeaf(prevLeafIndex, { - value: Field(newPrevLeaf.value), - path: Field(newPrevLeaf.path), - nextPath: Field(newPrevLeaf.nextPath), - }); + this.setMerkleLeaf( + prevLeafIndex, + new LinkedLeafStruct({ + value: Field(newPrevLeaf.value), + path: Field(newPrevLeaf.path), + nextPath: Field(newPrevLeaf.nextPath), + }) + ); index = tempIndex + 1n; } else { @@ -346,11 +353,14 @@ export function createLinkedMerkleTree( }; const witnessNext = this.getWitness(newLeaf.path); this.store.setLeaf(index, newLeaf); - this.setMerkleLeaf(index, { - value: Field(newLeaf.value), - path: Field(newLeaf.path), - nextPath: Field(newLeaf.nextPath), - }); + this.setMerkleLeaf( + index, + new LinkedLeafStruct({ + value: Field(newLeaf.value), + path: Field(newLeaf.path), + nextPath: Field(newLeaf.nextPath), + }) + ); return new LinkedMerkleWitness({ leafPrevious: witnessPrevious, leafCurrent: witnessNext.leafCurrent, @@ -373,7 +383,7 @@ export function createLinkedMerkleTree( // the tree. this.setMerkleLeaf( 0n, - new LinkedLeaf({ + new LinkedLeafStruct({ value: Field(0n), path: Field(0n), nextPath: Field(MAX_FIELD_VALUE), @@ -398,7 +408,7 @@ export function createLinkedMerkleTree( throw new Error("Store Undefined"); } currentIndex = storeIndex + 1n; - leaf = new LinkedLeaf({ + leaf = new LinkedLeafStruct({ value: Field(0), path: Field(0), nextPath: Field(0), @@ -447,7 +457,7 @@ export function createLinkedMerkleTree( // eslint-disable-next-line @typescript-eslint/consistent-type-assertions isLeft: Array(40).fill(new Bool(true)) as Bool[], }), - leaf: new LinkedLeaf({ + leaf: new LinkedLeafStruct({ value: Field(0), path: Field(0), nextPath: Field(0), diff --git a/packages/common/src/trees/LinkedMerkleTreeStore.ts b/packages/common/src/trees/LinkedMerkleTreeStore.ts index 7bcbc9ca..7bf19a2f 100644 --- a/packages/common/src/trees/LinkedMerkleTreeStore.ts +++ b/packages/common/src/trees/LinkedMerkleTreeStore.ts @@ -7,11 +7,11 @@ export interface LinkedMerkleTreeStore extends MerkleTreeStore { setLeaf: (index: bigint, value: LinkedLeaf) => void; - getLeaf: (index: bigint) => LinkedLeaf | undefined; + getLeaf: (path: bigint) => { leaf: LinkedLeaf; index: bigint } | undefined; - getLeafIndex: (path: bigint) => bigint | undefined; + // getLeafIndex: (path: bigint) => bigint | undefined; - getLeafLessOrEqual: (path: bigint) => LinkedLeaf; + getLeafLessOrEqual: (path: bigint) => { leaf: LinkedLeaf; index: bigint }; getMaximumIndex: () => bigint | undefined; } diff --git a/packages/common/test/trees/LinkedMerkleTree.test.ts b/packages/common/test/trees/LinkedMerkleTree.test.ts index 6bc70e08..a6d4e5e3 100644 --- a/packages/common/test/trees/LinkedMerkleTree.test.ts +++ b/packages/common/test/trees/LinkedMerkleTree.test.ts @@ -93,7 +93,7 @@ describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { }); it("should return zeroNode", () => { - expect.assertions(3); + expect.assertions(4); const MAX_FIELD_VALUE: bigint = Field.ORDER - 1n; const zeroLeaf = tree.getLeaf(0n); expectDefined(zeroLeaf); diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index a246b7b0..b18865f4 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -1,10 +1,11 @@ import { AreProofsEnabled, + LinkedLeafStruct, PlainZkProgram, provableMethod, ZkProgrammable, } from "@proto-kit/common"; -import { Bool, Field, Poseidon, Provable, SelfProof, ZkProgram } from "o1js"; +import { Bool, Field, Provable, SelfProof, ZkProgram } from "o1js"; import { injectable } from "tsyringe"; import { LinkedMerkleTreeWitness } from "@proto-kit/common/dist/trees/LinkedMerkleTree"; @@ -186,119 +187,109 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< merkleWitness: LinkedMerkleTreeWitness, index = 0 ) { - Provable.if( - transition.from.isSome, - // The following checks if this is an update or insert - // If it's an update then the leafCurrent will be the current leaf, - // rather than the zero/dummy leaf if it's an insert. - // If it's an insert then we need to check the leafPrevious is - // a valid leaf, i.e. path is less than transition.path and nextPath - // greater than transition.path. - // Even if we're just reading (rather than writing) then we expect - // the path for the current leaf to be populated. - Provable.if( - merkleWitness.leafPrevious.leaf.nextPath.equals(Field(0)), // nextPath equal to 0 only if it's a dummy., which is when we update - merkleWitness.leafCurrent.leaf.path.equals(transition.path), // update - merkleWitness.leafPrevious.leaf.path - .lessThan(transition.path) - .and( - merkleWitness.leafPrevious.leaf.nextPath.greaterThan( - transition.path - ) - ) // insert - ), - new Bool(true) - ).assertTrue(); - - Provable.if( - transition.from.isSome, - // We need to check the sequencer had fetched the correct previousLeaf, - // specifically that the previousLeaf is what is verified. - // We check the stateRoot matches. - // For an insert we the prev leaf is not a dummy, - // and for an update the prev leaf is a dummy. - Provable.if( - merkleWitness.leafPrevious.leaf.nextPath.equals(Field(0)), // nextPath equal to 0 only if it's a dummy., which is when we update - merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( - state.stateRoot, - Poseidon.hash([ - merkleWitness.leafCurrent.leaf.value, - merkleWitness.leafCurrent.leaf.path, - merkleWitness.leafCurrent.leaf.nextPath, - ]) - ), - merkleWitness.leafPrevious.merkleWitness.checkMembershipSimple( - state.stateRoot, - Poseidon.hash([ - merkleWitness.leafPrevious.leaf.value, - merkleWitness.leafPrevious.leaf.path, - merkleWitness.leafPrevious.leaf.nextPath, - ]) - ) - ), - new Bool(true) - ).assertTrue(); + const isUpdate = merkleWitness.leafPrevious.leaf.nextPath.equals(Field(0)); + + const isDummy = transition.path.equals(0); + const isNotDummy = isDummy.not(); + + // The following checks if this is an update or insert + // If it's an update then the leafCurrent will be the current leaf, + // rather than the zero/dummy leaf if it's an insert. + // If it's an insert then we need to check the leafPrevious is + // a valid leaf, i.e. path is less than transition.path and nextPath + // greater than transition.path. + // Even if we're just reading (rather than writing) then we expect + // the path for the current leaf to be populated. + const pathValid = Provable.if( + isUpdate, // nextPath equal to 0 only if it's a dummy., which is when we update + merkleWitness.leafCurrent.leaf.path.equals(transition.path), // update + merkleWitness.leafPrevious.leaf.path + .lessThan(transition.path) + .and( + merkleWitness.leafPrevious.leaf.nextPath.greaterThan(transition.path) + ) // insert + ); + // This is for dummy STs + Provable.if(isNotDummy, pathValid, new Bool(true)).assertTrue(); + + const previousWitnessValid = + merkleWitness.leafPrevious.merkleWitness.checkMembershipSimple( + state.stateRoot, + merkleWitness.leafPrevious.leaf.hash() + ); + + // We need to check the sequencer had fetched the correct previousLeaf, + // specifically that the previousLeaf is what is verified. + // We check the stateRoot matches. + // For an insert we the prev leaf is not a dummy, + // and for an update the prev leaf is a dummy. + + // We assert that the previous witness is valid in case of this one being an update + Provable.if(isNotDummy, previousWitnessValid, Bool(true)).assertTrue(); // Need to calculate the new state root after the previous leaf is changed. // This is only relevant if it's an insert. If an update, we will just use // the existing state root. const rootWithLeafChanged = merkleWitness.leafPrevious.merkleWitness.calculateRoot( - Poseidon.hash([ - merkleWitness.leafPrevious.leaf.value, - merkleWitness.leafPrevious.leaf.path, - transition.path, - ]) + new LinkedLeafStruct({ + value: merkleWitness.leafPrevious.leaf.value, + path: merkleWitness.leafPrevious.leaf.path, + nextPath: transition.path, + }).hash() ); - Provable.if( - transition.from.isSome, - // Need to check the second leaf is correct, i.e. leafCurrent. - // is what the sequencer claims it is. - // Again, we check whether we have an update or insert as the value - // depends on this. If insert then we have the current path would be 0. - // We use the existing state root if it's only an update as the prev leaf - // wouldn't have changed and therefore the state root should be the same. - Provable.if( - merkleWitness.leafCurrent.leaf.nextPath.equals(0n), // this means an insert - merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( - rootWithLeafChanged, - Field(0n) - ), - merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( - state.stateRoot, - Poseidon.hash([ - transition.from.value, - transition.path, - merkleWitness.leafCurrent.leaf.nextPath, - ]) - ) - ), - new Bool(true) - ).assertTrue(); + const rootAfterFirstStep = Provable.if( + isUpdate, + rootWithLeafChanged, + state.stateRoot + ); + + // Need to check the second leaf is correct, i.e. leafCurrent. + // is what the sequencer claims it is. + // Again, we check whether we have an update or insert as the value + // depends on this. If insert then we have the current path would be 0. + // We use the existing state root if it's only an update as the prev leaf + // wouldn't have changed and therefore the state root should be the same. + const currentWitnessLeaf = Provable.if( + isUpdate, + new LinkedLeafStruct({ + value: transition.from.value, + path: transition.path, + nextPath: merkleWitness.leafCurrent.leaf.nextPath, + }).hash(), + Field(0) + ); + const currentWitnessValid = + merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( + rootAfterFirstStep, + currentWitnessLeaf + ); + + Provable.if(isNotDummy, currentWitnessValid, Bool(true)).assertTrue(); // Compute the new final root. // For an insert we have to hash the new leaf and use the leafPrev's nextPath // For an update we just use the new value, but keep the leafCurrents // next path the same. - const newRoot = Provable.if( - merkleWitness.leafCurrent.leaf.path.equals(0n), - merkleWitness.leafCurrent.merkleWitness.calculateRoot( - Poseidon.hash([ - transition.to.value, - transition.path, - merkleWitness.leafPrevious.leaf.nextPath, - ]) - ), - merkleWitness.leafCurrent.merkleWitness.calculateRoot( - Poseidon.hash([ - transition.from.value, - transition.path, - merkleWitness.leafCurrent.leaf.nextPath, - ]) - ) + const newCurrentNextPath = Provable.if( + isUpdate, + merkleWitness.leafCurrent.leaf.nextPath, + merkleWitness.leafPrevious.leaf.nextPath ); + const newCurrentLeaf = new LinkedLeafStruct({ + value: transition.to.value, + path: transition.path, + nextPath: newCurrentNextPath, + }); + + const newRoot = merkleWitness.leafCurrent.merkleWitness.calculateRoot( + newCurrentLeaf.hash() + ); + + // TODO Make sure that path == 0 -> both isSomes == false + // This is checking if we have a read or write. // If a read the state root should stay the same. state.stateRoot = Provable.if( @@ -315,8 +306,6 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< state.protocolStateRoot ); - const isNotDummy = transition.path.equals(Field(0)).not(); - state.stateTransitionList.pushIf( transition, isNotDummy.and(type.isNormal()) diff --git a/packages/sequencer/src/protocol/production/TransactionTraceService.ts b/packages/sequencer/src/protocol/production/TransactionTraceService.ts index 4b5386d7..f0803130 100644 --- a/packages/sequencer/src/protocol/production/TransactionTraceService.ts +++ b/packages/sequencer/src/protocol/production/TransactionTraceService.ts @@ -266,6 +266,8 @@ export class TransactionTraceService { }> { const keys = this.allKeys(protocolTransitions.concat(stateTransitions)); + // TODO Consolidate + await merkleStore.preloadKey(0n); const runtimeSimulationMerkleStore = new SyncCachedLinkedMerkleTreeStore( merkleStore ); diff --git a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts index b666b099..ef8d10f6 100644 --- a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts @@ -17,10 +17,10 @@ export interface AsyncLinkedMerkleTreeStore { getLeavesAsync: (paths: bigint[]) => Promise<(LinkedLeaf | undefined)[]>; - getLeafIndex: (path: bigint) => bigint | undefined; + getLeafIndex: (path: bigint) => Promise; - getMaximumIndex: () => bigint | undefined; + getMaximumIndex: () => Promise; // For the preloadedKeys functionality - getLeafByIndex: (index: bigint) => LinkedLeaf | undefined; + getLeafByIndex: (index: bigint) => Promise; } From 4265bac8cfcd3a1516798c5cdb05b6c6ea117a1f Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Mon, 25 Nov 2024 19:50:25 +0000 Subject: [PATCH 087/107] Fix STProver with extra clause. --- .../statetransition/StateTransitionProver.ts | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index b18865f4..b974fa3a 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -212,20 +212,39 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< // This is for dummy STs Provable.if(isNotDummy, pathValid, new Bool(true)).assertTrue(); + // Only if we're doing an insert is this valid. const previousWitnessValid = merkleWitness.leafPrevious.merkleWitness.checkMembershipSimple( state.stateRoot, merkleWitness.leafPrevious.leaf.hash() ); + // Only if we're doing an update. + const currentWitnessHolds = + merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( + state.stateRoot, + merkleWitness.leafCurrent.leaf.hash() + ); + + // Combine previousWitnessValid and currentWitnessHolds + const prevWitnessOrCurrentWitness = Provable.if( + isUpdate, + currentWitnessHolds, + previousWitnessValid + ); + // We need to check the sequencer had fetched the correct previousLeaf, // specifically that the previousLeaf is what is verified. // We check the stateRoot matches. - // For an insert we the prev leaf is not a dummy, + // For an insert the prev leaf is not a dummy, // and for an update the prev leaf is a dummy. // We assert that the previous witness is valid in case of this one being an update - Provable.if(isNotDummy, previousWitnessValid, Bool(true)).assertTrue(); + Provable.if( + isNotDummy, + prevWitnessOrCurrentWitness, + Bool(true) + ).assertTrue(); // Need to calculate the new state root after the previous leaf is changed. // This is only relevant if it's an insert. If an update, we will just use From 585bf0e67ba2e4f654d7513dcad3dd0927ea9262 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:48:15 +0000 Subject: [PATCH 088/107] Refactoring. --- docs/sequencer/spec.md | 24 ++- packages/common/src/index.ts | 2 +- .../src/trees/InMemoryLinkedLeafStore.ts | 46 +++++ .../trees/InMemoryLinkedMerkleTreeStorage.ts | 66 ------- .../src/trees/InMemoryMerkleTreeStorage.ts | 2 +- packages/common/src/trees/LinkedMerkleTree.ts | 70 ++++---- .../common/src/trees/LinkedMerkleTreeStore.ts | 12 +- .../state/async/AsyncLinkedMerkleTreeStore.ts | 14 +- .../merkle/CachedLinkedMerkleTreeStore.ts | 168 +++++++++--------- .../merkle/SyncCachedLinkedMerkleTreeStore.ts | 51 +++--- .../InMemoryAsyncLinkedMerkleTreeStore.ts | 52 +++--- 11 files changed, 250 insertions(+), 257 deletions(-) create mode 100644 packages/common/src/trees/InMemoryLinkedLeafStore.ts delete mode 100644 packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts diff --git a/docs/sequencer/spec.md b/docs/sequencer/spec.md index 7dfea9dd..c74e9d2a 100644 --- a/docs/sequencer/spec.md +++ b/docs/sequencer/spec.md @@ -12,12 +12,10 @@ Record write getAsync getMaximumIndexAsync -getLeafIndexAsync (mapping of path -> leaf index) getLeafLessOrEqualAsync(path) (gives us either our current leaf or previous leaf in case of insert) openTransaction() commit() -mergeIntoParent() ( getLeafByIndex ) @@ -43,7 +41,7 @@ mergeIntoParent() preLoading: input: path ``` -const leaf = getLeaf(path) +const leaf = parent.getLeaf(path) if(leaf !== undefined) { super.cache(leaf); // Update @@ -57,4 +55,22 @@ if(leaf !== undefined) { preloadMerkleWitness(maximumIndex); } -``` \ No newline at end of file +``` + +Sync interface: +Union of LinkedMerkleTreeStore (rename to LinkedLeafStore) + MerkleTreeStore + +Async +Level 1 methods + +InMemoryLeafStore - subset that does leafs + maximumindex +InMemoryMerkleStore - subset that does only merkle nodes + +-> future Redis + +InMemoryAsyncLinkedMerkleTreeStore - implements Async +uses inmemory implementations + + + + diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index a370acf3..1a24aacb 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -17,6 +17,6 @@ export * from "./trees/LinkedMerkleTreeStore"; export * from "./trees/InMemoryMerkleTreeStorage"; export * from "./trees/RollupMerkleTree"; export * from "./trees/LinkedMerkleTree"; -export * from "./trees/InMemoryLinkedMerkleTreeStorage"; +export * from "./trees/InMemoryLinkedLeafStore"; export * from "./events/EventEmitterProxy"; export * from "./trees/MockAsyncMerkleStore"; diff --git a/packages/common/src/trees/InMemoryLinkedLeafStore.ts b/packages/common/src/trees/InMemoryLinkedLeafStore.ts new file mode 100644 index 00000000..4719fb68 --- /dev/null +++ b/packages/common/src/trees/InMemoryLinkedLeafStore.ts @@ -0,0 +1,46 @@ +import { LinkedLeafStore, LinkedLeaf } from "./LinkedMerkleTreeStore"; + +export class InMemoryLinkedLeafStore implements LinkedLeafStore { + public leaves: { + [key: string]: { leaf: LinkedLeaf; index: bigint }; + } = {}; + + public maximumIndex?: bigint; + + public getLeaf( + path: bigint + ): { leaf: LinkedLeaf; index: bigint } | undefined { + return this.leaves[path.toString()]; + } + + public setLeaf(index: bigint, value: LinkedLeaf): void { + const leaf = this.getLeaf(value.path); + if (leaf !== undefined && leaf?.index !== index) { + throw new Error("Cannot change index of existing leaf"); + } + this.leaves[value.path.toString()] = { leaf: value, index: index }; + if (this.maximumIndex === undefined || index > this.maximumIndex) { + this.maximumIndex = index; + } + } + + public getMaximumIndex(): bigint | undefined { + return this.maximumIndex; + } + + // This gets the leaf with the closest path. + public getLeafLessOrEqual(path: bigint): { leaf: LinkedLeaf; index: bigint } { + let largestLeaf = this.getLeaf(0n); + if (largestLeaf === undefined) { + throw new Error("Path 0n should always be defined"); + } + while (largestLeaf.leaf.nextPath <= path) { + const nextLeaf = this.getLeaf(largestLeaf.leaf.nextPath); + if (nextLeaf === undefined) { + throw new Error("Next Path should always be defined"); + } + largestLeaf = nextLeaf; + } + return largestLeaf; + } +} diff --git a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts b/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts deleted file mode 100644 index 4d1c9313..00000000 --- a/packages/common/src/trees/InMemoryLinkedMerkleTreeStorage.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { LinkedMerkleTreeStore, LinkedLeaf } from "./LinkedMerkleTreeStore"; - -export class InMemoryLinkedMerkleTreeStorage implements LinkedMerkleTreeStore { - protected nodes: { - [key: number]: { - [key: string]: bigint; - }; - } = {}; - - protected leaves: { - [key: string]: LinkedLeaf; - } = {}; - - public getNode(index: bigint, level: number): bigint | undefined { - return this.nodes[level]?.[index.toString()]; - } - - public setNode(index: bigint, level: number, value: bigint): void { - (this.nodes[level] ??= {})[index.toString()] = value; - } - - public getLeaf(index: bigint): LinkedLeaf | undefined { - return this.leaves[index.toString()]; - } - - public setLeaf(index: bigint, value: LinkedLeaf): void { - this.leaves[index.toString()] = value; - } - - public getLeafIndex(path: bigint): bigint | undefined { - const leafIndex = Object.keys(this.leaves).find((key) => { - return this.leaves[key].path === path; - }); - if (leafIndex === undefined) { - return undefined; - } - return BigInt(leafIndex); - } - - public getMaximumIndex(): bigint | undefined { - if (Object.keys(this.leaves).length === 0) { - return undefined; - } - let max = 0n; - Object.keys(this.leaves).forEach((x) => { - const key = BigInt(x); - if (key > max) { - max = key; - } - }); - return max; - } - - // This gets the leaf with the closest path. - public getLeafLessOrEqual(path: bigint): LinkedLeaf { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - let largestLeaf = this.getLeaf(0n) as LinkedLeaf; - while (largestLeaf.nextPath <= path) { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const nextIndex = this.getLeafIndex(largestLeaf.nextPath) as bigint; - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - largestLeaf = this.getLeaf(nextIndex) as LinkedLeaf; - } - return largestLeaf; - } -} diff --git a/packages/common/src/trees/InMemoryMerkleTreeStorage.ts b/packages/common/src/trees/InMemoryMerkleTreeStorage.ts index 390b87fc..c042c0d3 100644 --- a/packages/common/src/trees/InMemoryMerkleTreeStorage.ts +++ b/packages/common/src/trees/InMemoryMerkleTreeStorage.ts @@ -1,7 +1,7 @@ import { MerkleTreeStore } from "./MerkleTreeStore"; export class InMemoryMerkleTreeStorage implements MerkleTreeStore { - protected nodes: { + public nodes: { [key: number]: { [key: string]: bigint; }; diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 7723eaf1..efdba697 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -1,11 +1,15 @@ // eslint-disable-next-line max-classes-per-file import { Bool, Field, Poseidon, Provable, Struct } from "o1js"; +import { InMemoryAsyncLinkedMerkleTreeStore } from "@proto-kit/sequencer/dist/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore"; +import { CachedLinkedMerkleTreeStore } from "@proto-kit/sequencer/dist/state/merkle/CachedLinkedMerkleTreeStore"; import { TypedClass } from "../types"; import { range } from "../utils"; -import { LinkedMerkleTreeStore } from "./LinkedMerkleTreeStore"; -import { InMemoryLinkedMerkleTreeStorage } from "./InMemoryLinkedMerkleTreeStorage"; +import { + LinkedLeafStore, + LinkedMerkleTreeStore, +} from "./LinkedMerkleTreeStore"; import { AbstractMerkleWitness, maybeSwap, @@ -37,7 +41,7 @@ class LinkedStructTemplate extends Struct({ export interface AbstractLinkedMerkleWitness extends LinkedStructTemplate {} export interface AbstractLinkedMerkleTree { - store: LinkedMerkleTreeStore; + store: LinkedLeafStore; /** * Returns a node which lives at a given index and level. * @param level Level of the node. @@ -78,7 +82,7 @@ export interface AbstractLinkedMerkleTree { } export interface AbstractLinkedMerkleTreeClass { - new (store: LinkedMerkleTreeStore): AbstractLinkedMerkleTree; + new (store: LinkedLeafStore): AbstractLinkedMerkleTree; WITNESS: TypedClass & typeof LinkedStructTemplate; @@ -200,7 +204,9 @@ export function createLinkedMerkleTree( public static HEIGHT = height; public static EMPTY_ROOT = new AbstractLinkedRollupMerkleTree( - new InMemoryLinkedMerkleTreeStorage() + await CachedLinkedMerkleTreeStore.new( + InMemoryAsyncLinkedMerkleTreeStore() + ) ) .getRoot() .toBigInt(); @@ -244,18 +250,14 @@ export function createLinkedMerkleTree( * @returns The data of the node. */ public getLeaf(path: bigint): LinkedLeafStruct | undefined { - const index = this.store.getLeafIndex(path); - if (index === undefined) { - return undefined; - } - const closestLeaf = this.store.getLeaf(index); - if (closestLeaf === undefined) { + const storedLeaf = this.store.getLeaf(path); + if (storedLeaf === undefined) { return undefined; } return new LinkedLeafStruct({ - value: Field(closestLeaf.value), - path: Field(closestLeaf.path), - nextPath: Field(closestLeaf.nextPath), + value: Field(storedLeaf.leaf.value), + path: Field(storedLeaf.leaf.path), + nextPath: Field(storedLeaf.leaf.nextPath), }); } @@ -308,10 +310,11 @@ export function createLinkedMerkleTree( if (value === undefined) { return this.getWitness(path); } - let index = this.store.getLeafIndex(path); + const storedLeaf = this.store.getLeaf(path); const prevLeaf = this.store.getLeafLessOrEqual(path); let witnessPrevious; - if (index === undefined) { + let index: bigint; + if (storedLeaf === undefined) { // The above means the path doesn't already exist, and we are inserting, not updating. // This requires us to update the node with the previous path, as well. const tempIndex = this.store.getMaximumIndex(); @@ -321,17 +324,15 @@ export function createLinkedMerkleTree( if (tempIndex + 1n >= 2 ** height) { throw new Error("Index greater than maximum leaf number"); } - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const prevLeafIndex = this.store.getLeafIndex(prevLeaf.path) as bigint; - witnessPrevious = this.getWitness(prevLeaf.path).leafCurrent; + witnessPrevious = this.getWitness(prevLeaf.leaf.path).leafCurrent; const newPrevLeaf = { - value: prevLeaf.value, - path: prevLeaf.path, - nextPath: path, + value: prevLeaf.leaf.value, + path: prevLeaf.leaf.path, + nextPath: prevLeaf.leaf.path, }; - this.store.setLeaf(prevLeafIndex, newPrevLeaf); + this.store.setLeaf(prevLeaf.index, newPrevLeaf); this.setMerkleLeaf( - prevLeafIndex, + prevLeaf.index, new LinkedLeafStruct({ value: Field(newPrevLeaf.value), path: Field(newPrevLeaf.path), @@ -342,14 +343,12 @@ export function createLinkedMerkleTree( index = tempIndex + 1n; } else { witnessPrevious = this.dummy(); + index = storedLeaf.index; } - // The following sets a default for the previous value - // TODO: How to handle this better. - const newLeaf = { value: value, path: path, - nextPath: prevLeaf.nextPath, + nextPath: prevLeaf.leaf.nextPath, }; const witnessNext = this.getWitness(newLeaf.path); this.store.setLeaf(index, newLeaf); @@ -399,10 +398,11 @@ export function createLinkedMerkleTree( * @returns The witness that belongs to the leaf. */ public getWitness(path: bigint): LinkedMerkleWitness { - let currentIndex = this.store.getLeafIndex(path); + const storedLeaf = this.store.getLeaf(path); let leaf; + let currentIndex: bigint; - if (currentIndex === undefined) { + if (storedLeaf === undefined) { const storeIndex = this.store.getMaximumIndex(); if (storeIndex === undefined) { throw new Error("Store Undefined"); @@ -414,10 +414,12 @@ export function createLinkedMerkleTree( nextPath: Field(0), }); } else { - leaf = this.getLeaf(path); - if (leaf === undefined) { - throw new Error("Leaf is undefined"); - } + leaf = new LinkedLeafStruct({ + value: Field(storedLeaf.leaf.value), + path: Field(storedLeaf.leaf.path), + nextPath: Field(storedLeaf.leaf.nextPath), + }); + currentIndex = storedLeaf.index; } const pathArray = []; diff --git a/packages/common/src/trees/LinkedMerkleTreeStore.ts b/packages/common/src/trees/LinkedMerkleTreeStore.ts index 7bf19a2f..dae7710d 100644 --- a/packages/common/src/trees/LinkedMerkleTreeStore.ts +++ b/packages/common/src/trees/LinkedMerkleTreeStore.ts @@ -1,19 +1,17 @@ import { MerkleTreeStore } from "./MerkleTreeStore"; -export interface LinkedMerkleTreeStore extends MerkleTreeStore { - setNode: (index: bigint, level: number, value: bigint) => void; - - getNode: (index: bigint, level: number) => bigint | undefined; - +export interface LinkedLeafStore { setLeaf: (index: bigint, value: LinkedLeaf) => void; getLeaf: (path: bigint) => { leaf: LinkedLeaf; index: bigint } | undefined; - // getLeafIndex: (path: bigint) => bigint | undefined; - getLeafLessOrEqual: (path: bigint) => { leaf: LinkedLeaf; index: bigint }; getMaximumIndex: () => bigint | undefined; } export type LinkedLeaf = { value: bigint; path: bigint; nextPath: bigint }; + +export interface LinkedMerkleTreeStore + extends LinkedLeafStore, + MerkleTreeStore {} diff --git a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts index ef8d10f6..7ec4f952 100644 --- a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts @@ -2,6 +2,8 @@ import { LinkedLeaf } from "@proto-kit/common"; import { MerkleTreeNode, MerkleTreeNodeQuery } from "./AsyncMerkleTreeStore"; +export type StoredLeaf = { leaf: LinkedLeaf; index: bigint }; + export interface AsyncLinkedMerkleTreeStore { openTransaction: () => Promise; @@ -9,18 +11,16 @@ export interface AsyncLinkedMerkleTreeStore { writeNodes: (nodes: MerkleTreeNode[]) => void; - writeLeaves: (leaves: [string, LinkedLeaf][]) => void; + writeLeaves: (leaves: StoredLeaf[]) => void; getNodesAsync: ( nodes: MerkleTreeNodeQuery[] ) => Promise<(bigint | undefined)[]>; - getLeavesAsync: (paths: bigint[]) => Promise<(LinkedLeaf | undefined)[]>; - - getLeafIndex: (path: bigint) => Promise; + getLeavesAsync: (paths: bigint[]) => Promise<(StoredLeaf | undefined)[]>; - getMaximumIndex: () => Promise; + getMaximumIndexAsync: () => Promise; - // For the preloadedKeys functionality - getLeafByIndex: (index: bigint) => Promise; + // Doesn't return undefined as there should always be at least one leaf. + getLeafLessOrEqualAsync: (path: bigint) => Promise; } diff --git a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts index 27d4c0ff..8f988111 100644 --- a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts @@ -1,8 +1,9 @@ import { log, - noop, - InMemoryLinkedMerkleTreeStorage, + InMemoryLinkedLeafStore, LinkedLeaf, + InMemoryMerkleTreeStorage, + LinkedMerkleTreeStore, } from "@proto-kit/common"; import { @@ -11,10 +12,7 @@ import { } from "../async/AsyncMerkleTreeStore"; import { AsyncLinkedMerkleTreeStore } from "../async/AsyncLinkedMerkleTreeStore"; -export class CachedLinkedMerkleTreeStore - extends InMemoryLinkedMerkleTreeStorage - implements AsyncLinkedMerkleTreeStore -{ +export class CachedLinkedMerkleTreeStore implements LinkedMerkleTreeStore { private writeCache: { nodes: { [key: number]: { @@ -22,46 +20,27 @@ export class CachedLinkedMerkleTreeStore }; }; leaves: { - [key: string]: LinkedLeaf; + [key: string]: { leaf: LinkedLeaf; index: bigint }; }; } = { nodes: {}, leaves: {} }; - public async openTransaction(): Promise { - noop(); - } + private readonly leafStore = new InMemoryLinkedLeafStore(); - public async commit(): Promise { - noop(); - } + private readonly nodeStore = new InMemoryMerkleTreeStorage(); - private constructor(private readonly parent: AsyncLinkedMerkleTreeStore) { - super(); - } + private constructor(private readonly parent: AsyncLinkedMerkleTreeStore) {} public static async new( parent: AsyncLinkedMerkleTreeStore ): Promise { const cachedInstance = new CachedLinkedMerkleTreeStore(parent); - const maxIndex = parent.getMaximumIndex(); - // If the parent is populated then we - // load up the first key and the last key. - // The last key is to ensure we do not overwrite - // any existing paths when we insert a new node/leaf. - if (maxIndex !== undefined) { - const leaf = parent.getLeafByIndex(maxIndex); - if (leaf === undefined) { - throw Error("Max Path is not defined"); - } - await cachedInstance.preloadKeys([0n, leaf.path]); - } else { - await cachedInstance.preloadKeys([0n]); - } + await cachedInstance.preloadMaximumIndex(); return cachedInstance; } // This gets the nodes from the in memory store (which looks also to be the cache). public getNode(key: bigint, level: number): bigint | undefined { - return super.getNode(key, level); + return this.nodeStore.getNode(key, level); } // This gets the nodes from the in memory store. @@ -97,7 +76,7 @@ export class CachedLinkedMerkleTreeStore // This sets the nodes in the cache and in the in-memory tree. public setNode(key: bigint, level: number, value: bigint) { - super.setNode(key, level, value); + this.nodeStore.setNode(key, level, value); (this.writeCache.nodes[level] ??= {})[key.toString()] = value; } @@ -109,25 +88,18 @@ export class CachedLinkedMerkleTreeStore }); } - // This gets the nodes from the in memory store (which looks also to be the cache). - private getLeafByPath(path: bigint) { - const index = super.getLeafIndex(path); - if (index !== undefined) { - return super.getLeaf(index); - } - return undefined; - } - // This gets the leaves and the nodes from the in memory store. // If the leaf is not in the in-memory store it goes to the parent (i.e. // what's put in the constructor). public async getLeavesAsync(paths: bigint[]) { - const results = Array(paths.length).fill(undefined); + const results = Array<{ leaf: LinkedLeaf; index: bigint } | undefined>( + paths.length + ).fill(undefined); const toFetch: bigint[] = []; paths.forEach((path, index) => { - const localResult = this.getLeafByPath(path); + const localResult = this.getLeaf(path); if (localResult !== undefined) { results[index] = localResult; } else { @@ -151,19 +123,15 @@ export class CachedLinkedMerkleTreeStore // It doesn't need any fancy logic and just updates the leaves. // I don't think we need to coordinate this with the nodes // or do any calculations. Just a straight copy and paste. - public writeLeaves(leaves: [string, LinkedLeaf][]) { - leaves.forEach(([key, leaf]) => { - this.setLeaf(BigInt(key), leaf); + public writeLeaves(leaves: { leaf: LinkedLeaf; index: bigint }[]) { + leaves.forEach(({ leaf, index }) => { + this.setLeaf(index, leaf); }); } - public setLeaf(key: bigint, leaf: LinkedLeaf) { - this.writeCache.leaves[key.toString()] = leaf; - super.setLeaf(BigInt(key), leaf); - } - - public getLeafByIndex(index: bigint) { - return super.getLeaf(index); + public setLeaf(index: bigint, leaf: LinkedLeaf) { + this.writeCache.leaves[leaf.path.toString()] = { leaf: leaf, index: index }; + this.leafStore.setLeaf(index, leaf); } // This gets the nodes from the cache. @@ -178,10 +146,8 @@ export class CachedLinkedMerkleTreeStore // This gets the leaves from the cache. // Only used in mergeIntoParent - public getWrittenLeaves(): { - [key: string]: LinkedLeaf; - } { - return this.writeCache.leaves; + public getWrittenLeaves(): { leaf: LinkedLeaf; index: bigint }[] { + return Object.values(this.writeCache.leaves); } // This ensures all the keys needed to be loaded @@ -190,15 +156,17 @@ export class CachedLinkedMerkleTreeStore // (without the loading) when we find the closest leaf. // TODO: see how we could use a returned value. public async loadUpKeysForClosestPath(path: bigint): Promise { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - let largestLeaf = this.getLeaf(0n) as LinkedLeaf; - while (largestLeaf.nextPath <= path) { - let nextLeaf = this.getLeafByPath(largestLeaf.nextPath); + let largestLeaf = this.getLeaf(0n); + if (largestLeaf === undefined) { + throw Error("Path 0n should be defined."); + } + while (largestLeaf.leaf.nextPath <= path) { + let nextLeaf = this.getLeaf(largestLeaf.leaf.nextPath); // This means the nextPath wasn't preloaded and we have to load it. if (nextLeaf === undefined) { // eslint-disable-next-line no-await-in-loop - await this.preloadKey(largestLeaf.nextPath); - nextLeaf = this.getLeafByPath(largestLeaf.nextPath); + await this.preloadKey(largestLeaf.leaf.nextPath); + nextLeaf = this.getLeaf(largestLeaf.leaf.nextPath); if (nextLeaf === undefined) { throw Error(" Next Path is defined but not fetched"); } @@ -254,35 +222,55 @@ export class CachedLinkedMerkleTreeStore return nodesToRetrieve; } - // Takes a list of keys and for each key collects the relevant nodes from the - // parent tree and sets the leaf and node in the cached tree (and in-memory tree). - public async preloadKeys(paths: bigint[]) { - const nodesToRetrieve = paths.flatMap((path) => { - const pathIndex = this.parent.getLeafIndex(path) ?? 0n; - return this.collectNodesToFetch(pathIndex); - }); + protected async preloadMaximumIndex() { + if (this.leafStore.getMaximumIndex() === undefined) { + this.leafStore.maximumIndex = await this.parent.getMaximumIndexAsync(); + } + } + + public async preloadNodes(indexes: bigint[]) { + const nodesToRetrieve = indexes.flatMap((key) => + this.collectNodesToFetch(key) + ); - const resultsNode = await this.parent.getNodesAsync(nodesToRetrieve); - let index = 0; - for (const retrievedNode of nodesToRetrieve) { - const { key, level } = retrievedNode; - const value = resultsNode[index]; + const results = await this.parent.getNodesAsync(nodesToRetrieve); + nodesToRetrieve.forEach(({ key, level }, index) => { + const value = results[index]; if (value !== undefined) { this.setNode(key, level, value); - if (level === 0) { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const resultLeaf = this.parent.getLeafByIndex(key) as LinkedLeaf; - super.setLeaf(key, resultLeaf); - this.writeCache.leaves[key.toString()] = resultLeaf; - } } - index += 1; + }); + } + + public getLeaf(path: bigint) { + return this.leafStore.getLeaf(path); + } + + // Takes a list of paths and for each key collects the relevant nodes from the + // parent tree and sets the leaf and node in the cached tree (and in-memory tree). + public async preloadKey(path: bigint) { + const leaf = (await this.parent.getLeavesAsync([path]))[0]; + if (leaf !== undefined) { + this.leafStore.setLeaf(leaf.index, leaf.leaf); + // Update + await this.preloadNodes([leaf.index]); + } else { + // Insert + const previousLeaf = await this.parent.getLeafLessOrEqualAsync(path); + this.leafStore.setLeaf(previousLeaf.index, previousLeaf.leaf); + await this.preloadNodes([previousLeaf.index]); + const maximumIndex = + this.leafStore.getMaximumIndex() ?? + (await this.parent.getMaximumIndexAsync()); + if (maximumIndex === undefined) { + throw Error("Maximum index should be defined in parent."); + } + await this.preloadNodes([maximumIndex]); } } - // This is preloadKeys with just one index/key. - public async preloadKey(path: bigint): Promise { - await this.preloadKeys([path]); + public async preloadKeys(paths: bigint[]): Promise { + await paths.forEach(async (x) => await this.preloadKey(x)); } // This merges the cache into the parent tree and resets the cache, but not the @@ -297,7 +285,7 @@ export class CachedLinkedMerkleTreeStore const nodes = this.getWrittenNodes(); const leaves = this.getWrittenLeaves(); - this.parent.writeLeaves(Object.entries(leaves)); + this.parent.writeLeaves(Object.values(leaves)); const writes = Object.keys(nodes).flatMap((levelString) => { const level = Number(levelString); return Object.entries(nodes[level]).map( @@ -316,4 +304,12 @@ export class CachedLinkedMerkleTreeStore await this.parent.commit(); this.resetWrittenTree(); } + + public getLeafLessOrEqual(path: bigint) { + return this.leafStore.getLeafLessOrEqual(path); + } + + public getMaximumIndex() { + return this.leafStore.getMaximumIndex(); + } } diff --git a/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts index a8d4976e..32a5e5ee 100644 --- a/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts @@ -1,37 +1,38 @@ import { - InMemoryLinkedMerkleTreeStorage, LinkedLeaf, LinkedMerkleTree, LinkedMerkleTreeStore, + InMemoryLinkedLeafStore, + InMemoryMerkleTreeStorage, } from "@proto-kit/common"; +import { StoredLeaf } from "../async/AsyncLinkedMerkleTreeStore"; + // This is mainly used for supporting the rollbacks we need to do in case a runtimemethod fails // In this case everything should be preloaded in the parent async service -export class SyncCachedLinkedMerkleTreeStore extends InMemoryLinkedMerkleTreeStorage { - public constructor(private readonly parent: LinkedMerkleTreeStore) { - super(); - } +export class SyncCachedLinkedMerkleTreeStore implements LinkedMerkleTreeStore { + private readonly leafStore = new InMemoryLinkedLeafStore(); + + private readonly nodeStore = new InMemoryMerkleTreeStorage(); + + public constructor(private readonly parent: LinkedMerkleTreeStore) {} public getNode(key: bigint, level: number): bigint | undefined { - return super.getNode(key, level) ?? this.parent.getNode(key, level); + return ( + this.nodeStore.getNode(key, level) ?? this.parent.getNode(key, level) + ); } public setNode(key: bigint, level: number, value: bigint) { - super.setNode(key, level, value); + this.nodeStore.setNode(key, level, value); } - public getLeaf(index: bigint): LinkedLeaf | undefined { - return super.getLeaf(index) ?? this.parent.getLeaf(index); + public getLeaf(index: bigint): StoredLeaf | undefined { + return this.leafStore.getLeaf(index) ?? this.parent.getLeaf(index); } public setLeaf(index: bigint, value: LinkedLeaf) { - super.setLeaf(index, value); - } - - // Need to make sure we call the parent as the super will usually be empty - // The Tree calls this method. - public getLeafIndex(path: bigint): bigint | undefined { - return super.getLeafIndex(path) ?? this.parent.getLeafIndex(path); + this.leafStore.setLeaf(index, value); } // Need to make sure we call the parent as the super will usually be empty @@ -40,28 +41,28 @@ export class SyncCachedLinkedMerkleTreeStore extends InMemoryLinkedMerkleTreeSto return this.parent.getMaximumIndex(); } - public getLeafLessOrEqual(path: bigint): LinkedLeaf { + public getLeafLessOrEqual(path: bigint): StoredLeaf { return ( - super.getLeafLessOrEqual(path) ?? this.parent.getLeafLessOrEqual(path) + this.leafStore.getLeafLessOrEqual(path) ?? + this.parent.getLeafLessOrEqual(path) ); } public mergeIntoParent() { - if (Object.keys(this.leaves).length === 0) { + if (Object.keys(this.leafStore.leaves).length === 0) { return; } - const { nodes, leaves } = this; - Object.entries(leaves).forEach(([key, leaf]) => - this.parent.setLeaf(BigInt(key), leaf) + Object.values(this.leafStore.leaves).forEach(({ leaf, index }) => + this.parent.setLeaf(index, leaf) ); Array.from({ length: LinkedMerkleTree.HEIGHT }).forEach((ignored, level) => - Object.entries(nodes[level]).forEach((entry) => { + Object.entries(this.nodeStore.nodes[level]).forEach((entry) => { this.parent.setNode(BigInt(entry[0]), level, entry[1]); }) ); - this.leaves = {}; - this.nodes = {}; + this.leafStore.leaves = {}; + this.nodeStore.nodes = {}; } } diff --git a/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts b/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts index 1b902171..444707d1 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts @@ -1,5 +1,6 @@ import { - InMemoryLinkedMerkleTreeStorage, + InMemoryLinkedLeafStore, + InMemoryMerkleTreeStorage, LinkedLeaf, noop, } from "@proto-kit/common"; @@ -13,53 +14,52 @@ import { export class InMemoryAsyncLinkedMerkleTreeStore implements AsyncLinkedMerkleTreeStore { - private readonly store = new InMemoryLinkedMerkleTreeStorage(); + private readonly leafStore = new InMemoryLinkedLeafStore(); - public writeNodes(nodes: MerkleTreeNode[]): void { - nodes.forEach(({ key, level, value }) => - this.store.setNode(key, level, value) - ); + private readonly nodeStore = new InMemoryMerkleTreeStorage(); + + public async openTransaction(): Promise { + noop(); } public async commit(): Promise { noop(); } - public async openTransaction(): Promise { - noop(); + public writeNodes(nodes: MerkleTreeNode[]): void { + nodes.forEach(({ key, level, value }) => + this.nodeStore.setNode(key, level, value) + ); + } + + // This is using the index/key + public writeLeaves(leaves: { leaf: LinkedLeaf; index: bigint }[]) { + leaves.forEach(({ leaf, index }) => { + this.leafStore.setLeaf(index, leaf); + }); } public async getNodesAsync( nodes: MerkleTreeNodeQuery[] ): Promise<(bigint | undefined)[]> { - return nodes.map(({ key, level }) => this.store.getNode(key, level)); + return nodes.map(({ key, level }) => this.nodeStore.getNode(key, level)); } public async getLeavesAsync(paths: bigint[]) { return paths.map((path) => { - const index = this.store.getLeafIndex(path); - if (index !== undefined) { - this.store.getLeaf(index); + const leaf = this.leafStore.getLeaf(path); + if (leaf !== undefined) { + return leaf; } return undefined; }); } - public writeLeaves(leaves: [string, LinkedLeaf][]) { - leaves.forEach(([key, leaf]) => { - this.store.setLeaf(BigInt(key), leaf); - }); - } - - public getLeafIndex(path: bigint) { - return this.store.getLeafIndex(path); - } - - public getMaximumIndex() { - return this.store.getMaximumIndex(); + public getMaximumIndexAsync() { + return Promise.resolve(this.leafStore.getMaximumIndex()); } - public getLeafByIndex(index: bigint) { - return this.store.getLeaf(index); + public getLeafLessOrEqualAsync(path: bigint) { + return Promise.resolve(this.leafStore.getLeafLessOrEqual(path)); } } From 47c4401babacdf3b7c0691d20efdd8d23334fe48 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Wed, 27 Nov 2024 10:06:35 +0000 Subject: [PATCH 089/107] Refactoring and getting LinkedTree to build --- packages/common/src/index.ts | 1 + .../trees/InMemoryLinkedMerkleLeafStore.ts | 9 +++++++ packages/common/src/trees/LinkedMerkleTree.ts | 16 ++++------- .../common/src/trees/LinkedMerkleTreeStore.ts | 1 - .../redis/RedisLinkedMerkleTreeStore.ts | 25 +++++++++-------- .../src/model/StateTransitionProvableBatch.ts | 4 +-- .../InMemoryAsyncLinkedMerkleTreeStore.ts | 27 ++++++++++++++++++- 7 files changed, 55 insertions(+), 28 deletions(-) create mode 100644 packages/common/src/trees/InMemoryLinkedMerkleLeafStore.ts diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 1a24aacb..4b8dfc75 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -20,3 +20,4 @@ export * from "./trees/LinkedMerkleTree"; export * from "./trees/InMemoryLinkedLeafStore"; export * from "./events/EventEmitterProxy"; export * from "./trees/MockAsyncMerkleStore"; +export * from "./trees/InMemoryLinkedMerkleLeafStore"; diff --git a/packages/common/src/trees/InMemoryLinkedMerkleLeafStore.ts b/packages/common/src/trees/InMemoryLinkedMerkleLeafStore.ts new file mode 100644 index 00000000..6d673c3f --- /dev/null +++ b/packages/common/src/trees/InMemoryLinkedMerkleLeafStore.ts @@ -0,0 +1,9 @@ +import { Mixin } from "ts-mixer"; + +import { InMemoryLinkedLeafStore } from "./InMemoryLinkedLeafStore"; +import { InMemoryMerkleTreeStorage } from "./InMemoryMerkleTreeStorage"; + +export class InMemoryLinkedMerkleLeafStore extends Mixin( + InMemoryLinkedLeafStore, + InMemoryMerkleTreeStorage +) {} diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index efdba697..6d2742dd 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -1,20 +1,16 @@ // eslint-disable-next-line max-classes-per-file import { Bool, Field, Poseidon, Provable, Struct } from "o1js"; -import { InMemoryAsyncLinkedMerkleTreeStore } from "@proto-kit/sequencer/dist/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore"; -import { CachedLinkedMerkleTreeStore } from "@proto-kit/sequencer/dist/state/merkle/CachedLinkedMerkleTreeStore"; import { TypedClass } from "../types"; import { range } from "../utils"; -import { - LinkedLeafStore, - LinkedMerkleTreeStore, -} from "./LinkedMerkleTreeStore"; +import { LinkedMerkleTreeStore } from "./LinkedMerkleTreeStore"; import { AbstractMerkleWitness, maybeSwap, RollupMerkleTreeWitness, } from "./RollupMerkleTree"; +import { InMemoryLinkedMerkleLeafStore } from "./InMemoryLinkedMerkleLeafStore"; export class LinkedLeafStruct extends Struct({ value: Field, @@ -41,7 +37,7 @@ class LinkedStructTemplate extends Struct({ export interface AbstractLinkedMerkleWitness extends LinkedStructTemplate {} export interface AbstractLinkedMerkleTree { - store: LinkedLeafStore; + store: LinkedMerkleTreeStore; /** * Returns a node which lives at a given index and level. * @param level Level of the node. @@ -82,7 +78,7 @@ export interface AbstractLinkedMerkleTree { } export interface AbstractLinkedMerkleTreeClass { - new (store: LinkedLeafStore): AbstractLinkedMerkleTree; + new (store: LinkedMerkleTreeStore): AbstractLinkedMerkleTree; WITNESS: TypedClass & typeof LinkedStructTemplate; @@ -204,9 +200,7 @@ export function createLinkedMerkleTree( public static HEIGHT = height; public static EMPTY_ROOT = new AbstractLinkedRollupMerkleTree( - await CachedLinkedMerkleTreeStore.new( - InMemoryAsyncLinkedMerkleTreeStore() - ) + new InMemoryLinkedMerkleLeafStore() ) .getRoot() .toBigInt(); diff --git a/packages/common/src/trees/LinkedMerkleTreeStore.ts b/packages/common/src/trees/LinkedMerkleTreeStore.ts index dae7710d..0ce6d352 100644 --- a/packages/common/src/trees/LinkedMerkleTreeStore.ts +++ b/packages/common/src/trees/LinkedMerkleTreeStore.ts @@ -11,7 +11,6 @@ export interface LinkedLeafStore { } export type LinkedLeaf = { value: bigint; path: bigint; nextPath: bigint }; - export interface LinkedMerkleTreeStore extends LinkedLeafStore, MerkleTreeStore {} diff --git a/packages/persistance/src/services/redis/RedisLinkedMerkleTreeStore.ts b/packages/persistance/src/services/redis/RedisLinkedMerkleTreeStore.ts index 139b6a56..7616a546 100644 --- a/packages/persistance/src/services/redis/RedisLinkedMerkleTreeStore.ts +++ b/packages/persistance/src/services/redis/RedisLinkedMerkleTreeStore.ts @@ -60,25 +60,24 @@ export class RedisLinkedMerkleTreeStore implements AsyncLinkedMerkleTreeStore { this.cache = this.cache.concat(nodes); } - public writeLeaves(leaves: [string, LinkedLeaf][]) {} + public writeLeaves(leaves: { leaf: LinkedLeaf; index: bigint }[]) {} public getLeavesAsync(paths: bigint[]) { return Promise.resolve([undefined]); } - public getLeafIndex(path: bigint) { - return 0n; + public getMaximumIndexAsync() { + return Promise.resolve(0n); } - public getMaximumIndex() { - return 0n; - } - - public getLeafByIndex(index: bigint) { - return { - value: 0n, - path: 0n, - nextPath: 0n, - }; + public getLeafLessOrEqualAsync(path: bigint) { + return Promise.resolve({ + leaf: { + value: 0n, + path: 0n, + nextPath: 0n, + }, + index: 0n, + }); } } diff --git a/packages/protocol/src/model/StateTransitionProvableBatch.ts b/packages/protocol/src/model/StateTransitionProvableBatch.ts index bb363b6b..6fa06257 100644 --- a/packages/protocol/src/model/StateTransitionProvableBatch.ts +++ b/packages/protocol/src/model/StateTransitionProvableBatch.ts @@ -1,5 +1,5 @@ import { Bool, Provable, Struct } from "o1js"; -import { InMemoryLinkedMerkleTreeStorage, range } from "@proto-kit/common"; +import { InMemoryLinkedMerkleLeafStore, range } from "@proto-kit/common"; import { LinkedMerkleTree, LinkedMerkleTreeWitness, @@ -95,7 +95,7 @@ export class StateTransitionProvableBatch extends Struct({ batch.push(ProvableStateTransition.dummy()); transitionTypes.push(ProvableStateTransitionType.normal); witnesses.push( - new LinkedMerkleTree(new InMemoryLinkedMerkleTreeStorage()).getWitness( + new LinkedMerkleTree(new InMemoryLinkedMerkleLeafStore()).getWitness( BigInt(0) ) ); diff --git a/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts b/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts index 444707d1..9a6a240f 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts @@ -2,6 +2,7 @@ import { InMemoryLinkedLeafStore, InMemoryMerkleTreeStorage, LinkedLeaf, + LinkedMerkleTreeStore, noop, } from "@proto-kit/common"; @@ -12,7 +13,7 @@ import { } from "../../state/async/AsyncMerkleTreeStore"; export class InMemoryAsyncLinkedMerkleTreeStore - implements AsyncLinkedMerkleTreeStore + implements AsyncLinkedMerkleTreeStore, LinkedMerkleTreeStore { private readonly leafStore = new InMemoryLinkedLeafStore(); @@ -62,4 +63,28 @@ export class InMemoryAsyncLinkedMerkleTreeStore public getLeafLessOrEqualAsync(path: bigint) { return Promise.resolve(this.leafStore.getLeafLessOrEqual(path)); } + + public setLeaf(index: bigint, value: LinkedLeaf) { + this.leafStore.setLeaf(index, value); + } + + public getLeaf(path: bigint) { + return this.leafStore.getLeaf(path); + } + + public getLeafLessOrEqual(path: bigint) { + return this.leafStore.getLeafLessOrEqual(path); + } + + public getMaximumIndex() { + return this.leafStore.getMaximumIndex(); + } + + public setNode(key: bigint, level: number, value: bigint) { + this.nodeStore.setNode(key, level, value); + } + + public getNode(key: bigint, level: number) { + return this.nodeStore.getNode(key, level); + } } From 8a9835b14c37b47ea2eb617de6d0e20c0708b335 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Wed, 27 Nov 2024 10:22:12 +0000 Subject: [PATCH 090/107] Fix error in tree, --- packages/common/src/trees/LinkedMerkleTree.ts | 2 +- packages/common/test/trees/LinkedMerkleTree.test.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 6d2742dd..b332af0a 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -322,7 +322,7 @@ export function createLinkedMerkleTree( const newPrevLeaf = { value: prevLeaf.leaf.value, path: prevLeaf.leaf.path, - nextPath: prevLeaf.leaf.path, + nextPath: path, }; this.store.setLeaf(prevLeaf.index, newPrevLeaf); this.setMerkleLeaf( diff --git a/packages/common/test/trees/LinkedMerkleTree.test.ts b/packages/common/test/trees/LinkedMerkleTree.test.ts index a6d4e5e3..a13d8d7b 100644 --- a/packages/common/test/trees/LinkedMerkleTree.test.ts +++ b/packages/common/test/trees/LinkedMerkleTree.test.ts @@ -3,7 +3,7 @@ import { Field, Poseidon } from "o1js"; import { createLinkedMerkleTree, - InMemoryLinkedMerkleTreeStorage, + InMemoryLinkedMerkleLeafStore, log, } from "../../src"; import { expectDefined } from "../../dist/utils"; @@ -11,13 +11,13 @@ import { expectDefined } from "../../dist/utils"; describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { class LinkedMerkleTree extends createLinkedMerkleTree(height) {} - let store: InMemoryLinkedMerkleTreeStorage; + let store: InMemoryLinkedMerkleLeafStore; let tree: LinkedMerkleTree; beforeEach(() => { log.setLevel("INFO"); - store = new InMemoryLinkedMerkleTreeStorage(); + store = new InMemoryLinkedMerkleLeafStore(); tree = new LinkedMerkleTree(store); }); @@ -106,13 +106,13 @@ describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { // Separate describe here since we only want small trees for this test. describe("Error check", () => { class LinkedMerkleTree extends createLinkedMerkleTree(4) {} - let store: InMemoryLinkedMerkleTreeStorage; + let store: InMemoryLinkedMerkleLeafStore; let tree: LinkedMerkleTree; it("throw for invalid index", () => { log.setLevel("INFO"); - store = new InMemoryLinkedMerkleTreeStorage(); + store = new InMemoryLinkedMerkleLeafStore(); tree = new LinkedMerkleTree(store); expect(() => { for (let i = 0; i < 2n ** BigInt(4) + 1n; i++) { From eb4a6e1768deb789608ce4edf2069cc3de930e89 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Wed, 27 Nov 2024 14:15:16 +0000 Subject: [PATCH 091/107] Update interfaces --- packages/common/src/trees/LinkedMerkleTreeStore.ts | 4 ++++ .../state/merkle/CachedLinkedMerkleTreeStore.ts | 6 ++++-- .../merkle/SyncCachedLinkedMerkleTreeStore.ts | 14 +++++++++++--- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTreeStore.ts b/packages/common/src/trees/LinkedMerkleTreeStore.ts index 0ce6d352..5099adc0 100644 --- a/packages/common/src/trees/LinkedMerkleTreeStore.ts +++ b/packages/common/src/trees/LinkedMerkleTreeStore.ts @@ -14,3 +14,7 @@ export type LinkedLeaf = { value: bigint; path: bigint; nextPath: bigint }; export interface LinkedMerkleTreeStore extends LinkedLeafStore, MerkleTreeStore {} + +export interface PreloadingLinkedMerkleTreeStore extends LinkedMerkleTreeStore { + preloadKeys(path: bigint[]): Promise; +} diff --git a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts index 8f988111..fb1b7160 100644 --- a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts @@ -3,7 +3,7 @@ import { InMemoryLinkedLeafStore, LinkedLeaf, InMemoryMerkleTreeStorage, - LinkedMerkleTreeStore, + PreloadingLinkedMerkleTreeStore, } from "@proto-kit/common"; import { @@ -12,7 +12,9 @@ import { } from "../async/AsyncMerkleTreeStore"; import { AsyncLinkedMerkleTreeStore } from "../async/AsyncLinkedMerkleTreeStore"; -export class CachedLinkedMerkleTreeStore implements LinkedMerkleTreeStore { +export class CachedLinkedMerkleTreeStore + implements PreloadingLinkedMerkleTreeStore +{ private writeCache: { nodes: { [key: number]: { diff --git a/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts index 32a5e5ee..96dbe948 100644 --- a/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts @@ -1,21 +1,25 @@ import { LinkedLeaf, LinkedMerkleTree, - LinkedMerkleTreeStore, InMemoryLinkedLeafStore, InMemoryMerkleTreeStorage, + PreloadingLinkedMerkleTreeStore, } from "@proto-kit/common"; import { StoredLeaf } from "../async/AsyncLinkedMerkleTreeStore"; // This is mainly used for supporting the rollbacks we need to do in case a runtimemethod fails // In this case everything should be preloaded in the parent async service -export class SyncCachedLinkedMerkleTreeStore implements LinkedMerkleTreeStore { +export class SyncCachedLinkedMerkleTreeStore + implements PreloadingLinkedMerkleTreeStore +{ private readonly leafStore = new InMemoryLinkedLeafStore(); private readonly nodeStore = new InMemoryMerkleTreeStorage(); - public constructor(private readonly parent: LinkedMerkleTreeStore) {} + public constructor( + private readonly parent: PreloadingLinkedMerkleTreeStore + ) {} public getNode(key: bigint, level: number): bigint | undefined { return ( @@ -48,6 +52,10 @@ export class SyncCachedLinkedMerkleTreeStore implements LinkedMerkleTreeStore { ); } + public async preloadKeys(path: bigint[]) { + await this.parent.preloadKeys(path); + } + public mergeIntoParent() { if (Object.keys(this.leafStore.leaves).length === 0) { return; From 55fba98f33fd03e2ea071e62c7d322cadb94fd2d Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Wed, 27 Nov 2024 15:56:32 +0000 Subject: [PATCH 092/107] Update return type for getLeafLessOrEqual --- .../src/trees/InMemoryLinkedLeafStore.ts | 20 +++++++------------ packages/common/src/trees/LinkedMerkleTree.ts | 3 +++ .../common/src/trees/LinkedMerkleTreeStore.ts | 4 +++- .../state/async/AsyncLinkedMerkleTreeStore.ts | 3 +-- .../merkle/CachedLinkedMerkleTreeStore.ts | 3 +++ .../merkle/SyncCachedLinkedMerkleTreeStore.ts | 2 +- 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/packages/common/src/trees/InMemoryLinkedLeafStore.ts b/packages/common/src/trees/InMemoryLinkedLeafStore.ts index 4719fb68..0af12a83 100644 --- a/packages/common/src/trees/InMemoryLinkedLeafStore.ts +++ b/packages/common/src/trees/InMemoryLinkedLeafStore.ts @@ -29,18 +29,12 @@ export class InMemoryLinkedLeafStore implements LinkedLeafStore { } // This gets the leaf with the closest path. - public getLeafLessOrEqual(path: bigint): { leaf: LinkedLeaf; index: bigint } { - let largestLeaf = this.getLeaf(0n); - if (largestLeaf === undefined) { - throw new Error("Path 0n should always be defined"); - } - while (largestLeaf.leaf.nextPath <= path) { - const nextLeaf = this.getLeaf(largestLeaf.leaf.nextPath); - if (nextLeaf === undefined) { - throw new Error("Next Path should always be defined"); - } - largestLeaf = nextLeaf; - } - return largestLeaf; + public getLeafLessOrEqual( + path: bigint + ): { leaf: LinkedLeaf; index: bigint } | undefined { + return Object.values(this.leaves).find( + (storedLeaf) => + storedLeaf.leaf.nextPath > path && storedLeaf.leaf.path <= path + ); } } diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index b332af0a..c3ba818f 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -306,6 +306,9 @@ export function createLinkedMerkleTree( } const storedLeaf = this.store.getLeaf(path); const prevLeaf = this.store.getLeafLessOrEqual(path); + if (prevLeaf === undefined) { + throw Error("Prev leaf shouldn't be undefined"); + } let witnessPrevious; let index: bigint; if (storedLeaf === undefined) { diff --git a/packages/common/src/trees/LinkedMerkleTreeStore.ts b/packages/common/src/trees/LinkedMerkleTreeStore.ts index 5099adc0..f8efefd6 100644 --- a/packages/common/src/trees/LinkedMerkleTreeStore.ts +++ b/packages/common/src/trees/LinkedMerkleTreeStore.ts @@ -5,7 +5,9 @@ export interface LinkedLeafStore { getLeaf: (path: bigint) => { leaf: LinkedLeaf; index: bigint } | undefined; - getLeafLessOrEqual: (path: bigint) => { leaf: LinkedLeaf; index: bigint }; + getLeafLessOrEqual: ( + path: bigint + ) => { leaf: LinkedLeaf; index: bigint } | undefined; getMaximumIndex: () => bigint | undefined; } diff --git a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts index 7ec4f952..e2e6eb6f 100644 --- a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts @@ -21,6 +21,5 @@ export interface AsyncLinkedMerkleTreeStore { getMaximumIndexAsync: () => Promise; - // Doesn't return undefined as there should always be at least one leaf. - getLeafLessOrEqualAsync: (path: bigint) => Promise; + getLeafLessOrEqualAsync: (path: bigint) => Promise; } diff --git a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts index fb1b7160..1d08282f 100644 --- a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts @@ -259,6 +259,9 @@ export class CachedLinkedMerkleTreeStore } else { // Insert const previousLeaf = await this.parent.getLeafLessOrEqualAsync(path); + if (previousLeaf === undefined) { + throw Error("Previous Leaf should never be empty"); + } this.leafStore.setLeaf(previousLeaf.index, previousLeaf.leaf); await this.preloadNodes([previousLeaf.index]); const maximumIndex = diff --git a/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts index 96dbe948..4926126c 100644 --- a/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts @@ -45,7 +45,7 @@ export class SyncCachedLinkedMerkleTreeStore return this.parent.getMaximumIndex(); } - public getLeafLessOrEqual(path: bigint): StoredLeaf { + public getLeafLessOrEqual(path: bigint): StoredLeaf | undefined { return ( this.leafStore.getLeafLessOrEqual(path) ?? this.parent.getLeafLessOrEqual(path) From 271d9e432b3f0fa38d2c4d34e95252797497ee73 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 28 Nov 2024 08:59:10 +0000 Subject: [PATCH 093/107] Update Linked Leaf with hash functionality --- packages/common/src/trees/LinkedMerkleTree.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index c3ba818f..3c15ac45 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -277,11 +277,7 @@ export function createLinkedMerkleTree( * @param leaf New value. */ private setMerkleLeaf(index: bigint, leaf: LinkedLeafStruct) { - this.setNode( - 0, - index, - Poseidon.hash([leaf.value, leaf.path, leaf.nextPath]) - ); + this.setNode(0, index, leaf.hash()); let tempIndex = index; for ( let level = 1; From 7aa5320c3e7dc6bd13d58869b77e1ea345163118 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 28 Nov 2024 10:44:29 +0000 Subject: [PATCH 094/107] Update test --- .../merkle/CachedLinkedMerkleTreeStore.ts | 13 +- .../merkle/CachedLinkedMerkleStore.test.ts | 325 +++++++++--------- 2 files changed, 181 insertions(+), 157 deletions(-) diff --git a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts index 1d08282f..69514cb9 100644 --- a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts @@ -4,6 +4,7 @@ import { LinkedLeaf, InMemoryMerkleTreeStorage, PreloadingLinkedMerkleTreeStore, + mapSequential, } from "@proto-kit/common"; import { @@ -251,14 +252,18 @@ export class CachedLinkedMerkleTreeStore // Takes a list of paths and for each key collects the relevant nodes from the // parent tree and sets the leaf and node in the cached tree (and in-memory tree). public async preloadKey(path: bigint) { - const leaf = (await this.parent.getLeavesAsync([path]))[0]; + const leaf = + this.leafStore.getLeaf(path) ?? + (await this.parent.getLeavesAsync([path]))[0]; if (leaf !== undefined) { this.leafStore.setLeaf(leaf.index, leaf.leaf); // Update await this.preloadNodes([leaf.index]); } else { // Insert - const previousLeaf = await this.parent.getLeafLessOrEqualAsync(path); + const previousLeaf = + this.leafStore.getLeafLessOrEqual(path) ?? + (await this.parent.getLeafLessOrEqualAsync(path)); if (previousLeaf === undefined) { throw Error("Previous Leaf should never be empty"); } @@ -270,12 +275,12 @@ export class CachedLinkedMerkleTreeStore if (maximumIndex === undefined) { throw Error("Maximum index should be defined in parent."); } - await this.preloadNodes([maximumIndex]); + await this.preloadNodes([maximumIndex + 1n]); } } public async preloadKeys(paths: bigint[]): Promise { - await paths.forEach(async (x) => await this.preloadKey(x)); + await mapSequential(paths, (x) => this.preloadKey(x)); } // This merges the cache into the parent tree and resets the cache, but not the diff --git a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts index f56aa93f..0ee6523f 100644 --- a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts +++ b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts @@ -7,12 +7,14 @@ import { InMemoryAsyncLinkedMerkleTreeStore } from "../../src/storage/inmemory/I import { SyncCachedLinkedMerkleTreeStore } from "../../src/state/merkle/SyncCachedLinkedMerkleTreeStore"; describe("cached linked merkle store", () => { - const mainStore = new InMemoryAsyncLinkedMerkleTreeStore(); + let mainStore: InMemoryAsyncLinkedMerkleTreeStore; let cache1: CachedLinkedMerkleTreeStore; let tree1: LinkedMerkleTree; beforeEach(async () => { + mainStore = new InMemoryAsyncLinkedMerkleTreeStore(); + const cachedStore = await CachedLinkedMerkleTreeStore.new(mainStore); const tmpTree = new LinkedMerkleTree(cachedStore); @@ -24,45 +26,38 @@ describe("cached linked merkle store", () => { }); it("should cache multiple keys correctly", async () => { - expect.assertions(13); - + expect.assertions(11); + await cache1.preloadKeys([16n, 46n]); tree1.setLeaf(16n, 16n); tree1.setLeaf(46n, 46n); - const cache2 = await CachedLinkedMerkleTreeStore.new(cache1); + const cache2 = new SyncCachedLinkedMerkleTreeStore(cache1); const tree2 = new LinkedMerkleTree(cache2); // Need to preload 0n, as well since the nextPath of the leaf would have changed // when other leaves were added. - await cache2.preloadKeys([0n, 16n, 46n]); - const leaf0 = tree1.getLeaf(0n); const leaf1 = tree1.getLeaf(16n); const leaf2 = tree1.getLeaf(46n); - expectDefined(leaf0); expectDefined(leaf1); expectDefined(leaf2); - const leaf1Index = cache2.getLeafIndex(16n); - const leaf2Index = cache2.getLeafIndex(46n); + const storedLeaf1 = cache2.getLeaf(16n); + const storedLeaf2 = cache2.getLeaf(46n); - expectDefined(leaf1Index); - expectDefined(leaf2Index); + expectDefined(storedLeaf1); + expectDefined(storedLeaf2); - // The new leaves are at index 2 and 3, as the index 5 is auto-preloaded - // as it is next to 0, and 0 is always preloaded as well as any relevant - // nodes. - expect(leaf1Index).toStrictEqual(2n); - expect(leaf2Index).toStrictEqual(3n); + expect(storedLeaf1.index).toStrictEqual(2n); + expect(storedLeaf2.index).toStrictEqual(3n); - expect(tree2.getNode(0, leaf1Index).toBigInt()).toBe( - Poseidon.hash([leaf1.value, leaf1.path, leaf1.nextPath]).toBigInt() + expect(tree2.getNode(0, storedLeaf1.index).toBigInt()).toBe( + leaf1.hash().toBigInt() ); - expect(tree2.getNode(0, leaf2Index).toBigInt()).toBe( - Poseidon.hash([leaf2.value, leaf2.path, leaf2.nextPath]).toBigInt() + expect(tree2.getNode(0, storedLeaf2.index).toBigInt()).toBe( + leaf2.hash().toBigInt() ); - expect(tree2.getLeaf(0n)).toEqual(leaf0); expect(tree2.getLeaf(16n)).toEqual(leaf1); expect(tree2.getLeaf(46n)).toEqual(leaf2); @@ -71,25 +66,56 @@ describe("cached linked merkle store", () => { ); }); + it("simple test - check hash of updated node is updated", async () => { + // main store already has 0n and 5n paths defined. + // preloading 10n should load up 5n in the cache1 leaf and node stores. + await cache1.preloadKeys([10n]); + + expectDefined(cache1.getLeaf(5n)); + expectDefined(cache1.getNode(1n, 0)); + + tree1.setLeaf(10n, 10n); + await cache1.mergeIntoParent(); + + const leaf5 = tree1.getLeaf(5n); + const leaf10 = tree1.getLeaf(10n); + expectDefined(leaf5); + expectDefined(leaf10); + + const storedLeaf5 = cache1.getLeaf(5n); + const storedLeaf10 = cache1.getLeaf(10n); + + expectDefined(storedLeaf5); + expectDefined(storedLeaf10); + + expect(storedLeaf5).toStrictEqual({ + leaf: { value: 10n, path: 5n, nextPath: 10n }, + index: 1n, + }); + expect(storedLeaf10.index).toStrictEqual(2n); + + // Check leaves were hashed properly when added to nodes/merkle-tree + expect(cache1.getNode(storedLeaf10.index, 0)).toStrictEqual( + leaf10.hash().toBigInt() + ); + expect(cache1.getNode(storedLeaf5.index, 0)).toStrictEqual( + leaf5.hash().toBigInt() + ); + }); + it("should preload through multiple levels and insert correctly at right index", async () => { + await cache1.preloadKeys([10n, 11n, 12n, 13n]); + tree1.setLeaf(10n, 10n); tree1.setLeaf(11n, 11n); tree1.setLeaf(12n, 12n); tree1.setLeaf(13n, 13n); + await cache1.mergeIntoParent(); - // Nodes 0 and 5 should be auto-preloaded when cache2 is created - // as 0 is the first and 5 is its sibling. Similarly, 12 and 13 - // should be preloaded as 13 is in the maximum index and 12 is its sibling. - // Nodes 10 and 11 shouldn't be preloaded. - // We auto-preload 0 whenever the parent cache is already created. + const cache2 = new SyncCachedLinkedMerkleTreeStore(cache1); + await cache2.preloadKeys([14n]); - const cache2 = await CachedLinkedMerkleTreeStore.new(cache1); const tree2 = new LinkedMerkleTree(cache2); - - // When we set this leaf the missing nodes are preloaded - // as when we do a set we have to go through all the leaves to find - // the one with the nextPath that is suitable - await cache2.loadUpKeysForClosestPath(14n); tree2.setLeaf(14n, 14n); const leaf = tree1.getLeaf(5n); @@ -98,36 +124,39 @@ describe("cached linked merkle store", () => { expectDefined(leaf); expectDefined(leaf2); - const leaf5Index = cache2.getLeafIndex(5n); - const leaf10Index = cache2.getLeafIndex(10n); - const leaf11Index = cache2.getLeafIndex(11n); - const leaf12Index = cache2.getLeafIndex(12n); - const leaf13Index = cache2.getLeafIndex(13n); - const leaf14Index = cache2.getLeafIndex(14n); - - expectDefined(leaf5Index); - expectDefined(leaf10Index); - expectDefined(leaf11Index); - expectDefined(leaf12Index); - expectDefined(leaf13Index); - expectDefined(leaf14Index); - - expect(leaf5Index).toStrictEqual(1n); - expect(leaf10Index).toStrictEqual(2n); - expect(leaf11Index).toStrictEqual(3n); - expect(leaf12Index).toStrictEqual(4n); - expect(leaf13Index).toStrictEqual(5n); - expect(leaf14Index).toStrictEqual(6n); - - expect(cache2.getNode(leaf5Index, 0)).toStrictEqual( - Poseidon.hash([leaf.value, leaf.path, leaf.nextPath]).toBigInt() + const storedLeaf5 = cache2.getLeaf(5n); + const storedLeaf10 = cache2.getLeaf(10n); + const storedLeaf11 = cache2.getLeaf(11n); + const storedLeaf12 = cache2.getLeaf(12n); + const storedLeaf13 = cache2.getLeaf(13n); + const storedLeaf14 = cache2.getLeaf(14n); + + expectDefined(storedLeaf5); + expectDefined(storedLeaf10); + expectDefined(storedLeaf11); + expectDefined(storedLeaf12); + expectDefined(storedLeaf13); + expectDefined(storedLeaf14); + + expect(storedLeaf5.index).toStrictEqual(1n); + expect(storedLeaf10.index).toStrictEqual(2n); + expect(storedLeaf11.index).toStrictEqual(3n); + expect(storedLeaf12.index).toStrictEqual(4n); + expect(storedLeaf13.index).toStrictEqual(5n); + expect(storedLeaf14.index).toStrictEqual(6n); + + // Check leaves were hashed properly when added to nodes/merkle-tree + expect(cache1.getNode(storedLeaf5.index, 0)).toStrictEqual( + leaf.hash().toBigInt() ); - expect(cache2.getNode(leaf14Index, 0)).toStrictEqual( - Poseidon.hash([leaf2.value, leaf2.path, leaf2.nextPath]).toBigInt() + expect(cache2.getNode(storedLeaf14.index, 0)).toStrictEqual( + leaf2.hash().toBigInt() ); }); it("should preload through multiple levels and insert correctly at right index - harder", async () => { + await cache1.preloadKeys([10n, 100n, 200n, 300n, 400n, 500n]); + tree1.setLeaf(10n, 10n); tree1.setLeaf(100n, 100n); tree1.setLeaf(200n, 200n); @@ -135,21 +164,9 @@ describe("cached linked merkle store", () => { tree1.setLeaf(400n, 400n); tree1.setLeaf(500n, 500n); - // Nodes 0 and 5 should be auto-preloaded when cache2 is created - // as 0 is the first and 5 is its sibling. Similarly, 400 and 500 - // should be preloaded as 500 is in the maximum index and 400 is its sibling. - // Nodes 10 and 100, 300 and 400, shouldn't be preloaded. - // Note We auto-preload 0 whenever the parent cache is already created. - - const cache2 = await CachedLinkedMerkleTreeStore.new(cache1); + const cache2 = new SyncCachedLinkedMerkleTreeStore(cache1); + await cache2.preloadKeys([14n]); const tree2 = new LinkedMerkleTree(cache2); - - // When we set this leaf some of the missing nodes are preloaded - // as when we do a set we have to go through all the leaves to find - // the one with the nextPath that is suitable and this preloads that are missing before. - // This means 10n will be preloaded and since 100n is its sibling this will be preloaded, too. - // Note that the nodes 200n and 300n are not preloaded. - await cache2.loadUpKeysForClosestPath(14n); tree2.setLeaf(14n, 14n); const leaf = tree1.getLeaf(5n); @@ -158,50 +175,56 @@ describe("cached linked merkle store", () => { expectDefined(leaf); expectDefined(leaf2); - const leaf5Index = cache2.getLeafIndex(5n); - const leaf10Index = cache2.getLeafIndex(10n); - const leaf100Index = cache2.getLeafIndex(100n); - const leaf200Index = cache2.getLeafIndex(200n); - const leaf300Index = cache2.getLeafIndex(300n); - const leaf400Index = cache2.getLeafIndex(400n); - const leaf500Index = cache2.getLeafIndex(500n); - const leaf14Index = cache2.getLeafIndex(14n); - - expectDefined(leaf5Index); - expectDefined(leaf10Index); - expectDefined(leaf100Index); - expectDefined(leaf400Index); - expectDefined(leaf500Index); - expectDefined(leaf14Index); - - expect(leaf5Index).toStrictEqual(1n); - expect(leaf10Index).toStrictEqual(2n); - expect(leaf100Index).toStrictEqual(3n); - expect(leaf200Index).toStrictEqual(undefined); - expect(leaf300Index).toStrictEqual(undefined); - expect(leaf400Index).toStrictEqual(6n); - expect(leaf500Index).toStrictEqual(7n); - expect(leaf14Index).toStrictEqual(8n); - - expect(cache2.getNode(leaf5Index, 0)).toStrictEqual( - Poseidon.hash([leaf.value, leaf.path, leaf.nextPath]).toBigInt() + const storedLeaf5 = cache2.getLeaf(5n); + const storedLeaf10 = cache2.getLeaf(10n); + const storedLeaf100 = cache2.getLeaf(100n); + const storedLeaf200 = cache2.getLeaf(200n); + const storedLeaf300 = cache2.getLeaf(300n); + const storedLeaf400 = cache2.getLeaf(400n); + const storedLeaf500 = cache2.getLeaf(500n); + const storedLeaf14 = cache2.getLeaf(14n); + + expectDefined(storedLeaf5); + expectDefined(storedLeaf10); + expectDefined(storedLeaf100); + expectDefined(storedLeaf200); + expectDefined(storedLeaf300); + expectDefined(storedLeaf400); + expectDefined(storedLeaf500); + expectDefined(storedLeaf14); + + expect(storedLeaf5.index).toStrictEqual(1n); + expect(storedLeaf10.index).toStrictEqual(2n); + expect(storedLeaf100.index).toStrictEqual(3n); + expect(storedLeaf200?.index).toStrictEqual(4n); + expect(storedLeaf300?.index).toStrictEqual(5n); + expect(storedLeaf400.index).toStrictEqual(6n); + expect(storedLeaf500.index).toStrictEqual(7n); + expect(storedLeaf14.index).toStrictEqual(8n); + + expect(cache1.getNode(storedLeaf5.index, 0)).toStrictEqual( + leaf.hash().toBigInt() ); - expect(cache2.getNode(leaf14Index, 0)).toStrictEqual( - Poseidon.hash([leaf2.value, leaf2.path, leaf2.nextPath]).toBigInt() + expect(cache2.getNode(storedLeaf14.index, 0)).toStrictEqual( + leaf2.hash().toBigInt() ); expect(tree1.getRoot()).not.toEqual(tree2.getRoot()); + await cache2.mergeIntoParent(); + expect(tree1.getRoot()).toEqual(tree2.getRoot()); }); it("mimic transaction execution service", async () => { - expect.assertions(20); + expect.assertions(18); - const cache2 = new SyncCachedLinkedMerkleTreeStore(cache1); const treeCache1 = new LinkedMerkleTree(cache1); - const treeCache2 = new LinkedMerkleTree(cache2); - + await cache1.preloadKeys([10n, 20n]); treeCache1.setLeaf(10n, 10n); treeCache1.setLeaf(20n, 20n); + await cache1.mergeIntoParent(); + const cache2 = new SyncCachedLinkedMerkleTreeStore(cache1); + const treeCache2 = new LinkedMerkleTree(cache2); + await cache2.preloadKeys([7n]); treeCache2.setLeaf(7n, 7n); cache2.mergeIntoParent(); @@ -212,65 +235,58 @@ describe("cached linked merkle store", () => { expectDefined(leaves[3]); expectDefined(leaves[4]); - expect(leaves[0]).toEqual({ + expect(leaves[0]?.leaf).toEqual({ value: 0n, path: 0n, nextPath: 5n, }); - expect(leaves[1]).toEqual({ + expect(leaves[1]?.leaf).toEqual({ value: 10n, path: 5n, nextPath: 7n, }); - expect(leaves[2]).toEqual({ + expect(leaves[2]?.leaf).toEqual({ value: 7n, path: 7n, nextPath: 10n, }); - expect(leaves[3]).toEqual({ + expect(leaves[3]?.leaf).toEqual({ value: 10n, path: 10n, nextPath: 20n, }); - expect(leaves[4]).toEqual({ + expect(leaves[4]?.leaf).toEqual({ value: 20n, path: 20n, nextPath: Field.ORDER - 1n, }); - const leaf0Index = cache1.getLeafIndex(0n); - const leaf5Index = cache1.getLeafIndex(5n); - const leaf7Index = cache1.getLeafIndex(7n); - const leaf10Index = cache1.getLeafIndex(10n); - const leaf20Index = cache1.getLeafIndex(20n); + const storedLeaf5 = cache1.getLeaf(5n); + const storedLeaf7 = cache1.getLeaf(7n); + const storedLeaf10 = cache1.getLeaf(10n); + const storedLeaf20 = cache1.getLeaf(20n); - expectDefined(leaf0Index); - await expect( - cache1.getNodesAsync([{ key: leaf0Index, level: 0 }]) - ).resolves.toStrictEqual([ - Poseidon.hash([Field(0), Field(0), Field(5)]).toBigInt(), - ]); - expectDefined(leaf5Index); + expectDefined(storedLeaf5); await expect( - cache1.getNodesAsync([{ key: leaf5Index, level: 0 }]) + cache1.getNodesAsync([{ key: storedLeaf5.index, level: 0 }]) ).resolves.toStrictEqual([ Poseidon.hash([Field(10), Field(5), Field(7)]).toBigInt(), ]); - expectDefined(leaf7Index); + expectDefined(storedLeaf7); await expect( - cache1.getNodesAsync([{ key: leaf7Index, level: 0 }]) + cache1.getNodesAsync([{ key: storedLeaf7.index, level: 0 }]) ).resolves.toStrictEqual([ Poseidon.hash([Field(7), Field(7), Field(10)]).toBigInt(), ]); - expectDefined(leaf10Index); + expectDefined(storedLeaf10); await expect( - cache1.getNodesAsync([{ key: leaf10Index, level: 0 }]) + cache1.getNodesAsync([{ key: storedLeaf10.index, level: 0 }]) ).resolves.toStrictEqual([ Poseidon.hash([Field(10), Field(10), Field(20)]).toBigInt(), ]); - expectDefined(leaf20Index); + expectDefined(storedLeaf20); await expect( - cache1.getNodesAsync([{ key: leaf20Index, level: 0 }]) + cache1.getNodesAsync([{ key: storedLeaf20.index, level: 0 }]) ).resolves.toStrictEqual([ Poseidon.hash([Field(20), Field(20), Field(Field.ORDER - 1n)]).toBigInt(), ]); @@ -282,12 +298,13 @@ describe("cached linked merkle store", () => { const cache2 = new SyncCachedLinkedMerkleTreeStore(cache1); const tree2 = new LinkedMerkleTree(cache2); + await cache2.preloadKeys([5n]); const leaf1 = tree2.getLeaf(5n); - const leaf1Index = cache2.getLeafIndex(5n); + const storedLeaf1 = cache2.getLeaf(5n); expectDefined(leaf1); - expectDefined(leaf1Index); + expectDefined(storedLeaf1); await expect( - mainStore.getNodesAsync([{ key: leaf1Index, level: 0 }]) + mainStore.getNodesAsync([{ key: storedLeaf1.index, level: 0 }]) ).resolves.toStrictEqual([ Poseidon.hash([leaf1.value, leaf1.path, leaf1.nextPath]).toBigInt(), ]); @@ -295,10 +312,10 @@ describe("cached linked merkle store", () => { tree1.setLeaf(10n, 20n); const leaf2 = tree2.getLeaf(10n); - const leaf2Index = cache2.getLeafIndex(10n); + const storedLeaf2 = cache2.getLeaf(10n); expectDefined(leaf2); - expectDefined(leaf2Index); - expect(tree2.getNode(0, leaf2Index).toBigInt()).toBe( + expectDefined(storedLeaf2); + expect(tree2.getNode(0, storedLeaf2.index).toBigInt()).toBe( Poseidon.hash([leaf2.value, leaf2.path, leaf2.nextPath]).toBigInt() ); @@ -349,12 +366,12 @@ describe("cached linked merkle store", () => { // which tree1 has access to. cache2.mergeIntoParent(); - const index15 = cache2.getLeafIndex(15n); + const storedLeaf15 = cache2.getLeaf(15n); const leaf15 = tree2.getLeaf(15n); expectDefined(leaf15); - expectDefined(index15); + expectDefined(storedLeaf15); expect(tree1.getRoot().toString()).toBe(tree2.getRoot().toString()); - expect(tree1.getNode(0, index15).toString()).toBe( + expect(tree1.getNode(0, storedLeaf15.index).toString()).toBe( Poseidon.hash([leaf15.value, leaf15.path, leaf15.nextPath]).toString() ); @@ -378,9 +395,11 @@ describe("cached linked merkle store", () => { const treeCache1 = new LinkedMerkleTree(mCache); const treeCache2 = new LinkedMerkleTree(mCache2); + await mCache.preloadKeys([5n]); treeCache1.setLeaf(10n, 10n); treeCache1.setLeaf(20n, 20n); + await mCache2.preloadKeys([7n]); treeCache2.setLeaf(7n, 7n); mCache2.mergeIntoParent(); @@ -390,53 +409,53 @@ describe("cached linked merkle store", () => { expectDefined(leaves[2]); expectDefined(leaves[3]); - expect(leaves[0]).toEqual({ + expect(leaves[0]?.leaf).toEqual({ value: 0n, path: 0n, nextPath: 7n, }); - expect(leaves[1]).toEqual({ + expect(leaves[1]?.leaf).toEqual({ value: 7n, path: 7n, nextPath: 10n, }); - expect(leaves[2]).toEqual({ + expect(leaves[2]?.leaf).toEqual({ value: 10n, path: 10n, nextPath: 20n, }); - expect(leaves[3]).toEqual({ + expect(leaves[3]?.leaf).toEqual({ value: 20n, path: 20n, nextPath: Field.ORDER - 1n, }); - const leaf0Index = mCache.getLeafIndex(0n); - const leaf7Index = mCache.getLeafIndex(7n); - const leaf10Index = mCache.getLeafIndex(10n); - const leaf20Index = mCache.getLeafIndex(20n); + const storedLeaf0 = mCache.getLeaf(0n); + const storedLeaf7 = mCache.getLeaf(7n); + const storedLeaf10 = mCache.getLeaf(10n); + const storedLeaf20 = mCache.getLeaf(20n); - expectDefined(leaf0Index); + expectDefined(storedLeaf0); await expect( - mCache.getNodesAsync([{ key: leaf0Index, level: 0 }]) + mCache.getNodesAsync([{ key: storedLeaf0.index, level: 0 }]) ).resolves.toStrictEqual([ Poseidon.hash([Field(0), Field(0), Field(7)]).toBigInt(), ]); - expectDefined(leaf7Index); + expectDefined(storedLeaf7); await expect( - mCache.getNodesAsync([{ key: leaf7Index, level: 0 }]) + mCache.getNodesAsync([{ key: storedLeaf7.index, level: 0 }]) ).resolves.toStrictEqual([ Poseidon.hash([Field(7), Field(7), Field(10)]).toBigInt(), ]); - expectDefined(leaf10Index); + expectDefined(storedLeaf10); await expect( - mCache.getNodesAsync([{ key: leaf10Index, level: 0 }]) + mCache.getNodesAsync([{ key: storedLeaf10.index, level: 0 }]) ).resolves.toStrictEqual([ Poseidon.hash([Field(10), Field(10), Field(20)]).toBigInt(), ]); - expectDefined(leaf20Index); + expectDefined(storedLeaf20); await expect( - mCache.getNodesAsync([{ key: leaf20Index, level: 0 }]) + mCache.getNodesAsync([{ key: storedLeaf20.index, level: 0 }]) ).resolves.toStrictEqual([ Poseidon.hash([Field(20), Field(20), Field(Field.ORDER - 1n)]).toBigInt(), ]); From 9ffba4409be890ce0f122895a6f8169877bb2a72 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 28 Nov 2024 10:49:00 +0000 Subject: [PATCH 095/107] Remove comment --- packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts index 0ee6523f..9bbe600d 100644 --- a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts +++ b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts @@ -33,8 +33,6 @@ describe("cached linked merkle store", () => { const cache2 = new SyncCachedLinkedMerkleTreeStore(cache1); const tree2 = new LinkedMerkleTree(cache2); - // Need to preload 0n, as well since the nextPath of the leaf would have changed - // when other leaves were added. const leaf1 = tree1.getLeaf(16n); const leaf2 = tree1.getLeaf(46n); From 9b704b1ed473ca46844894a50e6c58ec7eea0e54 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 28 Nov 2024 13:22:03 +0000 Subject: [PATCH 096/107] Get BlockProductionTest working. --- .../statetransition/StateTransitionProver.ts | 17 ++++++----------- .../production/TransactionTraceService.ts | 10 +--------- .../sequencing/BlockProducerModule.ts | 3 ++- .../sequencing/TransactionExecutionService.ts | 9 ++++++--- .../src/storage/StorageDependencyFactory.ts | 3 ++- 5 files changed, 17 insertions(+), 25 deletions(-) diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index b974fa3a..11698a52 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -219,17 +219,12 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< merkleWitness.leafPrevious.leaf.hash() ); - // Only if we're doing an update. - const currentWitnessHolds = - merkleWitness.leafCurrent.merkleWitness.checkMembershipSimple( - state.stateRoot, - merkleWitness.leafCurrent.leaf.hash() - ); - - // Combine previousWitnessValid and currentWitnessHolds + // Combine previousWitnessValid and if it's an update + // it should just be true, as the prev leaf is just a dummy leaf + // so should always be true. const prevWitnessOrCurrentWitness = Provable.if( isUpdate, - currentWitnessHolds, + Bool(true), previousWitnessValid ); @@ -260,8 +255,8 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< const rootAfterFirstStep = Provable.if( isUpdate, - rootWithLeafChanged, - state.stateRoot + state.stateRoot, + rootWithLeafChanged ); // Need to check the second leaf is correct, i.e. leafCurrent. diff --git a/packages/sequencer/src/protocol/production/TransactionTraceService.ts b/packages/sequencer/src/protocol/production/TransactionTraceService.ts index f0803130..155f383d 100644 --- a/packages/sequencer/src/protocol/production/TransactionTraceService.ts +++ b/packages/sequencer/src/protocol/production/TransactionTraceService.ts @@ -266,6 +266,7 @@ export class TransactionTraceService { }> { const keys = this.allKeys(protocolTransitions.concat(stateTransitions)); + const tree = new LinkedMerkleTree(merkleStore); // TODO Consolidate await merkleStore.preloadKey(0n); const runtimeSimulationMerkleStore = new SyncCachedLinkedMerkleTreeStore( @@ -274,15 +275,6 @@ export class TransactionTraceService { await merkleStore.preloadKeys(keys.map((key) => key.toBigInt())); - // // TODO: Not efficient. Try to cache. - // for (const stateTransition of stateTransitions) { - // // eslint-disable-next-line no-await-in-loop - // await merkleStore.loadUpKeysForClosestPath( - // stateTransition.path.toBigInt() - // ); - // } - - const tree = new LinkedMerkleTree(merkleStore); const runtimeTree = new LinkedMerkleTree(runtimeSimulationMerkleStore); // const runtimeTree = new RollupMerkleTree(merkleStore); const initialRoot = tree.getRoot(); diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts index 1c567f4c..c6a14527 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts @@ -21,6 +21,7 @@ import { Block, BlockWithResult } from "../../../storage/model/Block"; import { CachedStateService } from "../../../state/state/CachedStateService"; import { MessageStorage } from "../../../storage/repositories/MessageStorage"; import { AsyncLinkedMerkleTreeStore } from "../../../state/async/AsyncLinkedMerkleTreeStore"; +import { AsyncMerkleTreeStore } from "../../../state/async/AsyncMerkleTreeStore"; import { TransactionExecutionService } from "./TransactionExecutionService"; @@ -43,7 +44,7 @@ export class BlockProducerModule extends SequencerModule { @inject("BlockQueue") private readonly blockQueue: BlockQueue, @inject("BlockTreeStore") - private readonly blockTreeStore: AsyncLinkedMerkleTreeStore, + private readonly blockTreeStore: AsyncMerkleTreeStore, private readonly executionService: TransactionExecutionService, @inject("MethodIdResolver") private readonly methodIdResolver: MethodIdResolver, diff --git a/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts index 6b5a3c25..f957c1e3 100644 --- a/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts @@ -48,6 +48,8 @@ import { UntypedStateTransition } from "../helpers/UntypedStateTransition"; import type { StateRecord } from "../BatchProducerModule"; import { CachedLinkedMerkleTreeStore } from "../../../state/merkle/CachedLinkedMerkleTreeStore"; import { AsyncLinkedMerkleTreeStore } from "../../../state/async/AsyncLinkedMerkleTreeStore"; +import { CachedMerkleTreeStore } from "../../../state/merkle/CachedMerkleTreeStore"; +import { AsyncMerkleTreeStore } from "../../../state/async/AsyncMerkleTreeStore"; const errors = { methodIdNotFound: (methodId: string) => @@ -323,7 +325,7 @@ export class TransactionExecutionService { public async generateMetadataForNextBlock( block: Block, merkleTreeStore: AsyncLinkedMerkleTreeStore, - blockHashTreeStore: AsyncLinkedMerkleTreeStore, + blockHashTreeStore: AsyncMerkleTreeStore, modifyTreeStore = true ): Promise { // Flatten diff list into a single diff by applying them over each other @@ -342,8 +344,9 @@ export class TransactionExecutionService { const inMemoryStore = await CachedLinkedMerkleTreeStore.new(merkleTreeStore); const tree = new LinkedMerkleTree(inMemoryStore); - const blockHashInMemoryStore = - await CachedLinkedMerkleTreeStore.new(blockHashTreeStore); + const blockHashInMemoryStore = new CachedMerkleTreeStore( + blockHashTreeStore + ); const blockHashTree = new BlockHashMerkleTree(blockHashInMemoryStore); await inMemoryStore.preloadKeys(Object.keys(combinedDiff).map(BigInt)); diff --git a/packages/sequencer/src/storage/StorageDependencyFactory.ts b/packages/sequencer/src/storage/StorageDependencyFactory.ts index dfd4ed6c..27c1a29b 100644 --- a/packages/sequencer/src/storage/StorageDependencyFactory.ts +++ b/packages/sequencer/src/storage/StorageDependencyFactory.ts @@ -6,6 +6,7 @@ import { import { AsyncStateService } from "../state/async/AsyncStateService"; import { AsyncLinkedMerkleTreeStore } from "../state/async/AsyncLinkedMerkleTreeStore"; +import { AsyncMerkleTreeStore } from "../state/async/AsyncMerkleTreeStore"; import { BatchStorage } from "./repositories/BatchStorage"; import { BlockQueue, BlockStorage } from "./repositories/BlockStorage"; @@ -21,7 +22,7 @@ export interface StorageDependencyMinimumDependencies extends DependencyRecord { blockStorage: DependencyDeclaration; unprovenStateService: DependencyDeclaration; unprovenMerkleStore: DependencyDeclaration; - blockTreeStore: DependencyDeclaration; + blockTreeStore: DependencyDeclaration; messageStorage: DependencyDeclaration; settlementStorage: DependencyDeclaration; transactionStorage: DependencyDeclaration; From fcf440932e9393282ccdff49c7eb0ad9b341e2ea Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:40:36 +0000 Subject: [PATCH 097/107] Add new MerkleStore Test --- .../merkle/CachedLinkedMerkleStore.test.ts | 83 ++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts index 9bbe600d..e097110a 100644 --- a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts +++ b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts @@ -1,4 +1,8 @@ -import { expectDefined, LinkedMerkleTree } from "@proto-kit/common"; +import { + expectDefined, + LinkedLeafStruct, + LinkedMerkleTree, +} from "@proto-kit/common"; import { beforeEach, expect } from "@jest/globals"; import { Field, Poseidon } from "o1js"; @@ -458,4 +462,81 @@ describe("cached linked merkle store", () => { Poseidon.hash([Field(20), Field(20), Field(Field.ORDER - 1n)]).toBigInt(), ]); }); + + it("mimic block production test && ST Prover", async () => { + // main store already has 0n and 5n paths defined. + // preloading 10n should load up 5n in the cache1 leaf and node stores. + await cache1.preloadKeys([10n, 10n, 10n]); + const state = tree1.getRoot(); + + // This is an insert as 10n is not already in the tree. + const witness1 = tree1.setLeaf(10n, 10n); + // This checks the right previous leaf was found. + expect( + witness1.leafPrevious.merkleWitness + .checkMembershipSimple( + state, + new LinkedLeafStruct({ + value: Field(witness1.leafPrevious.leaf.value), + path: Field(witness1.leafPrevious.leaf.path), + nextPath: Field(witness1.leafPrevious.leaf.nextPath), + }).hash() + ) + .toBoolean() + ).toStrictEqual(true); + + // We now look to the state after the prevLeaf is changed. + // The prev leaf should be the 5n. + const rootAfterFirstChange = + witness1.leafPrevious.merkleWitness.calculateRoot( + new LinkedLeafStruct({ + value: Field(witness1.leafPrevious.leaf.value), + path: Field(witness1.leafPrevious.leaf.path), + nextPath: Field(10n), + }).hash() + ); + expect( + witness1.leafCurrent.merkleWitness.calculateRoot(Field(0)).toBigInt() + ).toStrictEqual(rootAfterFirstChange.toBigInt()); + + // We now check that right hashing was done to get to the current root. + expect( + witness1.leafCurrent.merkleWitness + .checkMembershipSimple( + tree1.getRoot(), + new LinkedLeafStruct({ + value: Field(10n), + path: Field(10n), + nextPath: Field(witness1.leafPrevious.leaf.nextPath), + }).hash() + ) + .toBoolean() + ).toStrictEqual(true); + + // Now we update the node at 10n, + const witness2 = tree1.setLeaf(10n, 8n); + // We now check that right hashing was done to get to the current root. + expect( + witness2.leafCurrent.merkleWitness.calculateRoot( + new LinkedLeafStruct({ + value: Field(8n), + path: Field(10n), + nextPath: Field(witness2.leafCurrent.leaf.nextPath), + }).hash() + ) + ).toStrictEqual(tree1.getRoot()); + + // Now we update the node at 10n, again, + const witness3 = tree1.setLeaf(10n, 4n); + // We now check that right hashing was done to get to the current root. + expect( + witness3.leafCurrent.merkleWitness.calculateRoot( + new LinkedLeafStruct({ + value: Field(4n), + path: Field(10n), + nextPath: Field(witness2.leafCurrent.leaf.nextPath), + }).hash() + ) + ).toStrictEqual(tree1.getRoot()); + }); }); From aab6feb60d64738f7312eea5873087c2c046d1f2 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 29 Nov 2024 10:14:04 +0000 Subject: [PATCH 098/107] Make code async and remove log --- .../src/protocol/production/TransactionTraceService.ts | 1 - .../inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/sequencer/src/protocol/production/TransactionTraceService.ts b/packages/sequencer/src/protocol/production/TransactionTraceService.ts index 155f383d..85cd9fea 100644 --- a/packages/sequencer/src/protocol/production/TransactionTraceService.ts +++ b/packages/sequencer/src/protocol/production/TransactionTraceService.ts @@ -268,7 +268,6 @@ export class TransactionTraceService { const tree = new LinkedMerkleTree(merkleStore); // TODO Consolidate - await merkleStore.preloadKey(0n); const runtimeSimulationMerkleStore = new SyncCachedLinkedMerkleTreeStore( merkleStore ); diff --git a/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts b/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts index 9a6a240f..877efedb 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts @@ -56,12 +56,12 @@ export class InMemoryAsyncLinkedMerkleTreeStore }); } - public getMaximumIndexAsync() { - return Promise.resolve(this.leafStore.getMaximumIndex()); + public async getMaximumIndexAsync() { + return this.leafStore.getMaximumIndex(); } - public getLeafLessOrEqualAsync(path: bigint) { - return Promise.resolve(this.leafStore.getLeafLessOrEqual(path)); + public async getLeafLessOrEqualAsync(path: bigint) { + return this.leafStore.getLeafLessOrEqual(path); } public setLeaf(index: bigint, value: LinkedLeaf) { From a21fe8deeba57f9cd65e6fe349c9d222ce083512 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 29 Nov 2024 12:27:29 +0000 Subject: [PATCH 099/107] Update LinkedMerkleTree amd change tests --- packages/common/src/trees/LinkedMerkleTree.ts | 37 ++++++++++++------- .../test/trees/LinkedMerkleTree.test.ts | 6 +-- .../src/model/StateTransitionProvableBatch.ts | 4 +- .../merkle/CachedLinkedMerkleStore.test.ts | 14 +++---- 4 files changed, 34 insertions(+), 27 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 3c15ac45..89619704 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -74,7 +74,9 @@ export interface AbstractLinkedMerkleTree { * @param path Position of the leaf node. * @returns The witness that belongs to the leaf. */ - getWitness(path: bigint): LinkedMerkleTreeWitness; + getWitness(path: bigint): LinkedLeafAndMerkleWitness; + + dummyWitness(): LinkedMerkleTreeWitness; } export interface AbstractLinkedMerkleTreeClass { @@ -296,9 +298,12 @@ export function createLinkedMerkleTree( * @param path Position of the leaf node. * @param value New value. */ - public setLeaf(path: bigint, value?: bigint) { + public setLeaf(path: bigint, value?: bigint): LinkedMerkleWitness { if (value === undefined) { - return this.getWitness(path); + return new LinkedMerkleWitness({ + leafPrevious: this.dummy(), + leafCurrent: this.getWitness(path), + }); } const storedLeaf = this.store.getLeaf(path); const prevLeaf = this.store.getLeafLessOrEqual(path); @@ -317,7 +322,7 @@ export function createLinkedMerkleTree( if (tempIndex + 1n >= 2 ** height) { throw new Error("Index greater than maximum leaf number"); } - witnessPrevious = this.getWitness(prevLeaf.leaf.path).leafCurrent; + witnessPrevious = this.getWitness(prevLeaf.leaf.path); const newPrevLeaf = { value: prevLeaf.leaf.value, path: prevLeaf.leaf.path, @@ -355,7 +360,7 @@ export function createLinkedMerkleTree( ); return new LinkedMerkleWitness({ leafPrevious: witnessPrevious, - leafCurrent: witnessNext.leafCurrent, + leafCurrent: witnessNext, }); } @@ -390,7 +395,7 @@ export function createLinkedMerkleTree( * @param path of the leaf node. * @returns The witness that belongs to the leaf. */ - public getWitness(path: bigint): LinkedMerkleWitness { + public getWitness(path: bigint): LinkedLeafAndMerkleWitness { const storedLeaf = this.store.getLeaf(path); let leaf; let currentIndex: bigint; @@ -432,15 +437,12 @@ export function createLinkedMerkleTree( pathArray.push(sibling); currentIndex /= 2n; } - return new LinkedMerkleWitness({ - leafPrevious: this.dummy(), - leafCurrent: new LinkedLeafAndMerkleWitness({ - merkleWitness: new RollupMerkleWitnessV2({ - path: pathArray, - isLeft: isLefts, - }), - leaf: leaf, + return new LinkedLeafAndMerkleWitness({ + merkleWitness: new RollupMerkleWitnessV2({ + path: pathArray, + isLeft: isLefts, }), + leaf: leaf, }); } @@ -459,6 +461,13 @@ export function createLinkedMerkleTree( }), }); } + + public dummyWitness() { + return new LinkedMerkleWitness({ + leafPrevious: this.dummy(), + leafCurrent: this.dummy(), + }); + } }; } diff --git a/packages/common/test/trees/LinkedMerkleTree.test.ts b/packages/common/test/trees/LinkedMerkleTree.test.ts index a13d8d7b..755bc447 100644 --- a/packages/common/test/trees/LinkedMerkleTree.test.ts +++ b/packages/common/test/trees/LinkedMerkleTree.test.ts @@ -45,7 +45,7 @@ describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { tree.setLeaf(1n, 1n); tree.setLeaf(5n, 5n); - const witness = tree.getWitness(5n).leafCurrent; + const witness = tree.getWitness(5n); expect( witness.merkleWitness @@ -69,7 +69,7 @@ describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { const witness = tree.getWitness(5n); expect( - witness.leafCurrent.merkleWitness.calculateRoot(Field(6)).toBigInt() + witness.merkleWitness.calculateRoot(Field(6)).toBigInt() ).not.toStrictEqual(tree.getRoot().toBigInt()); }); @@ -79,7 +79,7 @@ describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { tree.setLeaf(1n, 1n); tree.setLeaf(5n, 5n); - const witness = tree.getWitness(5n).leafCurrent; + const witness = tree.getWitness(5n); tree.setLeaf(5n, 10n); diff --git a/packages/protocol/src/model/StateTransitionProvableBatch.ts b/packages/protocol/src/model/StateTransitionProvableBatch.ts index 6fa06257..1620610f 100644 --- a/packages/protocol/src/model/StateTransitionProvableBatch.ts +++ b/packages/protocol/src/model/StateTransitionProvableBatch.ts @@ -95,9 +95,7 @@ export class StateTransitionProvableBatch extends Struct({ batch.push(ProvableStateTransition.dummy()); transitionTypes.push(ProvableStateTransitionType.normal); witnesses.push( - new LinkedMerkleTree(new InMemoryLinkedMerkleLeafStore()).getWitness( - BigInt(0) - ) + new LinkedMerkleTree(new InMemoryLinkedMerkleLeafStore()).dummyWitness() ); } return new StateTransitionProvableBatch({ diff --git a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts index e097110a..4345ef76 100644 --- a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts +++ b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts @@ -327,19 +327,19 @@ describe("cached linked merkle store", () => { // The witness is from tree2, which comes from cache2, // but which because of the sync is really just cache1. expect( - witness.leafCurrent.merkleWitness + witness.merkleWitness .calculateRoot( Poseidon.hash([ - witness.leafCurrent.leaf.value, - witness.leafCurrent.leaf.path, - witness.leafCurrent.leaf.nextPath, + witness.leaf.value, + witness.leaf.path, + witness.leaf.nextPath, ]) ) .toString() ).toBe(tree1.getRoot().toString()); expect( - witness.leafCurrent.merkleWitness + witness.merkleWitness .calculateRoot(Poseidon.hash([Field(11), Field(5n), Field(10n)])) .toString() ).not.toBe(tree1.getRoot().toString()); @@ -347,12 +347,12 @@ describe("cached linked merkle store", () => { const witness2 = tree1.getWitness(10n); expect( - witness2.leafCurrent.merkleWitness + witness2.merkleWitness .calculateRoot( Poseidon.hash([ Field(20), Field(10n), - witness2.leafCurrent.leaf.nextPath, // This is the maximum as the the leaf 10n should be the last + witness2.leaf.nextPath, // This is the maximum as the the leaf 10n should be the last ]) ) .toString() From 77441fbd18c5aabc9102fe8e3b9af528b9ae3cf6 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 29 Nov 2024 15:20:57 +0000 Subject: [PATCH 100/107] Fix to get BP working. --- .../src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts index 4926126c..f38d9990 100644 --- a/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts @@ -31,8 +31,8 @@ export class SyncCachedLinkedMerkleTreeStore this.nodeStore.setNode(key, level, value); } - public getLeaf(index: bigint): StoredLeaf | undefined { - return this.leafStore.getLeaf(index) ?? this.parent.getLeaf(index); + public getLeaf(path: bigint): StoredLeaf | undefined { + return this.leafStore.getLeaf(path) ?? this.parent.getLeaf(path); } public setLeaf(index: bigint, value: LinkedLeaf) { @@ -42,7 +42,10 @@ export class SyncCachedLinkedMerkleTreeStore // Need to make sure we call the parent as the super will usually be empty // The tree calls this method. public getMaximumIndex(): bigint | undefined { - return this.parent.getMaximumIndex(); + return (this.leafStore.getMaximumIndex() ?? -1) > + (this.parent.getMaximumIndex() ?? -1) + ? this.leafStore.getMaximumIndex() + : this.parent.getMaximumIndex(); } public getLeafLessOrEqual(path: bigint): StoredLeaf | undefined { From 1f71c03a718700729d713b6c3d9797f3c8785f04 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 29 Nov 2024 15:44:33 +0000 Subject: [PATCH 101/107] Mixer missing from dependencies --- packages/common/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/common/package.json b/packages/common/package.json index 6fa29f05..ea6f866c 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -20,7 +20,8 @@ "lodash": "^4.17.21", "loglevel": "^1.8.1", "reflect-metadata": "^0.1.13", - "typescript-memoize": "^1.1.1" + "typescript-memoize": "^1.1.1", + "ts-mixer": "^6.0.3" }, "peerDependencies": { "o1js": "^1.1.0", From 984d2982151bfd69d4e554fced01cb82cd097b98 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Mon, 2 Dec 2024 19:46:03 +0000 Subject: [PATCH 102/107] Add in code to get GraphQL running --- .../api/src/graphql/VanillaGraphqlModules.ts | 2 +- .../api/src/graphql/modules/LeafResolver.ts | 28 +++++++++ .../modules/LinkedMerkleWitnessResolver.ts | 62 +++++++++++++++++++ packages/api/src/index.ts | 1 + .../graphql/GraphqlQueryTransportModule.ts | 34 ++++++---- .../sdk/src/query/StateServiceQueryModule.ts | 19 +++--- .../src/helpers/query/QueryBuilderFactory.ts | 6 +- .../src/helpers/query/QueryTransportModule.ts | 6 +- packages/stack/src/scripts/graphql/server.ts | 4 +- packages/stack/test/graphql/graphql.test.ts | 6 +- 10 files changed, 138 insertions(+), 30 deletions(-) create mode 100644 packages/api/src/graphql/modules/LeafResolver.ts create mode 100644 packages/api/src/graphql/modules/LinkedMerkleWitnessResolver.ts diff --git a/packages/api/src/graphql/VanillaGraphqlModules.ts b/packages/api/src/graphql/VanillaGraphqlModules.ts index e7859ec9..f94d2912 100644 --- a/packages/api/src/graphql/VanillaGraphqlModules.ts +++ b/packages/api/src/graphql/VanillaGraphqlModules.ts @@ -6,7 +6,7 @@ import { QueryGraphqlModule } from "./modules/QueryGraphqlModule"; import { BatchStorageResolver } from "./modules/BatchStorageResolver"; import { NodeStatusResolver } from "./modules/NodeStatusResolver"; import { BlockResolver } from "./modules/BlockResolver"; -import { MerkleWitnessResolver } from "./modules/MerkleWitnessResolver"; +import { LinkedMerkleWitnessResolver as MerkleWitnessResolver } from "./modules/LinkedMerkleWitnessResolver"; export type VanillaGraphqlModulesRecord = { MempoolResolver: typeof MempoolResolver; diff --git a/packages/api/src/graphql/modules/LeafResolver.ts b/packages/api/src/graphql/modules/LeafResolver.ts new file mode 100644 index 00000000..56806023 --- /dev/null +++ b/packages/api/src/graphql/modules/LeafResolver.ts @@ -0,0 +1,28 @@ +import { Field, ObjectType } from "type-graphql"; +import { LinkedLeafStruct } from "@proto-kit/common"; + +@ObjectType() +export class LeafDTO { + public static fromServiceLayerModel(leaf: LinkedLeafStruct) { + return new LeafDTO( + leaf.value.toString(), + leaf.path.toString(), + leaf.nextPath.toString() + ); + } + + @Field() + value: string; + + @Field() + path: string; + + @Field() + nextPath: string; + + private constructor(value: string, path: string, nextPath: string) { + this.value = value; + this.path = path; + this.nextPath = nextPath; + } +} diff --git a/packages/api/src/graphql/modules/LinkedMerkleWitnessResolver.ts b/packages/api/src/graphql/modules/LinkedMerkleWitnessResolver.ts new file mode 100644 index 00000000..1538506f --- /dev/null +++ b/packages/api/src/graphql/modules/LinkedMerkleWitnessResolver.ts @@ -0,0 +1,62 @@ +import { Arg, Field, ObjectType, Query } from "type-graphql"; +import { inject } from "tsyringe"; +import { + LinkedLeafAndMerkleWitness, + LinkedMerkleTree, +} from "@proto-kit/common"; +import { CachedLinkedMerkleTreeStore } from "@proto-kit/sequencer/dist/state/merkle/CachedLinkedMerkleTreeStore"; +import { AsyncLinkedMerkleTreeStore } from "@proto-kit/sequencer/dist/state/async/AsyncLinkedMerkleTreeStore"; + +import { GraphqlModule, graphqlModule } from "../GraphqlModule"; + +import { MerkleWitnessDTO } from "./MerkleWitnessResolver"; +import { LeafDTO } from "./LeafResolver"; + +@ObjectType() +export class LinkedMerkleWitnessDTO { + public static fromServiceLayerObject(witness: LinkedLeafAndMerkleWitness) { + const { leaf, merkleWitness } = witness; + const leafDTO = LeafDTO.fromServiceLayerModel(leaf); + const witnessDTO = MerkleWitnessDTO.fromServiceLayerObject(merkleWitness); + return new LinkedMerkleWitnessDTO(leafDTO, witnessDTO); + } + + public constructor(leaf: LeafDTO, witness: MerkleWitnessDTO) { + this.leaf = leaf; + this.merkleWitness = new MerkleWitnessDTO( + witness.siblings, + witness.isLefts + ); + } + + @Field(() => LeafDTO) + public leaf: LeafDTO; + + @Field(() => MerkleWitnessDTO) + public merkleWitness: MerkleWitnessDTO; +} + +@graphqlModule() +export class LinkedMerkleWitnessResolver extends GraphqlModule { + public constructor( + @inject("AsyncMerkleStore") + private readonly treeStore: AsyncLinkedMerkleTreeStore + ) { + super(); + } + + @Query(() => LinkedMerkleWitnessDTO, { + description: + "Allows retrieval of merkle witnesses corresponding to a specific path in the appchain's state tree. These proves are generally retrieved from the current 'proven' state", + }) + public async witness(@Arg("path") path: string) { + const syncStore = await CachedLinkedMerkleTreeStore.new(this.treeStore); + + const tree = new LinkedMerkleTree(syncStore); + await syncStore.preloadKey(BigInt(path)); + + const witness = tree.getWitness(BigInt(path)); + + return LinkedMerkleWitnessDTO.fromServiceLayerObject(witness); + } +} diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 9c7d5dbe..7f734c20 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -9,4 +9,5 @@ export * from "./graphql/modules/NodeStatusResolver"; export * from "./graphql/modules/AdvancedNodeStatusResolver"; export * from "./graphql/services/NodeStatusService"; export * from "./graphql/modules/MerkleWitnessResolver"; +export * from "./graphql/modules/LinkedMerkleWitnessResolver"; export * from "./graphql/VanillaGraphqlModules"; diff --git a/packages/sdk/src/graphql/GraphqlQueryTransportModule.ts b/packages/sdk/src/graphql/GraphqlQueryTransportModule.ts index 7a28965e..748490f6 100644 --- a/packages/sdk/src/graphql/GraphqlQueryTransportModule.ts +++ b/packages/sdk/src/graphql/GraphqlQueryTransportModule.ts @@ -2,7 +2,7 @@ import { QueryTransportModule } from "@proto-kit/sequencer"; import { Field } from "o1js"; import { inject, injectable } from "tsyringe"; import { gql } from "@urql/core"; -import { RollupMerkleTreeWitness } from "@proto-kit/common"; +import { LinkedLeafAndMerkleWitness } from "@proto-kit/common"; import { AppChainModule } from "../appChain/AppChainModule"; @@ -64,12 +64,19 @@ export class GraphqlQueryTransportModule public async merkleWitness( key: Field - ): Promise { + ): Promise { const query = gql` query Witness($path: String!) { witness(path: $path) { - siblings - isLefts + leaf { + value + path + nextPath + } + merkleWitness { + siblings + isLefts + } } } `; @@ -87,8 +94,9 @@ export class GraphqlQueryTransportModule } if ( - witnessJson.siblings === undefined || - witnessJson.isLefts === undefined + witnessJson.leaf === undefined || + witnessJson.merkleWitness.siblings === undefined || + witnessJson.merkleWitness.isLefts === undefined ) { throw new Error("Witness json object malformed"); } @@ -96,12 +104,16 @@ export class GraphqlQueryTransportModule assertStringArray(witnessJson.siblings); assertBooleanArray(witnessJson.isLefts); - return new RollupMerkleTreeWitness( - RollupMerkleTreeWitness.fromJSON({ + return new LinkedLeafAndMerkleWitness( + LinkedLeafAndMerkleWitness.fromJSON({ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - path: witnessJson.siblings, - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - isLeft: witnessJson.isLefts, + leaf: witnessJson.leaf, + merkleWitness: { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + path: witnessJson.merkleWitness.siblings, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + isLeft: witnessJson.merkleWitness.isLefts, + }, }) ); } diff --git a/packages/sdk/src/query/StateServiceQueryModule.ts b/packages/sdk/src/query/StateServiceQueryModule.ts index ec78f44a..deb4194e 100644 --- a/packages/sdk/src/query/StateServiceQueryModule.ts +++ b/packages/sdk/src/query/StateServiceQueryModule.ts @@ -1,14 +1,17 @@ import { AsyncStateService, - CachedMerkleTreeStore, QueryTransportModule, Sequencer, SequencerModulesRecord, - AsyncMerkleTreeStore, } from "@proto-kit/sequencer"; import { Field } from "o1js"; import { inject, injectable } from "tsyringe"; -import { RollupMerkleTree, RollupMerkleTreeWitness } from "@proto-kit/common"; +import { + LinkedLeafAndMerkleWitness, + LinkedMerkleTree, +} from "@proto-kit/common"; +import { CachedLinkedMerkleTreeStore } from "@proto-kit/sequencer/dist/state/merkle/CachedLinkedMerkleTreeStore"; +import { AsyncLinkedMerkleTreeStore } from "@proto-kit/sequencer/dist/state/async/AsyncLinkedMerkleTreeStore"; import { AppChainModule } from "../appChain/AppChainModule"; @@ -29,8 +32,8 @@ export class StateServiceQueryModule ); } - public get treeStore(): AsyncMerkleTreeStore { - return this.sequencer.dependencyContainer.resolve("AsyncMerkleStore"); + public get treeStore(): AsyncLinkedMerkleTreeStore { + return this.sequencer.dependencyContainer.resolve("AsyncLinkedMerkleStore"); } public get(key: Field) { @@ -39,11 +42,11 @@ export class StateServiceQueryModule public async merkleWitness( path: Field - ): Promise { - const syncStore = new CachedMerkleTreeStore(this.treeStore); + ): Promise { + const syncStore = await CachedLinkedMerkleTreeStore.new(this.treeStore); await syncStore.preloadKey(path.toBigInt()); - const tree = new RollupMerkleTree(syncStore); + const tree = new LinkedMerkleTree(syncStore); return tree.getWitness(path.toBigInt()); } diff --git a/packages/sequencer/src/helpers/query/QueryBuilderFactory.ts b/packages/sequencer/src/helpers/query/QueryBuilderFactory.ts index eb69ca10..1029f5d5 100644 --- a/packages/sequencer/src/helpers/query/QueryBuilderFactory.ts +++ b/packages/sequencer/src/helpers/query/QueryBuilderFactory.ts @@ -1,4 +1,4 @@ -import { TypedClass, RollupMerkleTreeWitness } from "@proto-kit/common"; +import { TypedClass, LinkedLeafAndMerkleWitness } from "@proto-kit/common"; import { Runtime, RuntimeModule, @@ -24,13 +24,13 @@ export type PickByType = { export interface QueryGetterState { get: () => Promise; path: () => string; - merkleWitness: () => Promise; + merkleWitness: () => Promise; } export interface QueryGetterStateMap { get: (key: Key) => Promise; path: (key: Key) => string; - merkleWitness: (key: Key) => Promise; + merkleWitness: (key: Key) => Promise; } export type PickStateProperties = PickByType>; diff --git a/packages/sequencer/src/helpers/query/QueryTransportModule.ts b/packages/sequencer/src/helpers/query/QueryTransportModule.ts index eae3290c..0998d119 100644 --- a/packages/sequencer/src/helpers/query/QueryTransportModule.ts +++ b/packages/sequencer/src/helpers/query/QueryTransportModule.ts @@ -1,7 +1,9 @@ import { Field } from "o1js"; -import { RollupMerkleTreeWitness } from "@proto-kit/common"; +import { LinkedLeafAndMerkleWitness } from "@proto-kit/common"; export interface QueryTransportModule { get: (key: Field) => Promise; - merkleWitness: (key: Field) => Promise; + merkleWitness: ( + key: Field + ) => Promise; } diff --git a/packages/stack/src/scripts/graphql/server.ts b/packages/stack/src/scripts/graphql/server.ts index 6f6cc3d8..8f074fdf 100644 --- a/packages/stack/src/scripts/graphql/server.ts +++ b/packages/stack/src/scripts/graphql/server.ts @@ -40,7 +40,7 @@ import { GraphqlSequencerModule, GraphqlServer, MempoolResolver, - MerkleWitnessResolver, + LinkedMerkleWitnessResolver, NodeStatusResolver, QueryGraphqlModule, BlockResolver, @@ -122,7 +122,7 @@ export async function startServer() { BatchStorageResolver, BlockResolver, NodeStatusResolver, - MerkleWitnessResolver, + MerkleWitnessResolver: LinkedMerkleWitnessResolver, }, config: { diff --git a/packages/stack/test/graphql/graphql.test.ts b/packages/stack/test/graphql/graphql.test.ts index fa1c1860..e15a8886 100644 --- a/packages/stack/test/graphql/graphql.test.ts +++ b/packages/stack/test/graphql/graphql.test.ts @@ -170,8 +170,8 @@ describe("graphql client test", () => { expect(witness).toBeDefined(); // Check if this works, i.e. if it correctly parsed - expect(witness!.calculateRoot(Field(0)).toBigInt()).toBeGreaterThanOrEqual( - 0n - ); + expect( + witness!.merkleWitness.calculateRoot(Field(0)).toBigInt() + ).toBeGreaterThanOrEqual(0n); }); }); From d084e8051fbdb111bd4360eb30c0cc653b14879f Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:07:16 +0000 Subject: [PATCH 103/107] Fix test error --- packages/sdk/src/graphql/GraphqlQueryTransportModule.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sdk/src/graphql/GraphqlQueryTransportModule.ts b/packages/sdk/src/graphql/GraphqlQueryTransportModule.ts index 748490f6..c69122f7 100644 --- a/packages/sdk/src/graphql/GraphqlQueryTransportModule.ts +++ b/packages/sdk/src/graphql/GraphqlQueryTransportModule.ts @@ -101,8 +101,8 @@ export class GraphqlQueryTransportModule throw new Error("Witness json object malformed"); } - assertStringArray(witnessJson.siblings); - assertBooleanArray(witnessJson.isLefts); + assertStringArray(witnessJson.merkleWitness.siblings); + assertBooleanArray(witnessJson.merkleWitness.isLefts); return new LinkedLeafAndMerkleWitness( LinkedLeafAndMerkleWitness.fromJSON({ From 21a07499f86b5bc727be45f37f42df4c4852cb90 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Tue, 3 Dec 2024 09:38:10 +0000 Subject: [PATCH 104/107] Get old merkle tree test working --- packages/common/src/trees/LinkedMerkleTree.ts | 3 ++- packages/common/src/trees/RollupMerkleTree.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 89619704..34fe5e84 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -7,11 +7,12 @@ import { range } from "../utils"; import { LinkedMerkleTreeStore } from "./LinkedMerkleTreeStore"; import { AbstractMerkleWitness, + createMerkleTree, maybeSwap, - RollupMerkleTreeWitness, } from "./RollupMerkleTree"; import { InMemoryLinkedMerkleLeafStore } from "./InMemoryLinkedMerkleLeafStore"; +const RollupMerkleTreeWitness = createMerkleTree(40).WITNESS; export class LinkedLeafStruct extends Struct({ value: Field, path: Field, diff --git a/packages/common/src/trees/RollupMerkleTree.ts b/packages/common/src/trees/RollupMerkleTree.ts index 32d98acc..b8b245d1 100644 --- a/packages/common/src/trees/RollupMerkleTree.ts +++ b/packages/common/src/trees/RollupMerkleTree.ts @@ -347,7 +347,7 @@ export function createMerkleTree(height: number): AbstractMerkleTreeClass { }; } -export class RollupMerkleTree extends createMerkleTree(40) {} +export class RollupMerkleTree extends createMerkleTree(256) {} export class RollupMerkleTreeWitness extends RollupMerkleTree.WITNESS {} /** From 7671c79c5200fbefa07fca9b9c0a1a29ccb9a995 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Wed, 4 Dec 2024 08:52:42 +0000 Subject: [PATCH 105/107] Change code in server.ts for merkleWitness --- packages/stack/src/scripts/graphql/server.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/stack/src/scripts/graphql/server.ts b/packages/stack/src/scripts/graphql/server.ts index 8f074fdf..b70ae125 100644 --- a/packages/stack/src/scripts/graphql/server.ts +++ b/packages/stack/src/scripts/graphql/server.ts @@ -40,7 +40,7 @@ import { GraphqlSequencerModule, GraphqlServer, MempoolResolver, - LinkedMerkleWitnessResolver, + LinkedMerkleWitnessResolver as MerkleWitnessResolver, NodeStatusResolver, QueryGraphqlModule, BlockResolver, @@ -122,7 +122,7 @@ export async function startServer() { BatchStorageResolver, BlockResolver, NodeStatusResolver, - MerkleWitnessResolver: LinkedMerkleWitnessResolver, + MerkleWitnessResolver, }, config: { From 1e7d42018e667e84bb702b75d7183a71fc576fb5 Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Wed, 4 Dec 2024 13:56:21 +0000 Subject: [PATCH 106/107] Settlement changes --- packages/common/src/trees/LinkedMerkleTree.ts | 4 +++- .../contracts/SettlementSmartContract.ts | 2 +- .../settlement/messages/OutgoingMessageArgument.ts | 12 +++++++++--- .../sequencer/src/settlement/SettlementModule.ts | 14 ++++++++------ packages/sequencer/test/settlement/Settlement.ts | 4 ++-- 5 files changed, 23 insertions(+), 13 deletions(-) diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/LinkedMerkleTree.ts index 34fe5e84..66d67ea9 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/LinkedMerkleTree.ts @@ -78,6 +78,8 @@ export interface AbstractLinkedMerkleTree { getWitness(path: bigint): LinkedLeafAndMerkleWitness; dummyWitness(): LinkedMerkleTreeWitness; + + dummy(): LinkedLeafAndMerkleWitness; } export interface AbstractLinkedMerkleTreeClass { @@ -447,7 +449,7 @@ export function createLinkedMerkleTree( }); } - private dummy(): LinkedLeafAndMerkleWitness { + public dummy(): LinkedLeafAndMerkleWitness { return new LinkedLeafAndMerkleWitness({ merkleWitness: new RollupMerkleTreeWitness({ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions diff --git a/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts b/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts index a0966ce3..54e9e3ba 100644 --- a/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts +++ b/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts @@ -320,7 +320,7 @@ export class SettlementSmartContract // Check witness const path = Path.fromKey(mapPath, Field, counter); - args.witness + args.witness.merkleWitness .checkMembership( stateRoot, path, diff --git a/packages/protocol/src/settlement/messages/OutgoingMessageArgument.ts b/packages/protocol/src/settlement/messages/OutgoingMessageArgument.ts index f7cf9ab2..112383e5 100644 --- a/packages/protocol/src/settlement/messages/OutgoingMessageArgument.ts +++ b/packages/protocol/src/settlement/messages/OutgoingMessageArgument.ts @@ -1,17 +1,23 @@ import { Bool, Provable, Struct } from "o1js"; -import { RollupMerkleTreeWitness } from "@proto-kit/common"; +import { + InMemoryLinkedMerkleLeafStore, + LinkedLeafAndMerkleWitness, + LinkedMerkleTree, +} from "@proto-kit/common"; import { Withdrawal } from "./Withdrawal"; export const OUTGOING_MESSAGE_BATCH_SIZE = 1; export class OutgoingMessageArgument extends Struct({ - witness: RollupMerkleTreeWitness, + witness: LinkedLeafAndMerkleWitness, value: Withdrawal, }) { public static dummy(): OutgoingMessageArgument { return new OutgoingMessageArgument({ - witness: RollupMerkleTreeWitness.dummy(), + witness: new LinkedMerkleTree( + new InMemoryLinkedMerkleLeafStore() + ).dummy(), value: Withdrawal.dummy(), }); } diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index 85887757..3063da3e 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -29,8 +29,8 @@ import { EventEmittingComponent, log, noop, - RollupMerkleTree, AreProofsEnabled, + LinkedMerkleTree, } from "@proto-kit/common"; import { Runtime, RuntimeModulesRecord } from "@proto-kit/module"; @@ -43,11 +43,11 @@ import { SettlementStorage } from "../storage/repositories/SettlementStorage"; import { MessageStorage } from "../storage/repositories/MessageStorage"; import type { MinaBaseLayer } from "../protocol/baselayer/MinaBaseLayer"; import { Batch, SettleableBatch } from "../storage/model/Batch"; -import { AsyncMerkleTreeStore } from "../state/async/AsyncMerkleTreeStore"; -import { CachedMerkleTreeStore } from "../state/merkle/CachedMerkleTreeStore"; import { BlockProofSerializer } from "../protocol/production/helpers/BlockProofSerializer"; import { Settlement } from "../storage/model/Settlement"; import { FeeStrategy } from "../protocol/baselayer/fees/FeeStrategy"; +import { AsyncLinkedMerkleTreeStore } from "../state/async/AsyncLinkedMerkleTreeStore"; +import { CachedLinkedMerkleTreeStore } from "../state/merkle/CachedLinkedMerkleTreeStore"; import { IncomingMessageAdapter } from "./messages/IncomingMessageAdapter"; import type { OutgoingMessageQueue } from "./messages/WithdrawalQueue"; @@ -105,7 +105,7 @@ export class SettlementModule @inject("OutgoingMessageQueue") private readonly outgoingMessageQueue: OutgoingMessageQueue, @inject("AsyncMerkleStore") - private readonly merkleTreeStore: AsyncMerkleTreeStore, + private readonly merkleTreeStore: AsyncLinkedMerkleTreeStore, private readonly blockProofSerializer: BlockProofSerializer, @inject("TransactionSender") private readonly transactionSender: MinaTransactionSender, @@ -206,8 +206,10 @@ export class SettlementModule const { settlement } = this.getContracts(); - const cachedStore = new CachedMerkleTreeStore(this.merkleTreeStore); - const tree = new RollupMerkleTree(cachedStore); + const cachedStore = await CachedLinkedMerkleTreeStore.new( + this.merkleTreeStore + ); + const tree = new LinkedMerkleTree(cachedStore); const [withdrawalModule, withdrawalStateName] = this.getSettlementModuleConfig().withdrawalStatePath.split("."); diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index 7735552f..b5f50b98 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -1,5 +1,5 @@ /* eslint-disable no-inner-declarations */ -import { log, mapSequential, RollupMerkleTree } from "@proto-kit/common"; +import { LinkedMerkleTree, log, mapSequential } from "@proto-kit/common"; import { VanillaProtocolModules } from "@proto-kit/library"; import { Runtime } from "@proto-kit/module"; import { @@ -274,7 +274,7 @@ export const settlementTestFn = ( batch!.proof.publicInput.map((x) => Field(x)) ); expect(input.stateRoot.toBigInt()).toStrictEqual( - RollupMerkleTree.EMPTY_ROOT + LinkedMerkleTree.EMPTY_ROOT ); const lastBlock = await blockQueue.getLatestBlock(); From 4261288b3bc4e52912f01da0bd267cf5d80e82dc Mon Sep 17 00:00:00 2001 From: ejMina226 <118474890+ejMina226@users.noreply.github.com> Date: Fri, 6 Dec 2024 08:13:50 +0000 Subject: [PATCH 107/107] Remove unneeded code --- .../merkle/CachedLinkedMerkleTreeStore.ts | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts index 69514cb9..5ddae4fd 100644 --- a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts @@ -153,31 +153,6 @@ export class CachedLinkedMerkleTreeStore return Object.values(this.writeCache.leaves); } - // This ensures all the keys needed to be loaded - // to find the closest path are loaded. - // A bit repetitive as we basically repeat the process - // (without the loading) when we find the closest leaf. - // TODO: see how we could use a returned value. - public async loadUpKeysForClosestPath(path: bigint): Promise { - let largestLeaf = this.getLeaf(0n); - if (largestLeaf === undefined) { - throw Error("Path 0n should be defined."); - } - while (largestLeaf.leaf.nextPath <= path) { - let nextLeaf = this.getLeaf(largestLeaf.leaf.nextPath); - // This means the nextPath wasn't preloaded and we have to load it. - if (nextLeaf === undefined) { - // eslint-disable-next-line no-await-in-loop - await this.preloadKey(largestLeaf.leaf.nextPath); - nextLeaf = this.getLeaf(largestLeaf.leaf.nextPath); - if (nextLeaf === undefined) { - throw Error(" Next Path is defined but not fetched"); - } - } - largestLeaf = nextLeaf; - } - } - // This resets the cache (not the in memory tree). public resetWrittenTree() { this.writeCache = { nodes: {}, leaves: {} };