From 13f8976a5e6a0f5857c6a3501814eeff8a60e6dd Mon Sep 17 00:00:00 2001 From: trungnotchung Date: Fri, 7 Feb 2025 11:36:27 +0700 Subject: [PATCH 01/10] check vertex dependencies before merging --- packages/object/src/index.ts | 5 ++++ packages/object/tests/drpobject.test.ts | 35 ++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index 34b5458bc..b9f836793 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -262,6 +262,11 @@ export class DRPObject implements ObjectPb.DRPObjectBase { ) { throw new Error(`Invalid hash for vertex ${vertex.hash}`); } + for (const dep of vertex.dependencies) { + if (!this.hashGraph.vertices.has(dep)) { + throw new Error(`Missing dependency ${dep} for vertex ${vertex.hash}`); + } + } const preComputeLca = this.computeLCA(vertex.dependencies); if (vertex.operation.drpType === DrpType.DRP) { diff --git a/packages/object/tests/drpobject.test.ts b/packages/object/tests/drpobject.test.ts index ebc6e3bb1..38cf61f41 100644 --- a/packages/object/tests/drpobject.test.ts +++ b/packages/object/tests/drpobject.test.ts @@ -1,5 +1,5 @@ import { SetDRP } from "@ts-drp/blueprints/src/index.js"; -import { beforeEach, describe, expect, it, test } from "vitest"; +import { beforeEach, describe, expect, it, test, vi } from "vitest"; import { DRPObject, ObjectACL } from "../src/index.js"; @@ -69,3 +69,36 @@ describe("Drp Object should be able to change state value", () => { } }); }); + +describe("Merging vertices tests", () => { + let obj1: DRPObject; + let obj2: DRPObject; + + beforeEach(() => { + obj1 = new DRPObject({ peerId: "peer1", acl, drp: new SetDRP() }); + obj2 = new DRPObject({ peerId: "peer2", acl, drp: new SetDRP() }); + }); + + test("Test: merge should skip unknown dependencies", () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date(1998, 11, 19)); + const drp1 = obj1.drp as SetDRP; + const drp2 = obj2.drp as SetDRP; + + drp1.add(1); + drp2.add(2); + obj1.merge(obj2.hashGraph.getAllVertices()); + drp1.add(3); + + const vertex = obj1.vertices.find( + (v) => v.operation?.opType === "add" && v.operation.value[0] === 3 + ); + if (!vertex) { + throw new Error("Vertex not found"); + } + expect(obj2.merge([vertex])).toEqual([ + false, + ["8f282270ff04d930b9372bad0f653cd84d113d3c28c5f07449906ee36f84e582"], + ]); + }); +}); From 24d18a7c36cde70466a606c778ef3708eeee4497 Mon Sep 17 00:00:00 2001 From: trungnotchung Date: Fri, 7 Feb 2025 12:17:03 +0700 Subject: [PATCH 02/10] update test --- packages/object/tests/drpobject.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/object/tests/drpobject.test.ts b/packages/object/tests/drpobject.test.ts index 38cf61f41..d70c7ee7f 100644 --- a/packages/object/tests/drpobject.test.ts +++ b/packages/object/tests/drpobject.test.ts @@ -73,6 +73,8 @@ describe("Drp Object should be able to change state value", () => { describe("Merging vertices tests", () => { let obj1: DRPObject; let obj2: DRPObject; + vi.useFakeTimers(); + vi.setSystemTime(new Date(1998, 11, 19)); beforeEach(() => { obj1 = new DRPObject({ peerId: "peer1", acl, drp: new SetDRP() }); @@ -80,8 +82,6 @@ describe("Merging vertices tests", () => { }); test("Test: merge should skip unknown dependencies", () => { - vi.useFakeTimers(); - vi.setSystemTime(new Date(1998, 11, 19)); const drp1 = obj1.drp as SetDRP; const drp2 = obj2.drp as SetDRP; From f0481cd96afd26847c9d4ee3b489783ece3b702e Mon Sep 17 00:00:00 2001 From: trungnotchung Date: Fri, 7 Feb 2025 12:21:58 +0700 Subject: [PATCH 03/10] update test --- packages/object/tests/drpobject.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/object/tests/drpobject.test.ts b/packages/object/tests/drpobject.test.ts index d70c7ee7f..38d70e228 100644 --- a/packages/object/tests/drpobject.test.ts +++ b/packages/object/tests/drpobject.test.ts @@ -73,8 +73,6 @@ describe("Drp Object should be able to change state value", () => { describe("Merging vertices tests", () => { let obj1: DRPObject; let obj2: DRPObject; - vi.useFakeTimers(); - vi.setSystemTime(new Date(1998, 11, 19)); beforeEach(() => { obj1 = new DRPObject({ peerId: "peer1", acl, drp: new SetDRP() }); @@ -82,6 +80,8 @@ describe("Merging vertices tests", () => { }); test("Test: merge should skip unknown dependencies", () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date(Date.UTC(1998, 11, 19))); const drp1 = obj1.drp as SetDRP; const drp2 = obj2.drp as SetDRP; @@ -98,7 +98,7 @@ describe("Merging vertices tests", () => { } expect(obj2.merge([vertex])).toEqual([ false, - ["8f282270ff04d930b9372bad0f653cd84d113d3c28c5f07449906ee36f84e582"], + ["e5ef52c6186abe51635619df8bc8676c19f5a6519e40f47072683437255f026a"], ]); }); }); From ccc96e0a87019d46452b5dc6cf499c27bb46541d Mon Sep 17 00:00:00 2001 From: trungnotchung Date: Fri, 7 Feb 2025 12:41:41 +0700 Subject: [PATCH 04/10] addVertex before computeLCA --- packages/object/src/index.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index b9f836793..a2f236fc2 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -262,16 +262,11 @@ export class DRPObject implements ObjectPb.DRPObjectBase { ) { throw new Error(`Invalid hash for vertex ${vertex.hash}`); } - for (const dep of vertex.dependencies) { - if (!this.hashGraph.vertices.has(dep)) { - throw new Error(`Missing dependency ${dep} for vertex ${vertex.hash}`); - } - } + this.hashGraph.addVertex(vertex); const preComputeLca = this.computeLCA(vertex.dependencies); if (vertex.operation.drpType === DrpType.DRP) { const drp = this._computeDRP(vertex.dependencies, preComputeLca); - this.hashGraph.addVertex(vertex); this._applyOperation(drp, vertex.operation); this._setObjectACLState(vertex, preComputeLca); @@ -279,7 +274,6 @@ export class DRPObject implements ObjectPb.DRPObjectBase { } else { const acl = this._computeObjectACL(vertex.dependencies, preComputeLca); - this.hashGraph.addVertex(vertex); this._applyOperation(acl, vertex.operation); this._setObjectACLState(vertex, preComputeLca, this._getDRPState(acl)); From 5fff602a34739fffb07c870fbf40869aba820a1f Mon Sep 17 00:00:00 2001 From: trungnotchung Date: Fri, 7 Feb 2025 21:01:40 +0700 Subject: [PATCH 05/10] validate new vertex --- packages/object/src/hashgraph/index.ts | 24 ---------- packages/object/src/index.ts | 64 +++++++++++++++++--------- 2 files changed, 43 insertions(+), 45 deletions(-) diff --git a/packages/object/src/hashgraph/index.ts b/packages/object/src/hashgraph/index.ts index ce251bcb7..337f969bb 100644 --- a/packages/object/src/hashgraph/index.ts +++ b/packages/object/src/hashgraph/index.ts @@ -137,30 +137,6 @@ export class HashGraph { * Throws an error if any of the dependencies are not present in the hashgraph. */ addVertex(vertex: Vertex) { - if (this.vertices.has(vertex.hash)) { - return; // Vertex already exists - } - - if (vertex.dependencies.length === 0) { - throw new Error("Vertex dependencies are empty."); - } - for (const dep of vertex.dependencies) { - const depVertex = this.vertices.get(dep); - if (depVertex === undefined) { - throw new Error("Invalid dependency detected."); - } - if (depVertex.timestamp > vertex.timestamp) { - // Vertex's timestamp must not be less than any of its dependencies' timestamps - throw new Error("Invalid timestamp detected."); - } - } - - const currentTimestamp = Date.now(); - if (vertex.timestamp > currentTimestamp) { - // Vertex created in the future is invalid - throw new Error("Invalid timestamp detected."); - } - this.vertices.set(vertex.hash, vertex); this.frontier.push(vertex.hash); // Update forward edges diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index 67cb1aaaf..149906e25 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -242,6 +242,38 @@ export class DRPObject implements ObjectPb.DRPObjectBase { return this._mergeWithDrp(vertices); } + validateVertex(vertex: Vertex) { + // Validate writer permission + if (!this._checkWriterPermission(vertex.peerId)) { + throw new Error(`${vertex.peerId} does not have write permission.`); + } + // Validate hash value + if ( + vertex.hash !== + computeHash(vertex.peerId, vertex.operation, vertex.dependencies, vertex.timestamp) + ) { + throw new Error(`Invalid hash for vertex ${vertex.hash}`); + } + // Validate vertex dependencies + if (vertex.dependencies.length === 0) { + throw new Error(`Vertex ${vertex.hash} has no dependencies.`); + } + for (const dep of vertex.dependencies) { + const depVertex = this.hashGraph.vertices.get(dep); + if (depVertex === undefined) { + throw new Error(`Vertex ${vertex.hash} has invalid dependency ${dep}.`); + } + if (depVertex.timestamp > vertex.timestamp) { + // Vertex's timestamp must not be less than any of its dependencies' timestamps + throw new Error(`Vertex ${vertex.hash} has invalid timestamp.`); + } + } + if (vertex.timestamp > Date.now()) { + // Vertex created in the future is invalid + throw new Error(`Vertex ${vertex.hash} has invalid timestamp.`); + } + } + /* Merges the vertices into the hashgraph using DRP */ private _mergeWithDrp(vertices: Vertex[]): [merged: boolean, missing: string[]] { @@ -254,32 +286,19 @@ export class DRPObject implements ObjectPb.DRPObjectBase { } try { - if (!this._checkWriterPermission(vertex.peerId)) { - throw new Error(`${vertex.peerId} does not have write permission.`); - } - if ( - vertex.hash !== - computeHash(vertex.peerId, vertex.operation, vertex.dependencies, vertex.timestamp) - ) { - throw new Error(`Invalid hash for vertex ${vertex.hash}`); - } - this.hashGraph.addVertex(vertex); + this.validateVertex(vertex); const preComputeLca = this.computeLCA(vertex.dependencies); if (vertex.operation.drpType === DrpType.DRP) { - const drp = this._computeDRP(vertex.dependencies, preComputeLca); - this._applyOperation(drp, vertex.operation); - + const drp = this._computeDRP(vertex.dependencies, preComputeLca, vertex.operation); this._setObjectACLState(vertex, preComputeLca); this._setDRPState(vertex, preComputeLca, this._getDRPState(drp)); } else { - const acl = this._computeObjectACL(vertex.dependencies, preComputeLca); - - this._applyOperation(acl, vertex.operation); - + const acl = this._computeObjectACL(vertex.dependencies, preComputeLca, vertex.operation); this._setObjectACLState(vertex, preComputeLca, this._getDRPState(acl)); this._setDRPState(vertex, preComputeLca); } + this.hashGraph.addVertex(vertex); this._initializeFinalityState(vertex.hash); newVertices.push(vertex); } catch (_) { @@ -304,9 +323,7 @@ export class DRPObject implements ObjectPb.DRPObjectBase { } try { - if (!this._checkWriterPermission(vertex.peerId)) { - return [false, [vertex.hash]]; - } + this.validateVertex(vertex); this.hashGraph.addVertex({ hash: vertex.hash, operation: vertex.operation, @@ -572,7 +589,12 @@ export class DRPObject implements ObjectPb.DRPObjectBase { } } -function computeHash(peerId: string, operation: Operation, deps: Hash[], timestamp: number): Hash { +function computeHash( + peerId: string, + operation: Operation | undefined, + deps: Hash[], + timestamp: number +): Hash { const serialized = JSON.stringify({ operation, deps, peerId, timestamp }); const hash = crypto.createHash("sha256").update(serialized).digest("hex"); return hash; From e8bee0a0e92e7f08486473ce30829c6bd64c4d4b Mon Sep 17 00:00:00 2001 From: trungnotchung Date: Fri, 7 Feb 2025 21:01:51 +0700 Subject: [PATCH 06/10] update tests --- packages/object/tests/hashgraph.test.ts | 73 ++++++++++++------------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/packages/object/tests/hashgraph.test.ts b/packages/object/tests/hashgraph.test.ts index c10062927..562b48e09 100644 --- a/packages/object/tests/hashgraph.test.ts +++ b/packages/object/tests/hashgraph.test.ts @@ -125,19 +125,18 @@ describe("HashGraph construction tests", () => { new Uint8Array() ); expect(() => { - obj1.hashGraph.addVertex(fakeRoot); - }).toThrowError("Vertex dependencies are empty."); + obj1.validateVertex(fakeRoot); + }).toThrowError(`Vertex ${fakeRoot.hash} has no dependencies.`); + const vertex = newVertex( + "peer1", + { opType: "add", value: [1], drpType: DrpType.DRP }, + [fakeRoot.hash], + Date.now(), + new Uint8Array() + ); expect(() => { - obj1.hashGraph.addVertex( - newVertex( - "peer1", - { opType: "add", value: [1], drpType: DrpType.DRP }, - [fakeRoot.hash], - Date.now(), - new Uint8Array() - ) - ); - }).toThrowError("Invalid dependency detected."); + obj1.validateVertex(vertex); + }).toThrowError(`Vertex ${vertex.hash} has invalid dependency ${fakeRoot.hash}.`); expect(selfCheckConstraints(obj1.hashGraph)).toBe(true); const linearOps = obj1.hashGraph.linearizeOperations(); @@ -564,17 +563,16 @@ describe("Vertex timestamp tests", () => { drp1.add(1); - expect(() => - obj1.hashGraph.addVertex( - newVertex( - "peer1", - { opType: "add", value: [1], drpType: DrpType.DRP }, - obj1.hashGraph.getFrontier(), - Number.POSITIVE_INFINITY, - new Uint8Array() - ) - ) - ).toThrowError("Invalid timestamp detected."); + const vertex = newVertex( + "peer1", + { opType: "add", value: [1], drpType: DrpType.DRP }, + obj1.hashGraph.getFrontier(), + Number.POSITIVE_INFINITY, + new Uint8Array() + ); + expect(() => obj1.validateVertex(vertex)).toThrowError( + `Vertex ${vertex.hash} has invalid timestamp.` + ); }); test("Test: Vertex's timestamp must not be less than any of its dependencies' timestamps", () => { @@ -597,21 +595,20 @@ describe("Vertex timestamp tests", () => { obj1.merge(obj2.hashGraph.getAllVertices()); obj1.merge(obj3.hashGraph.getAllVertices()); - expect(() => - obj1.hashGraph.addVertex( - newVertex( - "peer1", - { - opType: "add", - value: [1], - drpType: DrpType.DRP, - }, - obj1.hashGraph.getFrontier(), - 1, - new Uint8Array() - ) - ) - ).toThrowError("Invalid timestamp detected."); + const vertex = newVertex( + "peer1", + { + opType: "add", + value: [1], + drpType: DrpType.DRP, + }, + obj1.hashGraph.getFrontier(), + 1, + new Uint8Array() + ); + expect(() => obj1.validateVertex(vertex)).toThrowError( + `Vertex ${vertex.hash} has invalid timestamp.` + ); }); }); From 180b0345c6c26aec28b12068af61d6eabd4c3547 Mon Sep 17 00:00:00 2001 From: trungnotchung Date: Fri, 7 Feb 2025 21:03:27 +0700 Subject: [PATCH 07/10] update error message --- packages/object/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index 149906e25..45e02a536 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -245,7 +245,7 @@ export class DRPObject implements ObjectPb.DRPObjectBase { validateVertex(vertex: Vertex) { // Validate writer permission if (!this._checkWriterPermission(vertex.peerId)) { - throw new Error(`${vertex.peerId} does not have write permission.`); + throw new Error(`Vertex ${vertex.peerId} does not have write permission.`); } // Validate hash value if ( From ded6371d6089cdf78b6f814f3d26aba87364160e Mon Sep 17 00:00:00 2001 From: winprn Date: Sun, 9 Feb 2025 20:06:54 +0700 Subject: [PATCH 08/10] fix: refactor & fix tests --- packages/object/src/index.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index 9a4064422..dbf3fd903 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -243,10 +243,6 @@ export class DRPObject implements ObjectPb.DRPObjectBase { } validateVertex(vertex: Vertex) { - // Validate writer permission - if (!this._checkWriterPermission(vertex.peerId)) { - throw new Error(`Vertex ${vertex.peerId} does not have write permission.`); - } // Validate hash value if ( vertex.hash !== @@ -254,6 +250,7 @@ export class DRPObject implements ObjectPb.DRPObjectBase { ) { throw new Error(`Invalid hash for vertex ${vertex.hash}`); } + // Validate vertex dependencies if (vertex.dependencies.length === 0) { throw new Error(`Vertex ${vertex.hash} has no dependencies.`); @@ -272,6 +269,11 @@ export class DRPObject implements ObjectPb.DRPObjectBase { // Vertex created in the future is invalid throw new Error(`Vertex ${vertex.hash} has invalid timestamp.`); } + + // Validate writer permission + if (!this._checkWriterPermission(vertex.peerId, vertex.dependencies)) { + throw new Error(`Vertex ${vertex.peerId} does not have write permission.`); + } } /* Merges the vertices into the hashgraph using DRP @@ -369,8 +371,13 @@ export class DRPObject implements ObjectPb.DRPObjectBase { } // check if the given peer has write permission - private _checkWriterPermission(peerId: string): boolean { - return this.acl ? (this.acl as ACL).query_isWriter(peerId) : true; + private _checkWriterPermission(peerId: string, deps: Hash[]): boolean { + if (!this.drp) { + return (this.acl as ACL).query_isWriter(peerId); + } + + const acl = this._computeObjectACL(deps) + return (acl as ACL).query_isWriter(peerId); } // apply the operation to the DRP From af26871814fe367156546c940b61331aef8e8ee6 Mon Sep 17 00:00:00 2001 From: winprn Date: Sun, 9 Feb 2025 20:08:21 +0700 Subject: [PATCH 09/10] fix: lint --- packages/object/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index dbf3fd903..99128a6c9 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -376,7 +376,7 @@ export class DRPObject implements ObjectPb.DRPObjectBase { return (this.acl as ACL).query_isWriter(peerId); } - const acl = this._computeObjectACL(deps) + const acl = this._computeObjectACL(deps); return (acl as ACL).query_isWriter(peerId); } From 93248bfdab4c4b9606c28f79cd9793af0b518bf0 Mon Sep 17 00:00:00 2001 From: trungnotchung Date: Tue, 11 Feb 2025 21:07:19 +0700 Subject: [PATCH 10/10] update comment --- packages/object/src/hashgraph/index.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/object/src/hashgraph/index.ts b/packages/object/src/hashgraph/index.ts index 337f969bb..e23d91c24 100644 --- a/packages/object/src/hashgraph/index.ts +++ b/packages/object/src/hashgraph/index.ts @@ -132,10 +132,7 @@ export class HashGraph { this.arePredecessorsFresh = false; } - /* Add a vertex to the hashgraph with the given operation and dependencies. - * If the vertex already exists, return the hash of the existing vertex. - * Throws an error if any of the dependencies are not present in the hashgraph. - */ + // Add a new vertex to the hashgraph. addVertex(vertex: Vertex) { this.vertices.set(vertex.hash, vertex); this.frontier.push(vertex.hash);