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/128] 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 000000000..67bb28014 --- /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/128] 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 67bb28014..66dcbd716 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/128] 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 66dcbd716..5f1990ec8 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/128] 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 5f1990ec8..0d6d57a69 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 44049e805..ff913b393 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/128] 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 0d6d57a69..6d3265009 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 000000000..f113c8d60 --- /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/128] 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 000000000..907d2bcd2 --- /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 f113c8d60..9d9f4e4f0 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 ff913b393..bd61171fa 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/128] 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 6d3265009..e7405d37a 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/128] 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 2309ba770..6addb04f6 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 907d2bcd2..8fd2b666c 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 9d9f4e4f0..2df34863b 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 87cea4a29..5f78b1c0a 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/128] 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 8fd2b666c..433a8c80f 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 2df34863b..45983b838 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 208211217..51ef2c6f9 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/128] 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 433a8c80f..07d1f1462 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 e7405d37a..a6911eda2 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 45983b838..9127d008c 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 000000000..8de8feb54 --- /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/128] 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 07d1f1462..54ec04dff 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/128] 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 a6911eda2..857fe85a2 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 9127d008c..00ae5353c 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/128] 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 54ec04dff..7cb9b3447 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 857fe85a2..bf27b6632 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 00ae5353c..fec948740 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/128] 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 51ef2c6f9..208211217 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/128] 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 bf27b6632..dc938490e 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/128] 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 7cb9b3447..889b77059 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 dc938490e..36458be3e 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/128] 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 36458be3e..e21e1aaf2 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/128] 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 e21e1aaf2..19bb53f00 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/128] 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 19bb53f00..724fd38b1 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/128] 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 889b77059..5626c0d1f 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 724fd38b1..1682b1cfd 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 fec948740..92e8e11fb 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/128] 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 1682b1cfd..33bb70006 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/128] 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 33bb70006..138b42338 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/128] 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 5626c0d1f..b59a8b37a 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/128] 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 b59a8b37a..2628f23c0 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 138b42338..01da5fd7d 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 92e8e11fb..8ffe34201 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/128] 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 ff271f607..2c6731a4c 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 a002c7db9..fbe2930e5 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/128] 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 01da5fd7d..d0d2dc08e 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 bd61171fa..a5541adf1 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 fbe2930e5..1450f685a 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/128] 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 d0d2dc08e..b9bd73f0c 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/128] 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 2c6731a4c..43a2265f0 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/128] 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 43a2265f0..f8deae37d 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/128] 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 b9bd73f0c..df4b76e8c 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 a5541adf1..57f460673 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 f8deae37d..946afe061 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 1450f685a..645586147 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/128] 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 cbd7283be..bb363b6bf 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 2b55aac49..4e24c6cbf 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/128] 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 df4b76e8c..af80e9556 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 4e24c6cbf..314c9f7ea 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 f16ea9e33..ad61cce4c 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 3408f7b5a..2da8d0eeb 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/128] 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 314c9f7ea..beda35bb1 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/128] 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 af80e9556..93068ab39 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 beda35bb1..04595d3e6 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 e69de29bb..000000000 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/128] 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 04595d3e6..580ff4c00 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/128] 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 93068ab39..d37f01ab1 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/128] 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 d37f01ab1..b1c1ed1f6 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/128] 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 b1c1ed1f6..c6bbd0800 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 8ffe34201..ca295aea8 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 27c3ae6e1..b7dd27174 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 a2e122bca..a028006ee 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/128] 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 2da8d0eeb..5c17bda3a 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/128] 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 580ff4c00..ad95c16af 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/128] 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 ad95c16af..abc48363a 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/128] 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 6addb04f6..59886b4ae 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 c6bbd0800..eb7925199 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 57f460673..b8b245d16 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 2452960e8..ce301bb7a 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/128] 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 eb7925199..fe38dee55 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/128] 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 fe38dee55..66f5e220c 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 ce301bb7a..5d7186e13 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/128] 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 66f5e220c..64f594db4 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 5d7186e13..b0a3ea0e4 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 ad61cce4c..1a475644e 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/128] 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 8de8feb54..9c50a0a52 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/128] 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 59886b4ae..a370acf39 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 9c50a0a52..c103e8486 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/128] 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 c103e8486..ba6cdea09 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 b7dd27174..69f6dc088 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/128] 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 ba6cdea09..bcc1e9822 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 69f6dc088..8612fabea 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/128] 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 bcc1e9822..c988c54dd 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 8612fabea..61065de9e 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/128] 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 61065de9e..e24c1b21a 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/128] 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 000000000..e24c1b21a --- /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 e24c1b21a..27c3ae6e1 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/128] 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 967d34ac4..3e5a4052b 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 1a475644e..1a717fa35 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 e24c1b21a..0900ada87 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/128] 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 abc48363a..1378a8b38 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/128] 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 000000000..b0a3ea0e4 --- /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 b0a3ea0e4..2452960e8 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/128] 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 1a717fa35..06121170d 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 000000000..fb01e5426 --- /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 a028006ee..a2e122bca 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/128] 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 c988c54dd..01d5b1465 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 0900ada87..df0b11d76 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 000000000..05779d1ae --- /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/128] 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 3e5a4052b..186597e74 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 106833f48..1c567f4c8 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 ed660d8fa..dfd4ed6c7 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 963909589..be06030ec 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/128] 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 df0b11d76..9408013ea 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 000000000..2b4903dc5 --- /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/128] 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 64f594db4..bc2debe93 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/128] 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 9408013ea..4f50f6787 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 2b4903dc5..83163bb1b 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 4d882d7db..fd6884263 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/128] 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 bc2debe93..c35a093a6 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/128] 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 c35a093a6..2cccf2536 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/128] 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 01d5b1465..dd65a2183 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 4f50f6787..ca9d7d79b 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 83163bb1b..7955ad3ec 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/128] 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 05779d1ae..1dedbe481 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 7955ad3ec..a0ea3eb60 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/128] 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 fd6884263..1b6cbf378 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/128] 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 2628f23c0..e65333f4d 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 ca9d7d79b..6b151b582 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 a0ea3eb60..96f8eda84 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/128] 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 186597e74..95561a0b6 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 6b151b582..98c4d8e95 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/128] 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 2cccf2536..577ac2bd1 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/128] 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 e65333f4d..af9eff6af 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 577ac2bd1..9dfe45aa7 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 ca295aea8..e7e53af3f 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 dd65a2183..4d4b8d652 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 98c4d8e95..40239f3de 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 96f8eda84..d54b233f5 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/128] 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 af9eff6af..892cdc3d6 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 9dfe45aa7..4fa116433 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 e7e53af3f..18b9fe774 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 40239f3de..62668fb0e 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 1dedbe481..1b9021718 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 d54b233f5..07c17b3e3 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/128] 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 4fa116433..a51d456a8 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 fb01e5426..4102ff01c 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 07c17b3e3..10c1e7ffd 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/128] 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 e9bd6a9f8..80e10eeb0 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 208211217..51ef2c6f9 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 000000000..139b6a564 --- /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/128] 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 a51d456a8..8c9696676 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 b0a3ea0e4..e21907db5 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 62668fb0e..60bb46bfb 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/128] 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 892cdc3d6..4d1c93130 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 8c9696676..aa0878b1c 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 18b9fe774..7bcbc9ca7 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 e21907db5..6bc70e08e 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 dae2a7b7a..6b5a3c255 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 4d4b8d652..b666b0998 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 60bb46bfb..27d4c0ffe 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 4102ff01c..a8d4976e6 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/128] 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 10c1e7ffd..f68df43cb 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/128] 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 f68df43cb..f56aa93ff 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/128] 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 1378a8b38..a06db8430 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/128] 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 a06db8430..f5458dbf8 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/128] 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 f5458dbf8..aba6abba0 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/128] 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 aba6abba0..9faf1ae7d 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/128] 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 06121170d..3734a1928 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/128] 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 3734a1928..4b5386d7a 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/128] 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 aa0878b1c..9701b6231 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 9faf1ae7d..861f083d9 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/128] 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 9701b6231..ea745f446 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 b8b245d16..32d98acc3 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 861f083d9..a246b7b00 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/128] 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 000000000..7dfea9ddd --- /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 ea745f446..7723eaf1e 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 7bcbc9ca7..7bf19a2ff 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 6bc70e08e..a6d4e5e33 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 a246b7b00..b18865f45 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 4b5386d7a..f0803130d 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 b666b0998..ef8d10f6f 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/128] 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 b18865f45..b974fa3a5 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/128] 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 7dfea9ddd..c74e9d2a8 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 a370acf39..1a24aacb3 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 000000000..4719fb68e --- /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 4d1c93130..000000000 --- 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 390b87fc8..c042c0d3a 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 7723eaf1e..efdba697b 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 7bf19a2ff..dae7710d2 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 ef8d10f6f..7ec4f9520 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 27d4c0ffe..8f9881117 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 a8d4976e6..32a5e5ee3 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 1b9021718..444707d16 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/128] 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 1a24aacb3..4b8dfc759 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 000000000..6d673c3f4 --- /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 efdba697b..6d2742dd3 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 dae7710d2..0ce6d352b 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 139b6a564..7616a546a 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 bb363b6bf..6fa06257f 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 444707d16..9a6a240fc 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/128] 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 6d2742dd3..b332af0a6 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 a6d4e5e33..a13d8d7b2 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/128] 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 0ce6d352b..5099adc08 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 8f9881117..fb1b7160b 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 32a5e5ee3..96dbe9486 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/128] 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 4719fb68e..0af12a83c 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 b332af0a6..c3ba818fe 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 5099adc08..f8efefd65 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 7ec4f9520..e2e6eb6f4 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 fb1b7160b..1d08282fb 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 96dbe9486..4926126c3 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/128] 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 c3ba818fe..3c15ac45a 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/128] 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 1d08282fb..69514cb97 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 f56aa93ff..0ee6523f5 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/128] 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 0ee6523f5..9bbe600d8 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/128] 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 b974fa3a5..11698a524 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 f0803130d..155f383d8 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 1c567f4c8..c6a145279 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 6b5a3c255..f957c1e35 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 dfd4ed6c7..27c1a29b2 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/128] 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 9bbe600d8..e097110af 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/128] 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 155f383d8..85cd9fea5 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 9a6a240fc..877efedbd 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/128] 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 3c15ac45a..896197040 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 a13d8d7b2..755bc4473 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 6fa06257f..1620610f2 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 e097110af..4345ef761 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/128] 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 4926126c3..f38d9990b 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/128] 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 6fa29f054..ea6f866c4 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/128] 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 e7859ec9d..f94d29128 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 000000000..568060233 --- /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 000000000..1538506f8 --- /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 9c7d5dbef..7f734c208 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 7a28965e8..748490f67 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 ec78f44aa..deb4194e8 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 eb69ca104..1029f5d57 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 eae3290c3..0998d1194 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 6f6cc3d86..8f074fdf9 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 fa1c18603..e15a8886f 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/128] 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 748490f67..c69122f74 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/128] 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 896197040..34fe5e841 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 32d98acc3..b8b245d16 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/128] 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 8f074fdf9..b70ae1255 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/128] 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 34fe5e841..66d67ea9f 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 a0966ce37..54e9e3bac 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 f7cf9ab2b..112383e59 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 85887757b..3063da3ef 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 7735552fb..b5f50b987 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/128] 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 69514cb97..5ddae4fd8 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: {} }; From c6a1b8665e818ba945b2b6c2eb30134a196291c9 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 4 Apr 2025 13:18:27 +0200 Subject: [PATCH 108/128] Moved trees in separate packages --- packages/common/src/index.ts | 16 +++++++-------- .../src/trees/VirtualMerkleTreeStore.ts | 20 ------------------- .../{ => lmt}/InMemoryLinkedLeafStore.ts | 0 .../InMemoryLinkedMerkleLeafStore.ts | 2 +- .../src/trees/{ => lmt}/LinkedMerkleTree.ts | 6 +++--- .../trees/{ => lmt}/LinkedMerkleTreeStore.ts | 2 +- .../{ => sparse}/InMemoryMerkleTreeStorage.ts | 0 .../src/trees/{ => sparse}/MerkleTreeStore.ts | 0 .../{ => sparse}/MockAsyncMerkleStore.ts | 2 +- .../trees/{ => sparse}/RollupMerkleTree.ts | 4 ++-- .../protocol/test/StateTransition.test.ts | 2 +- 11 files changed, 17 insertions(+), 37 deletions(-) delete mode 100644 packages/common/src/trees/VirtualMerkleTreeStore.ts rename packages/common/src/trees/{ => lmt}/InMemoryLinkedLeafStore.ts (100%) rename packages/common/src/trees/{ => lmt}/InMemoryLinkedMerkleLeafStore.ts (73%) rename packages/common/src/trees/{ => lmt}/LinkedMerkleTree.ts (99%) rename packages/common/src/trees/{ => lmt}/LinkedMerkleTreeStore.ts (90%) rename packages/common/src/trees/{ => sparse}/InMemoryMerkleTreeStorage.ts (100%) rename packages/common/src/trees/{ => sparse}/MerkleTreeStore.ts (100%) rename packages/common/src/trees/{ => sparse}/MockAsyncMerkleStore.ts (94%) rename packages/common/src/trees/{ => sparse}/RollupMerkleTree.ts (99%) diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index d66e07d02..412cb9384 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -13,16 +13,16 @@ export * from "./dependencyFactory/injectOptional"; 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"; -export * from "./trees/InMemoryLinkedLeafStore"; +export * from "./trees/sparse/MerkleTreeStore"; +export * from "./trees/lmt/LinkedMerkleTreeStore"; +export * from "./trees/sparse/InMemoryMerkleTreeStorage"; +export * from "./trees/sparse/RollupMerkleTree"; +export * from "./trees/lmt/LinkedMerkleTree"; +export * from "./trees/lmt/InMemoryLinkedLeafStore"; export * from "./events/EventEmitterProxy"; export * from "./events/ReplayingSingleUseEventEmitter"; -export * from "./trees/MockAsyncMerkleStore"; -export * from "./trees/InMemoryLinkedMerkleLeafStore"; +export * from "./trees/sparse/MockAsyncMerkleStore"; +export * from "./trees/lmt/InMemoryLinkedMerkleLeafStore"; export * from "./compiling/AtomicCompileHelper"; export * from "./compiling/CompileRegistry"; export * from "./compiling/CompilableModule"; diff --git a/packages/common/src/trees/VirtualMerkleTreeStore.ts b/packages/common/src/trees/VirtualMerkleTreeStore.ts deleted file mode 100644 index a0cbd3127..000000000 --- a/packages/common/src/trees/VirtualMerkleTreeStore.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { MerkleTreeStore } from "./MerkleTreeStore"; -import { InMemoryMerkleTreeStorage } from "./InMemoryMerkleTreeStorage"; - -/** - * A MemoryMerkleTreeStore that, if falls back to a parent store if it - * has no data - */ -export class VirtualMerkleTreeStore extends InMemoryMerkleTreeStorage { - public constructor(private readonly parent: MerkleTreeStore) { - 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): void { - super.setNode(key, level, value); - } -} diff --git a/packages/common/src/trees/InMemoryLinkedLeafStore.ts b/packages/common/src/trees/lmt/InMemoryLinkedLeafStore.ts similarity index 100% rename from packages/common/src/trees/InMemoryLinkedLeafStore.ts rename to packages/common/src/trees/lmt/InMemoryLinkedLeafStore.ts diff --git a/packages/common/src/trees/InMemoryLinkedMerkleLeafStore.ts b/packages/common/src/trees/lmt/InMemoryLinkedMerkleLeafStore.ts similarity index 73% rename from packages/common/src/trees/InMemoryLinkedMerkleLeafStore.ts rename to packages/common/src/trees/lmt/InMemoryLinkedMerkleLeafStore.ts index 6d673c3f4..6292a58ee 100644 --- a/packages/common/src/trees/InMemoryLinkedMerkleLeafStore.ts +++ b/packages/common/src/trees/lmt/InMemoryLinkedMerkleLeafStore.ts @@ -1,7 +1,7 @@ import { Mixin } from "ts-mixer"; import { InMemoryLinkedLeafStore } from "./InMemoryLinkedLeafStore"; -import { InMemoryMerkleTreeStorage } from "./InMemoryMerkleTreeStorage"; +import { InMemoryMerkleTreeStorage } from "../sparse/InMemoryMerkleTreeStorage"; export class InMemoryLinkedMerkleLeafStore extends Mixin( InMemoryLinkedLeafStore, diff --git a/packages/common/src/trees/LinkedMerkleTree.ts b/packages/common/src/trees/lmt/LinkedMerkleTree.ts similarity index 99% rename from packages/common/src/trees/LinkedMerkleTree.ts rename to packages/common/src/trees/lmt/LinkedMerkleTree.ts index 66d67ea9f..c8be81cf2 100644 --- a/packages/common/src/trees/LinkedMerkleTree.ts +++ b/packages/common/src/trees/lmt/LinkedMerkleTree.ts @@ -1,15 +1,15 @@ // eslint-disable-next-line max-classes-per-file import { Bool, Field, Poseidon, Provable, Struct } from "o1js"; -import { TypedClass } from "../types"; -import { range } from "../utils"; +import { TypedClass } from "../../types"; +import { range } from "../../utils"; import { LinkedMerkleTreeStore } from "./LinkedMerkleTreeStore"; import { AbstractMerkleWitness, createMerkleTree, maybeSwap, -} from "./RollupMerkleTree"; +} from "../sparse/RollupMerkleTree"; import { InMemoryLinkedMerkleLeafStore } from "./InMemoryLinkedMerkleLeafStore"; const RollupMerkleTreeWitness = createMerkleTree(40).WITNESS; diff --git a/packages/common/src/trees/LinkedMerkleTreeStore.ts b/packages/common/src/trees/lmt/LinkedMerkleTreeStore.ts similarity index 90% rename from packages/common/src/trees/LinkedMerkleTreeStore.ts rename to packages/common/src/trees/lmt/LinkedMerkleTreeStore.ts index f8efefd65..ea766774d 100644 --- a/packages/common/src/trees/LinkedMerkleTreeStore.ts +++ b/packages/common/src/trees/lmt/LinkedMerkleTreeStore.ts @@ -1,4 +1,4 @@ -import { MerkleTreeStore } from "./MerkleTreeStore"; +import { MerkleTreeStore } from "../sparse/MerkleTreeStore"; export interface LinkedLeafStore { setLeaf: (index: bigint, value: LinkedLeaf) => void; diff --git a/packages/common/src/trees/InMemoryMerkleTreeStorage.ts b/packages/common/src/trees/sparse/InMemoryMerkleTreeStorage.ts similarity index 100% rename from packages/common/src/trees/InMemoryMerkleTreeStorage.ts rename to packages/common/src/trees/sparse/InMemoryMerkleTreeStorage.ts diff --git a/packages/common/src/trees/MerkleTreeStore.ts b/packages/common/src/trees/sparse/MerkleTreeStore.ts similarity index 100% rename from packages/common/src/trees/MerkleTreeStore.ts rename to packages/common/src/trees/sparse/MerkleTreeStore.ts diff --git a/packages/common/src/trees/MockAsyncMerkleStore.ts b/packages/common/src/trees/sparse/MockAsyncMerkleStore.ts similarity index 94% rename from packages/common/src/trees/MockAsyncMerkleStore.ts rename to packages/common/src/trees/sparse/MockAsyncMerkleStore.ts index 26279aea9..355be3b28 100644 --- a/packages/common/src/trees/MockAsyncMerkleStore.ts +++ b/packages/common/src/trees/sparse/MockAsyncMerkleStore.ts @@ -1,4 +1,4 @@ -import { noop } from "../utils"; +import { noop } from "../../utils"; import { InMemoryMerkleTreeStorage } from "./InMemoryMerkleTreeStorage"; diff --git a/packages/common/src/trees/RollupMerkleTree.ts b/packages/common/src/trees/sparse/RollupMerkleTree.ts similarity index 99% rename from packages/common/src/trees/RollupMerkleTree.ts rename to packages/common/src/trees/sparse/RollupMerkleTree.ts index 59ae24efe..e5673ec95 100644 --- a/packages/common/src/trees/RollupMerkleTree.ts +++ b/packages/common/src/trees/sparse/RollupMerkleTree.ts @@ -1,7 +1,7 @@ import { Bool, Field, Poseidon, Provable, Struct } from "o1js"; -import { range } from "../utils"; -import { TypedClass } from "../types"; +import { range } from "../../utils"; +import { TypedClass } from "../../types"; import { MerkleTreeStore } from "./MerkleTreeStore"; import { InMemoryMerkleTreeStorage } from "./InMemoryMerkleTreeStorage"; diff --git a/packages/protocol/test/StateTransition.test.ts b/packages/protocol/test/StateTransition.test.ts index 936d2d85d..8d764b751 100644 --- a/packages/protocol/test/StateTransition.test.ts +++ b/packages/protocol/test/StateTransition.test.ts @@ -3,7 +3,7 @@ import { InMemoryMerkleTreeStorage } from "@proto-kit/common"; import { Bool, Field } from "o1js"; import { Option, ProvableStateTransition } from "../src/index"; -import { RollupMerkleTree } from "../../common/src/trees/RollupMerkleTree.js"; +import { RollupMerkleTree } from "../../common/src/trees/sparse/RollupMerkleTree.js"; // TODO Not worth fixing rn because we will revamp the STProver very soon From 3e831ae0699bfba384719a47fdaf44c0f6b90c30 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 8 Apr 2025 13:15:32 +0200 Subject: [PATCH 109/128] Reimplemented LMT with new witness layout --- .../common/src/trees/lmt/LinkedMerkleTree.ts | 455 +++++++----------- .../test/trees/LinkedMerkleTree.test.ts | 23 +- 2 files changed, 189 insertions(+), 289 deletions(-) diff --git a/packages/common/src/trees/lmt/LinkedMerkleTree.ts b/packages/common/src/trees/lmt/LinkedMerkleTree.ts index c8be81cf2..6ad0afb23 100644 --- a/packages/common/src/trees/lmt/LinkedMerkleTree.ts +++ b/packages/common/src/trees/lmt/LinkedMerkleTree.ts @@ -4,48 +4,71 @@ import { Bool, Field, Poseidon, Provable, Struct } from "o1js"; import { TypedClass } from "../../types"; import { range } from "../../utils"; -import { LinkedMerkleTreeStore } from "./LinkedMerkleTreeStore"; +import { + LinkedLeaf, + LinkedLeafStore, + LinkedMerkleTreeStore, +} from "./LinkedMerkleTreeStore"; import { AbstractMerkleWitness, createMerkleTree, maybeSwap, + RollupMerkleTree, } from "../sparse/RollupMerkleTree"; import { InMemoryLinkedMerkleLeafStore } from "./InMemoryLinkedMerkleLeafStore"; +import { MerkleTreeStore } from "../sparse/MerkleTreeStore"; +import { InMemoryMerkleTreeStorage } from "../sparse/InMemoryMerkleTreeStorage"; +import { InMemoryLinkedLeafStore } from "./InMemoryLinkedLeafStore"; const RollupMerkleTreeWitness = createMerkleTree(40).WITNESS; + export class LinkedLeafStruct extends Struct({ value: Field, path: Field, nextPath: Field, }) { + public isDummy() { + return this.path.equals(0).and(this.nextPath.equals(0)); + } + public hash(): Field { - return Poseidon.hash(LinkedLeafStruct.toFields(this)); + const hash = Poseidon.hash(LinkedLeafStruct.toFields(this)); + return Provable.if(this.isDummy(), Field(0), hash); + } + + public static dummy(): LinkedLeafStruct { + return new LinkedLeafStruct({ + value: Field(0), + path: Field(0), + nextPath: Field(0), + }); } } +// TODO Improve that // We use the RollupMerkleTreeWitness here, although we will actually implement // the RollupMerkleTreeWitnessV2 defined below when instantiating the class. -export class LinkedLeafAndMerkleWitness extends Struct({ +class LinkedLeafAndMerkleWitnessTemplate extends Struct({ leaf: LinkedLeafStruct, merkleWitness: RollupMerkleTreeWitness, }) {} class LinkedStructTemplate extends Struct({ - leafPrevious: LinkedLeafAndMerkleWitness, - leafCurrent: LinkedLeafAndMerkleWitness, + leafPrevious: LinkedLeafAndMerkleWitnessTemplate, + leafCurrent: LinkedLeafAndMerkleWitnessTemplate, +}) {} + +export class LinkedMerkleTreeGlobalState extends Struct({ + root: Field, + lastOccupiedIndex: Field, }) {} export interface AbstractLinkedMerkleWitness extends LinkedStructTemplate {} export interface AbstractLinkedMerkleTree { - store: LinkedMerkleTreeStore; - /** - * 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; + leafStore: LinkedLeafStore; + + tree: RollupMerkleTree; /** * Returns the root of the [Merkle Tree](https://en.wikipedia.org/wiki/Merkle_tree). @@ -53,12 +76,14 @@ export interface AbstractLinkedMerkleTree { */ getRoot(): Field; + getGlobalState(): LinkedMerkleTreeGlobalState; + /** * Sets the value of a leaf node at a given index to a given value. * @param path of the leaf node. * @param value New value. */ - setLeaf(path: bigint, value?: bigint): LinkedMerkleTreeWitness; + setLeaf(path: bigint, value?: bigint): LinkedStructTemplate; /** * Returns a leaf which lives at a given path. @@ -75,18 +100,25 @@ export interface AbstractLinkedMerkleTree { * @param path Position of the leaf node. * @returns The witness that belongs to the leaf. */ - getWitness(path: bigint): LinkedLeafAndMerkleWitness; + getReadWitness(path: bigint): LinkedLeafAndMerkleWitnessTemplate; - dummyWitness(): LinkedMerkleTreeWitness; + dummyWitness(): LinkedStructTemplate; - dummy(): LinkedLeafAndMerkleWitness; + dummyReadWitness(): LinkedLeafAndMerkleWitnessTemplate; } export interface AbstractLinkedMerkleTreeClass { - new (store: LinkedMerkleTreeStore): AbstractLinkedMerkleTree; + new ( + store: MerkleTreeStore, + leafStore: LinkedLeafStore + ): AbstractLinkedMerkleTree; WITNESS: TypedClass & - typeof LinkedStructTemplate; + typeof LinkedStructTemplate & { + fromReadWitness( + readWitness: LinkedLeafAndMerkleWitnessTemplate + ): AbstractLinkedMerkleWitness; + }; HEIGHT: number; @@ -96,152 +128,64 @@ export interface AbstractLinkedMerkleTreeClass { export function createLinkedMerkleTree( height: number ): AbstractLinkedMerkleTreeClass { - 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). - class RollupMerkleWitnessV2 + class LinkedLeafAndMerkleWitness extends Struct({ + leaf: LinkedLeafStruct, + merkleWitness: RollupMerkleTreeWitness, + }) {} + + class LinkedTreeOpWitness extends Struct({ - path: Provable.Array(Field, height - 1), - isLeft: Provable.Array(Bool, height - 1), + leafPrevious: LinkedLeafAndMerkleWitnessTemplate, + leafCurrent: LinkedLeafAndMerkleWitnessTemplate, }) - implements AbstractMerkleWitness + implements AbstractLinkedMerkleWitness { - 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)), + public static fromReadWitness( + readWitness: LinkedLeafAndMerkleWitnessTemplate + ) { + return new LinkedStructTemplate({ + leafPrevious: new LinkedLeafAndMerkleWitness({ + merkleWitness: RollupMerkleTreeWitness.dummy(), + leaf: LinkedLeafStruct.dummy(), + }), + leafCurrent: readWitness, }); } } + + const SparseTreeClass = createMerkleTree(height); + return class AbstractLinkedRollupMerkleTree implements AbstractLinkedMerkleTree { public static HEIGHT = height; public static EMPTY_ROOT = new AbstractLinkedRollupMerkleTree( - new InMemoryLinkedMerkleLeafStore() + new InMemoryMerkleTreeStorage(), + new InMemoryLinkedLeafStore() ) .getRoot() .toBigInt(); - public static WITNESS = LinkedMerkleWitness; + public static WITNESS = LinkedTreeOpWitness; - readonly zeroes: bigint[]; + readonly tree: RollupMerkleTree; - readonly store: LinkedMerkleTreeStore; + readonly leafStore: LinkedLeafStore; + + public constructor(store: MerkleTreeStore, leafStore: LinkedLeafStore) { + this.leafStore = leafStore; + + this.tree = new SparseTreeClass(store); - 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() - ); - } // 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() === undefined) { + if (this.leafStore.getMaximumIndex() === undefined) { this.setLeafInitialisation(); } } - public getNode(level: number, index: bigint): Field { - const node = this.store.getNode(index, level); - return Field(node ?? this.zeroes[level]); - } - /** * Returns leaf which lives at a given path. * Errors if the path is not defined. @@ -249,7 +193,7 @@ export function createLinkedMerkleTree( * @returns The data of the node. */ public getLeaf(path: bigint): LinkedLeafStruct | undefined { - const storedLeaf = this.store.getLeaf(path); + const storedLeaf = this.leafStore.getLeaf(path); if (storedLeaf === undefined) { return undefined; } @@ -265,35 +209,23 @@ export function createLinkedMerkleTree( * @returns The root of the Merkle Tree. */ public getRoot(): Field { - return this.getNode( - AbstractLinkedRollupMerkleTree.HEIGHT - 1, - 0n - ).toConstant(); + return this.tree.getRoot().toConstant(); } - private setNode(level: number, index: bigint, value: Field) { - this.store.setNode(index, level, value.toBigInt()); + private setMerkleLeaf(index: bigint, leaf: LinkedLeaf) { + this.leafStore.setLeaf(index, leaf); + + const leafHash = new LinkedLeafStruct( + LinkedLeafStruct.fromValue(leaf) + ).hash(); + this.tree.setLeaf(index, leafHash); } - /** - * 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. - */ - private setMerkleLeaf(index: bigint, leaf: LinkedLeafStruct) { - this.setNode(0, index, leaf.hash()); - 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 getGlobalState(): LinkedMerkleTreeGlobalState { + return { + root: this.getRoot(), + lastOccupiedIndex: Field(this.leafStore.getMaximumIndex() ?? 0n), + }; } /** @@ -301,70 +233,81 @@ export function createLinkedMerkleTree( * @param path Position of the leaf node. * @param value New value. */ - public setLeaf(path: bigint, value?: bigint): LinkedMerkleWitness { - if (value === undefined) { - return new LinkedMerkleWitness({ - leafPrevious: this.dummy(), - leafCurrent: this.getWitness(path), - }); - } - 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; + public setLeaf(path: bigint, value: bigint): LinkedTreeOpWitness { + const storedLeaf = this.leafStore.getLeaf(path); + // const prevLeaf = this.store.getLeafLessOrEqual(path); + // + // if (prevLeaf === undefined) { + // throw Error("Prev leaf shouldn't be undefined"); + // } + if (storedLeaf === undefined) { + // Insert case // 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(); + const tempIndex = this.leafStore.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"); } - witnessPrevious = this.getWitness(prevLeaf.leaf.path); + const nextFreeIndex = tempIndex + 1n; + + const previousLeaf = this.leafStore.getLeafLessOrEqual(path); + + if (previousLeaf === undefined) { + throw Error("Prev leaf shouldn't be undefined"); + } + + const previousLeafMerkleWitness = this.tree.getWitness( + previousLeaf.index + ); + const newPrevLeaf = { - value: prevLeaf.leaf.value, - path: prevLeaf.leaf.path, + ...previousLeaf.leaf, nextPath: path, }; - this.store.setLeaf(prevLeaf.index, newPrevLeaf); - this.setMerkleLeaf( - prevLeaf.index, - new LinkedLeafStruct({ - value: Field(newPrevLeaf.value), - path: Field(newPrevLeaf.path), - nextPath: Field(newPrevLeaf.nextPath), - }) - ); + this.setMerkleLeaf(previousLeaf.index, newPrevLeaf); + + const currentMerkleWitness = this.tree.getWitness(nextFreeIndex); - index = tempIndex + 1n; + const newLeaf = { + path, + value, + nextPath: previousLeaf.leaf.nextPath, + }; + this.setMerkleLeaf(nextFreeIndex, newLeaf); + + return new LinkedTreeOpWitness({ + leafPrevious: { + leaf: new LinkedLeafStruct( + LinkedLeafStruct.fromValue(previousLeaf.leaf) + ), + merkleWitness: previousLeafMerkleWitness, + }, + leafCurrent: { + leaf: LinkedLeafStruct.dummy(), + merkleWitness: currentMerkleWitness, + }, + }); } else { - witnessPrevious = this.dummy(); - index = storedLeaf.index; + // Update case + const witnessPrevious = this.dummyReadWitness(); + + // TODO This makes an unnecessary leafstore lookup currently, reuse storedLeaf instead + const current = this.getReadWitness(storedLeaf.leaf.path); + + this.setMerkleLeaf(storedLeaf.index, { + ...storedLeaf.leaf, + value: value, + }); + + return new LinkedTreeOpWitness({ + leafPrevious: witnessPrevious, + leafCurrent: current, + }); } - const newLeaf = { - value: value, - path: path, - nextPath: prevLeaf.leaf.nextPath, - }; - const witnessNext = this.getWitness(newLeaf.path); - this.store.setLeaf(index, newLeaf); - this.setMerkleLeaf( - index, - new LinkedLeafStruct({ - value: Field(newLeaf.value), - path: Field(newLeaf.path), - nextPath: Field(newLeaf.nextPath), - }) - ); - return new LinkedMerkleWitness({ - leafPrevious: witnessPrevious, - leafCurrent: witnessNext, - }); } /** @@ -374,21 +317,18 @@ export function createLinkedMerkleTree( private setLeafInitialisation() { // This is the maximum value of the hash const MAX_FIELD_VALUE: bigint = Field.ORDER - 1n; - this.store.setLeaf(0n, { + this.leafStore.setLeaf(0n, { value: 0n, path: 0n, nextPath: MAX_FIELD_VALUE, }); // We now set the leafs in the merkle tree to cascade the values up // the tree. - this.setMerkleLeaf( - 0n, - new LinkedLeafStruct({ - value: Field(0n), - path: Field(0n), - nextPath: Field(MAX_FIELD_VALUE), - }) - ); + this.setMerkleLeaf(0n, { + value: 0n, + path: 0n, + nextPath: MAX_FIELD_VALUE, + }); } /** @@ -398,77 +338,44 @@ export function createLinkedMerkleTree( * @param path of the leaf node. * @returns The witness that belongs to the leaf. */ - public getWitness(path: bigint): LinkedLeafAndMerkleWitness { - const storedLeaf = this.store.getLeaf(path); + public getReadWitness(path: bigint): LinkedLeafAndMerkleWitness { + const storedLeaf = this.leafStore.getLeaf(path); let leaf; let currentIndex: bigint; if (storedLeaf === undefined) { - const storeIndex = this.store.getMaximumIndex(); + const storeIndex = this.leafStore.getMaximumIndex(); if (storeIndex === undefined) { - throw new Error("Store Undefined"); + throw new Error("Store undefined"); } currentIndex = storeIndex + 1n; - leaf = new LinkedLeafStruct({ - value: Field(0), - path: Field(0), - nextPath: Field(0), - }); + leaf = LinkedLeafStruct.dummy(); } else { - leaf = new LinkedLeafStruct({ - value: Field(storedLeaf.leaf.value), - path: Field(storedLeaf.leaf.path), - nextPath: Field(storedLeaf.leaf.nextPath), - }); + leaf = new LinkedLeafStruct( + LinkedLeafStruct.fromValue(storedLeaf.leaf) + ); currentIndex = storedLeaf.index; } - const pathArray = []; - const isLefts = []; - - for ( - let level = 0; - level < AbstractLinkedRollupMerkleTree.HEIGHT - 1; - level += 1 - ) { - const isLeft = currentIndex % 2n === 0n; - const sibling = this.getNode( - level, - isLeft ? currentIndex + 1n : currentIndex - 1n - ); - isLefts.push(Bool(isLeft)); - pathArray.push(sibling); - currentIndex /= 2n; - } + const merkleWitness = this.tree.getWitness(currentIndex); + return new LinkedLeafAndMerkleWitness({ - merkleWitness: new RollupMerkleWitnessV2({ - path: pathArray, - isLeft: isLefts, - }), - leaf: leaf, + merkleWitness, + leaf, }); } - public dummy(): LinkedLeafAndMerkleWitness { + public dummyReadWitness(): LinkedLeafAndMerkleWitness { return new LinkedLeafAndMerkleWitness({ - merkleWitness: new RollupMerkleTreeWitness({ - // 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 LinkedLeafStruct({ - value: Field(0), - path: Field(0), - nextPath: Field(0), - }), + merkleWitness: RollupMerkleTreeWitness.dummy(), + leaf: LinkedLeafStruct.dummy(), }); } public dummyWitness() { - return new LinkedMerkleWitness({ - leafPrevious: this.dummy(), - leafCurrent: this.dummy(), + return new LinkedTreeOpWitness({ + leafPrevious: this.dummyReadWitness(), + leafCurrent: this.dummyReadWitness(), }); } }; diff --git a/packages/common/test/trees/LinkedMerkleTree.test.ts b/packages/common/test/trees/LinkedMerkleTree.test.ts index 755bc4473..18b39066d 100644 --- a/packages/common/test/trees/LinkedMerkleTree.test.ts +++ b/packages/common/test/trees/LinkedMerkleTree.test.ts @@ -18,7 +18,7 @@ describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { log.setLevel("INFO"); store = new InMemoryLinkedMerkleLeafStore(); - tree = new LinkedMerkleTree(store); + tree = new LinkedMerkleTree(store, store); }); it("should have the same root when empty", () => { @@ -40,23 +40,16 @@ describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { }); it("should provide correct witnesses", () => { - expect.assertions(1); + expect.assertions(2); tree.setLeaf(1n, 1n); tree.setLeaf(5n, 5n); - const witness = tree.getWitness(5n); + const witness = tree.getReadWitness(5n); + expect(witness.leaf.value.toString()).toStrictEqual("5"); expect( - witness.merkleWitness - .calculateRoot( - Poseidon.hash([ - witness.leaf.value, - witness.leaf.path, - witness.leaf.nextPath, - ]) - ) - .toBigInt() + witness.merkleWitness.calculateRoot(witness.leaf.hash()).toBigInt() ).toStrictEqual(tree.getRoot().toBigInt()); }); @@ -66,7 +59,7 @@ describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { tree.setLeaf(1n, 1n); tree.setLeaf(5n, 5n); - const witness = tree.getWitness(5n); + const witness = tree.getReadWitness(5n); expect( witness.merkleWitness.calculateRoot(Field(6)).toBigInt() @@ -79,7 +72,7 @@ describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { tree.setLeaf(1n, 1n); tree.setLeaf(5n, 5n); - const witness = tree.getWitness(5n); + const witness = tree.getReadWitness(5n); tree.setLeaf(5n, 10n); @@ -113,7 +106,7 @@ describe("Error check", () => { log.setLevel("INFO"); store = new InMemoryLinkedMerkleLeafStore(); - tree = new LinkedMerkleTree(store); + tree = new LinkedMerkleTree(store, store); expect(() => { for (let i = 0; i < 2n ** BigInt(4) + 1n; i++) { tree.setLeaf(BigInt(i), 2n); From ba261291a84edd3e22ffa669c92c8d6cd8b4d73e Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 8 Apr 2025 13:15:44 +0200 Subject: [PATCH 110/128] Implemented circuit ops for new LMT impl --- packages/common/src/index.ts | 3 +- .../trees/lmt/LinkedMerkleTreeCircuitOps.ts | 229 ++++++++++++++++++ .../trees/LinkedMerkleTreeCircuitOps.test.ts | 145 +++++++++++ 3 files changed, 376 insertions(+), 1 deletion(-) create mode 100644 packages/common/src/trees/lmt/LinkedMerkleTreeCircuitOps.ts create mode 100644 packages/common/test/trees/LinkedMerkleTreeCircuitOps.test.ts diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 412cb9384..916db5d73 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -19,10 +19,11 @@ export * from "./trees/sparse/InMemoryMerkleTreeStorage"; export * from "./trees/sparse/RollupMerkleTree"; export * from "./trees/lmt/LinkedMerkleTree"; export * from "./trees/lmt/InMemoryLinkedLeafStore"; +export * from "./trees/lmt/InMemoryLinkedMerkleLeafStore"; +export * from "./trees/lmt/LinkedMerkleTreeCircuitOps"; export * from "./events/EventEmitterProxy"; export * from "./events/ReplayingSingleUseEventEmitter"; export * from "./trees/sparse/MockAsyncMerkleStore"; -export * from "./trees/lmt/InMemoryLinkedMerkleLeafStore"; export * from "./compiling/AtomicCompileHelper"; export * from "./compiling/CompileRegistry"; export * from "./compiling/CompilableModule"; diff --git a/packages/common/src/trees/lmt/LinkedMerkleTreeCircuitOps.ts b/packages/common/src/trees/lmt/LinkedMerkleTreeCircuitOps.ts new file mode 100644 index 000000000..366cab092 --- /dev/null +++ b/packages/common/src/trees/lmt/LinkedMerkleTreeCircuitOps.ts @@ -0,0 +1,229 @@ +import { Bool, Field, Provable, Struct } from "o1js"; + +import { + LinkedLeafStruct, + LinkedMerkleTreeGlobalState, + LinkedMerkleTreeWitness, +} from "./LinkedMerkleTree"; + +/* eslint-disable no-inner-declarations */ +// TODO +export class MonadBool extends Struct({ + b: Bool, + metadata: String, +}) { + and(bool2: Bool, msg: string): MonadBool { + const result = this.b.and(bool2); + let metadata: string = this.metadata; + Provable.asProver(() => { + if (!bool2.toBoolean()) { + metadata = metadata + (metadata.length > 0 ? "\n" : "") + msg; + } + }); + return new MonadBool({ + b: result, + metadata, + }); + } + + static from(b: Bool) { + return new MonadBool({ b, metadata: "" }); + } +} + +export type TreeWrite = { + path: Field; + from: Field; + to: Field; +}; + +export namespace LinkedMerkleTreeCircuitOps { + export class LinkedMerkleTreeGlobalStateWithoutRoot + extends Struct({ + lastOccupiedIndex: Field, + }) + implements Omit {} + + function boolAllTrue(...args: Bool[]): Bool { + return args.reduce((a, b, i) => { + // if (!b.toBoolean()) { + // console.log(); + // } + return a.and(b); + }); + } + + export class ComputeRootInstruction extends Struct({ + newPreviousLeaf: LinkedLeafStruct, + newCurrentLeaf: LinkedLeafStruct, + allChecksMet: Bool, + update: LinkedMerkleTreeGlobalStateWithoutRoot, + }) {} + + function chooseInstruction( + isUpdate: Bool, + updateInstruction: ComputeRootInstruction, + insertInstruction: ComputeRootInstruction + ): ComputeRootInstruction { + return Provable.if( + isUpdate, + ComputeRootInstruction, + updateInstruction, + insertInstruction + ); + } + + /** + * verifyWitness(current.merkleWitness, current.leaf) + * + * st.path == current.leaf.path + * st.from.value == current.leaf.value + * ``` + * + * updates: + * ``` + * current := { current.path, current.nextPath, value: st.to.value } + */ + function update( + state: LinkedMerkleTreeGlobalState, + { leafCurrent, leafPrevious }: LinkedMerkleTreeWitness, + { to, from, path }: TreeWrite + ): ComputeRootInstruction { + const allChecksMet = boolAllTrue( + path.equals(leafCurrent.leaf.path), + leafCurrent.leaf.value.equals(from) + ); + + return { + newPreviousLeaf: leafPrevious.leaf, + newCurrentLeaf: new LinkedLeafStruct({ + ...leafCurrent.leaf, + value: to, + }), + allChecksMet, + update: { + lastOccupiedIndex: state.lastOccupiedIndex, + }, + }; + } + + /** + * st.path == current.leaf.path + * + * previous.leaf.nextPath > current.leaf.path + * previous.leaf.path < current.leaf.path + * previous.leaf.nextPath == current.leaf.nextPath + * + * index(current.merkleWitness) == nextFreeIndex + * + * updates: + * previous := { previous.path, previous.value, nextPath: current.path } + * current := current.leaf + */ + function insert( + state: LinkedMerkleTreeGlobalState, + witness: LinkedMerkleTreeWitness, + { path, to }: TreeWrite + ): ComputeRootInstruction { + const { leafPrevious: previous, leafCurrent: current } = witness; + + const nextFreeIndex = state.lastOccupiedIndex.add(1); + + const allChecksMet = boolAllTrue( + // Already covered in general checks + // path.equals(current.leaf.path), + current.leaf.isDummy(), + previous.leaf.nextPath.greaterThan(path), + previous.leaf.path.lessThan(path), + current.merkleWitness.calculateIndex().equals(nextFreeIndex) + ); + + return { + newPreviousLeaf: new LinkedLeafStruct({ + ...previous.leaf, + nextPath: path, + }), + newCurrentLeaf: new LinkedLeafStruct({ + path, + value: to, + nextPath: previous.leaf.nextPath, + }), + allChecksMet, + update: { + lastOccupiedIndex: nextFreeIndex, + }, + }; + } + + function computeRoot( + witness: LinkedMerkleTreeWitness, + newPreviousLeaf: LinkedLeafStruct, + newCurrentLeaf: LinkedLeafStruct, + isUpdate: Bool, + root: Field + ) { + const { leafPrevious, leafCurrent } = witness; + + leafPrevious.merkleWitness + .calculateRoot(leafPrevious.leaf.hash()) + .equals(root) + .or(isUpdate) + .assertTrue("Previous leaf calculation not matching"); + + const root1 = leafPrevious.merkleWitness.calculateRoot( + newPreviousLeaf.hash() + ); + + let intermediateRoot = Provable.if(isUpdate, root, root1); + + // TODO Make this Provable.if more efficient + const leafCurrentLeaf = Provable.if( + isUpdate, + leafCurrent.leaf.hash(), + Field(0) + ); + leafCurrent.merkleWitness + .calculateRoot(leafCurrentLeaf) + .assertEquals(intermediateRoot); + + return leafCurrent.merkleWitness.calculateRoot(newCurrentLeaf.hash()); + } + + export function applyTreeWrite( + state: LinkedMerkleTreeGlobalState, + witness: LinkedMerkleTreeWitness, + treeWrite: TreeWrite, + index: number + ): LinkedMerkleTreeGlobalState { + const { leafPrevious, leafCurrent } = witness; + + const isUpdate = leafPrevious.leaf.isDummy(); + + // For read-only and update + const updateState = update(state, witness, treeWrite); + + // For insert + const insertState = insert(state, witness, treeWrite); + + const instruction = chooseInstruction(isUpdate, updateState, insertState); + + instruction.allChecksMet.assertTrue( + `Not all witness checks have been met: ${index}` + ); + + const newRoot = computeRoot( + witness, + instruction.newPreviousLeaf, + instruction.newCurrentLeaf, + isUpdate, + state.root + ); + + return { + root: newRoot, + lastOccupiedIndex: instruction.update.lastOccupiedIndex, + }; + } +} + +/* eslint-enable no-inner-declarations */ diff --git a/packages/common/test/trees/LinkedMerkleTreeCircuitOps.test.ts b/packages/common/test/trees/LinkedMerkleTreeCircuitOps.test.ts new file mode 100644 index 000000000..1a0b8b779 --- /dev/null +++ b/packages/common/test/trees/LinkedMerkleTreeCircuitOps.test.ts @@ -0,0 +1,145 @@ +import { + InMemoryLinkedLeafStore, + InMemoryMerkleTreeStorage, + LinkedLeafStruct, + LinkedMerkleTree, + LinkedMerkleTreeCircuitOps, + LinkedMerkleTreeGlobalState, + LinkedMerkleTreeWitness, +} from "../../src"; +import { Field, Provable } from "o1js"; + +describe("LinkedMerkleTree - Circuit Ops", () => { + function setupTree() { + const leafStore = new InMemoryLinkedLeafStore(); + const store = new InMemoryMerkleTreeStorage(); + return new LinkedMerkleTree(store, leafStore); + } + + let tree: LinkedMerkleTree; + + beforeEach(() => { + tree = setupTree(); + }); + + it("should correctly verify insert witness", () => { + try { + const root = tree.getGlobalState(); + const insertWitness = tree.setLeaf(5n, 1000n); + + const globalState = LinkedMerkleTreeCircuitOps.applyTreeWrite( + root, + insertWitness, + { + path: Field(5), + from: Field(0), + to: Field(1000), + }, + 0 + ); + + expect(globalState.root.toString()).toStrictEqual( + tree.getRoot().toString() + ); + expect(globalState.lastOccupiedIndex.toString()).toStrictEqual( + root.lastOccupiedIndex.add(1).toString() + ); + } catch (e) { + console.error(e); + throw e; + } + }); + + it("should correctly verify update witness", () => { + try { + tree.setLeaf(5n, 1000n); + tree.setLeaf(10n, 1500n); + + const root = tree.getGlobalState(); + + const updateWitness = tree.setLeaf(10n, 500n); + + const globalState = LinkedMerkleTreeCircuitOps.applyTreeWrite( + root, + updateWitness, + { + path: Field(10), + from: Field(1500), + to: Field(500), + }, + 0 + ); + + expect(globalState.root.toString()).toStrictEqual( + tree.getRoot().toString() + ); + expect(globalState.lastOccupiedIndex.toString()).toStrictEqual( + root.lastOccupiedIndex.toString() + ); + } catch (e) { + console.error(e); + throw e; + } + }); + + it("should not update root when only reading", () => { + tree.setLeaf(5n, 1000n); + tree.setLeaf(10n, 1500n); + + const root = tree.getGlobalState(); + + const updateWitness = tree.getReadWitness(10n); + + const globalState = LinkedMerkleTreeCircuitOps.applyTreeWrite( + root, + LinkedMerkleTreeWitness.fromReadWitness(updateWitness), + { + path: Field(10), + from: Field(1500), + to: Field(1500), + }, + 0 + ); + + expect(globalState.root.toString()).toStrictEqual(root.root.toString()); + expect(globalState.root.toString()).toStrictEqual( + tree.getRoot().toString() + ); + expect(globalState.lastOccupiedIndex.toString()).toStrictEqual( + root.lastOccupiedIndex.toString() + ); + }); + + it("Circuit size", async () => { + const root = tree.getGlobalState(); + + const updateWitness = tree.setLeaf(10n, 500n); + + const cs = await Provable.constraintSystem(() => { + const rootWitness = Provable.witness( + LinkedMerkleTreeGlobalState, + () => root + ); + const updateWitnessWitness = Provable.witness( + LinkedMerkleTreeWitness, + () => updateWitness + ); + const treeWrite = { + path: Provable.witness(Field, () => 1), + from: Provable.witness(Field, () => 1), + to: Provable.witness(Field, () => 1), + }; + + const globalState = LinkedMerkleTreeCircuitOps.applyTreeWrite( + rootWitness, + updateWitnessWitness, + treeWrite, + 0 + ); + }); + + console.log(cs.rows); + + // expect(cs.rows).toBeLessThan(2500); + }); +}); From 1c48fbe7697090b332a1dcc3a3f892502364bd2a Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 8 Apr 2025 15:27:55 +0200 Subject: [PATCH 111/128] Fixed typing issues for LMT --- packages/common/src/index.ts | 2 + .../src/trees/lmt/AbstractLinkedMerkleTree.ts | 105 +++++++++ .../common/src/trees/lmt/LinkedMerkleTree.ts | 203 +++++------------- .../trees/lmt/LinkedMerkleTreeCircuitOps.ts | 25 ++- .../src/trees/lmt/LinkedMerkleTreeTypes.ts | 53 +++++ .../trees/LinkedMerkleTreeCircuitOps.test.ts | 26 +++ 6 files changed, 256 insertions(+), 158 deletions(-) create mode 100644 packages/common/src/trees/lmt/AbstractLinkedMerkleTree.ts create mode 100644 packages/common/src/trees/lmt/LinkedMerkleTreeTypes.ts diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 916db5d73..e28bfab6f 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -21,6 +21,8 @@ export * from "./trees/lmt/LinkedMerkleTree"; export * from "./trees/lmt/InMemoryLinkedLeafStore"; export * from "./trees/lmt/InMemoryLinkedMerkleLeafStore"; export * from "./trees/lmt/LinkedMerkleTreeCircuitOps"; +export * from "./trees/lmt/AbstractLinkedMerkleTree"; +export * from "./trees/lmt/LinkedMerkleTreeTypes"; export * from "./events/EventEmitterProxy"; export * from "./events/ReplayingSingleUseEventEmitter"; export * from "./trees/sparse/MockAsyncMerkleStore"; diff --git a/packages/common/src/trees/lmt/AbstractLinkedMerkleTree.ts b/packages/common/src/trees/lmt/AbstractLinkedMerkleTree.ts new file mode 100644 index 000000000..22b430daa --- /dev/null +++ b/packages/common/src/trees/lmt/AbstractLinkedMerkleTree.ts @@ -0,0 +1,105 @@ +import { Bool, Field, Struct } from "o1js"; + +import { createMerkleTree, RollupMerkleTree } from "../sparse/RollupMerkleTree"; +import { TypedClass } from "../../types"; +import { MerkleTreeStore } from "../sparse/MerkleTreeStore"; + +import { LinkedLeafStore } from "./LinkedMerkleTreeStore"; +import { + LinkedLeafStruct, + LinkedMerkleTreeGlobalState, +} from "./LinkedMerkleTreeTypes"; + +class RollupMerkleTreeWitness extends createMerkleTree(40).WITNESS {} + +type LinkedMerkleWitnessValue = { + leaf: LinkedLeafStruct; + merkleWitness: RollupMerkleTreeWitness; + checkMembership(root: Field, path: Field, value: Field): Bool; +}; + +// We use the RollupMerkleTreeWitness here, although we will actually implement +// the RollupMerkleTreeWitnessV2 defined below when instantiating the class. +class LinkedMerkleWitnessTemplate extends Struct({ + leaf: LinkedLeafStruct, + merkleWitness: RollupMerkleTreeWitness, +}) { + public checkMembership(root: Field, path: Field, value: Field): Bool { + // Mock implementation for typing + return Bool(true); + } +} + +type LinkedOperationWitnessValue = { + leafPrevious: LinkedMerkleWitnessValue; + leafCurrent: LinkedMerkleWitnessValue; +}; + +class LinkedOperationWitnessTemplate extends Struct({ + leafPrevious: LinkedMerkleWitnessTemplate, + leafCurrent: LinkedMerkleWitnessTemplate, +}) {} + +export interface AbstractLinkedMerkleTree { + leafStore: LinkedLeafStore; + + tree: RollupMerkleTree; + + /** + * Returns the root of the [Merkle Tree](https://en.wikipedia.org/wiki/Merkle_tree). + * @returns The root of the Merkle Tree. + */ + getRoot(): Field; + + getGlobalState(): LinkedMerkleTreeGlobalState; + + /** + * Sets the value of a leaf node at a given index to a given value. + * @param path of the leaf node. + * @param value New value. + */ + setLeaf(path: bigint, value?: bigint): LinkedOperationWitnessValue; + + /** + * Returns a leaf which lives at a given path. + * Errors otherwise. + * @param path Index of the node. + * @returns The data of the leaf. + */ + getLeaf(path: bigint): LinkedLeafStruct | undefined; + + /** + * Returns the witness (also known as + * [Merkle Proof or Merkle Witness](https://computersciencewiki.org/index.php/Merkle_proof)) + * for the leaf at the given path. + * @param path Position of the leaf node. + * @returns The witness that belongs to the leaf. + */ + getReadWitness(path: bigint): LinkedMerkleWitnessValue; + + dummyWitness(): LinkedOperationWitnessValue; + + dummyReadWitness(): LinkedMerkleWitnessValue; +} + +export interface AbstractLinkedMerkleTreeClass { + new ( + store: MerkleTreeStore, + leafStore: LinkedLeafStore + ): AbstractLinkedMerkleTree; + + WITNESS: typeof LinkedOperationWitnessTemplate & { + fromReadWitness( + readWitness: LinkedMerkleWitnessTemplate + ): LinkedOperationWitnessTemplate; + }; + + READ_WITNESS: typeof LinkedMerkleWitnessTemplate & + TypedClass<{ + checkMembership(root: Field, path: Field, value: Field): Bool; + }>; + + HEIGHT: number; + + EMPTY_ROOT: bigint; +} diff --git a/packages/common/src/trees/lmt/LinkedMerkleTree.ts b/packages/common/src/trees/lmt/LinkedMerkleTree.ts index 6ad0afb23..42ecb0327 100644 --- a/packages/common/src/trees/lmt/LinkedMerkleTree.ts +++ b/packages/common/src/trees/lmt/LinkedMerkleTree.ts @@ -1,151 +1,50 @@ -// eslint-disable-next-line max-classes-per-file -import { Bool, Field, Poseidon, Provable, Struct } from "o1js"; +import { Field, Struct } from "o1js"; -import { TypedClass } from "../../types"; -import { range } from "../../utils"; - -import { - LinkedLeaf, - LinkedLeafStore, - LinkedMerkleTreeStore, -} from "./LinkedMerkleTreeStore"; -import { - AbstractMerkleWitness, - createMerkleTree, - maybeSwap, - RollupMerkleTree, -} from "../sparse/RollupMerkleTree"; -import { InMemoryLinkedMerkleLeafStore } from "./InMemoryLinkedMerkleLeafStore"; +import { createMerkleTree, RollupMerkleTree } from "../sparse/RollupMerkleTree"; import { MerkleTreeStore } from "../sparse/MerkleTreeStore"; import { InMemoryMerkleTreeStorage } from "../sparse/InMemoryMerkleTreeStorage"; -import { InMemoryLinkedLeafStore } from "./InMemoryLinkedLeafStore"; - -const RollupMerkleTreeWitness = createMerkleTree(40).WITNESS; - -export class LinkedLeafStruct extends Struct({ - value: Field, - path: Field, - nextPath: Field, -}) { - public isDummy() { - return this.path.equals(0).and(this.nextPath.equals(0)); - } - - public hash(): Field { - const hash = Poseidon.hash(LinkedLeafStruct.toFields(this)); - return Provable.if(this.isDummy(), Field(0), hash); - } - - public static dummy(): LinkedLeafStruct { - return new LinkedLeafStruct({ - value: Field(0), - path: Field(0), - nextPath: Field(0), - }); - } -} - -// TODO Improve that -// We use the RollupMerkleTreeWitness here, although we will actually implement -// the RollupMerkleTreeWitnessV2 defined below when instantiating the class. -class LinkedLeafAndMerkleWitnessTemplate extends Struct({ - leaf: LinkedLeafStruct, - merkleWitness: RollupMerkleTreeWitness, -}) {} - -class LinkedStructTemplate extends Struct({ - leafPrevious: LinkedLeafAndMerkleWitnessTemplate, - leafCurrent: LinkedLeafAndMerkleWitnessTemplate, -}) {} - -export class LinkedMerkleTreeGlobalState extends Struct({ - root: Field, - lastOccupiedIndex: Field, -}) {} - -export interface AbstractLinkedMerkleWitness extends LinkedStructTemplate {} - -export interface AbstractLinkedMerkleTree { - leafStore: LinkedLeafStore; - - tree: RollupMerkleTree; - - /** - * Returns the root of the [Merkle Tree](https://en.wikipedia.org/wiki/Merkle_tree). - * @returns The root of the Merkle Tree. - */ - getRoot(): Field; - - getGlobalState(): LinkedMerkleTreeGlobalState; - - /** - * Sets the value of a leaf node at a given index to a given value. - * @param path of the leaf node. - * @param value New value. - */ - setLeaf(path: bigint, value?: bigint): LinkedStructTemplate; - - /** - * Returns a leaf which lives at a given path. - * Errors otherwise. - * @param path Index of the node. - * @returns The data of the leaf. - */ - getLeaf(path: bigint): LinkedLeafStruct | undefined; - - /** - * Returns the witness (also known as - * [Merkle Proof or Merkle Witness](https://computersciencewiki.org/index.php/Merkle_proof)) - * for the leaf at the given path. - * @param path Position of the leaf node. - * @returns The witness that belongs to the leaf. - */ - getReadWitness(path: bigint): LinkedLeafAndMerkleWitnessTemplate; - - dummyWitness(): LinkedStructTemplate; - - dummyReadWitness(): LinkedLeafAndMerkleWitnessTemplate; -} - -export interface AbstractLinkedMerkleTreeClass { - new ( - store: MerkleTreeStore, - leafStore: LinkedLeafStore - ): AbstractLinkedMerkleTree; - - WITNESS: TypedClass & - typeof LinkedStructTemplate & { - fromReadWitness( - readWitness: LinkedLeafAndMerkleWitnessTemplate - ): AbstractLinkedMerkleWitness; - }; - HEIGHT: number; - - EMPTY_ROOT: bigint; -} +import { InMemoryLinkedLeafStore } from "./InMemoryLinkedLeafStore"; +import { LinkedLeaf, LinkedLeafStore } from "./LinkedMerkleTreeStore"; +import { + LinkedLeafStruct, + LinkedMerkleTreeGlobalState, +} from "./LinkedMerkleTreeTypes"; +import { + AbstractLinkedMerkleTree, + AbstractLinkedMerkleTreeClass, +} from "./AbstractLinkedMerkleTree"; export function createLinkedMerkleTree( height: number ): AbstractLinkedMerkleTreeClass { + const SparseTreeClass = createMerkleTree(height); + class LinkedLeafAndMerkleWitness extends Struct({ leaf: LinkedLeafStruct, - merkleWitness: RollupMerkleTreeWitness, - }) {} - - class LinkedTreeOpWitness - extends Struct({ - leafPrevious: LinkedLeafAndMerkleWitnessTemplate, - leafCurrent: LinkedLeafAndMerkleWitnessTemplate, - }) - implements AbstractLinkedMerkleWitness - { - public static fromReadWitness( - readWitness: LinkedLeafAndMerkleWitnessTemplate - ) { - return new LinkedStructTemplate({ + merkleWitness: SparseTreeClass.WITNESS, + }) { + public checkMembership(root: Field, path: Field, value: Field) { + return this.merkleWitness.checkMembership( + root, + path, + new LinkedLeafStruct({ + ...this.leaf, + value, + }).hash() + ); + } + } + + class LinkedOperationWitness extends Struct({ + leafPrevious: LinkedLeafAndMerkleWitness, + leafCurrent: LinkedLeafAndMerkleWitness, + }) { + // implements LinkedStructTemplate + public static fromReadWitness(readWitness: LinkedLeafAndMerkleWitness) { + return new LinkedOperationWitness({ leafPrevious: new LinkedLeafAndMerkleWitness({ - merkleWitness: RollupMerkleTreeWitness.dummy(), + merkleWitness: SparseTreeClass.WITNESS.dummy(), leaf: LinkedLeafStruct.dummy(), }), leafCurrent: readWitness, @@ -153,8 +52,6 @@ export function createLinkedMerkleTree( } } - const SparseTreeClass = createMerkleTree(height); - return class AbstractLinkedRollupMerkleTree implements AbstractLinkedMerkleTree { @@ -167,7 +64,9 @@ export function createLinkedMerkleTree( .getRoot() .toBigInt(); - public static WITNESS = LinkedTreeOpWitness; + public static READ_WITNESS = LinkedLeafAndMerkleWitness; + + public static WITNESS = LinkedOperationWitness; readonly tree: RollupMerkleTree; @@ -222,10 +121,10 @@ export function createLinkedMerkleTree( } public getGlobalState(): LinkedMerkleTreeGlobalState { - return { + return new LinkedMerkleTreeGlobalState({ root: this.getRoot(), lastOccupiedIndex: Field(this.leafStore.getMaximumIndex() ?? 0n), - }; + }); } /** @@ -233,7 +132,7 @@ export function createLinkedMerkleTree( * @param path Position of the leaf node. * @param value New value. */ - public setLeaf(path: bigint, value: bigint): LinkedTreeOpWitness { + public setLeaf(path: bigint, value: bigint): LinkedOperationWitness { const storedLeaf = this.leafStore.getLeaf(path); // const prevLeaf = this.store.getLeafLessOrEqual(path); // @@ -279,18 +178,19 @@ export function createLinkedMerkleTree( }; this.setMerkleLeaf(nextFreeIndex, newLeaf); - return new LinkedTreeOpWitness({ - leafPrevious: { + return new LinkedOperationWitness({ + leafPrevious: new LinkedLeafAndMerkleWitness({ leaf: new LinkedLeafStruct( LinkedLeafStruct.fromValue(previousLeaf.leaf) ), merkleWitness: previousLeafMerkleWitness, - }, - leafCurrent: { + }), + leafCurrent: new LinkedLeafAndMerkleWitness({ leaf: LinkedLeafStruct.dummy(), merkleWitness: currentMerkleWitness, - }, + }), }); + // eslint-disable-next-line no-else-return } else { // Update case const witnessPrevious = this.dummyReadWitness(); @@ -303,7 +203,7 @@ export function createLinkedMerkleTree( value: value, }); - return new LinkedTreeOpWitness({ + return new LinkedOperationWitness({ leafPrevious: witnessPrevious, leafCurrent: current, }); @@ -367,13 +267,13 @@ export function createLinkedMerkleTree( public dummyReadWitness(): LinkedLeafAndMerkleWitness { return new LinkedLeafAndMerkleWitness({ - merkleWitness: RollupMerkleTreeWitness.dummy(), + merkleWitness: SparseTreeClass.WITNESS.dummy(), leaf: LinkedLeafStruct.dummy(), }); } public dummyWitness() { - return new LinkedTreeOpWitness({ + return new LinkedOperationWitness({ leafPrevious: this.dummyReadWitness(), leafCurrent: this.dummyReadWitness(), }); @@ -383,3 +283,4 @@ export function createLinkedMerkleTree( export class LinkedMerkleTree extends createLinkedMerkleTree(40) {} export class LinkedMerkleTreeWitness extends LinkedMerkleTree.WITNESS {} +export class LinkedMerkleTreeReadWitness extends LinkedMerkleTree.READ_WITNESS {} diff --git a/packages/common/src/trees/lmt/LinkedMerkleTreeCircuitOps.ts b/packages/common/src/trees/lmt/LinkedMerkleTreeCircuitOps.ts index 366cab092..5218b8cb2 100644 --- a/packages/common/src/trees/lmt/LinkedMerkleTreeCircuitOps.ts +++ b/packages/common/src/trees/lmt/LinkedMerkleTreeCircuitOps.ts @@ -1,10 +1,10 @@ import { Bool, Field, Provable, Struct } from "o1js"; +import { LinkedMerkleTreeWitness } from "./LinkedMerkleTree"; import { LinkedLeafStruct, LinkedMerkleTreeGlobalState, - LinkedMerkleTreeWitness, -} from "./LinkedMerkleTree"; +} from "./LinkedMerkleTreeTypes"; /* eslint-disable no-inner-declarations */ // TODO @@ -160,6 +160,7 @@ export namespace LinkedMerkleTreeCircuitOps { newPreviousLeaf: LinkedLeafStruct, newCurrentLeaf: LinkedLeafStruct, isUpdate: Bool, + isDummyAndUpdate: Bool, root: Field ) { const { leafPrevious, leafCurrent } = witness; @@ -184,7 +185,9 @@ export namespace LinkedMerkleTreeCircuitOps { ); leafCurrent.merkleWitness .calculateRoot(leafCurrentLeaf) - .assertEquals(intermediateRoot); + .equals(intermediateRoot) + .or(isDummyAndUpdate) + .assertTrue("Current leaf witness invalid"); return leafCurrent.merkleWitness.calculateRoot(newCurrentLeaf.hash()); } @@ -198,6 +201,7 @@ export namespace LinkedMerkleTreeCircuitOps { const { leafPrevious, leafCurrent } = witness; const isUpdate = leafPrevious.leaf.isDummy(); + const isDummy = leafCurrent.leaf.isDummy().and(isUpdate); // For read-only and update const updateState = update(state, witness, treeWrite); @@ -207,22 +211,29 @@ export namespace LinkedMerkleTreeCircuitOps { const instruction = chooseInstruction(isUpdate, updateState, insertState); - instruction.allChecksMet.assertTrue( - `Not all witness checks have been met: ${index}` - ); + instruction.allChecksMet + .or(isDummy) + .assertTrue(`Not all witness checks have been met: ${index}`); const newRoot = computeRoot( witness, instruction.newPreviousLeaf, instruction.newCurrentLeaf, isUpdate, + isDummy, state.root ); - return { + const updatedState = { root: newRoot, lastOccupiedIndex: instruction.update.lastOccupiedIndex, }; + return Provable.if( + isDummy, + LinkedMerkleTreeGlobalState, + state, + updatedState + ); } } diff --git a/packages/common/src/trees/lmt/LinkedMerkleTreeTypes.ts b/packages/common/src/trees/lmt/LinkedMerkleTreeTypes.ts new file mode 100644 index 000000000..326d6a518 --- /dev/null +++ b/packages/common/src/trees/lmt/LinkedMerkleTreeTypes.ts @@ -0,0 +1,53 @@ +import { Bool, Field, Poseidon, Provable, Struct } from "o1js"; + +export class LinkedLeafStruct extends Struct({ + value: Field, + path: Field, + nextPath: Field, +}) { + public isDummy() { + return this.path.equals(0).and(this.nextPath.equals(0)); + } + + public hash(): Field { + const hash = Poseidon.hash(LinkedLeafStruct.toFields(this)); + return Provable.if(this.isDummy(), Field(0), hash); + } + + public static dummy(): LinkedLeafStruct { + return new LinkedLeafStruct({ + value: Field(0), + path: Field(0), + nextPath: Field(0), + }); + } +} + +export class LinkedMerkleTreeGlobalState extends Struct({ + root: Field, + lastOccupiedIndex: Field, +}) { + public static equals( + state1: LinkedMerkleTreeGlobalState, + state2: LinkedMerkleTreeGlobalState + ): Bool { + return state1.root + .equals(state2.root) + .and(state1.lastOccupiedIndex.equals(state2.lastOccupiedIndex)); + } + + public static assertEquals( + state1: LinkedMerkleTreeGlobalState, + state2: LinkedMerkleTreeGlobalState, + msg?: string + ) { + state1.root.assertEquals( + state2.root, + msg !== undefined ? `${msg}: root` : msg + ); + state1.lastOccupiedIndex.assertEquals( + state2.lastOccupiedIndex, + msg !== undefined ? `${msg}: lastOccupiedIndex` : msg + ); + } +} diff --git a/packages/common/test/trees/LinkedMerkleTreeCircuitOps.test.ts b/packages/common/test/trees/LinkedMerkleTreeCircuitOps.test.ts index 1a0b8b779..841ba2e51 100644 --- a/packages/common/test/trees/LinkedMerkleTreeCircuitOps.test.ts +++ b/packages/common/test/trees/LinkedMerkleTreeCircuitOps.test.ts @@ -110,6 +110,32 @@ describe("LinkedMerkleTree - Circuit Ops", () => { ); }); + it("should noop when used with a dummy witness", () => { + tree.setLeaf(5n, 1000n); + tree.setLeaf(10n, 1500n); + + const root = tree.getGlobalState(); + + const globalState = LinkedMerkleTreeCircuitOps.applyTreeWrite( + root, + tree.dummyWitness(), + { + path: Field(0), + from: Field(0), + to: Field(0), + }, + 0 + ); + + expect(globalState.root.toString()).toStrictEqual(root.root.toString()); + expect(globalState.root.toString()).toStrictEqual( + tree.getRoot().toString() + ); + expect(globalState.lastOccupiedIndex.toString()).toStrictEqual( + root.lastOccupiedIndex.toString() + ); + }); + it("Circuit size", async () => { const root = tree.getGlobalState(); From 597a6ae1f0edbb5db38744d83fc2e187ab562b00 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 8 Apr 2025 15:28:17 +0200 Subject: [PATCH 112/128] Integrated LMT into circuits --- .../src/hooks/LastStateRootBlockHook.ts | 2 +- .../src/model/AppliedStateTransitionBatch.ts | 3 +- .../src/model/StateTransitionProvableBatch.ts | 8 ++- .../src/protocol/ProvableBlockHook.ts | 4 +- .../accumulators/WitnessedRootHashList.ts | 5 +- .../src/prover/block/BlockProvable.ts | 12 ++-- .../protocol/src/prover/block/BlockProver.ts | 36 +++++++--- .../StateTransitionProvable.ts | 10 ++- .../statetransition/StateTransitionProver.ts | 69 +++++++++++++------ .../settlement/contracts/BridgeContract.ts | 18 +++-- .../contracts/SettlementSmartContract.ts | 23 +++++-- .../messages/OutgoingMessageArgument.ts | 12 ++-- .../modularity/ProvableSettlementHook.ts | 4 +- 13 files changed, 142 insertions(+), 64 deletions(-) diff --git a/packages/protocol/src/hooks/LastStateRootBlockHook.ts b/packages/protocol/src/hooks/LastStateRootBlockHook.ts index 4c7cf8091..12dd4b43b 100644 --- a/packages/protocol/src/hooks/LastStateRootBlockHook.ts +++ b/packages/protocol/src/hooks/LastStateRootBlockHook.ts @@ -14,7 +14,7 @@ export class LastStateRootBlockHook extends ProvableBlockHook< return new NetworkState({ block: networkState.block, previous: { - rootHash: stateRoot, + rootHash: stateRoot.root, }, }); } diff --git a/packages/protocol/src/model/AppliedStateTransitionBatch.ts b/packages/protocol/src/model/AppliedStateTransitionBatch.ts index cf904631a..4ba031276 100644 --- a/packages/protocol/src/model/AppliedStateTransitionBatch.ts +++ b/packages/protocol/src/model/AppliedStateTransitionBatch.ts @@ -1,4 +1,5 @@ import { Bool, Field, Poseidon, Provable, Struct } from "o1js"; +import { LinkedMerkleTreeGlobalState } from "@proto-kit/common"; export class AppliedStateTransitionBatch extends Struct({ batchHash: Field, @@ -7,7 +8,7 @@ export class AppliedStateTransitionBatch extends Struct({ export class AppliedStateTransitionBatchState extends Struct({ batchHash: Field, - root: Field, + root: LinkedMerkleTreeGlobalState, }) { public hashOrZero(): Field { const hash = Poseidon.hash(AppliedStateTransitionBatchState.toFields(this)); diff --git a/packages/protocol/src/model/StateTransitionProvableBatch.ts b/packages/protocol/src/model/StateTransitionProvableBatch.ts index c63aeb347..8ee50fdcd 100644 --- a/packages/protocol/src/model/StateTransitionProvableBatch.ts +++ b/packages/protocol/src/model/StateTransitionProvableBatch.ts @@ -1,5 +1,9 @@ import { Bool, Field, Provable, Struct } from "o1js"; -import { batch, RollupMerkleTreeWitness } from "@proto-kit/common"; +import { + batch, + LinkedMerkleTreeWitness, + RollupMerkleTreeWitness, +} from "@proto-kit/common"; import { constants } from "../Constants"; @@ -53,7 +57,7 @@ export class ProvableStateTransitionType extends Struct({ export class MerkleWitnessBatch extends Struct({ witnesses: Provable.Array( - RollupMerkleTreeWitness, + LinkedMerkleTreeWitness, constants.stateTransitionProverBatchSize ), }) {} diff --git a/packages/protocol/src/protocol/ProvableBlockHook.ts b/packages/protocol/src/protocol/ProvableBlockHook.ts index b5381e193..9c1e20787 100644 --- a/packages/protocol/src/protocol/ProvableBlockHook.ts +++ b/packages/protocol/src/protocol/ProvableBlockHook.ts @@ -1,5 +1,5 @@ import { Field } from "o1js"; -import { NoConfig } from "@proto-kit/common"; +import { LinkedMerkleTreeGlobalState, NoConfig } from "@proto-kit/common"; import { NetworkState } from "../model/network/NetworkState"; import { MethodPublicOutput } from "../model/MethodPublicOutput"; @@ -16,7 +16,7 @@ import { export interface BeforeBlockHookArguments extends ProvableHookBlockState {} export interface AfterBlockHookArguments extends BeforeBlockHookArguments { - stateRoot: Field; + stateRoot: LinkedMerkleTreeGlobalState; } export function toBeforeTransactionHookArgument( diff --git a/packages/protocol/src/prover/accumulators/WitnessedRootHashList.ts b/packages/protocol/src/prover/accumulators/WitnessedRootHashList.ts index 9a8250168..76a5313f9 100644 --- a/packages/protocol/src/prover/accumulators/WitnessedRootHashList.ts +++ b/packages/protocol/src/prover/accumulators/WitnessedRootHashList.ts @@ -1,4 +1,5 @@ import { Bool, Field, Struct } from "o1js"; +import { LinkedMerkleTreeGlobalState } from "@proto-kit/common"; import { DefaultProvableHashList } from "../../utils/ProvableHashList"; @@ -8,11 +9,11 @@ import { DefaultProvableHashList } from "../../utils/ProvableHashList"; */ export class WitnessedRoot extends Struct({ appliedBatchListState: Field, - root: Field, + root: LinkedMerkleTreeGlobalState, }) {} export class WitnessedRootWitness extends Struct({ - witnessedRoot: Field, + witnessedRoot: LinkedMerkleTreeGlobalState, preimage: Field, }) {} diff --git a/packages/protocol/src/prover/block/BlockProvable.ts b/packages/protocol/src/prover/block/BlockProvable.ts index 84d6493af..5933cd981 100644 --- a/packages/protocol/src/prover/block/BlockProvable.ts +++ b/packages/protocol/src/prover/block/BlockProvable.ts @@ -8,7 +8,11 @@ import { Struct, Void, } from "o1js"; -import { WithZkProgrammable, CompilableModule } from "@proto-kit/common"; +import { + WithZkProgrammable, + CompilableModule, + LinkedMerkleTreeGlobalState, +} from "@proto-kit/common"; import { StateTransitionProof } from "../statetransition/StateTransitionProvable"; import { MethodPublicOutput } from "../../model/MethodPublicOutput"; @@ -30,7 +34,7 @@ export interface BlockProverState { /** * The current state root of the block prover */ - stateRoot: Field; + stateRoot: LinkedMerkleTreeGlobalState; /** * The current commitment of the transaction-list which @@ -69,7 +73,7 @@ export interface BlockProverState { // TODO Sort and organize public inputs and outputs export class BlockProverStateCommitments extends Struct({ transactionsHash: Field, - stateRoot: Field, + stateRoot: LinkedMerkleTreeGlobalState, // Commitment to the list of unprocessed (pending) batches of STs that need to be proven pendingSTBatchesHash: Field, witnessedRootsHash: Field, @@ -128,7 +132,7 @@ export class BlockProverPublicInput extends BlockProverStateCommitments {} export class BlockProverPublicOutput extends Struct({ transactionsHash: Field, - stateRoot: Field, + stateRoot: LinkedMerkleTreeGlobalState, pendingSTBatchesHash: Field, witnessedRootsHash: Field, networkStateHash: Field, diff --git a/packages/protocol/src/prover/block/BlockProver.ts b/packages/protocol/src/prover/block/BlockProver.ts index 6362e7055..5cbb48180 100644 --- a/packages/protocol/src/prover/block/BlockProver.ts +++ b/packages/protocol/src/prover/block/BlockProver.ts @@ -13,6 +13,7 @@ import { CompilableModule, CompileArtifact, CompileRegistry, + LinkedMerkleTreeGlobalState, log, MAX_FIELD, PlainZkProgram, @@ -476,11 +477,11 @@ export class BlockProverProgrammable extends ZkProgrammable< public includeSTProof( stateTransitionProof: StateTransitionProof, apply: Bool, - stateRoot: Field, + stateRoot: LinkedMerkleTreeGlobalState, pendingSTBatchesHash: Field, witnessedRootsHash: Field ): { - stateRoot: Field; + stateRoot: LinkedMerkleTreeGlobalState; pendingSTBatchesHash: Field; witnessedRootsHash: Field; } { @@ -504,13 +505,21 @@ export class BlockProverProgrammable extends ZkProgrammable< "Batcheshash doesn't start at 0" ); - // Assert from state roots + // Assert from state roots tree root assertEqualsIf( - stateRoot, - stateTransitionProof.publicInput.root, + stateRoot.root, + stateTransitionProof.publicInput.root.root, apply, errors.propertyNotMatching("from state root") ); + // Assert from state roots last occupied index + assertEqualsIf( + stateRoot.lastOccupiedIndex, + stateTransitionProof.publicInput.root.lastOccupiedIndex, + apply, + errors.propertyNotMatching("from state root") + ); + // Assert the stBatchesHash executed is the same assertEqualsIf( pendingSTBatchesHash, @@ -537,6 +546,7 @@ export class BlockProverProgrammable extends ZkProgrammable< // update root only if we didn't defer const newRoot = Provable.if( apply, + LinkedMerkleTreeGlobalState, stateTransitionProof.publicOutput.root, stateRoot ); @@ -621,7 +631,8 @@ export class BlockProverProgrammable extends ZkProgrammable< .assertTrue( "TransactionProof networkstate hash not matching beforeBlock hook result" ); - transactionProof.publicInput.stateRoot.assertEquals( + LinkedMerkleTreeGlobalState.assertEquals( + transactionProof.publicInput.stateRoot, transactionProof.publicOutput.stateRoot, "TransactionProofs can't change the state root" ); @@ -654,7 +665,12 @@ export class BlockProverProgrammable extends ZkProgrammable< // Witness root const isEmpty = state.pendingSTBatches.commitment.equals(0); isEmpty - .implies(state.stateRoot.equals(afterBlockRootWitness.witnessedRoot)) + .implies( + LinkedMerkleTreeGlobalState.equals( + state.stateRoot, + afterBlockRootWitness.witnessedRoot + ) + ) .assertTrue(); state.witnessedRoots.witnessRoot( @@ -748,11 +764,13 @@ export class BlockProverProgrammable extends ZkProgrammable< proof2.verify(); // Check state - publicInput.stateRoot.assertEquals( + LinkedMerkleTreeGlobalState.assertEquals( + publicInput.stateRoot, proof1.publicInput.stateRoot, errors.stateRootNotMatching("publicInput.from -> proof1.from") ); - proof1.publicOutput.stateRoot.assertEquals( + LinkedMerkleTreeGlobalState.assertEquals( + proof1.publicOutput.stateRoot, proof2.publicInput.stateRoot, errors.stateRootNotMatching("proof1.to -> proof2.from") ); diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProvable.ts b/packages/protocol/src/prover/statetransition/StateTransitionProvable.ts index c80b471bd..85c0564da 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProvable.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProvable.ts @@ -1,5 +1,9 @@ import { Field, Proof, Struct } from "o1js"; -import { WithZkProgrammable, CompilableModule } from "@proto-kit/common"; +import { + WithZkProgrammable, + CompilableModule, + LinkedMerkleTreeGlobalState, +} from "@proto-kit/common"; import { MerkleWitnessBatch, @@ -10,14 +14,14 @@ import { AppliedStateTransitionBatchState } from "../../model/AppliedStateTransi export class StateTransitionProverPublicInput extends Struct({ batchesHash: Field, currentBatchStateHash: Field, - root: Field, + root: LinkedMerkleTreeGlobalState, witnessedRootsHash: Field, }) {} export class StateTransitionProverPublicOutput extends Struct({ batchesHash: Field, currentBatchStateHash: Field, - root: Field, + root: LinkedMerkleTreeGlobalState, witnessedRootsHash: Field, }) {} diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index 6adaf7e76..0f448d2f9 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -7,6 +7,10 @@ import { CompilableModule, type ArtifactRecord, type CompileRegistry, + TreeWrite, + LinkedMerkleTreeCircuitOps, + LinkedMerkleTreeGlobalState, + LinkedMerkleTreeWitness, } from "@proto-kit/common"; import { Field, Provable, SelfProof, ZkProgram } from "o1js"; import { injectable } from "tsyringe"; @@ -43,7 +47,7 @@ const errors = { interface StateTransitionProverExecutionState { currentBatch: AppliedStateTransitionBatchState; batchList: AppliedBatchHashList; - finalizedRoot: Field; + finalizedRoot: LinkedMerkleTreeGlobalState; witnessedRoots: WitnessedRootHashList; } @@ -143,6 +147,27 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< ]; } + private transitionToTreeWrite( + st: ProvableStateTransition, + witness: LinkedMerkleTreeWitness + ): TreeWrite { + // If from isSome isn't set, the user "ignored the previous value", i.e. we + // can assume the value in the witness is correct + const from = Provable.if( + st.from.isSome, + st.from.value, + witness.leafCurrent.leaf.value + ); + + // If the user doesn't want to write, we just carry over the from-value + const to = Provable.if(st.to.isSome, st.to.value, from); + return { + path: st.path, + from, + to, + }; + } + /** * Applies the state transitions to the current stateRoot * and returns the new prover state @@ -178,6 +203,7 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< // The root is based on if the previous batch will be applied or not const base = Provable.if( closingAndApply, + LinkedMerkleTreeGlobalState, updatedBatchState.root, state.finalizedRoot ); @@ -193,6 +219,7 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< state.batchList.pushIf(updatedBatch, closing); state.finalizedRoot = Provable.if( closingAndApply, + LinkedMerkleTreeGlobalState, updatedBatchState.root, state.finalizedRoot ); @@ -248,7 +275,7 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< public applyTransition( currentBatch: AppliedStateTransitionBatchState, transition: ProvableStateTransition, - witness: RollupMerkleTreeWitness, + witness: LinkedMerkleTreeWitness, index = 0 ) { const impliedRoot = this.applyTransitionToRoot( @@ -274,23 +301,18 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< private applyTransitionToRoot( transition: ProvableStateTransition, - root: Field, - merkleWitness: RollupMerkleTreeWitness, + treeState: LinkedMerkleTreeGlobalState, + merkleWitness: LinkedMerkleTreeWitness, index: number - ): Field { - const membershipValid = merkleWitness.checkMembership( - root, - transition.path, - transition.from.value - ); + ): LinkedMerkleTreeGlobalState { + const treeWrite = this.transitionToTreeWrite(transition, merkleWitness); - membershipValid - .or(transition.from.isSome.not()) - .assertTrue(errors.merkleWitnessNotCorrect(index)); - - const newRoot = merkleWitness.calculateRoot(transition.to.value); - - return Provable.if(transition.to.isSome, newRoot, root); + return LinkedMerkleTreeCircuitOps.applyTreeWrite( + treeState, + merkleWitness, + treeWrite, + index + ); } /** @@ -312,8 +334,10 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< // Assert that either the currentAppliedBatch is somewhere intermediary // or the root is the current "finalized" root - currentAppliedBatch.root - .equals(publicInput.root) + LinkedMerkleTreeGlobalState.equals( + currentAppliedBatch.root, + publicInput.root + ) .or(publicInput.currentBatchStateHash.equals(0).not()) .assertTrue(); @@ -373,11 +397,14 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< ); // Check root - publicInput.root.assertEquals( + LinkedMerkleTreeGlobalState.assertEquals( + publicInput.root, proof1.publicInput.root, errors.propertyNotMatching("root", "publicInput.from -> proof1.from") ); - proof1.publicOutput.root.assertEquals( + + LinkedMerkleTreeGlobalState.assertEquals( + proof1.publicOutput.root, proof2.publicInput.root, errors.propertyNotMatching("root", "proof1.to -> proof2.from") ); diff --git a/packages/protocol/src/settlement/contracts/BridgeContract.ts b/packages/protocol/src/settlement/contracts/BridgeContract.ts index 55048889a..d426d9470 100644 --- a/packages/protocol/src/settlement/contracts/BridgeContract.ts +++ b/packages/protocol/src/settlement/contracts/BridgeContract.ts @@ -15,7 +15,13 @@ import { TokenId, VerificationKey, } from "o1js"; -import { noop, range, TypedClass } from "@proto-kit/common"; +import { + LinkedLeafStruct, + LinkedMerkleTreeGlobalState, + noop, + range, + TypedClass, +} from "@proto-kit/common"; import { OUTGOING_MESSAGE_BATCH_SIZE, @@ -43,7 +49,7 @@ export type BridgeContractType = { settlementContractAddress: PublicKey ) => Promise; - updateStateRoot: (root: Field) => Promise; + updateStateRoot: (root: LinkedMerkleTreeGlobalState) => Promise; }; // Equal to WithdrawalKey @@ -112,8 +118,10 @@ export abstract class BridgeContractBase extends TokenContractV2 { noop(); } - public async updateStateRootBase(root: Field) { - this.stateRoot.set(root); + public async updateStateRootBase(root: LinkedMerkleTreeGlobalState) { + // It's fine for us to only store the actual root since we only have to + // witness values, not update/insert + this.stateRoot.set(root.root); const settlementContractAddress = this.settlementContractAddress.getAndRequireEquals(); @@ -224,7 +232,7 @@ export class BridgeContract @state(Field) public outgoingMessageCursor = State(); @method - public async updateStateRoot(root: Field) { + public async updateStateRoot(root: LinkedMerkleTreeGlobalState) { return await this.updateStateRootBase(root); } diff --git a/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts b/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts index 675c4ce72..865a8ae0d 100644 --- a/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts +++ b/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts @@ -4,6 +4,7 @@ import { TypedClass, mapSequential, ChildVerificationKeyService, + LinkedMerkleTreeGlobalState, } from "@proto-kit/common"; import { AccountUpdate, @@ -71,7 +72,7 @@ export interface SettlementContractType { bridgeContract: PublicKey, contractKey: PrivateKey ) => Promise; - assertStateRoot: (root: Field) => AccountUpdate; + assertStateRoot: (root: LinkedMerkleTreeGlobalState) => AccountUpdate; settle: ( blockProof: DynamicBlockProof, signature: Signature, @@ -127,7 +128,7 @@ export abstract class SettlementSmartContractBase extends TokenContractV2 { abstract sequencerKey: State; abstract lastSettlementL1BlockHeight: State; - abstract stateRoot: State; + abstract stateRoot: State; abstract networkStateHash: State; abstract blockHashRoot: State; abstract dispatchContractAddressX: State; @@ -137,7 +138,7 @@ export abstract class SettlementSmartContractBase extends TokenContractV2 { // Not @state // abstract offchainStateCommitmentsHash: State; - public assertStateRoot(root: Field): AccountUpdate { + public assertStateRoot(root: LinkedMerkleTreeGlobalState): AccountUpdate { this.stateRoot.requireEquals(root); return this.self; } @@ -250,13 +251,19 @@ export abstract class SettlementSmartContractBase extends TokenContractV2 { contractKey: PrivateKey ) { this.sequencerKey.getAndRequireEquals().assertEquals(Field(0)); - this.stateRoot.getAndRequireEquals().assertEquals(Field(0)); + LinkedMerkleTreeGlobalState.assertEquals( + this.stateRoot.getAndRequireEquals(), + { root: Field(0), lastOccupiedIndex: Field(0) } + ); this.blockHashRoot.getAndRequireEquals().assertEquals(Field(0)); this.networkStateHash.getAndRequireEquals().assertEquals(Field(0)); this.dispatchContractAddressX.getAndRequireEquals().assertEquals(Field(0)); this.sequencerKey.set(sequencer.x); - this.stateRoot.set(Field(RollupMerkleTree.EMPTY_ROOT)); + this.stateRoot.set({ + root: Field(RollupMerkleTree.EMPTY_ROOT), + lastOccupiedIndex: Field(0), + }); this.blockHashRoot.set(Field(BlockHashMerkleTree.EMPTY_ROOT)); this.networkStateHash.set(NetworkState.empty().hash()); this.dispatchContractAddressX.set(dispatchContract.x); @@ -388,7 +395,8 @@ export abstract class SettlementSmartContractBase extends TokenContractV2 { }); // Apply blockProof - stateRoot.assertEquals( + LinkedMerkleTreeGlobalState.assertEquals( + stateRoot, blockProof.publicInput.stateRoot, "Input state root not matching" ); @@ -441,7 +449,8 @@ export class SettlementSmartContract @state(Field) public sequencerKey = State(); @state(UInt32) public lastSettlementL1BlockHeight = State(); - @state(Field) public stateRoot = State(); + @state(LinkedMerkleTreeGlobalState) public stateRoot = + State(); @state(Field) public networkStateHash = State(); @state(Field) public blockHashRoot = State(); diff --git a/packages/protocol/src/settlement/messages/OutgoingMessageArgument.ts b/packages/protocol/src/settlement/messages/OutgoingMessageArgument.ts index 112383e59..9c2348e4f 100644 --- a/packages/protocol/src/settlement/messages/OutgoingMessageArgument.ts +++ b/packages/protocol/src/settlement/messages/OutgoingMessageArgument.ts @@ -1,8 +1,9 @@ import { Bool, Provable, Struct } from "o1js"; import { - InMemoryLinkedMerkleLeafStore, - LinkedLeafAndMerkleWitness, + InMemoryLinkedLeafStore, + InMemoryMerkleTreeStorage, LinkedMerkleTree, + LinkedMerkleTreeReadWitness, } from "@proto-kit/common"; import { Withdrawal } from "./Withdrawal"; @@ -10,14 +11,15 @@ import { Withdrawal } from "./Withdrawal"; export const OUTGOING_MESSAGE_BATCH_SIZE = 1; export class OutgoingMessageArgument extends Struct({ - witness: LinkedLeafAndMerkleWitness, + witness: LinkedMerkleTreeReadWitness, value: Withdrawal, }) { public static dummy(): OutgoingMessageArgument { return new OutgoingMessageArgument({ witness: new LinkedMerkleTree( - new InMemoryLinkedMerkleLeafStore() - ).dummy(), + new InMemoryMerkleTreeStorage(), + new InMemoryLinkedLeafStore() + ).dummyReadWitness(), value: Withdrawal.dummy(), }); } diff --git a/packages/protocol/src/settlement/modularity/ProvableSettlementHook.ts b/packages/protocol/src/settlement/modularity/ProvableSettlementHook.ts index ecb117129..c7a5e2409 100644 --- a/packages/protocol/src/settlement/modularity/ProvableSettlementHook.ts +++ b/packages/protocol/src/settlement/modularity/ProvableSettlementHook.ts @@ -1,5 +1,5 @@ import { Field, PublicKey, UInt32 } from "o1js"; -import { InferProofBase } from "@proto-kit/common"; +import { InferProofBase, LinkedMerkleTreeGlobalState } from "@proto-kit/common"; import { ProtocolModule } from "../../protocol/ProtocolModule"; import { NetworkState } from "../../model/network/NetworkState"; @@ -12,7 +12,7 @@ export type SettlementStateRecord = { sequencerKey: PublicKey; lastSettlementL1BlockHeight: UInt32; - stateRoot: Field; + stateRoot: LinkedMerkleTreeGlobalState; networkStateHash: Field; blockHashRoot: Field; }; From f5db8055d6902e467c3bfe88976ba3fe35121dce Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 10 Apr 2025 17:23:23 +0200 Subject: [PATCH 113/128] Finished LMT implementation, removed lastOccupiedIndex from state --- packages/common/src/index.ts | 3 +- .../src/trees/lmt/AbstractLinkedMerkleTree.ts | 11 +--- .../src/trees/lmt/InMemoryLinkedLeafStore.ts | 6 +- .../lmt/InMemoryLinkedMerkleLeafStore.ts | 9 --- .../common/src/trees/lmt/LinkedLinkedStore.ts | 17 ++++++ .../common/src/trees/lmt/LinkedMerkleTree.ts | 18 +----- .../trees/lmt/LinkedMerkleTreeCircuitOps.ts | 48 +++------------ .../src/trees/lmt/LinkedMerkleTreeStore.ts | 22 ------- .../src/trees/lmt/LinkedMerkleTreeTypes.ts | 58 +++++++++---------- packages/common/src/utils.ts | 6 ++ 10 files changed, 73 insertions(+), 125 deletions(-) delete mode 100644 packages/common/src/trees/lmt/InMemoryLinkedMerkleLeafStore.ts create mode 100644 packages/common/src/trees/lmt/LinkedLinkedStore.ts delete mode 100644 packages/common/src/trees/lmt/LinkedMerkleTreeStore.ts diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index e28bfab6f..60968d4c8 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -14,12 +14,11 @@ export * from "./log"; export * from "./events/EventEmittingComponent"; export * from "./events/EventEmitter"; export * from "./trees/sparse/MerkleTreeStore"; -export * from "./trees/lmt/LinkedMerkleTreeStore"; export * from "./trees/sparse/InMemoryMerkleTreeStorage"; export * from "./trees/sparse/RollupMerkleTree"; +export * from "./trees/lmt/LinkedLinkedStore"; export * from "./trees/lmt/LinkedMerkleTree"; export * from "./trees/lmt/InMemoryLinkedLeafStore"; -export * from "./trees/lmt/InMemoryLinkedMerkleLeafStore"; export * from "./trees/lmt/LinkedMerkleTreeCircuitOps"; export * from "./trees/lmt/AbstractLinkedMerkleTree"; export * from "./trees/lmt/LinkedMerkleTreeTypes"; diff --git a/packages/common/src/trees/lmt/AbstractLinkedMerkleTree.ts b/packages/common/src/trees/lmt/AbstractLinkedMerkleTree.ts index 22b430daa..697904520 100644 --- a/packages/common/src/trees/lmt/AbstractLinkedMerkleTree.ts +++ b/packages/common/src/trees/lmt/AbstractLinkedMerkleTree.ts @@ -4,11 +4,8 @@ import { createMerkleTree, RollupMerkleTree } from "../sparse/RollupMerkleTree"; import { TypedClass } from "../../types"; import { MerkleTreeStore } from "../sparse/MerkleTreeStore"; -import { LinkedLeafStore } from "./LinkedMerkleTreeStore"; -import { - LinkedLeafStruct, - LinkedMerkleTreeGlobalState, -} from "./LinkedMerkleTreeTypes"; +import { LinkedLeafStore } from "./LinkedLinkedStore"; +import { LinkedLeafStruct } from "./LinkedMerkleTreeTypes"; class RollupMerkleTreeWitness extends createMerkleTree(40).WITNESS {} @@ -51,8 +48,6 @@ export interface AbstractLinkedMerkleTree { */ getRoot(): Field; - getGlobalState(): LinkedMerkleTreeGlobalState; - /** * Sets the value of a leaf node at a given index to a given value. * @param path of the leaf node. @@ -101,5 +96,5 @@ export interface AbstractLinkedMerkleTreeClass { HEIGHT: number; - EMPTY_ROOT: bigint; + EMPTY_ROOT: Field; } diff --git a/packages/common/src/trees/lmt/InMemoryLinkedLeafStore.ts b/packages/common/src/trees/lmt/InMemoryLinkedLeafStore.ts index 0af12a83c..f571af642 100644 --- a/packages/common/src/trees/lmt/InMemoryLinkedLeafStore.ts +++ b/packages/common/src/trees/lmt/InMemoryLinkedLeafStore.ts @@ -1,10 +1,12 @@ -import { LinkedLeafStore, LinkedLeaf } from "./LinkedMerkleTreeStore"; +import { LinkedLeafStore, LinkedLeaf } from "./LinkedLinkedStore"; export class InMemoryLinkedLeafStore implements LinkedLeafStore { public leaves: { [key: string]: { leaf: LinkedLeaf; index: bigint }; } = {}; + // public treeStore = new InMemoryMerkleTreeStorage(); + public maximumIndex?: bigint; public getLeaf( @@ -15,9 +17,11 @@ export class InMemoryLinkedLeafStore implements LinkedLeafStore { 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; diff --git a/packages/common/src/trees/lmt/InMemoryLinkedMerkleLeafStore.ts b/packages/common/src/trees/lmt/InMemoryLinkedMerkleLeafStore.ts deleted file mode 100644 index 6292a58ee..000000000 --- a/packages/common/src/trees/lmt/InMemoryLinkedMerkleLeafStore.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Mixin } from "ts-mixer"; - -import { InMemoryLinkedLeafStore } from "./InMemoryLinkedLeafStore"; -import { InMemoryMerkleTreeStorage } from "../sparse/InMemoryMerkleTreeStorage"; - -export class InMemoryLinkedMerkleLeafStore extends Mixin( - InMemoryLinkedLeafStore, - InMemoryMerkleTreeStorage -) {} diff --git a/packages/common/src/trees/lmt/LinkedLinkedStore.ts b/packages/common/src/trees/lmt/LinkedLinkedStore.ts new file mode 100644 index 000000000..77fd5760d --- /dev/null +++ b/packages/common/src/trees/lmt/LinkedLinkedStore.ts @@ -0,0 +1,17 @@ +import { MerkleTreeStore } from "../sparse/MerkleTreeStore"; + +export type LinkedLeaf = { value: bigint; path: bigint; nextPath: bigint }; + +export type StoredLeaf = { leaf: LinkedLeaf; index: bigint }; + +export interface LinkedLeafStore { + // treeStore: MerkleTreeStore; + + setLeaf: (index: bigint, value: LinkedLeaf) => void; + + getLeaf: (path: bigint) => StoredLeaf | undefined; + + getLeafLessOrEqual: (path: bigint) => StoredLeaf | undefined; + + getMaximumIndex: () => bigint | undefined; +} diff --git a/packages/common/src/trees/lmt/LinkedMerkleTree.ts b/packages/common/src/trees/lmt/LinkedMerkleTree.ts index 42ecb0327..afb342a3b 100644 --- a/packages/common/src/trees/lmt/LinkedMerkleTree.ts +++ b/packages/common/src/trees/lmt/LinkedMerkleTree.ts @@ -5,11 +5,8 @@ import { MerkleTreeStore } from "../sparse/MerkleTreeStore"; import { InMemoryMerkleTreeStorage } from "../sparse/InMemoryMerkleTreeStorage"; import { InMemoryLinkedLeafStore } from "./InMemoryLinkedLeafStore"; -import { LinkedLeaf, LinkedLeafStore } from "./LinkedMerkleTreeStore"; -import { - LinkedLeafStruct, - LinkedMerkleTreeGlobalState, -} from "./LinkedMerkleTreeTypes"; +import { LinkedLeaf, LinkedLeafStore } from "./LinkedLinkedStore"; +import { LinkedLeafStruct } from "./LinkedMerkleTreeTypes"; import { AbstractLinkedMerkleTree, AbstractLinkedMerkleTreeClass, @@ -60,9 +57,7 @@ export function createLinkedMerkleTree( public static EMPTY_ROOT = new AbstractLinkedRollupMerkleTree( new InMemoryMerkleTreeStorage(), new InMemoryLinkedLeafStore() - ) - .getRoot() - .toBigInt(); + ).getRoot(); public static READ_WITNESS = LinkedLeafAndMerkleWitness; @@ -120,13 +115,6 @@ export function createLinkedMerkleTree( this.tree.setLeaf(index, leafHash); } - public getGlobalState(): LinkedMerkleTreeGlobalState { - return new LinkedMerkleTreeGlobalState({ - root: this.getRoot(), - lastOccupiedIndex: Field(this.leafStore.getMaximumIndex() ?? 0n), - }); - } - /** * Sets the value of a node at a given index to a given value. * @param path Position of the leaf node. diff --git a/packages/common/src/trees/lmt/LinkedMerkleTreeCircuitOps.ts b/packages/common/src/trees/lmt/LinkedMerkleTreeCircuitOps.ts index 5218b8cb2..543088f4f 100644 --- a/packages/common/src/trees/lmt/LinkedMerkleTreeCircuitOps.ts +++ b/packages/common/src/trees/lmt/LinkedMerkleTreeCircuitOps.ts @@ -1,10 +1,7 @@ import { Bool, Field, Provable, Struct } from "o1js"; import { LinkedMerkleTreeWitness } from "./LinkedMerkleTree"; -import { - LinkedLeafStruct, - LinkedMerkleTreeGlobalState, -} from "./LinkedMerkleTreeTypes"; +import { LinkedLeafStruct } from "./LinkedMerkleTreeTypes"; /* eslint-disable no-inner-declarations */ // TODO @@ -38,12 +35,6 @@ export type TreeWrite = { }; export namespace LinkedMerkleTreeCircuitOps { - export class LinkedMerkleTreeGlobalStateWithoutRoot - extends Struct({ - lastOccupiedIndex: Field, - }) - implements Omit {} - function boolAllTrue(...args: Bool[]): Bool { return args.reduce((a, b, i) => { // if (!b.toBoolean()) { @@ -57,7 +48,6 @@ export namespace LinkedMerkleTreeCircuitOps { newPreviousLeaf: LinkedLeafStruct, newCurrentLeaf: LinkedLeafStruct, allChecksMet: Bool, - update: LinkedMerkleTreeGlobalStateWithoutRoot, }) {} function chooseInstruction( @@ -85,7 +75,6 @@ export namespace LinkedMerkleTreeCircuitOps { * current := { current.path, current.nextPath, value: st.to.value } */ function update( - state: LinkedMerkleTreeGlobalState, { leafCurrent, leafPrevious }: LinkedMerkleTreeWitness, { to, from, path }: TreeWrite ): ComputeRootInstruction { @@ -101,9 +90,6 @@ export namespace LinkedMerkleTreeCircuitOps { value: to, }), allChecksMet, - update: { - lastOccupiedIndex: state.lastOccupiedIndex, - }, }; } @@ -121,21 +107,17 @@ export namespace LinkedMerkleTreeCircuitOps { * current := current.leaf */ function insert( - state: LinkedMerkleTreeGlobalState, witness: LinkedMerkleTreeWitness, { path, to }: TreeWrite ): ComputeRootInstruction { const { leafPrevious: previous, leafCurrent: current } = witness; - const nextFreeIndex = state.lastOccupiedIndex.add(1); - const allChecksMet = boolAllTrue( // Already covered in general checks // path.equals(current.leaf.path), current.leaf.isDummy(), previous.leaf.nextPath.greaterThan(path), - previous.leaf.path.lessThan(path), - current.merkleWitness.calculateIndex().equals(nextFreeIndex) + previous.leaf.path.lessThan(path) ); return { @@ -149,9 +131,6 @@ export namespace LinkedMerkleTreeCircuitOps { nextPath: previous.leaf.nextPath, }), allChecksMet, - update: { - lastOccupiedIndex: nextFreeIndex, - }, }; } @@ -175,7 +154,7 @@ export namespace LinkedMerkleTreeCircuitOps { newPreviousLeaf.hash() ); - let intermediateRoot = Provable.if(isUpdate, root, root1); + const intermediateRoot = Provable.if(isUpdate, root, root1); // TODO Make this Provable.if more efficient const leafCurrentLeaf = Provable.if( @@ -193,21 +172,21 @@ export namespace LinkedMerkleTreeCircuitOps { } export function applyTreeWrite( - state: LinkedMerkleTreeGlobalState, + root: Field, witness: LinkedMerkleTreeWitness, treeWrite: TreeWrite, index: number - ): LinkedMerkleTreeGlobalState { + ): Field { const { leafPrevious, leafCurrent } = witness; const isUpdate = leafPrevious.leaf.isDummy(); const isDummy = leafCurrent.leaf.isDummy().and(isUpdate); // For read-only and update - const updateState = update(state, witness, treeWrite); + const updateState = update(witness, treeWrite); // For insert - const insertState = insert(state, witness, treeWrite); + const insertState = insert(witness, treeWrite); const instruction = chooseInstruction(isUpdate, updateState, insertState); @@ -221,19 +200,10 @@ export namespace LinkedMerkleTreeCircuitOps { instruction.newCurrentLeaf, isUpdate, isDummy, - state.root + root ); - const updatedState = { - root: newRoot, - lastOccupiedIndex: instruction.update.lastOccupiedIndex, - }; - return Provable.if( - isDummy, - LinkedMerkleTreeGlobalState, - state, - updatedState - ); + return Provable.if(isDummy, root, newRoot); } } diff --git a/packages/common/src/trees/lmt/LinkedMerkleTreeStore.ts b/packages/common/src/trees/lmt/LinkedMerkleTreeStore.ts deleted file mode 100644 index ea766774d..000000000 --- a/packages/common/src/trees/lmt/LinkedMerkleTreeStore.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { MerkleTreeStore } from "../sparse/MerkleTreeStore"; - -export interface LinkedLeafStore { - setLeaf: (index: bigint, value: LinkedLeaf) => void; - - getLeaf: (path: bigint) => { leaf: LinkedLeaf; index: bigint } | undefined; - - getLeafLessOrEqual: ( - path: bigint - ) => { leaf: LinkedLeaf; index: bigint } | undefined; - - getMaximumIndex: () => bigint | undefined; -} - -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/common/src/trees/lmt/LinkedMerkleTreeTypes.ts b/packages/common/src/trees/lmt/LinkedMerkleTreeTypes.ts index 326d6a518..ecd1ec970 100644 --- a/packages/common/src/trees/lmt/LinkedMerkleTreeTypes.ts +++ b/packages/common/src/trees/lmt/LinkedMerkleTreeTypes.ts @@ -1,4 +1,4 @@ -import { Bool, Field, Poseidon, Provable, Struct } from "o1js"; +import { Field, Poseidon, Provable, Struct } from "o1js"; export class LinkedLeafStruct extends Struct({ value: Field, @@ -23,31 +23,31 @@ export class LinkedLeafStruct extends Struct({ } } -export class LinkedMerkleTreeGlobalState extends Struct({ - root: Field, - lastOccupiedIndex: Field, -}) { - public static equals( - state1: LinkedMerkleTreeGlobalState, - state2: LinkedMerkleTreeGlobalState - ): Bool { - return state1.root - .equals(state2.root) - .and(state1.lastOccupiedIndex.equals(state2.lastOccupiedIndex)); - } - - public static assertEquals( - state1: LinkedMerkleTreeGlobalState, - state2: LinkedMerkleTreeGlobalState, - msg?: string - ) { - state1.root.assertEquals( - state2.root, - msg !== undefined ? `${msg}: root` : msg - ); - state1.lastOccupiedIndex.assertEquals( - state2.lastOccupiedIndex, - msg !== undefined ? `${msg}: lastOccupiedIndex` : msg - ); - } -} +// export class LinkedMerkleTreeGlobalState extends Struct({ +// root: Field, +// lastOccupiedIndex: Field, +// }) { +// public static equals( +// state1: LinkedMerkleTreeGlobalState, +// state2: LinkedMerkleTreeGlobalState +// ): Bool { +// return state1.root +// .equals(state2.root) +// .and(state1.lastOccupiedIndex.equals(state2.lastOccupiedIndex)); +// } +// +// public static assertEquals( +// state1: LinkedMerkleTreeGlobalState, +// state2: LinkedMerkleTreeGlobalState, +// msg?: string +// ) { +// state1.root.assertEquals( +// state2.root, +// msg !== undefined ? `${msg}: root` : msg +// ); +// state1.lastOccupiedIndex.assertEquals( +// state2.lastOccupiedIndex, +// msg !== undefined ? `${msg}: lastOccupiedIndex` : msg +// ); +// } +// } diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts index 316be8184..e412ec5b3 100644 --- a/packages/common/src/utils.ts +++ b/packages/common/src/utils.ts @@ -280,3 +280,9 @@ class ReferenceObject { export function createReference(initial: T): Reference { return new ReferenceObject(initial); } + +export namespace BigIntMath { + export function max(...args: bigint[]) { + return args.reduce((m, e) => (e > m ? e : m)); + } +} From d8a1df5738d4828a2c68a69e0695c8e881e97809 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 10 Apr 2025 17:23:42 +0200 Subject: [PATCH 114/128] Adapted protocol to changed LMT interface --- .../src/hooks/LastStateRootBlockHook.ts | 2 +- .../src/model/AppliedStateTransitionBatch.ts | 3 +- .../src/protocol/ProvableBlockHook.ts | 4 +-- .../accumulators/WitnessedRootHashList.ts | 5 ++- .../src/prover/block/BlockProvable.ts | 12 +++---- .../protocol/src/prover/block/BlockProver.ts | 35 +++++-------------- .../StateTransitionProvable.ts | 10 ++---- .../statetransition/StateTransitionProver.ts | 24 +++++-------- .../settlement/contracts/BridgeContract.ts | 16 +++------ .../contracts/SettlementSmartContract.ts | 24 +++++-------- .../modularity/ProvableSettlementHook.ts | 4 +-- 11 files changed, 45 insertions(+), 94 deletions(-) diff --git a/packages/protocol/src/hooks/LastStateRootBlockHook.ts b/packages/protocol/src/hooks/LastStateRootBlockHook.ts index 12dd4b43b..4c7cf8091 100644 --- a/packages/protocol/src/hooks/LastStateRootBlockHook.ts +++ b/packages/protocol/src/hooks/LastStateRootBlockHook.ts @@ -14,7 +14,7 @@ export class LastStateRootBlockHook extends ProvableBlockHook< return new NetworkState({ block: networkState.block, previous: { - rootHash: stateRoot.root, + rootHash: stateRoot, }, }); } diff --git a/packages/protocol/src/model/AppliedStateTransitionBatch.ts b/packages/protocol/src/model/AppliedStateTransitionBatch.ts index 4ba031276..cf904631a 100644 --- a/packages/protocol/src/model/AppliedStateTransitionBatch.ts +++ b/packages/protocol/src/model/AppliedStateTransitionBatch.ts @@ -1,5 +1,4 @@ import { Bool, Field, Poseidon, Provable, Struct } from "o1js"; -import { LinkedMerkleTreeGlobalState } from "@proto-kit/common"; export class AppliedStateTransitionBatch extends Struct({ batchHash: Field, @@ -8,7 +7,7 @@ export class AppliedStateTransitionBatch extends Struct({ export class AppliedStateTransitionBatchState extends Struct({ batchHash: Field, - root: LinkedMerkleTreeGlobalState, + root: Field, }) { public hashOrZero(): Field { const hash = Poseidon.hash(AppliedStateTransitionBatchState.toFields(this)); diff --git a/packages/protocol/src/protocol/ProvableBlockHook.ts b/packages/protocol/src/protocol/ProvableBlockHook.ts index 9c1e20787..b5381e193 100644 --- a/packages/protocol/src/protocol/ProvableBlockHook.ts +++ b/packages/protocol/src/protocol/ProvableBlockHook.ts @@ -1,5 +1,5 @@ import { Field } from "o1js"; -import { LinkedMerkleTreeGlobalState, NoConfig } from "@proto-kit/common"; +import { NoConfig } from "@proto-kit/common"; import { NetworkState } from "../model/network/NetworkState"; import { MethodPublicOutput } from "../model/MethodPublicOutput"; @@ -16,7 +16,7 @@ import { export interface BeforeBlockHookArguments extends ProvableHookBlockState {} export interface AfterBlockHookArguments extends BeforeBlockHookArguments { - stateRoot: LinkedMerkleTreeGlobalState; + stateRoot: Field; } export function toBeforeTransactionHookArgument( diff --git a/packages/protocol/src/prover/accumulators/WitnessedRootHashList.ts b/packages/protocol/src/prover/accumulators/WitnessedRootHashList.ts index 76a5313f9..9a8250168 100644 --- a/packages/protocol/src/prover/accumulators/WitnessedRootHashList.ts +++ b/packages/protocol/src/prover/accumulators/WitnessedRootHashList.ts @@ -1,5 +1,4 @@ import { Bool, Field, Struct } from "o1js"; -import { LinkedMerkleTreeGlobalState } from "@proto-kit/common"; import { DefaultProvableHashList } from "../../utils/ProvableHashList"; @@ -9,11 +8,11 @@ import { DefaultProvableHashList } from "../../utils/ProvableHashList"; */ export class WitnessedRoot extends Struct({ appliedBatchListState: Field, - root: LinkedMerkleTreeGlobalState, + root: Field, }) {} export class WitnessedRootWitness extends Struct({ - witnessedRoot: LinkedMerkleTreeGlobalState, + witnessedRoot: Field, preimage: Field, }) {} diff --git a/packages/protocol/src/prover/block/BlockProvable.ts b/packages/protocol/src/prover/block/BlockProvable.ts index 5933cd981..84d6493af 100644 --- a/packages/protocol/src/prover/block/BlockProvable.ts +++ b/packages/protocol/src/prover/block/BlockProvable.ts @@ -8,11 +8,7 @@ import { Struct, Void, } from "o1js"; -import { - WithZkProgrammable, - CompilableModule, - LinkedMerkleTreeGlobalState, -} from "@proto-kit/common"; +import { WithZkProgrammable, CompilableModule } from "@proto-kit/common"; import { StateTransitionProof } from "../statetransition/StateTransitionProvable"; import { MethodPublicOutput } from "../../model/MethodPublicOutput"; @@ -34,7 +30,7 @@ export interface BlockProverState { /** * The current state root of the block prover */ - stateRoot: LinkedMerkleTreeGlobalState; + stateRoot: Field; /** * The current commitment of the transaction-list which @@ -73,7 +69,7 @@ export interface BlockProverState { // TODO Sort and organize public inputs and outputs export class BlockProverStateCommitments extends Struct({ transactionsHash: Field, - stateRoot: LinkedMerkleTreeGlobalState, + stateRoot: Field, // Commitment to the list of unprocessed (pending) batches of STs that need to be proven pendingSTBatchesHash: Field, witnessedRootsHash: Field, @@ -132,7 +128,7 @@ export class BlockProverPublicInput extends BlockProverStateCommitments {} export class BlockProverPublicOutput extends Struct({ transactionsHash: Field, - stateRoot: LinkedMerkleTreeGlobalState, + stateRoot: Field, pendingSTBatchesHash: Field, witnessedRootsHash: Field, networkStateHash: Field, diff --git a/packages/protocol/src/prover/block/BlockProver.ts b/packages/protocol/src/prover/block/BlockProver.ts index 5cbb48180..e66e34e22 100644 --- a/packages/protocol/src/prover/block/BlockProver.ts +++ b/packages/protocol/src/prover/block/BlockProver.ts @@ -13,7 +13,6 @@ import { CompilableModule, CompileArtifact, CompileRegistry, - LinkedMerkleTreeGlobalState, log, MAX_FIELD, PlainZkProgram, @@ -477,11 +476,11 @@ export class BlockProverProgrammable extends ZkProgrammable< public includeSTProof( stateTransitionProof: StateTransitionProof, apply: Bool, - stateRoot: LinkedMerkleTreeGlobalState, + stateRoot: Field, pendingSTBatchesHash: Field, witnessedRootsHash: Field ): { - stateRoot: LinkedMerkleTreeGlobalState; + stateRoot: Field; pendingSTBatchesHash: Field; witnessedRootsHash: Field; } { @@ -505,17 +504,10 @@ export class BlockProverProgrammable extends ZkProgrammable< "Batcheshash doesn't start at 0" ); - // Assert from state roots tree root + // Assert from state root assertEqualsIf( - stateRoot.root, - stateTransitionProof.publicInput.root.root, - apply, - errors.propertyNotMatching("from state root") - ); - // Assert from state roots last occupied index - assertEqualsIf( - stateRoot.lastOccupiedIndex, - stateTransitionProof.publicInput.root.lastOccupiedIndex, + stateRoot, + stateTransitionProof.publicInput.root, apply, errors.propertyNotMatching("from state root") ); @@ -546,7 +538,6 @@ export class BlockProverProgrammable extends ZkProgrammable< // update root only if we didn't defer const newRoot = Provable.if( apply, - LinkedMerkleTreeGlobalState, stateTransitionProof.publicOutput.root, stateRoot ); @@ -631,8 +622,7 @@ export class BlockProverProgrammable extends ZkProgrammable< .assertTrue( "TransactionProof networkstate hash not matching beforeBlock hook result" ); - LinkedMerkleTreeGlobalState.assertEquals( - transactionProof.publicInput.stateRoot, + transactionProof.publicInput.stateRoot.assertEquals( transactionProof.publicOutput.stateRoot, "TransactionProofs can't change the state root" ); @@ -665,12 +655,7 @@ export class BlockProverProgrammable extends ZkProgrammable< // Witness root const isEmpty = state.pendingSTBatches.commitment.equals(0); isEmpty - .implies( - LinkedMerkleTreeGlobalState.equals( - state.stateRoot, - afterBlockRootWitness.witnessedRoot - ) - ) + .implies(state.stateRoot.equals(afterBlockRootWitness.witnessedRoot)) .assertTrue(); state.witnessedRoots.witnessRoot( @@ -764,13 +749,11 @@ export class BlockProverProgrammable extends ZkProgrammable< proof2.verify(); // Check state - LinkedMerkleTreeGlobalState.assertEquals( - publicInput.stateRoot, + publicInput.stateRoot.assertEquals( proof1.publicInput.stateRoot, errors.stateRootNotMatching("publicInput.from -> proof1.from") ); - LinkedMerkleTreeGlobalState.assertEquals( - proof1.publicOutput.stateRoot, + proof1.publicOutput.stateRoot.assertEquals( proof2.publicInput.stateRoot, errors.stateRootNotMatching("proof1.to -> proof2.from") ); diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProvable.ts b/packages/protocol/src/prover/statetransition/StateTransitionProvable.ts index 85c0564da..c80b471bd 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProvable.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProvable.ts @@ -1,9 +1,5 @@ import { Field, Proof, Struct } from "o1js"; -import { - WithZkProgrammable, - CompilableModule, - LinkedMerkleTreeGlobalState, -} from "@proto-kit/common"; +import { WithZkProgrammable, CompilableModule } from "@proto-kit/common"; import { MerkleWitnessBatch, @@ -14,14 +10,14 @@ import { AppliedStateTransitionBatchState } from "../../model/AppliedStateTransi export class StateTransitionProverPublicInput extends Struct({ batchesHash: Field, currentBatchStateHash: Field, - root: LinkedMerkleTreeGlobalState, + root: Field, witnessedRootsHash: Field, }) {} export class StateTransitionProverPublicOutput extends Struct({ batchesHash: Field, currentBatchStateHash: Field, - root: LinkedMerkleTreeGlobalState, + root: Field, witnessedRootsHash: Field, }) {} diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index 0f448d2f9..47932fad7 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -2,14 +2,12 @@ import { AreProofsEnabled, PlainZkProgram, provableMethod, - RollupMerkleTreeWitness, ZkProgrammable, CompilableModule, type ArtifactRecord, type CompileRegistry, TreeWrite, LinkedMerkleTreeCircuitOps, - LinkedMerkleTreeGlobalState, LinkedMerkleTreeWitness, } from "@proto-kit/common"; import { Field, Provable, SelfProof, ZkProgram } from "o1js"; @@ -47,7 +45,7 @@ const errors = { interface StateTransitionProverExecutionState { currentBatch: AppliedStateTransitionBatchState; batchList: AppliedBatchHashList; - finalizedRoot: LinkedMerkleTreeGlobalState; + finalizedRoot: Field; witnessedRoots: WitnessedRootHashList; } @@ -203,7 +201,6 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< // The root is based on if the previous batch will be applied or not const base = Provable.if( closingAndApply, - LinkedMerkleTreeGlobalState, updatedBatchState.root, state.finalizedRoot ); @@ -219,7 +216,6 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< state.batchList.pushIf(updatedBatch, closing); state.finalizedRoot = Provable.if( closingAndApply, - LinkedMerkleTreeGlobalState, updatedBatchState.root, state.finalizedRoot ); @@ -301,14 +297,14 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< private applyTransitionToRoot( transition: ProvableStateTransition, - treeState: LinkedMerkleTreeGlobalState, + root: Field, merkleWitness: LinkedMerkleTreeWitness, index: number - ): LinkedMerkleTreeGlobalState { + ): Field { const treeWrite = this.transitionToTreeWrite(transition, merkleWitness); return LinkedMerkleTreeCircuitOps.applyTreeWrite( - treeState, + root, merkleWitness, treeWrite, index @@ -334,10 +330,8 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< // Assert that either the currentAppliedBatch is somewhere intermediary // or the root is the current "finalized" root - LinkedMerkleTreeGlobalState.equals( - currentAppliedBatch.root, - publicInput.root - ) + currentAppliedBatch.root + .equals(publicInput.root) .or(publicInput.currentBatchStateHash.equals(0).not()) .assertTrue(); @@ -397,14 +391,12 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< ); // Check root - LinkedMerkleTreeGlobalState.assertEquals( - publicInput.root, + publicInput.root.assertEquals( proof1.publicInput.root, errors.propertyNotMatching("root", "publicInput.from -> proof1.from") ); - LinkedMerkleTreeGlobalState.assertEquals( - proof1.publicOutput.root, + proof1.publicOutput.root.assertEquals( proof2.publicInput.root, errors.propertyNotMatching("root", "proof1.to -> proof2.from") ); diff --git a/packages/protocol/src/settlement/contracts/BridgeContract.ts b/packages/protocol/src/settlement/contracts/BridgeContract.ts index d426d9470..eb2d7a94b 100644 --- a/packages/protocol/src/settlement/contracts/BridgeContract.ts +++ b/packages/protocol/src/settlement/contracts/BridgeContract.ts @@ -15,13 +15,7 @@ import { TokenId, VerificationKey, } from "o1js"; -import { - LinkedLeafStruct, - LinkedMerkleTreeGlobalState, - noop, - range, - TypedClass, -} from "@proto-kit/common"; +import { noop, range, TypedClass } from "@proto-kit/common"; import { OUTGOING_MESSAGE_BATCH_SIZE, @@ -49,7 +43,7 @@ export type BridgeContractType = { settlementContractAddress: PublicKey ) => Promise; - updateStateRoot: (root: LinkedMerkleTreeGlobalState) => Promise; + updateStateRoot: (root: Field) => Promise; }; // Equal to WithdrawalKey @@ -118,10 +112,10 @@ export abstract class BridgeContractBase extends TokenContractV2 { noop(); } - public async updateStateRootBase(root: LinkedMerkleTreeGlobalState) { + public async updateStateRootBase(root: Field) { // It's fine for us to only store the actual root since we only have to // witness values, not update/insert - this.stateRoot.set(root.root); + this.stateRoot.set(root); const settlementContractAddress = this.settlementContractAddress.getAndRequireEquals(); @@ -232,7 +226,7 @@ export class BridgeContract @state(Field) public outgoingMessageCursor = State(); @method - public async updateStateRoot(root: LinkedMerkleTreeGlobalState) { + public async updateStateRoot(root: Field) { return await this.updateStateRootBase(root); } diff --git a/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts b/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts index 865a8ae0d..9962c3e18 100644 --- a/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts +++ b/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts @@ -4,7 +4,7 @@ import { TypedClass, mapSequential, ChildVerificationKeyService, - LinkedMerkleTreeGlobalState, + LinkedMerkleTree, } from "@proto-kit/common"; import { AccountUpdate, @@ -72,7 +72,7 @@ export interface SettlementContractType { bridgeContract: PublicKey, contractKey: PrivateKey ) => Promise; - assertStateRoot: (root: LinkedMerkleTreeGlobalState) => AccountUpdate; + assertStateRoot: (root: Field) => AccountUpdate; settle: ( blockProof: DynamicBlockProof, signature: Signature, @@ -128,7 +128,7 @@ export abstract class SettlementSmartContractBase extends TokenContractV2 { abstract sequencerKey: State; abstract lastSettlementL1BlockHeight: State; - abstract stateRoot: State; + abstract stateRoot: State; abstract networkStateHash: State; abstract blockHashRoot: State; abstract dispatchContractAddressX: State; @@ -138,7 +138,7 @@ export abstract class SettlementSmartContractBase extends TokenContractV2 { // Not @state // abstract offchainStateCommitmentsHash: State; - public assertStateRoot(root: LinkedMerkleTreeGlobalState): AccountUpdate { + public assertStateRoot(root: Field): AccountUpdate { this.stateRoot.requireEquals(root); return this.self; } @@ -251,19 +251,13 @@ export abstract class SettlementSmartContractBase extends TokenContractV2 { contractKey: PrivateKey ) { this.sequencerKey.getAndRequireEquals().assertEquals(Field(0)); - LinkedMerkleTreeGlobalState.assertEquals( - this.stateRoot.getAndRequireEquals(), - { root: Field(0), lastOccupiedIndex: Field(0) } - ); + this.stateRoot.getAndRequireEquals().assertEquals(Field(0)); this.blockHashRoot.getAndRequireEquals().assertEquals(Field(0)); this.networkStateHash.getAndRequireEquals().assertEquals(Field(0)); this.dispatchContractAddressX.getAndRequireEquals().assertEquals(Field(0)); this.sequencerKey.set(sequencer.x); - this.stateRoot.set({ - root: Field(RollupMerkleTree.EMPTY_ROOT), - lastOccupiedIndex: Field(0), - }); + this.stateRoot.set(LinkedMerkleTree.EMPTY_ROOT); this.blockHashRoot.set(Field(BlockHashMerkleTree.EMPTY_ROOT)); this.networkStateHash.set(NetworkState.empty().hash()); this.dispatchContractAddressX.set(dispatchContract.x); @@ -395,8 +389,7 @@ export abstract class SettlementSmartContractBase extends TokenContractV2 { }); // Apply blockProof - LinkedMerkleTreeGlobalState.assertEquals( - stateRoot, + stateRoot.assertEquals( blockProof.publicInput.stateRoot, "Input state root not matching" ); @@ -449,8 +442,7 @@ export class SettlementSmartContract @state(Field) public sequencerKey = State(); @state(UInt32) public lastSettlementL1BlockHeight = State(); - @state(LinkedMerkleTreeGlobalState) public stateRoot = - State(); + @state(Field) public stateRoot = State(); @state(Field) public networkStateHash = State(); @state(Field) public blockHashRoot = State(); diff --git a/packages/protocol/src/settlement/modularity/ProvableSettlementHook.ts b/packages/protocol/src/settlement/modularity/ProvableSettlementHook.ts index c7a5e2409..ecb117129 100644 --- a/packages/protocol/src/settlement/modularity/ProvableSettlementHook.ts +++ b/packages/protocol/src/settlement/modularity/ProvableSettlementHook.ts @@ -1,5 +1,5 @@ import { Field, PublicKey, UInt32 } from "o1js"; -import { InferProofBase, LinkedMerkleTreeGlobalState } from "@proto-kit/common"; +import { InferProofBase } from "@proto-kit/common"; import { ProtocolModule } from "../../protocol/ProtocolModule"; import { NetworkState } from "../../model/network/NetworkState"; @@ -12,7 +12,7 @@ export type SettlementStateRecord = { sequencerKey: PublicKey; lastSettlementL1BlockHeight: UInt32; - stateRoot: LinkedMerkleTreeGlobalState; + stateRoot: Field; networkStateHash: Field; blockHashRoot: Field; }; From 10219f9f6f591fcff86ffe93d513be697e993de3 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 10 Apr 2025 17:24:01 +0200 Subject: [PATCH 115/128] Built sequencer-side lmt stores --- .../src/state/async/AsyncLinkedLeafStore.ts | 19 ++ .../state/async/AsyncLinkedMerkleTreeStore.ts | 25 -- .../lmt/AsyncLinkedMerkleTreeDatabase.ts | 7 + .../src/state/lmt/CachedLinkedLeafStore.ts | 184 +++++++++++ .../merkle/CachedLinkedMerkleTreeStore.ts | 300 ------------------ .../src/state/merkle/CachedMerkleTreeStore.ts | 5 + .../state/merkle/SyncCachedLinkedLeafStore.ts | 65 ++++ .../merkle/SyncCachedLinkedMerkleTreeStore.ts | 79 ----- ...ore.ts => InMemoryAsyncLinkedLeafStore.ts} | 46 +-- .../src/storage/inmemory/InMemoryDatabase.ts | 13 +- 10 files changed, 297 insertions(+), 446 deletions(-) create mode 100644 packages/sequencer/src/state/async/AsyncLinkedLeafStore.ts delete mode 100644 packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts create mode 100644 packages/sequencer/src/state/lmt/AsyncLinkedMerkleTreeDatabase.ts create mode 100644 packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts delete mode 100644 packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts create mode 100644 packages/sequencer/src/state/merkle/SyncCachedLinkedLeafStore.ts delete mode 100644 packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts rename packages/sequencer/src/storage/inmemory/{InMemoryAsyncLinkedMerkleTreeStore.ts => InMemoryAsyncLinkedLeafStore.ts} (52%) diff --git a/packages/sequencer/src/state/async/AsyncLinkedLeafStore.ts b/packages/sequencer/src/state/async/AsyncLinkedLeafStore.ts new file mode 100644 index 000000000..d51ac129f --- /dev/null +++ b/packages/sequencer/src/state/async/AsyncLinkedLeafStore.ts @@ -0,0 +1,19 @@ +import { StoredLeaf } from "@proto-kit/common"; + +import { AsyncMerkleTreeStore } from "./AsyncMerkleTreeStore"; + +export interface AsyncLinkedLeafStore { + treeStore: AsyncMerkleTreeStore; + + openTransaction: () => Promise; + + commit: () => Promise; + + writeLeaves: (leaves: StoredLeaf[]) => void; + + getLeavesAsync: (paths: bigint[]) => Promise<(StoredLeaf | undefined)[]>; + + getMaximumIndexAsync: () => Promise; + + getLeafLessOrEqualAsync: (path: bigint) => Promise; +} diff --git a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts deleted file mode 100644 index e2e6eb6f4..000000000 --- a/packages/sequencer/src/state/async/AsyncLinkedMerkleTreeStore.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { LinkedLeaf } from "@proto-kit/common"; - -import { MerkleTreeNode, MerkleTreeNodeQuery } from "./AsyncMerkleTreeStore"; - -export type StoredLeaf = { leaf: LinkedLeaf; index: bigint }; - -export interface AsyncLinkedMerkleTreeStore { - openTransaction: () => Promise; - - commit: () => Promise; - - writeNodes: (nodes: MerkleTreeNode[]) => void; - - writeLeaves: (leaves: StoredLeaf[]) => void; - - getNodesAsync: ( - nodes: MerkleTreeNodeQuery[] - ) => Promise<(bigint | undefined)[]>; - - getLeavesAsync: (paths: bigint[]) => Promise<(StoredLeaf | undefined)[]>; - - getMaximumIndexAsync: () => Promise; - - getLeafLessOrEqualAsync: (path: bigint) => Promise; -} diff --git a/packages/sequencer/src/state/lmt/AsyncLinkedMerkleTreeDatabase.ts b/packages/sequencer/src/state/lmt/AsyncLinkedMerkleTreeDatabase.ts new file mode 100644 index 000000000..a8e2bd904 --- /dev/null +++ b/packages/sequencer/src/state/lmt/AsyncLinkedMerkleTreeDatabase.ts @@ -0,0 +1,7 @@ +import { AsyncMerkleTreeStore } from "../async/AsyncMerkleTreeStore"; +import { AsyncLinkedLeafStore } from "../async/AsyncLinkedLeafStore"; + +export interface AsyncLinkedMerkleTreeDatabase { + treeStore: AsyncMerkleTreeStore; + leafStore: AsyncLinkedLeafStore; +} diff --git a/packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts b/packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts new file mode 100644 index 000000000..e11586b15 --- /dev/null +++ b/packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts @@ -0,0 +1,184 @@ +import { + InMemoryLinkedLeafStore, + LinkedLeaf, + mapSequential, + LinkedLeafStore, +} from "@proto-kit/common"; + +import { AsyncLinkedLeafStore } from "../async/AsyncLinkedLeafStore"; + +import { CachedMerkleTreeStore } from "../merkle/CachedMerkleTreeStore"; + +export class CachedLinkedLeafStore implements LinkedLeafStore { + private writeCache: { + [key: string]: { leaf: LinkedLeaf; index: bigint }; + } = {}; + + private readonly leafStore = new InMemoryLinkedLeafStore(); + + private readonly treeCache: CachedMerkleTreeStore; + + private constructor(private readonly parent: AsyncLinkedLeafStore) { + this.treeCache = new CachedMerkleTreeStore(parent.treeStore); + } + + public get treeStore() { + return this.treeCache; + } + + public static async new( + parent: AsyncLinkedLeafStore + ): Promise { + const cachedInstance = new CachedLinkedLeafStore(parent); + await cachedInstance.preloadMaximumIndex(); + return cachedInstance; + } + + public getLeaf(path: bigint) { + return this.leafStore.getLeaf(path); + } + + // 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<{ leaf: LinkedLeaf; index: bigint } | undefined>( + 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; + } + + public setLeaf(index: bigint, leaf: LinkedLeaf) { + this.writeCache[leaf.path.toString()] = { leaf: leaf, index: index }; + this.leafStore.setLeaf(index, leaf); + } + + // This is just used in the mergeIntoParent + public writeLeaves(leaves: { leaf: LinkedLeaf; index: bigint }[]) { + leaves.forEach(({ leaf, index }) => { + this.setLeaf(index, leaf); + }); + } + + // This gets the leaves from the cache. + // Only used in mergeIntoParent + public getWrittenLeaves(): { leaf: LinkedLeaf; index: bigint }[] { + return Object.values(this.writeCache); + } + + // This resets the cache (not the in memory tree). + public resetWrittenLeaves() { + this.writeCache = {}; + } + + protected async preloadMaximumIndex() { + if (this.leafStore.getMaximumIndex() === undefined) { + this.leafStore.maximumIndex = await this.parent.getMaximumIndexAsync(); + } + } + + // 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 preloadKeyInternal( + path: bigint + ): Promise<{ requiredTreePaths: bigint[] }> { + const leaf = (await this.getLeavesAsync([path]))[0]; + + if (leaf !== undefined) { + // Update case, this leaf is the only one we need + this.leafStore.setLeaf(leaf.index, leaf.leaf); + + return { requiredTreePaths: [leaf.index] }; + } else { + // Insert case, this leaf doesn't yet exist - we need to fetch the previous one + + // Calling getLeafLessOrEqual assures that it is actually the leaf we want + // (i.e. pointing over our path) + // TODO Rename getLeafLessOrEqual + const previousLeaf = + this.leafStore.getLeafLessOrEqual(path) ?? + (await this.parent.getLeafLessOrEqualAsync(path)); + + if (previousLeaf === undefined) { + throw Error("Previous Leaf should never be empty"); + } + + this.leafStore.setLeaf(previousLeaf.index, previousLeaf.leaf); + + // Since we set a leaf right before this call, getMaximumIndex will always return a value + // Also note that this maximumIndex is already the "to be occupied" index of the new + // inserted leaf, not the "last occupied one" as normally the case + const maximumIndex = this.leafStore.getMaximumIndex(); + + if (maximumIndex === undefined) { + throw Error("Maximum index should be defined in parent."); + } + + return { requiredTreePaths: [previousLeaf.index, maximumIndex] }; + } + } + + public async preloadKey(path: bigint) { + const { requiredTreePaths } = await this.preloadKeyInternal(path); + await this.treeCache.preloadKeys(requiredTreePaths); + } + + public async preloadKeys(paths: bigint[]): Promise { + const results = await mapSequential(paths, (x) => + this.preloadKeyInternal(x) + ); + const treePaths = results.flatMap( + ({ requiredTreePaths }) => requiredTreePaths + ); + await this.treeCache.preloadKeys(treePaths); + } + + // 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 leaves = this.getWrittenLeaves(); + this.parent.writeLeaves(Object.values(leaves)); + + await this.parent.commit(); + + await this.treeCache.mergeIntoParent(); + + this.resetWrittenLeaves(); + } + + public getLeafLessOrEqual(path: bigint) { + return this.leafStore.getLeafLessOrEqual(path); + } + + public getMaximumIndex() { + return this.leafStore.getMaximumIndex(); + } +} diff --git a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts deleted file mode 100644 index 5ddae4fd8..000000000 --- a/packages/sequencer/src/state/merkle/CachedLinkedMerkleTreeStore.ts +++ /dev/null @@ -1,300 +0,0 @@ -import { - log, - InMemoryLinkedLeafStore, - LinkedLeaf, - InMemoryMerkleTreeStorage, - PreloadingLinkedMerkleTreeStore, - mapSequential, -} from "@proto-kit/common"; - -import { - MerkleTreeNode, - MerkleTreeNodeQuery, -} from "../async/AsyncMerkleTreeStore"; -import { AsyncLinkedMerkleTreeStore } from "../async/AsyncLinkedMerkleTreeStore"; - -export class CachedLinkedMerkleTreeStore - implements PreloadingLinkedMerkleTreeStore -{ - private writeCache: { - nodes: { - [key: number]: { - [key: string]: bigint; - }; - }; - leaves: { - [key: string]: { leaf: LinkedLeaf; index: bigint }; - }; - } = { nodes: {}, leaves: {} }; - - private readonly leafStore = new InMemoryLinkedLeafStore(); - - private readonly nodeStore = new InMemoryMerkleTreeStorage(); - - private constructor(private readonly parent: AsyncLinkedMerkleTreeStore) {} - - public static async new( - parent: AsyncLinkedMerkleTreeStore - ): Promise { - const cachedInstance = new CachedLinkedMerkleTreeStore(parent); - 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 this.nodeStore.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) { - this.nodeStore.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 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<{ leaf: LinkedLeaf; index: bigint } | undefined>( - 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: { leaf: LinkedLeaf; index: bigint }[]) { - leaves.forEach(({ leaf, index }) => { - this.setLeaf(index, leaf); - }); - } - - 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. - // 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(): { leaf: LinkedLeaf; index: bigint }[] { - return Object.values(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) { - // This is hardcoded, but should be changed. - const HEIGHT = 40n; - const leafCount = 2n ** (HEIGHT - 1n); - - 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; - } - - 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 results = await this.parent.getNodesAsync(nodesToRetrieve); - nodesToRetrieve.forEach(({ key, level }, index) => { - const value = results[index]; - if (value !== undefined) { - this.setNode(key, level, value); - } - }); - } - - 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 = - 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 = - this.leafStore.getLeafLessOrEqual(path) ?? - (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 = - this.leafStore.getMaximumIndex() ?? - (await this.parent.getMaximumIndexAsync()); - if (maximumIndex === undefined) { - throw Error("Maximum index should be defined in parent."); - } - await this.preloadNodes([maximumIndex + 1n]); - } - } - - public async preloadKeys(paths: bigint[]): Promise { - await mapSequential(paths, (x) => this.preloadKey(x)); - } - - // 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.parent.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(); - } - - public getLeafLessOrEqual(path: bigint) { - return this.leafStore.getLeafLessOrEqual(path); - } - - public getMaximumIndex() { - return this.leafStore.getMaximumIndex(); - } -} diff --git a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts index 677668abd..fb819b27d 100644 --- a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts @@ -154,6 +154,11 @@ export class CachedMerkleTreeStore const toFetch: MerkleTreeNodeQuery[] = []; + // TODO Replace this logic with the following: + // Collect toFetch as { nodeQuery, arrayIndex } + // After fetching, set the data at the specific arrayIndizes + // Same goes for CachedLinkedLeafStore + nodes.forEach((node, index) => { const localResult = this.getNode(node.key, node.level); if (localResult !== undefined) { diff --git a/packages/sequencer/src/state/merkle/SyncCachedLinkedLeafStore.ts b/packages/sequencer/src/state/merkle/SyncCachedLinkedLeafStore.ts new file mode 100644 index 000000000..cb8d17f5f --- /dev/null +++ b/packages/sequencer/src/state/merkle/SyncCachedLinkedLeafStore.ts @@ -0,0 +1,65 @@ +import { + LinkedLeaf, + InMemoryLinkedLeafStore, + LinkedLeafStore, + StoredLeaf, + BigIntMath, +} from "@proto-kit/common"; + +import { CachedLinkedLeafStore } from "../lmt/CachedLinkedLeafStore"; +import { SyncCachedMerkleTreeStore } from "./SyncCachedMerkleTreeStore"; + +// 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 SyncCachedLinkedLeafStore implements LinkedLeafStore { + private readonly leafStore = new InMemoryLinkedLeafStore(); + + private readonly treeCache: SyncCachedMerkleTreeStore; + + public constructor(private readonly parent: CachedLinkedLeafStore) { + this.treeCache = new SyncCachedMerkleTreeStore(parent.treeStore); + } + + public get treeStore() { + return this.treeCache; + } + + public getLeaf(path: bigint): StoredLeaf | undefined { + return this.leafStore.getLeaf(path) ?? this.parent.getLeaf(path); + } + + public setLeaf(index: bigint, value: LinkedLeaf) { + this.leafStore.setLeaf(index, value); + } + + public getMaximumIndex(): bigint | undefined { + return ( + this.leafStore.getMaximumIndex() ?? this.parent.getMaximumIndex() ?? 0n + ); + } + + public getLeafLessOrEqual(path: bigint): StoredLeaf | undefined { + return ( + this.leafStore.getLeafLessOrEqual(path) ?? + this.parent.getLeafLessOrEqual(path) + ); + } + + public async preloadKeys(path: bigint[]) { + await this.parent.preloadKeys(path); + } + + public mergeIntoParent() { + if (Object.keys(this.leafStore.leaves).length === 0) { + return; + } + + Object.values(this.leafStore.leaves).forEach(({ leaf, index }) => + this.parent.setLeaf(index, leaf) + ); + + this.leafStore.leaves = {}; + + this.treeCache.mergeIntoParent(); + } +} diff --git a/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts deleted file mode 100644 index f38d9990b..000000000 --- a/packages/sequencer/src/state/merkle/SyncCachedLinkedMerkleTreeStore.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { - LinkedLeaf, - LinkedMerkleTree, - 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 PreloadingLinkedMerkleTreeStore -{ - private readonly leafStore = new InMemoryLinkedLeafStore(); - - private readonly nodeStore = new InMemoryMerkleTreeStorage(); - - public constructor( - private readonly parent: PreloadingLinkedMerkleTreeStore - ) {} - - public getNode(key: bigint, level: number): bigint | undefined { - return ( - this.nodeStore.getNode(key, level) ?? this.parent.getNode(key, level) - ); - } - - public setNode(key: bigint, level: number, value: bigint) { - this.nodeStore.setNode(key, level, value); - } - - public getLeaf(path: bigint): StoredLeaf | undefined { - return this.leafStore.getLeaf(path) ?? this.parent.getLeaf(path); - } - - public setLeaf(index: bigint, value: LinkedLeaf) { - this.leafStore.setLeaf(index, value); - } - - // 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.leafStore.getMaximumIndex() ?? -1) > - (this.parent.getMaximumIndex() ?? -1) - ? this.leafStore.getMaximumIndex() - : this.parent.getMaximumIndex(); - } - - public getLeafLessOrEqual(path: bigint): StoredLeaf | undefined { - return ( - this.leafStore.getLeafLessOrEqual(path) ?? - this.parent.getLeafLessOrEqual(path) - ); - } - - public async preloadKeys(path: bigint[]) { - await this.parent.preloadKeys(path); - } - - public mergeIntoParent() { - if (Object.keys(this.leafStore.leaves).length === 0) { - return; - } - - Object.values(this.leafStore.leaves).forEach(({ leaf, index }) => - this.parent.setLeaf(index, leaf) - ); - Array.from({ length: LinkedMerkleTree.HEIGHT }).forEach((ignored, level) => - Object.entries(this.nodeStore.nodes[level]).forEach((entry) => { - this.parent.setNode(BigInt(entry[0]), level, entry[1]); - }) - ); - - this.leafStore.leaves = {}; - this.nodeStore.nodes = {}; - } -} diff --git a/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts b/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedLeafStore.ts similarity index 52% rename from packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts rename to packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedLeafStore.ts index 877efedbd..44b72163b 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedMerkleTreeStore.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedLeafStore.ts @@ -1,23 +1,17 @@ -import { - InMemoryLinkedLeafStore, - InMemoryMerkleTreeStorage, - LinkedLeaf, - LinkedMerkleTreeStore, - noop, -} from "@proto-kit/common"; +import { InMemoryLinkedLeafStore, LinkedLeaf, noop } from "@proto-kit/common"; -import { AsyncLinkedMerkleTreeStore } from "../../state/async/AsyncLinkedMerkleTreeStore"; -import { - MerkleTreeNode, - MerkleTreeNodeQuery, -} from "../../state/async/AsyncMerkleTreeStore"; +import { AsyncLinkedLeafStore } from "../../state/async/AsyncLinkedLeafStore"; -export class InMemoryAsyncLinkedMerkleTreeStore - implements AsyncLinkedMerkleTreeStore, LinkedMerkleTreeStore -{ +import { InMemoryAsyncMerkleTreeStore } from "./InMemoryAsyncMerkleTreeStore"; + +export class InMemoryAsyncLinkedLeafStore implements AsyncLinkedLeafStore { private readonly leafStore = new InMemoryLinkedLeafStore(); - private readonly nodeStore = new InMemoryMerkleTreeStorage(); + private readonly nodeStore = new InMemoryAsyncMerkleTreeStore(); + + public get treeStore() { + return this.nodeStore; + } public async openTransaction(): Promise { noop(); @@ -27,12 +21,6 @@ export class InMemoryAsyncLinkedMerkleTreeStore 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 }) => { @@ -40,12 +28,6 @@ export class InMemoryAsyncLinkedMerkleTreeStore }); } - public async getNodesAsync( - nodes: MerkleTreeNodeQuery[] - ): Promise<(bigint | undefined)[]> { - return nodes.map(({ key, level }) => this.nodeStore.getNode(key, level)); - } - public async getLeavesAsync(paths: bigint[]) { return paths.map((path) => { const leaf = this.leafStore.getLeaf(path); @@ -79,12 +61,4 @@ export class InMemoryAsyncLinkedMerkleTreeStore 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); - } } diff --git a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts index 861431924..5406d539f 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts @@ -10,19 +10,20 @@ import { Database } from "../Database"; import { closeable } from "../../sequencer/builder/Closeable"; import { InMemoryBlockStorage } from "./InMemoryBlockStorage"; -import { InMemoryAsyncLinkedMerkleTreeStore } from "./InMemoryAsyncLinkedMerkleTreeStore"; +import { InMemoryAsyncLinkedLeafStore } from "./InMemoryAsyncLinkedLeafStore"; import { InMemoryBatchStorage } from "./InMemoryBatchStorage"; import { InMemoryMessageStorage } from "./InMemoryMessageStorage"; import { InMemorySettlementStorage } from "./InMemorySettlementStorage"; import { InMemoryTransactionStorage } from "./InMemoryTransactionStorage"; +import { InMemoryAsyncMerkleTreeStore } from "./InMemoryAsyncMerkleTreeStore"; @sequencerModule() @closeable() export class InMemoryDatabase extends SequencerModule implements Database { public dependencies(): StorageDependencyMinimumDependencies { return { - asyncMerkleStore: { - useClass: InMemoryAsyncLinkedMerkleTreeStore, + asyncLinkedLeafStore: { + useClass: InMemoryAsyncLinkedLeafStore, }, asyncStateService: { useFactory: () => new CachedStateService(undefined), @@ -39,11 +40,11 @@ export class InMemoryDatabase extends SequencerModule implements Database { unprovenStateService: { useFactory: () => new CachedStateService(undefined), }, - unprovenMerkleStore: { - useClass: InMemoryAsyncLinkedMerkleTreeStore, + unprovenLinkedLeafStore: { + useClass: InMemoryAsyncLinkedLeafStore, }, blockTreeStore: { - useClass: InMemoryAsyncLinkedMerkleTreeStore, + useClass: InMemoryAsyncMerkleTreeStore, }, messageStorage: { useClass: InMemoryMessageStorage, From 1faf5de3dfc862fb0b0bf73fe7acf34036dae6bf Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 10 Apr 2025 17:24:27 +0200 Subject: [PATCH 116/128] Integrated LMT into block prod pipeline --- .../production/BatchProducerModule.ts | 12 +++-- .../sequencing/BlockProducerModule.ts | 7 +-- .../sequencing/BlockResultService.ts | 18 ++++---- .../production/tasks/StateTransitionTask.ts | 3 +- .../StateTransitionParametersSerializer.ts | 11 +++-- .../production/tracing/BatchTracingService.ts | 9 ++-- .../tracing/StateTransitionTracingService.ts | 46 +++++++++++++------ .../src/settlement/BridgingModule.ts | 16 +++---- .../src/storage/StorageDependencyFactory.ts | 10 ++-- packages/sequencer/src/storage/model/Block.ts | 8 ++-- 10 files changed, 83 insertions(+), 57 deletions(-) diff --git a/packages/sequencer/src/protocol/production/BatchProducerModule.ts b/packages/sequencer/src/protocol/production/BatchProducerModule.ts index fcfffa5b5..f9915cac0 100644 --- a/packages/sequencer/src/protocol/production/BatchProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BatchProducerModule.ts @@ -21,12 +21,14 @@ import type { Database } from "../../storage/Database"; import { BlockProofSerializer } from "./tasks/serializers/BlockProofSerializer"; import { BatchTracingService } from "./tracing/BatchTracingService"; import { BatchFlow } from "./flow/BatchFlow"; +import { AsyncLinkedLeafStore } from "../../state/async/AsyncLinkedLeafStore"; +import { CachedLinkedLeafStore } from "../../state/lmt/CachedLinkedLeafStore"; export type StateRecord = Record; interface BatchMetadata { batch: SettleableBatch; - changes: CachedMerkleTreeStore; + changes: CachedLinkedLeafStore; } const errors = { @@ -47,8 +49,8 @@ export class BatchProducerModule extends SequencerModule { private productionInProgress = false; public constructor( - @inject("AsyncMerkleStore") - private readonly merkleStore: AsyncMerkleTreeStore, + @inject("AsyncLinkedLeafStore") + private readonly merkleStore: AsyncLinkedLeafStore, @inject("BatchStorage") private readonly batchStorage: BatchStorage, @inject("Database") private readonly database: Database, @@ -178,7 +180,7 @@ export class BatchProducerModule extends SequencerModule { batchId: number ): Promise<{ proof: Proof; - changes: CachedMerkleTreeStore; + changes: CachedLinkedLeafStore; fromNetworkState: NetworkState; toNetworkState: NetworkState; }> { @@ -186,7 +188,7 @@ export class BatchProducerModule extends SequencerModule { throw errors.blockWithoutTxs(); } - const merkleTreeStore = new CachedMerkleTreeStore(this.merkleStore); + const merkleTreeStore = await CachedLinkedLeafStore.new(this.merkleStore); const trace = await this.batchTraceService.traceBatch( blocks.map((block) => block), diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts index a85fbcf8e..3e82dffb2 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts @@ -29,6 +29,7 @@ import { trace } from "../../../logging/trace"; import { BlockProductionService } from "./BlockProductionService"; import { BlockResultService } from "./BlockResultService"; +import { AsyncLinkedLeafStore } from "../../../state/async/AsyncLinkedLeafStore"; export interface BlockConfig { allowEmptyBlock?: boolean; @@ -45,8 +46,8 @@ export class BlockProducerModule extends SequencerModule { private readonly messageService: IncomingMessagesService | undefined, @inject("UnprovenStateService") private readonly unprovenStateService: AsyncStateService, - @inject("UnprovenMerkleStore") - private readonly unprovenMerkleStore: AsyncMerkleTreeStore, + @inject("UnprovenLinkedLeafStore") + private readonly unprovenLinkedLeafStore: AsyncLinkedLeafStore, @inject("BlockQueue") private readonly blockQueue: BlockQueue, @inject("BlockTreeStore") @@ -118,7 +119,7 @@ export class BlockProducerModule extends SequencerModule { const { result, blockHashTreeStore, treeStore, stateService } = await this.resultService.generateMetadataForNextBlock( block, - this.unprovenMerkleStore, + this.unprovenLinkedLeafStore, this.blockTreeStore, this.unprovenStateService ); diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockResultService.ts b/packages/sequencer/src/protocol/production/sequencing/BlockResultService.ts index 7e5803e7e..7030700da 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockResultService.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockResultService.ts @@ -1,5 +1,5 @@ import { Bool, Field, Poseidon } from "o1js"; -import { RollupMerkleTree } from "@proto-kit/common"; +import { LinkedMerkleTree } from "@proto-kit/common"; import { AfterBlockHookArguments, BlockHashMerkleTree, @@ -27,6 +27,8 @@ import { AsyncStateService } from "../../../state/async/AsyncStateService"; import type { StateRecord } from "../BatchProducerModule"; import { trace } from "../../../logging/trace"; import { Tracer } from "../../../logging/Tracer"; +import { AsyncLinkedLeafStore } from "../../../state/async/AsyncLinkedLeafStore"; +import { CachedLinkedLeafStore } from "../../../state/lmt/CachedLinkedLeafStore"; import { executeWithExecutionContext } from "./TransactionExecutionService"; @@ -153,9 +155,9 @@ export class BlockResultService { } public async applyStateDiff( - store: CachedMerkleTreeStore, + store: CachedLinkedLeafStore, stateDiff: StateRecord - ): Promise { + ): Promise { await store.preloadKeys(Object.keys(stateDiff).map(BigInt)); // In case the diff is empty, we preload key 0 in order to @@ -164,11 +166,11 @@ export class BlockResultService { await store.preloadKey(0n); } - const tree = new RollupMerkleTree(store); + const tree = new LinkedMerkleTree(store.treeStore, store); Object.entries(stateDiff).forEach(([key, state]) => { const treeValue = state !== undefined ? Poseidon.hash(state) : Field(0); - tree.setLeaf(BigInt(key), treeValue); + tree.setLeaf(BigInt(key), treeValue.toBigInt()); }); return tree; @@ -179,12 +181,12 @@ export class BlockResultService { })) public async generateMetadataForNextBlock( block: Block, - merkleTreeStore: AsyncMerkleTreeStore, + merkleTreeStore: AsyncLinkedLeafStore, blockHashTreeStore: AsyncMerkleTreeStore, stateService: AsyncStateService ): Promise<{ result: BlockResult; - treeStore: CachedMerkleTreeStore; + treeStore: CachedLinkedLeafStore; blockHashTreeStore: CachedMerkleTreeStore; stateService: CachedStateService; }> { @@ -193,7 +195,7 @@ export class BlockResultService { block.beforeBlockStateTransitions ); - const inMemoryStore = new CachedMerkleTreeStore(merkleTreeStore); + const inMemoryStore = await CachedLinkedLeafStore.new(merkleTreeStore); const tree = await this.applyStateDiff(inMemoryStore, combinedDiff); diff --git a/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts b/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts index 03303d3fa..2569096f0 100644 --- a/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts @@ -16,6 +16,7 @@ import { ProvableMethodExecutionContext, CompileRegistry, RollupMerkleTreeWitness, + LinkedMerkleTreeWitness, } from "@proto-kit/common"; import { Task, TaskSerializer } from "../../../worker/flow/Task"; @@ -28,7 +29,7 @@ export interface StateTransitionProofParameters { publicInput: StateTransitionProverPublicInput; batch: StateTransitionProvableBatch; batchState: AppliedStateTransitionBatchState; - merkleWitnesses: RollupMerkleTreeWitness[]; + merkleWitnesses: LinkedMerkleTreeWitness[]; } @injectable() diff --git a/packages/sequencer/src/protocol/production/tasks/serializers/StateTransitionParametersSerializer.ts b/packages/sequencer/src/protocol/production/tasks/serializers/StateTransitionParametersSerializer.ts index dd7966730..1f6c84f46 100644 --- a/packages/sequencer/src/protocol/production/tasks/serializers/StateTransitionParametersSerializer.ts +++ b/packages/sequencer/src/protocol/production/tasks/serializers/StateTransitionParametersSerializer.ts @@ -3,7 +3,10 @@ import { StateTransitionProvableBatch, StateTransitionProverPublicInput, } from "@proto-kit/protocol"; -import { RollupMerkleTreeWitness } from "@proto-kit/common"; +import { + LinkedMerkleTreeWitness, + RollupMerkleTreeWitness, +} from "@proto-kit/common"; import { TaskSerializer } from "../../../../worker/flow/Task"; import type { StateTransitionProofParameters } from "../StateTransitionTask"; @@ -11,7 +14,7 @@ import type { StateTransitionProofParameters } from "../StateTransitionTask"; interface StateTransitionParametersJSON { publicInput: ReturnType; batch: ReturnType; - merkleWitnesses: ReturnType[]; + merkleWitnesses: ReturnType[]; batchState: ReturnType; } @@ -27,7 +30,7 @@ export class StateTransitionParametersSerializer batch: StateTransitionProvableBatch.toJSON(parameters.batch), merkleWitnesses: parameters.merkleWitnesses.map((witness) => - RollupMerkleTreeWitness.toJSON(witness) + LinkedMerkleTreeWitness.toJSON(witness) ), batchState: AppliedStateTransitionBatchState.toJSON( @@ -49,7 +52,7 @@ export class StateTransitionParametersSerializer merkleWitnesses: parsed.merkleWitnesses.map( (witness) => - new RollupMerkleTreeWitness(RollupMerkleTreeWitness.fromJSON(witness)) + new LinkedMerkleTreeWitness(LinkedMerkleTreeWitness.fromJSON(witness)) ), batchState: new AppliedStateTransitionBatchState( diff --git a/packages/sequencer/src/protocol/production/tracing/BatchTracingService.ts b/packages/sequencer/src/protocol/production/tracing/BatchTracingService.ts index bf4f3877b..e099ead0b 100644 --- a/packages/sequencer/src/protocol/production/tracing/BatchTracingService.ts +++ b/packages/sequencer/src/protocol/production/tracing/BatchTracingService.ts @@ -5,14 +5,13 @@ import { TransactionHashList, WitnessedRootHashList, } from "@proto-kit/protocol"; -import { Field } from "o1js"; import { inject, injectable } from "tsyringe"; -import { CachedMerkleTreeStore } from "../../../state/merkle/CachedMerkleTreeStore"; import { StateTransitionProofParameters } from "../tasks/StateTransitionTask"; import { BlockWithResult } from "../../../storage/model/Block"; import { trace } from "../../../logging/trace"; import { Tracer } from "../../../logging/Tracer"; +import { CachedLinkedLeafStore } from "../../../state/lmt/CachedLinkedLeafStore"; import { BlockTrace, @@ -41,7 +40,7 @@ export class BatchTracingService { return { pendingSTBatches: new AppliedBatchHashList(), witnessedRoots: new WitnessedRootHashList(), - stateRoot: Field(block.block.fromStateRoot), + stateRoot: block.block.fromStateRoot, eternalTransactionsList: new TransactionHashList( block.block.fromEternalTransactionsHash ), @@ -80,7 +79,7 @@ export class BatchTracingService { @trace("batch.trace.transitions") public async traceStateTransitions( blocks: BlockWithResult[], - merkleTreeStore: CachedMerkleTreeStore + merkleTreeStore: CachedLinkedLeafStore ) { const batches = await this.tracer.trace( "batch.trace.transitions.encoding", @@ -96,7 +95,7 @@ export class BatchTracingService { @trace("batch.trace", ([, , batchId]) => ({ batchId })) public async traceBatch( blocks: BlockWithResult[], - merkleTreeStore: CachedMerkleTreeStore, + merkleTreeStore: CachedLinkedLeafStore, // Only for trace metadata batchId: number ): Promise { diff --git a/packages/sequencer/src/protocol/production/tracing/StateTransitionTracingService.ts b/packages/sequencer/src/protocol/production/tracing/StateTransitionTracingService.ts index 3c00f69b2..4f0d2194b 100644 --- a/packages/sequencer/src/protocol/production/tracing/StateTransitionTracingService.ts +++ b/packages/sequencer/src/protocol/production/tracing/StateTransitionTracingService.ts @@ -1,5 +1,9 @@ import { Bool, Field } from "o1js"; -import { mapSequential, RollupMerkleTree } from "@proto-kit/common"; +import { + LinkedMerkleTree, + LinkedMerkleTreeWitness, + mapSequential, +} from "@proto-kit/common"; import { inject, injectable } from "tsyringe"; import { AppliedBatchHashList, @@ -15,11 +19,11 @@ import { import { distinctByString } from "../../../helpers/utils"; import { BlockWithResult } from "../../../storage/model/Block"; import { UntypedStateTransition } from "../helpers/UntypedStateTransition"; -import { CachedMerkleTreeStore } from "../../../state/merkle/CachedMerkleTreeStore"; import { StateTransitionProofParameters } from "../tasks/StateTransitionTask"; -import { SyncCachedMerkleTreeStore } from "../../../state/merkle/SyncCachedMerkleTreeStore"; import { trace } from "../../../logging/trace"; import { Tracer } from "../../../logging/Tracer"; +import { CachedLinkedLeafStore } from "../../../state/lmt/CachedLinkedLeafStore"; +import { SyncCachedLinkedLeafStore } from "../../../state/merkle/SyncCachedLinkedLeafStore"; export interface TracingStateTransitionBatch { stateTransitions: UntypedStateTransition[]; @@ -67,7 +71,7 @@ export class StateTransitionTracingService { @trace("batch.trace.transitions.merkle_trace") public async createMerkleTrace( - merkleStore: CachedMerkleTreeStore, + merkleStore: CachedLinkedLeafStore, stateTransitions: TracingStateTransitionBatch[] ) { const batches = StateTransitionProvableBatch.fromBatches( @@ -90,7 +94,7 @@ export class StateTransitionTracingService { } public async traceTransitions( - merkleStore: CachedMerkleTreeStore, + merkleStore: CachedLinkedLeafStore, batches: StateTransitionProvableBatch[] ): Promise { const keys = this.allKeys( @@ -101,9 +105,12 @@ export class StateTransitionTracingService { await merkleStore.preloadKeys(keys.map((key) => key.toBigInt())); - let batchMerkleStore = new SyncCachedMerkleTreeStore(merkleStore); + let batchMerkleStore = new SyncCachedLinkedLeafStore(merkleStore); - let tree = new RollupMerkleTree(batchMerkleStore); + let tree = new LinkedMerkleTree( + batchMerkleStore.treeStore, + batchMerkleStore + ); const initialRoot = tree.getRoot(); const batchList = new AppliedBatchHashList(Field(0)); @@ -135,17 +142,23 @@ export class StateTransitionTracingService { async (transitionInfo) => { const { stateTransition, type, witnessRoot } = transitionInfo; - const merkleWitness = tree.getWitness( - stateTransition.path.toBigInt() - ); + // const merkleWitness = tree.getWitness( + // stateTransition.path.toBigInt() + // ); + + let witness: LinkedMerkleTreeWitness; if (stateTransition.to.isSome.toBoolean()) { - tree.setLeaf( + witness = tree.setLeaf( stateTransition.path.toBigInt(), - stateTransition.to.value + stateTransition.to.value.toBigInt() ); danglingStateRoot = tree.getRoot(); + } else { + witness = LinkedMerkleTreeWitness.fromReadWitness( + tree.getReadWitness(stateTransition.path.toBigInt()) + ); } currentSTList.push(stateTransition); @@ -173,8 +186,11 @@ export class StateTransitionTracingService { danglingStateRoot = finalizedStateRoot; - batchMerkleStore = new SyncCachedMerkleTreeStore(merkleStore); - tree = new RollupMerkleTree(batchMerkleStore); + batchMerkleStore = new SyncCachedLinkedLeafStore(merkleStore); + tree = new LinkedMerkleTree( + batchMerkleStore.treeStore, + batchMerkleStore + ); } else { throw new Error("Unreachable"); } @@ -197,7 +213,7 @@ export class StateTransitionTracingService { ); } - return [merkleWitness, witnessRoot] as const; + return [witness, witnessRoot] as const; } ); diff --git a/packages/sequencer/src/settlement/BridgingModule.ts b/packages/sequencer/src/settlement/BridgingModule.ts index c4e407246..d67a839a4 100644 --- a/packages/sequencer/src/settlement/BridgingModule.ts +++ b/packages/sequencer/src/settlement/BridgingModule.ts @@ -29,9 +29,9 @@ import { import { AreProofsEnabled, filterNonUndefined, + LinkedMerkleTree, log, noop, - RollupMerkleTree, } from "@proto-kit/common"; import { match, Pattern } from "ts-pattern"; import { FungibleToken } from "mina-fungible-token"; @@ -40,10 +40,10 @@ import { SequencerModule, sequencerModule, } from "../sequencer/builder/SequencerModule"; -import { CachedMerkleTreeStore } from "../state/merkle/CachedMerkleTreeStore"; -import { AsyncMerkleTreeStore } from "../state/async/AsyncMerkleTreeStore"; import { FeeStrategy } from "../protocol/baselayer/fees/FeeStrategy"; import type { MinaBaseLayer } from "../protocol/baselayer/MinaBaseLayer"; +import { AsyncLinkedLeafStore } from "../state/async/AsyncLinkedLeafStore"; +import { CachedLinkedLeafStore } from "../state/lmt/CachedLinkedLeafStore"; import type { OutgoingMessageAdapter } from "./messages/WithdrawalQueue"; import type { SettlementModule } from "./SettlementModule"; @@ -75,8 +75,8 @@ export class BridgingModule extends SequencerModule { private readonly settlementModule: SettlementModule, @inject("OutgoingMessageQueue") private readonly outgoingMessageQueue: OutgoingMessageAdapter, - @inject("AsyncMerkleStore") - private readonly merkleTreeStore: AsyncMerkleTreeStore, + @inject("AsyncLinkedLeafStore") + private readonly linkedLeafStore: AsyncLinkedLeafStore, @inject("FeeStrategy") private readonly feeStrategy: FeeStrategy, @inject("AreProofsEnabled") areProofsEnabled: AreProofsEnabled, @@ -357,8 +357,8 @@ export class BridgingModule extends SequencerModule { const bridgeContract = this.createBridgeContract(bridgeAddress, tokenId); - const cachedStore = new CachedMerkleTreeStore(this.merkleTreeStore); - const tree = new RollupMerkleTree(cachedStore); + const cachedStore = await CachedLinkedLeafStore.new(this.linkedLeafStore); + const tree = new LinkedMerkleTree(cachedStore.treeStore, cachedStore); const [withdrawalModule, withdrawalStateName] = this.getBridgingModuleConfig().withdrawalStatePath.split("."); @@ -400,7 +400,7 @@ export class BridgingModule extends SequencerModule { await cachedStore.preloadKeys(keys.map((key) => key.toBigInt())); const transactionParamaters = batch.map((message, index) => { - const witness = tree.getWitness(keys[index].toBigInt()); + const witness = tree.getReadWitness(keys[index].toBigInt()); return new OutgoingMessageArgument({ witness, value: message.value, diff --git a/packages/sequencer/src/storage/StorageDependencyFactory.ts b/packages/sequencer/src/storage/StorageDependencyFactory.ts index 27c1a29b2..e53d3b0c9 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 { AsyncLinkedMerkleTreeStore } from "../state/async/AsyncLinkedMerkleTreeStore"; +import { AsyncLinkedLeafStore } from "../state/async/AsyncLinkedLeafStore"; import { AsyncMerkleTreeStore } from "../state/async/AsyncMerkleTreeStore"; import { BatchStorage } from "./repositories/BatchStorage"; @@ -16,12 +16,14 @@ import { TransactionStorage } from "./repositories/TransactionStorage"; export interface StorageDependencyMinimumDependencies extends DependencyRecord { asyncStateService: DependencyDeclaration; - asyncMerkleStore: DependencyDeclaration; + asyncLinkedLeafStore: DependencyDeclaration; + + unprovenStateService: DependencyDeclaration; + unprovenLinkedLeafStore: DependencyDeclaration; + batchStorage: DependencyDeclaration; blockQueue: DependencyDeclaration; blockStorage: DependencyDeclaration; - unprovenStateService: DependencyDeclaration; - unprovenMerkleStore: DependencyDeclaration; blockTreeStore: DependencyDeclaration; messageStorage: DependencyDeclaration; settlementStorage: DependencyDeclaration; diff --git a/packages/sequencer/src/storage/model/Block.ts b/packages/sequencer/src/storage/model/Block.ts index 823b3c8c6..022b23837 100644 --- a/packages/sequencer/src/storage/model/Block.ts +++ b/packages/sequencer/src/storage/model/Block.ts @@ -5,7 +5,7 @@ import { BlockHashMerkleTreeWitness, NetworkState, } from "@proto-kit/protocol"; -import { RollupMerkleTree } from "@proto-kit/common"; +import { LinkedMerkleTree, RollupMerkleTree } from "@proto-kit/common"; import { PendingTransaction } from "../../mempool/PendingTransaction"; import { UntypedStateTransition } from "../../protocol/production/helpers/UntypedStateTransition"; @@ -105,7 +105,7 @@ export const BlockWithResult = { }, fromBlockHashRoot: Field(BlockHashMerkleTree.EMPTY_ROOT), fromMessagesHash: Field(0), - fromStateRoot: Field(RollupMerkleTree.EMPTY_ROOT), + fromStateRoot: LinkedMerkleTree.EMPTY_ROOT, toMessagesHash: ACTIONS_EMPTY_HASH, beforeBlockStateTransitions: [], @@ -113,12 +113,12 @@ export const BlockWithResult = { }, result: { afterNetworkState: NetworkState.empty(), - stateRoot: RollupMerkleTree.EMPTY_ROOT, + stateRoot: LinkedMerkleTree.EMPTY_ROOT.toBigInt(), blockHashRoot: BlockHashMerkleTree.EMPTY_ROOT, afterBlockStateTransitions: [], blockHashWitness: BlockHashMerkleTree.WITNESS.dummy(), blockHash: 0n, - witnessedRoots: [RollupMerkleTree.EMPTY_ROOT], + witnessedRoots: [LinkedMerkleTree.EMPTY_ROOT.toBigInt()], }, }) satisfies BlockWithResult, }; From 9b220e7afd2e3b396c0f2830bb2f6d814bbd9f08 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 11 Apr 2025 14:56:40 +0200 Subject: [PATCH 117/128] Fixed a few bugs, block production working --- .../src/trees/lmt/InMemoryLinkedLeafStore.ts | 4 +- .../common/src/trees/lmt/LinkedMerkleTree.ts | 7 +- .../trees/lmt/LinkedMerkleTreeCircuitOps.ts | 4 +- .../test/trees/LinkedMerkleTree.test.ts | 50 ++++---- .../src/helpers/query/QueryBuilderFactory.ts | 6 +- .../src/helpers/query/QueryTransportModule.ts | 4 +- packages/sequencer/src/index.ts | 3 + .../sequencing/BlockResultService.ts | 11 +- .../src/state/lmt/CachedLinkedLeafStore.ts | 45 +++++--- .../state/merkle/SyncCachedLinkedLeafStore.ts | 1 + .../state/merkle/SyncCachedMerkleTreeStore.ts | 12 +- .../inmemory/InMemoryAsyncLinkedLeafStore.ts | 13 ++- .../test/integration/BlockProduction.test.ts | 1 + .../merkle/CachedLinkedMerkleStore.test.ts | 107 +++++++++--------- 14 files changed, 153 insertions(+), 115 deletions(-) diff --git a/packages/common/src/trees/lmt/InMemoryLinkedLeafStore.ts b/packages/common/src/trees/lmt/InMemoryLinkedLeafStore.ts index f571af642..055c839f2 100644 --- a/packages/common/src/trees/lmt/InMemoryLinkedLeafStore.ts +++ b/packages/common/src/trees/lmt/InMemoryLinkedLeafStore.ts @@ -5,8 +5,6 @@ export class InMemoryLinkedLeafStore implements LinkedLeafStore { [key: string]: { leaf: LinkedLeaf; index: bigint }; } = {}; - // public treeStore = new InMemoryMerkleTreeStorage(); - public maximumIndex?: bigint; public getLeaf( @@ -23,7 +21,7 @@ export class InMemoryLinkedLeafStore implements LinkedLeafStore { } this.leaves[value.path.toString()] = { leaf: value, index: index }; - if (this.maximumIndex === undefined || index > this.maximumIndex) { + if (index > (this.maximumIndex ?? -1)) { this.maximumIndex = index; } } diff --git a/packages/common/src/trees/lmt/LinkedMerkleTree.ts b/packages/common/src/trees/lmt/LinkedMerkleTree.ts index afb342a3b..998c07ed4 100644 --- a/packages/common/src/trees/lmt/LinkedMerkleTree.ts +++ b/packages/common/src/trees/lmt/LinkedMerkleTree.ts @@ -75,7 +75,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.leafStore.getMaximumIndex() === undefined) { + if (this.leafStore.getLeaf(0n) === undefined) { this.setLeafInitialisation(); } } @@ -122,11 +122,6 @@ export function createLinkedMerkleTree( */ public setLeaf(path: bigint, value: bigint): LinkedOperationWitness { const storedLeaf = this.leafStore.getLeaf(path); - // const prevLeaf = this.store.getLeafLessOrEqual(path); - // - // if (prevLeaf === undefined) { - // throw Error("Prev leaf shouldn't be undefined"); - // } if (storedLeaf === undefined) { // Insert case diff --git a/packages/common/src/trees/lmt/LinkedMerkleTreeCircuitOps.ts b/packages/common/src/trees/lmt/LinkedMerkleTreeCircuitOps.ts index 543088f4f..c3660fcdd 100644 --- a/packages/common/src/trees/lmt/LinkedMerkleTreeCircuitOps.ts +++ b/packages/common/src/trees/lmt/LinkedMerkleTreeCircuitOps.ts @@ -139,7 +139,7 @@ export namespace LinkedMerkleTreeCircuitOps { newPreviousLeaf: LinkedLeafStruct, newCurrentLeaf: LinkedLeafStruct, isUpdate: Bool, - isDummyAndUpdate: Bool, + isDummy: Bool, root: Field ) { const { leafPrevious, leafCurrent } = witness; @@ -165,7 +165,7 @@ export namespace LinkedMerkleTreeCircuitOps { leafCurrent.merkleWitness .calculateRoot(leafCurrentLeaf) .equals(intermediateRoot) - .or(isDummyAndUpdate) + .or(isDummy) .assertTrue("Current leaf witness invalid"); return leafCurrent.merkleWitness.calculateRoot(newCurrentLeaf.hash()); diff --git a/packages/common/test/trees/LinkedMerkleTree.test.ts b/packages/common/test/trees/LinkedMerkleTree.test.ts index 18b39066d..440fe4caa 100644 --- a/packages/common/test/trees/LinkedMerkleTree.test.ts +++ b/packages/common/test/trees/LinkedMerkleTree.test.ts @@ -3,7 +3,8 @@ import { Field, Poseidon } from "o1js"; import { createLinkedMerkleTree, - InMemoryLinkedMerkleLeafStore, + InMemoryLinkedLeafStore, + InMemoryMerkleTreeStorage, log, } from "../../src"; import { expectDefined } from "../../dist/utils"; @@ -11,22 +12,25 @@ import { expectDefined } from "../../dist/utils"; describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { class LinkedMerkleTree extends createLinkedMerkleTree(height) {} - let store: InMemoryLinkedMerkleLeafStore; + let leafStore: InMemoryLinkedLeafStore; + let merkleStore: InMemoryMerkleTreeStorage; let tree: LinkedMerkleTree; beforeEach(() => { log.setLevel("INFO"); - store = new InMemoryLinkedMerkleLeafStore(); - tree = new LinkedMerkleTree(store, store); + leafStore = new InMemoryLinkedLeafStore(); + merkleStore = new InMemoryMerkleTreeStorage(); + tree = new LinkedMerkleTree(merkleStore, leafStore); }); it("should have the same root when empty", () => { - expect.assertions(1); + expect.assertions(2); - expect(tree.getRoot().toBigInt()).toStrictEqual( - LinkedMerkleTree.EMPTY_ROOT + expect(tree.getRoot().toString()).toStrictEqual( + LinkedMerkleTree.EMPTY_ROOT.toString() ); + expectDefined(tree.getLeaf(0n)); }); it("should have a different root when not empty", () => { @@ -34,8 +38,8 @@ describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { tree.setLeaf(1n, 1n); - expect(tree.getRoot().toBigInt()).not.toStrictEqual( - LinkedMerkleTree.EMPTY_ROOT + expect(tree.getRoot().toString()).not.toStrictEqual( + LinkedMerkleTree.EMPTY_ROOT.toString() ); }); @@ -49,8 +53,8 @@ describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { expect(witness.leaf.value.toString()).toStrictEqual("5"); expect( - witness.merkleWitness.calculateRoot(witness.leaf.hash()).toBigInt() - ).toStrictEqual(tree.getRoot().toBigInt()); + witness.merkleWitness.calculateRoot(witness.leaf.hash()).toString() + ).toStrictEqual(tree.getRoot().toString()); }); it("should have invalid witnesses with wrong values", () => { @@ -62,8 +66,8 @@ describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { const witness = tree.getReadWitness(5n); expect( - witness.merkleWitness.calculateRoot(Field(6)).toBigInt() - ).not.toStrictEqual(tree.getRoot().toBigInt()); + witness.merkleWitness.calculateRoot(Field(6)).toString() + ).not.toStrictEqual(tree.getRoot().toString()); }); it("should have valid witnesses with changed value on the same leafs", () => { @@ -81,8 +85,8 @@ describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { .calculateRoot( Poseidon.hash([Field(10), witness.leaf.path, witness.leaf.nextPath]) ) - .toBigInt() - ).toStrictEqual(tree.getRoot().toBigInt()); + .toString() + ).toStrictEqual(tree.getRoot().toString()); }); it("should return zeroNode", () => { @@ -90,23 +94,27 @@ describe.each([4, 16, 254])("cachedMerkleTree - %s", (height) => { 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(MAX_FIELD_VALUE); + expect(zeroLeaf.value.toString()).toStrictEqual("0"); + expect(zeroLeaf.path.toString()).toStrictEqual("0"); + expect(zeroLeaf.nextPath.toString()).toStrictEqual( + MAX_FIELD_VALUE.toString() + ); }); }); // Separate describe here since we only want small trees for this test. describe("Error check", () => { class LinkedMerkleTree extends createLinkedMerkleTree(4) {} - let store: InMemoryLinkedMerkleLeafStore; + let leafStore: InMemoryLinkedLeafStore; + let merkleStore: InMemoryMerkleTreeStorage; let tree: LinkedMerkleTree; it("throw for invalid index", () => { log.setLevel("INFO"); - store = new InMemoryLinkedMerkleLeafStore(); - tree = new LinkedMerkleTree(store, store); + leafStore = new InMemoryLinkedLeafStore(); + merkleStore = new InMemoryMerkleTreeStorage(); + tree = new LinkedMerkleTree(merkleStore, leafStore); expect(() => { for (let i = 0; i < 2n ** BigInt(4) + 1n; i++) { tree.setLeaf(BigInt(i), 2n); diff --git a/packages/sequencer/src/helpers/query/QueryBuilderFactory.ts b/packages/sequencer/src/helpers/query/QueryBuilderFactory.ts index 1029f5d57..08f65b7c7 100644 --- a/packages/sequencer/src/helpers/query/QueryBuilderFactory.ts +++ b/packages/sequencer/src/helpers/query/QueryBuilderFactory.ts @@ -1,4 +1,4 @@ -import { TypedClass, LinkedLeafAndMerkleWitness } from "@proto-kit/common"; +import { LinkedMerkleTreeReadWitness, TypedClass } 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 0998d1194..ce36eeec5 100644 --- a/packages/sequencer/src/helpers/query/QueryTransportModule.ts +++ b/packages/sequencer/src/helpers/query/QueryTransportModule.ts @@ -1,9 +1,9 @@ import { Field } from "o1js"; -import { LinkedLeafAndMerkleWitness } from "@proto-kit/common"; +import { LinkedMerkleTreeReadWitness } from "@proto-kit/common"; export interface QueryTransportModule { get: (key: Field) => Promise; merkleWitness: ( key: Field - ) => Promise; + ) => Promise; } diff --git a/packages/sequencer/src/index.ts b/packages/sequencer/src/index.ts index 476da4de4..4605b4f53 100644 --- a/packages/sequencer/src/index.ts +++ b/packages/sequencer/src/index.ts @@ -86,10 +86,13 @@ export * from "./helpers/query/NetworkStateTransportModule"; export * from "./state/prefilled/PreFilledStateService"; export * from "./state/async/AsyncMerkleTreeStore"; export * from "./state/async/AsyncStateService"; +export * from "./state/async/AsyncLinkedLeafStore"; export * from "./state/merkle/CachedMerkleTreeStore"; export * from "./state/merkle/SyncCachedMerkleTreeStore"; export * from "./state/state/DummyStateService"; export * from "./state/state/CachedStateService"; +export * from "./state/lmt/AsyncLinkedMerkleTreeDatabase"; +export * from "./state/lmt/CachedLinkedLeafStore"; export * from "./settlement/SettlementModule"; export * from "./settlement/messages/WithdrawalQueue"; export * from "./settlement/messages/IncomingMessageAdapter"; diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockResultService.ts b/packages/sequencer/src/protocol/production/sequencing/BlockResultService.ts index 7030700da..5b2cbe9d8 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockResultService.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockResultService.ts @@ -32,7 +32,8 @@ import { CachedLinkedLeafStore } from "../../../state/lmt/CachedLinkedLeafStore" import { executeWithExecutionContext } from "./TransactionExecutionService"; -function collectStateDiff( +// This is ordered, because javascript maintains the order based on time of first insertion +function collectOrderedStateDiff( stateTransitions: UntypedStateTransition[] ): StateRecord { return stateTransitions.reduce>( @@ -46,7 +47,7 @@ function collectStateDiff( ); } -function createCombinedStateDiff( +function createCombinedOrderedStateDiff( transactions: TransactionExecutionResult[], blockHookSTs: UntypedStateTransition[] ) { @@ -59,7 +60,7 @@ function createCombinedStateDiff( transitions.push(...blockHookSTs); - return collectStateDiff(transitions); + return collectOrderedStateDiff(transitions); }) .reduce((accumulator, diff) => { // accumulator properties will be overwritten by diff's values @@ -190,7 +191,7 @@ export class BlockResultService { blockHashTreeStore: CachedMerkleTreeStore; stateService: CachedStateService; }> { - const combinedDiff = createCombinedStateDiff( + const combinedDiff = createCombinedOrderedStateDiff( block.transactions, block.beforeBlockStateTransitions ); @@ -222,7 +223,7 @@ export class BlockResultService { // Apply afterBlock STs to the tree const tree2 = await this.applyStateDiff( inMemoryStore, - collectStateDiff( + collectOrderedStateDiff( stateTransitions.map((stateTransition) => UntypedStateTransition.fromStateTransition(stateTransition) ) diff --git a/packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts b/packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts index e11586b15..ebc323166 100644 --- a/packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts +++ b/packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts @@ -31,6 +31,7 @@ export class CachedLinkedLeafStore implements LinkedLeafStore { ): Promise { const cachedInstance = new CachedLinkedLeafStore(parent); await cachedInstance.preloadMaximumIndex(); + await cachedInstance.preloadZeroNode(); return cachedInstance; } @@ -92,7 +93,15 @@ export class CachedLinkedLeafStore implements LinkedLeafStore { this.writeCache = {}; } - protected async preloadMaximumIndex() { + protected async preloadZeroNode() { + if (this.leafStore.getLeaf(0n) === undefined) { + await this.preloadKey(0n); + } + } + + private async preloadMaximumIndex() { + // Preload maximumIndex before all others, to have a accurate index loaded already + // before setting a ny other leaves if (this.leafStore.getMaximumIndex() === undefined) { this.leafStore.maximumIndex = await this.parent.getMaximumIndexAsync(); } @@ -102,14 +111,14 @@ export class CachedLinkedLeafStore implements LinkedLeafStore { // parent tree and sets the leaf and node in the cached tree (and in-memory tree). public async preloadKeyInternal( path: bigint - ): Promise<{ requiredTreePaths: bigint[] }> { + ): Promise<{ requiredTreeIndizes: bigint[] }> { const leaf = (await this.getLeavesAsync([path]))[0]; if (leaf !== undefined) { // Update case, this leaf is the only one we need this.leafStore.setLeaf(leaf.index, leaf.leaf); - return { requiredTreePaths: [leaf.index] }; + return { requiredTreeIndizes: [leaf.index] }; } else { // Insert case, this leaf doesn't yet exist - we need to fetch the previous one @@ -121,50 +130,56 @@ export class CachedLinkedLeafStore implements LinkedLeafStore { (await this.parent.getLeafLessOrEqualAsync(path)); if (previousLeaf === undefined) { - throw Error("Previous Leaf should never be empty"); + // throw Error("Previous Leaf should never be empty"); + // This only happens when the store is empty, because in this case, the tree + // initializes the 0-leaf, but this only happens after preloading. + const [zeroLeaf] = await this.parent.getLeavesAsync([0n]); + if (zeroLeaf !== undefined) { + throw Error("Previous Leaf should never be empty"); + } + return { + requiredTreeIndizes: [], + }; } this.leafStore.setLeaf(previousLeaf.index, previousLeaf.leaf); - // Since we set a leaf right before this call, getMaximumIndex will always return a value - // Also note that this maximumIndex is already the "to be occupied" index of the new - // inserted leaf, not the "last occupied one" as normally the case const maximumIndex = this.leafStore.getMaximumIndex(); if (maximumIndex === undefined) { throw Error("Maximum index should be defined in parent."); } - return { requiredTreePaths: [previousLeaf.index, maximumIndex] }; + return { requiredTreeIndizes: [previousLeaf.index, maximumIndex + 1n] }; } } public async preloadKey(path: bigint) { - const { requiredTreePaths } = await this.preloadKeyInternal(path); - await this.treeCache.preloadKeys(requiredTreePaths); + const { requiredTreeIndizes } = await this.preloadKeyInternal(path); + await this.treeCache.preloadKeys(requiredTreeIndizes); } public async preloadKeys(paths: bigint[]): Promise { const results = await mapSequential(paths, (x) => this.preloadKeyInternal(x) ); - const treePaths = results.flatMap( - ({ requiredTreePaths }) => requiredTreePaths + const treeIndizes = results.flatMap( + ({ requiredTreeIndizes }) => requiredTreeIndizes ); - await this.treeCache.preloadKeys(treePaths); + await this.treeCache.preloadKeys(treeIndizes); } // This merges the cache into the parent tree and resets the cache, but not the // in-memory merkle tree. public async mergeIntoParent(): Promise { + const leaves = this.getWrittenLeaves(); // In case no state got set we can skip this step - if (Object.keys(this.writeCache.leaves).length === 0) { + if (leaves.length === 0) { return; } await this.parent.openTransaction(); - const leaves = this.getWrittenLeaves(); this.parent.writeLeaves(Object.values(leaves)); await this.parent.commit(); diff --git a/packages/sequencer/src/state/merkle/SyncCachedLinkedLeafStore.ts b/packages/sequencer/src/state/merkle/SyncCachedLinkedLeafStore.ts index cb8d17f5f..1ad43869a 100644 --- a/packages/sequencer/src/state/merkle/SyncCachedLinkedLeafStore.ts +++ b/packages/sequencer/src/state/merkle/SyncCachedLinkedLeafStore.ts @@ -18,6 +18,7 @@ export class SyncCachedLinkedLeafStore implements LinkedLeafStore { public constructor(private readonly parent: CachedLinkedLeafStore) { this.treeCache = new SyncCachedMerkleTreeStore(parent.treeStore); + this.leafStore.maximumIndex = parent.getMaximumIndex(); } public get treeStore() { diff --git a/packages/sequencer/src/state/merkle/SyncCachedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/SyncCachedMerkleTreeStore.ts index a2e122bca..337ad0d36 100644 --- a/packages/sequencer/src/state/merkle/SyncCachedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/SyncCachedMerkleTreeStore.ts @@ -24,11 +24,13 @@ export class SyncCachedMerkleTreeStore extends InMemoryMerkleTreeStorage { const { nodes } = this; - Array.from({ length: RollupMerkleTree.HEIGHT }).forEach((ignored, level) => - Object.entries(nodes[level]).forEach((entry) => { - this.parent.setNode(BigInt(entry[0]), level, entry[1]); - }) - ); + Object.keys(nodes) + .map((level) => parseInt(level, 10)) + .forEach((level) => + Object.entries(nodes[level]).forEach((entry) => { + this.parent.setNode(BigInt(entry[0]), level, entry[1]); + }) + ); this.nodes = {}; } diff --git a/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedLeafStore.ts b/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedLeafStore.ts index 44b72163b..657719dd3 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedLeafStore.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedLeafStore.ts @@ -1,4 +1,9 @@ -import { InMemoryLinkedLeafStore, LinkedLeaf, noop } from "@proto-kit/common"; +import { + initialLinkedLeaf, + InMemoryLinkedLeafStore, + LinkedLeaf, + noop, +} from "@proto-kit/common"; import { AsyncLinkedLeafStore } from "../../state/async/AsyncLinkedLeafStore"; @@ -9,6 +14,12 @@ export class InMemoryAsyncLinkedLeafStore implements AsyncLinkedLeafStore { private readonly nodeStore = new InMemoryAsyncMerkleTreeStore(); + // public constructor() { + // const initialLeaf = initialLinkedLeaf(); + // this.leafStore.setLeaf(0n, initialLeaf); + // this.nodeStore.writeNodes([{ }]); + // } + public get treeStore() { return this.nodeStore; } diff --git a/packages/sequencer/test/integration/BlockProduction.test.ts b/packages/sequencer/test/integration/BlockProduction.test.ts index 049b5d223..cbee1003f 100644 --- a/packages/sequencer/test/integration/BlockProduction.test.ts +++ b/packages/sequencer/test/integration/BlockProduction.test.ts @@ -175,6 +175,7 @@ describe("block production", () => { }); it("should produce a dummy block proof", async () => { + log.setLevel("DEBUG"); expect.assertions(26); const privateKey = PrivateKey.random(); diff --git a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts index 4345ef761..5ceae2768 100644 --- a/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts +++ b/packages/sequencer/test/merkle/CachedLinkedMerkleStore.test.ts @@ -6,27 +6,28 @@ import { 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"; +import { CachedLinkedLeafStore } from "../../src/state/lmt/CachedLinkedLeafStore"; +import { InMemoryAsyncLinkedLeafStore } from "../../src/storage/inmemory/InMemoryAsyncLinkedLeafStore"; +import { SyncCachedLinkedLeafStore } from "../../src/state/merkle/SyncCachedLinkedLeafStore"; describe("cached linked merkle store", () => { - let mainStore: InMemoryAsyncLinkedMerkleTreeStore; + let mainStore: InMemoryAsyncLinkedLeafStore; - let cache1: CachedLinkedMerkleTreeStore; + let cache1: CachedLinkedLeafStore; let tree1: LinkedMerkleTree; beforeEach(async () => { - mainStore = new InMemoryAsyncLinkedMerkleTreeStore(); + mainStore = new InMemoryAsyncLinkedLeafStore(); - const cachedStore = await CachedLinkedMerkleTreeStore.new(mainStore); + const cachedStore = await CachedLinkedLeafStore.new(mainStore); - const tmpTree = new LinkedMerkleTree(cachedStore); + const tmpTree = new LinkedMerkleTree(cachedStore.treeStore, cachedStore); tmpTree.setLeaf(5n, 10n); + await cachedStore.mergeIntoParent(); - cache1 = await CachedLinkedMerkleTreeStore.new(mainStore); - tree1 = new LinkedMerkleTree(cache1); + cache1 = await CachedLinkedLeafStore.new(mainStore); + tree1 = new LinkedMerkleTree(cache1.treeStore, cache1); }); it("should cache multiple keys correctly", async () => { @@ -35,8 +36,8 @@ describe("cached linked merkle store", () => { tree1.setLeaf(16n, 16n); tree1.setLeaf(46n, 46n); - const cache2 = new SyncCachedLinkedMerkleTreeStore(cache1); - const tree2 = new LinkedMerkleTree(cache2); + const cache2 = new SyncCachedLinkedLeafStore(cache1); + const tree2 = new LinkedMerkleTree(cache2.treeStore, cache2); const leaf1 = tree1.getLeaf(16n); const leaf2 = tree1.getLeaf(46n); @@ -53,10 +54,10 @@ describe("cached linked merkle store", () => { expect(storedLeaf1.index).toStrictEqual(2n); expect(storedLeaf2.index).toStrictEqual(3n); - expect(tree2.getNode(0, storedLeaf1.index).toBigInt()).toBe( + expect(tree2.tree.getNode(0, storedLeaf1.index).toBigInt()).toBe( leaf1.hash().toBigInt() ); - expect(tree2.getNode(0, storedLeaf2.index).toBigInt()).toBe( + expect(tree2.tree.getNode(0, storedLeaf2.index).toBigInt()).toBe( leaf2.hash().toBigInt() ); @@ -74,7 +75,7 @@ describe("cached linked merkle store", () => { await cache1.preloadKeys([10n]); expectDefined(cache1.getLeaf(5n)); - expectDefined(cache1.getNode(1n, 0)); + expectDefined(cache1.treeStore.getNode(1n, 0)); tree1.setLeaf(10n, 10n); await cache1.mergeIntoParent(); @@ -97,10 +98,10 @@ describe("cached linked merkle store", () => { expect(storedLeaf10.index).toStrictEqual(2n); // Check leaves were hashed properly when added to nodes/merkle-tree - expect(cache1.getNode(storedLeaf10.index, 0)).toStrictEqual( + expect(cache1.treeStore.getNode(storedLeaf10.index, 0)).toStrictEqual( leaf10.hash().toBigInt() ); - expect(cache1.getNode(storedLeaf5.index, 0)).toStrictEqual( + expect(cache1.treeStore.getNode(storedLeaf5.index, 0)).toStrictEqual( leaf5.hash().toBigInt() ); }); @@ -114,10 +115,10 @@ describe("cached linked merkle store", () => { tree1.setLeaf(13n, 13n); await cache1.mergeIntoParent(); - const cache2 = new SyncCachedLinkedMerkleTreeStore(cache1); + const cache2 = new SyncCachedLinkedLeafStore(cache1); await cache2.preloadKeys([14n]); - const tree2 = new LinkedMerkleTree(cache2); + const tree2 = new LinkedMerkleTree(cache2.treeStore, cache2); tree2.setLeaf(14n, 14n); const leaf = tree1.getLeaf(5n); @@ -148,10 +149,10 @@ describe("cached linked merkle store", () => { expect(storedLeaf14.index).toStrictEqual(6n); // Check leaves were hashed properly when added to nodes/merkle-tree - expect(cache1.getNode(storedLeaf5.index, 0)).toStrictEqual( + expect(cache1.treeStore.getNode(storedLeaf5.index, 0)).toStrictEqual( leaf.hash().toBigInt() ); - expect(cache2.getNode(storedLeaf14.index, 0)).toStrictEqual( + expect(cache2.treeStore.getNode(storedLeaf14.index, 0)).toStrictEqual( leaf2.hash().toBigInt() ); }); @@ -166,9 +167,9 @@ describe("cached linked merkle store", () => { tree1.setLeaf(400n, 400n); tree1.setLeaf(500n, 500n); - const cache2 = new SyncCachedLinkedMerkleTreeStore(cache1); + const cache2 = new SyncCachedLinkedLeafStore(cache1); await cache2.preloadKeys([14n]); - const tree2 = new LinkedMerkleTree(cache2); + const tree2 = new LinkedMerkleTree(cache2.treeStore, cache2); tree2.setLeaf(14n, 14n); const leaf = tree1.getLeaf(5n); @@ -204,10 +205,10 @@ describe("cached linked merkle store", () => { expect(storedLeaf500.index).toStrictEqual(7n); expect(storedLeaf14.index).toStrictEqual(8n); - expect(cache1.getNode(storedLeaf5.index, 0)).toStrictEqual( + expect(cache1.treeStore.getNode(storedLeaf5.index, 0)).toStrictEqual( leaf.hash().toBigInt() ); - expect(cache2.getNode(storedLeaf14.index, 0)).toStrictEqual( + expect(cache2.treeStore.getNode(storedLeaf14.index, 0)).toStrictEqual( leaf2.hash().toBigInt() ); expect(tree1.getRoot()).not.toEqual(tree2.getRoot()); @@ -218,14 +219,14 @@ describe("cached linked merkle store", () => { it("mimic transaction execution service", async () => { expect.assertions(18); - const treeCache1 = new LinkedMerkleTree(cache1); + const treeCache1 = new LinkedMerkleTree(cache1.treeStore, cache1); 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); + const cache2 = new SyncCachedLinkedLeafStore(cache1); + const treeCache2 = new LinkedMerkleTree(cache2.treeStore, cache2); await cache2.preloadKeys([7n]); treeCache2.setLeaf(7n, 7n); cache2.mergeIntoParent(); @@ -270,25 +271,25 @@ describe("cached linked merkle store", () => { expectDefined(storedLeaf5); await expect( - cache1.getNodesAsync([{ key: storedLeaf5.index, level: 0 }]) + cache1.treeStore.getNodesAsync([{ key: storedLeaf5.index, level: 0 }]) ).resolves.toStrictEqual([ Poseidon.hash([Field(10), Field(5), Field(7)]).toBigInt(), ]); expectDefined(storedLeaf7); await expect( - cache1.getNodesAsync([{ key: storedLeaf7.index, level: 0 }]) + cache1.treeStore.getNodesAsync([{ key: storedLeaf7.index, level: 0 }]) ).resolves.toStrictEqual([ Poseidon.hash([Field(7), Field(7), Field(10)]).toBigInt(), ]); expectDefined(storedLeaf10); await expect( - cache1.getNodesAsync([{ key: storedLeaf10.index, level: 0 }]) + cache1.treeStore.getNodesAsync([{ key: storedLeaf10.index, level: 0 }]) ).resolves.toStrictEqual([ Poseidon.hash([Field(10), Field(10), Field(20)]).toBigInt(), ]); expectDefined(storedLeaf20); await expect( - cache1.getNodesAsync([{ key: storedLeaf20.index, level: 0 }]) + cache1.treeStore.getNodesAsync([{ key: storedLeaf20.index, level: 0 }]) ).resolves.toStrictEqual([ Poseidon.hash([Field(20), Field(20), Field(Field.ORDER - 1n)]).toBigInt(), ]); @@ -297,8 +298,8 @@ describe("cached linked merkle store", () => { it("should cache correctly", async () => { expect.assertions(15); - const cache2 = new SyncCachedLinkedMerkleTreeStore(cache1); - const tree2 = new LinkedMerkleTree(cache2); + const cache2 = new SyncCachedLinkedLeafStore(cache1); + const tree2 = new LinkedMerkleTree(cache2.treeStore, cache2); await cache2.preloadKeys([5n]); const leaf1 = tree2.getLeaf(5n); @@ -306,7 +307,7 @@ describe("cached linked merkle store", () => { expectDefined(leaf1); expectDefined(storedLeaf1); await expect( - mainStore.getNodesAsync([{ key: storedLeaf1.index, level: 0 }]) + mainStore.treeStore.getNodesAsync([{ key: storedLeaf1.index, level: 0 }]) ).resolves.toStrictEqual([ Poseidon.hash([leaf1.value, leaf1.path, leaf1.nextPath]).toBigInt(), ]); @@ -317,11 +318,11 @@ describe("cached linked merkle store", () => { const storedLeaf2 = cache2.getLeaf(10n); expectDefined(leaf2); expectDefined(storedLeaf2); - expect(tree2.getNode(0, storedLeaf2.index).toBigInt()).toBe( + expect(tree2.tree.getNode(0, storedLeaf2.index).toBigInt()).toBe( Poseidon.hash([leaf2.value, leaf2.path, leaf2.nextPath]).toBigInt() ); - const witness = tree2.getWitness(5n); + const witness = tree2.getReadWitness(5n); // We check tree1 and tree2 have same hash roots. // The witness is from tree2, which comes from cache2, @@ -344,7 +345,7 @@ describe("cached linked merkle store", () => { .toString() ).not.toBe(tree1.getRoot().toString()); - const witness2 = tree1.getWitness(10n); + const witness2 = tree1.getReadWitness(10n); expect( witness2.merkleWitness @@ -373,29 +374,31 @@ describe("cached linked merkle store", () => { expectDefined(leaf15); expectDefined(storedLeaf15); expect(tree1.getRoot().toString()).toBe(tree2.getRoot().toString()); - expect(tree1.getNode(0, storedLeaf15.index).toString()).toBe( + expect(tree1.tree.getNode(0, storedLeaf15.index).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); + const cachedStore = await CachedLinkedLeafStore.new(mainStore); await cachedStore.preloadKey(15n); - expect(new LinkedMerkleTree(cachedStore).getRoot().toString()).toBe( - tree2.getRoot().toString() - ); + expect( + new LinkedMerkleTree(cachedStore.treeStore, cachedStore) + .getRoot() + .toString() + ).toBe(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); + const mStore = new InMemoryAsyncLinkedLeafStore(); + const mCache = await CachedLinkedLeafStore.new(mStore); + const mCache2 = new SyncCachedLinkedLeafStore(mCache); + const treeCache1 = new LinkedMerkleTree(mCache.treeStore, mCache); + const treeCache2 = new LinkedMerkleTree(mCache2.treeStore, mCache2); await mCache.preloadKeys([5n]); treeCache1.setLeaf(10n, 10n); @@ -439,25 +442,25 @@ describe("cached linked merkle store", () => { expectDefined(storedLeaf0); await expect( - mCache.getNodesAsync([{ key: storedLeaf0.index, level: 0 }]) + mCache.treeStore.getNodesAsync([{ key: storedLeaf0.index, level: 0 }]) ).resolves.toStrictEqual([ Poseidon.hash([Field(0), Field(0), Field(7)]).toBigInt(), ]); expectDefined(storedLeaf7); await expect( - mCache.getNodesAsync([{ key: storedLeaf7.index, level: 0 }]) + mCache.treeStore.getNodesAsync([{ key: storedLeaf7.index, level: 0 }]) ).resolves.toStrictEqual([ Poseidon.hash([Field(7), Field(7), Field(10)]).toBigInt(), ]); expectDefined(storedLeaf10); await expect( - mCache.getNodesAsync([{ key: storedLeaf10.index, level: 0 }]) + mCache.treeStore.getNodesAsync([{ key: storedLeaf10.index, level: 0 }]) ).resolves.toStrictEqual([ Poseidon.hash([Field(10), Field(10), Field(20)]).toBigInt(), ]); expectDefined(storedLeaf20); await expect( - mCache.getNodesAsync([{ key: storedLeaf20.index, level: 0 }]) + mCache.treeStore.getNodesAsync([{ key: storedLeaf20.index, level: 0 }]) ).resolves.toStrictEqual([ Poseidon.hash([Field(20), Field(20), Field(Field.ORDER - 1n)]).toBigInt(), ]); From 0a9863693070a445509728c4210e0fce1809ac9b Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 11 Apr 2025 14:57:06 +0200 Subject: [PATCH 118/128] Disabled no-else-return eslint rule --- .eslintrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintrc b/.eslintrc index 91bd9533d..2db6084a1 100644 --- a/.eslintrc +++ b/.eslintrc @@ -91,7 +91,8 @@ "sonarjs/no-duplicate-string": "off", // Handled by prettier "@typescript-eslint/indent": ["off"], - "@typescript-eslint/no-floating-promises": "error" + "@typescript-eslint/no-floating-promises": "error", + "no-else-return": "off" }, "overrides": [ From 2f3a29db9c2a7659dcc203a09ac7dbddf16f18c5 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 11 Apr 2025 15:06:08 +0200 Subject: [PATCH 119/128] Adapted sdk to new lmt types --- .../src/graphql/GraphqlQueryTransportModule.ts | 8 ++++---- .../sdk/src/query/StateServiceQueryModule.ts | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/sdk/src/graphql/GraphqlQueryTransportModule.ts b/packages/sdk/src/graphql/GraphqlQueryTransportModule.ts index c69122f74..4bcb032ed 100644 --- a/packages/sdk/src/graphql/GraphqlQueryTransportModule.ts +++ b/packages/sdk/src/graphql/GraphqlQueryTransportModule.ts @@ -2,11 +2,11 @@ import { QueryTransportModule } from "@proto-kit/sequencer"; import { Field } from "o1js"; import { inject, injectable } from "tsyringe"; import { gql } from "@urql/core"; -import { LinkedLeafAndMerkleWitness } from "@proto-kit/common"; import { AppChainModule } from "../appChain/AppChainModule"; import { GraphqlClient } from "./GraphqlClient"; +import { LinkedMerkleTreeReadWitness } from "@proto-kit/common"; function assertStringArray(array: any): asserts array is string[] { if ( @@ -64,7 +64,7 @@ export class GraphqlQueryTransportModule public async merkleWitness( key: Field - ): Promise { + ): Promise { const query = gql` query Witness($path: String!) { witness(path: $path) { @@ -104,8 +104,8 @@ export class GraphqlQueryTransportModule assertStringArray(witnessJson.merkleWitness.siblings); assertBooleanArray(witnessJson.merkleWitness.isLefts); - return new LinkedLeafAndMerkleWitness( - LinkedLeafAndMerkleWitness.fromJSON({ + return new LinkedMerkleTreeReadWitness( + LinkedMerkleTreeReadWitness.fromJSON({ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment leaf: witnessJson.leaf, merkleWitness: { diff --git a/packages/sdk/src/query/StateServiceQueryModule.ts b/packages/sdk/src/query/StateServiceQueryModule.ts index deb4194e8..d36a69593 100644 --- a/packages/sdk/src/query/StateServiceQueryModule.ts +++ b/packages/sdk/src/query/StateServiceQueryModule.ts @@ -3,15 +3,15 @@ import { QueryTransportModule, Sequencer, SequencerModulesRecord, + CachedLinkedLeafStore, + AsyncLinkedLeafStore, } from "@proto-kit/sequencer"; import { Field } from "o1js"; import { inject, injectable } from "tsyringe"; import { - LinkedLeafAndMerkleWitness, + LinkedMerkleTreeReadWitness, 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"; @@ -32,7 +32,7 @@ export class StateServiceQueryModule ); } - public get treeStore(): AsyncLinkedMerkleTreeStore { + public get treeStore(): AsyncLinkedLeafStore { return this.sequencer.dependencyContainer.resolve("AsyncLinkedMerkleStore"); } @@ -42,12 +42,12 @@ export class StateServiceQueryModule public async merkleWitness( path: Field - ): Promise { - const syncStore = await CachedLinkedMerkleTreeStore.new(this.treeStore); + ): Promise { + const syncStore = await CachedLinkedLeafStore.new(this.treeStore); await syncStore.preloadKey(path.toBigInt()); - const tree = new LinkedMerkleTree(syncStore); + const tree = new LinkedMerkleTree(syncStore.treeStore, syncStore); - return tree.getWitness(path.toBigInt()); + return tree.getReadWitness(path.toBigInt()); } } From 0356520ec2c1252735f1991c2b907cdf9d4ef47d Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 11 Apr 2025 15:06:12 +0200 Subject: [PATCH 120/128] Adapted api to new lmt types --- .../modules/LinkedMerkleWitnessResolver.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/api/src/graphql/modules/LinkedMerkleWitnessResolver.ts b/packages/api/src/graphql/modules/LinkedMerkleWitnessResolver.ts index 1538506f8..2a016dfc0 100644 --- a/packages/api/src/graphql/modules/LinkedMerkleWitnessResolver.ts +++ b/packages/api/src/graphql/modules/LinkedMerkleWitnessResolver.ts @@ -1,11 +1,13 @@ import { Arg, Field, ObjectType, Query } from "type-graphql"; import { inject } from "tsyringe"; import { - LinkedLeafAndMerkleWitness, LinkedMerkleTree, + LinkedMerkleTreeReadWitness, } 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 { + AsyncLinkedLeafStore, + CachedLinkedLeafStore, +} from "@proto-kit/sequencer"; import { GraphqlModule, graphqlModule } from "../GraphqlModule"; @@ -14,7 +16,7 @@ import { LeafDTO } from "./LeafResolver"; @ObjectType() export class LinkedMerkleWitnessDTO { - public static fromServiceLayerObject(witness: LinkedLeafAndMerkleWitness) { + public static fromServiceLayerObject(witness: LinkedMerkleTreeReadWitness) { const { leaf, merkleWitness } = witness; const leafDTO = LeafDTO.fromServiceLayerModel(leaf); const witnessDTO = MerkleWitnessDTO.fromServiceLayerObject(merkleWitness); @@ -40,7 +42,7 @@ export class LinkedMerkleWitnessDTO { export class LinkedMerkleWitnessResolver extends GraphqlModule { public constructor( @inject("AsyncMerkleStore") - private readonly treeStore: AsyncLinkedMerkleTreeStore + private readonly treeStore: AsyncLinkedLeafStore ) { super(); } @@ -50,12 +52,12 @@ export class LinkedMerkleWitnessResolver extends GraphqlModule { "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 syncStore = await CachedLinkedLeafStore.new(this.treeStore); - const tree = new LinkedMerkleTree(syncStore); + const tree = new LinkedMerkleTree(syncStore.treeStore, syncStore); await syncStore.preloadKey(BigInt(path)); - const witness = tree.getWitness(BigInt(path)); + const witness = tree.getReadWitness(BigInt(path)); return LinkedMerkleWitnessDTO.fromServiceLayerObject(witness); } From 20968300ea98be34ba70b47c16f1d61d38997c36 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 11 Apr 2025 19:15:43 +0200 Subject: [PATCH 121/128] Implemented batching algorithm for CachedLinkedLeafStore preloading --- packages/common/src/utils.ts | 9 ++ .../src/state/async/AsyncLinkedLeafStore.ts | 4 +- .../src/state/lmt/CachedLinkedLeafStore.ts | 149 ++++++++++++------ .../inmemory/InMemoryAsyncLinkedLeafStore.ts | 11 +- 4 files changed, 119 insertions(+), 54 deletions(-) diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts index e412ec5b3..0576aacb5 100644 --- a/packages/common/src/utils.ts +++ b/packages/common/src/utils.ts @@ -286,3 +286,12 @@ export namespace BigIntMath { return args.reduce((m, e) => (e > m ? e : m)); } } + +export function assertDefined( + t: T | undefined, + msg?: string +): asserts t is T { + if (t === undefined) { + throw new Error(msg ?? "Value is undefined"); + } +} diff --git a/packages/sequencer/src/state/async/AsyncLinkedLeafStore.ts b/packages/sequencer/src/state/async/AsyncLinkedLeafStore.ts index d51ac129f..2153e0de9 100644 --- a/packages/sequencer/src/state/async/AsyncLinkedLeafStore.ts +++ b/packages/sequencer/src/state/async/AsyncLinkedLeafStore.ts @@ -15,5 +15,7 @@ export interface AsyncLinkedLeafStore { getMaximumIndexAsync: () => Promise; - getLeafLessOrEqualAsync: (path: bigint) => Promise; + getLeavesLessOrEqualAsync: ( + path: bigint[] + ) => Promise<(StoredLeaf | undefined)[]>; } diff --git a/packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts b/packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts index ebc323166..d7677d571 100644 --- a/packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts +++ b/packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts @@ -3,15 +3,20 @@ import { LinkedLeaf, mapSequential, LinkedLeafStore, + assertDefined, + StoredLeaf, + filterNonUndefined, } from "@proto-kit/common"; +// eslint-disable-next-line import/no-extraneous-dependencies +import zip from "lodash/zip"; +import groupBy from "lodash/groupBy"; import { AsyncLinkedLeafStore } from "../async/AsyncLinkedLeafStore"; - import { CachedMerkleTreeStore } from "../merkle/CachedMerkleTreeStore"; export class CachedLinkedLeafStore implements LinkedLeafStore { private writeCache: { - [key: string]: { leaf: LinkedLeaf; index: bigint }; + [key: string]: StoredLeaf; } = {}; private readonly leafStore = new InMemoryLinkedLeafStore(); @@ -43,9 +48,7 @@ export class CachedLinkedLeafStore implements LinkedLeafStore { // 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<{ leaf: LinkedLeaf; index: bigint } | undefined>( - paths.length - ).fill(undefined); + const results = Array(paths.length).fill(undefined); const toFetch: bigint[] = []; @@ -76,7 +79,7 @@ export class CachedLinkedLeafStore implements LinkedLeafStore { } // This is just used in the mergeIntoParent - public writeLeaves(leaves: { leaf: LinkedLeaf; index: bigint }[]) { + public writeLeaves(leaves: StoredLeaf[]) { leaves.forEach(({ leaf, index }) => { this.setLeaf(index, leaf); }); @@ -84,7 +87,7 @@ export class CachedLinkedLeafStore implements LinkedLeafStore { // This gets the leaves from the cache. // Only used in mergeIntoParent - public getWrittenLeaves(): { leaf: LinkedLeaf; index: bigint }[] { + public getWrittenLeaves(): StoredLeaf[] { return Object.values(this.writeCache); } @@ -107,66 +110,122 @@ export class CachedLinkedLeafStore implements LinkedLeafStore { } } + async retrieveBatched( + inputs: Input[], + cache: (input: Input) => Element | undefined, + parent: (inputs: Input[]) => Promise<(Element | undefined)[]> + ) { + // The reason I built it using this weird closure-centric algorithm is that doing it + // purely functional would require a lot more array operations than this + const results: (Element | undefined)[] = Array.from({ + length: inputs.length, + }); + + const toFetchRemotely = inputs + .map((input, i) => { + const localResult = cache(input); + if (localResult !== undefined) { + results[i] = localResult; + return undefined; + } else { + return { path: input, index: i }; + } + }) + .filter(filterNonUndefined); + + let remoteResults: (Element | undefined)[] = []; + if (toFetchRemotely.length > 0) { + remoteResults = await parent(toFetchRemotely.map((value) => value.path)); + } + + zip(toFetchRemotely, remoteResults).forEach(([query, result]) => { + assertDefined(query); + + results[query.index] = result; + }); + + return results; + } + // 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 preloadKeyInternal( - path: bigint - ): Promise<{ requiredTreeIndizes: bigint[] }> { - const leaf = (await this.getLeavesAsync([path]))[0]; + public async preloadKeysInternal(paths: bigint[]): Promise { + const leaves = await this.getLeavesAsync(paths); + + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const zipped = zip(paths, leaves) as [bigint, StoredLeaf | undefined][]; + const groupedOps = groupBy(zipped, ([, leaf]) => + leaf !== undefined ? "update" : "insert" + ); - if (leaf !== undefined) { - // Update case, this leaf is the only one we need - this.leafStore.setLeaf(leaf.index, leaf.leaf); + let treeIndizesToFetch: bigint[] = []; - return { requiredTreeIndizes: [leaf.index] }; - } else { - // Insert case, this leaf doesn't yet exist - we need to fetch the previous one + if (groupedOps.update !== undefined) { + // Preload updates + const treeUpdates = groupedOps.update.map(([path, leaf]) => { + assertDefined(leaf); + // Update case, this leaf is the only one we need + this.leafStore.setLeaf(leaf.index, leaf.leaf); + + return leaf.index; + }); + treeIndizesToFetch.push(...treeUpdates); + } + + if (groupedOps.insert !== undefined) { + // Insert case, this leaf doesn't yet exist - we need to fetch the previous one // Calling getLeafLessOrEqual assures that it is actually the leaf we want // (i.e. pointing over our path) - // TODO Rename getLeafLessOrEqual - const previousLeaf = - this.leafStore.getLeafLessOrEqual(path) ?? - (await this.parent.getLeafLessOrEqualAsync(path)); - - if (previousLeaf === undefined) { - // throw Error("Previous Leaf should never be empty"); + const previousLeaves = await this.retrieveBatched( + groupedOps.insert.map(([path]) => path), + this.leafStore.getLeafLessOrEqual.bind(this.leafStore), + this.parent.getLeavesLessOrEqualAsync.bind(this.parent) + ); + + // This is a check that all previous leaves have been found, with the + // one exception being when the tree is empty (see below) + const anyUndefined = + previousLeaves.findIndex((x) => x === undefined) > -1; + if (anyUndefined) { // This only happens when the store is empty, because in this case, the tree // initializes the 0-leaf, but this only happens after preloading. const [zeroLeaf] = await this.parent.getLeavesAsync([0n]); if (zeroLeaf !== undefined) { throw Error("Previous Leaf should never be empty"); } - return { - requiredTreeIndizes: [], - }; } - this.leafStore.setLeaf(previousLeaf.index, previousLeaf.leaf); - - const maximumIndex = this.leafStore.getMaximumIndex(); - - if (maximumIndex === undefined) { - throw Error("Maximum index should be defined in parent."); - } - - return { requiredTreeIndizes: [previousLeaf.index, maximumIndex + 1n] }; + const definedPreviousLeaves = previousLeaves.filter(filterNonUndefined); + + definedPreviousLeaves.forEach(({ index, leaf }) => + this.leafStore.setLeaf(index, leaf) + ); + treeIndizesToFetch.push( + ...definedPreviousLeaves.map(({ index }) => index) + ); + + // Additionally preload the next empty tree index. + // This is enough, because we know that all subsequent empty tree indizes + // (in case there are multiple inserts) will be bigger than that index. + // In that case, everything will be either contained in the siblings of this index + // or be zero. So in either case, we don't have to preload more than we do here. + const maximumIndex = this.leafStore.getMaximumIndex() ?? -1n; + // if (maximumIndex === undefined) { + // throw Error("Maximum index should be defined in parent."); + // } + treeIndizesToFetch.push(maximumIndex + 1n); } + + await this.treeCache.preloadKeys(treeIndizesToFetch); } public async preloadKey(path: bigint) { - const { requiredTreeIndizes } = await this.preloadKeyInternal(path); - await this.treeCache.preloadKeys(requiredTreeIndizes); + await this.preloadKeysInternal([path]); } public async preloadKeys(paths: bigint[]): Promise { - const results = await mapSequential(paths, (x) => - this.preloadKeyInternal(x) - ); - const treeIndizes = results.flatMap( - ({ requiredTreeIndizes }) => requiredTreeIndizes - ); - await this.treeCache.preloadKeys(treeIndizes); + await this.preloadKeysInternal(paths); } // This merges the cache into the parent tree and resets the cache, but not the diff --git a/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedLeafStore.ts b/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedLeafStore.ts index 657719dd3..db4834da8 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedLeafStore.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedLeafStore.ts @@ -1,9 +1,4 @@ -import { - initialLinkedLeaf, - InMemoryLinkedLeafStore, - LinkedLeaf, - noop, -} from "@proto-kit/common"; +import { InMemoryLinkedLeafStore, LinkedLeaf, noop } from "@proto-kit/common"; import { AsyncLinkedLeafStore } from "../../state/async/AsyncLinkedLeafStore"; @@ -53,8 +48,8 @@ export class InMemoryAsyncLinkedLeafStore implements AsyncLinkedLeafStore { return this.leafStore.getMaximumIndex(); } - public async getLeafLessOrEqualAsync(path: bigint) { - return this.leafStore.getLeafLessOrEqual(path); + public async getLeavesLessOrEqualAsync(paths: bigint[]) { + return paths.map((path) => this.leafStore.getLeafLessOrEqual(path)); } public setLeaf(index: bigint, value: LinkedLeaf) { From 09676806554e376a02eebd189947b608b2fd1dda Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 11 Apr 2025 19:16:25 +0200 Subject: [PATCH 122/128] Implemented PrismaLinkedLeafStore --- .../migration.sql | 25 +++ packages/persistance/prisma/schema.prisma | 17 ++ .../src/PrismaDatabaseConnection.ts | 4 +- .../persistance/src/PrismaRedisDatabase.ts | 25 ++- packages/persistance/src/index.ts | 2 +- .../services/prisma/PrismaLinkedLeafStore.ts | 163 ++++++++++++++++++ .../src/services/prisma/PrismaStateService.ts | 2 +- .../redis/RedisLinkedMerkleTreeStore.ts | 83 --------- 8 files changed, 234 insertions(+), 87 deletions(-) create mode 100644 packages/persistance/prisma/migrations/20250411162042_linked_leaves/migration.sql create mode 100644 packages/persistance/src/services/prisma/PrismaLinkedLeafStore.ts delete mode 100644 packages/persistance/src/services/redis/RedisLinkedMerkleTreeStore.ts diff --git a/packages/persistance/prisma/migrations/20250411162042_linked_leaves/migration.sql b/packages/persistance/prisma/migrations/20250411162042_linked_leaves/migration.sql new file mode 100644 index 000000000..3893fbe5e --- /dev/null +++ b/packages/persistance/prisma/migrations/20250411162042_linked_leaves/migration.sql @@ -0,0 +1,25 @@ +-- CreateTable +CREATE TABLE "LinkedLeaf" ( + "index" DECIMAL(78,0) NOT NULL, + "path" DECIMAL(78,0) NOT NULL, + "value" DECIMAL(78,0) NOT NULL, + "nextPath" DECIMAL(78,0) NOT NULL, + "mask" TEXT NOT NULL, + + CONSTRAINT "LinkedLeaf_pkey" PRIMARY KEY ("index","mask") +); + +-- CreateIndex +CREATE INDEX "LinkedLeaf_index_idx" ON "LinkedLeaf"("index"); + +-- CreateIndex +CREATE INDEX "LinkedLeaf_path_idx" ON "LinkedLeaf"("path"); + +-- CreateIndex +CREATE INDEX "LinkedLeaf_nextPath_idx" ON "LinkedLeaf"("nextPath"); + +-- CreateIndex +CREATE INDEX "State_path_idx" ON "State" USING HASH ("path"); + +-- CreateIndex +CREATE INDEX "State_mask_path_idx" ON "State"("mask", "path"); diff --git a/packages/persistance/prisma/schema.prisma b/packages/persistance/prisma/schema.prisma index 58c214477..17aa71d5f 100644 --- a/packages/persistance/prisma/schema.prisma +++ b/packages/persistance/prisma/schema.prisma @@ -19,6 +19,23 @@ model State { mask String @db.VarChar(256) @@id([path, mask]) + // We only need equality for the path + @@index([path], type: Hash) + @@index([mask, path], type: BTree) +} + +model LinkedLeaf { + index Decimal @db.Decimal(78, 0) + path Decimal @db.Decimal(78, 0) + value Decimal @db.Decimal(78, 0) + nextPath Decimal @db.Decimal(78, 0) + mask String + + @@id([index, mask]) + // We need a btree here, because we need to support queries with < and > for finding previous lmt leaves for inserting + @@index([index], type: BTree) + @@index([path], type: BTree) + @@index([nextPath], type: BTree) } model Transaction { diff --git a/packages/persistance/src/PrismaDatabaseConnection.ts b/packages/persistance/src/PrismaDatabaseConnection.ts index 234f7977a..12d3422ea 100644 --- a/packages/persistance/src/PrismaDatabaseConnection.ts +++ b/packages/persistance/src/PrismaDatabaseConnection.ts @@ -54,7 +54,7 @@ export class PrismaDatabaseConnection public dependencies(): OmitKeys< StorageDependencyMinimumDependencies, - "asyncMerkleStore" | "blockTreeStore" | "unprovenMerkleStore" + "blockTreeStore" | "asyncLinkedLeafStore" | "unprovenLinkedLeafStore" > { return { asyncStateService: { @@ -95,6 +95,7 @@ export class PrismaDatabaseConnection "Settlement", "IncomingMessageBatch", "IncomingMessageBatchTransaction", + "LinkedLeaf", ]; await this.prismaClient.$transaction( @@ -133,6 +134,7 @@ export class PrismaDatabaseConnection url, }, }, + log: ["query", "info", "warn", "error"], }); } else { this.initializedClient = new PrismaClient(); diff --git a/packages/persistance/src/PrismaRedisDatabase.ts b/packages/persistance/src/PrismaRedisDatabase.ts index 92a68d0b5..c13b2ff91 100644 --- a/packages/persistance/src/PrismaRedisDatabase.ts +++ b/packages/persistance/src/PrismaRedisDatabase.ts @@ -22,6 +22,7 @@ import { RedisConnectionModule, RedisTransaction, } from "./RedisConnection"; +import { PrismaLinkedLeafStore } from "./services/prisma/PrismaLinkedLeafStore"; export interface PrismaRedisCombinedConfig { prisma: PrismaDatabaseConfig; @@ -38,7 +39,7 @@ export class PrismaRedisDatabase public redis: RedisConnectionModule; - public constructor(@inject("Tracer") tracer: Tracer) { + public constructor(@inject("Tracer") private readonly tracer: Tracer) { super(); this.prisma = new PrismaDatabaseConnection(tracer); this.redis = new RedisConnectionModule(tracer); @@ -66,6 +67,28 @@ export class PrismaRedisDatabase return { ...this.prisma.dependencies(), ...this.redis.dependencies(), + + asyncLinkedLeafStore: { + useFactory: () => { + return new PrismaLinkedLeafStore( + this.prisma, + this.redis, + this.tracer, + "batch" + ); + }, + }, + + unprovenLinkedLeafStore: { + useFactory: () => { + return new PrismaLinkedLeafStore( + this.prisma, + this.redis, + this.tracer, + "block" + ); + }, + }, }; } diff --git a/packages/persistance/src/index.ts b/packages/persistance/src/index.ts index 51ef2c6f9..788de3d57 100644 --- a/packages/persistance/src/index.ts +++ b/packages/persistance/src/index.ts @@ -15,4 +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"; +export * from "./services/prisma/PrismaLinkedLeafStore"; diff --git a/packages/persistance/src/services/prisma/PrismaLinkedLeafStore.ts b/packages/persistance/src/services/prisma/PrismaLinkedLeafStore.ts new file mode 100644 index 000000000..e48382afd --- /dev/null +++ b/packages/persistance/src/services/prisma/PrismaLinkedLeafStore.ts @@ -0,0 +1,163 @@ +import { + LinkedLeaf, + log, + mapSequential, + noop, + StoredLeaf, +} from "@proto-kit/common"; +import { AsyncLinkedLeafStore } from "@proto-kit/sequencer/dist/state/async/AsyncLinkedLeafStore"; + +import { PrismaConnection } from "../../PrismaDatabaseConnection"; +import { Decimal } from "./PrismaStateService"; +import { inject, injectable } from "tsyringe"; +import { Tracer } from "@proto-kit/sequencer"; +import { RedisMerkleTreeStore } from "../redis/RedisMerkleTreeStore"; +import { RedisConnection } from "../../RedisConnection"; +import { Prisma } from "@prisma/client"; + +@injectable() +export class PrismaLinkedLeafStore implements AsyncLinkedLeafStore { + private cache: StoredLeaf[] = []; + + private redisMerkleStore: RedisMerkleTreeStore; + + public constructor( + private readonly connection: PrismaConnection, + redisConnection: RedisConnection, + tracer: Tracer, + private readonly mask: string = "base" + ) { + this.redisMerkleStore = new RedisMerkleTreeStore( + redisConnection, + tracer, + mask + ); + } + + public get treeStore() { + return this.redisMerkleStore; + } + + private assertCacheEmpty() { + if (this.cache.length > 0) { + throw new Error("For this operation, the cache must be empty"); + } + } + + public async openTransaction(): Promise { + noop(); + } + + public async commit(): Promise { + if (this.cache.length > 0) { + const data = this.cache.map((entry) => ({ + path: entry.leaf.path.toString(), + value: entry.leaf.value.toString(), + nextPath: entry.leaf.nextPath.toString(), + index: entry.index.toString(), + mask: this.mask, + })); + + await this.connection.prismaClient.linkedLeaf.deleteMany({ + where: { + path: { + in: data.map((entry) => entry.path), + }, + mask: this.mask, + }, + }); + + await this.connection.prismaClient.linkedLeaf.createMany({ + data, + skipDuplicates: false, + }); + + this.cache = []; + } + } + + public writeLeaves(leaves: StoredLeaf[]) { + this.cache = this.cache.concat(leaves); + } + + public async getLeavesAsync(paths: bigint[]) { + this.assertCacheEmpty(); + + const pathsDecimal = paths.map((path) => new Decimal(path.toString(10))); + const records = await this.connection.prismaClient.linkedLeaf.findMany({ + where: { + path: { + in: pathsDecimal, + }, + mask: this.mask, + }, + }); + + return records.map((record) => { + return { + index: BigInt(record.index.toFixed()), + leaf: { + path: BigInt(record.path.toFixed()), + value: BigInt(record.value.toFixed()), + nextPath: BigInt(record.nextPath.toFixed()), + }, + }; + }); + } + + public async getMaximumIndexAsync() { + this.assertCacheEmpty(); + + const result = await this.connection.prismaClient.linkedLeaf.aggregate({ + _max: { + index: true, + }, + }); + const maximumIndexString = result._max.index?.toFixed(); + return maximumIndexString !== undefined + ? BigInt(maximumIndexString) + : undefined; + } + + public async getLeavesLessOrEqualAsync(paths: bigint[]) { + this.assertCacheEmpty(); + + const pathsDecimals = paths.map((path) => new Decimal(path.toString(10))); + type LinkedLeafQueryResult = { + index: Prisma.Decimal; + path: Prisma.Decimal; + nextPath: Prisma.Decimal; + value: Prisma.Decimal; + mask: string; + }; + const result = await this.connection.prismaClient.$queryRaw< + ({ + query_path: Prisma.Decimal; + } & LinkedLeafQueryResult)[] + >` + SELECT * FROM "LinkedLeaf" l + RIGHT JOIN (SELECT unnest(ARRAY[${pathsDecimals}]) as newpath) f + ON l.path < f.newpath AND l."nextPath" > f.newpath + WHERE l.mask = '1' + `; + + const map: Record = Object.fromEntries( + result.map((obj) => [obj.query_path.toFixed(), obj]) + ); + + return paths.map((path) => { + const record = map[path.toString()]; + if (record !== undefined) { + return { + index: BigInt(record.index.toFixed()), + leaf: { + path: BigInt(record.index.toFixed()), + value: BigInt(record.value.toFixed()), + nextPath: BigInt(record.nextPath.toFixed()), + }, + }; + } + return undefined; + }); + } +} diff --git a/packages/persistance/src/services/prisma/PrismaStateService.ts b/packages/persistance/src/services/prisma/PrismaStateService.ts index 73881127d..286a2e327 100644 --- a/packages/persistance/src/services/prisma/PrismaStateService.ts +++ b/packages/persistance/src/services/prisma/PrismaStateService.ts @@ -13,7 +13,7 @@ import type { PrismaConnection } from "../../PrismaDatabaseConnection"; // We need to create a correctly configured Decimal constructor // with our parameters -const Decimal = Prisma.Decimal.clone({ +export const Decimal = Prisma.Decimal.clone({ precision: 78, }); diff --git a/packages/persistance/src/services/redis/RedisLinkedMerkleTreeStore.ts b/packages/persistance/src/services/redis/RedisLinkedMerkleTreeStore.ts deleted file mode 100644 index 7616a546a..000000000 --- a/packages/persistance/src/services/redis/RedisLinkedMerkleTreeStore.ts +++ /dev/null @@ -1,83 +0,0 @@ -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: { leaf: LinkedLeaf; index: bigint }[]) {} - - public getLeavesAsync(paths: bigint[]) { - return Promise.resolve([undefined]); - } - - public getMaximumIndexAsync() { - return Promise.resolve(0n); - } - - public getLeafLessOrEqualAsync(path: bigint) { - return Promise.resolve({ - leaf: { - value: 0n, - path: 0n, - nextPath: 0n, - }, - index: 0n, - }); - } -} From 0d79d77167b13ca29e20cd7142d6103448de88c6 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 11 Apr 2025 20:09:07 +0200 Subject: [PATCH 123/128] Fixed bug where maximum index wasn't filtering mask --- .../src/services/prisma/PrismaLinkedLeafStore.ts | 5 ++++- .../test-integration/PrismaBlockProduction.test.ts | 8 +++++--- packages/persistance/test-integration/utils.ts | 5 +++++ .../protocol/production/tracing/BatchTracingService.ts | 4 +++- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/persistance/src/services/prisma/PrismaLinkedLeafStore.ts b/packages/persistance/src/services/prisma/PrismaLinkedLeafStore.ts index e48382afd..524f5a112 100644 --- a/packages/persistance/src/services/prisma/PrismaLinkedLeafStore.ts +++ b/packages/persistance/src/services/prisma/PrismaLinkedLeafStore.ts @@ -109,6 +109,9 @@ export class PrismaLinkedLeafStore implements AsyncLinkedLeafStore { this.assertCacheEmpty(); const result = await this.connection.prismaClient.linkedLeaf.aggregate({ + where: { + mask: this.mask, + }, _max: { index: true, }, @@ -138,7 +141,7 @@ export class PrismaLinkedLeafStore implements AsyncLinkedLeafStore { SELECT * FROM "LinkedLeaf" l RIGHT JOIN (SELECT unnest(ARRAY[${pathsDecimals}]) as newpath) f ON l.path < f.newpath AND l."nextPath" > f.newpath - WHERE l.mask = '1' + WHERE l.mask = ${this.mask} `; const map: Record = Object.fromEntries( diff --git a/packages/persistance/test-integration/PrismaBlockProduction.test.ts b/packages/persistance/test-integration/PrismaBlockProduction.test.ts index 61eb02d3c..fc7948421 100644 --- a/packages/persistance/test-integration/PrismaBlockProduction.test.ts +++ b/packages/persistance/test-integration/PrismaBlockProduction.test.ts @@ -1,6 +1,6 @@ import "reflect-metadata"; import { afterAll, beforeAll, describe, expect } from "@jest/globals"; -import { expectDefined } from "@proto-kit/common"; +import { expectDefined, log } from "@proto-kit/common"; import { BalancesKey, TokenId } from "@proto-kit/library"; import { NetworkState } from "@proto-kit/protocol"; import { AppChainTransaction } from "@proto-kit/sdk"; @@ -26,6 +26,8 @@ describe("prisma integration", () => { const sender = PrivateKey.random(); let senderNonce = 0; + log.setLevel("TRACE"); + const setup = async () => { const { prismaConfig, redisConfig } = IntegrationTestDBConfig; appChain = createPrismaAppchain(prismaConfig, redisConfig); @@ -39,8 +41,8 @@ describe("prisma integration", () => { await appChain.start(false, container.createChildContainer()); const db = appChain.sequencer.resolve("Database"); - await db.prisma.pruneDatabase(); - await db.redis.pruneDatabase(); + // await db.prisma.pruneDatabase(); + // await db.redis.pruneDatabase(); senderNonce = 0; }; diff --git a/packages/persistance/test-integration/utils.ts b/packages/persistance/test-integration/utils.ts index e42b336e8..a23be616c 100644 --- a/packages/persistance/test-integration/utils.ts +++ b/packages/persistance/test-integration/utils.ts @@ -32,6 +32,7 @@ import { BlockProducerModule, VanillaTaskWorkerModules, SequencerStartupModule, + DatabasePruneModule, } from "@proto-kit/sequencer"; import { Bool, PrivateKey, PublicKey, Struct } from "o1js"; @@ -103,6 +104,7 @@ export function createPrismaAppchain( }), Sequencer: Sequencer.from({ modules: { + DatabasePruneModule, Database: PrismaRedisDatabase, Mempool: PrivateMempool, @@ -153,6 +155,9 @@ export function createPrismaAppchain( simulatedDuration: 0, }, SequencerStartupModule: {}, + DatabasePruneModule: { + pruneOnStartup: true, + }, }, Signer: { signer: PrivateKey.random(), diff --git a/packages/sequencer/src/protocol/production/tracing/BatchTracingService.ts b/packages/sequencer/src/protocol/production/tracing/BatchTracingService.ts index e099ead0b..f54d52e6f 100644 --- a/packages/sequencer/src/protocol/production/tracing/BatchTracingService.ts +++ b/packages/sequencer/src/protocol/production/tracing/BatchTracingService.ts @@ -1,4 +1,4 @@ -import { yieldSequential } from "@proto-kit/common"; +import { log, yieldSequential } from "@proto-kit/common"; import { AppliedBatchHashList, MinaActionsHashList, @@ -51,6 +51,8 @@ export class BatchTracingService { @trace("batch.trace.blocks") public async traceBlocks(blocks: BlockWithResult[]) { + log.debug(`Tracing ${blocks.length} blocks...`); + const batchState = this.createBatchState(blocks[0]); // Trace blocks From 8a2e1774fde7752c58b39b7a096d8ed21a3bd8a5 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 11 Apr 2025 20:38:55 +0200 Subject: [PATCH 124/128] Fix linting --- .../common/src/trees/lmt/LinkedLinkedStore.ts | 4 -- .../common/src/trees/lmt/LinkedMerkleTree.ts | 1 - .../trees/lmt/LinkedMerkleTreeCircuitOps.ts | 24 +------- .../trees/LinkedMerkleTreeCircuitOps.test.ts | 56 ++++++------------- .../src/PrismaDatabaseConnection.ts | 5 +- .../services/prisma/PrismaLinkedLeafStore.ts | 20 +++---- .../PrismaBlockProduction.test.ts | 4 -- .../test-integration/SequencerRestart.test.ts | 11 +++- .../src/model/StateTransitionProvableBatch.ts | 6 +- .../contracts/SettlementSmartContract.ts | 1 - .../graphql/GraphqlQueryTransportModule.ts | 2 +- .../production/BatchProducerModule.ts | 6 +- .../sequencing/BlockProducerModule.ts | 2 +- .../production/tasks/StateTransitionTask.ts | 1 - .../StateTransitionParametersSerializer.ts | 5 +- .../src/state/lmt/CachedLinkedLeafStore.ts | 7 +-- .../state/merkle/SyncCachedLinkedLeafStore.ts | 2 +- .../state/merkle/SyncCachedMerkleTreeStore.ts | 6 +- packages/sequencer/src/storage/model/Block.ts | 2 +- 19 files changed, 46 insertions(+), 119 deletions(-) diff --git a/packages/common/src/trees/lmt/LinkedLinkedStore.ts b/packages/common/src/trees/lmt/LinkedLinkedStore.ts index 77fd5760d..e0a10b397 100644 --- a/packages/common/src/trees/lmt/LinkedLinkedStore.ts +++ b/packages/common/src/trees/lmt/LinkedLinkedStore.ts @@ -1,12 +1,8 @@ -import { MerkleTreeStore } from "../sparse/MerkleTreeStore"; - export type LinkedLeaf = { value: bigint; path: bigint; nextPath: bigint }; export type StoredLeaf = { leaf: LinkedLeaf; index: bigint }; export interface LinkedLeafStore { - // treeStore: MerkleTreeStore; - setLeaf: (index: bigint, value: LinkedLeaf) => void; getLeaf: (path: bigint) => StoredLeaf | undefined; diff --git a/packages/common/src/trees/lmt/LinkedMerkleTree.ts b/packages/common/src/trees/lmt/LinkedMerkleTree.ts index 998c07ed4..504af79a2 100644 --- a/packages/common/src/trees/lmt/LinkedMerkleTree.ts +++ b/packages/common/src/trees/lmt/LinkedMerkleTree.ts @@ -173,7 +173,6 @@ export function createLinkedMerkleTree( merkleWitness: currentMerkleWitness, }), }); - // eslint-disable-next-line no-else-return } else { // Update case const witnessPrevious = this.dummyReadWitness(); diff --git a/packages/common/src/trees/lmt/LinkedMerkleTreeCircuitOps.ts b/packages/common/src/trees/lmt/LinkedMerkleTreeCircuitOps.ts index c3660fcdd..bfef7568a 100644 --- a/packages/common/src/trees/lmt/LinkedMerkleTreeCircuitOps.ts +++ b/packages/common/src/trees/lmt/LinkedMerkleTreeCircuitOps.ts @@ -4,29 +4,7 @@ import { LinkedMerkleTreeWitness } from "./LinkedMerkleTree"; import { LinkedLeafStruct } from "./LinkedMerkleTreeTypes"; /* eslint-disable no-inner-declarations */ -// TODO -export class MonadBool extends Struct({ - b: Bool, - metadata: String, -}) { - and(bool2: Bool, msg: string): MonadBool { - const result = this.b.and(bool2); - let metadata: string = this.metadata; - Provable.asProver(() => { - if (!bool2.toBoolean()) { - metadata = metadata + (metadata.length > 0 ? "\n" : "") + msg; - } - }); - return new MonadBool({ - b: result, - metadata, - }); - } - - static from(b: Bool) { - return new MonadBool({ b, metadata: "" }); - } -} +// TODO Add a struct that captures the errors monad-style export type TreeWrite = { path: Field; diff --git a/packages/common/test/trees/LinkedMerkleTreeCircuitOps.test.ts b/packages/common/test/trees/LinkedMerkleTreeCircuitOps.test.ts index 841ba2e51..d8cadc937 100644 --- a/packages/common/test/trees/LinkedMerkleTreeCircuitOps.test.ts +++ b/packages/common/test/trees/LinkedMerkleTreeCircuitOps.test.ts @@ -1,13 +1,12 @@ +import { Field, Provable } from "o1js"; + import { InMemoryLinkedLeafStore, InMemoryMerkleTreeStorage, - LinkedLeafStruct, LinkedMerkleTree, LinkedMerkleTreeCircuitOps, - LinkedMerkleTreeGlobalState, LinkedMerkleTreeWitness, } from "../../src"; -import { Field, Provable } from "o1js"; describe("LinkedMerkleTree - Circuit Ops", () => { function setupTree() { @@ -24,7 +23,7 @@ describe("LinkedMerkleTree - Circuit Ops", () => { it("should correctly verify insert witness", () => { try { - const root = tree.getGlobalState(); + const root = tree.getRoot(); const insertWitness = tree.setLeaf(5n, 1000n); const globalState = LinkedMerkleTreeCircuitOps.applyTreeWrite( @@ -38,12 +37,7 @@ describe("LinkedMerkleTree - Circuit Ops", () => { 0 ); - expect(globalState.root.toString()).toStrictEqual( - tree.getRoot().toString() - ); - expect(globalState.lastOccupiedIndex.toString()).toStrictEqual( - root.lastOccupiedIndex.add(1).toString() - ); + expect(globalState.toString()).toStrictEqual(tree.getRoot().toString()); } catch (e) { console.error(e); throw e; @@ -55,7 +49,7 @@ describe("LinkedMerkleTree - Circuit Ops", () => { tree.setLeaf(5n, 1000n); tree.setLeaf(10n, 1500n); - const root = tree.getGlobalState(); + const root = tree.getRoot(); const updateWitness = tree.setLeaf(10n, 500n); @@ -70,12 +64,7 @@ describe("LinkedMerkleTree - Circuit Ops", () => { 0 ); - expect(globalState.root.toString()).toStrictEqual( - tree.getRoot().toString() - ); - expect(globalState.lastOccupiedIndex.toString()).toStrictEqual( - root.lastOccupiedIndex.toString() - ); + expect(globalState.toString()).toStrictEqual(tree.getRoot().toString()); } catch (e) { console.error(e); throw e; @@ -86,7 +75,7 @@ describe("LinkedMerkleTree - Circuit Ops", () => { tree.setLeaf(5n, 1000n); tree.setLeaf(10n, 1500n); - const root = tree.getGlobalState(); + const root = tree.getRoot(); const updateWitness = tree.getReadWitness(10n); @@ -101,20 +90,15 @@ describe("LinkedMerkleTree - Circuit Ops", () => { 0 ); - expect(globalState.root.toString()).toStrictEqual(root.root.toString()); - expect(globalState.root.toString()).toStrictEqual( - tree.getRoot().toString() - ); - expect(globalState.lastOccupiedIndex.toString()).toStrictEqual( - root.lastOccupiedIndex.toString() - ); + expect(globalState.toString()).toStrictEqual(root.toString()); + expect(globalState.toString()).toStrictEqual(tree.getRoot().toString()); }); it("should noop when used with a dummy witness", () => { tree.setLeaf(5n, 1000n); tree.setLeaf(10n, 1500n); - const root = tree.getGlobalState(); + const root = tree.getRoot(); const globalState = LinkedMerkleTreeCircuitOps.applyTreeWrite( root, @@ -127,25 +111,17 @@ describe("LinkedMerkleTree - Circuit Ops", () => { 0 ); - expect(globalState.root.toString()).toStrictEqual(root.root.toString()); - expect(globalState.root.toString()).toStrictEqual( - tree.getRoot().toString() - ); - expect(globalState.lastOccupiedIndex.toString()).toStrictEqual( - root.lastOccupiedIndex.toString() - ); + expect(globalState.toString()).toStrictEqual(root.toString()); + expect(globalState.toString()).toStrictEqual(tree.getRoot().toString()); }); it("Circuit size", async () => { - const root = tree.getGlobalState(); + const root = tree.getRoot(); const updateWitness = tree.setLeaf(10n, 500n); const cs = await Provable.constraintSystem(() => { - const rootWitness = Provable.witness( - LinkedMerkleTreeGlobalState, - () => root - ); + const rootWitness = Provable.witness(Field, () => root); const updateWitnessWitness = Provable.witness( LinkedMerkleTreeWitness, () => updateWitness @@ -156,7 +132,7 @@ describe("LinkedMerkleTree - Circuit Ops", () => { to: Provable.witness(Field, () => 1), }; - const globalState = LinkedMerkleTreeCircuitOps.applyTreeWrite( + LinkedMerkleTreeCircuitOps.applyTreeWrite( rootWitness, updateWitnessWitness, treeWrite, @@ -166,6 +142,6 @@ describe("LinkedMerkleTree - Circuit Ops", () => { console.log(cs.rows); - // expect(cs.rows).toBeLessThan(2500); + expect(cs.rows).toBeLessThan(2500); }); }); diff --git a/packages/persistance/src/PrismaDatabaseConnection.ts b/packages/persistance/src/PrismaDatabaseConnection.ts index 12d3422ea..84440ca1c 100644 --- a/packages/persistance/src/PrismaDatabaseConnection.ts +++ b/packages/persistance/src/PrismaDatabaseConnection.ts @@ -1,4 +1,4 @@ -import { PrismaClient } from "@prisma/client"; +import { Prisma, PrismaClient } from "@prisma/client"; import { sequencerModule, SequencerModule, @@ -28,6 +28,7 @@ export interface PrismaDatabaseConfig { }; } | string; + log?: (Prisma.LogLevel | Prisma.LogDefinition)[]; } export interface PrismaConnection { @@ -134,7 +135,7 @@ export class PrismaDatabaseConnection url, }, }, - log: ["query", "info", "warn", "error"], + log: this.config.log, }); } else { this.initializedClient = new PrismaClient(); diff --git a/packages/persistance/src/services/prisma/PrismaLinkedLeafStore.ts b/packages/persistance/src/services/prisma/PrismaLinkedLeafStore.ts index 524f5a112..6c0f3a324 100644 --- a/packages/persistance/src/services/prisma/PrismaLinkedLeafStore.ts +++ b/packages/persistance/src/services/prisma/PrismaLinkedLeafStore.ts @@ -1,25 +1,19 @@ -import { - LinkedLeaf, - log, - mapSequential, - noop, - StoredLeaf, -} from "@proto-kit/common"; -import { AsyncLinkedLeafStore } from "@proto-kit/sequencer/dist/state/async/AsyncLinkedLeafStore"; +import { noop, StoredLeaf } from "@proto-kit/common"; +import { AsyncLinkedLeafStore, Tracer } from "@proto-kit/sequencer"; +import { injectable } from "tsyringe"; +import { Prisma } from "@prisma/client"; import { PrismaConnection } from "../../PrismaDatabaseConnection"; -import { Decimal } from "./PrismaStateService"; -import { inject, injectable } from "tsyringe"; -import { Tracer } from "@proto-kit/sequencer"; import { RedisMerkleTreeStore } from "../redis/RedisMerkleTreeStore"; import { RedisConnection } from "../../RedisConnection"; -import { Prisma } from "@prisma/client"; + +import { Decimal } from "./PrismaStateService"; @injectable() export class PrismaLinkedLeafStore implements AsyncLinkedLeafStore { private cache: StoredLeaf[] = []; - private redisMerkleStore: RedisMerkleTreeStore; + private readonly redisMerkleStore: RedisMerkleTreeStore; public constructor( private readonly connection: PrismaConnection, diff --git a/packages/persistance/test-integration/PrismaBlockProduction.test.ts b/packages/persistance/test-integration/PrismaBlockProduction.test.ts index fc7948421..8789073e0 100644 --- a/packages/persistance/test-integration/PrismaBlockProduction.test.ts +++ b/packages/persistance/test-integration/PrismaBlockProduction.test.ts @@ -40,10 +40,6 @@ describe("prisma integration", () => { await appChain.start(false, container.createChildContainer()); - const db = appChain.sequencer.resolve("Database"); - // await db.prisma.pruneDatabase(); - // await db.redis.pruneDatabase(); - senderNonce = 0; }; diff --git a/packages/persistance/test-integration/SequencerRestart.test.ts b/packages/persistance/test-integration/SequencerRestart.test.ts index 6619ed5ab..940bc40ca 100644 --- a/packages/persistance/test-integration/SequencerRestart.test.ts +++ b/packages/persistance/test-integration/SequencerRestart.test.ts @@ -1,6 +1,6 @@ import "reflect-metadata"; import { afterAll, beforeAll, expect } from "@jest/globals"; -import { expectDefined } from "@proto-kit/common"; +import { expectDefined, log } from "@proto-kit/common"; import { PrivateKey } from "o1js"; import { container } from "tsyringe"; @@ -18,8 +18,7 @@ describe("sequencer restart", () => { const clearDB = async () => { const db = appChain.sequencer.resolve("Database"); - await db.prisma.pruneDatabase(); - await db.redis.pruneDatabase(); + await db.pruneDatabase(); }; const setup = async () => { @@ -30,6 +29,11 @@ describe("sequencer restart", () => { Signer: { signer: sender, }, + Sequencer: { + DatabasePruneModule: { + pruneOnStartup: false, + }, + }, }); await appChain.start(false, container.createChildContainer()); @@ -40,6 +44,7 @@ describe("sequencer restart", () => { }; beforeAll(async () => { + log.setLevel("DEBUG"); await setup(); await clearDB(); diff --git a/packages/protocol/src/model/StateTransitionProvableBatch.ts b/packages/protocol/src/model/StateTransitionProvableBatch.ts index 8ee50fdcd..d08ab7ffb 100644 --- a/packages/protocol/src/model/StateTransitionProvableBatch.ts +++ b/packages/protocol/src/model/StateTransitionProvableBatch.ts @@ -1,9 +1,5 @@ import { Bool, Field, Provable, Struct } from "o1js"; -import { - batch, - LinkedMerkleTreeWitness, - RollupMerkleTreeWitness, -} from "@proto-kit/common"; +import { batch, LinkedMerkleTreeWitness } from "@proto-kit/common"; import { constants } from "../Constants"; diff --git a/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts b/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts index 9962c3e18..f1cb1db8b 100644 --- a/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts +++ b/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts @@ -1,6 +1,5 @@ import { prefixToField, - RollupMerkleTree, TypedClass, mapSequential, ChildVerificationKeyService, diff --git a/packages/sdk/src/graphql/GraphqlQueryTransportModule.ts b/packages/sdk/src/graphql/GraphqlQueryTransportModule.ts index 4bcb032ed..a45a3871c 100644 --- a/packages/sdk/src/graphql/GraphqlQueryTransportModule.ts +++ b/packages/sdk/src/graphql/GraphqlQueryTransportModule.ts @@ -2,11 +2,11 @@ import { QueryTransportModule } from "@proto-kit/sequencer"; import { Field } from "o1js"; import { inject, injectable } from "tsyringe"; import { gql } from "@urql/core"; +import { LinkedMerkleTreeReadWitness } from "@proto-kit/common"; import { AppChainModule } from "../appChain/AppChainModule"; import { GraphqlClient } from "./GraphqlClient"; -import { LinkedMerkleTreeReadWitness } from "@proto-kit/common"; function assertStringArray(array: any): asserts array is string[] { if ( diff --git a/packages/sequencer/src/protocol/production/BatchProducerModule.ts b/packages/sequencer/src/protocol/production/BatchProducerModule.ts index f9915cac0..545190624 100644 --- a/packages/sequencer/src/protocol/production/BatchProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BatchProducerModule.ts @@ -13,16 +13,14 @@ import { } from "../../sequencer/builder/SequencerModule"; import { BatchStorage } from "../../storage/repositories/BatchStorage"; import { SettleableBatch } from "../../storage/model/Batch"; -import { CachedMerkleTreeStore } from "../../state/merkle/CachedMerkleTreeStore"; -import { AsyncMerkleTreeStore } from "../../state/async/AsyncMerkleTreeStore"; import { BlockWithResult } from "../../storage/model/Block"; import type { Database } from "../../storage/Database"; +import { AsyncLinkedLeafStore } from "../../state/async/AsyncLinkedLeafStore"; +import { CachedLinkedLeafStore } from "../../state/lmt/CachedLinkedLeafStore"; import { BlockProofSerializer } from "./tasks/serializers/BlockProofSerializer"; import { BatchTracingService } from "./tracing/BatchTracingService"; import { BatchFlow } from "./flow/BatchFlow"; -import { AsyncLinkedLeafStore } from "../../state/async/AsyncLinkedLeafStore"; -import { CachedLinkedLeafStore } from "../../state/lmt/CachedLinkedLeafStore"; export type StateRecord = Record; diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts index 3e82dffb2..12722e3b4 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts @@ -26,10 +26,10 @@ import { Database } from "../../../storage/Database"; import { IncomingMessagesService } from "../../../settlement/messages/IncomingMessagesService"; import { Tracer } from "../../../logging/Tracer"; import { trace } from "../../../logging/trace"; +import { AsyncLinkedLeafStore } from "../../../state/async/AsyncLinkedLeafStore"; import { BlockProductionService } from "./BlockProductionService"; import { BlockResultService } from "./BlockResultService"; -import { AsyncLinkedLeafStore } from "../../../state/async/AsyncLinkedLeafStore"; export interface BlockConfig { allowEmptyBlock?: boolean; diff --git a/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts b/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts index 2569096f0..b7281c649 100644 --- a/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts @@ -15,7 +15,6 @@ import { log, ProvableMethodExecutionContext, CompileRegistry, - RollupMerkleTreeWitness, LinkedMerkleTreeWitness, } from "@proto-kit/common"; diff --git a/packages/sequencer/src/protocol/production/tasks/serializers/StateTransitionParametersSerializer.ts b/packages/sequencer/src/protocol/production/tasks/serializers/StateTransitionParametersSerializer.ts index 1f6c84f46..f88c80132 100644 --- a/packages/sequencer/src/protocol/production/tasks/serializers/StateTransitionParametersSerializer.ts +++ b/packages/sequencer/src/protocol/production/tasks/serializers/StateTransitionParametersSerializer.ts @@ -3,10 +3,7 @@ import { StateTransitionProvableBatch, StateTransitionProverPublicInput, } from "@proto-kit/protocol"; -import { - LinkedMerkleTreeWitness, - RollupMerkleTreeWitness, -} from "@proto-kit/common"; +import { LinkedMerkleTreeWitness } from "@proto-kit/common"; import { TaskSerializer } from "../../../../worker/flow/Task"; import type { StateTransitionProofParameters } from "../StateTransitionTask"; diff --git a/packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts b/packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts index d7677d571..ef5765487 100644 --- a/packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts +++ b/packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts @@ -1,7 +1,6 @@ import { InMemoryLinkedLeafStore, LinkedLeaf, - mapSequential, LinkedLeafStore, assertDefined, StoredLeaf, @@ -9,6 +8,7 @@ import { } from "@proto-kit/common"; // eslint-disable-next-line import/no-extraneous-dependencies import zip from "lodash/zip"; +// eslint-disable-next-line import/no-extraneous-dependencies import groupBy from "lodash/groupBy"; import { AsyncLinkedLeafStore } from "../async/AsyncLinkedLeafStore"; @@ -158,7 +158,7 @@ export class CachedLinkedLeafStore implements LinkedLeafStore { leaf !== undefined ? "update" : "insert" ); - let treeIndizesToFetch: bigint[] = []; + const treeIndizesToFetch: bigint[] = []; if (groupedOps.update !== undefined) { // Preload updates @@ -211,9 +211,6 @@ export class CachedLinkedLeafStore implements LinkedLeafStore { // In that case, everything will be either contained in the siblings of this index // or be zero. So in either case, we don't have to preload more than we do here. const maximumIndex = this.leafStore.getMaximumIndex() ?? -1n; - // if (maximumIndex === undefined) { - // throw Error("Maximum index should be defined in parent."); - // } treeIndizesToFetch.push(maximumIndex + 1n); } diff --git a/packages/sequencer/src/state/merkle/SyncCachedLinkedLeafStore.ts b/packages/sequencer/src/state/merkle/SyncCachedLinkedLeafStore.ts index 1ad43869a..7804010da 100644 --- a/packages/sequencer/src/state/merkle/SyncCachedLinkedLeafStore.ts +++ b/packages/sequencer/src/state/merkle/SyncCachedLinkedLeafStore.ts @@ -3,10 +3,10 @@ import { InMemoryLinkedLeafStore, LinkedLeafStore, StoredLeaf, - BigIntMath, } from "@proto-kit/common"; import { CachedLinkedLeafStore } from "../lmt/CachedLinkedLeafStore"; + import { SyncCachedMerkleTreeStore } from "./SyncCachedMerkleTreeStore"; // This is mainly used for supporting the rollbacks we need to do in case a runtimemethod fails diff --git a/packages/sequencer/src/state/merkle/SyncCachedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/SyncCachedMerkleTreeStore.ts index 337ad0d36..e6965d0eb 100644 --- a/packages/sequencer/src/state/merkle/SyncCachedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/SyncCachedMerkleTreeStore.ts @@ -1,8 +1,4 @@ -import { - InMemoryMerkleTreeStorage, - MerkleTreeStore, - RollupMerkleTree, -} from "@proto-kit/common"; +import { InMemoryMerkleTreeStorage, MerkleTreeStore } from "@proto-kit/common"; export class SyncCachedMerkleTreeStore extends InMemoryMerkleTreeStorage { public constructor(private readonly parent: MerkleTreeStore) { diff --git a/packages/sequencer/src/storage/model/Block.ts b/packages/sequencer/src/storage/model/Block.ts index 022b23837..b16e8d748 100644 --- a/packages/sequencer/src/storage/model/Block.ts +++ b/packages/sequencer/src/storage/model/Block.ts @@ -5,7 +5,7 @@ import { BlockHashMerkleTreeWitness, NetworkState, } from "@proto-kit/protocol"; -import { LinkedMerkleTree, RollupMerkleTree } from "@proto-kit/common"; +import { LinkedMerkleTree } from "@proto-kit/common"; import { PendingTransaction } from "../../mempool/PendingTransaction"; import { UntypedStateTransition } from "../../protocol/production/helpers/UntypedStateTransition"; From 1043b913aff9ff058153be0621c4d7f6605bb54c Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 11 Apr 2025 21:56:22 +0200 Subject: [PATCH 125/128] Fixed tests --- .../graphql/modules/MerkleWitnessResolver.ts | 76 ++++++++++++++++--- .../src/trees/lmt/AbstractLinkedMerkleTree.ts | 8 +- .../common/src/trees/lmt/LinkedMerkleTree.ts | 30 ++++---- .../trees/LinkedMerkleTreeCircuitOps.test.ts | 2 +- .../messages/OutgoingMessageArgument.ts | 7 +- .../StateTransitionProver.test.ts | 67 ++++++++++------ .../StateTransitionTracingService.test.ts | 41 +++++----- .../sequencer/test/settlement/Settlement.ts | 32 ++++---- 8 files changed, 174 insertions(+), 89 deletions(-) diff --git a/packages/api/src/graphql/modules/MerkleWitnessResolver.ts b/packages/api/src/graphql/modules/MerkleWitnessResolver.ts index 8c59b8cc8..a39324ab0 100644 --- a/packages/api/src/graphql/modules/MerkleWitnessResolver.ts +++ b/packages/api/src/graphql/modules/MerkleWitnessResolver.ts @@ -1,10 +1,15 @@ import { Arg, Field, ObjectType, Query } from "type-graphql"; import { Length } from "class-validator"; import { inject } from "tsyringe"; -import { RollupMerkleTree, RollupMerkleTreeWitness } from "@proto-kit/common"; import { - AsyncMerkleTreeStore, - CachedMerkleTreeStore, + LinkedLeafStruct, + LinkedMerkleTree, + LinkedMerkleTreeReadWitness, + RollupMerkleTreeWitness, +} from "@proto-kit/common"; +import { + AsyncLinkedLeafStore, + CachedLinkedLeafStore, } from "@proto-kit/sequencer"; import { GraphqlModule, graphqlModule } from "../GraphqlModule"; @@ -31,26 +36,79 @@ export class MerkleWitnessDTO { public isLefts: boolean[]; } +@ObjectType() +export class LinkedLeafDTO { + public static fromServiceLayerObject({ + path, + value, + nextPath, + }: LinkedLeafStruct) { + return new LinkedLeafDTO( + path.toString(), + value.toString(), + nextPath.toString() + ); + } + + constructor(path: string, value: string, nextPath: string) { + this.path = path; + this.value = value; + this.nextPath = nextPath; + } + + @Field(() => String) + public path: string; + + @Field(() => String) + public value: string; + + @Field(() => String) + public nextPath: string; +} + +@ObjectType() +export class LinkedTreeWitnessDTO { + public static fromServiceLayerObject(witness: LinkedMerkleTreeReadWitness) { + const merkleWitness = MerkleWitnessDTO.fromServiceLayerObject( + witness.merkleWitness + ); + const linkedLeaf = LinkedLeafDTO.fromServiceLayerObject(witness.leaf); + return new LinkedTreeWitnessDTO(merkleWitness, linkedLeaf); + } + + public constructor(merkleWitness: MerkleWitnessDTO, leaf: LinkedLeafDTO) { + this.merkleWitness = merkleWitness; + this.leaf = leaf; + } + + @Field(() => MerkleWitnessDTO) + public merkleWitness: MerkleWitnessDTO; + + @Field(() => LinkedLeafDTO) + public leaf: LinkedLeafDTO; +} + @graphqlModule() export class MerkleWitnessResolver extends GraphqlModule { public constructor( - @inject("AsyncMerkleStore") private readonly treeStore: AsyncMerkleTreeStore + @inject("AsyncLinkedLeafStore") + private readonly treeStore: AsyncLinkedLeafStore ) { super(); } - @Query(() => MerkleWitnessDTO, { + @Query(() => LinkedTreeWitnessDTO, { 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 = new CachedMerkleTreeStore(this.treeStore); + const syncStore = await CachedLinkedLeafStore.new(this.treeStore); await syncStore.preloadKey(BigInt(path)); - const tree = new RollupMerkleTree(syncStore); + const tree = new LinkedMerkleTree(syncStore.treeStore, syncStore); - const witness = tree.getWitness(BigInt(path)); + const witness = tree.getReadWitness(BigInt(path)); - return MerkleWitnessDTO.fromServiceLayerObject(witness); + return LinkedTreeWitnessDTO.fromServiceLayerObject(witness); } } diff --git a/packages/common/src/trees/lmt/AbstractLinkedMerkleTree.ts b/packages/common/src/trees/lmt/AbstractLinkedMerkleTree.ts index 697904520..d5209ad06 100644 --- a/packages/common/src/trees/lmt/AbstractLinkedMerkleTree.ts +++ b/packages/common/src/trees/lmt/AbstractLinkedMerkleTree.ts @@ -71,10 +71,6 @@ export interface AbstractLinkedMerkleTree { * @returns The witness that belongs to the leaf. */ getReadWitness(path: bigint): LinkedMerkleWitnessValue; - - dummyWitness(): LinkedOperationWitnessValue; - - dummyReadWitness(): LinkedMerkleWitnessValue; } export interface AbstractLinkedMerkleTreeClass { @@ -97,4 +93,8 @@ export interface AbstractLinkedMerkleTreeClass { HEIGHT: number; EMPTY_ROOT: Field; + + dummyWitness(): LinkedOperationWitnessValue; + + dummyReadWitness(): LinkedMerkleWitnessValue; } diff --git a/packages/common/src/trees/lmt/LinkedMerkleTree.ts b/packages/common/src/trees/lmt/LinkedMerkleTree.ts index 504af79a2..96eb03052 100644 --- a/packages/common/src/trees/lmt/LinkedMerkleTree.ts +++ b/packages/common/src/trees/lmt/LinkedMerkleTree.ts @@ -22,14 +22,17 @@ export function createLinkedMerkleTree( merkleWitness: SparseTreeClass.WITNESS, }) { public checkMembership(root: Field, path: Field, value: Field) { - return this.merkleWitness.checkMembership( - root, - path, - new LinkedLeafStruct({ - ...this.leaf, - value, - }).hash() - ); + const pathEquals = path.equals(this.leaf.path); + + return this.merkleWitness + .calculateRoot( + new LinkedLeafStruct({ + ...this.leaf, + value, + }).hash() + ) + .equals(root) + .and(pathEquals); } } @@ -175,7 +178,8 @@ export function createLinkedMerkleTree( }); } else { // Update case - const witnessPrevious = this.dummyReadWitness(); + const witnessPrevious = + AbstractLinkedRollupMerkleTree.dummyReadWitness(); // TODO This makes an unnecessary leafstore lookup currently, reuse storedLeaf instead const current = this.getReadWitness(storedLeaf.leaf.path); @@ -247,17 +251,17 @@ export function createLinkedMerkleTree( }); } - public dummyReadWitness(): LinkedLeafAndMerkleWitness { + public static dummyReadWitness(): LinkedLeafAndMerkleWitness { return new LinkedLeafAndMerkleWitness({ merkleWitness: SparseTreeClass.WITNESS.dummy(), leaf: LinkedLeafStruct.dummy(), }); } - public dummyWitness() { + public static dummyWitness() { return new LinkedOperationWitness({ - leafPrevious: this.dummyReadWitness(), - leafCurrent: this.dummyReadWitness(), + leafPrevious: AbstractLinkedRollupMerkleTree.dummyReadWitness(), + leafCurrent: AbstractLinkedRollupMerkleTree.dummyReadWitness(), }); } }; diff --git a/packages/common/test/trees/LinkedMerkleTreeCircuitOps.test.ts b/packages/common/test/trees/LinkedMerkleTreeCircuitOps.test.ts index d8cadc937..cce9a2707 100644 --- a/packages/common/test/trees/LinkedMerkleTreeCircuitOps.test.ts +++ b/packages/common/test/trees/LinkedMerkleTreeCircuitOps.test.ts @@ -102,7 +102,7 @@ describe("LinkedMerkleTree - Circuit Ops", () => { const globalState = LinkedMerkleTreeCircuitOps.applyTreeWrite( root, - tree.dummyWitness(), + LinkedMerkleTree.dummyWitness(), { path: Field(0), from: Field(0), diff --git a/packages/protocol/src/settlement/messages/OutgoingMessageArgument.ts b/packages/protocol/src/settlement/messages/OutgoingMessageArgument.ts index 9c2348e4f..7a6a79995 100644 --- a/packages/protocol/src/settlement/messages/OutgoingMessageArgument.ts +++ b/packages/protocol/src/settlement/messages/OutgoingMessageArgument.ts @@ -1,7 +1,5 @@ import { Bool, Provable, Struct } from "o1js"; import { - InMemoryLinkedLeafStore, - InMemoryMerkleTreeStorage, LinkedMerkleTree, LinkedMerkleTreeReadWitness, } from "@proto-kit/common"; @@ -16,10 +14,7 @@ export class OutgoingMessageArgument extends Struct({ }) { public static dummy(): OutgoingMessageArgument { return new OutgoingMessageArgument({ - witness: new LinkedMerkleTree( - new InMemoryMerkleTreeStorage(), - new InMemoryLinkedLeafStore() - ).dummyReadWitness(), + witness: LinkedMerkleTree.dummyReadWitness(), value: Withdrawal.dummy(), }); } diff --git a/packages/protocol/test/prover/statetransition/StateTransitionProver.test.ts b/packages/protocol/test/prover/statetransition/StateTransitionProver.test.ts index 930a2e49e..43cf92935 100644 --- a/packages/protocol/test/prover/statetransition/StateTransitionProver.test.ts +++ b/packages/protocol/test/prover/statetransition/StateTransitionProver.test.ts @@ -1,10 +1,10 @@ import { InMemoryAreProofsEnabled } from "@proto-kit/sdk"; import { Bool, Field } from "o1js"; import { + InMemoryLinkedLeafStore, InMemoryMerkleTreeStorage, + LinkedMerkleTree, padArray, - RollupMerkleTree, - RollupMerkleTreeWitness, } from "@proto-kit/common"; import { @@ -74,8 +74,13 @@ describe("StateTransitionProver", () => { }, ]); - const tree = new RollupMerkleTree(new InMemoryMerkleTreeStorage()); - const witness = tree.getWitness(1n); + const tree = new LinkedMerkleTree( + new InMemoryMerkleTreeStorage(), + new InMemoryLinkedLeafStore() + ); + const witness = LinkedMerkleTree.WITNESS.fromReadWitness( + tree.getReadWitness(1n) + ); const result = await prover.proveBatch( { @@ -87,7 +92,7 @@ describe("StateTransitionProver", () => { batch[0], { witnesses: padArray([witness], 4, () => - RollupMerkleTreeWitness.dummy() + LinkedMerkleTree.dummyWitness() ), }, new AppliedStateTransitionBatchState({ @@ -116,17 +121,17 @@ describe("StateTransitionProver", () => { const prove = async () => await prover.proveBatch( { - root: Field(RollupMerkleTree.EMPTY_ROOT), + root: Field(LinkedMerkleTree.EMPTY_ROOT), witnessedRootsHash: Field(0), batchesHash: Field(0), currentBatchStateHash: Field(0), }, batch[0], { - witnesses: padArray([], 4, RollupMerkleTreeWitness.dummy), + witnesses: padArray([], 4, () => LinkedMerkleTree.dummyWitness()), }, new AppliedStateTransitionBatchState({ - root: Field(RollupMerkleTree.EMPTY_ROOT), + root: Field(LinkedMerkleTree.EMPTY_ROOT), batchHash: Field(0), }) ); @@ -151,13 +156,20 @@ describe("StateTransitionProver", () => { }, ]); - const tree = new RollupMerkleTree(new InMemoryMerkleTreeStorage()); + const tree = new LinkedMerkleTree( + new InMemoryMerkleTreeStorage(), + new InMemoryLinkedLeafStore() + ); const inputRoot = tree.getRoot(); - const witness = tree.getWitness(1n); - tree.setLeaf(1n, Field(2)); - const witness2 = tree.getWitness(2n); + const witness = LinkedMerkleTree.WITNESS.fromReadWitness( + tree.getReadWitness(1n) + ); + tree.setLeaf(1n, 2n); + const witness2 = LinkedMerkleTree.WITNESS.fromReadWitness( + tree.getReadWitness(2n) + ); const prove = async () => await prover.proveBatch( @@ -171,9 +183,9 @@ describe("StateTransitionProver", () => { { witnesses: [ witness, - RollupMerkleTreeWitness.dummy(), + LinkedMerkleTree.dummyWitness(), witness2, - RollupMerkleTreeWitness.dummy(), + LinkedMerkleTree.dummyWitness(), ], }, new AppliedStateTransitionBatchState({ @@ -209,31 +221,40 @@ describe("StateTransitionProver", () => { }, ]); - const tree = new RollupMerkleTree(new InMemoryMerkleTreeStorage()); + const tree = new LinkedMerkleTree( + new InMemoryMerkleTreeStorage(), + new InMemoryLinkedLeafStore() + ); + + const witness1 = tree.setLeaf(1n, 2n); + const witness2 = tree.setLeaf(2n, 3n); + + const resultRoot = tree.getRoot(); - const witness1 = tree.getWitness(1n); - tree.setLeaf(1n, Field(2)); - const witness2 = tree.getWitness(2n); - tree.setLeaf(2n, Field(3)); + const witness3 = tree.setLeaf(2n, 4n); + const witness4 = tree.setLeaf(2n, 5n); const result = await prover.proveBatch( { - root: Field(RollupMerkleTree.EMPTY_ROOT), + root: Field(LinkedMerkleTree.EMPTY_ROOT), witnessedRootsHash: Field(0), batchesHash: Field(0), currentBatchStateHash: Field(0), }, batch[0], { - witnesses: [witness1, witness2, witness2, witness2], + witnesses: [witness1, witness2, witness3, witness4], + // .map((x) => + // LinkedMerkleTree.WITNESS.fromReadWitness(x) + // ), }, new AppliedStateTransitionBatchState({ - root: Field(RollupMerkleTree.EMPTY_ROOT), + root: Field(LinkedMerkleTree.EMPTY_ROOT), batchHash: Field(0), }) ); - expect(result.root.toString()).toStrictEqual(tree.getRoot().toString()); + expect(result.root.toString()).toStrictEqual(resultRoot.toString()); expect(result.currentBatchStateHash.toString()).toStrictEqual("0"); }); }); diff --git a/packages/sequencer/test/production/tracing/StateTransitionTracingService.test.ts b/packages/sequencer/test/production/tracing/StateTransitionTracingService.test.ts index e82d845d3..6c877194c 100644 --- a/packages/sequencer/test/production/tracing/StateTransitionTracingService.test.ts +++ b/packages/sequencer/test/production/tracing/StateTransitionTracingService.test.ts @@ -9,18 +9,18 @@ import { WitnessedRootHashList, } from "@proto-kit/protocol"; import { Bool, Field } from "o1js"; -import { mapSequential, RollupMerkleTree } from "@proto-kit/common"; +import { LinkedMerkleTree, mapSequential } from "@proto-kit/common"; import { toStateTransitionsHash } from "@proto-kit/module"; import { - CachedMerkleTreeStore, - InMemoryAsyncMerkleTreeStore, UntypedStateTransition, StateTransitionTracingService, TracingStateTransitionBatch, StateTransitionProofParameters, ConsoleTracer, + CachedLinkedLeafStore, } from "../../../src"; +import { InMemoryAsyncLinkedLeafStore } from "../../../src/storage/inmemory/InMemoryAsyncLinkedLeafStore"; function createST(obj: { path: string; @@ -52,19 +52,19 @@ function createSTSimple( async function applyBatchesToTree( batches: TracingStateTransitionBatch[], - cached: CachedMerkleTreeStore + cached: CachedLinkedLeafStore ) { const sts = batches .filter((x) => x.applied) .flatMap(({ stateTransitions }) => stateTransitions); - const tree = new RollupMerkleTree(cached); + const tree = new LinkedMerkleTree(cached.treeStore, cached); await mapSequential(sts, async (st) => { await cached.preloadKey(st.path.toBigInt()); if (st.to.isSome.toBoolean()) { - tree.setLeaf(st.path.toBigInt(), st.to.treeValue); + tree.setLeaf(st.path.toBigInt(), st.to.treeValue.toBigInt()); } }); @@ -162,12 +162,13 @@ describe("StateTransitionTracingService", () => { }); describe.each(cases)("tracing two chunks of STs", ({ batch, numSTs }) => { - const store = new InMemoryAsyncMerkleTreeStore(); - const cached = new CachedMerkleTreeStore(store); + const store = new InMemoryAsyncLinkedLeafStore(); let trace: StateTransitionProofParameters[]; beforeAll(async () => { + const cached = await CachedLinkedLeafStore.new(store); + trace = await service.createMerkleTrace(cached, batch); }); @@ -180,7 +181,7 @@ describe("StateTransitionTracingService", () => { it("should set second publicInput correctly", async () => { const tree = await applyBatchesToTree( batch.slice(0, 4), - new CachedMerkleTreeStore(store) + await CachedLinkedLeafStore.new(store) ); expect(trace[1].publicInput.root.toString()).toStrictEqual( @@ -215,7 +216,7 @@ describe("StateTransitionTracingService", () => { const witnessedRootsList = new WitnessedRootHashList(); const tempTree = await applyBatchesToTree( batch.slice(0, 2), - new CachedMerkleTreeStore(store) + await CachedLinkedLeafStore.new(store) ); witnessedRootsList.push({ @@ -234,12 +235,12 @@ describe("StateTransitionTracingService", () => { }); describe("tracing two separate sequences", () => { - const store = new InMemoryAsyncMerkleTreeStore(); - const cached = new CachedMerkleTreeStore(store); + const store = new InMemoryAsyncLinkedLeafStore(); + let cached: CachedLinkedLeafStore; let trace1: StateTransitionProofParameters[]; let trace2: StateTransitionProofParameters[]; - let tree1: RollupMerkleTree; + let tree1: LinkedMerkleTree; const batches: TracingStateTransitionBatch[][] = [ [ @@ -264,9 +265,10 @@ describe("StateTransitionTracingService", () => { ]; beforeAll(async () => { + cached = await CachedLinkedLeafStore.new(store); trace1 = await service.createMerkleTrace(cached, batches[0]); - const cached2 = new CachedMerkleTreeStore(store); + const cached2 = await CachedLinkedLeafStore.new(store); tree1 = await applyBatchesToTree(batches[0], cached2); trace2 = await service.createMerkleTrace(cached, batches[1]); @@ -295,8 +297,8 @@ describe("StateTransitionTracingService", () => { }); describe("should trace correctly", () => { - const store = new InMemoryAsyncMerkleTreeStore(); - const cached = new CachedMerkleTreeStore(store); + const store = new InMemoryAsyncLinkedLeafStore(); + let cached: CachedLinkedLeafStore; const batches: TracingStateTransitionBatch[] = [ { @@ -319,6 +321,7 @@ describe("StateTransitionTracingService", () => { let trace: StateTransitionProofParameters[]; beforeAll(async () => { + cached = await CachedLinkedLeafStore.new(store); trace = await service.createMerkleTrace(cached, batches); }); @@ -348,15 +351,15 @@ describe("StateTransitionTracingService", () => { expect(result).toBeDefined(); // Check that root matches - const tree = new RollupMerkleTree(cached); + const tree = new LinkedMerkleTree(cached.treeStore, cached); expect(result.root.toString()).toStrictEqual(tree.getRoot().toString()); }); }); it("check that STs have been applied to the tree store", async () => { - const tracedTree = new RollupMerkleTree(cached); + const tracedTree = new LinkedMerkleTree(cached.treeStore, cached); - const cached2 = new CachedMerkleTreeStore(store); + const cached2 = await CachedLinkedLeafStore.new(store); const tree = await applyBatchesToTree(batches, cached2); expect(tracedTree.getRoot().toString()).toStrictEqual( diff --git a/packages/sequencer/test/settlement/Settlement.ts b/packages/sequencer/test/settlement/Settlement.ts index 4fcda0d93..b6aa1fd22 100644 --- a/packages/sequencer/test/settlement/Settlement.ts +++ b/packages/sequencer/test/settlement/Settlement.ts @@ -2,7 +2,7 @@ import { expectDefined, mapSequential, TypedClass, - RollupMerkleTree, + LinkedMerkleTree, } from "@proto-kit/common"; import { VanillaProtocolModules } from "@proto-kit/library"; import { Runtime } from "@proto-kit/module"; @@ -487,8 +487,8 @@ export const settlementTestFn = ( const input = BlockProverPublicInput.fromFields( batch!.proof.publicInput.map((x) => Field(x)) ); - expect(input.stateRoot.toBigInt()).toStrictEqual( - RollupMerkleTree.EMPTY_ROOT + expect(input.stateRoot.toString()).toStrictEqual( + LinkedMerkleTree.EMPTY_ROOT.toString() ); const lastBlock = await blockQueue.getLatestBlockAndResult(); @@ -503,14 +503,14 @@ export const settlementTestFn = ( const { settlement } = settlementModule.getContracts(); expectDefined(lastBlock); expectDefined(lastBlock.result); - expect(settlement.networkStateHash.get().toBigInt()).toStrictEqual( - lastBlock!.result.afterNetworkState.hash().toBigInt() + expect(settlement.networkStateHash.get().toString()).toStrictEqual( + lastBlock!.result.afterNetworkState.hash().toString() ); - expect(settlement.stateRoot.get().toBigInt()).toStrictEqual( - lastBlock!.result.stateRoot + expect(settlement.stateRoot.get().toString()).toStrictEqual( + lastBlock!.result.stateRoot.toString() ); - expect(settlement.blockHashRoot.get().toBigInt()).toStrictEqual( - lastBlock!.result.blockHashRoot + expect(settlement.blockHashRoot.get().toString()).toStrictEqual( + lastBlock!.result.blockHashRoot.toString() ); } catch (e) { console.error(e); @@ -591,7 +591,7 @@ export const settlementTestFn = ( .sub(contractBalanceBefore); expect(actions).toHaveLength(1); - expect(balanceDiff.toBigInt()).toBe(depositAmount); + expect(balanceDiff.toString()).toBe(depositAmount.toString()); const [, batch] = await createBatch(false); @@ -632,7 +632,9 @@ export const settlementTestFn = ( const l2balanceDiff = balance.sub( userL2BalanceBefore ?? UInt64.from(0) ); - expect(l2balanceDiff.toBigInt()).toStrictEqual(depositAmount); + expect(l2balanceDiff.toString()).toStrictEqual( + depositAmount.toString() + ); } catch (e) { console.error(e); throw e; @@ -696,7 +698,9 @@ export const settlementTestFn = ( bridgingContract.deriveTokenId() ); - expect(account.balance.toBigInt()).toStrictEqual(BigInt(withdrawAmount)); + expect(account.balance.toString()).toStrictEqual( + withdrawAmount.toString() + ); }, timeout * 2 ); @@ -770,8 +774,8 @@ export const settlementTestFn = ( // tx fee const minaFees = BigInt(fee); - expect(balanceAfter - balanceBefore).toBe( - amount - (tokenConfig === undefined ? minaFees : 0n) + expect((balanceAfter - balanceBefore).toString()).toBe( + (amount - (tokenConfig === undefined ? minaFees : 0n)).toString() ); }, timeout From 1e14e33d780f7c4aa2ed7519643dc5afc7e7ebfd Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 12 Apr 2025 16:04:19 +0200 Subject: [PATCH 126/128] Fixed a bunch of bugs, performance improvements --- .../src/trees/lmt/InMemoryLinkedLeafStore.ts | 2 +- .../common/src/trees/lmt/LinkedMerkleTree.ts | 2 +- .../services/prisma/PrismaLinkedLeafStore.ts | 56 +- .../src/services/prisma/PrismaStateService.ts | 36 +- .../PrismaBlockProduction.test.ts | 13 + .../sequencing/BlockResultService.ts | 5 +- .../src/sequencer/executor/Sequencer.ts | 6 +- .../src/state/lmt/CachedLinkedLeafStore.ts | 41 +- .../test/LinkedMerkleTreeIntegrity.ts | 46 ++ .../test/integration/BlockProduction-test.ts | 757 ++++++++++++++++++ .../test/integration/BlockProduction.test.ts | 715 +---------------- 11 files changed, 894 insertions(+), 785 deletions(-) create mode 100644 packages/sequencer/test/LinkedMerkleTreeIntegrity.ts create mode 100644 packages/sequencer/test/integration/BlockProduction-test.ts diff --git a/packages/common/src/trees/lmt/InMemoryLinkedLeafStore.ts b/packages/common/src/trees/lmt/InMemoryLinkedLeafStore.ts index 055c839f2..684bac5f2 100644 --- a/packages/common/src/trees/lmt/InMemoryLinkedLeafStore.ts +++ b/packages/common/src/trees/lmt/InMemoryLinkedLeafStore.ts @@ -36,7 +36,7 @@ export class InMemoryLinkedLeafStore implements LinkedLeafStore { ): { leaf: LinkedLeaf; index: bigint } | undefined { return Object.values(this.leaves).find( (storedLeaf) => - storedLeaf.leaf.nextPath > path && storedLeaf.leaf.path <= path + storedLeaf.leaf.nextPath > path && storedLeaf.leaf.path < path ); } } diff --git a/packages/common/src/trees/lmt/LinkedMerkleTree.ts b/packages/common/src/trees/lmt/LinkedMerkleTree.ts index 96eb03052..93bc395d8 100644 --- a/packages/common/src/trees/lmt/LinkedMerkleTree.ts +++ b/packages/common/src/trees/lmt/LinkedMerkleTree.ts @@ -142,7 +142,7 @@ export function createLinkedMerkleTree( const previousLeaf = this.leafStore.getLeafLessOrEqual(path); if (previousLeaf === undefined) { - throw Error("Prev leaf shouldn't be undefined"); + throw Error(`Prev leaf shouldn't be undefined (path ${path})`); } const previousLeafMerkleWitness = this.tree.getWitness( diff --git a/packages/persistance/src/services/prisma/PrismaLinkedLeafStore.ts b/packages/persistance/src/services/prisma/PrismaLinkedLeafStore.ts index 6c0f3a324..031a55083 100644 --- a/packages/persistance/src/services/prisma/PrismaLinkedLeafStore.ts +++ b/packages/persistance/src/services/prisma/PrismaLinkedLeafStore.ts @@ -77,26 +77,36 @@ export class PrismaLinkedLeafStore implements AsyncLinkedLeafStore { public async getLeavesAsync(paths: bigint[]) { this.assertCacheEmpty(); - const pathsDecimal = paths.map((path) => new Decimal(path.toString(10))); - const records = await this.connection.prismaClient.linkedLeaf.findMany({ - where: { - path: { - in: pathsDecimal, + if (paths.length > 0) { + const pathsDecimal = paths.map((path) => new Decimal(path.toString(10))); + const records = await this.connection.prismaClient.linkedLeaf.findMany({ + where: { + path: { + in: pathsDecimal, + }, + mask: this.mask, }, - mask: this.mask, - }, - }); + }); - return records.map((record) => { - return { - index: BigInt(record.index.toFixed()), - leaf: { - path: BigInt(record.path.toFixed()), - value: BigInt(record.value.toFixed()), - nextPath: BigInt(record.nextPath.toFixed()), - }, - }; - }); + const stack = records + .map((record) => { + return { + index: BigInt(record.index.toFixed()), + leaf: { + path: BigInt(record.path.toFixed()), + value: BigInt(record.value.toFixed()), + nextPath: BigInt(record.nextPath.toFixed()), + }, + }; + }) + .reverse(); + + // TODO this runs in O(n^2), find a better matching algorithm for this (ordering?) + return paths.map((path) => { + return stack.find((candidate) => candidate.leaf.path === path); + }); + } + return []; } public async getMaximumIndexAsync() { @@ -133,13 +143,15 @@ export class PrismaLinkedLeafStore implements AsyncLinkedLeafStore { } & LinkedLeafQueryResult)[] >` SELECT * FROM "LinkedLeaf" l - RIGHT JOIN (SELECT unnest(ARRAY[${pathsDecimals}]) as newpath) f - ON l.path < f.newpath AND l."nextPath" > f.newpath + RIGHT JOIN (SELECT unnest(ARRAY[${pathsDecimals}]) as query_path) f + ON l.path < f.query_path AND l."nextPath" > f.query_path WHERE l.mask = ${this.mask} `; const map: Record = Object.fromEntries( - result.map((obj) => [obj.query_path.toFixed(), obj]) + result.map((obj) => { + return [obj.query_path.toFixed(), obj]; + }) ); return paths.map((path) => { @@ -148,7 +160,7 @@ export class PrismaLinkedLeafStore implements AsyncLinkedLeafStore { return { index: BigInt(record.index.toFixed()), leaf: { - path: BigInt(record.index.toFixed()), + path: BigInt(record.path.toFixed()), value: BigInt(record.value.toFixed()), nextPath: BigInt(record.nextPath.toFixed()), }, diff --git a/packages/persistance/src/services/prisma/PrismaStateService.ts b/packages/persistance/src/services/prisma/PrismaStateService.ts index 286a2e327..1aed65a38 100644 --- a/packages/persistance/src/services/prisma/PrismaStateService.ts +++ b/packages/persistance/src/services/prisma/PrismaStateService.ts @@ -36,25 +36,27 @@ export class PrismaStateService implements AsyncStateService { public async commit(): Promise { const { prismaClient } = this.connection; - const data = this.cache - .filter((entry) => entry.value !== undefined) - .map((entry) => ({ - path: new Decimal(entry.key.toString()), - values: entry.value!.map((field) => new Decimal(field.toString())), - mask: this.mask, - })); + if (this.cache.length > 0) { + const data = this.cache + .filter((entry) => entry.value !== undefined) + .map((entry) => ({ + path: new Decimal(entry.key.toString()), + values: entry.value!.map((field) => new Decimal(field.toString())), + mask: this.mask, + })); - await prismaClient.state.deleteMany({ - where: { - path: { - in: this.cache.map((x) => new Decimal(x.key.toString())), + await prismaClient.state.deleteMany({ + where: { + path: { + in: this.cache.map((x) => new Decimal(x.key.toString())), + }, + mask: this.mask, }, - mask: this.mask, - }, - }); - await prismaClient.state.createMany({ - data, - }); + }); + await prismaClient.state.createMany({ + data, + }); + } this.cache = []; } diff --git a/packages/persistance/test-integration/PrismaBlockProduction.test.ts b/packages/persistance/test-integration/PrismaBlockProduction.test.ts index 8789073e0..dcf69c648 100644 --- a/packages/persistance/test-integration/PrismaBlockProduction.test.ts +++ b/packages/persistance/test-integration/PrismaBlockProduction.test.ts @@ -7,10 +7,12 @@ import { AppChainTransaction } from "@proto-kit/sdk"; import { Block, Batch } from "@proto-kit/sequencer"; import { PrivateKey, PublicKey } from "o1js"; import { container } from "tsyringe"; +import { testBlockProduction } from "@proto-kit/sequencer/test/integration/BlockProduction-test"; import { PrismaBatchStore, PrismaBlockStorage, + PrismaRedisDatabase, PrismaTransactionStorage, } from "../src"; @@ -20,6 +22,17 @@ import { prepareBlock, } from "./utils"; +describe("Prisma block production", () => { + const { prismaConfig, redisConfig } = IntegrationTestDBConfig; + testBlockProduction(PrismaRedisDatabase, { + prisma: { + connection: prismaConfig, + log: [{ level: "query", emit: "event" }], + }, + redis: redisConfig, + }); +}); + describe("prisma integration", () => { let appChain: ReturnType; diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockResultService.ts b/packages/sequencer/src/protocol/production/sequencing/BlockResultService.ts index 5b2cbe9d8..969a8e838 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockResultService.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockResultService.ts @@ -159,11 +159,12 @@ export class BlockResultService { store: CachedLinkedLeafStore, stateDiff: StateRecord ): Promise { - await store.preloadKeys(Object.keys(stateDiff).map(BigInt)); + const stateKeys = Object.keys(stateDiff); + await store.preloadKeys(stateKeys.map(BigInt)); // In case the diff is empty, we preload key 0 in order to // retrieve the root, which we need later - if (Object.keys(stateDiff).length === 0) { + if (stateKeys.length === 0) { await store.preloadKey(0n); } diff --git a/packages/sequencer/src/sequencer/executor/Sequencer.ts b/packages/sequencer/src/sequencer/executor/Sequencer.ts index 88e87b99e..bf41d9768 100644 --- a/packages/sequencer/src/sequencer/executor/Sequencer.ts +++ b/packages/sequencer/src/sequencer/executor/Sequencer.ts @@ -121,8 +121,10 @@ export class Sequencer await sequencerModule.start(); } - // TODO This currently also warns for client appchains - if (!moduleClassNames.includes("SequencerStartupModule")) { + if ( + !moduleClassNames.includes("SequencerStartupModule") && + moduleClassNames.includes("BatchProducerModule") + ) { log.warn("SequencerStartupModule is not defined."); } } diff --git a/packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts b/packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts index ef5765487..595a9ae55 100644 --- a/packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts +++ b/packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts @@ -48,29 +48,11 @@ export class CachedLinkedLeafStore implements LinkedLeafStore { // 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; + return await this.retrieveBatched( + paths, + (path) => this.getLeaf(path), + (remotePaths) => this.parent.getLeavesAsync(remotePaths) + ); } public setLeaf(index: bigint, leaf: LinkedLeaf) { @@ -166,7 +148,7 @@ export class CachedLinkedLeafStore implements LinkedLeafStore { assertDefined(leaf); // Update case, this leaf is the only one we need - this.leafStore.setLeaf(leaf.index, leaf.leaf); + this.setLeaf(leaf.index, leaf.leaf); return leaf.index; }); @@ -187,19 +169,22 @@ export class CachedLinkedLeafStore implements LinkedLeafStore { // one exception being when the tree is empty (see below) const anyUndefined = previousLeaves.findIndex((x) => x === undefined) > -1; + // eslint-disable-next-line sonarjs/no-collapsible-if if (anyUndefined) { // This only happens when the store is empty, because in this case, the tree // initializes the 0-leaf, but this only happens after preloading. - const [zeroLeaf] = await this.parent.getLeavesAsync([0n]); - if (zeroLeaf !== undefined) { - throw Error("Previous Leaf should never be empty"); + if (this.leafStore.getLeaf(0n) === undefined) { + const [zeroLeaf] = await this.parent.getLeavesAsync([0n]); + if (zeroLeaf !== undefined) { + throw Error("Previous Leaf should never be empty"); + } } } const definedPreviousLeaves = previousLeaves.filter(filterNonUndefined); definedPreviousLeaves.forEach(({ index, leaf }) => - this.leafStore.setLeaf(index, leaf) + this.setLeaf(index, leaf) ); treeIndizesToFetch.push( ...definedPreviousLeaves.map(({ index }) => index) diff --git a/packages/sequencer/test/LinkedMerkleTreeIntegrity.ts b/packages/sequencer/test/LinkedMerkleTreeIntegrity.ts new file mode 100644 index 000000000..c19a6f5d8 --- /dev/null +++ b/packages/sequencer/test/LinkedMerkleTreeIntegrity.ts @@ -0,0 +1,46 @@ +import { Field } from "o1js"; +import { LinkedLeafStruct, log } from "@proto-kit/common"; + +import { AsyncLinkedLeafStore } from "../src/state/async/AsyncLinkedLeafStore"; + +export namespace LinkedMerkleTreeIntegrity { + export async function checkIntegrity(store: AsyncLinkedLeafStore) { + log.info("Checking tree integrity..."); + + let currentPath = 0n; + const maxPath = Field.ORDER - 1n; + + while (currentPath < maxPath) { + const leaves = await store.getLeavesAsync([currentPath]); + if (leaves.length === 0 || leaves[0] === undefined) { + return false; + } + + const leaf = leaves[0]!; + + const treeValues = await store.treeStore.getNodesAsync([ + { level: 0, key: leaf.index }, + ]); + if (treeValues.length === 0 || treeValues[0] === undefined) { + return false; + } + + const treeValue = treeValues[0]; + const leafHash = new LinkedLeafStruct( + LinkedLeafStruct.fromValue(leaf.leaf) + ) + .hash() + .toBigInt(); + + if (treeValue !== leafHash) { + return false; + } + if (leaf.leaf.nextPath <= currentPath) { + return false; + } + + currentPath = leaf.leaf.nextPath; + } + return true; + } +} diff --git a/packages/sequencer/test/integration/BlockProduction-test.ts b/packages/sequencer/test/integration/BlockProduction-test.ts new file mode 100644 index 000000000..d1c8af2da --- /dev/null +++ b/packages/sequencer/test/integration/BlockProduction-test.ts @@ -0,0 +1,757 @@ +import { + log, + range, + MOCK_PROOF, + expectDefined, + mapSequential, + TypedClass, +} from "@proto-kit/common"; +import { VanillaProtocolModules } from "@proto-kit/library"; +import { + Runtime, + runtimeMethod, + RuntimeModule, + runtimeModule, + RuntimeEvents, +} from "@proto-kit/module"; +import { + AccountState, + MandatoryProtocolModulesRecord, + Path, + Protocol, + PROTOKIT_PREFIXES, +} from "@proto-kit/protocol"; +import { AppChain } from "@proto-kit/sdk"; +import { Bool, Field, PrivateKey, PublicKey, Struct, UInt64 } from "o1js"; +import "reflect-metadata"; +import { container } from "tsyringe"; + +import { + BatchStorage, + HistoricalBatchStorage, + Sequencer, + SequencerModule, + VanillaTaskWorkerModules, + DatabasePruneModule, + AsyncLinkedLeafStore, +} from "../../src"; +import { + DefaultTestingSequencerModules, + testingSequencerModules, +} from "../TestingSequencer"; + +import { Balance } from "./mocks/Balance"; +import { ProtocolStateTestHook } from "./mocks/ProtocolStateTestHook"; +import { NoopRuntime } from "./mocks/NoopRuntime"; +import { BlockTestService } from "./services/BlockTestService"; +import { infer } from "ts-pattern/dist/patterns"; +import { afterEach } from "@jest/globals"; +import { LinkedMerkleTreeIntegrity } from "../LinkedMerkleTreeIntegrity"; + +export class PrimaryTestEvent extends Struct({ + message: Bool, +}) {} + +export class SecondaryTestEvent extends Struct({ + message: Bool, +}) {} + +@runtimeModule() +class EventMaker extends RuntimeModule { + public constructor() { + super(); + } + + public events = new RuntimeEvents({ + primary: PrimaryTestEvent, + secondary: SecondaryTestEvent, + }); + + @runtimeMethod() + public async makeEvent() { + this.events.emit("primary", new PrimaryTestEvent({ message: Bool(false) })); + // Should not emit as condition is false. + this.events.emitIf( + Bool(false), + "primary", + new PrimaryTestEvent({ message: Bool(false) }) + ); + this.events.emit( + "secondary", + new SecondaryTestEvent({ message: Bool(true) }) + ); + } +} + +export function testBlockProduction< + T extends TypedClass>, +>( + database: T, + databaseConfig: T extends TypedClass + ? Module extends SequencerModule + ? Config + : never + : never +) { + let runtime: Runtime<{ + Balance: typeof Balance; + NoopRuntime: typeof NoopRuntime; + EventMaker: typeof EventMaker; + }>; + let sequencer: Sequencer; + + let protocol: Protocol< + MandatoryProtocolModulesRecord & { + ProtocolStateTestHook: typeof ProtocolStateTestHook; + } + >; + + let appChain: AppChain; + + let test: BlockTestService; + let linkedLeafStore: AsyncLinkedLeafStore; + + beforeEach(async () => { + const runtimeClass = Runtime.from({ + modules: { + Balance, + NoopRuntime, + EventMaker, + }, + + config: { + Balance: {}, + NoopRuntime: {}, + EventMaker: {}, + }, + }); + + const sequencerClass = Sequencer.from({ + modules: { + DatabasePruneModule, + ...testingSequencerModules({}), + Database: database, + }, + }); + + // TODO Analyze how we can get rid of the library import for mandatory modules + const protocolClass = Protocol.from({ + modules: VanillaProtocolModules.mandatoryModules({ + ProtocolStateTestHook, + }), + }); + + const app = AppChain.from({ + Runtime: runtimeClass, + Sequencer: sequencerClass, + Protocol: protocolClass, + modules: {}, + }); + + app.configure({ + Sequencer: { + DatabasePruneModule: { + pruneOnStartup: true, + }, + Database: databaseConfig, + BlockTrigger: {}, + Mempool: {}, + BatchProducerModule: {}, + BlockProducerModule: {}, + LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(), + BaseLayer: {}, + TaskQueue: {}, + FeeStrategy: {}, + SequencerStartupModule: {}, + }, + Runtime: { + Balance: {}, + NoopRuntime: {}, + EventMaker: {}, + }, + Protocol: { + AccountState: {}, + BlockProver: {}, + StateTransitionProver: {}, + BlockHeight: {}, + LastStateRoot: {}, + ProtocolStateTestHook: {}, + }, + }); + + try { + // Start AppChain + await app.start(false, container.createChildContainer()); + } catch (e) { + console.error(e); + throw e; + } + + appChain = app; + + // @ts-ignore + ({ runtime, sequencer, protocol } = app); + + test = app.sequencer.dependencyContainer.resolve(BlockTestService); + + linkedLeafStore = + app.sequencer.dependencyContainer.resolve( + "UnprovenLinkedLeafStore" + ); + }); + + afterEach(async () => { + await appChain.close(); + }); + + it("should produce a dummy block proof", async () => { + log.setLevel("DEBUG"); + expect.assertions(27); + + const privateKey = PrivateKey.random(); + const publicKey = privateKey.toPublicKey(); + + await test.addTransaction({ + method: ["Balance", "setBalanceIf"], + privateKey, + args: [publicKey, UInt64.from(100), Bool(true)], + }); + + // let [block, batch] = await blockTrigger.produceBlockAndBatch(); + let block = await test.produceBlock(); + + expect(block).toBeDefined(); + + expect(block!.transactions).toHaveLength(1); + expect(block!.transactions[0].status.toBoolean()).toBe(true); + expect(block!.transactions[0].statusMessage).toBeUndefined(); + + expect(block!.transactions[0].stateTransitions).toHaveLength(3); + expect( + block!.transactions[0].stateTransitions[0].stateTransitions + ).toHaveLength(2); + expect( + block!.transactions[0].stateTransitions[1].stateTransitions + ).toHaveLength(1); + + const latestBlockWithResult = await sequencer + .resolve("BlockQueue") + .getLatestBlockAndResult(); + + let batch = await test.produceBatch(); + + expect(batch).toBeDefined(); + + expect(batch!.blockHashes).toHaveLength(1); + expect(batch!.proof.proof).toBe(MOCK_PROOF); + + expectDefined(latestBlockWithResult); + expectDefined(latestBlockWithResult.result); + expect( + latestBlockWithResult.result.afterNetworkState.hash().toString() + ).toStrictEqual(batch!.toNetworkState.hash().toString()); + + // Check if the batchstorage has received the block + const batchStorage = sequencer.resolve("BatchStorage") as BatchStorage & + HistoricalBatchStorage; + const retrievedBatch = await batchStorage.getBatchAt(0); + expect(retrievedBatch).toBeDefined(); + + const balanceModule = runtime.resolve("Balance"); + const balancesPath = Path.fromKey( + balanceModule.balances.path!, + balanceModule.balances.keyType, + publicKey + ); + // TODO + // const newState = await test.getState(balancesPath, "batch"); + const newUnprovenState = await test.getState(balancesPath, "block"); + + // expect(newState).toBeDefined(); + expect(newUnprovenState).toBeDefined(); + // expect(UInt64.fromFields(newState!).toString()).toStrictEqual("100"); + expect(UInt64.fromFields(newUnprovenState!).toString()).toStrictEqual( + "100" + ); + + // Check that nonce has been set + const accountModule = protocol.resolve("AccountState"); + const accountStatePath = Path.fromKey( + accountModule.accountState.path!, + accountModule.accountState.keyType, + publicKey + ); + const newAccountState = await test.getState(accountStatePath, "block"); + + expect(newAccountState).toBeDefined(); + expect(AccountState.fromFields(newAccountState!).nonce.toBigInt()).toBe(1n); + + // Second tx + await test.addTransaction({ + method: ["Balance", "addBalanceToSelf"], + privateKey, + args: [UInt64.from(100), UInt64.from(1)], + }); + + log.info("Starting second block"); + + [block, batch] = await test.produceBlockAndBatch(); + + expect(block).toBeDefined(); + + expect(block!.transactions).toHaveLength(1); + expect(block!.transactions[0].status.toBoolean()).toBe(true); + expect(block!.transactions[0].statusMessage).toBeUndefined(); + + expect(batch!.blockHashes).toHaveLength(1); + expect(batch!.proof.proof).toBe(MOCK_PROOF); + + const state2 = await test.getState(balancesPath, "block"); + + expect(state2).toBeDefined(); + expect(UInt64.fromFields(state2!)).toStrictEqual(UInt64.from(200)); + + await expect( + LinkedMerkleTreeIntegrity.checkIntegrity(linkedLeafStore) + ).resolves.toBe(true); + }, 60_000); + + it("should reject tx and not apply the state", async () => { + expect.assertions(6); + + const privateKey = PrivateKey.random(); + + await test.addTransaction({ + method: ["Balance", "setBalanceIf"], + privateKey, + args: [PrivateKey.random().toPublicKey(), UInt64.from(100), Bool(false)], + }); + + const [block] = await test.produceBlockAndBatch(); + + expect(block?.transactions).toHaveLength(1); + expect(block?.transactions[0].status.toBoolean()).toBe(false); + expect(block?.transactions[0].statusMessage).toBe("Condition not met"); + + const balanceModule = runtime.resolve("Balance"); + const balancesPath = Path.fromKey( + balanceModule.balances.path!, + balanceModule.balances.keyType, + PublicKey.empty() + ); + const unprovenState = await test.getState(balancesPath, "block"); + const newState = await test.getState(balancesPath, "batch"); + + // Assert that state is not set + expect(unprovenState).toBeUndefined(); + expect(newState).toBeUndefined(); + + await expect( + LinkedMerkleTreeIntegrity.checkIntegrity(linkedLeafStore) + ).resolves.toBe(true); + }, 30_000); + + it("should produce txs in non-consecutive blocks", async () => { + const privateKey = PrivateKey.random(); + const publicKey = privateKey.toPublicKey(); + + const privateKey2 = PrivateKey.random(); + const publicKey2 = privateKey2.toPublicKey(); + + await test.addTransaction({ + method: ["Balance", "setBalanceIf"], + privateKey, + args: [publicKey, UInt64.from(100), Bool(true)], + }); + + // let [block, batch] = await blockTrigger.produceBlockAndBatch(); + const block = await test.produceBlock(); + + expect(block).toBeDefined(); + + expect(block!.transactions).toHaveLength(1); + expect(block!.transactions[0].status.toBoolean()).toBe(true); + expect(block!.transactions[0].statusMessage).toBeUndefined(); + + expect( + block!.transactions[0].stateTransitions[0].stateTransitions + ).toHaveLength(2); + expect( + block!.transactions[0].stateTransitions[1].stateTransitions + ).toHaveLength(1); + + await test.produceBlock(); + + await test.addTransaction({ + method: ["Balance", "setBalanceIf"], + privateKey: privateKey2, + args: [publicKey2, UInt64.from(100), Bool(true)], + }); + await test.produceBlock(); + + await test.addTransaction({ + method: ["Balance", "setBalanceIf"], + privateKey: privateKey2, + args: [publicKey2, UInt64.from(100), Bool(true)], + }); + + await test.produceBlock(); + + await test.addTransaction({ + method: ["Balance", "setBalanceIf"], + privateKey: privateKey2, + args: [publicKey2, UInt64.from(100), Bool(true)], + }); + + await test.produceBlock(); + + await test.addTransaction({ + method: ["Balance", "setBalanceIf"], + privateKey: privateKey2, + args: [publicKey2, UInt64.from(100), Bool(true)], + }); + await test.produceBlock(); + + // Second tx + await test.addTransaction({ + method: ["Balance", "setBalanceIf"], + privateKey, + args: [publicKey, UInt64.from(100), Bool(true)], + }); + + log.info("Starting second block"); + + const block2 = await test.produceBlock(); + + expect(block2).toBeDefined(); + + expect(block2!.transactions).toHaveLength(1); + expect(block2!.transactions[0].status.toBoolean()).toBe(true); + expect(block2!.transactions[0].statusMessage).toBeUndefined(); + + await expect( + LinkedMerkleTreeIntegrity.checkIntegrity(linkedLeafStore) + ).resolves.toBe(true); + }, 60_000); + + const numberTxs = 3; + + it("should produce block with multiple transaction", async () => { + log.setLevel("TRACE"); + + expect.assertions(6 + 4 * numberTxs); + + const privateKey = PrivateKey.random(); + const publicKey = privateKey.toPublicKey(); + + const increment = 100; + + await mapSequential(range(0, numberTxs), async (index) => { + await test.addTransaction({ + method: ["Balance", "addBalanceToSelf"], + privateKey, + args: [UInt64.from(increment), UInt64.from(0)], + }); + }); + + const block = await test.produceBlock(); + + expect(block).toBeDefined(); + expect(block!.transactions).toHaveLength(numberTxs); + + range(0, numberTxs).forEach((index) => { + expect(block!.transactions[index].status.toBoolean()).toBe(true); + expect(block!.transactions[index].statusMessage).toBe(undefined); + + const transitions = + block!.transactions[index].stateTransitions[1].stateTransitions; + + const fromBalance = increment * index; + expect(transitions[0].fromValue.value[0].toBigInt()).toStrictEqual( + BigInt(fromBalance) + ); + expect(transitions[0].toValue.value[0].toBigInt()).toStrictEqual( + BigInt(fromBalance + increment) + ); + }); + + const batch = await test.produceBatch(); + + expect(batch!.blockHashes).toHaveLength(1); + expect(batch!.proof.proof).toBe(MOCK_PROOF); + + const balanceModule = runtime.resolve("Balance"); + const balancesPath = Path.fromKey( + balanceModule.balances.path!, + balanceModule.balances.keyType, + publicKey + ); + const newState = await test.getState(balancesPath, "block"); + + expect(newState).toBeDefined(); + expect(UInt64.fromFields(newState!)).toStrictEqual( + UInt64.from(100 * numberTxs) + ); + }, 160_000); + + it("should produce a block with a mix of failing and succeeding transactions and empty blocks", async () => { + expect.assertions(7); + + log.setLevel("TRACE"); + + const pk1 = PrivateKey.random(); + const pk2 = PrivateKey.random(); + + await test.addTransaction({ + method: ["Balance", "setBalanceIf"], + privateKey: pk1, + args: [pk1.toPublicKey(), UInt64.from(100), Bool(false)], + }); + await test.addTransaction({ + method: ["Balance", "setBalanceIf"], + privateKey: pk2, + args: [pk2.toPublicKey(), UInt64.from(100), Bool(true)], + }); + + const block = await test.produceBlock(); + await test.produceBlock(); + const batch = await test.produceBatch(); + + console.log("Pt1"); + + expect(block).toBeDefined(); + + expect(batch!.blockHashes).toHaveLength(2); + expect(block!.transactions).toHaveLength(2); + + const balanceModule = runtime.resolve("Balance"); + const balancesPath1 = Path.fromKey( + balanceModule.balances.path!, + balanceModule.balances.keyType, + pk1.toPublicKey() + ); + const newState1 = await test.getState(balancesPath1, "block"); + + expect(newState1).toBeUndefined(); + + const balancesPath2 = Path.fromKey( + balanceModule.balances.path!, + balanceModule.balances.keyType, + pk2.toPublicKey() + ); + const newState2 = await test.getState(balancesPath2, "block"); + + expect(newState2).toBeDefined(); + expect(UInt64.fromFields(newState2!)).toStrictEqual(UInt64.from(100)); + + await test.produceBlock(); + await test.produceBlock(); + const proven2 = await test.produceBatch(); + + expect(proven2?.blockHashes.length).toBe(2); + }, 720_000); + + // TODO Test with batch that only consists of empty blocks + + it.each([ + [2, 1, 1], + [1, 2, 1], + [1, 1, 2], + [2, 2, 2], + [1, 14, 0], + ])( + "should produce multiple blocks with multiple batches with multiple transactions", + async (batches, blocksPerBatch, txsPerBlock) => { + expect.assertions( + 2 * batches + + 1 * batches * blocksPerBatch + + 2 * batches * blocksPerBatch * txsPerBlock + ); + + log.setLevel("DEBUG"); + + const sender = PrivateKey.random(); + + const keys = range(0, batches * blocksPerBatch * txsPerBlock).map(() => + PrivateKey.random() + ); + + const increment = 100; + + let iterationIndex = 0; + + for (let i = 0; i < batches; i++) { + for (let j = 0; j < blocksPerBatch; j++) { + for (let k = 0; k < txsPerBlock; k++) { + await test.addTransaction({ + method: ["Balance", "addBalance"], + privateKey: sender, + args: [ + keys[iterationIndex].toPublicKey(), + UInt64.from(increment * (iterationIndex + 1)), + ], + }); + + iterationIndex += 1; + } + + // Produce block + const block = await test.produceBlock(); + + expect(block).toBeDefined(); + + for (let k = 0; k < txsPerBlock; k++) { + expect(block!.transactions).toHaveLength(txsPerBlock); + expect(block!.transactions[0].status.toBoolean()).toBe(true); + } + } + + const batch = await test.produceBatch(); + + expect(batch).toBeDefined(); + expect(batch!.blockHashes).toHaveLength(blocksPerBatch); + } + }, + 500_000 + ); + + it("should produce block with a tx with a lot of STs", async () => { + expect.assertions(11); + + const privateKey = PrivateKey.random(); + + const field = Field(100); + + await test.addTransaction({ + method: ["Balance", "lotOfSTs"], + privateKey, + args: [field], + }); + + const [block, batch] = await test.produceBlockAndBatch(); + + expect(block).toBeDefined(); + expect(batch).toBeDefined(); + + expect(block!.transactions).toHaveLength(1); + + expect(block!.transactions[0].status.toBoolean()).toBe(true); + expect(block!.transactions[0].statusMessage).toBe(undefined); + + expect(batch!.blockHashes).toHaveLength(1); + expect(batch!.proof.proof).toBe(MOCK_PROOF); + + const supplyPath = Path.fromProperty( + "Balance", + "totalSupply", + PROTOKIT_PREFIXES.STATE_RUNTIME + ); + const newState = await test.getState(supplyPath, "block"); + + expect(newState).toBeDefined(); + expect(UInt64.fromFields(newState!)).toStrictEqual( + // 10 is the number of iterations inside the runtime method + UInt64.from(100 * 10) + ); + + const pk2 = PublicKey.from({ x: field.add(Field(2)), isOdd: Bool(false) }); + const balanceModule = runtime.resolve("Balance"); + const balancesPath = Path.fromKey( + balanceModule.balances.path!, + balanceModule.balances.keyType, + pk2 + ); + + const newBalance = await test.getState(balancesPath, "block"); + + expect(newBalance).toBeDefined(); + expect(UInt64.fromFields(newBalance!)).toStrictEqual(UInt64.from(200)); + }, 360_000); + + it("regression - should produce block with no STs emitted", async () => { + const privateKey = PrivateKey.random(); + + await test.addTransaction({ + method: ["NoopRuntime", "emittingNoSTs"], + privateKey, + args: [], + }); + + const block = await test.produceBlock(); + + expect(block).toBeDefined(); + + expect(block!.transactions).toHaveLength(1); + expect(block!.transactions[0].status.toBoolean()).toBe(true); + expect(block!.transactions[0].statusMessage).toBeUndefined(); + + expect( + block!.transactions[0].stateTransitions[0].stateTransitions + ).toHaveLength(2); + expect( + block!.transactions[0].stateTransitions[1].stateTransitions + ).toHaveLength(0); + + const batch = await test.produceBatch(); + + expect(batch).toBeDefined(); + + expect(batch!.blockHashes).toHaveLength(1); + expect(batch!.proof.proof).toBe(MOCK_PROOF); + }, 30000); + + it("events - should produce block with the right events", async () => { + log.setLevel("TRACE"); + + const privateKey = PrivateKey.random(); + + await test.addTransaction({ + method: ["EventMaker", "makeEvent"], + privateKey, + args: [], + }); + + const firstExpectedEvent = { + eventType: PrimaryTestEvent, + event: new PrimaryTestEvent({ + message: Bool(false), + }), + eventName: "primary", + }; + + const secondExpectedEvent = { + eventType: SecondaryTestEvent, + event: new SecondaryTestEvent({ + message: Bool(true), + }), + eventName: "secondary", + }; + const firstEventReduced = { + eventName: firstExpectedEvent.eventName, + data: firstExpectedEvent.eventType.toFields(firstExpectedEvent.event), + source: "runtime", + }; + + const secondEventReduced = { + eventName: secondExpectedEvent.eventName, + data: secondExpectedEvent.eventType.toFields(secondExpectedEvent.event), + source: "runtime", + }; + + const block = await test.produceBlock(); + + expect(block).toBeDefined(); + + expect(block!.transactions).toHaveLength(1); + expect(block!.transactions[0].events).toHaveLength(2); + expect(block!.transactions[0].events[0]).toStrictEqual(firstEventReduced); + expect(block!.transactions[0].events[1]).toStrictEqual(secondEventReduced); + + const batch = await test.produceBatch(); + + expect(batch).toBeDefined(); + + expect(batch!.blockHashes).toHaveLength(1); + expect(batch!.proof.proof).toBe(MOCK_PROOF); + }, 30000); +} diff --git a/packages/sequencer/test/integration/BlockProduction.test.ts b/packages/sequencer/test/integration/BlockProduction.test.ts index cbee1003f..1c1b62852 100644 --- a/packages/sequencer/test/integration/BlockProduction.test.ts +++ b/packages/sequencer/test/integration/BlockProduction.test.ts @@ -1,715 +1,6 @@ -import { - log, - range, - MOCK_PROOF, - expectDefined, - mapSequential, -} from "@proto-kit/common"; -import { VanillaProtocolModules } from "@proto-kit/library"; -import { - Runtime, - runtimeMethod, - RuntimeModule, - runtimeModule, - RuntimeEvents, -} from "@proto-kit/module"; -import { - AccountState, - MandatoryProtocolModulesRecord, - Path, - Protocol, - PROTOKIT_PREFIXES, -} from "@proto-kit/protocol"; -import { AppChain } from "@proto-kit/sdk"; -import { Bool, Field, PrivateKey, PublicKey, Struct, UInt64 } from "o1js"; -import "reflect-metadata"; -import { container } from "tsyringe"; - -import { - BatchStorage, - HistoricalBatchStorage, - Sequencer, - VanillaTaskWorkerModules, -} from "../../src"; -import { - DefaultTestingSequencerModules, - testingSequencerModules, -} from "../TestingSequencer"; - -import { Balance } from "./mocks/Balance"; -import { ProtocolStateTestHook } from "./mocks/ProtocolStateTestHook"; -import { NoopRuntime } from "./mocks/NoopRuntime"; -import { BlockTestService } from "./services/BlockTestService"; - -export class PrimaryTestEvent extends Struct({ - message: Bool, -}) {} - -export class SecondaryTestEvent extends Struct({ - message: Bool, -}) {} - -@runtimeModule() -class EventMaker extends RuntimeModule { - public constructor() { - super(); - } - - public events = new RuntimeEvents({ - primary: PrimaryTestEvent, - secondary: SecondaryTestEvent, - }); - - @runtimeMethod() - public async makeEvent() { - this.events.emit("primary", new PrimaryTestEvent({ message: Bool(false) })); - // Should not emit as condition is false. - this.events.emitIf( - Bool(false), - "primary", - new PrimaryTestEvent({ message: Bool(false) }) - ); - this.events.emit( - "secondary", - new SecondaryTestEvent({ message: Bool(true) }) - ); - } -} +import { InMemoryDatabase } from "../../src"; +import { testBlockProduction } from "./BlockProduction-test"; describe("block production", () => { - let runtime: Runtime<{ - Balance: typeof Balance; - NoopRuntime: typeof NoopRuntime; - EventMaker: typeof EventMaker; - }>; - let sequencer: Sequencer; - - let protocol: Protocol< - MandatoryProtocolModulesRecord & { - ProtocolStateTestHook: typeof ProtocolStateTestHook; - } - >; - // let protocol: Protocol; - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - let appChain: AppChain; - - let test: BlockTestService; - - beforeEach(async () => { - const runtimeClass = Runtime.from({ - modules: { - Balance, - NoopRuntime, - EventMaker, - }, - - config: { - Balance: {}, - NoopRuntime: {}, - EventMaker: {}, - }, - }); - - const sequencerClass = Sequencer.from({ - modules: testingSequencerModules({}), - }); - - // TODO Analyze how we can get rid of the library import for mandatory modules - const protocolClass = Protocol.from({ - modules: VanillaProtocolModules.mandatoryModules({ - ProtocolStateTestHook, - }), - // modules: VanillaProtocolModules.with({}), - }); - - const app = AppChain.from({ - Runtime: runtimeClass, - Sequencer: sequencerClass, - Protocol: protocolClass, - modules: {}, - }); - - app.configure({ - Sequencer: { - Database: {}, - BlockTrigger: {}, - Mempool: {}, - BatchProducerModule: {}, - BlockProducerModule: {}, - LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(), - BaseLayer: {}, - TaskQueue: {}, - FeeStrategy: {}, - SequencerStartupModule: {}, - }, - Runtime: { - Balance: {}, - NoopRuntime: {}, - EventMaker: {}, - }, - Protocol: { - AccountState: {}, - BlockProver: {}, - StateTransitionProver: {}, - BlockHeight: {}, - LastStateRoot: {}, - ProtocolStateTestHook: {}, - }, - }); - - try { - // Start AppChain - await app.start(false, container.createChildContainer()); - } catch (e) { - console.error(e); - throw e; - } - - appChain = app; - - // @ts-ignore - ({ runtime, sequencer, protocol } = app); - - test = app.sequencer.dependencyContainer.resolve(BlockTestService); - }); - - it("should produce a dummy block proof", async () => { - log.setLevel("DEBUG"); - expect.assertions(26); - - const privateKey = PrivateKey.random(); - const publicKey = privateKey.toPublicKey(); - - await test.addTransaction({ - method: ["Balance", "setBalanceIf"], - privateKey, - args: [publicKey, UInt64.from(100), Bool(true)], - }); - - // let [block, batch] = await blockTrigger.produceBlockAndBatch(); - let block = await test.produceBlock(); - - expect(block).toBeDefined(); - - expect(block!.transactions).toHaveLength(1); - expect(block!.transactions[0].status.toBoolean()).toBe(true); - expect(block!.transactions[0].statusMessage).toBeUndefined(); - - expect(block!.transactions[0].stateTransitions).toHaveLength(3); - expect( - block!.transactions[0].stateTransitions[0].stateTransitions - ).toHaveLength(2); - expect( - block!.transactions[0].stateTransitions[1].stateTransitions - ).toHaveLength(1); - - const latestBlockWithResult = await sequencer - .resolve("BlockQueue") - .getLatestBlockAndResult(); - - let batch = await test.produceBatch(); - - expect(batch).toBeDefined(); - - expect(batch!.blockHashes).toHaveLength(1); - expect(batch!.proof.proof).toBe(MOCK_PROOF); - - expectDefined(latestBlockWithResult); - expectDefined(latestBlockWithResult.result); - expect( - latestBlockWithResult.result.afterNetworkState.hash().toString() - ).toStrictEqual(batch!.toNetworkState.hash().toString()); - - // Check if the batchstorage has received the block - const batchStorage = sequencer.resolve("BatchStorage") as BatchStorage & - HistoricalBatchStorage; - const retrievedBatch = await batchStorage.getBatchAt(0); - expect(retrievedBatch).toBeDefined(); - - const balanceModule = runtime.resolve("Balance"); - const balancesPath = Path.fromKey( - balanceModule.balances.path!, - balanceModule.balances.keyType, - publicKey - ); - // TODO - // const newState = await test.getState(balancesPath, "batch"); - const newUnprovenState = await test.getState(balancesPath, "block"); - - // expect(newState).toBeDefined(); - expect(newUnprovenState).toBeDefined(); - // expect(UInt64.fromFields(newState!).toString()).toStrictEqual("100"); - expect(UInt64.fromFields(newUnprovenState!).toString()).toStrictEqual( - "100" - ); - - // Check that nonce has been set - const accountModule = protocol.resolve("AccountState"); - const accountStatePath = Path.fromKey( - accountModule.accountState.path!, - accountModule.accountState.keyType, - publicKey - ); - const newAccountState = await test.getState(accountStatePath, "block"); - - expect(newAccountState).toBeDefined(); - expect(AccountState.fromFields(newAccountState!).nonce.toBigInt()).toBe(1n); - - // Second tx - await test.addTransaction({ - method: ["Balance", "addBalanceToSelf"], - privateKey, - args: [UInt64.from(100), UInt64.from(1)], - }); - - log.info("Starting second block"); - - [block, batch] = await test.produceBlockAndBatch(); - - expect(block).toBeDefined(); - - expect(block!.transactions).toHaveLength(1); - expect(block!.transactions[0].status.toBoolean()).toBe(true); - expect(block!.transactions[0].statusMessage).toBeUndefined(); - - expect(batch!.blockHashes).toHaveLength(1); - expect(batch!.proof.proof).toBe(MOCK_PROOF); - - const state2 = await test.getState(balancesPath, "block"); - - expect(state2).toBeDefined(); - expect(UInt64.fromFields(state2!)).toStrictEqual(UInt64.from(200)); - }, 60_000); - - it("should reject tx and not apply the state", async () => { - expect.assertions(5); - - const privateKey = PrivateKey.random(); - - await test.addTransaction({ - method: ["Balance", "setBalanceIf"], - privateKey, - args: [PrivateKey.random().toPublicKey(), UInt64.from(100), Bool(false)], - }); - - const [block] = await test.produceBlockAndBatch(); - - expect(block?.transactions).toHaveLength(1); - expect(block?.transactions[0].status.toBoolean()).toBe(false); - expect(block?.transactions[0].statusMessage).toBe("Condition not met"); - - const balanceModule = runtime.resolve("Balance"); - const balancesPath = Path.fromKey( - balanceModule.balances.path!, - balanceModule.balances.keyType, - PublicKey.empty() - ); - const unprovenState = await test.getState(balancesPath, "block"); - const newState = await test.getState(balancesPath, "batch"); - - // Assert that state is not set - expect(unprovenState).toBeUndefined(); - expect(newState).toBeUndefined(); - }, 30_000); - - it("should produce txs in non-consecutive blocks", async () => { - const privateKey = PrivateKey.random(); - const publicKey = privateKey.toPublicKey(); - - const privateKey2 = PrivateKey.random(); - const publicKey2 = privateKey2.toPublicKey(); - - await test.addTransaction({ - method: ["Balance", "setBalanceIf"], - privateKey, - args: [publicKey, UInt64.from(100), Bool(true)], - }); - - // let [block, batch] = await blockTrigger.produceBlockAndBatch(); - const block = await test.produceBlock(); - - expect(block).toBeDefined(); - - expect(block!.transactions).toHaveLength(1); - expect(block!.transactions[0].status.toBoolean()).toBe(true); - expect(block!.transactions[0].statusMessage).toBeUndefined(); - - expect( - block!.transactions[0].stateTransitions[0].stateTransitions - ).toHaveLength(2); - expect( - block!.transactions[0].stateTransitions[1].stateTransitions - ).toHaveLength(1); - - await test.produceBlock(); - - await test.addTransaction({ - method: ["Balance", "setBalanceIf"], - privateKey: privateKey2, - args: [publicKey2, UInt64.from(100), Bool(true)], - }); - await test.produceBlock(); - - await test.addTransaction({ - method: ["Balance", "setBalanceIf"], - privateKey: privateKey2, - args: [publicKey2, UInt64.from(100), Bool(true)], - }); - - await test.produceBlock(); - - await test.addTransaction({ - method: ["Balance", "setBalanceIf"], - privateKey: privateKey2, - args: [publicKey2, UInt64.from(100), Bool(true)], - }); - - await test.produceBlock(); - - await test.addTransaction({ - method: ["Balance", "setBalanceIf"], - privateKey: privateKey2, - args: [publicKey2, UInt64.from(100), Bool(true)], - }); - await test.produceBlock(); - - // Second tx - await test.addTransaction({ - method: ["Balance", "setBalanceIf"], - privateKey, - args: [publicKey, UInt64.from(100), Bool(true)], - }); - - log.info("Starting second block"); - - const block2 = await test.produceBlock(); - - expect(block2).toBeDefined(); - - expect(block2!.transactions).toHaveLength(1); - expect(block2!.transactions[0].status.toBoolean()).toBe(true); - expect(block2!.transactions[0].statusMessage).toBeUndefined(); - }, 60_000); - - const numberTxs = 3; - - it("should produce block with multiple transaction", async () => { - log.setLevel("TRACE"); - - expect.assertions(6 + 4 * numberTxs); - - const privateKey = PrivateKey.random(); - const publicKey = privateKey.toPublicKey(); - - const increment = 100; - - await mapSequential(range(0, numberTxs), async (index) => { - await test.addTransaction({ - method: ["Balance", "addBalanceToSelf"], - privateKey, - args: [UInt64.from(increment), UInt64.from(0)], - }); - }); - - const block = await test.produceBlock(); - - expect(block).toBeDefined(); - expect(block!.transactions).toHaveLength(numberTxs); - - range(0, numberTxs).forEach((index) => { - expect(block!.transactions[index].status.toBoolean()).toBe(true); - expect(block!.transactions[index].statusMessage).toBe(undefined); - - const transitions = - block!.transactions[index].stateTransitions[1].stateTransitions; - - const fromBalance = increment * index; - expect(transitions[0].fromValue.value[0].toBigInt()).toStrictEqual( - BigInt(fromBalance) - ); - expect(transitions[0].toValue.value[0].toBigInt()).toStrictEqual( - BigInt(fromBalance + increment) - ); - }); - - const batch = await test.produceBatch(); - - expect(batch!.blockHashes).toHaveLength(1); - expect(batch!.proof.proof).toBe(MOCK_PROOF); - - const balanceModule = runtime.resolve("Balance"); - const balancesPath = Path.fromKey( - balanceModule.balances.path!, - balanceModule.balances.keyType, - publicKey - ); - const newState = await test.getState(balancesPath, "block"); - - expect(newState).toBeDefined(); - expect(UInt64.fromFields(newState!)).toStrictEqual( - UInt64.from(100 * numberTxs) - ); - }, 160_000); - - it("should produce a block with a mix of failing and succeeding transactions and empty blocks", async () => { - expect.assertions(7); - - log.setLevel("TRACE"); - - const pk1 = PrivateKey.random(); - const pk2 = PrivateKey.random(); - - await test.addTransaction({ - method: ["Balance", "setBalanceIf"], - privateKey: pk1, - args: [pk1.toPublicKey(), UInt64.from(100), Bool(false)], - }); - await test.addTransaction({ - method: ["Balance", "setBalanceIf"], - privateKey: pk2, - args: [pk2.toPublicKey(), UInt64.from(100), Bool(true)], - }); - - const block = await test.produceBlock(); - await test.produceBlock(); - const batch = await test.produceBatch(); - - console.log("Pt1"); - - expect(block).toBeDefined(); - - expect(batch!.blockHashes).toHaveLength(2); - expect(block!.transactions).toHaveLength(2); - - const balanceModule = runtime.resolve("Balance"); - const balancesPath1 = Path.fromKey( - balanceModule.balances.path!, - balanceModule.balances.keyType, - pk1.toPublicKey() - ); - const newState1 = await test.getState(balancesPath1, "block"); - - expect(newState1).toBeUndefined(); - - const balancesPath2 = Path.fromKey( - balanceModule.balances.path!, - balanceModule.balances.keyType, - pk2.toPublicKey() - ); - const newState2 = await test.getState(balancesPath2, "block"); - - expect(newState2).toBeDefined(); - expect(UInt64.fromFields(newState2!)).toStrictEqual(UInt64.from(100)); - - await test.produceBlock(); - await test.produceBlock(); - const proven2 = await test.produceBatch(); - - expect(proven2?.blockHashes.length).toBe(2); - }, 720_000); - - // TODO Test with batch that only consists of empty blocks - - it.each([ - [2, 1, 1], - [1, 2, 1], - [1, 1, 2], - [2, 2, 2], - [1, 14, 0], - ])( - "should produce multiple blocks with multiple batches with multiple transactions", - async (batches, blocksPerBatch, txsPerBlock) => { - expect.assertions( - 2 * batches + - 1 * batches * blocksPerBatch + - 2 * batches * blocksPerBatch * txsPerBlock - ); - - log.setLevel("DEBUG"); - - const sender = PrivateKey.random(); - - const keys = range(0, batches * blocksPerBatch * txsPerBlock).map(() => - PrivateKey.random() - ); - - const increment = 100; - - let iterationIndex = 0; - - for (let i = 0; i < batches; i++) { - for (let j = 0; j < blocksPerBatch; j++) { - for (let k = 0; k < txsPerBlock; k++) { - await test.addTransaction({ - method: ["Balance", "addBalance"], - privateKey: sender, - args: [ - keys[iterationIndex].toPublicKey(), - UInt64.from(increment * (iterationIndex + 1)), - ], - }); - - iterationIndex += 1; - } - - // Produce block - const block = await test.produceBlock(); - - expect(block).toBeDefined(); - - for (let k = 0; k < txsPerBlock; k++) { - expect(block!.transactions).toHaveLength(txsPerBlock); - expect(block!.transactions[0].status.toBoolean()).toBe(true); - } - } - - const batch = await test.produceBatch(); - - expect(batch).toBeDefined(); - expect(batch!.blockHashes).toHaveLength(blocksPerBatch); - } - }, - 500_000 - ); - - it("should produce block with a tx with a lot of STs", async () => { - expect.assertions(11); - - const privateKey = PrivateKey.random(); - - const field = Field(100); - - await test.addTransaction({ - method: ["Balance", "lotOfSTs"], - privateKey, - args: [field], - }); - - const [block, batch] = await test.produceBlockAndBatch(); - - expect(block).toBeDefined(); - expect(batch).toBeDefined(); - - expect(block!.transactions).toHaveLength(1); - - expect(block!.transactions[0].status.toBoolean()).toBe(true); - expect(block!.transactions[0].statusMessage).toBe(undefined); - - expect(batch!.blockHashes).toHaveLength(1); - expect(batch!.proof.proof).toBe(MOCK_PROOF); - - const supplyPath = Path.fromProperty( - "Balance", - "totalSupply", - PROTOKIT_PREFIXES.STATE_RUNTIME - ); - const newState = await test.getState(supplyPath, "block"); - - expect(newState).toBeDefined(); - expect(UInt64.fromFields(newState!)).toStrictEqual( - // 10 is the number of iterations inside the runtime method - UInt64.from(100 * 10) - ); - - const pk2 = PublicKey.from({ x: field.add(Field(2)), isOdd: Bool(false) }); - const balanceModule = runtime.resolve("Balance"); - const balancesPath = Path.fromKey( - balanceModule.balances.path!, - balanceModule.balances.keyType, - pk2 - ); - - const newBalance = await test.getState(balancesPath, "block"); - - expect(newBalance).toBeDefined(); - expect(UInt64.fromFields(newBalance!)).toStrictEqual(UInt64.from(200)); - }, 360_000); - - it("regression - should produce block with no STs emitted", async () => { - const privateKey = PrivateKey.random(); - - await test.addTransaction({ - method: ["NoopRuntime", "emittingNoSTs"], - privateKey, - args: [], - }); - - const block = await test.produceBlock(); - - expect(block).toBeDefined(); - - expect(block!.transactions).toHaveLength(1); - expect(block!.transactions[0].status.toBoolean()).toBe(true); - expect(block!.transactions[0].statusMessage).toBeUndefined(); - - expect( - block!.transactions[0].stateTransitions[0].stateTransitions - ).toHaveLength(2); - expect( - block!.transactions[0].stateTransitions[1].stateTransitions - ).toHaveLength(0); - - const batch = await test.produceBatch(); - - expect(batch).toBeDefined(); - - expect(batch!.blockHashes).toHaveLength(1); - expect(batch!.proof.proof).toBe(MOCK_PROOF); - }, 30000); - - it("events - should produce block with the right events", async () => { - log.setLevel("TRACE"); - - const privateKey = PrivateKey.random(); - - await test.addTransaction({ - method: ["EventMaker", "makeEvent"], - privateKey, - args: [], - }); - - const firstExpectedEvent = { - eventType: PrimaryTestEvent, - event: new PrimaryTestEvent({ - message: Bool(false), - }), - eventName: "primary", - }; - - const secondExpectedEvent = { - eventType: SecondaryTestEvent, - event: new SecondaryTestEvent({ - message: Bool(true), - }), - eventName: "secondary", - }; - const firstEventReduced = { - eventName: firstExpectedEvent.eventName, - data: firstExpectedEvent.eventType.toFields(firstExpectedEvent.event), - source: "runtime", - }; - - const secondEventReduced = { - eventName: secondExpectedEvent.eventName, - data: secondExpectedEvent.eventType.toFields(secondExpectedEvent.event), - source: "runtime", - }; - - const block = await test.produceBlock(); - - expect(block).toBeDefined(); - - expect(block!.transactions).toHaveLength(1); - expect(block!.transactions[0].events).toHaveLength(2); - expect(block!.transactions[0].events[0]).toStrictEqual(firstEventReduced); - expect(block!.transactions[0].events[1]).toStrictEqual(secondEventReduced); - - const batch = await test.produceBatch(); - - expect(batch).toBeDefined(); - - expect(batch!.blockHashes).toHaveLength(1); - expect(batch!.proof.proof).toBe(MOCK_PROOF); - }, 30000); + testBlockProduction(InMemoryDatabase, {}); }); From ccdec44859a8e6e9ab4dc869bca90ef9bc2413de Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 12 Apr 2025 20:03:25 +0200 Subject: [PATCH 127/128] Fixed linting errors --- packages/sequencer/test/integration/BlockProduction-test.ts | 5 ++--- packages/sequencer/test/integration/BlockProduction.test.ts | 3 +++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/sequencer/test/integration/BlockProduction-test.ts b/packages/sequencer/test/integration/BlockProduction-test.ts index d1c8af2da..537d8e322 100644 --- a/packages/sequencer/test/integration/BlockProduction-test.ts +++ b/packages/sequencer/test/integration/BlockProduction-test.ts @@ -25,6 +25,7 @@ import { AppChain } from "@proto-kit/sdk"; import { Bool, Field, PrivateKey, PublicKey, Struct, UInt64 } from "o1js"; import "reflect-metadata"; import { container } from "tsyringe"; +import { afterEach } from "@jest/globals"; import { BatchStorage, @@ -39,14 +40,12 @@ import { DefaultTestingSequencerModules, testingSequencerModules, } from "../TestingSequencer"; +import { LinkedMerkleTreeIntegrity } from "../LinkedMerkleTreeIntegrity"; import { Balance } from "./mocks/Balance"; import { ProtocolStateTestHook } from "./mocks/ProtocolStateTestHook"; import { NoopRuntime } from "./mocks/NoopRuntime"; import { BlockTestService } from "./services/BlockTestService"; -import { infer } from "ts-pattern/dist/patterns"; -import { afterEach } from "@jest/globals"; -import { LinkedMerkleTreeIntegrity } from "../LinkedMerkleTreeIntegrity"; export class PrimaryTestEvent extends Struct({ message: Bool, diff --git a/packages/sequencer/test/integration/BlockProduction.test.ts b/packages/sequencer/test/integration/BlockProduction.test.ts index 1c1b62852..2ff7920dc 100644 --- a/packages/sequencer/test/integration/BlockProduction.test.ts +++ b/packages/sequencer/test/integration/BlockProduction.test.ts @@ -1,4 +1,7 @@ +import "reflect-metadata"; + import { InMemoryDatabase } from "../../src"; + import { testBlockProduction } from "./BlockProduction-test"; describe("block production", () => { From 4bb978ce9e6e87b4324252ef0b16950e6a75051b Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Mon, 14 Apr 2025 13:35:17 +0200 Subject: [PATCH 128/128] Renamed getLeafLessOrEqual to getPreviousLeaf --- packages/common/src/index.ts | 2 +- packages/common/src/trees/lmt/AbstractLinkedMerkleTree.ts | 2 +- packages/common/src/trees/lmt/InMemoryLinkedLeafStore.ts | 4 ++-- .../lmt/{LinkedLinkedStore.ts => LinkedLeafStore.ts} | 2 +- packages/common/src/trees/lmt/LinkedMerkleTree.ts | 4 ++-- .../src/services/prisma/PrismaLinkedLeafStore.ts | 2 +- .../sequencer/src/state/async/AsyncLinkedLeafStore.ts | 2 +- packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts | 8 ++++---- .../src/state/merkle/SyncCachedLinkedLeafStore.ts | 5 ++--- .../src/storage/inmemory/InMemoryAsyncLinkedLeafStore.ts | 8 ++++---- 10 files changed, 19 insertions(+), 20 deletions(-) rename packages/common/src/trees/lmt/{LinkedLinkedStore.ts => LinkedLeafStore.ts} (83%) diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 60968d4c8..cec176fcd 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -16,7 +16,7 @@ export * from "./events/EventEmitter"; export * from "./trees/sparse/MerkleTreeStore"; export * from "./trees/sparse/InMemoryMerkleTreeStorage"; export * from "./trees/sparse/RollupMerkleTree"; -export * from "./trees/lmt/LinkedLinkedStore"; +export * from "./trees/lmt/LinkedLeafStore"; export * from "./trees/lmt/LinkedMerkleTree"; export * from "./trees/lmt/InMemoryLinkedLeafStore"; export * from "./trees/lmt/LinkedMerkleTreeCircuitOps"; diff --git a/packages/common/src/trees/lmt/AbstractLinkedMerkleTree.ts b/packages/common/src/trees/lmt/AbstractLinkedMerkleTree.ts index d5209ad06..795dc5129 100644 --- a/packages/common/src/trees/lmt/AbstractLinkedMerkleTree.ts +++ b/packages/common/src/trees/lmt/AbstractLinkedMerkleTree.ts @@ -4,7 +4,7 @@ import { createMerkleTree, RollupMerkleTree } from "../sparse/RollupMerkleTree"; import { TypedClass } from "../../types"; import { MerkleTreeStore } from "../sparse/MerkleTreeStore"; -import { LinkedLeafStore } from "./LinkedLinkedStore"; +import { LinkedLeafStore } from "./LinkedLeafStore"; import { LinkedLeafStruct } from "./LinkedMerkleTreeTypes"; class RollupMerkleTreeWitness extends createMerkleTree(40).WITNESS {} diff --git a/packages/common/src/trees/lmt/InMemoryLinkedLeafStore.ts b/packages/common/src/trees/lmt/InMemoryLinkedLeafStore.ts index 684bac5f2..fa395f8ed 100644 --- a/packages/common/src/trees/lmt/InMemoryLinkedLeafStore.ts +++ b/packages/common/src/trees/lmt/InMemoryLinkedLeafStore.ts @@ -1,4 +1,4 @@ -import { LinkedLeafStore, LinkedLeaf } from "./LinkedLinkedStore"; +import { LinkedLeafStore, LinkedLeaf } from "./LinkedLeafStore"; export class InMemoryLinkedLeafStore implements LinkedLeafStore { public leaves: { @@ -31,7 +31,7 @@ export class InMemoryLinkedLeafStore implements LinkedLeafStore { } // This gets the leaf with the closest path. - public getLeafLessOrEqual( + public getPreviousLeaf( path: bigint ): { leaf: LinkedLeaf; index: bigint } | undefined { return Object.values(this.leaves).find( diff --git a/packages/common/src/trees/lmt/LinkedLinkedStore.ts b/packages/common/src/trees/lmt/LinkedLeafStore.ts similarity index 83% rename from packages/common/src/trees/lmt/LinkedLinkedStore.ts rename to packages/common/src/trees/lmt/LinkedLeafStore.ts index e0a10b397..a9c15ff5f 100644 --- a/packages/common/src/trees/lmt/LinkedLinkedStore.ts +++ b/packages/common/src/trees/lmt/LinkedLeafStore.ts @@ -7,7 +7,7 @@ export interface LinkedLeafStore { getLeaf: (path: bigint) => StoredLeaf | undefined; - getLeafLessOrEqual: (path: bigint) => StoredLeaf | undefined; + getPreviousLeaf: (path: bigint) => StoredLeaf | undefined; getMaximumIndex: () => bigint | undefined; } diff --git a/packages/common/src/trees/lmt/LinkedMerkleTree.ts b/packages/common/src/trees/lmt/LinkedMerkleTree.ts index 93bc395d8..213d5ae55 100644 --- a/packages/common/src/trees/lmt/LinkedMerkleTree.ts +++ b/packages/common/src/trees/lmt/LinkedMerkleTree.ts @@ -5,7 +5,7 @@ import { MerkleTreeStore } from "../sparse/MerkleTreeStore"; import { InMemoryMerkleTreeStorage } from "../sparse/InMemoryMerkleTreeStorage"; import { InMemoryLinkedLeafStore } from "./InMemoryLinkedLeafStore"; -import { LinkedLeaf, LinkedLeafStore } from "./LinkedLinkedStore"; +import { LinkedLeaf, LinkedLeafStore } from "./LinkedLeafStore"; import { LinkedLeafStruct } from "./LinkedMerkleTreeTypes"; import { AbstractLinkedMerkleTree, @@ -139,7 +139,7 @@ export function createLinkedMerkleTree( } const nextFreeIndex = tempIndex + 1n; - const previousLeaf = this.leafStore.getLeafLessOrEqual(path); + const previousLeaf = this.leafStore.getPreviousLeaf(path); if (previousLeaf === undefined) { throw Error(`Prev leaf shouldn't be undefined (path ${path})`); diff --git a/packages/persistance/src/services/prisma/PrismaLinkedLeafStore.ts b/packages/persistance/src/services/prisma/PrismaLinkedLeafStore.ts index 031a55083..1761854af 100644 --- a/packages/persistance/src/services/prisma/PrismaLinkedLeafStore.ts +++ b/packages/persistance/src/services/prisma/PrismaLinkedLeafStore.ts @@ -126,7 +126,7 @@ export class PrismaLinkedLeafStore implements AsyncLinkedLeafStore { : undefined; } - public async getLeavesLessOrEqualAsync(paths: bigint[]) { + public async getPreviousLeavesAsync(paths: bigint[]) { this.assertCacheEmpty(); const pathsDecimals = paths.map((path) => new Decimal(path.toString(10))); diff --git a/packages/sequencer/src/state/async/AsyncLinkedLeafStore.ts b/packages/sequencer/src/state/async/AsyncLinkedLeafStore.ts index 2153e0de9..5d9dcc0fb 100644 --- a/packages/sequencer/src/state/async/AsyncLinkedLeafStore.ts +++ b/packages/sequencer/src/state/async/AsyncLinkedLeafStore.ts @@ -15,7 +15,7 @@ export interface AsyncLinkedLeafStore { getMaximumIndexAsync: () => Promise; - getLeavesLessOrEqualAsync: ( + getPreviousLeavesAsync: ( path: bigint[] ) => Promise<(StoredLeaf | undefined)[]>; } diff --git a/packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts b/packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts index 595a9ae55..b55c1b267 100644 --- a/packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts +++ b/packages/sequencer/src/state/lmt/CachedLinkedLeafStore.ts @@ -161,8 +161,8 @@ export class CachedLinkedLeafStore implements LinkedLeafStore { // (i.e. pointing over our path) const previousLeaves = await this.retrieveBatched( groupedOps.insert.map(([path]) => path), - this.leafStore.getLeafLessOrEqual.bind(this.leafStore), - this.parent.getLeavesLessOrEqualAsync.bind(this.parent) + this.leafStore.getPreviousLeaf.bind(this.leafStore), + this.parent.getPreviousLeavesAsync.bind(this.parent) ); // This is a check that all previous leaves have been found, with the @@ -230,8 +230,8 @@ export class CachedLinkedLeafStore implements LinkedLeafStore { this.resetWrittenLeaves(); } - public getLeafLessOrEqual(path: bigint) { - return this.leafStore.getLeafLessOrEqual(path); + public getPreviousLeaf(path: bigint) { + return this.leafStore.getPreviousLeaf(path); } public getMaximumIndex() { diff --git a/packages/sequencer/src/state/merkle/SyncCachedLinkedLeafStore.ts b/packages/sequencer/src/state/merkle/SyncCachedLinkedLeafStore.ts index 7804010da..846d7fb03 100644 --- a/packages/sequencer/src/state/merkle/SyncCachedLinkedLeafStore.ts +++ b/packages/sequencer/src/state/merkle/SyncCachedLinkedLeafStore.ts @@ -39,10 +39,9 @@ export class SyncCachedLinkedLeafStore implements LinkedLeafStore { ); } - public getLeafLessOrEqual(path: bigint): StoredLeaf | undefined { + public getPreviousLeaf(path: bigint): StoredLeaf | undefined { return ( - this.leafStore.getLeafLessOrEqual(path) ?? - this.parent.getLeafLessOrEqual(path) + this.leafStore.getPreviousLeaf(path) ?? this.parent.getPreviousLeaf(path) ); } diff --git a/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedLeafStore.ts b/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedLeafStore.ts index db4834da8..382e2ca7a 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedLeafStore.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryAsyncLinkedLeafStore.ts @@ -48,8 +48,8 @@ export class InMemoryAsyncLinkedLeafStore implements AsyncLinkedLeafStore { return this.leafStore.getMaximumIndex(); } - public async getLeavesLessOrEqualAsync(paths: bigint[]) { - return paths.map((path) => this.leafStore.getLeafLessOrEqual(path)); + public async getPreviousLeavesAsync(paths: bigint[]) { + return paths.map((path) => this.leafStore.getPreviousLeaf(path)); } public setLeaf(index: bigint, value: LinkedLeaf) { @@ -60,8 +60,8 @@ export class InMemoryAsyncLinkedLeafStore implements AsyncLinkedLeafStore { return this.leafStore.getLeaf(path); } - public getLeafLessOrEqual(path: bigint) { - return this.leafStore.getLeafLessOrEqual(path); + public getPreviousLeaf(path: bigint) { + return this.leafStore.getPreviousLeaf(path); } public getMaximumIndex() {