From 130b977d0317c1ac5dab868e7f4660df347a7f41 Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Wed, 28 Aug 2024 17:08:25 -0300 Subject: [PATCH 01/13] feat(lean-imt): added `updateMany` method to package re #117 --- packages/lean-imt/src/lean-imt.ts | 49 +++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index 0a1f14d71..d4cbcb6b9 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -233,6 +233,55 @@ export default class LeanIMT { this._nodes[this.depth] = [node] } + /** + * Updates N leaves all at once. + * It is more efficient than using the {@link LeanIMT#update} method N times because it + * prevents updating middle nodes several times. This would happen when updating leaves + * with common ancestors. + * @param leaves The list of leaves to be updated. + * @param indices The list of indices of the respective leaves. + */ + public updateMany(leaves: N[], indices: number[]) { + requireDefined(leaves, "leaves") + requireDefined(indices, "indices") + requireArray(leaves, "leaves") + requireArray(indices, "indices") + + if (leaves.length === 0) { + throw new Error("There are no leaves to modify") + } + if (leaves.length !== indices.length) { + throw new Error("There is no correspondence between indices and leaves") + } + for (let leaf = 0; leaf < indices.length; leaf += 1) { + if (indices[leaf] < 0 || indices[leaf] >= this.size) { + throw new Error(`Index ${leaf} is out of range`) + // throw new Error("Index " + leaf.toString() + " is out of range") + } + } + + // This will keep track of the outdated nodes of each level. + let modifiedIndices = new Set() + // First, modify the first level, which consists only of raw, un-hashed values + for (let leaf = 0; leaf < indices.length; leaf += 1) { + this._nodes[0][indices[leaf]] = leaves[leaf] + modifiedIndices.add(indices[leaf] >> 1) + } + + // Now update each node of the corresponding levels + for (let level = 1; level <= this.depth; level += 1) { + const newModifiedIndices: number[] = [] + for (const index of modifiedIndices) { + const leftChild = this._nodes[level - 1][2 * index] + const rightChild = this._nodes[level - 1][2 * index + 1] + this._nodes[level][index] = rightChild ? this._hash(leftChild, rightChild) : leftChild + newModifiedIndices.push(index >> 1) + } + modifiedIndices.clear() + modifiedIndices = new Set(newModifiedIndices) + } + } + /** * It generates a {@link LeanIMTMerkleProof} for a leaf of the tree. * That proof can be verified by this tree using the same hash function. From 6049ea415b04b953a297a8da6da441a1d266a07e Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Wed, 28 Aug 2024 17:38:07 -0300 Subject: [PATCH 02/13] feat(lean-imt): implemented some tests on lean-imt re #117 --- packages/lean-imt/src/lean-imt.ts | 8 +--- packages/lean-imt/tests/lean-imt.test.ts | 53 ++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index d4cbcb6b9..4fdc4f067 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -238,25 +238,21 @@ export default class LeanIMT { * It is more efficient than using the {@link LeanIMT#update} method N times because it * prevents updating middle nodes several times. This would happen when updating leaves * with common ancestors. - * @param leaves The list of leaves to be updated. * @param indices The list of indices of the respective leaves. + * @param leaves The list of leaves to be updated. */ - public updateMany(leaves: N[], indices: number[]) { + public updateMany(indices: number[], leaves: N[]) { requireDefined(leaves, "leaves") requireDefined(indices, "indices") requireArray(leaves, "leaves") requireArray(indices, "indices") - if (leaves.length === 0) { - throw new Error("There are no leaves to modify") - } if (leaves.length !== indices.length) { throw new Error("There is no correspondence between indices and leaves") } for (let leaf = 0; leaf < indices.length; leaf += 1) { if (indices[leaf] < 0 || indices[leaf] >= this.size) { throw new Error(`Index ${leaf} is out of range`) - // throw new Error("Index " + leaf.toString() + " is out of range") } } diff --git a/packages/lean-imt/tests/lean-imt.test.ts b/packages/lean-imt/tests/lean-imt.test.ts index 0fb3c57e6..1c7633cd0 100644 --- a/packages/lean-imt/tests/lean-imt.test.ts +++ b/packages/lean-imt/tests/lean-imt.test.ts @@ -220,6 +220,59 @@ describe("Lean IMT", () => { }) }) + describe("# updateMany", () => { + it(`Should not update any leaf if one of the parameters is not defined`, () => { + const tree = new LeanIMT(poseidon, leaves) + + const fun1 = () => tree.updateMany([1], undefined as any) + const fun2 = () => tree.updateMany(undefined as any, [BigInt(1)]) + + expect(fun1).toThrow("Parameter 'leaves' is not defined") + expect(fun2).toThrow("Parameter 'indices' is not defined") + }) + + it(`Should not update any leaf if the parameters are not arrays`, () => { + const tree = new LeanIMT(poseidon, leaves) + + const fun1 = () => tree.updateMany([3], BigInt(1) as any) + const fun2 = () => tree.updateMany(3 as any, [BigInt(1)]) + + expect(fun1).toThrow("Parameter 'leaves' is not an Array instance") + expect(fun2).toThrow("Parameter 'indices' is not an Array instance") + }) + + it(`Should not update any leaf if the parameters are of different size`, () => { + const tree = new LeanIMT(poseidon, leaves) + + const fun = () => tree.updateMany([1, 2, 3], [BigInt(1), BigInt(2)]) + + expect(fun).toThrow("There is no correspondence between indices and leaves") + }) + + it(`Should not update any leaf if some index is out of range`, () => { + const tree = new LeanIMT(poseidon, leaves) + + const fun1 = () => tree.updateMany([-1, 2, 3], [BigInt(1), BigInt(2), BigInt(3)]) + const fun2 = () => tree.updateMany([1, 200000, 3], [BigInt(1), BigInt(2), BigInt(3)]) + const fun3 = () => tree.updateMany([1, 2, 345], [BigInt(1), BigInt(2), BigInt(3)]) + + expect(fun1).toThrow("Index 0 is out of range") + expect(fun2).toThrow("Index 1 is out of range") + expect(fun3).toThrow("Index 2 is out of range") + }) + + it(`Should not update any leaf when passing an empty list`, () => { + const tree = new LeanIMT(poseidon, leaves) + const previousRoot = tree.root + + tree.updateMany([], []) + + expect(tree.root).toBe(previousRoot) + }) + + it(`Should update leafs correctly`, () => {}) + }) + describe("# generateProof", () => { it(`Should not generate any proof if the index is not defined`, () => { const tree = new LeanIMT(poseidon, leaves) From e00a89156fc6de79c99648727eb71edcd8650aac Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Thu, 29 Aug 2024 12:46:34 -0300 Subject: [PATCH 03/13] feat(lean-imt): added more precondition checks re #117 --- packages/lean-imt/src/lean-imt.ts | 3 ++- packages/lean-imt/tests/lean-imt.test.ts | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index 4fdc4f067..c74d43b3d 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -237,7 +237,7 @@ export default class LeanIMT { * Updates N leaves all at once. * It is more efficient than using the {@link LeanIMT#update} method N times because it * prevents updating middle nodes several times. This would happen when updating leaves - * with common ancestors. + * with common ancestors. However, it doesn't offer a better worst-case time complexity. * @param indices The list of indices of the respective leaves. * @param leaves The list of leaves to be updated. */ @@ -251,6 +251,7 @@ export default class LeanIMT { throw new Error("There is no correspondence between indices and leaves") } for (let leaf = 0; leaf < indices.length; leaf += 1) { + requireNumber(indices[leaf], `index ${leaf}`) if (indices[leaf] < 0 || indices[leaf] >= this.size) { throw new Error(`Index ${leaf} is out of range`) } diff --git a/packages/lean-imt/tests/lean-imt.test.ts b/packages/lean-imt/tests/lean-imt.test.ts index 1c7633cd0..c683c1910 100644 --- a/packages/lean-imt/tests/lean-imt.test.ts +++ b/packages/lean-imt/tests/lean-imt.test.ts @@ -244,9 +244,21 @@ describe("Lean IMT", () => { it(`Should not update any leaf if the parameters are of different size`, () => { const tree = new LeanIMT(poseidon, leaves) - const fun = () => tree.updateMany([1, 2, 3], [BigInt(1), BigInt(2)]) + const fun1 = () => tree.updateMany([1, 2, 3], [BigInt(1), BigInt(2)]) + const fun2 = () => tree.updateMany([1], []) - expect(fun).toThrow("There is no correspondence between indices and leaves") + expect(fun1).toThrow("There is no correspondence between indices and leaves") + expect(fun2).toThrow("There is no correspondence between indices and leaves") + }) + + it(`Should not update any leaf if some index is not a number`, () => { + const tree = new LeanIMT(poseidon, leaves) + + const fun1 = () => tree.updateMany([1, "hello" as any, 3], [BigInt(1), BigInt(2), BigInt(3)]) + const fun2 = () => tree.updateMany([1, 2, undefined as any], [BigInt(1), BigInt(2), BigInt(3)]) + + expect(fun1).toThrow("Parameter 'index 1' is not a number") + expect(fun2).toThrow("Parameter 'index 2' is not a number") }) it(`Should not update any leaf if some index is out of range`, () => { @@ -254,7 +266,7 @@ describe("Lean IMT", () => { const fun1 = () => tree.updateMany([-1, 2, 3], [BigInt(1), BigInt(2), BigInt(3)]) const fun2 = () => tree.updateMany([1, 200000, 3], [BigInt(1), BigInt(2), BigInt(3)]) - const fun3 = () => tree.updateMany([1, 2, 345], [BigInt(1), BigInt(2), BigInt(3)]) + const fun3 = () => tree.updateMany([1, 2, tree.size], [BigInt(1), BigInt(2), BigInt(3)]) expect(fun1).toThrow("Index 0 is out of range") expect(fun2).toThrow("Index 1 is out of range") From 3e9e54bc62357a21bd3088fa09c17bf98b344640 Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Thu, 29 Aug 2024 13:15:00 -0300 Subject: [PATCH 04/13] feat(lean-imt): finished testing on `updateMany` method re #117 --- packages/lean-imt/tests/lean-imt.test.ts | 27 +++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/lean-imt/tests/lean-imt.test.ts b/packages/lean-imt/tests/lean-imt.test.ts index c683c1910..7ab99c5af 100644 --- a/packages/lean-imt/tests/lean-imt.test.ts +++ b/packages/lean-imt/tests/lean-imt.test.ts @@ -282,7 +282,32 @@ describe("Lean IMT", () => { expect(tree.root).toBe(previousRoot) }) - it(`Should update leafs correctly`, () => {}) + it(`Should updateMany with 1 change be the same as update`, () => { + const tree1 = new LeanIMT(poseidon, leaves) + const tree2 = new LeanIMT(poseidon, leaves) + + tree1.update(4, BigInt(-100)) + tree2.updateMany([4], [BigInt(-100)]) + expect(tree1.root).toBe(tree2.root) + + tree1.update(0, BigInt(24)) + tree2.updateMany([0], [BigInt(24)]) + expect(tree1.root).toBe(tree2.root) + }) + + it(`Should update leaves correctly`, () => { + const tree = new LeanIMT(poseidon, leaves) + + const updateLeaves = [BigInt(24), BigInt(-10), BigInt(100000)] + tree.updateMany([0, 1, 4], updateLeaves) + + const h1_0 = poseidon(updateLeaves[0], updateLeaves[1]) + const h1_1 = poseidon(leaves[2], leaves[3]) + const h2_0 = poseidon(h1_0, h1_1) + const updatedRoot = poseidon(h2_0, updateLeaves[2]) + + expect(tree.root).toBe(updatedRoot) + }) }) describe("# generateProof", () => { From cd3156eae1397b4fd9943fcca733f8924a29bb35 Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Sat, 31 Aug 2024 13:00:39 -0300 Subject: [PATCH 05/13] feat(lean-imt): added test to the case when passing repeated indices re #117 --- packages/lean-imt/src/lean-imt.ts | 1 - packages/lean-imt/tests/lean-imt.test.ts | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index c74d43b3d..3971b3777 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -274,7 +274,6 @@ export default class LeanIMT { this._nodes[level][index] = rightChild ? this._hash(leftChild, rightChild) : leftChild newModifiedIndices.push(index >> 1) } - modifiedIndices.clear() modifiedIndices = new Set(newModifiedIndices) } } diff --git a/packages/lean-imt/tests/lean-imt.test.ts b/packages/lean-imt/tests/lean-imt.test.ts index 7ab99c5af..4ed1d0325 100644 --- a/packages/lean-imt/tests/lean-imt.test.ts +++ b/packages/lean-imt/tests/lean-imt.test.ts @@ -282,7 +282,7 @@ describe("Lean IMT", () => { expect(tree.root).toBe(previousRoot) }) - it(`Should updateMany with 1 change be the same as update`, () => { + it(`'updateMany' with 1 change should be the same as 'update'`, () => { const tree1 = new LeanIMT(poseidon, leaves) const tree2 = new LeanIMT(poseidon, leaves) @@ -295,6 +295,19 @@ describe("Lean IMT", () => { expect(tree1.root).toBe(tree2.root) }) + it(`'updateMany' with repeated indices should overwrite the last update`, () => { + const tree = new LeanIMT(poseidon, leaves) + + tree.updateMany([4, 1, 4, 4], [BigInt(-100), BigInt(-17), BigInt(1), BigInt(24)]) + + const n1_0 = poseidon(leaves[0], BigInt(-17)) + const n1_1 = poseidon(leaves[2], leaves[3]) + const n2_0 = poseidon(n1_0, n1_1) + const newRoot = poseidon(n2_0, BigInt(24)) + + expect(tree.root).toBe(newRoot) + }) + it(`Should update leaves correctly`, () => { const tree = new LeanIMT(poseidon, leaves) From 06e941c26f5eb357afd51042cee498ba91f64055 Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Sat, 31 Aug 2024 19:36:00 -0300 Subject: [PATCH 06/13] feat(lean-imt): added complexity documentation for `updateMany` method re #117 --- packages/lean-imt/src/lean-imt.ts | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index 3971b3777..8ff709b41 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -234,10 +234,26 @@ export default class LeanIMT { } /** - * Updates N leaves all at once. - * It is more efficient than using the {@link LeanIMT#update} method N times because it - * prevents updating middle nodes several times. This would happen when updating leaves - * with common ancestors. However, it doesn't offer a better worst-case time complexity. + * Updates m leaves all at once. + * It is more efficient than using the {@link LeanIMT#update} method m times because + * it prevents updating middle nodes several times, which would happen when + * updating leaves with common ancestors. The worst-case time complexity + * of the naive approach of calling 'update' m times is O(m*log(n)) where n is the size + * of the tree, while this algorithm offers a complexity of O(m*(log(n)-log(m)) + m). + * This is a slight improvement, mostly when m ~ n. This makes sense because + * if we update a lot of leaves, most of them will have common ancestors. + * The proof for this would be as follows: If we have a list of m updates in + * a tree of depth d ~ log(n), let k be the smallest integer such that m <= 2^k. + * In the worst case, the paths of the m updates will pass through m different nodes + * of the level k of the tree. In other words, the m leaves to update have + * all different ancestors up to the level k (which has 2^k nodes). In this scenario, + * the number of nodes of level k or higher that need to be updated is exactly m*(d-k), + * and the number of nodes of level k-1 or lower to update is at least m/2 (because + * there are at least m/2 different ancestors of a set of m nodes) and at most + * 2*m (because there are not even 2^k of nodes among all those levels, and 2^k < 2m). + * Therefore, the number of nodes to update is at least m*(d-k) + m/2 and at most + * m*(d-k) + 2m. This gives us that the number of nodes to update, which is the most + * expensive operation of the method, is O(m*(log(n)-log(m)) + m) since k ~ log(m). * @param indices The list of indices of the respective leaves. * @param leaves The list of leaves to be updated. */ From 4b51e44ec1d325d65c94004fefa645e7ee9c3838 Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Sun, 1 Sep 2024 18:28:50 -0300 Subject: [PATCH 07/13] feat(lean-imt): added test of several updates re #117 --- packages/lean-imt/src/lean-imt.ts | 8 ++++---- packages/lean-imt/tests/lean-imt.test.ts | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index 8ff709b41..49eed4163 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -266,10 +266,10 @@ export default class LeanIMT { if (leaves.length !== indices.length) { throw new Error("There is no correspondence between indices and leaves") } - for (let leaf = 0; leaf < indices.length; leaf += 1) { - requireNumber(indices[leaf], `index ${leaf}`) - if (indices[leaf] < 0 || indices[leaf] >= this.size) { - throw new Error(`Index ${leaf} is out of range`) + for (let i = 0; i < indices.length; i += 1) { + requireNumber(indices[i], `index ${i}`) + if (indices[i] < 0 || indices[i] >= this.size) { + throw new Error(`Index ${i} is out of range`) } } diff --git a/packages/lean-imt/tests/lean-imt.test.ts b/packages/lean-imt/tests/lean-imt.test.ts index 4ed1d0325..e3e9ee768 100644 --- a/packages/lean-imt/tests/lean-imt.test.ts +++ b/packages/lean-imt/tests/lean-imt.test.ts @@ -295,6 +295,22 @@ describe("Lean IMT", () => { expect(tree1.root).toBe(tree2.root) }) + it(`'updateMany' should be the same as executing the 'update' function multiple times`, () => { + const tree1 = new LeanIMT(poseidon, leaves) + const tree2 = new LeanIMT(poseidon, leaves) + + const indices = [0, 2, 4] + + const nodes = [BigInt(10), BigInt(11), BigInt(12)] + + for (let i = 0; i < indices.length; i += 1) { + tree1.update(indices[i], nodes[i]) + } + tree2.updateMany(indices, nodes) + + expect(tree1.root).toBe(tree2.root) + }) + it(`'updateMany' with repeated indices should overwrite the last update`, () => { const tree = new LeanIMT(poseidon, leaves) From 29638d50fef78f00db83d710e6378ab1e4817a65 Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Sun, 1 Sep 2024 18:36:28 -0300 Subject: [PATCH 08/13] feat(lean-imt): added repeated indices check re #117 --- packages/lean-imt/src/lean-imt.ts | 9 +++++++-- packages/lean-imt/tests/lean-imt.test.ts | 11 +++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index 49eed4163..a7f27f9e6 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -266,15 +266,20 @@ export default class LeanIMT { if (leaves.length !== indices.length) { throw new Error("There is no correspondence between indices and leaves") } + // This will keep track of the outdated nodes of each level. + let modifiedIndices = new Set() for (let i = 0; i < indices.length; i += 1) { requireNumber(indices[i], `index ${i}`) if (indices[i] < 0 || indices[i] >= this.size) { throw new Error(`Index ${i} is out of range`) } + if (modifiedIndices.has(indices[i])) { + throw new Error(`Index ${indices[i]} is repeated`) + } + modifiedIndices.add(indices[i]) } - // This will keep track of the outdated nodes of each level. - let modifiedIndices = new Set() + modifiedIndices.clear() // First, modify the first level, which consists only of raw, un-hashed values for (let leaf = 0; leaf < indices.length; leaf += 1) { this._nodes[0][indices[leaf]] = leaves[leaf] diff --git a/packages/lean-imt/tests/lean-imt.test.ts b/packages/lean-imt/tests/lean-imt.test.ts index e3e9ee768..007f77a78 100644 --- a/packages/lean-imt/tests/lean-imt.test.ts +++ b/packages/lean-imt/tests/lean-imt.test.ts @@ -311,17 +311,12 @@ describe("Lean IMT", () => { expect(tree1.root).toBe(tree2.root) }) - it(`'updateMany' with repeated indices should overwrite the last update`, () => { + it(`'updateMany' with repeated indices should fail`, () => { const tree = new LeanIMT(poseidon, leaves) - tree.updateMany([4, 1, 4, 4], [BigInt(-100), BigInt(-17), BigInt(1), BigInt(24)]) + const fun = () => tree.updateMany([4, 1, 4], [BigInt(-100), BigInt(-17), BigInt(1)]) - const n1_0 = poseidon(leaves[0], BigInt(-17)) - const n1_1 = poseidon(leaves[2], leaves[3]) - const n2_0 = poseidon(n1_0, n1_1) - const newRoot = poseidon(n2_0, BigInt(24)) - - expect(tree.root).toBe(newRoot) + expect(fun).toThrow("Index 4 is repeated") }) it(`Should update leaves correctly`, () => { From 880812c2b33b9440e09c385045bc654f4af4cfd3 Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Sun, 1 Sep 2024 18:49:42 -0300 Subject: [PATCH 09/13] feat(lean-imt): changed error message to be more accurate re #117 --- packages/lean-imt/src/lean-imt.ts | 2 +- packages/lean-imt/tests/lean-imt.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index a7f27f9e6..6b1f8477c 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -274,7 +274,7 @@ export default class LeanIMT { throw new Error(`Index ${i} is out of range`) } if (modifiedIndices.has(indices[i])) { - throw new Error(`Index ${indices[i]} is repeated`) + throw new Error(`Leaf ${indices[i]} is repeated`) } modifiedIndices.add(indices[i]) } diff --git a/packages/lean-imt/tests/lean-imt.test.ts b/packages/lean-imt/tests/lean-imt.test.ts index 007f77a78..38212c1c5 100644 --- a/packages/lean-imt/tests/lean-imt.test.ts +++ b/packages/lean-imt/tests/lean-imt.test.ts @@ -316,7 +316,7 @@ describe("Lean IMT", () => { const fun = () => tree.updateMany([4, 1, 4], [BigInt(-100), BigInt(-17), BigInt(1)]) - expect(fun).toThrow("Index 4 is repeated") + expect(fun).toThrow("Leaf 4 is repeated") }) it(`Should update leaves correctly`, () => { From 681239eae47c61148f3c6a2cca7adc1a98e8bb21 Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Sun, 1 Sep 2024 19:16:46 -0300 Subject: [PATCH 10/13] feat(lean-imt): added complexity in terms only of n re #117 --- packages/lean-imt/src/lean-imt.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index 6b1f8477c..66a635dfa 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -254,6 +254,9 @@ export default class LeanIMT { * Therefore, the number of nodes to update is at least m*(d-k) + m/2 and at most * m*(d-k) + 2m. This gives us that the number of nodes to update, which is the most * expensive operation of the method, is O(m*(log(n)-log(m)) + m) since k ~ log(m). + * If we consider the worst case of m, which is m = n, the complexity is O(n). + * This is mainly because each node will be modified at most once, and the total + * number of nodes of the tree is <= 2*n. * @param indices The list of indices of the respective leaves. * @param leaves The list of leaves to be updated. */ From 1bfffd4b21a33387fd77d0ed5b88884c9d2ec200 Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Mon, 2 Sep 2024 14:18:23 -0300 Subject: [PATCH 11/13] feat(lean-imt): changed documentation to add discussion in another issue re #117 --- packages/lean-imt/src/lean-imt.ts | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index 66a635dfa..e14da95b8 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -235,28 +235,12 @@ export default class LeanIMT { /** * Updates m leaves all at once. - * It is more efficient than using the {@link LeanIMT#update} method m times because - * it prevents updating middle nodes several times, which would happen when - * updating leaves with common ancestors. The worst-case time complexity - * of the naive approach of calling 'update' m times is O(m*log(n)) where n is the size - * of the tree, while this algorithm offers a complexity of O(m*(log(n)-log(m)) + m). - * This is a slight improvement, mostly when m ~ n. This makes sense because - * if we update a lot of leaves, most of them will have common ancestors. - * The proof for this would be as follows: If we have a list of m updates in - * a tree of depth d ~ log(n), let k be the smallest integer such that m <= 2^k. - * In the worst case, the paths of the m updates will pass through m different nodes - * of the level k of the tree. In other words, the m leaves to update have - * all different ancestors up to the level k (which has 2^k nodes). In this scenario, - * the number of nodes of level k or higher that need to be updated is exactly m*(d-k), - * and the number of nodes of level k-1 or lower to update is at least m/2 (because - * there are at least m/2 different ancestors of a set of m nodes) and at most - * 2*m (because there are not even 2^k of nodes among all those levels, and 2^k < 2m). - * Therefore, the number of nodes to update is at least m*(d-k) + m/2 and at most - * m*(d-k) + 2m. This gives us that the number of nodes to update, which is the most - * expensive operation of the method, is O(m*(log(n)-log(m)) + m) since k ~ log(m). - * If we consider the worst case of m, which is m = n, the complexity is O(n). - * This is mainly because each node will be modified at most once, and the total - * number of nodes of the tree is <= 2*n. + * It is more efficient than using the {@link LeanIMT#update} method m times because it + * prevents updating middle nodes several times. This would happen when updating leaves + * with common ancestors. The naive approach of calling 'update' m times has complexity + * O(m*log(n)) (where n is the number of leaves of the tree), which ends up in + * O(n*log(n)) when m ~ n. With this new approach, this ends up being O(n) because every + * node is updated at most once and there are < 2*n in the tree. * @param indices The list of indices of the respective leaves. * @param leaves The list of leaves to be updated. */ From bd67b35ecf4322af384896e41b9f2b967adb7051 Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Mon, 2 Sep 2024 14:35:12 -0300 Subject: [PATCH 12/13] feat(lean-imt): fixed typo on documentation re #117 --- packages/lean-imt/src/lean-imt.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index e14da95b8..251a6ac2a 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -240,7 +240,7 @@ export default class LeanIMT { * with common ancestors. The naive approach of calling 'update' m times has complexity * O(m*log(n)) (where n is the number of leaves of the tree), which ends up in * O(n*log(n)) when m ~ n. With this new approach, this ends up being O(n) because every - * node is updated at most once and there are < 2*n in the tree. + * node is updated at most once and there are < 2*n nodes in the tree. * @param indices The list of indices of the respective leaves. * @param leaves The list of leaves to be updated. */ From c5e836d4d26bd5d0c032185f134b40c89f53e3bb Mon Sep 17 00:00:00 2001 From: ChinoCribioli <54038430+ChinoCribioli@users.noreply.github.com> Date: Sun, 8 Sep 2024 23:52:18 -0300 Subject: [PATCH 13/13] Update packages/lean-imt/src/lean-imt.ts Co-authored-by: Vivian Plasencia --- packages/lean-imt/src/lean-imt.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index 251a6ac2a..f453ff1dc 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -240,7 +240,7 @@ export default class LeanIMT { * with common ancestors. The naive approach of calling 'update' m times has complexity * O(m*log(n)) (where n is the number of leaves of the tree), which ends up in * O(n*log(n)) when m ~ n. With this new approach, this ends up being O(n) because every - * node is updated at most once and there are < 2*n nodes in the tree. + * node is updated at most once and there are around 2*n nodes in the tree. * @param indices The list of indices of the respective leaves. * @param leaves The list of leaves to be updated. */