Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: vertex not found #445

Merged
merged 12 commits into from
Feb 12, 2025
29 changes: 1 addition & 28 deletions packages/object/src/hashgraph/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,35 +132,8 @@ export class HashGraph {
this.arePredecessorsFresh = false;
}

/* Add a vertex to the hashgraph with the given operation and dependencies.
* If the vertex already exists, return the hash of the existing vertex.
* Throws an error if any of the dependencies are not present in the hashgraph.
*/
// Add a new vertex to the hashgraph.
addVertex(vertex: Vertex) {
if (this.vertices.has(vertex.hash)) {
return; // Vertex already exists
}

if (vertex.dependencies.length === 0) {
throw new Error("Vertex dependencies are empty.");
}
for (const dep of vertex.dependencies) {
const depVertex = this.vertices.get(dep);
if (depVertex === undefined) {
throw new Error("Invalid dependency detected.");
}
if (depVertex.timestamp > vertex.timestamp) {
// Vertex's timestamp must not be less than any of its dependencies' timestamps
throw new Error("Invalid timestamp detected.");
}
}

const currentTimestamp = Date.now();
if (vertex.timestamp > currentTimestamp) {
// Vertex created in the future is invalid
throw new Error("Invalid timestamp detected.");
}

this.vertices.set(vertex.hash, vertex);
this.frontier.push(vertex.hash);
// Update forward edges
Expand Down
74 changes: 48 additions & 26 deletions packages/object/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,40 @@ export class DRPObject implements ObjectPb.DRPObjectBase {
return this._mergeWithDrp(vertices);
}

validateVertex(vertex: Vertex) {
// Validate hash value
if (
vertex.hash !==
computeHash(vertex.peerId, vertex.operation, vertex.dependencies, vertex.timestamp)
) {
throw new Error(`Invalid hash for vertex ${vertex.hash}`);
}

// Validate vertex dependencies
if (vertex.dependencies.length === 0) {
throw new Error(`Vertex ${vertex.hash} has no dependencies.`);
}
for (const dep of vertex.dependencies) {
const depVertex = this.hashGraph.vertices.get(dep);
if (depVertex === undefined) {
throw new Error(`Vertex ${vertex.hash} has invalid dependency ${dep}.`);
}
if (depVertex.timestamp > vertex.timestamp) {
// Vertex's timestamp must not be less than any of its dependencies' timestamps
throw new Error(`Vertex ${vertex.hash} has invalid timestamp.`);
}
}
if (vertex.timestamp > Date.now()) {
// Vertex created in the future is invalid
throw new Error(`Vertex ${vertex.hash} has invalid timestamp.`);
}

// Validate writer permission
if (!this._checkWriterPermission(vertex.peerId, vertex.dependencies)) {
throw new Error(`Vertex ${vertex.peerId} does not have write permission.`);
}
}

/* Merges the vertices into the hashgraph using DRP
*/
private _mergeWithDrp(vertices: Vertex[]): [merged: boolean, missing: string[]] {
Expand All @@ -254,34 +288,19 @@ export class DRPObject implements ObjectPb.DRPObjectBase {
}

try {
const acl = this._computeObjectACL(vertex.dependencies);
if (!acl.query_isWriter(vertex.peerId)) {
throw new Error(`${vertex.peerId} does not have write permission.`);
}
if (
vertex.hash !==
computeHash(vertex.peerId, vertex.operation, vertex.dependencies, vertex.timestamp)
) {
throw new Error(`Invalid hash for vertex ${vertex.hash}`);
}
this.validateVertex(vertex);
const preComputeLca = this.computeLCA(vertex.dependencies);

if (vertex.operation.drpType === DrpType.DRP) {
const drp = this._computeDRP(vertex.dependencies, preComputeLca);
this.hashGraph.addVertex(vertex);
this._applyOperation(drp, vertex.operation);

const drp = this._computeDRP(vertex.dependencies, preComputeLca, vertex.operation);
this._setObjectACLState(vertex, preComputeLca);
this._setDRPState(vertex, preComputeLca, this._getDRPState(drp));
} else {
const acl = this._computeObjectACL(vertex.dependencies, preComputeLca);

this.hashGraph.addVertex(vertex);
this._applyOperation(acl, vertex.operation);

const acl = this._computeObjectACL(vertex.dependencies, preComputeLca, vertex.operation);
this._setObjectACLState(vertex, preComputeLca, this._getDRPState(acl));
this._setDRPState(vertex, preComputeLca);
}
this.hashGraph.addVertex(vertex);
this._initializeFinalityState(vertex.hash);
newVertices.push(vertex);
} catch (_) {
Expand All @@ -306,9 +325,7 @@ export class DRPObject implements ObjectPb.DRPObjectBase {
}

try {
if (!this._checkWriterPermission(vertex.peerId)) {
return [false, [vertex.hash]];
}
this.validateVertex(vertex);
this.hashGraph.addVertex({
hash: vertex.hash,
operation: vertex.operation,
Expand Down Expand Up @@ -354,8 +371,13 @@ export class DRPObject implements ObjectPb.DRPObjectBase {
}

// check if the given peer has write permission
private _checkWriterPermission(peerId: string): boolean {
return this.acl ? (this.acl as ACL).query_isWriter(peerId) : true;
private _checkWriterPermission(peerId: string, deps: Hash[]): boolean {
if (!this.drp) {
return (this.acl as ACL).query_isWriter(peerId);
}

const acl = this._computeObjectACL(deps);
return (acl as ACL).query_isWriter(peerId);
}

// apply the operation to the DRP
Expand Down Expand Up @@ -572,9 +594,9 @@ export class DRPObject implements ObjectPb.DRPObjectBase {
}
}

export function computeHash(
function computeHash(
peerId: string,
operation: Operation,
operation: Operation | undefined,
deps: Hash[],
timestamp: number
): Hash {
Expand Down
35 changes: 34 additions & 1 deletion packages/object/tests/drpobject.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SetDRP } from "@ts-drp/blueprints/src/index.js";
import { beforeEach, describe, expect, it, test } from "vitest";
import { beforeEach, describe, expect, it, test, vi } from "vitest";

import { DRPObject, ObjectACL } from "../src/index.js";

Expand Down Expand Up @@ -69,3 +69,36 @@ describe("Drp Object should be able to change state value", () => {
}
});
});

describe("Merging vertices tests", () => {
let obj1: DRPObject;
let obj2: DRPObject;

beforeEach(() => {
obj1 = new DRPObject({ peerId: "peer1", acl, drp: new SetDRP<number>() });
obj2 = new DRPObject({ peerId: "peer2", acl, drp: new SetDRP<number>() });
});

test("Test: merge should skip unknown dependencies", () => {
vi.useFakeTimers();
vi.setSystemTime(new Date(Date.UTC(1998, 11, 19)));
const drp1 = obj1.drp as SetDRP<number>;
const drp2 = obj2.drp as SetDRP<number>;

drp1.add(1);
drp2.add(2);
obj1.merge(obj2.hashGraph.getAllVertices());
drp1.add(3);

const vertex = obj1.vertices.find(
(v) => v.operation?.opType === "add" && v.operation.value[0] === 3
);
if (!vertex) {
throw new Error("Vertex not found");
}
expect(obj2.merge([vertex])).toEqual([
false,
["e5ef52c6186abe51635619df8bc8676c19f5a6519e40f47072683437255f026a"],
]);
});
});
73 changes: 35 additions & 38 deletions packages/object/tests/hashgraph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,19 +125,18 @@ describe("HashGraph construction tests", () => {
new Uint8Array()
);
expect(() => {
obj1.hashGraph.addVertex(fakeRoot);
}).toThrowError("Vertex dependencies are empty.");
obj1.validateVertex(fakeRoot);
}).toThrowError(`Vertex ${fakeRoot.hash} has no dependencies.`);
const vertex = newVertex(
"peer1",
{ opType: "add", value: [1], drpType: DrpType.DRP },
[fakeRoot.hash],
Date.now(),
new Uint8Array()
);
expect(() => {
obj1.hashGraph.addVertex(
newVertex(
"peer1",
{ opType: "add", value: [1], drpType: DrpType.DRP },
[fakeRoot.hash],
Date.now(),
new Uint8Array()
)
);
}).toThrowError("Invalid dependency detected.");
obj1.validateVertex(vertex);
}).toThrowError(`Vertex ${vertex.hash} has invalid dependency ${fakeRoot.hash}.`);
expect(selfCheckConstraints(obj1.hashGraph)).toBe(true);

const linearOps = obj1.hashGraph.linearizeOperations();
Expand Down Expand Up @@ -564,17 +563,16 @@ describe("Vertex timestamp tests", () => {

drp1.add(1);

expect(() =>
obj1.hashGraph.addVertex(
newVertex(
"peer1",
{ opType: "add", value: [1], drpType: DrpType.DRP },
obj1.hashGraph.getFrontier(),
Number.POSITIVE_INFINITY,
new Uint8Array()
)
)
).toThrowError("Invalid timestamp detected.");
const vertex = newVertex(
"peer1",
{ opType: "add", value: [1], drpType: DrpType.DRP },
obj1.hashGraph.getFrontier(),
Number.POSITIVE_INFINITY,
new Uint8Array()
);
expect(() => obj1.validateVertex(vertex)).toThrowError(
`Vertex ${vertex.hash} has invalid timestamp.`
);
});

test("Test: Vertex's timestamp must not be less than any of its dependencies' timestamps", () => {
Expand All @@ -597,21 +595,20 @@ describe("Vertex timestamp tests", () => {
obj1.merge(obj2.hashGraph.getAllVertices());
obj1.merge(obj3.hashGraph.getAllVertices());

expect(() =>
obj1.hashGraph.addVertex(
newVertex(
"peer1",
{
opType: "add",
value: [1],
drpType: DrpType.DRP,
},
obj1.hashGraph.getFrontier(),
1,
new Uint8Array()
)
)
).toThrowError("Invalid timestamp detected.");
const vertex = newVertex(
"peer1",
{
opType: "add",
value: [1],
drpType: DrpType.DRP,
},
obj1.hashGraph.getFrontier(),
1,
new Uint8Array()
);
expect(() => obj1.validateVertex(vertex)).toThrowError(
`Vertex ${vertex.hash} has invalid timestamp.`
);
});
});

Expand Down