From 96d058f8317acdb7d2d9c0f21f190adc6cf3cd07 Mon Sep 17 00:00:00 2001 From: hoangquocvietuet Date: Thu, 23 Jan 2025 15:01:13 +0700 Subject: [PATCH 01/11] fix stuffs --- packages/node/src/handlers.ts | 23 ++--- packages/node/src/rpc/index.ts | 6 +- packages/node/src/utils.ts | 23 +++++ packages/node/tests/node.test.ts | 12 ++- packages/object/src/hashgraph/index.ts | 8 +- packages/object/src/index.ts | 125 ++++++++++++++----------- packages/object/src/utils/object.ts | 9 ++ 7 files changed, 132 insertions(+), 74 deletions(-) create mode 100644 packages/object/src/utils/object.ts diff --git a/packages/node/src/handlers.ts b/packages/node/src/handlers.ts index 708c2d67f..0163fd8e7 100644 --- a/packages/node/src/handlers.ts +++ b/packages/node/src/handlers.ts @@ -9,7 +9,11 @@ import { } from "@ts-drp/object"; import { fromString as uint8ArrayFromString } from "uint8arrays/from-string"; import { type DRPNode, log } from "./index.js"; -import { deserializeStateMessage, serializeStateMessage } from "./utils.js"; +import { + deserializeStateMessage, + serializeStateMessage, + verifyACLSignature, +} from "./utils.js"; /* Handler for all DRP messages, including pubsub messages and direct messages @@ -168,7 +172,7 @@ async function updateHandler(node: DRPNode, sender: string, data: Uint8Array) { if ((object.acl as ACL).permissionless) { verifiedVertices = updateMessage.vertices; } else { - verifiedVertices = await verifyIncomingVertices( + verifiedVertices = await verifyACLIncomingVertices( object, updateMessage.vertices, ); @@ -273,7 +277,7 @@ async function syncAcceptHandler( if ((object.acl as ACL).permissionless) { verifiedVertices = syncAcceptMessage.requested; } else { - verifiedVertices = await verifyIncomingVertices( + verifiedVertices = await verifyACLIncomingVertices( object, syncAcceptMessage.requested, ); @@ -427,7 +431,7 @@ function getAttestations( .filter((a) => a !== undefined); } -export async function verifyIncomingVertices( +export async function verifyACLIncomingVertices( object: DRPObject, incomingVertices: ObjectPb.Vertex[], ): Promise { @@ -467,17 +471,8 @@ export async function verifyIncomingVertices( const data = uint8ArrayFromString(vertex.hash); try { - const cryptoKey = await crypto.subtle.importKey( - "raw", + const isValid = await verifyACLSignature( publicKeyBytes, - { name: "Ed25519" }, - true, - ["verify"], - ); - - const isValid = await crypto.subtle.verify( - { name: "Ed25519" }, - cryptoKey, vertex.signature, data, ); diff --git a/packages/node/src/rpc/index.ts b/packages/node/src/rpc/index.ts index 1e0371fd1..75f15133d 100644 --- a/packages/node/src/rpc/index.ts +++ b/packages/node/src/rpc/index.ts @@ -19,13 +19,15 @@ import type { } from "../proto/drp/node/v1/rpc_pb.js"; export function init(node: DRPNode) { - function subscribeDRP( + async function subscribeDRP( call: ServerUnaryCall, callback: sendUnaryData, ) { let returnCode = 0; try { - node.subscribeObject(call.request.drpId); + await node.connectObject({ + id: call.request.drpId, + }); } catch (e) { log.error("::rpc::subscribeDRP: Error", e); returnCode = 1; diff --git a/packages/node/src/utils.ts b/packages/node/src/utils.ts index 9fe222dbc..791540bcd 100644 --- a/packages/node/src/utils.ts +++ b/packages/node/src/utils.ts @@ -27,3 +27,26 @@ export function deserializeStateMessage( } return drpState; } + +export async function verifyACLSignature( + publicKeyBytes: Uint8Array, + signature: Uint8Array, + data: Uint8Array, +) { + const cryptoKey = await crypto.subtle.importKey( + "raw", + publicKeyBytes, + { name: "Ed25519" }, + true, + ["verify"], + ); + + const isValid = await crypto.subtle.verify( + { name: "Ed25519" }, + cryptoKey, + signature, + data, + ); + + return isValid; +} diff --git a/packages/node/tests/node.test.ts b/packages/node/tests/node.test.ts index 49150af1e..280d1cd27 100644 --- a/packages/node/tests/node.test.ts +++ b/packages/node/tests/node.test.ts @@ -6,7 +6,7 @@ import { beforeAll, beforeEach, describe, expect, test } from "vitest"; import { signFinalityVertices, signGeneratedVertices, - verifyIncomingVertices, + verifyACLIncomingVertices, } from "../src/handlers.js"; import { DRPNode } from "../src/index.js"; @@ -86,7 +86,10 @@ describe("DPRNode with verify and sign signature", () => { }, ]; await signGeneratedVertices(drpNode, vertices); - const verifiedVertices = await verifyIncomingVertices(drpObject, vertices); + const verifiedVertices = await verifyACLIncomingVertices( + drpObject, + vertices, + ); expect(verifiedVertices.length).toBe(1); }); @@ -105,7 +108,10 @@ describe("DPRNode with verify and sign signature", () => { signature: new Uint8Array(), }, ]; - const verifiedVertices = await verifyIncomingVertices(drpObject, vertices); + const verifiedVertices = await verifyACLIncomingVertices( + drpObject, + vertices, + ); expect(verifiedVertices.length).toBe(0); }); }); diff --git a/packages/object/src/hashgraph/index.ts b/packages/object/src/hashgraph/index.ts index fe9a787f3..da5a13d44 100644 --- a/packages/object/src/hashgraph/index.ts +++ b/packages/object/src/hashgraph/index.ts @@ -514,7 +514,13 @@ export class HashGraph { } getAllVertices(): Vertex[] { - return Array.from(this.vertices.values()); + const topologicalOrder = this.topologicalSort(); + const vertices: Vertex[] = []; + for (const hash of topologicalOrder) { + const vertex = this.vertices.get(hash); + if (vertex) vertices.push(vertex); + } + return vertices; } getReachablePredecessors(hash: Hash): BitSet | undefined { diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index 623ff7b2c..011f7b700 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -1,4 +1,3 @@ -import * as crypto from "node:crypto"; import { Logger, type LoggerOptions } from "@ts-drp/logger"; import { cloneDeep } from "es-toolkit"; import { deepEqual } from "fast-equals"; @@ -20,6 +19,7 @@ import { type LcaAndOperations, } from "./interface.js"; import * as ObjectPb from "./proto/drp/object/v1/object_pb.js"; +import { computeDRPObjectId } from "./utils/object.js"; import { ObjectSet } from "./utils/objectSet.js"; export * as ObjectPb from "./proto/drp/object/v1/object_pb.js"; @@ -69,13 +69,7 @@ export class DRPObject implements ObjectPb.DRPObjectBase { this.peerId = options.peerId; log = new Logger("drp::object", options.config?.log_config); - this.id = - options.id ?? - crypto - .createHash("sha256") - .update(options.peerId) - .update(Math.floor(Math.random() * Number.MAX_VALUE).toString()) - .digest("hex"); + this.id = options.id ?? computeDRPObjectId(options.peerId); const objAcl = options.acl ?? @@ -248,41 +242,20 @@ export class DRPObject implements ObjectPb.DRPObjectBase { * missing vertices and an array with the missing vertices */ merge(vertices: Vertex[]): [merged: boolean, missing: string[]] { + const missing = []; if (!this.hashGraph) { throw new Error("Hashgraph is undefined"); } - const missing = []; - for (const vertex of vertices) { - // Check to avoid manually crafted `undefined` operations - if (!vertex.operation || this.hashGraph.vertices.has(vertex.hash)) { - continue; - } - - try { - if (!this._checkWriterPermission(vertex.peerId)) { - throw new Error(`${vertex.peerId} does not have write permission.`); + if (!this.drp) { + for (const vertex of vertices) { + if (!vertex.operation || this.hashGraph.vertices.has(vertex.hash)) { + continue; } - const preComputeLca = this.computeLCA(vertex.dependencies); - - if (vertex.operation.drpType === DrpType.DRP) { - const drp = this._computeDRP(vertex.dependencies, preComputeLca); - this.hashGraph.addVertex( - vertex.operation, - vertex.dependencies, - vertex.peerId, - vertex.timestamp, - vertex.signature, - ); - this._applyOperation(drp, vertex.operation); - - this._setObjectACLState(vertex, preComputeLca); - this._setDRPState(vertex, preComputeLca, this._getDRPState(drp)); - } else { - const acl = this._computeObjectACL( - vertex.dependencies, - preComputeLca, - ); + try { + if (!this._checkWriterPermission(vertex.peerId)) { + return [false, [vertex.hash]]; + } this.hashGraph.addVertex( vertex.operation, vertex.dependencies, @@ -290,26 +263,70 @@ export class DRPObject implements ObjectPb.DRPObjectBase { vertex.timestamp, vertex.signature, ); - this._applyOperation(acl, vertex.operation); - - this._setObjectACLState( - vertex, - preComputeLca, - this._getDRPState(acl), - ); - this._setDRPState(vertex, preComputeLca); + } catch (_) { + missing.push(vertex.hash); } - this._initializeFinalityState(vertex.hash); - } catch (_) { - missing.push(vertex.hash); } - } + } else { + const missing = []; + for (const vertex of vertices) { + // Check to avoid manually crafted `undefined` operations + if (!vertex.operation || this.hashGraph.vertices.has(vertex.hash)) { + continue; + } - this.vertices = this.hashGraph.getAllVertices(); - this._updateObjectACLState(); - this._updateDRPState(); - this._notify("merge", this.vertices); + try { + if (!this._checkWriterPermission(vertex.peerId)) { + throw new Error(`${vertex.peerId} does not have write permission.`); + } + const preComputeLca = this.computeLCA(vertex.dependencies); + + if (vertex.operation.drpType === DrpType.DRP) { + const drp = this._computeDRP(vertex.dependencies, preComputeLca); + this.hashGraph.addVertex( + vertex.operation, + vertex.dependencies, + vertex.peerId, + vertex.timestamp, + vertex.signature, + ); + this._applyOperation(drp, vertex.operation); + + this._setObjectACLState(vertex, preComputeLca); + this._setDRPState(vertex, preComputeLca, this._getDRPState(drp)); + } else { + const acl = this._computeObjectACL( + vertex.dependencies, + preComputeLca, + ); + + this.hashGraph.addVertex( + vertex.operation, + vertex.dependencies, + vertex.peerId, + vertex.timestamp, + vertex.signature, + ); + this._applyOperation(acl, vertex.operation); + + this._setObjectACLState( + vertex, + preComputeLca, + this._getDRPState(acl), + ); + this._setDRPState(vertex, preComputeLca); + } + this._initializeFinalityState(vertex.hash); + } catch (_) { + missing.push(vertex.hash); + } + } + this.vertices = this.hashGraph.getAllVertices(); + this._updateObjectACLState(); + this._updateDRPState(); + this._notify("merge", this.vertices); + } return [missing.length === 0, missing]; } diff --git a/packages/object/src/utils/object.ts b/packages/object/src/utils/object.ts new file mode 100644 index 000000000..6f2f96faa --- /dev/null +++ b/packages/object/src/utils/object.ts @@ -0,0 +1,9 @@ +import * as crypto from "node:crypto"; + +export function computeDRPObjectId(peerId: string): string { + return crypto + .createHash("sha256") + .update(peerId) + .update(Math.floor(Math.random() * Number.MAX_VALUE).toString()) + .digest("hex"); +} From 5dcb9dc0b1e51e8d559e366610a9f28441f6daa0 Mon Sep 17 00:00:00 2001 From: hoangquocvietuet Date: Thu, 23 Jan 2025 16:21:53 +0700 Subject: [PATCH 02/11] fix testcases, prevent empty deps --- packages/object/src/hashgraph/index.ts | 3 ++ packages/object/tests/hashgraph.test.ts | 52 ++++++++++++++----------- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/packages/object/src/hashgraph/index.ts b/packages/object/src/hashgraph/index.ts index da5a13d44..069adee67 100644 --- a/packages/object/src/hashgraph/index.ts +++ b/packages/object/src/hashgraph/index.ts @@ -170,6 +170,9 @@ export class HashGraph { return hash; // Vertex already exists } + if (deps.length === 0) { + throw new Error("Vertex dependencies are empty."); + } for (const dep of deps) { const vertex = this.vertices.get(dep); if (vertex === undefined) { diff --git a/packages/object/tests/hashgraph.test.ts b/packages/object/tests/hashgraph.test.ts index 28f787cd1..116939cd4 100644 --- a/packages/object/tests/hashgraph.test.ts +++ b/packages/object/tests/hashgraph.test.ts @@ -80,29 +80,35 @@ describe("HashGraph construction tests", () => { const drp1 = obj1.drp as SetDRP; drp1.add(1); // add fake root - const hash = obj1.hashGraph.addVertex( - { - opType: "root", - value: null, - drpType: DrpType.DRP, - }, - [], - "", - Date.now(), - new Uint8Array(), - ); - obj1.hashGraph.addVertex( - { - opType: "add", - value: [1], - drpType: DrpType.DRP, - }, - [hash], - "", - Date.now(), - new Uint8Array(), - ); - expect(obj1.hashGraph.selfCheckConstraints()).toBe(false); + expect(() => { + obj1.hashGraph.addVertex( + { + opType: "root", + value: null, + drpType: DrpType.DRP, + }, + [], + "", + Date.now(), + new Uint8Array(), + ); + }).toThrowError("Vertex dependencies are empty."); + expect( + () => { + obj1.hashGraph.addVertex( + { + opType: "add", + value: [1], + drpType: DrpType.DRP, + }, + ["123"], + "", + Date.now(), + new Uint8Array(), + ) + } + ).toThrowError("Invalid dependency detected."); + expect(obj1.hashGraph.selfCheckConstraints()).toBe(true); const linearOps = obj1.hashGraph.linearizeOperations(); const expectedOps: Operation[] = [ From b1a24e52a813f7c170e58170ec5f73ca80c0e136 Mon Sep 17 00:00:00 2001 From: hoangquocvietuet Date: Thu, 23 Jan 2025 16:22:16 +0700 Subject: [PATCH 03/11] fix biome --- packages/object/tests/hashgraph.test.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/object/tests/hashgraph.test.ts b/packages/object/tests/hashgraph.test.ts index 116939cd4..075920e20 100644 --- a/packages/object/tests/hashgraph.test.ts +++ b/packages/object/tests/hashgraph.test.ts @@ -93,9 +93,8 @@ describe("HashGraph construction tests", () => { new Uint8Array(), ); }).toThrowError("Vertex dependencies are empty."); - expect( - () => { - obj1.hashGraph.addVertex( + expect(() => { + obj1.hashGraph.addVertex( { opType: "add", value: [1], @@ -105,9 +104,8 @@ describe("HashGraph construction tests", () => { "", Date.now(), new Uint8Array(), - ) - } - ).toThrowError("Invalid dependency detected."); + ); + }).toThrowError("Invalid dependency detected."); expect(obj1.hashGraph.selfCheckConstraints()).toBe(true); const linearOps = obj1.hashGraph.linearizeOperations(); From de55070b2adc7c2426ad7d9ddce2d4b7703252ea Mon Sep 17 00:00:00 2001 From: Sacha Froment Date: Thu, 23 Jan 2025 17:28:16 +0100 Subject: [PATCH 04/11] feat: add README.md for tracer (#392) Signed-off-by: Sacha Froment --- packages/object/src/hashgraph/index.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/object/src/hashgraph/index.ts b/packages/object/src/hashgraph/index.ts index 762404e73..98810962f 100644 --- a/packages/object/src/hashgraph/index.ts +++ b/packages/object/src/hashgraph/index.ts @@ -507,13 +507,7 @@ export class HashGraph { } getAllVertices(): Vertex[] { - const topologicalOrder = this.topologicalSort(); - const vertices: Vertex[] = []; - for (const hash of topologicalOrder) { - const vertex = this.vertices.get(hash); - if (vertex) vertices.push(vertex); - } - return vertices; + return Array.from(this.vertices.values()); } getReachablePredecessors(hash: Hash): BitSet | undefined { From 35b7977b0af26fcd5e29434294a3149e5d5f257a Mon Sep 17 00:00:00 2001 From: hoangquocvietuet Date: Mon, 3 Feb 2025 11:01:00 +0700 Subject: [PATCH 05/11] fix lint --- packages/node/src/handlers.ts | 22 ++++--------------- packages/node/src/utils.ts | 11 +++------- packages/node/src/version.ts | 2 +- packages/node/tests/node.test.ts | 10 ++------- packages/object/src/index.ts | 16 ++++---------- packages/object/tests/hashgraph.test.ts | 4 ++-- pnpm-lock.yaml | 28 ++++++++++++------------- 7 files changed, 30 insertions(+), 63 deletions(-) diff --git a/packages/node/src/handlers.ts b/packages/node/src/handlers.ts index a59439965..809a7f76b 100644 --- a/packages/node/src/handlers.ts +++ b/packages/node/src/handlers.ts @@ -4,11 +4,7 @@ import { type ACL, type DRPObject, HashGraph, type ObjectPb, type Vertex } from import { fromString as uint8ArrayFromString } from "uint8arrays/from-string"; import { type DRPNode, log } from "./index.js"; -import { - deserializeStateMessage, - serializeStateMessage, - verifyACLSignature, -} from "./utils.js"; +import { deserializeStateMessage, serializeStateMessage, verifyACLSignature } from "./utils.js"; /* Handler for all DRP messages, including pubsub messages and direct messages @@ -153,10 +149,7 @@ async function updateHandler(node: DRPNode, sender: string, data: Uint8Array) { if ((object.acl as ACL).permissionless) { verifiedVertices = updateMessage.vertices; } else { - verifiedVertices = await verifyACLIncomingVertices( - object, - updateMessage.vertices, - ); + verifiedVertices = await verifyACLIncomingVertices(object, updateMessage.vertices); } const [merged, _] = object.merge(verifiedVertices); @@ -258,10 +251,7 @@ async function syncAcceptHandler(node: DRPNode, sender: string, data: Uint8Array if ((object.acl as ACL).permissionless) { verifiedVertices = syncAcceptMessage.requested; } else { - verifiedVertices = await verifyACLIncomingVertices( - object, - syncAcceptMessage.requested, - ); + verifiedVertices = await verifyACLIncomingVertices(object, syncAcceptMessage.requested); } if (verifiedVertices.length !== 0) { @@ -440,11 +430,7 @@ export async function verifyACLIncomingVertices( const data = uint8ArrayFromString(vertex.hash); try { - const isValid = await verifyACLSignature( - publicKeyBytes, - vertex.signature, - data - ); + const isValid = await verifyACLSignature(publicKeyBytes, vertex.signature, data); return isValid ? vertex : null; } catch (error) { diff --git a/packages/node/src/utils.ts b/packages/node/src/utils.ts index de8dc7ccb..55b212559 100644 --- a/packages/node/src/utils.ts +++ b/packages/node/src/utils.ts @@ -27,22 +27,17 @@ export function deserializeStateMessage(state?: ObjectPb.DRPState): ObjectPb.DRP export async function verifyACLSignature( publicKeyBytes: Uint8Array, signature: Uint8Array, - data: Uint8Array, + data: Uint8Array ) { const cryptoKey = await crypto.subtle.importKey( "raw", publicKeyBytes, { name: "Ed25519" }, true, - ["verify"], + ["verify"] ); - const isValid = await crypto.subtle.verify( - { name: "Ed25519" }, - cryptoKey, - signature, - data, - ); + const isValid = await crypto.subtle.verify({ name: "Ed25519" }, cryptoKey, signature, data); return isValid; } diff --git a/packages/node/src/version.ts b/packages/node/src/version.ts index 72a311182..909cf14de 100644 --- a/packages/node/src/version.ts +++ b/packages/node/src/version.ts @@ -1 +1 @@ -export const VERSION = "0.6.1"; +export const VERSION = "0.7.0"; diff --git a/packages/node/tests/node.test.ts b/packages/node/tests/node.test.ts index 6642ba181..16921e9ef 100644 --- a/packages/node/tests/node.test.ts +++ b/packages/node/tests/node.test.ts @@ -84,10 +84,7 @@ describe("DPRNode with verify and sign signature", () => { }, ]; await signGeneratedVertices(drpNode, vertices); - const verifiedVertices = await verifyACLIncomingVertices( - drpObject, - vertices, - ); + const verifiedVertices = await verifyACLIncomingVertices(drpObject, vertices); expect(verifiedVertices.length).toBe(1); }); @@ -106,10 +103,7 @@ describe("DPRNode with verify and sign signature", () => { signature: new Uint8Array(), }, ]; - const verifiedVertices = await verifyACLIncomingVertices( - drpObject, - vertices, - ); + const verifiedVertices = await verifyACLIncomingVertices(drpObject, vertices); expect(verifiedVertices.length).toBe(0); }); }); diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index 96a1ed35f..cf7cee19d 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -1,7 +1,6 @@ import { Logger, type LoggerOptions } from "@ts-drp/logger"; import { cloneDeep } from "es-toolkit"; import { deepEqual } from "fast-equals"; -import * as crypto from "node:crypto"; import { ObjectACL } from "./acl/index.js"; import type { ACL } from "./acl/interface.js"; @@ -269,32 +268,25 @@ export class DRPObject implements ObjectPb.DRPObjectBase { vertex.dependencies, vertex.peerId, vertex.timestamp, - vertex.signature, + vertex.signature ); this._applyOperation(drp, vertex.operation); this._setObjectACLState(vertex, preComputeLca); this._setDRPState(vertex, preComputeLca, this._getDRPState(drp)); } else { - const acl = this._computeObjectACL( - vertex.dependencies, - preComputeLca, - ); + const acl = this._computeObjectACL(vertex.dependencies, preComputeLca); this.hashGraph.addVertex( vertex.operation, vertex.dependencies, vertex.peerId, vertex.timestamp, - vertex.signature, + vertex.signature ); this._applyOperation(acl, vertex.operation); - this._setObjectACLState( - vertex, - preComputeLca, - this._getDRPState(acl), - ); + this._setObjectACLState(vertex, preComputeLca, this._getDRPState(acl)); this._setDRPState(vertex, preComputeLca); } this._initializeFinalityState(vertex.hash); diff --git a/packages/object/tests/hashgraph.test.ts b/packages/object/tests/hashgraph.test.ts index 88cc3e593..ae13ae876 100644 --- a/packages/object/tests/hashgraph.test.ts +++ b/packages/object/tests/hashgraph.test.ts @@ -82,7 +82,7 @@ describe("HashGraph construction tests", () => { [], "", Date.now(), - new Uint8Array(), + new Uint8Array() ); }).toThrowError("Vertex dependencies are empty."); expect(() => { @@ -95,7 +95,7 @@ describe("HashGraph construction tests", () => { ["123"], "", Date.now(), - new Uint8Array(), + new Uint8Array() ); }).toThrowError("Invalid dependency detected."); expect(obj1.hashGraph.selfCheckConstraints()).toBe(true); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43ae1368f..54b87289b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -78,10 +78,10 @@ importers: examples/canvas: dependencies: '@ts-drp/node': - specifier: 0.6.1 + specifier: 0.7.0 version: link:../../packages/node '@ts-drp/object': - specifier: 0.6.1 + specifier: 0.7.0 version: link:../../packages/object devDependencies: '@types/node': @@ -100,10 +100,10 @@ importers: examples/chat: dependencies: '@ts-drp/node': - specifier: 0.6.1 + specifier: 0.7.0 version: link:../../packages/node '@ts-drp/object': - specifier: 0.6.1 + specifier: 0.7.0 version: link:../../packages/object devDependencies: '@types/node': @@ -122,10 +122,10 @@ importers: examples/grid: dependencies: '@ts-drp/node': - specifier: 0.6.1 + specifier: 0.7.0 version: link:../../packages/node '@ts-drp/object': - specifier: 0.6.1 + specifier: 0.7.0 version: link:../../packages/object devDependencies: '@types/node': @@ -144,7 +144,7 @@ importers: examples/local-bootstrap: dependencies: '@ts-drp/node': - specifier: 0.6.1 + specifier: 0.7.0 version: link:../../packages/node devDependencies: '@types/node': @@ -167,7 +167,7 @@ importers: version: 4.1.5 devDependencies: '@ts-drp/object': - specifier: 0.6.1 + specifier: 0.7.0 version: link:../object packages/logger: @@ -236,7 +236,7 @@ importers: specifier: ^1.6.0 version: 1.6.0 '@ts-drp/logger': - specifier: ^0.6.0 + specifier: ^0.7.0 version: link:../logger it-length-prefixed: specifier: ^9.1.0 @@ -285,16 +285,16 @@ importers: specifier: ^2.1.3 version: 2.3.0 '@ts-drp/blueprints': - specifier: 0.6.1 + specifier: 0.7.0 version: link:../blueprints '@ts-drp/logger': - specifier: 0.6.1 + specifier: 0.7.0 version: link:../logger '@ts-drp/network': - specifier: 0.6.1 + specifier: 0.7.0 version: link:../network '@ts-drp/object': - specifier: 0.6.1 + specifier: 0.7.0 version: link:../object commander: specifier: ^13.0.0 @@ -328,7 +328,7 @@ importers: specifier: ^8.1.0 version: 8.1.0 '@ts-drp/logger': - specifier: ^0.6.1 + specifier: ^0.7.0 version: link:../logger es-toolkit: specifier: 1.30.1 From e18d89649e5dbb3cc51fda86a56c396cafb11112 Mon Sep 17 00:00:00 2001 From: hoangquocvietuet Date: Tue, 4 Feb 2025 19:10:47 +0700 Subject: [PATCH 06/11] refactor merge --- packages/object/src/index.ts | 146 +++++++++++++++++++---------------- 1 file changed, 80 insertions(+), 66 deletions(-) diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index cf7cee19d..a57dca2c5 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -217,25 +217,24 @@ export class DRPObject implements ObjectPb.DRPObjectBase { this._notify("callFn", [serializedVertex]); } - /* Merges the vertices into the hashgraph - * Returns a tuple with a boolean indicating if there were - * missing vertices and an array with the missing vertices + /* Merges the vertices into the hashgraph using DRP */ - merge(vertices: Vertex[]): [merged: boolean, missing: string[]] { + private _mergeWithDrp(vertices: Vertex[]): [merged: boolean, missing: string[]] { const missing = []; - if (!this.hashGraph) { - throw new Error("Hashgraph is undefined"); - } - if (!this.drp) { - for (const vertex of vertices) { - if (!vertex.operation || this.hashGraph.vertices.has(vertex.hash)) { - continue; + for (const vertex of vertices) { + // Check to avoid manually crafted `undefined` operations + if (!vertex.operation || this.hashGraph.vertices.has(vertex.hash)) { + continue; + } + + try { + if (!this._checkWriterPermission(vertex.peerId)) { + throw new Error(`${vertex.peerId} does not have write permission.`); } + const preComputeLca = this.computeLCA(vertex.dependencies); - try { - if (!this._checkWriterPermission(vertex.peerId)) { - return [false, [vertex.hash]]; - } + if (vertex.operation.drpType === DrpType.DRP) { + const drp = this._computeDRP(vertex.dependencies, preComputeLca); this.hashGraph.addVertex( vertex.operation, vertex.dependencies, @@ -243,66 +242,81 @@ export class DRPObject implements ObjectPb.DRPObjectBase { vertex.timestamp, vertex.signature ); - } catch (_) { - missing.push(vertex.hash); + this._applyOperation(drp, vertex.operation); + + this._setObjectACLState(vertex, preComputeLca); + this._setDRPState(vertex, preComputeLca, this._getDRPState(drp)); + } else { + const acl = this._computeObjectACL(vertex.dependencies, preComputeLca); + + this.hashGraph.addVertex( + vertex.operation, + vertex.dependencies, + vertex.peerId, + vertex.timestamp, + vertex.signature + ); + this._applyOperation(acl, vertex.operation); + + this._setObjectACLState(vertex, preComputeLca, this._getDRPState(acl)); + this._setDRPState(vertex, preComputeLca); } + this._initializeFinalityState(vertex.hash); + } catch (_) { + missing.push(vertex.hash); } - } else { - const missing = []; - for (const vertex of vertices) { - // Check to avoid manually crafted `undefined` operations - if (!vertex.operation || this.hashGraph.vertices.has(vertex.hash)) { - continue; - } + } - try { - if (!this._checkWriterPermission(vertex.peerId)) { - throw new Error(`${vertex.peerId} does not have write permission.`); - } - const preComputeLca = this.computeLCA(vertex.dependencies); - - if (vertex.operation.drpType === DrpType.DRP) { - const drp = this._computeDRP(vertex.dependencies, preComputeLca); - this.hashGraph.addVertex( - vertex.operation, - vertex.dependencies, - vertex.peerId, - vertex.timestamp, - vertex.signature - ); - this._applyOperation(drp, vertex.operation); - - this._setObjectACLState(vertex, preComputeLca); - this._setDRPState(vertex, preComputeLca, this._getDRPState(drp)); - } else { - const acl = this._computeObjectACL(vertex.dependencies, preComputeLca); - - this.hashGraph.addVertex( - vertex.operation, - vertex.dependencies, - vertex.peerId, - vertex.timestamp, - vertex.signature - ); - this._applyOperation(acl, vertex.operation); - - this._setObjectACLState(vertex, preComputeLca, this._getDRPState(acl)); - this._setDRPState(vertex, preComputeLca); - } - this._initializeFinalityState(vertex.hash); - } catch (_) { - missing.push(vertex.hash); - } + this.vertices = this.hashGraph.getAllVertices(); + this._updateObjectACLState(); + this._updateDRPState(); + this._notify("merge", this.vertices); + + return [missing.length === 0, missing]; + } + + /* Merges the vertices into the hashgraph without using DRP + */ + private _mergeWithoutDrp(vertices: Vertex[]): [merged: boolean, missing: string[]] { + const missing = []; + for (const vertex of vertices) { + if (!vertex.operation || this.hashGraph.vertices.has(vertex.hash)) { + continue; } - this.vertices = this.hashGraph.getAllVertices(); - this._updateObjectACLState(); - this._updateDRPState(); - this._notify("merge", this.vertices); + try { + if (!this._checkWriterPermission(vertex.peerId)) { + return [false, [vertex.hash]]; + } + this.hashGraph.addVertex( + vertex.operation, + vertex.dependencies, + vertex.peerId, + vertex.timestamp, + vertex.signature + ); + } catch (_) { + missing.push(vertex.hash); + } } + return [missing.length === 0, missing]; } + /* Merges the vertices into the hashgraph + * Returns a tuple with a boolean indicating if there were + * missing vertices and an array with the missing vertices + */ + merge(vertices: Vertex[]): [merged: boolean, missing: string[]] { + if (!this.hashGraph) { + throw new Error("Hashgraph is undefined"); + } + if (!this.drp) { + return this._mergeWithoutDrp(vertices); + } + return this._mergeWithDrp(vertices); + } + subscribe(callback: DRPObjectCallback) { this.subscriptions.push(callback); } From c517757c9be42dd2a62bb29712b4c81b809864d8 Mon Sep 17 00:00:00 2001 From: hoangquocvietuet Date: Wed, 5 Feb 2025 15:24:25 +0700 Subject: [PATCH 07/11] remove utils --- packages/node/src/handlers.ts | 17 +++++++++++++++-- packages/node/src/utils.ts | 18 ------------------ packages/object/src/index.ts | 10 ++++++++-- packages/object/src/utils/object.ts | 9 --------- 4 files changed, 23 insertions(+), 31 deletions(-) delete mode 100644 packages/object/src/utils/object.ts diff --git a/packages/node/src/handlers.ts b/packages/node/src/handlers.ts index 809a7f76b..818257240 100644 --- a/packages/node/src/handlers.ts +++ b/packages/node/src/handlers.ts @@ -4,7 +4,7 @@ import { type ACL, type DRPObject, HashGraph, type ObjectPb, type Vertex } from import { fromString as uint8ArrayFromString } from "uint8arrays/from-string"; import { type DRPNode, log } from "./index.js"; -import { deserializeStateMessage, serializeStateMessage, verifyACLSignature } from "./utils.js"; +import { deserializeStateMessage, serializeStateMessage } from "./utils.js"; /* Handler for all DRP messages, including pubsub messages and direct messages @@ -430,7 +430,20 @@ export async function verifyACLIncomingVertices( const data = uint8ArrayFromString(vertex.hash); try { - const isValid = await verifyACLSignature(publicKeyBytes, vertex.signature, data); + const cryptoKey = await crypto.subtle.importKey( + "raw", + publicKeyBytes, + { name: "Ed25519" }, + true, + ["verify"] + ); + + const isValid = await crypto.subtle.verify( + { name: "Ed25519" }, + cryptoKey, + vertex.signature, + data + ); return isValid ? vertex : null; } catch (error) { diff --git a/packages/node/src/utils.ts b/packages/node/src/utils.ts index 55b212559..2144d63dc 100644 --- a/packages/node/src/utils.ts +++ b/packages/node/src/utils.ts @@ -23,21 +23,3 @@ export function deserializeStateMessage(state?: ObjectPb.DRPState): ObjectPb.DRP } return drpState; } - -export async function verifyACLSignature( - publicKeyBytes: Uint8Array, - signature: Uint8Array, - data: Uint8Array -) { - const cryptoKey = await crypto.subtle.importKey( - "raw", - publicKeyBytes, - { name: "Ed25519" }, - true, - ["verify"] - ); - - const isValid = await crypto.subtle.verify({ name: "Ed25519" }, cryptoKey, signature, data); - - return isValid; -} diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index a57dca2c5..03ab48165 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -1,6 +1,7 @@ import { Logger, type LoggerOptions } from "@ts-drp/logger"; import { cloneDeep } from "es-toolkit"; import { deepEqual } from "fast-equals"; +import * as crypto from "node:crypto"; import { ObjectACL } from "./acl/index.js"; import type { ACL } from "./acl/interface.js"; @@ -20,7 +21,6 @@ import { type LcaAndOperations, } from "./interface.js"; import * as ObjectPb from "./proto/drp/object/v1/object_pb.js"; -import { computeDRPObjectId } from "./utils/object.js"; import { ObjectSet } from "./utils/objectSet.js"; export * as ObjectPb from "./proto/drp/object/v1/object_pb.js"; @@ -68,7 +68,13 @@ export class DRPObject implements ObjectPb.DRPObjectBase { this.peerId = options.peerId; log = new Logger("drp::object", options.config?.log_config); - this.id = options.id ?? computeDRPObjectId(options.peerId); + this.id = + options.id ?? + crypto + .createHash("sha256") + .update(options.peerId) + .update(Math.floor(Math.random() * Number.MAX_VALUE).toString()) + .digest("hex"); const objAcl = options.acl ?? diff --git a/packages/object/src/utils/object.ts b/packages/object/src/utils/object.ts deleted file mode 100644 index 6f2f96faa..000000000 --- a/packages/object/src/utils/object.ts +++ /dev/null @@ -1,9 +0,0 @@ -import * as crypto from "node:crypto"; - -export function computeDRPObjectId(peerId: string): string { - return crypto - .createHash("sha256") - .update(peerId) - .update(Math.floor(Math.random() * Number.MAX_VALUE).toString()) - .digest("hex"); -} From 5afaebc3675b637be7444f57d369163252b492f3 Mon Sep 17 00:00:00 2001 From: hoangquocvietuet Date: Wed, 5 Feb 2025 17:50:24 +0700 Subject: [PATCH 08/11] fix testcase --- packages/object/tests/hashgraph.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/object/tests/hashgraph.test.ts b/packages/object/tests/hashgraph.test.ts index 76178c868..c6c261df1 100644 --- a/packages/object/tests/hashgraph.test.ts +++ b/packages/object/tests/hashgraph.test.ts @@ -133,7 +133,7 @@ describe("HashGraph construction tests", () => { signature: new Uint8Array(), }); }).toThrowError("Invalid dependency detected."); - expect(selfCheckConstraints(obj1.hashGraph)).toBe(false); + expect(selfCheckConstraints(obj1.hashGraph)).toBe(true); const linearOps = obj1.hashGraph.linearizeOperations(); const expectedOps: Operation[] = [{ opType: "add", value: [1], drpType: DrpType.DRP }]; From cf03df2d8d05842e289615924dcb7ea5c16f954c Mon Sep 17 00:00:00 2001 From: droak Date: Wed, 5 Feb 2025 13:23:52 +0100 Subject: [PATCH 09/11] change order of merge fns --- packages/object/src/index.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index 9b4e93bfc..57caebef8 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -222,6 +222,20 @@ export class DRPObject implements ObjectPb.DRPObjectBase { this._notify("callFn", [vertex]); } + /* Merges the vertices into the hashgraph + * Returns a tuple with a boolean indicating if there were + * missing vertices and an array with the missing vertices + */ + merge(vertices: Vertex[]): [merged: boolean, missing: string[]] { + if (!this.hashGraph) { + throw new Error("Hashgraph is undefined"); + } + if (!this.drp) { + return this._mergeWithoutDrp(vertices); + } + return this._mergeWithDrp(vertices); + } + /* Merges the vertices into the hashgraph using DRP */ private _mergeWithDrp(vertices: Vertex[]): [merged: boolean, missing: string[]] { @@ -303,20 +317,6 @@ export class DRPObject implements ObjectPb.DRPObjectBase { return [missing.length === 0, missing]; } - /* Merges the vertices into the hashgraph - * Returns a tuple with a boolean indicating if there were - * missing vertices and an array with the missing vertices - */ - merge(vertices: Vertex[]): [merged: boolean, missing: string[]] { - if (!this.hashGraph) { - throw new Error("Hashgraph is undefined"); - } - if (!this.drp) { - return this._mergeWithoutDrp(vertices); - } - return this._mergeWithDrp(vertices); - } - subscribe(callback: DRPObjectCallback) { this.subscriptions.push(callback); } From 935488f543edfa38b4d3fa14efdc8156430a3d09 Mon Sep 17 00:00:00 2001 From: hoangquocvietuet Date: Wed, 5 Feb 2025 20:37:33 +0700 Subject: [PATCH 10/11] add tests --- packages/object/tests/hashgraph.test.ts | 57 +++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/packages/object/tests/hashgraph.test.ts b/packages/object/tests/hashgraph.test.ts index c6c261df1..13f52534c 100644 --- a/packages/object/tests/hashgraph.test.ts +++ b/packages/object/tests/hashgraph.test.ts @@ -366,6 +366,63 @@ describe("HashGraph for undefined operations tests", () => { }); }); +describe("Hashgraph and DRPObject merge without DRP tests", () => { + let obj1: DRPObject; + let obj2: DRPObject; + let obj3: DRPObject; + const acl = new ObjectACL({ + admins: new Map([ + ["peer1", { ed25519PublicKey: "pubKey1", blsPublicKey: "pubKey1" }], + ["peer2", { ed25519PublicKey: "pubKey2", blsPublicKey: "pubKey2" }], + ]), + }); + + beforeEach(async () => { + obj1 = new DRPObject({ peerId: "peer1", acl, drp: new SetDRP() }); + obj2 = new DRPObject({ peerId: "peer2", acl, drp: new SetDRP() }); + obj3 = new DRPObject({ peerId: "peer3", acl }); + + // reproduce Test: Joao's latest brain teaser + /* + __ V2:ADD(2) -------------\ + ROOT -- V1:ADD(1) / \ V5:RM(2) + \__ V3:RM(2) -- V4:RM(2) --/ + */ + + const drp1 = obj1.drp as SetDRP; + const drp2 = obj2.drp as SetDRP; + + drp1.add(1); + obj2.merge(obj1.hashGraph.getAllVertices()); + + drp1.add(2); + drp2.delete(2); + drp2.delete(2); + obj1.merge(obj2.hashGraph.getAllVertices()); + obj2.merge(obj1.hashGraph.getAllVertices()); + + drp1.delete(2); + obj2.merge(obj1.hashGraph.getAllVertices()); + + expect(drp1.query_has(1)).toBe(true); + expect(drp1.query_has(2)).toBe(false); + expect(obj1.hashGraph.vertices).toEqual(obj2.hashGraph.vertices); + + const linearOps = obj1.hashGraph.linearizeOperations(); + const expectedOps: Operation[] = [ + { opType: "add", value: [1], drpType: DrpType.DRP }, + { opType: "add", value: [2], drpType: DrpType.DRP }, + { opType: "delete", value: [2], drpType: DrpType.DRP }, + ]; + expect(linearOps).toEqual(expectedOps); + }); + + test("Test object3 merge", () => { + obj3.merge(obj1.hashGraph.getAllVertices()); + expect(obj3.hashGraph.vertices).toEqual(obj1.hashGraph.vertices); + }); +}); + describe("Vertex state tests", () => { let obj1: DRPObject; let obj2: DRPObject; From 0772ddf8fc7a42082fc77e144d62f898b54cf7b0 Mon Sep 17 00:00:00 2001 From: hoangquocvietuet Date: Wed, 5 Feb 2025 21:41:47 +0700 Subject: [PATCH 11/11] move test inside --- packages/object/tests/hashgraph.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/object/tests/hashgraph.test.ts b/packages/object/tests/hashgraph.test.ts index 13f52534c..65fc9ca3b 100644 --- a/packages/object/tests/hashgraph.test.ts +++ b/packages/object/tests/hashgraph.test.ts @@ -1,6 +1,6 @@ import { MapConflictResolution, MapDRP } from "@ts-drp/blueprints/src/Map/index.js"; import { SetDRP } from "@ts-drp/blueprints/src/Set/index.js"; -import { beforeEach, describe, expect, test } from "vitest"; +import { beforeAll, beforeEach, describe, expect, test } from "vitest"; import { ObjectACL } from "../src/acl/index.js"; import { ACLGroup, DRPObject, DrpType, Hash, HashGraph, type Operation } from "../src/index.js"; @@ -377,11 +377,13 @@ describe("Hashgraph and DRPObject merge without DRP tests", () => { ]), }); - beforeEach(async () => { + beforeAll(async () => { obj1 = new DRPObject({ peerId: "peer1", acl, drp: new SetDRP() }); obj2 = new DRPObject({ peerId: "peer2", acl, drp: new SetDRP() }); obj3 = new DRPObject({ peerId: "peer3", acl }); + }); + test("Test object3 merge", () => { // reproduce Test: Joao's latest brain teaser /* __ V2:ADD(2) -------------\ @@ -415,9 +417,7 @@ describe("Hashgraph and DRPObject merge without DRP tests", () => { { opType: "delete", value: [2], drpType: DrpType.DRP }, ]; expect(linearOps).toEqual(expectedOps); - }); - test("Test object3 merge", () => { obj3.merge(obj1.hashGraph.getAllVertices()); expect(obj3.hashGraph.vertices).toEqual(obj1.hashGraph.vertices); });