diff --git a/.gitignore b/.gitignore index b2c616ea..e2d9284b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ node_modules/ **/tsconfig.tsbuildinfo coverage/* -.vscode/* \ No newline at end of file +.vscode/* +**/object_grpc_pb.js \ No newline at end of file diff --git a/biome.json b/biome.json index 87ce871c..d589a0b1 100644 --- a/biome.json +++ b/biome.json @@ -23,5 +23,10 @@ }, "organizeImports": { "enabled": true - } + }, + "overrides": [ + { + "include": ["tests/**"] + } + ] } diff --git a/packages/blueprints/src/ACL/index.ts b/packages/blueprints/src/ACL/index.ts index 1fd2f14a..76b96497 100644 --- a/packages/blueprints/src/ACL/index.ts +++ b/packages/blueprints/src/ACL/index.ts @@ -72,7 +72,7 @@ export class ACL implements IACL, DRP { if (!vertices[0].operation || !vertices[1].operation) return { action: ActionType.Nop }; if ( - vertices[0].operation.type === vertices[1].operation.type || + vertices[0].operation.opType === vertices[1].operation.opType || vertices[0].operation.value[0] !== vertices[1].operation.value[0] ) return { action: ActionType.Nop }; @@ -80,13 +80,13 @@ export class ACL implements IACL, DRP { return this._conflictResolution === ACLConflictResolution.GrantWins ? { action: - vertices[0].operation.type === "grant" + vertices[0].operation.opType === "grant" ? ActionType.DropRight : ActionType.DropLeft, } : { action: - vertices[0].operation.type === "grant" + vertices[0].operation.opType === "grant" ? ActionType.DropLeft : ActionType.DropRight, }; diff --git a/packages/blueprints/src/AddWinsSet/index.ts b/packages/blueprints/src/AddWinsSet/index.ts index 5cbfcce3..20c81164 100644 --- a/packages/blueprints/src/AddWinsSet/index.ts +++ b/packages/blueprints/src/AddWinsSet/index.ts @@ -38,10 +38,10 @@ export class AddWinsSet implements DRP { if ( vertices[0].operation && vertices[1].operation && - vertices[0].operation?.type !== vertices[1].operation?.type && + vertices[0].operation?.opType !== vertices[1].operation?.opType && vertices[0].operation?.value[0] === vertices[1].operation?.value[0] ) { - return vertices[0].operation.type === "add" + return vertices[0].operation.opType === "add" ? { action: ActionType.DropRight } : { action: ActionType.DropLeft }; } diff --git a/packages/blueprints/src/AddWinsSetWithACL/index.ts b/packages/blueprints/src/AddWinsSetWithACL/index.ts index 101de154..65ace174 100644 --- a/packages/blueprints/src/AddWinsSetWithACL/index.ts +++ b/packages/blueprints/src/AddWinsSetWithACL/index.ts @@ -41,23 +41,23 @@ export class AddWinsSetWithACL implements DRP { if (!vertices[0].operation || !vertices[1].operation) return { action: ActionType.Nop }; if ( - vertices[0].operation.type === vertices[1].operation.type || + vertices[0].operation.opType === vertices[1].operation.opType || vertices[0].operation.value[0] !== vertices[1].operation.value[0] ) return { action: ActionType.Nop }; if ( - ["grant", "revoke"].includes(vertices[0].operation.type) && - ["grant", "revoke"].includes(vertices[1].operation.type) + ["grant", "revoke"].includes(vertices[0].operation.opType) && + ["grant", "revoke"].includes(vertices[1].operation.opType) ) { return this.acl.resolveConflicts(vertices); } if ( - this.operations.includes(vertices[0].operation.type) && - this.operations.includes(vertices[1].operation.type) + this.operations.includes(vertices[0].operation.opType) && + this.operations.includes(vertices[1].operation.opType) ) { - return vertices[0].operation.type === "add" + return vertices[0].operation.opType === "add" ? { action: ActionType.DropRight } : { action: ActionType.DropLeft }; } diff --git a/packages/blueprints/tests/AddWinsSetWithACL.test.ts b/packages/blueprints/tests/AddWinsSetWithACL.test.ts index ecc1b34d..c1941afe 100644 --- a/packages/blueprints/tests/AddWinsSetWithACL.test.ts +++ b/packages/blueprints/tests/AddWinsSetWithACL.test.ts @@ -43,7 +43,7 @@ describe("AccessControl tests with RevokeWins resolution", () => { { hash: "", peerId: "peer1", - operation: { type: "grant", value: "peer3" }, + operation: { opType: "grant", value: "peer3" }, dependencies: [], signature: new Uint8Array(), timestamp: 0, @@ -51,7 +51,7 @@ describe("AccessControl tests with RevokeWins resolution", () => { { hash: "", peerId: "peer2", - operation: { type: "revoke", value: "peer3" }, + operation: { opType: "revoke", value: "peer3" }, dependencies: [], signature: new Uint8Array(), timestamp: 0, diff --git a/packages/network/src/proto/drp/object/v1/object_pb.ts b/packages/network/src/proto/drp/object/v1/object_pb.ts index 9dcf6e84..c1eb666d 100644 --- a/packages/network/src/proto/drp/object/v1/object_pb.ts +++ b/packages/network/src/proto/drp/object/v1/object_pb.ts @@ -21,7 +21,8 @@ export interface Vertex { } export interface Vertex_Operation { - type: string; + drpType: string; + opType: string; value: any | undefined; } @@ -177,16 +178,19 @@ export const Vertex: MessageFns = { }; function createBaseVertex_Operation(): Vertex_Operation { - return { type: "", value: undefined }; + return { drpType: "", opType: "", value: undefined }; } export const Vertex_Operation: MessageFns = { encode(message: Vertex_Operation, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { - if (message.type !== "") { - writer.uint32(10).string(message.type); + if (message.drpType !== "") { + writer.uint32(10).string(message.drpType); + } + if (message.opType !== "") { + writer.uint32(18).string(message.opType); } if (message.value !== undefined) { - Value.encode(Value.wrap(message.value), writer.uint32(18).fork()).join(); + Value.encode(Value.wrap(message.value), writer.uint32(26).fork()).join(); } return writer; }, @@ -203,7 +207,7 @@ export const Vertex_Operation: MessageFns = { break; } - message.type = reader.string(); + message.drpType = reader.string(); continue; } case 2: { @@ -211,6 +215,14 @@ export const Vertex_Operation: MessageFns = { break; } + message.opType = reader.string(); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + message.value = Value.unwrap(Value.decode(reader, reader.uint32())); continue; } @@ -225,15 +237,19 @@ export const Vertex_Operation: MessageFns = { fromJSON(object: any): Vertex_Operation { return { - type: isSet(object.type) ? globalThis.String(object.type) : "", + drpType: isSet(object.drpType) ? globalThis.String(object.drpType) : "", + opType: isSet(object.opType) ? globalThis.String(object.opType) : "", value: isSet(object?.value) ? object.value : undefined, }; }, toJSON(message: Vertex_Operation): unknown { const obj: any = {}; - if (message.type !== "") { - obj.type = message.type; + if (message.drpType !== "") { + obj.drpType = message.drpType; + } + if (message.opType !== "") { + obj.opType = message.opType; } if (message.value !== undefined) { obj.value = message.value; @@ -246,7 +262,8 @@ export const Vertex_Operation: MessageFns = { }, fromPartial, I>>(object: I): Vertex_Operation { const message = createBaseVertex_Operation(); - message.type = object.type ?? ""; + message.drpType = object.drpType ?? ""; + message.opType = object.opType ?? ""; message.value = object.value ?? undefined; return message; }, diff --git a/packages/node/src/handlers.ts b/packages/node/src/handlers.ts index da035812..45b10b37 100644 --- a/packages/node/src/handlers.ts +++ b/packages/node/src/handlers.ts @@ -1,6 +1,6 @@ import type { Stream } from "@libp2p/interface"; import { NetworkPb, streamToUint8Array } from "@ts-drp/network"; -import type { DRP, DRPObject, ObjectPb, Vertex } from "@ts-drp/object"; +import type { DRPObject, IACL, ObjectPb, Vertex } from "@ts-drp/object"; import { fromString as uint8ArrayFromString } from "uint8arrays/from-string"; import { type DRPNode, log } from "./index.js"; @@ -248,7 +248,8 @@ export async function verifyIncomingVertices( hash: vertex.hash, peerId: vertex.peerId, operation: { - type: vertex.operation?.type ?? "", + drpType: vertex.operation?.drpType ?? "", + opType: vertex.operation?.opType ?? "", value: vertex.operation?.value, }, dependencies: vertex.dependencies, @@ -257,11 +258,10 @@ export async function verifyIncomingVertices( }; }); - const drp = object.drp as DRP; - if (!drp.acl) { + const acl: IACL = object.acl as IACL; + if (!acl) { return vertices; } - const acl = drp.acl; const verificationPromises = vertices.map(async (vertex) => { if (vertex.signature.length === 0) { return null; diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index ee3f8210..ad77951f 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -6,7 +6,7 @@ import { type DRPNetworkNodeConfig, NetworkPb, } from "@ts-drp/network"; -import { type DRP, DRPObject, type Vertex } from "@ts-drp/object"; +import { type DRP, DRPObject, type IACL, type Vertex } from "@ts-drp/object"; import { drpMessagesHandler } from "./handlers.js"; import * as operations from "./operations.js"; import { DRPObjectStore } from "./store/index.js"; @@ -86,7 +86,13 @@ export class DRPNode { sync?: boolean, peerId?: string, ) { - const object = new DRPObject(this.networkNode.peerId, drp, id, abi); + const object = new DRPObject( + this.networkNode.peerId, + drp, + null as unknown as IACL & DRP, + id, + abi, + ); operations.createObject(this, object); operations.subscribeObject(this, object.id); if (sync) { diff --git a/packages/node/tests/node.test.ts b/packages/node/tests/node.test.ts index 4c997dc7..58c857aa 100644 --- a/packages/node/tests/node.test.ts +++ b/packages/node/tests/node.test.ts @@ -1,6 +1,6 @@ -import { AddWinsSetWithACL } from "@topology-foundation/blueprints/src/AddWinsSetWithACL/index.js"; +import { ACL } from "@topology-foundation/blueprints/src/ACL/index.js"; import { AddWinsSet } from "@topology-foundation/blueprints/src/index.js"; -import { type DRP, DRPObject } from "@ts-drp/object"; +import { type DRP, DRPObject, DrpType } from "@ts-drp/object"; import { beforeAll, beforeEach, describe, expect, test } from "vitest"; import { signGeneratedVertices, @@ -18,12 +18,13 @@ describe("DPRNode with verify and sign signature", () => { }); beforeEach(async () => { - drp = new AddWinsSetWithACL( + drp = new AddWinsSet(); + const acl = new ACL( new Map([ [drpNode.networkNode.peerId, drpNode.networkNode.publicKey || ""], ]), ); - drpObject = new DRPObject(drpNode.networkNode.peerId, drp); + drpObject = new DRPObject(drpNode.networkNode.peerId, drp, acl); }); test("Node will not sign vertex if it is not the creator", async () => { @@ -68,7 +69,7 @@ describe("DPRNode with verify and sign signature", () => { hash: "hash", peerId: drpNode.networkNode.peerId, operation: { - type: "add", + opType: "add", value: 1, }, dependencies: [], @@ -110,6 +111,7 @@ describe("DPRNode with verify and sign signature", () => { operation: { type: "add", value: 1, + vertexType: DrpType.Drp, }, dependencies: [], timestamp: Date.now(), diff --git a/packages/object/src/hashgraph/index.ts b/packages/object/src/hashgraph/index.ts index f0405a0a..db0527ca 100644 --- a/packages/object/src/hashgraph/index.ts +++ b/packages/object/src/hashgraph/index.ts @@ -44,7 +44,8 @@ export type VertexDistance = { export class HashGraph { peerId: string; - resolveConflicts: (vertices: Vertex[]) => ResolveConflictsType; + resolveConflictsDRP: (vertices: Vertex[]) => ResolveConflictsType; + resolveConflictsACL: (vertices: Vertex[]) => ResolveConflictsType; semanticsType: SemanticsType; vertices: Map = new Map(); @@ -69,18 +70,21 @@ export class HashGraph { constructor( peerId: string, - resolveConflicts: (vertices: Vertex[]) => ResolveConflictsType, + resolveConflictsDRP: (vertices: Vertex[]) => ResolveConflictsType, + resolveConflictsACL: (vertices: Vertex[]) => ResolveConflictsType, semanticsType: SemanticsType, ) { this.peerId = peerId; - this.resolveConflicts = resolveConflicts; + this.resolveConflictsDRP = resolveConflictsDRP; + this.resolveConflictsACL = resolveConflictsACL; this.semanticsType = semanticsType; const rootVertex: Vertex = { hash: HashGraph.rootHash, peerId: "", operation: { - type: OperationType.NOP, + drpType: "", + opType: OperationType.NOP, value: null, }, dependencies: [], @@ -95,6 +99,13 @@ export class HashGraph { }); } + resolveConflicts(vertices: Vertex[]): ResolveConflictsType { + if (vertices[0].operation?.drpType === "ACL") { + return this.resolveConflictsACL(vertices); + } + return this.resolveConflictsDRP(vertices); + } + addToFrontier(operation: Operation): Vertex { const deps = this.getFrontier(); const currentTimestamp = Date.now(); @@ -103,7 +114,7 @@ export class HashGraph { const vertex: Vertex = { hash, peerId: this.peerId, - operation: operation ?? { type: OperationType.NOP }, + operation: operation ?? { opType: OperationType.NOP }, dependencies: deps, timestamp: currentTimestamp, signature: new Uint8Array(), diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index 95139e91..a7f084e7 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -27,7 +27,6 @@ export interface IACL { export interface DRP { semanticsType: SemanticsType; resolveConflicts: (vertices: Vertex[]) => ResolveConflictsType; - acl?: IACL & DRP; // biome-ignore lint: attributes can be anything [key: string]: any; } @@ -54,8 +53,18 @@ export interface DRPObjectConfig { log_config?: LoggerOptions; } +export interface LcaAndOperations { + lca: string; + linearizedOperations: Operation[]; +} + export let log: Logger; +export enum DrpType { + Acl = "ACL", + Drp = "DRP", +} + export class DRPObject implements IDRPObject { peerId: string; id: string; @@ -63,15 +72,19 @@ export class DRPObject implements IDRPObject { bytecode: Uint8Array; vertices: ObjectPb.Vertex[]; drp: ProxyHandler | null; + acl: ProxyHandler | null; hashGraph: HashGraph; // mapping from vertex hash to the DRP state - states: Map; + drpStates: Map; + aclStates: Map; originalDRP: DRP; + originalACL: IACL & DRP; subscriptions: DRPObjectCallback[]; constructor( peerId: string, drp: DRP, + acl: IACL & DRP, id?: string, abi?: string, config?: DRPObjectConfig, @@ -89,29 +102,43 @@ export class DRPObject implements IDRPObject { this.abi = abi ?? ""; this.bytecode = new Uint8Array(); this.vertices = []; - this.drp = drp ? new Proxy(drp, this.proxyDRPHandler()) : null; + this.drp = drp ? new Proxy(drp, this.proxyDRPHandler(DrpType.Drp)) : null; + this.acl = acl ? new Proxy(acl, this.proxyDRPHandler(DrpType.Acl)) : null; this.hashGraph = new HashGraph( peerId, drp?.resolveConflicts?.bind(drp ?? this), + acl?.resolveConflicts?.bind(acl ?? this), drp?.semanticsType, ); this.subscriptions = []; - this.states = new Map([[HashGraph.rootHash, { state: new Map() }]]); + this.drpStates = new Map([[HashGraph.rootHash, { state: new Map() }]]); + this.aclStates = new Map([[HashGraph.rootHash, { state: new Map() }]]); this.originalDRP = cloneDeep(drp); + this.originalACL = cloneDeep(acl); this.vertices = this.hashGraph.getAllVertices(); } + resolveConflicts(vertices: Vertex[]): ResolveConflictsType { + if ( + this.acl && + vertices.some((v) => v.operation?.drpType === DrpType.Acl) + ) { + const acl = this.acl as IACL & DRP; + 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(parentProp?: string): ProxyHandler { + proxyDRPHandler(vertexType: DrpType): ProxyHandler { const obj = this; return { get(target, propKey, receiver) { const value = Reflect.get(target, propKey, receiver); if (typeof value === "function") { - const fullPropKey = parentProp - ? `${parentProp}.${String(propKey)}` - : String(propKey); + const fullPropKey = String(propKey); return new Proxy(target[propKey as keyof object], { apply(applyTarget, thisArg, args) { if ((propKey as string).startsWith("query_")) { @@ -121,32 +148,36 @@ export class DRPObject implements IDRPObject { ?.split("\n")[2] ?.trim() .split(" ")[1]; + if (callerName?.startsWith("DRPObject.resolveConflicts")) { + return Reflect.apply(applyTarget, thisArg, args); + } if (!callerName?.startsWith("Proxy.")) - obj.callFn(fullPropKey, args); + obj.callFn(fullPropKey, args, vertexType); return Reflect.apply(applyTarget, thisArg, args); }, }); } - if (typeof value === "object" && value !== null && propKey === "acl") { - return new Proxy( - value, - obj.proxyDRPHandler( - parentProp ? `${parentProp}.${String(propKey)}` : String(propKey), - ), - ); - } - return value; }, }; } - // biome-ignore lint: value can't be unknown because of protobuf - callFn(fn: string, args: any) { - const preOperationDRP = this._computeDRP(this.hashGraph.getFrontier()); + callFn( + fn: string, + // biome-ignore lint: value can't be unknown because of protobuf + args: any, + drpType: DrpType, + ) { + // biome-ignore lint/suspicious/noExplicitAny: + let preOperationDRP: any; + if (drpType === DrpType.Acl) { + preOperationDRP = this._computeACL(this.hashGraph.getFrontier()); + } else { + preOperationDRP = this._computeDRP(this.hashGraph.getFrontier()); + } const drp = cloneDeep(preOperationDRP); - this._applyOperation(drp, { type: fn, value: args }); + this._applyOperation(drp, { opType: fn, value: args, drpType }); let stateChanged = false; for (const key of Object.keys(preOperationDRP)) { @@ -160,7 +191,11 @@ export class DRPObject implements IDRPObject { return; } - const vertex = this.hashGraph.addToFrontier({ type: fn, value: args }); + const vertex = this.hashGraph.addToFrontier({ + drpType: drpType, + opType: fn, + value: args, + }); this._setState(vertex, this._getDRPState(drp)); @@ -188,28 +223,46 @@ export class DRPObject implements IDRPObject { } try { - const drp = this._computeDRP(vertex.dependencies); - if (!this._checkWriterPermission(drp, vertex.peerId)) { + 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._setACLState(vertex, preComputeLca); + this._setDRPState(vertex, preComputeLca, this._getDRPState(drp)); + } else { + const acl = this._computeACL(vertex.dependencies, preComputeLca); + + this.hashGraph.addVertex( + vertex.operation, + vertex.dependencies, + vertex.peerId, + vertex.timestamp, + vertex.signature, + ); + this._applyOperation(acl, vertex.operation); - this.hashGraph.addVertex( - vertex.operation, - vertex.dependencies, - vertex.peerId, - vertex.timestamp, - vertex.signature, - ); - - this._applyOperation(drp, vertex.operation); - this._setState(vertex, this._getDRPState(drp)); + this._setACLState(vertex, preComputeLca, this._getDRPState(acl)); + this._setDRPState(vertex, preComputeLca); + } } catch (_) { missing.push(vertex.hash); } } this.vertices = this.hashGraph.getAllVertices(); - + this._updateACLState(); this._updateDRPState(); this._notify("merge", this.vertices); @@ -227,30 +280,27 @@ export class DRPObject implements IDRPObject { } // check if the given peer has write permission - private _checkWriterPermission(drp: DRP, peerId: string): boolean { - if (drp.acl) { - return drp.acl.query_isWriter(peerId); - } - return true; + private _checkWriterPermission(peerId: string): boolean { + return this.acl ? (this.acl as IACL).query_isWriter(peerId) : true; } // apply the operation to the DRP private _applyOperation(drp: DRP, operation: Operation) { - const { type, value } = operation; + const { opType, value } = operation; - const typeParts = type.split("."); + const typeParts = opType.split("."); // biome-ignore lint: target can be anything let target: any = drp; for (let i = 0; i < typeParts.length - 1; i++) { target = target[typeParts[i]]; if (!target) { - throw new Error(`Invalid operation type: ${type}`); + throw new Error(`Invalid operation type: ${opType}`); } } const methodName = typeParts[typeParts.length - 1]; if (typeof target[methodName] !== "function") { - throw new Error(`${type} is not a function`); + throw new Error(`${opType} is not a function`); } target[methodName](...value); @@ -259,24 +309,15 @@ export class DRPObject implements IDRPObject { // compute the DRP based on all dependencies of the current vertex using partial linearization private _computeDRP( vertexDependencies: Hash[], + preCompute?: LcaAndOperations, vertexOperation?: Operation, ): DRP { - const subgraph: ObjectSet = new ObjectSet(); - const lca = - vertexDependencies.length === 1 - ? vertexDependencies[0] - : this.hashGraph.lowestCommonAncestorMultipleVertices( - vertexDependencies, - subgraph, - ); - const linearizedOperations = - vertexDependencies.length === 1 - ? [] - : this.hashGraph.linearizeOperations(lca, subgraph); + const { lca, linearizedOperations } = + preCompute ?? this.computeLCA(vertexDependencies); const drp = cloneDeep(this.originalDRP); - const fetchedState = this.states.get(lca); + const fetchedState = this.drpStates.get(lca); if (!fetchedState) { throw new Error("State is undefined"); } @@ -288,15 +329,63 @@ export class DRPObject implements IDRPObject { } for (const op of linearizedOperations) { - this._applyOperation(drp, op); + op.drpType === DrpType.Drp && this._applyOperation(drp, op); } if (vertexOperation) { - this._applyOperation(drp, vertexOperation); + vertexOperation.drpType === DrpType.Drp && + this._applyOperation(drp, vertexOperation); } return drp; } + private _computeACL( + vertexDependencies: Hash[], + preCompute?: LcaAndOperations, + vertexOperation?: Operation, + ): DRP { + const { lca, linearizedOperations } = + preCompute ?? this.computeLCA(vertexDependencies); + + const acl = cloneDeep(this.originalACL); + + const fetchedState = this.aclStates.get(lca); + if (!fetchedState) { + throw new Error("State is undefined"); + } + + const state = cloneDeep(fetchedState); + + for (const [key, value] of state.state) { + acl[key] = value; + } + for (const op of linearizedOperations) { + op.drpType === DrpType.Acl && this._applyOperation(acl, op); + } + if (vertexOperation) { + vertexOperation.drpType === DrpType.Acl && + this._applyOperation(acl, vertexOperation); + } + + return acl; + } + + private computeLCA(vertexDependencies: string[]) { + const subgraph: ObjectSet = new ObjectSet(); + const lca = + vertexDependencies.length === 1 + ? vertexDependencies[0] + : this.hashGraph.lowestCommonAncestorMultipleVertices( + vertexDependencies, + subgraph, + ); + const linearizedOperations = + vertexDependencies.length === 1 + ? [] + : this.hashGraph.linearizeOperations(lca, subgraph); + return { lca, linearizedOperations }; + } + // get the map representing the state of the given DRP by mapping variable names to their corresponding values private _getDRPState(drp: DRP): DRPState { const varNames: string[] = Object.keys(drp); @@ -311,16 +400,68 @@ export class DRPObject implements IDRPObject { private _computeDRPState( vertexDependencies: Hash[], + preCompute?: LcaAndOperations, vertexOperation?: Operation, ): DRPState { - const drp = this._computeDRP(vertexDependencies, vertexOperation); + const drp = this._computeDRP( + vertexDependencies, + preCompute, + vertexOperation, + ); return this._getDRPState(drp); } + private _computeACLState( + vertexDependencies: Hash[], + preCompute?: LcaAndOperations, + vertexOperation?: Operation, + ): DRPState { + const acl = this._computeACL( + vertexDependencies, + preCompute, + vertexOperation, + ); + return this._getDRPState(acl); + } + + // store the state of the DRP corresponding to the given vertex private _setState(vertex: Vertex, drpState?: DRPState) { - this.states.set( + const preCompute = this.computeLCA(vertex.dependencies); + this._setACLState(vertex, preCompute, drpState); + this._setDRPState(vertex, preCompute, drpState); + } + + private _setACLState( + vertex: Vertex, + preCompute?: LcaAndOperations, + drpState?: DRPState, + ) { + if (this.acl) { + this.aclStates.set( + vertex.hash, + drpState ?? + this._computeACLState( + vertex.dependencies, + preCompute, + vertex.operation, + ), + ); + } + } + + private _setDRPState( + vertex: Vertex, + preCompute?: LcaAndOperations, + drpState?: DRPState, + ) { + this.drpStates.set( vertex.hash, - drpState ?? this._computeDRPState(vertex.dependencies, vertex.operation), + drpState ?? + this._computeDRPState( + vertex.dependencies, + preCompute, + vertex.operation, + ), ); } @@ -337,4 +478,17 @@ export class DRPObject implements IDRPObject { } } } + + private _updateACLState() { + if (!this.acl) { + return; + } + const currentACL = this.acl as IACL & DRP; + const newState = this._computeACLState(this.hashGraph.getFrontier()); + for (const [key, value] of newState.state.entries()) { + if (key in currentACL && typeof currentACL[key] !== "function") { + currentACL[key] = value; + } + } + } } diff --git a/packages/object/src/linearize/multipleSemantics.ts b/packages/object/src/linearize/multipleSemantics.ts index b0b58fb1..e6f2b561 100644 --- a/packages/object/src/linearize/multipleSemantics.ts +++ b/packages/object/src/linearize/multipleSemantics.ts @@ -63,6 +63,7 @@ export function linearizeMultipleSemantics( } k++; } + const resolved = hashGraph.resolveConflicts( concurrentOps.map((hash) => hashGraph.vertices.get(hash) as Vertex), ); diff --git a/packages/object/src/proto/drp/object/v1/object.proto b/packages/object/src/proto/drp/object/v1/object.proto index 930b51c0..95c8dc05 100644 --- a/packages/object/src/proto/drp/object/v1/object.proto +++ b/packages/object/src/proto/drp/object/v1/object.proto @@ -7,8 +7,9 @@ import "google/protobuf/struct.proto"; // Supposed to be the RIBLT stuff message Vertex { message Operation { - string type = 1; - google.protobuf.Value value = 2; + string drp_type = 1; + string op_type = 2; + google.protobuf.Value value = 3; } string hash = 1; string peer_id = 2; diff --git a/packages/object/src/proto/drp/object/v1/object_pb.ts b/packages/object/src/proto/drp/object/v1/object_pb.ts index 9dcf6e84..c1eb666d 100644 --- a/packages/object/src/proto/drp/object/v1/object_pb.ts +++ b/packages/object/src/proto/drp/object/v1/object_pb.ts @@ -21,7 +21,8 @@ export interface Vertex { } export interface Vertex_Operation { - type: string; + drpType: string; + opType: string; value: any | undefined; } @@ -177,16 +178,19 @@ export const Vertex: MessageFns = { }; function createBaseVertex_Operation(): Vertex_Operation { - return { type: "", value: undefined }; + return { drpType: "", opType: "", value: undefined }; } export const Vertex_Operation: MessageFns = { encode(message: Vertex_Operation, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { - if (message.type !== "") { - writer.uint32(10).string(message.type); + if (message.drpType !== "") { + writer.uint32(10).string(message.drpType); + } + if (message.opType !== "") { + writer.uint32(18).string(message.opType); } if (message.value !== undefined) { - Value.encode(Value.wrap(message.value), writer.uint32(18).fork()).join(); + Value.encode(Value.wrap(message.value), writer.uint32(26).fork()).join(); } return writer; }, @@ -203,7 +207,7 @@ export const Vertex_Operation: MessageFns = { break; } - message.type = reader.string(); + message.drpType = reader.string(); continue; } case 2: { @@ -211,6 +215,14 @@ export const Vertex_Operation: MessageFns = { break; } + message.opType = reader.string(); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + message.value = Value.unwrap(Value.decode(reader, reader.uint32())); continue; } @@ -225,15 +237,19 @@ export const Vertex_Operation: MessageFns = { fromJSON(object: any): Vertex_Operation { return { - type: isSet(object.type) ? globalThis.String(object.type) : "", + drpType: isSet(object.drpType) ? globalThis.String(object.drpType) : "", + opType: isSet(object.opType) ? globalThis.String(object.opType) : "", value: isSet(object?.value) ? object.value : undefined, }; }, toJSON(message: Vertex_Operation): unknown { const obj: any = {}; - if (message.type !== "") { - obj.type = message.type; + if (message.drpType !== "") { + obj.drpType = message.drpType; + } + if (message.opType !== "") { + obj.opType = message.opType; } if (message.value !== undefined) { obj.value = message.value; @@ -246,7 +262,8 @@ export const Vertex_Operation: MessageFns = { }, fromPartial, I>>(object: I): Vertex_Operation { const message = createBaseVertex_Operation(); - message.type = object.type ?? ""; + message.drpType = object.drpType ?? ""; + message.opType = object.opType ?? ""; message.value = object.value ?? undefined; return message; }, diff --git a/packages/object/tests/causallyrelated.bench.ts b/packages/object/tests/causallyrelated.bench.ts index 65b292aa..28c8b035 100644 --- a/packages/object/tests/causallyrelated.bench.ts +++ b/packages/object/tests/causallyrelated.bench.ts @@ -1,3 +1,4 @@ +import type { ACL } from "@topology-foundation/blueprints/src/ACL/index.js"; import { bench, describe } from "vitest"; import { AddWinsSet } from "../../blueprints/src/AddWinsSet/index.js"; import { DRPObject, type Hash } from "../src/index.js"; @@ -6,9 +7,21 @@ describe("AreCausallyDependent benchmark", async () => { const samples = 100000; const tests: Hash[][] = []; - const obj1 = new DRPObject("peer1", new AddWinsSet()); - const obj2 = new DRPObject("peer2", new AddWinsSet()); - const obj3 = new DRPObject("peer3", new AddWinsSet()); + const obj1 = new DRPObject( + "peer1", + new AddWinsSet(), + null as unknown as ACL, + ); + const obj2 = new DRPObject( + "peer2", + new AddWinsSet(), + null as unknown as ACL, + ); + const obj3 = new DRPObject( + "peer3", + new AddWinsSet(), + null as unknown as ACL, + ); const drp1 = obj1.drp as AddWinsSet; const drp2 = obj2.drp as AddWinsSet; diff --git a/packages/object/tests/hashgraph.test.ts b/packages/object/tests/hashgraph.test.ts index 1a368ebc..8f380a60 100644 --- a/packages/object/tests/hashgraph.test.ts +++ b/packages/object/tests/hashgraph.test.ts @@ -1,15 +1,22 @@ -import { AddWinsSetWithACL } from "@topology-foundation/blueprints/src/AddWinsSetWithACL/index.js"; +import { ACL } from "@topology-foundation/blueprints/src/ACL/index.js"; import { beforeEach, describe, expect, test } from "vitest"; import { AddWinsSet } from "../../blueprints/src/AddWinsSet/index.js"; -import { DRPObject, type Operation, OperationType } from "../src/index.js"; +import { + DRPObject, + DrpType, + type Operation, + OperationType, +} from "../src/index.js"; describe("HashGraph construction tests", () => { let obj1: DRPObject; let obj2: DRPObject; beforeEach(async () => { - obj1 = new DRPObject("peer1", new AddWinsSet()); - obj2 = new DRPObject("peer2", new AddWinsSet()); + // biome-ignore lint/suspicious/noExplicitAny: + obj1 = new DRPObject("peer1", new AddWinsSet(), null as any); + // biome-ignore lint/suspicious/noExplicitAny: + obj2 = new DRPObject("peer2", new AddWinsSet(), null as any); }); test("Test: Vertices are consistent across data structures", () => { @@ -49,9 +56,9 @@ describe("HashGraph construction tests", () => { const linearOps = obj2.hashGraph.linearizeOperations(); expect(linearOps).toEqual([ - { type: "add", value: [2] }, - { type: "add", value: [1] }, - ]); + { opType: "add", value: [2], drpType: DrpType.Drp }, + { opType: "add", value: [1], drpType: DrpType.Drp }, + ] as Operation[]); }); test("Test: HashGraph with 2 root vertices", () => { @@ -64,8 +71,9 @@ describe("HashGraph construction tests", () => { // add fake root const hash = obj1.hashGraph.addVertex( { - type: "root", + opType: "root", value: null, + drpType: DrpType.Drp, }, [], "", @@ -74,8 +82,9 @@ describe("HashGraph construction tests", () => { ); obj1.hashGraph.addVertex( { - type: "add", + opType: "add", value: [1], + drpType: DrpType.Drp, }, [hash], "", @@ -85,7 +94,10 @@ describe("HashGraph construction tests", () => { expect(obj1.hashGraph.selfCheckConstraints()).toBe(false); const linearOps = obj1.hashGraph.linearizeOperations(); - expect(linearOps).toEqual([{ type: "add", value: [1] }]); + const expectedOps: Operation[] = [ + { opType: "add", value: [1], drpType: DrpType.Drp }, + ]; + expect(linearOps).toEqual(expectedOps); }); }); @@ -95,9 +107,21 @@ describe("HashGraph for AddWinSet tests", () => { let obj3: DRPObject; beforeEach(async () => { - obj1 = new DRPObject("peer1", new AddWinsSet()); - obj2 = new DRPObject("peer2", new AddWinsSet()); - obj3 = new DRPObject("peer3", new AddWinsSet()); + obj1 = new DRPObject( + "peer1", + new AddWinsSet(), + null as unknown as ACL, + ); + obj2 = new DRPObject( + "peer2", + new AddWinsSet(), + null as unknown as ACL, + ); + obj3 = new DRPObject( + "peer3", + new AddWinsSet(), + null as unknown as ACL, + ); }); test("Test: Add Two Vertices", () => { @@ -111,10 +135,15 @@ describe("HashGraph for AddWinSet tests", () => { expect(drp1.query_contains(1)).toBe(false); const linearOps = obj1.hashGraph.linearizeOperations(); - expect(linearOps).toEqual([ - { type: "add", value: [1] }, - { type: "remove", value: [1] }, - ]); + const expectedOps: Operation[] = [ + { + opType: "add", + value: [1], + drpType: DrpType.Drp, + }, + { opType: "remove", value: [1], drpType: DrpType.Drp }, + ]; + expect(linearOps).toEqual(expectedOps); }); test("Test: Add Two Concurrent Vertices With Same Value", () => { @@ -140,10 +169,11 @@ describe("HashGraph for AddWinSet tests", () => { expect(obj1.hashGraph.vertices).toEqual(obj2.hashGraph.vertices); const linearOps = obj1.hashGraph.linearizeOperations(); - expect(linearOps).toEqual([ - { type: "add", value: [1] }, - { type: "remove", value: [1] }, - ]); + const expectedOps: Operation[] = [ + { opType: "add", value: [1], drpType: DrpType.Drp }, + { opType: "remove", value: [1], drpType: DrpType.Drp }, + ]; + expect(linearOps).toEqual(expectedOps); }); test("Test: Add Two Concurrent Vertices With Different Values", () => { @@ -169,11 +199,12 @@ describe("HashGraph for AddWinSet tests", () => { expect(obj1.hashGraph.vertices).toEqual(obj2.hashGraph.vertices); const linearOps = obj1.hashGraph.linearizeOperations(); - expect(linearOps).toEqual([ - { type: "add", value: [1] }, - { type: "remove", value: [1] }, - { type: "add", value: [2] }, - ]); + const expectedOps: Operation[] = [ + { opType: "add", value: [1], drpType: DrpType.Drp }, + { opType: "remove", value: [1], drpType: DrpType.Drp }, + { opType: "add", value: [2], drpType: DrpType.Drp }, + ]; + expect(linearOps).toEqual(expectedOps); }); test("Test: Tricky Case", () => { @@ -203,11 +234,12 @@ describe("HashGraph for AddWinSet tests", () => { expect(obj1.hashGraph.vertices).toEqual(obj2.hashGraph.vertices); const linearOps = obj1.hashGraph.linearizeOperations(); - expect(linearOps).toEqual([ - { type: "add", value: [1] }, - { type: "remove", value: [1] }, - { type: "add", value: [10] }, - ]); + const expectedOps: Operation[] = [ + { opType: "add", value: [1], drpType: DrpType.Drp }, + { opType: "remove", value: [1], drpType: DrpType.Drp }, + { opType: "add", value: [10], drpType: DrpType.Drp }, + ]; + expect(linearOps).toEqual(expectedOps); }); test("Test: Yuta Papa's Case", () => { @@ -235,11 +267,12 @@ describe("HashGraph for AddWinSet tests", () => { expect(obj1.hashGraph.vertices).toEqual(obj2.hashGraph.vertices); const linearOps = obj1.hashGraph.linearizeOperations(); - expect(linearOps).toEqual([ - { type: "add", value: [1] }, - { type: "remove", value: [1] }, - { type: "add", value: [2] }, - ]); + const expectedOps: Operation[] = [ + { opType: "add", value: [1], drpType: DrpType.Drp }, + { opType: "remove", value: [1], drpType: DrpType.Drp }, + { opType: "add", value: [2], drpType: DrpType.Drp }, + ]; + expect(linearOps).toEqual(expectedOps); }); test("Test: Mega Complex Case", () => { @@ -288,13 +321,14 @@ describe("HashGraph for AddWinSet tests", () => { expect(obj1.hashGraph.vertices).toEqual(obj3.hashGraph.vertices); const linearOps = obj1.hashGraph.linearizeOperations(); - expect(linearOps).toEqual([ - { type: "add", value: [1] }, - { type: "remove", value: [1] }, - { type: "add", value: [2] }, - { type: "add", value: [3] }, - { type: "remove", value: [1] }, - ]); + const expectedOps: Operation[] = [ + { opType: "add", value: [1], drpType: DrpType.Drp }, + { opType: "remove", value: [1], drpType: DrpType.Drp }, + { opType: "add", value: [2], drpType: DrpType.Drp }, + { opType: "add", value: [3], drpType: DrpType.Drp }, + { opType: "remove", value: [1], drpType: DrpType.Drp }, + ]; + expect(linearOps).toEqual(expectedOps); }); test("Test: Mega Complex Case 1", () => { @@ -344,10 +378,10 @@ describe("HashGraph for AddWinSet tests", () => { const linearOps = obj1.hashGraph.linearizeOperations(); expect(linearOps).toEqual([ - { type: "add", value: [1] }, - { type: "remove", value: [1] }, - { type: "add", value: [3] }, - { type: "add", value: [2] }, + { opType: "add", value: [1], drpType: DrpType.Drp }, + { opType: "remove", value: [1], drpType: DrpType.Drp }, + { opType: "add", value: [3], drpType: DrpType.Drp }, + { opType: "add", value: [2], drpType: DrpType.Drp }, ]); }); @@ -378,11 +412,12 @@ describe("HashGraph for AddWinSet tests", () => { expect(obj1.hashGraph.vertices).toEqual(obj2.hashGraph.vertices); const linearOps = obj1.hashGraph.linearizeOperations(); - expect(linearOps).toEqual([ - { type: "add", value: [1] }, - { type: "add", value: [2] }, - { type: "remove", value: [2] }, - ]); + const expectedOps: Operation[] = [ + { opType: "add", value: [1], drpType: DrpType.Drp }, + { opType: "add", value: [2], drpType: DrpType.Drp }, + { opType: "remove", value: [2], drpType: DrpType.Drp }, + ]; + expect(linearOps).toEqual(expectedOps); }); }); @@ -391,8 +426,16 @@ describe("HashGraph for undefined operations tests", () => { let obj2: DRPObject; beforeEach(async () => { - obj1 = new DRPObject("peer1", new AddWinsSet()); - obj2 = new DRPObject("peer2", new AddWinsSet()); + obj1 = new DRPObject( + "peer1", + new AddWinsSet(), + null as unknown as ACL, + ); + obj2 = new DRPObject( + "peer2", + new AddWinsSet(), + null as unknown as ACL, + ); }); test("Test: merge should skip undefined operations", () => { @@ -409,7 +452,9 @@ describe("HashGraph for undefined operations tests", () => { const linearOps = obj2.hashGraph.linearizeOperations(); // Should only have one, since we skipped the undefined operations - expect(linearOps).toEqual([{ type: "add", value: [2] }]); + expect(linearOps).toEqual([ + { opType: "add", value: [2], drpType: DrpType.Drp }, + ]); }); test("Test: addToFrontier with undefined operation return Vertex with NoOp operation", () => { @@ -419,7 +464,7 @@ describe("HashGraph for undefined operations tests", () => { ); expect(createdVertex.operation).toEqual({ - type: OperationType.NOP, + opType: OperationType.NOP, } as Operation); }); }); @@ -430,9 +475,21 @@ describe("Vertex state tests", () => { let obj3: DRPObject; beforeEach(async () => { - obj1 = new DRPObject("peer1", new AddWinsSet()); - obj2 = new DRPObject("peer2", new AddWinsSet()); - obj3 = new DRPObject("peer3", new AddWinsSet()); + obj1 = new DRPObject( + "peer1", + new AddWinsSet(), + null as unknown as ACL, + ); + obj2 = new DRPObject( + "peer2", + new AddWinsSet(), + null as unknown as ACL, + ); + obj3 = new DRPObject( + "peer3", + new AddWinsSet(), + null as unknown as ACL, + ); }); test("Test: Vertex states work correctly with single HashGraph", () => { @@ -447,17 +504,17 @@ describe("Vertex state tests", () => { const vertices = obj1.hashGraph.topologicalSort(); - const drpState1 = obj1.states.get(vertices[1]); + const drpState1 = obj1.drpStates.get(vertices[1]); expect(drpState1?.state.get("state").get(1)).toBe(true); expect(drpState1?.state.get("state").get(2)).toBe(undefined); expect(drpState1?.state.get("state").get(3)).toBe(undefined); - const drpState2 = obj1.states.get(vertices[2]); + const drpState2 = obj1.drpStates.get(vertices[2]); expect(drpState2?.state.get("state").get(1)).toBe(true); expect(drpState2?.state.get("state").get(2)).toBe(true); expect(drpState2?.state.get("state").get(3)).toBe(undefined); - const drpState3 = obj1.states.get(vertices[3]); + const drpState3 = obj1.drpStates.get(vertices[3]); expect(drpState3?.state.get("state").get(1)).toBe(true); expect(drpState3?.state.get("state").get(2)).toBe(true); expect(drpState3?.state.get("state").get(3)).toBe(true); @@ -495,21 +552,21 @@ describe("Vertex state tests", () => { drp1.add(6); const hashA6 = obj1.hashGraph.getFrontier()[0]; - const drpState1 = obj1.states.get(hashA4); + const drpState1 = obj1.drpStates.get(hashA4); expect(drpState1?.state.get("state").get(1)).toBe(true); expect(drpState1?.state.get("state").get(2)).toBe(true); expect(drpState1?.state.get("state").get(3)).toBe(undefined); expect(drpState1?.state.get("state").get(4)).toBe(true); expect(drpState1?.state.get("state").get(5)).toBe(undefined); - const drpState2 = obj1.states.get(hashC5); + const drpState2 = obj1.drpStates.get(hashC5); expect(drpState2?.state.get("state").get(1)).toBe(undefined); expect(drpState2?.state.get("state").get(2)).toBe(true); expect(drpState2?.state.get("state").get(3)).toBe(true); expect(drpState2?.state.get("state").get(4)).toBe(undefined); expect(drpState2?.state.get("state").get(5)).toBe(true); - const drpState3 = obj1.states.get(hashA6); + const drpState3 = obj1.drpStates.get(hashA6); expect(drpState3?.state.get("state").get(1)).toBe(true); expect(drpState3?.state.get("state").get(2)).toBe(true); expect(drpState3?.state.get("state").get(3)).toBe(true); @@ -558,7 +615,7 @@ describe("Vertex state tests", () => { obj3.merge(obj2.hashGraph.getAllVertices()); const hashV8 = obj1.hashGraph.getFrontier()[0]; - const drpStateV8 = obj1.states.get(hashV8); + const drpStateV8 = obj1.drpStates.get(hashV8); expect(drpStateV8?.state.get("state").get(1)).toBe(false); expect(drpStateV8?.state.get("state").get(2)).toBe(undefined); expect(drpStateV8?.state.get("state").get(3)).toBe(undefined); @@ -571,9 +628,21 @@ describe("Vertex timestamp tests", () => { let obj3: DRPObject; beforeEach(async () => { - obj1 = new DRPObject("peer1", new AddWinsSet()); - obj2 = new DRPObject("peer1", new AddWinsSet()); - obj3 = new DRPObject("peer1", new AddWinsSet()); + obj1 = new DRPObject( + "peer1", + new AddWinsSet(), + null as unknown as ACL, + ); + obj2 = new DRPObject( + "peer1", + new AddWinsSet(), + null as unknown as ACL, + ); + obj3 = new DRPObject( + "peer1", + new AddWinsSet(), + null as unknown as ACL, + ); }); test("Test: Vertex created in the future is invalid", () => { @@ -584,8 +653,9 @@ describe("Vertex timestamp tests", () => { expect(() => obj1.hashGraph.addVertex( { - type: "add", + opType: "add", value: 1, + drpType: DrpType.Drp, }, obj1.hashGraph.getFrontier(), "", @@ -598,7 +668,7 @@ describe("Vertex timestamp tests", () => { test("Test: Vertex's timestamp must not be less than any of its dependencies' timestamps", () => { /* __ V1:ADD(1) __ - / \ + / \ ROOT -- V2:ADD(2) ---- V4:ADD(4) (invalid) \ / -- V3:ADD(3) -- @@ -618,8 +688,9 @@ describe("Vertex timestamp tests", () => { expect(() => obj1.hashGraph.addVertex( { - type: "add", + opType: "add", value: 1, + drpType: DrpType.Drp, }, obj1.hashGraph.getFrontier(), "", @@ -630,72 +701,6 @@ describe("Vertex timestamp tests", () => { }); }); -describe("Operation with ACL tests", () => { - let obj1: DRPObject; - let obj2: DRPObject; - - beforeEach(async () => { - const peerIdToPublicKey = new Map([ - ["peer1", "publicKey1"], - ]); - obj1 = new DRPObject( - "peer1", - new AddWinsSetWithACL(peerIdToPublicKey), - ); - obj2 = new DRPObject( - "peer2", - new AddWinsSetWithACL(peerIdToPublicKey), - ); - }); - - test("Node with admin permission can grant permission to other nodes", () => { - /* - ROOT -- V1:GRANT("peer2") - */ - - const drp1 = obj1.drp as AddWinsSetWithACL; - const drp2 = obj2.drp as AddWinsSetWithACL; - - drp1.acl.grant("peer1", "peer2", "publicKey2"); - obj2.merge(obj1.hashGraph.getAllVertices()); - expect(drp2.acl.query_isWriter("peer2")).toBe(true); - }); - - test("Node with writer permission can create vertices", () => { - /* - ROOT -- V1:GRANT("peer2") -- V2:ADD(1) - */ - const drp1 = obj1.drp as AddWinsSetWithACL; - const drp2 = obj2.drp as AddWinsSetWithACL; - - drp1.acl.grant("peer1", "peer2", "publicKey2"); - obj2.merge(obj1.hashGraph.getAllVertices()); - - drp2.add(1); - obj1.merge(obj2.hashGraph.getAllVertices()); - expect(drp1.query_contains(1)).toBe(true); - }); - - test("Revoke permission from writer", () => { - /* - ROOT -- V1:GRANT("peer2") -- V2:ADD(1) -- V3:REVOKE("peer2") - */ - const drp1 = obj1.drp as AddWinsSetWithACL; - const drp2 = obj2.drp as AddWinsSetWithACL; - - drp1.acl.grant("peer1", "peer2", "publicKey2"); - obj2.merge(obj1.hashGraph.getAllVertices()); - - expect(drp2.acl.query_isWriter("peer2")).toBe(true); - drp2.add(1); - - obj1.merge(obj2.hashGraph.getAllVertices()); - drp1.acl.revoke("peer1", "peer2"); - obj2.merge(obj1.hashGraph.getAllVertices()); - expect(drp2.acl.query_isWriter("peer2")).toBe(false); - }); -}); - describe("Writer permission tests", () => { let obj1: DRPObject; let obj2: DRPObject; @@ -703,13 +708,14 @@ describe("Writer permission tests", () => { beforeEach(async () => { const peerIdToPublicKeyMap = new Map([["peer1", "publicKey1"]]); - obj1 = new DRPObject("peer1", new AddWinsSetWithACL(peerIdToPublicKeyMap)); - obj2 = new DRPObject("peer2", new AddWinsSetWithACL(peerIdToPublicKeyMap)); - obj3 = new DRPObject("peer3", new AddWinsSetWithACL(peerIdToPublicKeyMap)); + const acl = new ACL(peerIdToPublicKeyMap); + obj1 = new DRPObject("peer1", new AddWinsSet(), acl); + obj2 = new DRPObject("peer2", new AddWinsSet(), acl); + obj3 = new DRPObject("peer3", new AddWinsSet(), acl); }); test("Node without writer permission can generate vertex locally", () => { - const drp = obj1.drp as AddWinsSetWithACL; + const drp = obj1.drp as AddWinsSet; drp.add(1); drp.add(2); @@ -718,8 +724,8 @@ describe("Writer permission tests", () => { }); test("Discard vertex if creator does not have write permission", () => { - const drp1 = obj1.drp as AddWinsSetWithACL; - const drp2 = obj2.drp as AddWinsSetWithACL; + const drp1 = obj1.drp as AddWinsSet; + const drp2 = obj2.drp as AddWinsSet; drp1.add(1); drp2.add(2); @@ -732,16 +738,18 @@ describe("Writer permission tests", () => { /* ROOT -- V1:ADD(1) -- V2:GRANT(peer2) -- V3:ADD(4) */ - const drp1 = obj1.drp as AddWinsSetWithACL; - const drp2 = obj2.drp as AddWinsSetWithACL; + const drp1 = obj1.drp as AddWinsSet; + const drp2 = obj2.drp as AddWinsSet; + const acl1 = obj1.acl as ACL; + const acl2 = obj2.acl as ACL; drp1.add(1); - drp1.acl.grant("peer1", "peer2", "publicKey2"); - expect(drp1.acl.query_isAdmin("peer1")).toBe(true); + acl1.grant("peer1", "peer2", "publicKey2"); + expect(acl1.query_isAdmin("peer1")).toBe(true); obj2.merge(obj1.hashGraph.getAllVertices()); expect(drp2.query_contains(1)).toBe(true); - expect(drp2.acl.query_isWriter("peer2")).toBe(true); + expect(acl2.query_isWriter("peer2")).toBe(true); drp2.add(4); obj1.merge(obj2.hashGraph.getAllVertices()); @@ -756,12 +764,13 @@ describe("Writer permission tests", () => { \ / -- V5:ADD(2) -- */ - const drp1 = obj1.drp as AddWinsSetWithACL; - const drp2 = obj2.drp as AddWinsSetWithACL; - const drp3 = obj3.drp as AddWinsSetWithACL; + const drp1 = obj1.drp as AddWinsSet; + const drp2 = obj2.drp as AddWinsSet; + const drp3 = obj3.drp as AddWinsSet; + const acl1 = obj1.acl as ACL; - drp1.acl.grant("peer1", "peer2", "publicKey2"); - drp1.acl.grant("peer1", "peer3", "publicKey3"); + acl1.grant("peer1", "peer2", "publicKey2"); + acl1.grant("peer1", "peer3", "publicKey3"); obj2.merge(obj1.hashGraph.getAllVertices()); obj3.merge(obj1.hashGraph.getAllVertices()); @@ -774,7 +783,7 @@ describe("Writer permission tests", () => { expect(drp1.query_contains(1)).toBe(true); expect(drp1.query_contains(2)).toBe(true); - drp1.acl.revoke("peer1", "peer3"); + acl1.revoke("peer1", "peer3"); obj3.merge(obj1.hashGraph.getAllVertices()); drp3.add(3); obj2.merge(obj3.hashGraph.getAllVertices());