From 1f88e38fd08e9c38898c2ca3a012f9116e46b079 Mon Sep 17 00:00:00 2001 From: hoangquocvietuet Date: Wed, 5 Feb 2025 18:29:09 +0700 Subject: [PATCH 01/13] fix duplicate call --- packages/object/src/index.ts | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index 3b4ad7b4..99fcf84e 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -156,7 +156,15 @@ export class DRPObject implements ObjectPb.DRPObjectBase { if (callerName?.startsWith("DRPObject.resolveConflicts")) { return Reflect.apply(applyTarget, thisArg, args); } - if (!callerName?.startsWith("Proxy.")) obj.callFn(fullPropKey, args, vertexType); + if (!callerName?.startsWith("Proxy.")) { + const { applyOperationResult, stateChanged } = obj.callFn( + fullPropKey, + args, + vertexType + ); + if (stateChanged) return applyOperationResult; + return undefined; + } return Reflect.apply(applyTarget, thisArg, args); }, }); @@ -184,11 +192,15 @@ export class DRPObject implements ObjectPb.DRPObjectBase { preOperationDRP = this._computeDRP(this.hashGraph.getFrontier()); } const drp = cloneDeep(preOperationDRP); + let applyOperationResult = undefined; try { - this._applyOperation(drp, { opType: fn, value: args, drpType }); + applyOperationResult = this._applyOperation(drp, { opType: fn, value: args, drpType }); } catch (e) { log.error(`::drpObject::callFn: ${e}`); - return; + return { + applyOperationResult: undefined, + stateChanged: false, + }; } let stateChanged = false; @@ -200,7 +212,10 @@ export class DRPObject implements ObjectPb.DRPObjectBase { } if (!stateChanged) { - return; + return { + applyOperationResult: undefined, + stateChanged: false, + }; } const vertexTimestamp = Date.now(); @@ -220,6 +235,11 @@ export class DRPObject implements ObjectPb.DRPObjectBase { this.vertices.push(vertex); this._notify("callFn", [vertex]); + + return { + applyOperationResult, + stateChanged, + }; } /* Merges the vertices into the hashgraph @@ -334,7 +354,7 @@ export class DRPObject implements ObjectPb.DRPObjectBase { } try { - target[methodName](...value); + return target[methodName](...value); } catch (e) { throw new Error(`Error while applying operation ${opType}: ${e}`); } From 82204f00e300cd079aa4cb8f496386a4a4440135 Mon Sep 17 00:00:00 2001 From: hoangquocvietuet Date: Wed, 5 Feb 2025 20:15:39 +0700 Subject: [PATCH 02/13] fix duplicate call --- packages/object/src/index.ts | 43 +++++++++++++++--------- packages/object/tests/drpobject.test.ts | 44 ++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 17 deletions(-) diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index 99fcf84e..3714857e 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -157,13 +157,14 @@ export class DRPObject implements ObjectPb.DRPObjectBase { return Reflect.apply(applyTarget, thisArg, args); } if (!callerName?.startsWith("Proxy.")) { - const { applyOperationResult, stateChanged } = obj.callFn( - fullPropKey, - args, - vertexType - ); - if (stateChanged) return applyOperationResult; - return undefined; + const { + drp: newDRP, + appliedOperationResult, + isACL, + } = obj.callFn(fullPropKey, args, vertexType); + if (!isACL) Object.assign(obj.drp as DRP, newDRP); + else Object.assign(obj.acl as ObjectACL, newDRP); + return appliedOperationResult; } return Reflect.apply(applyTarget, thisArg, args); }, @@ -180,26 +181,34 @@ export class DRPObject implements ObjectPb.DRPObjectBase { // eslint-disable-next-line @typescript-eslint/no-explicit-any args: any, drpType: DrpType - ) { + ): { + drp: DRP; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + appliedOperationResult: any; + isACL: boolean; + } { if (!this.hashGraph) { throw new Error("Hashgraph is undefined"); } // eslint-disable-next-line @typescript-eslint/no-explicit-any let preOperationDRP: any; + let isACL = false; if (drpType === DrpType.ACL) { + isACL = true; preOperationDRP = this._computeObjectACL(this.hashGraph.getFrontier()); } else { preOperationDRP = this._computeDRP(this.hashGraph.getFrontier()); } const drp = cloneDeep(preOperationDRP); - let applyOperationResult = undefined; + let appliedOperationResult = undefined; try { - applyOperationResult = this._applyOperation(drp, { opType: fn, value: args, drpType }); + appliedOperationResult = this._applyOperation(drp, { opType: fn, value: args, drpType }); } catch (e) { log.error(`::drpObject::callFn: ${e}`); return { - applyOperationResult: undefined, - stateChanged: false, + drp, + appliedOperationResult, + isACL, }; } @@ -213,8 +222,9 @@ export class DRPObject implements ObjectPb.DRPObjectBase { if (!stateChanged) { return { - applyOperationResult: undefined, - stateChanged: false, + drp, + appliedOperationResult, + isACL, }; } @@ -237,8 +247,9 @@ export class DRPObject implements ObjectPb.DRPObjectBase { this._notify("callFn", [vertex]); return { - applyOperationResult, - stateChanged, + drp, + appliedOperationResult, + isACL, }; } diff --git a/packages/object/tests/drpobject.test.ts b/packages/object/tests/drpobject.test.ts index 7561a81d..1d1820e6 100644 --- a/packages/object/tests/drpobject.test.ts +++ b/packages/object/tests/drpobject.test.ts @@ -1,6 +1,8 @@ import { beforeEach, describe, expect, test } from "vitest"; -import { DRPObject } from "../src/index.js"; +import { SemanticsType } from "../dist/src/hashgraph/index.js"; +import { ActionType } from "../dist/src/hashgraph/index.js"; +import { DRP, DRPObject, ResolveConflictsType, Vertex } from "../src/index.js"; describe("AccessControl tests with RevokeWins resolution", () => { beforeEach(() => {}); @@ -25,3 +27,43 @@ describe("AccessControl tests with RevokeWins resolution", () => { expect(obj.drp).toBeUndefined(); }); }); + +describe("Hi", () => { + let counter = 0; + + class CounterDRP implements DRP { + semanticsType = SemanticsType.pair; + + private _counter: number; + + constructor() { + this._counter = 0; + } + + test() { + this._counter++; + counter++; + return this._counter; + } + + resolveConflicts(_: Vertex[]): ResolveConflictsType { + return { action: ActionType.Nop }; + } + } + + test("Detect duplicate call", () => { + const obj = new DRPObject({ + peerId: "", + publicCredential: { + ed25519PublicKey: "cred", + blsPublicKey: "cred", + }, + drp: new CounterDRP(), + }); + + const testDRP = obj.drp as CounterDRP; + expect(testDRP).toBeDefined(); + const ret = testDRP.test(); + expect(ret).toBe(counter); + }); +}); From b42cc8b6333c88196ec043e6b55a7a85f2b30a93 Mon Sep 17 00:00:00 2001 From: Sacha Froment Date: Wed, 5 Feb 2025 21:43:50 +0100 Subject: [PATCH 03/13] refactor: :recycle: make the function more readable Signed-off-by: Sacha Froment --- packages/object/src/index.ts | 81 +++++++++++++++--------------------- 1 file changed, 33 insertions(+), 48 deletions(-) diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index 3714857e..66e16981 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -36,6 +36,13 @@ export interface DRPObjectConfig { finality_config?: FinalityConfig; } +export interface callFnResult { + drp: DRP; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + appliedOperationResult: any; + isACL: boolean; +} + export let log: Logger; export class DRPObject implements ObjectPb.DRPObjectBase { @@ -181,56 +188,38 @@ export class DRPObject implements ObjectPb.DRPObjectBase { // eslint-disable-next-line @typescript-eslint/no-explicit-any args: any, drpType: DrpType - ): { - drp: DRP; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - appliedOperationResult: any; - isACL: boolean; - } { + ): callFnResult { if (!this.hashGraph) { throw new Error("Hashgraph is undefined"); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let preOperationDRP: any; - let isACL = false; - if (drpType === DrpType.ACL) { - isACL = true; - preOperationDRP = this._computeObjectACL(this.hashGraph.getFrontier()); - } else { - preOperationDRP = this._computeDRP(this.hashGraph.getFrontier()); - } + + const isACL = drpType === DrpType.ACL; + const computeFn = isACL ? this._computeObjectACL : this._computeDRP; + const vertexDependencies = this.hashGraph.getFrontier(); + const preOperationDRP = computeFn(vertexDependencies); + const drp = cloneDeep(preOperationDRP); - let appliedOperationResult = undefined; + const appliedOperationResult: callFnResult = { isACL, drp, appliedOperationResult: undefined }; try { - appliedOperationResult = this._applyOperation(drp, { opType: fn, value: args, drpType }); + appliedOperationResult.appliedOperationResult = this._applyOperation(drp, { + opType: fn, + value: args, + drpType, + }); } catch (e) { log.error(`::drpObject::callFn: ${e}`); - return { - drp, - appliedOperationResult, - isACL, - }; - } - - let stateChanged = false; - for (const key of Object.keys(preOperationDRP)) { - if (!deepEqual(preOperationDRP[key], drp[key])) { - stateChanged = true; - break; - } + return appliedOperationResult; } + const stateChanged = Object.keys(preOperationDRP).some( + (key) => !deepEqual(preOperationDRP[key], drp[key]) + ); if (!stateChanged) { - return { - drp, - appliedOperationResult, - isACL, - }; + return appliedOperationResult; } const vertexTimestamp = Date.now(); const vertexOperation = { drpType: drpType, opType: fn, value: args }; - const vertexDependencies = this.hashGraph.getFrontier(); const vertex = ObjectPb.Vertex.create({ hash: computeHash(this.peerId, vertexOperation, vertexDependencies, vertexTimestamp), peerId: this.peerId, @@ -238,19 +227,15 @@ export class DRPObject implements ObjectPb.DRPObjectBase { dependencies: vertexDependencies, timestamp: vertexTimestamp, }); - this.hashGraph.addToFrontier(vertex); + this.hashGraph.addToFrontier(vertex); this._setState(vertex, this._getDRPState(drp)); this._initializeFinalityState(vertex.hash); this.vertices.push(vertex); this._notify("callFn", [vertex]); - return { - drp, - appliedOperationResult, - isACL, - }; + return appliedOperationResult; } /* Merges the vertices into the hashgraph @@ -372,11 +357,11 @@ export class DRPObject implements ObjectPb.DRPObjectBase { } // compute the DRP based on all dependencies of the current vertex using partial linearization - private _computeDRP( + private _computeDRP = ( vertexDependencies: Hash[], preCompute?: LcaAndOperations, vertexOperation?: Operation - ): DRP { + ): DRP => { if (!this.drp || !this.originalDRP) { throw new Error("DRP is undefined"); } @@ -406,13 +391,13 @@ export class DRPObject implements ObjectPb.DRPObjectBase { } return drp; - } + }; - private _computeObjectACL( + private _computeObjectACL = ( vertexDependencies: Hash[], preCompute?: LcaAndOperations, vertexOperation?: Operation - ): DRP { + ): DRP => { if (!this.acl || !this.originalObjectACL) { throw new Error("ObjectACL is undefined"); } @@ -441,7 +426,7 @@ export class DRPObject implements ObjectPb.DRPObjectBase { } return acl; - } + }; private computeLCA(vertexDependencies: string[]) { if (!this.hashGraph) { From 1767403674e559e49e3d326034eca63eda9fcdc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ho=C3=A0ng=20Qu=E1=BB=91c=20Vi=E1=BB=87t?= Date: Thu, 6 Feb 2025 12:06:04 +0700 Subject: [PATCH 04/13] Update packages/object/tests/drpobject.test.ts Co-authored-by: Sacha Froment --- packages/object/tests/drpobject.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/object/tests/drpobject.test.ts b/packages/object/tests/drpobject.test.ts index 1d1820e6..d189836a 100644 --- a/packages/object/tests/drpobject.test.ts +++ b/packages/object/tests/drpobject.test.ts @@ -28,7 +28,7 @@ describe("AccessControl tests with RevokeWins resolution", () => { }); }); -describe("Hi", () => { +describe("Duplicate call detection", () => { let counter = 0; class CounterDRP implements DRP { From 2c58dbf890b910002e9c62ecaca69de9b7755e68 Mon Sep 17 00:00:00 2001 From: hoangquocvietuet Date: Mon, 10 Feb 2025 15:23:47 +0700 Subject: [PATCH 05/13] applying result in callFn --- packages/object/src/index.ts | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index 36a2fc20..03160837 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -36,13 +36,6 @@ export interface DRPObjectConfig { finality_config?: FinalityConfig; } -export interface callFnResult { - drp: DRP; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - appliedOperationResult: any; - isACL: boolean; -} - export let log: Logger; export class DRPObject implements ObjectPb.DRPObjectBase { @@ -164,13 +157,7 @@ export class DRPObject implements ObjectPb.DRPObjectBase { return Reflect.apply(applyTarget, thisArg, args); } if (!callerName?.startsWith("Proxy.")) { - const { - drp: newDRP, - appliedOperationResult, - isACL, - } = obj.callFn(fullPropKey, args, vertexType); - if (!isACL) Object.assign(obj.drp as DRP, newDRP); - else Object.assign(obj.acl as ObjectACL, newDRP); + const appliedOperationResult = obj.callFn(fullPropKey, args, vertexType); return appliedOperationResult; } return Reflect.apply(applyTarget, thisArg, args); @@ -188,7 +175,7 @@ export class DRPObject implements ObjectPb.DRPObjectBase { // eslint-disable-next-line @typescript-eslint/no-explicit-any args: any, drpType: DrpType - ): callFnResult { + ) { if (!this.hashGraph) { throw new Error("Hashgraph is undefined"); } @@ -199,9 +186,9 @@ export class DRPObject implements ObjectPb.DRPObjectBase { const preOperationDRP = computeFn(vertexDependencies); const drp = cloneDeep(preOperationDRP); - const appliedOperationResult: callFnResult = { isACL, drp, appliedOperationResult: undefined }; + let appliedOperationResult = undefined; try { - appliedOperationResult.appliedOperationResult = this._applyOperation(drp, { + appliedOperationResult = this._applyOperation(drp, { opType: fn, value: args, drpType, @@ -241,6 +228,9 @@ export class DRPObject implements ObjectPb.DRPObjectBase { this.vertices.push(vertex); this._notify("callFn", [vertex]); + if (!isACL) Object.assign(this.drp as DRP, drp); + else Object.assign(this.acl as ObjectACL, drp); + return appliedOperationResult; } From 686fdd99f3ffe08a00fc2940a472997dbdf98aa5 Mon Sep 17 00:00:00 2001 From: hoangquocvietuet Date: Wed, 12 Feb 2025 20:25:55 +0700 Subject: [PATCH 06/13] return directly --- packages/object/src/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index 03160837..1e5754f7 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -157,8 +157,7 @@ export class DRPObject implements ObjectPb.DRPObjectBase { return Reflect.apply(applyTarget, thisArg, args); } if (!callerName?.startsWith("Proxy.")) { - const appliedOperationResult = obj.callFn(fullPropKey, args, vertexType); - return appliedOperationResult; + return obj.callFn(fullPropKey, args, vertexType); } return Reflect.apply(applyTarget, thisArg, args); }, From e54f85e1895b4f4fefa8fe6262c145cf8571d3c2 Mon Sep 17 00:00:00 2001 From: Sacha Froment Date: Wed, 12 Feb 2025 15:20:43 +0100 Subject: [PATCH 07/13] chore: simplify Signed-off-by: Sacha Froment --- packages/object/src/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index 1e5754f7..6833def1 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -180,9 +180,10 @@ export class DRPObject implements ObjectPb.DRPObjectBase { } const isACL = drpType === DrpType.ACL; - const computeFn = isACL ? this._computeObjectACL : this._computeDRP; const vertexDependencies = this.hashGraph.getFrontier(); - const preOperationDRP = computeFn(vertexDependencies); + const preOperationDRP = isACL + ? this._computeObjectACL(vertexDependencies) + : this._computeDRP(vertexDependencies); const drp = cloneDeep(preOperationDRP); let appliedOperationResult = undefined; From 472f1787eb76987271bf5964bd47687e97e9ef81 Mon Sep 17 00:00:00 2001 From: Sacha Froment Date: Wed, 12 Feb 2025 15:24:10 +0100 Subject: [PATCH 08/13] chore: simplify Signed-off-by: Sacha Froment --- packages/object/src/index.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index 6833def1..644f0b08 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -149,17 +149,17 @@ export class DRPObject implements ObjectPb.DRPObjectBase { const fullPropKey = String(propKey); return new Proxy(target[propKey as keyof object], { apply(applyTarget, thisArg, args) { - if ((propKey as string).startsWith("query_")) { - return Reflect.apply(applyTarget, thisArg, args); - } const callerName = new Error().stack?.split("\n")[2]?.trim().split(" ")[1]; - if (callerName?.startsWith("DRPObject.resolveConflicts")) { + const startWithQuery = (propKey as string).startsWith("query_"); + const startWithResolveConflicts = callerName?.startsWith( + "DRPObject.resolveConflicts" + ); + const startWithProxy = callerName?.startsWith("Proxy."); + const needReflect = startWithQuery || startWithResolveConflicts || startWithProxy; + if (needReflect) { return Reflect.apply(applyTarget, thisArg, args); } - if (!callerName?.startsWith("Proxy.")) { - return obj.callFn(fullPropKey, args, vertexType); - } - return Reflect.apply(applyTarget, thisArg, args); + return obj.callFn(fullPropKey, args, vertexType); }, }); } From 68032b089919bea05fa657f8d72ea4308c649352 Mon Sep 17 00:00:00 2001 From: hoangquocvietuet Date: Wed, 12 Feb 2025 23:19:04 +0700 Subject: [PATCH 09/13] add testcase --- packages/object/tests/drpobject.test.ts | 44 ++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/packages/object/tests/drpobject.test.ts b/packages/object/tests/drpobject.test.ts index ebc6e3bb..8f334a18 100644 --- a/packages/object/tests/drpobject.test.ts +++ b/packages/object/tests/drpobject.test.ts @@ -1,7 +1,9 @@ import { SetDRP } from "@ts-drp/blueprints/src/index.js"; import { beforeEach, describe, expect, it, test } from "vitest"; -import { DRPObject, ObjectACL } from "../src/index.js"; +import { SemanticsType } from "../dist/src/hashgraph/index.js"; +import { ActionType } from "../dist/src/hashgraph/index.js"; +import { DRP, DRPObject, ObjectACL, ResolveConflictsType, Vertex } from "../src/index.js"; const acl = new ObjectACL({ admins: new Map([ @@ -69,3 +71,43 @@ describe("Drp Object should be able to change state value", () => { } }); }); + +describe("Test for duplicate call issue", () => { + let counter = 0; + + class CounterDRP implements DRP { + semanticsType = SemanticsType.pair; + + private _counter: number; + + constructor() { + this._counter = 0; + } + + test() { + this._counter++; + counter++; + return this._counter; + } + + resolveConflicts(_: Vertex[]): ResolveConflictsType { + return { action: ActionType.Nop }; + } + } + + test("Detect duplicate call", () => { + const obj = new DRPObject({ + peerId: "", + publicCredential: { + ed25519PublicKey: "cred", + blsPublicKey: "cred", + }, + drp: new CounterDRP(), + }); + + const testDRP = obj.drp as CounterDRP; + expect(testDRP).toBeDefined(); + const ret = testDRP.test(); + expect(ret).toBe(counter); + }); +}); From eef7383d8de2099917a90e35ed423dc253662e06 Mon Sep 17 00:00:00 2001 From: Jan Lewandowski <62848215+JanLewDev@users.noreply.github.com> Date: Sat, 15 Feb 2025 13:24:17 +0100 Subject: [PATCH 10/13] fix: pr template (#464) --- .github/PULL_REQUEST_TEMPLATE.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index bc3ed887..b51a947f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -14,7 +14,9 @@ - Update any related documentation. --> -## What type of PR is this? (check all applicable) +## What type of PR is this? + + - [ ] Refactor - [ ] Feature @@ -31,6 +33,8 @@ ## Added/updated tests? + + - [ ] Yes - [ ] No, because why: _justify why you didn't add tests_ - [ ] I need help with writing tests From 23abdfd6b95ae32b12f03fc0376de6cf0c41c7fc Mon Sep 17 00:00:00 2001 From: anhnd350309 <156870690+anhnd350309@users.noreply.github.com> Date: Mon, 17 Feb 2025 19:27:34 +0700 Subject: [PATCH 11/13] refactor: remove unuse transport (#466) --- packages/network/src/node.ts | 5 +---- packages/object/src/index.ts | 17 +---------------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/packages/network/src/node.ts b/packages/network/src/node.ts index cb16fe54..ced15c90 100644 --- a/packages/network/src/node.ts +++ b/packages/network/src/node.ts @@ -25,10 +25,9 @@ import { type PubSubPeerDiscoveryComponents, pubsubPeerDiscovery, } from "@libp2p/pubsub-peer-discovery"; -import { webRTC, webRTCDirect } from "@libp2p/webrtc"; +import { webRTC } from "@libp2p/webrtc"; import { webSockets } from "@libp2p/websockets"; import * as filters from "@libp2p/websockets/filters"; -import { webTransport } from "@libp2p/webtransport"; import { type MultiaddrInput, multiaddr } from "@multiformats/multiaddr"; import { WebRTC } from "@multiformats/multiaddr-matcher"; import { Logger, type LoggerOptions } from "@ts-drp/logger"; @@ -192,11 +191,9 @@ export class DRPNetworkNode { transports: [ circuitRelayTransport(), webRTC(), - webRTCDirect(), webSockets({ filter: filters.all, }), - webTransport(), ], }); log.info( diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index c64451f9..877a0f55 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -6,13 +6,7 @@ import * as crypto from "node:crypto"; import { ObjectACL } from "./acl/index.js"; import type { ACL } from "./acl/interface.js"; import { type FinalityConfig, FinalityStore } from "./finality/index.js"; -import { - type Hash, - HashGraph, - type Operation, - type ResolveConflictsType, - type Vertex, -} from "./hashgraph/index.js"; +import { type Hash, HashGraph, type Operation, type Vertex } from "./hashgraph/index.js"; import { type DRP, type DRPObjectCallback, @@ -128,15 +122,6 @@ export class DRPObject implements ObjectPb.DRPObjectBase { return object; } - resolveConflicts(vertices: Vertex[]): ResolveConflictsType { - if (this.acl && vertices.some((v) => v.operation?.drpType === DrpType.ACL)) { - const acl = this.acl as ACL; - return acl.resolveConflicts(vertices); - } - const drp = this.drp as DRP; - return drp.resolveConflicts(vertices); - } - // This function is black magic, it allows us to intercept calls to the DRP object proxyDRPHandler(vertexType: DrpType): ProxyHandler { // eslint-disable-next-line @typescript-eslint/no-this-alias From 8ffdcec7d34cf7a0ada6c3ae013b1f322f11b3d6 Mon Sep 17 00:00:00 2001 From: Sacha Froment Date: Mon, 17 Feb 2025 13:28:53 +0100 Subject: [PATCH 12/13] refactor(object): linearize pair semantic (#476) --- .../object/src/linearize/pairSemantics.ts | 51 ++-- packages/object/tests/linearize/multi.test.ts | 240 ++++++++++++++++++ 2 files changed, 263 insertions(+), 28 deletions(-) create mode 100644 packages/object/tests/linearize/multi.test.ts diff --git a/packages/object/src/linearize/pairSemantics.ts b/packages/object/src/linearize/pairSemantics.ts index c1036fb3..9c3d3066 100644 --- a/packages/object/src/linearize/pairSemantics.ts +++ b/packages/object/src/linearize/pairSemantics.ts @@ -6,44 +6,39 @@ export function linearizePairSemantics( origin: Hash, subgraph: ObjectSet ): Operation[] { - const order: Hash[] = hashGraph.topologicalSort(true, origin, subgraph); - const dropped = new Array(order.length).fill(false); - const result = []; - // alway remove the first operation - let i = 1; + const order = hashGraph.topologicalSort(true, origin, subgraph); + const dropped = new Array(order.length).fill(false); + const result: Operation[] = []; + + // Skip root operation + for (let i = 1; i < order.length; i++) { + if (dropped[i]) continue; - while (i < order.length) { - if (dropped[i]) { - i++; - continue; - } let anchor = order[i]; - let j = i + 1; + let modified = false; - while (j < order.length) { - if (hashGraph.areCausallyRelatedUsingBitsets(anchor, order[j]) || dropped[j]) { - j++; + // Compare with all later operations + for (let j = i + 1; j < order.length; j++) { + if (dropped[j] || hashGraph.areCausallyRelatedUsingBitsets(anchor, order[j])) { continue; } - const moving = order[j]; const v1 = hashGraph.vertices.get(anchor); - const v2 = hashGraph.vertices.get(moving); - let action: ActionType; + const v2 = hashGraph.vertices.get(order[j]); + if (!v1 || !v2) { - action = ActionType.Nop; - } else { - action = hashGraph.resolveConflicts([v1, v2]).action; + continue; } + const { action } = hashGraph.resolveConflicts([v1, v2]); + switch (action) { case ActionType.DropLeft: dropped[i] = true; - j = order.length; + modified = true; break; case ActionType.DropRight: dropped[j] = true; - j++; break; case ActionType.Swap: hashGraph.swapReachablePredecessors(order[i], order[j]); @@ -51,17 +46,17 @@ export function linearizePairSemantics( j = i + 1; anchor = order[i]; break; - case ActionType.Nop: - j++; - break; } + + if (modified) break; } if (!dropped[i]) { - const op = hashGraph.vertices.get(order[i])?.operation; - if (op && op.value !== null) result.push(op); + const vertex = hashGraph.vertices.get(order[i]); + if (vertex?.operation && vertex.operation.value !== null) { + result.push(vertex.operation); + } } - i++; } return result; diff --git a/packages/object/tests/linearize/multi.test.ts b/packages/object/tests/linearize/multi.test.ts new file mode 100644 index 00000000..c81bc562 --- /dev/null +++ b/packages/object/tests/linearize/multi.test.ts @@ -0,0 +1,240 @@ +import { newVertex } from "@ts-drp/object/src/index.js"; +import { describe, expect, test } from "vitest"; + +import { ActionType, HashGraph, type Vertex, SemanticsType } from "../../src/hashgraph/index.js"; +import { DrpType } from "../../src/index.js"; +import { linearizeMultipleSemantics } from "../../src/linearize/multipleSemantics.js"; +import { ObjectSet } from "../../src/utils/objectSet.js"; + +describe("linearizeMultipleSemantics", () => { + test("should linearize operations in a simple sequence", () => { + const hashGraph = new HashGraph( + "", + (_vertices: Vertex[]) => ({ + action: ActionType.Nop, + }), + () => ({ + action: ActionType.Nop, + }), + SemanticsType.multiple + ); + const origin = HashGraph.rootHash; + + // Add vertices to the graph + hashGraph.addVertex( + newVertex( + "", + { + opType: "set", + value: [1], + drpType: DrpType.DRP, + }, + hashGraph.getFrontier(), + Date.now(), + new Uint8Array() + ) + ); + + hashGraph.addVertex( + newVertex( + "", + { + opType: "set", + value: [2], + drpType: DrpType.DRP, + }, + hashGraph.getFrontier(), + Date.now(), + new Uint8Array() + ) + ); + + const subgraph = new ObjectSet(); + hashGraph.getAllVertices().forEach((vertex) => subgraph.add(vertex.hash)); + + const result = linearizeMultipleSemantics(hashGraph, origin, subgraph); + expect(result.map((op) => op.value)).toEqual([[1], [2]]); + }); + + test("should handle concurrent operations with conflict resolution", () => { + const hashGraph = new HashGraph( + "", + (_vertices: Vertex[]) => ({ + action: ActionType.Drop, + vertices: _vertices.filter((_, index) => index !== 0).map((vertex) => vertex.hash), + }), + (_vertices: Vertex[]) => ({ + action: ActionType.Drop, + vertices: _vertices.filter((_, index) => index !== 0).map((vertex) => vertex.hash), + }), + SemanticsType.multiple + ); + const origin = HashGraph.rootHash; + let frontier = hashGraph.getFrontier(); + + // Add concurrent vertices + hashGraph.addVertex( + newVertex( + "", + { + opType: "set", + value: [1], + drpType: DrpType.DRP, + }, + frontier, + Date.now(), + new Uint8Array() + ) + ); + + hashGraph.addVertex( + newVertex( + "", + { + opType: "set", + value: [2], + drpType: DrpType.DRP, + }, + frontier, + Date.now(), + new Uint8Array() + ) + ); + + hashGraph.addVertex( + newVertex( + "", + { + opType: "set", + value: [3], + drpType: DrpType.DRP, + }, + frontier, + Date.now(), + new Uint8Array() + ) + ); + + // Get the frontier + frontier = hashGraph.getFrontier(); + + hashGraph.addVertex( + newVertex( + "", + { + opType: "set", + value: [4], + drpType: DrpType.DRP, + }, + frontier, + Date.now(), + new Uint8Array() + ) + ); + console.log(`frontier: ${frontier}`); + hashGraph.addVertex( + newVertex( + "", + { + opType: "set", + value: [5], + drpType: DrpType.DRP, + }, + frontier.filter((_, idx) => idx !== 0), + Date.now(), + new Uint8Array() + ) + ); + + frontier = hashGraph.getFrontier(); + + hashGraph.addVertex( + newVertex( + "", + { + opType: "set", + value: [6], + drpType: DrpType.DRP, + }, + frontier, + Date.now(), + new Uint8Array() + ) + ); + + const subgraph = new ObjectSet(); + hashGraph.getAllVertices().forEach((vertex) => subgraph.add(vertex.hash)); + + const result = linearizeMultipleSemantics(hashGraph, origin, subgraph); + expect(result.map((op) => op.value)).toEqual([[1], [4], [6]]); + }); + + test("should handle operations with null values", () => { + const hashGraph = new HashGraph( + "", + () => ({ + action: ActionType.Nop, + }), + () => ({ + action: ActionType.Nop, + }), + SemanticsType.multiple + ); + const origin = HashGraph.rootHash; + + // Add vertices to the graph + hashGraph.addVertex( + newVertex( + "", + { + opType: "set", + value: null, + drpType: DrpType.DRP, + }, + hashGraph.getFrontier(), + Date.now(), + new Uint8Array() + ) + ); + + hashGraph.addVertex( + newVertex( + "", + { + opType: "set", + value: [2], + drpType: DrpType.DRP, + }, + hashGraph.getFrontier(), + Date.now(), + new Uint8Array() + ) + ); + + const subgraph = new ObjectSet(); + hashGraph.getAllVertices().forEach((vertex) => subgraph.add(vertex.hash)); + + const result = linearizeMultipleSemantics(hashGraph, origin, subgraph); + expect(result.map((op) => op.value)).toEqual([[2]]); + }); + + test("should handle empty subgraph", () => { + const hashGraph = new HashGraph( + "", + () => ({ + action: ActionType.Nop, + }), + () => ({ + action: ActionType.Nop, + }), + SemanticsType.multiple + ); + const origin = HashGraph.rootHash; + + const subgraph = new ObjectSet(); + subgraph.add(origin); + + const result = linearizeMultipleSemantics(hashGraph, origin, subgraph); + expect(result).toEqual([]); + }); +}); From 35a4a362eeca58636f115777311fa19bb1b7e679 Mon Sep 17 00:00:00 2001 From: hoangquocvietuet Date: Tue, 18 Feb 2025 21:14:56 +0700 Subject: [PATCH 13/13] try to revert proxy handler --- packages/object/src/index.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index 877a0f55..e0a7b366 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -134,17 +134,17 @@ export class DRPObject implements ObjectPb.DRPObjectBase { const fullPropKey = String(propKey); return new Proxy(target[propKey as keyof object], { apply(applyTarget, thisArg, args) { + if ((propKey as string).startsWith("query_")) { + return Reflect.apply(applyTarget, thisArg, args); + } const callerName = new Error().stack?.split("\n")[2]?.trim().split(" ")[1]; - const startWithQuery = (propKey as string).startsWith("query_"); - const startWithResolveConflicts = callerName?.startsWith( - "DRPObject.resolveConflicts" - ); - const startWithProxy = callerName?.startsWith("Proxy."); - const needReflect = startWithQuery || startWithResolveConflicts || startWithProxy; - if (needReflect) { + if (callerName?.startsWith("DRPObject.resolveConflicts")) { return Reflect.apply(applyTarget, thisArg, args); } - return obj.callFn(fullPropKey, args, vertexType); + if (!callerName?.startsWith("Proxy.")) { + return obj.callFn(fullPropKey, args, vertexType); + } + return Reflect.apply(applyTarget, thisArg, args); }, }); }