diff --git a/buf.gen.yaml b/buf.gen.yaml index 16dcc12d..1926b9b6 100644 --- a/buf.gen.yaml +++ b/buf.gen.yaml @@ -2,11 +2,12 @@ version: v2 plugins: - local: ./node_modules/ts-proto/protoc-gen-ts_proto strategy: directory - # out: ./packages/network/src - out: ./packages/object/src + out: ./packages opt: - esModuleInterop=true - fileSuffix=_pb inputs: - #- directory: ./packages/network/src - - directory: ./packages/object/src + - directory: ./packages + exclude_paths: + - packages/network/node_modules + - packages/object/node_modules diff --git a/examples/canvas/src/index.ts b/examples/canvas/src/index.ts index dda05405..28b6a2a6 100644 --- a/examples/canvas/src/index.ts +++ b/examples/canvas/src/index.ts @@ -6,8 +6,8 @@ import { Canvas } from "./objects/canvas"; import { Pixel } from "./objects/pixel"; const node = new TopologyNode(); +let topologyObject: TopologyObject; let canvasCRO: Canvas; -let topologyObject: TopologyObject; let peers: string[] = []; let discoveryPeers: string[] = []; let objectPeers: string[] = []; @@ -62,17 +62,6 @@ async function paint_pixel(pixel: HTMLDivElement) { canvasCRO.paint(node.networkNode.peerId, [x, y], painting); const [r, g, b] = canvasCRO.pixel(x, y).color(); pixel.style.backgroundColor = `rgb(${r}, ${g}, ${b})`; - - node.updateObject(topologyObject.id, [ - { - fn: "paint", - args: [ - node.networkNode.peerId, - `${x},${y}`, - `${painting[0]},${painting[1]},${painting[2]}`, - ], - }, - ]); } async function init() { @@ -86,8 +75,8 @@ async function init() { const create_button = document.getElementById("create"); create_button.addEventListener("click", async () => { - canvasCRO = new Canvas(5, 10); - topologyObject = await node.createObject(); + topologyObject = await node.createObject(new Canvas(5, 10)); + canvasCRO = topologyObject.cro as Canvas; // message handler for the CRO node.addCustomGroupMessageHandler(topologyObject.id, (e) => { @@ -98,7 +87,7 @@ async function init() { }); node.objectStore.subscribe(topologyObject.id, (_, obj) => { - handleObjectOps(canvasCRO, obj.operations); + handleObjectOps(canvasCRO, obj.vertices); }); (document.getElementById("canvasId")).innerText = @@ -111,8 +100,8 @@ async function init() { const croId = (document.getElementById("canvasIdInput")) .value; try { - canvasCRO = new Canvas(5, 10); - topologyObject = await node.createObject(); + topologyObject = await node.createObject(new Canvas(5, 10), croId); + canvasCRO = topologyObject.cro as Canvas; // message handler for the CRO node.addCustomGroupMessageHandler(topologyObject.id, (e) => { @@ -125,7 +114,7 @@ async function init() { }); node.objectStore.subscribe(topologyObject.id, (_, obj) => { - handleObjectOps(canvasCRO, obj.operations); + handleObjectOps(canvasCRO, obj.vertices); render(); }); diff --git a/examples/canvas/src/objects/canvas.ts b/examples/canvas/src/objects/canvas.ts index 154c0cfc..7d052e9f 100644 --- a/examples/canvas/src/objects/canvas.ts +++ b/examples/canvas/src/objects/canvas.ts @@ -1,6 +1,13 @@ import { Pixel } from "./pixel"; +import { + ActionType, + type CRO, + type Operation, +} from "@topology-foundation/object"; + +export class Canvas implements CRO { + operations: string[] = ["splash", "paint"]; -export class Canvas { width: number; height: number; canvas: Pixel[][]; @@ -14,7 +21,7 @@ export class Canvas { } splash( - node_id: string, + nodeId: string, offset: [number, number], size: [number, number], rgb: [number, number, number], @@ -24,7 +31,7 @@ export class Canvas { for (let x = offset[0]; x < this.width || x < offset[0] + size[0]; x++) { for (let y = offset[1]; y < this.height || y < offset[1] + size[1]; y++) { - this.canvas[x][y].paint(node_id, rgb); + this.canvas[x][y].paint(nodeId, rgb); } } } @@ -49,4 +56,15 @@ export class Canvas { row.forEach((pixel, y) => pixel.merge(peerCanvas.pixel(x, y))), ); } + + resolveConflicts(_): ActionType { + return ActionType.Nop; + } + + mergeCallback(operations: Operation[]): void { + for (const op of operations) { + if (!op.value) continue; + this.merge(op.value); + } + } } diff --git a/examples/chat/src/index.ts b/examples/chat/src/index.ts index 8cc7e377..4ee9c3c9 100644 --- a/examples/chat/src/index.ts +++ b/examples/chat/src/index.ts @@ -9,7 +9,7 @@ import { Chat, addMessage, getMessages } from "./objects/chat"; const node = new TopologyNode(); // CRO = Conflict-free Replicated Object -let topologyObject: TopologyObject; +let topologyObject: TopologyObject; let chatCRO: Chat; let peers: string[] = []; let discoveryPeers: string[] = []; @@ -91,8 +91,8 @@ async function main() { document.getElementById("createRoom") ); button_create.addEventListener("click", async () => { - chatCRO = new Chat(); - topologyObject = await node.createObject(); + topologyObject = await node.createObject(new Chat()); + chatCRO = topologyObject.cro as Chat; node.addCustomGroupMessageHandler(topologyObject.id, (e) => { // on create/connect @@ -103,7 +103,7 @@ async function main() { node.objectStore.subscribe(topologyObject.id, (_, obj) => { console.log("Received object operations: ", obj); - handleObjectOps(chatCRO, obj.operations); + handleObjectOps(chatCRO, obj.vertices); }); (document.getElementById("chatId")).innerText = diff --git a/examples/chat/src/objects/chat.ts b/examples/chat/src/objects/chat.ts index 908e4192..49583e3b 100644 --- a/examples/chat/src/objects/chat.ts +++ b/examples/chat/src/objects/chat.ts @@ -5,8 +5,15 @@ import { gset_create, gset_merge, } from "@topology-foundation/crdt"; +import { + ActionType, + Vertex, + type CRO, + type Operation, +} from "@topology-foundation/object"; -export class Chat { +export class Chat implements CRO { + operations: string[] = ["addMessage"]; // store messages as strings in the format (timestamp, message, nodeId) messages: GSet; constructor() { @@ -24,6 +31,16 @@ export class Chat { merge(other: Chat): void { this.messages.merge(other.messages); } + + resolveConflicts(vertices: Vertex[]): ActionType { + return ActionType.Nop; + } + + mergeCallback(operations: Operation[]): void { + for (const op of operations) { + console.log(op); + } + } } export function createChat(): Chat { diff --git a/packages/crdt/src/cros/AddWinsSet/index.ts b/packages/crdt/src/cros/AddWinsSet/index.ts index 54e826af..c9f937ff 100644 --- a/packages/crdt/src/cros/AddWinsSet/index.ts +++ b/packages/crdt/src/cros/AddWinsSet/index.ts @@ -5,7 +5,7 @@ import { type Vertex, } from "@topology-foundation/object"; -export class AddWinsSet implements CRO { +export class AddWinsSet implements CRO { operations: string[] = ["add", "remove"]; state: Map; @@ -40,10 +40,10 @@ export class AddWinsSet implements CRO { } // in this case is an array of length 2 and there are only two possible operations - resolveConflicts(vertices: Vertex[]): ActionType { + resolveConflicts(vertices: Vertex[]): ActionType { if ( vertices[0].operation.type !== vertices[1].operation.type && - vertices[0].operation.value === vertices[1].operation.value + vertices[0].operation.value === (vertices[1].operation.value as T) ) { return vertices[0].operation.type === "add" ? ActionType.DropRight @@ -53,15 +53,15 @@ export class AddWinsSet implements CRO { } // merged at HG level and called as a callback - mergeCallback(operations: Operation[]): void { + mergeCallback(operations: Operation[]): void { this.state = new Map(); for (const op of operations) { switch (op.type) { case "add": - if (op.value !== null) this._add(op.value); + if (op.value !== null) this._add(op.value as T); break; case "remove": - if (op.value !== null) this._remove(op.value); + if (op.value !== null) this._remove(op.value as T); break; default: break; diff --git a/packages/network/src/index.ts b/packages/network/src/index.ts index 900cd65f..c550a43d 100644 --- a/packages/network/src/index.ts +++ b/packages/network/src/index.ts @@ -1,2 +1,2 @@ export * from "./node.js"; -export * from "./proto/messages_pb.js"; +export * as NetworkPb from "./proto/messages_pb.js"; diff --git a/packages/network/src/proto/messages.proto b/packages/network/src/proto/messages.proto index 6aa971fe..ef857fbd 100644 --- a/packages/network/src/proto/messages.proto +++ b/packages/network/src/proto/messages.proto @@ -1,6 +1,18 @@ syntax = "proto3"; package topology.network; +// Supposed to be the RIBLT stuff +message Vertex { + message Operation { + string type = 1; + string value = 2; + } + string hash = 1; + string nodeId = 2; + Operation operation = 3; + repeated string dependencies = 4; +}; + message Message { enum MessageType { UPDATE = 0; @@ -14,3 +26,18 @@ message Message { MessageType type = 2; bytes data = 3; } + +message Update { +} + +message Sync { + repeated string vertex_hashes = 1; +} + +message SyncAccept { + // not strings + repeated Vertex diff = 1; + repeated string missing = 2; +} + +message SyncReject { } diff --git a/packages/network/src/proto/messages_pb.ts b/packages/network/src/proto/messages_pb.ts index bde7f067..b53785cf 100644 --- a/packages/network/src/proto/messages_pb.ts +++ b/packages/network/src/proto/messages_pb.ts @@ -2,13 +2,26 @@ // versions: // protoc-gen-ts_proto v2.0.3 // protoc unknown -// source: proto/messages.proto +// source: network/src/proto/messages.proto /* eslint-disable */ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; export const protobufPackage = "topology.network"; +/** Supposed to be the RIBLT stuff */ +export interface Vertex { + hash: string; + nodeId: string; + operation: Vertex_Operation | undefined; + dependencies: string[]; +} + +export interface Vertex_Operation { + type: string; + value: string; +} + export interface Message { sender: string; type: Message_MessageType; @@ -66,6 +79,204 @@ export function message_MessageTypeToJSON(object: Message_MessageType): string { } } +export interface Update { +} + +export interface Sync { + vertexHashes: string[]; +} + +export interface SyncAccept { + /** not strings */ + diff: Vertex[]; + missing: string[]; +} + +export interface SyncReject { +} + +function createBaseVertex(): Vertex { + return { hash: "", nodeId: "", operation: undefined, dependencies: [] }; +} + +export const Vertex = { + encode(message: Vertex, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.hash !== "") { + writer.uint32(10).string(message.hash); + } + if (message.nodeId !== "") { + writer.uint32(18).string(message.nodeId); + } + if (message.operation !== undefined) { + Vertex_Operation.encode(message.operation, writer.uint32(26).fork()).join(); + } + for (const v of message.dependencies) { + writer.uint32(34).string(v!); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Vertex { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseVertex(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.hash = reader.string(); + continue; + case 2: + if (tag !== 18) { + break; + } + + message.nodeId = reader.string(); + continue; + case 3: + if (tag !== 26) { + break; + } + + message.operation = Vertex_Operation.decode(reader, reader.uint32()); + continue; + case 4: + if (tag !== 34) { + break; + } + + message.dependencies.push(reader.string()); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): Vertex { + return { + hash: isSet(object.hash) ? globalThis.String(object.hash) : "", + nodeId: isSet(object.nodeId) ? globalThis.String(object.nodeId) : "", + operation: isSet(object.operation) ? Vertex_Operation.fromJSON(object.operation) : undefined, + dependencies: globalThis.Array.isArray(object?.dependencies) + ? object.dependencies.map((e: any) => globalThis.String(e)) + : [], + }; + }, + + toJSON(message: Vertex): unknown { + const obj: any = {}; + if (message.hash !== "") { + obj.hash = message.hash; + } + if (message.nodeId !== "") { + obj.nodeId = message.nodeId; + } + if (message.operation !== undefined) { + obj.operation = Vertex_Operation.toJSON(message.operation); + } + if (message.dependencies?.length) { + obj.dependencies = message.dependencies; + } + return obj; + }, + + create, I>>(base?: I): Vertex { + return Vertex.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Vertex { + const message = createBaseVertex(); + message.hash = object.hash ?? ""; + message.nodeId = object.nodeId ?? ""; + message.operation = (object.operation !== undefined && object.operation !== null) + ? Vertex_Operation.fromPartial(object.operation) + : undefined; + message.dependencies = object.dependencies?.map((e) => e) || []; + return message; + }, +}; + +function createBaseVertex_Operation(): Vertex_Operation { + return { type: "", value: "" }; +} + +export const Vertex_Operation = { + encode(message: Vertex_Operation, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.type !== "") { + writer.uint32(10).string(message.type); + } + if (message.value !== "") { + writer.uint32(18).string(message.value); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Vertex_Operation { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseVertex_Operation(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.type = reader.string(); + continue; + case 2: + if (tag !== 18) { + break; + } + + message.value = reader.string(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): Vertex_Operation { + return { + type: isSet(object.type) ? globalThis.String(object.type) : "", + value: isSet(object.value) ? globalThis.String(object.value) : "", + }; + }, + + toJSON(message: Vertex_Operation): unknown { + const obj: any = {}; + if (message.type !== "") { + obj.type = message.type; + } + if (message.value !== "") { + obj.value = message.value; + } + return obj; + }, + + create, I>>(base?: I): Vertex_Operation { + return Vertex_Operation.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Vertex_Operation { + const message = createBaseVertex_Operation(); + message.type = object.type ?? ""; + message.value = object.value ?? ""; + return message; + }, +}; + function createBaseMessage(): Message { return { sender: "", type: 0, data: new Uint8Array(0) }; } @@ -155,6 +366,227 @@ export const Message = { }, }; +function createBaseUpdate(): Update { + return {}; +} + +export const Update = { + encode(_: Update, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Update { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseUpdate(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(_: any): Update { + return {}; + }, + + toJSON(_: Update): unknown { + const obj: any = {}; + return obj; + }, + + create, I>>(base?: I): Update { + return Update.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(_: I): Update { + const message = createBaseUpdate(); + return message; + }, +}; + +function createBaseSync(): Sync { + return { vertexHashes: [] }; +} + +export const Sync = { + encode(message: Sync, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + for (const v of message.vertexHashes) { + writer.uint32(10).string(v!); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Sync { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseSync(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.vertexHashes.push(reader.string()); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): Sync { + return { + vertexHashes: globalThis.Array.isArray(object?.vertexHashes) + ? object.vertexHashes.map((e: any) => globalThis.String(e)) + : [], + }; + }, + + toJSON(message: Sync): unknown { + const obj: any = {}; + if (message.vertexHashes?.length) { + obj.vertexHashes = message.vertexHashes; + } + return obj; + }, + + create, I>>(base?: I): Sync { + return Sync.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Sync { + const message = createBaseSync(); + message.vertexHashes = object.vertexHashes?.map((e) => e) || []; + return message; + }, +}; + +function createBaseSyncAccept(): SyncAccept { + return { diff: [], missing: [] }; +} + +export const SyncAccept = { + encode(message: SyncAccept, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + for (const v of message.diff) { + Vertex.encode(v!, writer.uint32(10).fork()).join(); + } + for (const v of message.missing) { + writer.uint32(18).string(v!); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): SyncAccept { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseSyncAccept(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.diff.push(Vertex.decode(reader, reader.uint32())); + continue; + case 2: + if (tag !== 18) { + break; + } + + message.missing.push(reader.string()); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): SyncAccept { + return { + diff: globalThis.Array.isArray(object?.diff) ? object.diff.map((e: any) => Vertex.fromJSON(e)) : [], + missing: globalThis.Array.isArray(object?.missing) ? object.missing.map((e: any) => globalThis.String(e)) : [], + }; + }, + + toJSON(message: SyncAccept): unknown { + const obj: any = {}; + if (message.diff?.length) { + obj.diff = message.diff.map((e) => Vertex.toJSON(e)); + } + if (message.missing?.length) { + obj.missing = message.missing; + } + return obj; + }, + + create, I>>(base?: I): SyncAccept { + return SyncAccept.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): SyncAccept { + const message = createBaseSyncAccept(); + message.diff = object.diff?.map((e) => Vertex.fromPartial(e)) || []; + message.missing = object.missing?.map((e) => e) || []; + return message; + }, +}; + +function createBaseSyncReject(): SyncReject { + return {}; +} + +export const SyncReject = { + encode(_: SyncReject, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): SyncReject { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseSyncReject(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(_: any): SyncReject { + return {}; + }, + + toJSON(_: SyncReject): unknown { + const obj: any = {}; + return obj; + }, + + create, I>>(base?: I): SyncReject { + return SyncReject.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(_: I): SyncReject { + const message = createBaseSyncReject(); + return message; + }, +}; + function bytesFromBase64(b64: string): Uint8Array { if ((globalThis as any).Buffer) { return Uint8Array.from(globalThis.Buffer.from(b64, "base64")); diff --git a/packages/node/src/handlers.ts b/packages/node/src/handlers.ts index 3f2de986..5e30aaf0 100644 --- a/packages/node/src/handlers.ts +++ b/packages/node/src/handlers.ts @@ -1,6 +1,6 @@ import type { Stream } from "@libp2p/interface"; -import { Message, Message_MessageType } from "@topology-foundation/network"; -import { TopologyObjectBase } from "@topology-foundation/object"; +import { NetworkPb } from "@topology-foundation/network"; +import { type TopologyObject, ObjectPb } from "@topology-foundation/object"; import * as lp from "it-length-prefixed"; import type { TopologyNode } from "./index.js"; @@ -13,12 +13,14 @@ export async function topologyMessagesHandler( stream?: Stream, data?: Uint8Array, ) { - let message: Message; + let message: NetworkPb.Message; if (stream) { const buf = (await lp.decode(stream.source).return()).value; - message = Message.decode(new Uint8Array(buf ? buf.subarray() : [])); + message = NetworkPb.Message.decode( + new Uint8Array(buf ? buf.subarray() : []), + ); } else if (data) { - message = Message.decode(data); + message = NetworkPb.Message.decode(data); } else { console.error( "topology::node::messageHandler", @@ -28,10 +30,10 @@ export async function topologyMessagesHandler( } switch (message.type) { - case Message_MessageType.UPDATE: + case NetworkPb.Message_MessageType.UPDATE: updateHandler(node, message.data); break; - case Message_MessageType.SYNC: + case NetworkPb.Message_MessageType.SYNC: if (!stream) { console.error("topology::node::messageHandler", "Stream is undefined"); return; @@ -43,10 +45,10 @@ export async function topologyMessagesHandler( message.data, ); break; - case Message_MessageType.SYNC_ACCEPT: + case NetworkPb.Message_MessageType.SYNC_ACCEPT: syncAcceptHandler(node, message.data); break; - case Message_MessageType.SYNC_REJECT: + case NetworkPb.Message_MessageType.SYNC_REJECT: syncRejectHandler(node, message.data); break; default: @@ -60,10 +62,10 @@ export async function topologyMessagesHandler( operations array doesn't contain the full remote operations array */ function updateHandler(node: TopologyNode, data: Uint8Array) { - const object_operations = TopologyObjectBase.decode(data); + const object_operations = ObjectPb.TopologyObjectBase.decode(data); let object = node.objectStore.get(object_operations.id); if (!object) { - object = TopologyObjectBase.create({ + object = ObjectPb.TopologyObjectBase.create({ id: object_operations.id, }); } @@ -85,9 +87,9 @@ function syncHandler( // process, calculate diffs, and send back - const message = Message.create({ + const message = NetworkPb.Message.create({ sender: node.networkNode.peerId, - type: Message_MessageType.SYNC_ACCEPT, + type: NetworkPb.Message_MessageType.SYNC_ACCEPT, // add data here data: new Uint8Array(0), }); @@ -102,12 +104,12 @@ function syncHandler( function syncAcceptHandler(node: TopologyNode, data: Uint8Array) { // don't blindly accept, validate the operations // might have have appeared in the meantime - const object_operations = TopologyObjectBase.decode(data); - let object: TopologyObjectBase | undefined = node.objectStore.get( + const object_operations = ObjectPb.TopologyObjectBase.decode(data); + let object: ObjectPb.TopologyObjectBase | undefined = node.objectStore.get( object_operations.id, ); if (!object) { - object = TopologyObjectBase.create({ + object = ObjectPb.TopologyObjectBase.create({ id: object_operations.id, }); } @@ -131,3 +133,28 @@ function syncRejectHandler(node: TopologyNode, data: Uint8Array) { // - Ask sync from another peer // - Do nothing } + +export function topologyObjectChangesHandler( + node: TopologyNode, + obj: TopologyObject, + originFn: string, + vertices: ObjectPb.Vertex[], +) { + switch (originFn) { + case "merge": + node.objectStore.put(obj.id, obj); + break; + case "callFn": { + node.objectStore.put(obj.id, obj); + // send vertices to the pubsub group + const message = NetworkPb.Message.create({ + type: NetworkPb.Message_MessageType.UPDATE, + data: ObjectPb.TopologyObjectBase.encode(obj).finish(), + }); + node.networkNode.broadcastMessage(obj.id, message); + break; + } + default: + console.error("topology::node::createObject", "Invalid origin function"); + } +} diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 94425455..aebcc5f3 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -1,22 +1,20 @@ import type { GossipsubMessage } from "@chainsafe/libp2p-gossipsub"; import type { EventCallback, StreamHandler } from "@libp2p/interface"; import { - Message, - Message_MessageType, + NetworkPb, TopologyNetworkNode, type TopologyNetworkNodeConfig, } from "@topology-foundation/network"; import { type CRO, - TopologyObjectBase, - newTopologyObject, + TopologyObject, + ObjectPb, } from "@topology-foundation/object"; import { topologyMessagesHandler } from "./handlers.js"; -import { OPERATIONS, executeObjectOperation } from "./operations.js"; +import * as operations from "./operations.js"; import { TopologyObjectStore } from "./store/index.js"; import * as crypto from "node:crypto"; -export * from "./operations.js"; // snake_casing to match the JSON config export interface TopologyNodeConfig { @@ -54,9 +52,9 @@ export class TopologyNode { } sendGroupMessage(group: string, data: Uint8Array) { - const message = Message.create({ + const message = NetworkPb.Message.create({ sender: this.networkNode.peerId, - type: Message_MessageType.CUSTOM, + type: NetworkPb.Message_MessageType.CUSTOM, data, }); this.networkNode.broadcastMessage(group, message); @@ -67,56 +65,32 @@ export class TopologyNode { } sendCustomMessage(peerId: string, protocol: string, data: Uint8Array) { - const message = Message.create({ + const message = NetworkPb.Message.create({ sender: this.networkNode.peerId, - type: Message_MessageType.CUSTOM, + type: NetworkPb.Message_MessageType.CUSTOM, data, }); this.networkNode.sendMessage(peerId, [protocol], message); } - async createObject(cro: CRO, id?: string, path?: string, abi?: string) { - const object = await newTopologyObject( - this.networkNode.peerId, - cro, - path, - id, - abi, - ); - executeObjectOperation( - this, - OPERATIONS.CREATE, - TopologyObjectBase.encode(object).finish(), - ); - this.networkNode.addGroupMessageHandler(object.id, async (e) => - topologyMessagesHandler(this, undefined, e.detail.msg.data), - ); + async createObject(cro: CRO, id?: string, abi?: string) { + const object = new TopologyObject(this.networkNode.peerId, cro, id, abi); + operations.createObject(this, object); + return object; } updateObject(id: string, operations: { fn: string; args: string[] }[]) { // TODO: needs refactor for working with hash graph - const object = TopologyObjectBase.create({ + const object = ObjectPb.TopologyObjectBase.create({ id, }); - executeObjectOperation( - this, - OPERATIONS.UPDATE, - TopologyObjectBase.encode(object).finish(), - ); } async subscribeObject(id: string, fetch?: boolean, peerId?: string) { - const object = TopologyObjectBase.create({ + const object = ObjectPb.TopologyObjectBase.create({ id, }); - executeObjectOperation( - this, - OPERATIONS.SUBSCRIBE, - TopologyObjectBase.encode(object).finish(), - fetch, - peerId, - ); this.networkNode.addGroupMessageHandler(id, async (e) => topologyMessagesHandler(this, undefined, e.detail.msg.data), ); @@ -124,31 +98,15 @@ export class TopologyNode { } unsubscribeObject(id: string, purge?: boolean) { - const object = TopologyObjectBase.create({ + const object = ObjectPb.TopologyObjectBase.create({ id, }); - executeObjectOperation( - this, - OPERATIONS.UNSUBSCRIBE, - TopologyObjectBase.encode(object).finish(), - purge, - ); } - async syncObject( - id: string, - operations: { nonce: string; fn: string; args: string[] }[], - peerId?: string, - ) { - const object = TopologyObjectBase.create({ + async syncObject(id: string, vertices: NetworkPb.Vertex[], peerId?: string) { + const object = ObjectPb.TopologyObjectBase.create({ id, }); - executeObjectOperation( - this, - OPERATIONS.SYNC, - TopologyObjectBase.encode(object).finish(), - peerId, - ); } } diff --git a/packages/node/src/operations.ts b/packages/node/src/operations.ts index 74a54a89..796efd0c 100644 --- a/packages/node/src/operations.ts +++ b/packages/node/src/operations.ts @@ -1,6 +1,10 @@ -import { Message, Message_MessageType } from "@topology-foundation/network"; -import { TopologyObjectBase } from "@topology-foundation/object"; +import { NetworkPb } from "@topology-foundation/network"; +import { type TopologyObject, ObjectPb } from "@topology-foundation/object"; import type { TopologyNode } from "./index.js"; +import { + topologyMessagesHandler, + topologyObjectChangesHandler, +} from "./handlers.js"; /* Object operations */ export enum OPERATIONS { @@ -17,55 +21,28 @@ export enum OPERATIONS { SYNC = 4, } -/* Utility function to execute object operations apart of calling the functions directly */ -export async function executeObjectOperation( - node: TopologyNode, - operation: OPERATIONS, - data: Uint8Array, - // biome-ignore lint/suspicious/noExplicitAny: intended to be any - ...args: any[] -) { - switch (operation) { - case OPERATIONS.CREATE: - createObject(node, data); - break; - case OPERATIONS.UPDATE: - updateObject(node, data); - break; - case OPERATIONS.SUBSCRIBE: - await subscribeObject(node, data, ...args); - break; - case OPERATIONS.UNSUBSCRIBE: - unsubscribeObject(node, data, ...args); - break; - case OPERATIONS.SYNC: - await syncObject(node, data, ...args); - break; - default: - console.error( - "topology::node::executeObjectOperation", - "Invalid operation", - ); - break; - } -} - -/* data: { id: string, abi: string, bytecode: Uint8Array } */ -function createObject(node: TopologyNode, data: Uint8Array) { - const object = TopologyObjectBase.decode(data); - node.networkNode.subscribe(object.id); +export function createObject(node: TopologyNode, object: TopologyObject) { node.objectStore.put(object.id, object); + object.subscribe((obj, originFn, vertices) => + topologyObjectChangesHandler(node, obj, originFn, vertices), + ); + node.networkNode.subscribe(object.id); + node.networkNode.addGroupMessageHandler(object.id, async (e) => + topologyMessagesHandler(node, undefined, e.detail.msg.data), + ); } /* data: { id: string, operations: {nonce: string, fn: string, args: string[] }[] } operations array doesn't contain the full remote operations array */ -function updateObject(node: TopologyNode, data: Uint8Array) { - const object_operations = TopologyObjectBase.decode(data); - let object = node.objectStore.get(object_operations.id); +export function updateObject(node: TopologyNode, data: Uint8Array) { + const object_operations = ObjectPb.TopologyObjectBase.decode(data); + let object: ObjectPb.TopologyObjectBase | undefined = node.objectStore.get( + object_operations.id, + ); if (!object) { - object = TopologyObjectBase.create({ + object = ObjectPb.TopologyObjectBase.create({ id: object_operations.id, }); } @@ -76,34 +53,33 @@ function updateObject(node: TopologyNode, data: Uint8Array) { } node.objectStore.put(object.id, object); - const message = Message.create({ - type: Message_MessageType.UPDATE, + const message = NetworkPb.Message.create({ + type: NetworkPb.Message_MessageType.UPDATE, data: data, }); node.networkNode.broadcastMessage(object.id, message); } /* data: { id: string } */ -async function subscribeObject( +export async function subscribeObject( node: TopologyNode, - data: Uint8Array, - fetch?: boolean, + objectId: string, + sync?: boolean, peerId?: string, ) { - const object = TopologyObjectBase.decode(data); - node.networkNode.subscribe(object.id); + node.networkNode.subscribe(objectId); - if (!fetch) return; + if (!sync) return; // complies with format, since the operations array is empty - const message = Message.create({ + const message = NetworkPb.Message.create({ sender: node.networkNode.peerId, - type: Message_MessageType.SYNC, - data, + type: NetworkPb.Message_MessageType.SYNC, + data: new Uint8Array(0), }); if (!peerId) { await node.networkNode.sendGroupMessageRandomPeer( - object.id, + objectId, ["/topology/message/0.0.1"], message, ); @@ -116,36 +92,33 @@ async function subscribeObject( } } -/* data: { id: string } */ -function unsubscribeObject( +export function unsubscribeObject( node: TopologyNode, - data: Uint8Array, + objectId: string, purge?: boolean, ) { - const object = TopologyObjectBase.decode(data); - node.networkNode.unsubscribe(object.id); - if (!purge) return; - node.objectStore.remove(object.id); + node.networkNode.unsubscribe(objectId); + if (purge) node.objectStore.remove(objectId); } /* - data: { id: string, operations: {nonce: string, fn: string, args: string[] }[] } + data: { id: string, vertices: Vertex[] } operations array contain the full remote operations array */ -async function syncObject( +export async function syncObject( node: TopologyNode, + objectId: string, data: Uint8Array, peerId?: string, ) { - const object = TopologyObjectBase.decode(data); - const message = Message.create({ - type: Message_MessageType.SYNC, + const message = NetworkPb.Message.create({ + type: NetworkPb.Message_MessageType.SYNC, data: data, }); if (!peerId) { await node.networkNode.sendGroupMessageRandomPeer( - object.id, + objectId, ["/topology/message/0.0.1"], message, ); diff --git a/packages/node/src/store/index.ts b/packages/node/src/store/index.ts index 17b70408..c4a5c64c 100644 --- a/packages/node/src/store/index.ts +++ b/packages/node/src/store/index.ts @@ -1,25 +1,25 @@ -import type { TopologyObjectBase } from "@topology-foundation/object"; +import type { ObjectPb } from "@topology-foundation/object"; export type TopologyObjectStoreCallback = ( objectId: string, - object: TopologyObjectBase, + object: ObjectPb.TopologyObjectBase, ) => void; export class TopologyObjectStore { // TODO: should be abstracted in handling multiple types of storage - private _store: Map; + private _store: Map; private _subscriptions: Map; constructor() { - this._store = new Map(); + this._store = new Map(); this._subscriptions = new Map(); } - get(objectId: string): TopologyObjectBase | undefined { + get(objectId: string): ObjectPb.TopologyObjectBase | undefined { return this._store.get(objectId); } - put(objectId: string, object: TopologyObjectBase) { + put(objectId: string, object: ObjectPb.TopologyObjectBase) { this._store.set(objectId, object); this._notifySubscribers(objectId, object); } @@ -33,7 +33,7 @@ export class TopologyObjectStore { private _notifySubscribers( objectId: string, - object: TopologyObjectBase, + object: ObjectPb.TopologyObjectBase, ): void { const callbacks = this._subscriptions.get(objectId); if (callbacks) { diff --git a/packages/object/src/hashgraph.ts b/packages/object/src/hashgraph.ts index 2810514d..1fa879db 100644 --- a/packages/object/src/hashgraph.ts +++ b/packages/object/src/hashgraph.ts @@ -58,7 +58,7 @@ export class HashGraph { this.forwardEdges.set(HashGraph.rootHash, []); } - addToFrontier(operation: Operation): Hash { + addToFrontier(operation: Operation): Vertex { const deps = this.getFrontier(); const hash = computeHash(this.nodeId, operation, deps); const vertex: Vertex = { @@ -81,7 +81,7 @@ export class HashGraph { const depsSet = new Set(deps); this.frontier = this.frontier.filter((hash) => !depsSet.has(hash)); - return hash; + return vertex; } // Time complexity: O(d), where d is the number of dependencies diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index eafcb052..43249d93 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -5,9 +5,12 @@ import { type Operation, type Vertex, } from "./hashgraph.js"; -import type { TopologyObjectBase } from "./proto/object_pb.js"; +import type { + TopologyObjectBase, + Vertex as VertexPb, +} from "./proto/object_pb.js"; -export * from "./proto/object_pb.js"; +export * as ObjectPb from "./proto/object_pb.js"; export * from "./hashgraph.js"; export interface CRO { @@ -15,83 +18,71 @@ export interface CRO { mergeCallback: (operations: Operation[]) => void; } -export interface TopologyObject extends TopologyObjectBase { +export type TopologyObjectCallback = ( + object: TopologyObject, + origin: string, + vertices: VertexPb[], +) => void; + +export interface ITopologyObject extends TopologyObjectBase { cro: ProxyHandler | null; hashGraph: HashGraph; + subscriptions: TopologyObjectCallback[]; } -/* Creates a new TopologyObject */ -export async function newTopologyObject( - nodeId: string, - cro: CRO, - id?: string, - abi?: string, -): Promise { - // const bytecode = await compileWasm(path); - const bytecode = new Uint8Array(); - const obj: TopologyObject = { - id: +export class TopologyObject implements ITopologyObject { + nodeId: string; + id: string; + abi: string; + bytecode: Uint8Array; + vertices: VertexPb[]; + cro: ProxyHandler | null; + hashGraph: HashGraph; + subscriptions: TopologyObjectCallback[]; + + constructor(nodeId: string, cro: CRO, id?: string, abi?: string) { + this.nodeId = nodeId; + this.id = id ?? crypto .createHash("sha256") .update(abi ?? "") .update(nodeId) .update(Math.floor(Math.random() * Number.MAX_VALUE).toString()) - .digest("hex"), - abi: abi ?? "", - bytecode: bytecode ?? new Uint8Array(), - vertices: [], - cro: null, - hashGraph: new HashGraph(nodeId, cro.resolveConflicts), - }; - obj.cro = new Proxy(cro, proxyCROHandler(obj)); - return obj; -} - -// This function is black magic, it allows us to intercept calls to the CRO object -function proxyCROHandler(obj: TopologyObject): ProxyHandler { - return { - get(target, propKey, receiver) { - if (typeof target[propKey as keyof object] === "function") { - return new Proxy(target[propKey as keyof object], { - apply(applyTarget, thisArg, args) { - if ((thisArg.operations as string[]).includes(propKey as string)) - callFn( - obj, - propKey as string, - args.length === 1 ? args[0] : args, - ); - return Reflect.apply(applyTarget, thisArg, args); - }, - }); - } - return Reflect.get(target, propKey, receiver); - }, - }; -} - -export async function callFn( - obj: TopologyObject, - fn: string, - args: unknown, -): Promise { - obj.hashGraph.addToFrontier({ type: fn, value: args }); - return obj; -} - -export async function merge(obj: TopologyObject, vertices: Vertex[]) { - for (const vertex of vertices) { - obj.hashGraph.addVertex( - vertex.operation, - vertex.dependencies, - vertex.nodeId, - ); + .digest("hex"); + this.abi = abi ?? ""; + this.bytecode = new Uint8Array(); + this.vertices = []; + this.cro = new Proxy(cro, this.proxyCROHandler()); + this.hashGraph = new HashGraph(nodeId, cro.resolveConflicts); + this.subscriptions = []; } - const operations = obj.hashGraph.linearizeOperations(); - // TODO remove this in favor of RIBLT - obj.vertices = obj.hashGraph.getAllVertices().map((vertex) => { + // This function is black magic, it allows us to intercept calls to the CRO object + proxyCROHandler(): ProxyHandler { + const obj = this; return { + get(target, propKey, receiver) { + if (typeof target[propKey as keyof object] === "function") { + return new Proxy(target[propKey as keyof object], { + apply(applyTarget, thisArg, args) { + if ((thisArg.operations as string[]).includes(propKey as string)) + obj.callFn( + propKey as string, + args.length === 1 ? args[0] : args, + ); + return Reflect.apply(applyTarget, thisArg, args); + }, + }); + } + return Reflect.get(target, propKey, receiver); + }, + }; + } + + callFn(fn: string, args: unknown) { + const vertex = this.hashGraph.addToFrontier({ type: fn, value: args }); + const serializedVertex = { hash: vertex.hash, nodeId: vertex.nodeId, operation: { @@ -100,7 +91,44 @@ export async function merge(obj: TopologyObject, vertices: Vertex[]) { }, dependencies: vertex.dependencies, }; - }); + this.vertices.push(serializedVertex); + this._notify("callFn", [serializedVertex]); + } + + merge(obj: TopologyObject, vertices: Vertex[]) { + for (const vertex of vertices) { + obj.hashGraph.addVertex( + vertex.operation, + vertex.dependencies, + vertex.nodeId, + ); + } + + const operations = obj.hashGraph.linearizeOperations(); + // TODO remove this in favor of RIBLT + obj.vertices = obj.hashGraph.getAllVertices().map((vertex) => { + return { + hash: vertex.hash, + nodeId: vertex.nodeId, + operation: { + type: vertex.operation.type, + value: vertex.operation.value as string, + }, + dependencies: vertex.dependencies, + }; + }); - (obj.cro as CRO).mergeCallback(operations); + (obj.cro as CRO).mergeCallback(operations); + this._notify("merge", obj.vertices); + } + + subscribe(callback: TopologyObjectCallback) { + this.subscriptions.push(callback); + } + + private _notify(origin: string, vertices: VertexPb[]) { + for (const callback of this.subscriptions) { + callback(this, origin, vertices); + } + } } diff --git a/packages/object/src/proto/object.proto b/packages/object/src/proto/object.proto index f234574d..a8ce1144 100644 --- a/packages/object/src/proto/object.proto +++ b/packages/object/src/proto/object.proto @@ -1,19 +1,19 @@ syntax = "proto3"; package topology.object; -message TopologyObjectBase { - // Supposed to be the RIBLT stuff - message Vertex { - message Operation { - string type = 1; - string value = 2; - } - string hash = 1; - string nodeId = 2; - Operation operation = 3; - repeated string dependencies = 4; - }; +// Supposed to be the RIBLT stuff +message Vertex { + message Operation { + string type = 1; + string value = 2; + } + string hash = 1; + string nodeId = 2; + Operation operation = 3; + repeated string dependencies = 4; +}; +message TopologyObjectBase { string id = 1; optional string abi = 2; optional bytes bytecode = 3; diff --git a/packages/object/src/proto/object_pb.ts b/packages/object/src/proto/object_pb.ts index 31165a5d..ce73f29b 100644 --- a/packages/object/src/proto/object_pb.ts +++ b/packages/object/src/proto/object_pb.ts @@ -2,58 +2,58 @@ // versions: // protoc-gen-ts_proto v2.0.3 // protoc unknown -// source: proto/object.proto +// source: crdt/node_modules/@topology-foundation/object/src/proto/object.proto /* eslint-disable */ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; export const protobufPackage = "topology.object"; -export interface TopologyObjectBase { - id: string; - abi?: string | undefined; - bytecode?: Uint8Array | undefined; - vertices: TopologyObjectBase_Vertex[]; -} - /** Supposed to be the RIBLT stuff */ -export interface TopologyObjectBase_Vertex { +export interface Vertex { hash: string; nodeId: string; - operation: TopologyObjectBase_Vertex_Operation | undefined; + operation: Vertex_Operation | undefined; dependencies: string[]; } -export interface TopologyObjectBase_Vertex_Operation { +export interface Vertex_Operation { type: string; value: string; } -function createBaseTopologyObjectBase(): TopologyObjectBase { - return { id: "", abi: undefined, bytecode: undefined, vertices: [] }; +export interface TopologyObjectBase { + id: string; + abi?: string | undefined; + bytecode?: Uint8Array | undefined; + vertices: Vertex[]; } -export const TopologyObjectBase = { - encode(message: TopologyObjectBase, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { - if (message.id !== "") { - writer.uint32(10).string(message.id); +function createBaseVertex(): Vertex { + return { hash: "", nodeId: "", operation: undefined, dependencies: [] }; +} + +export const Vertex = { + encode(message: Vertex, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.hash !== "") { + writer.uint32(10).string(message.hash); } - if (message.abi !== undefined) { - writer.uint32(18).string(message.abi); + if (message.nodeId !== "") { + writer.uint32(18).string(message.nodeId); } - if (message.bytecode !== undefined) { - writer.uint32(26).bytes(message.bytecode); + if (message.operation !== undefined) { + Vertex_Operation.encode(message.operation, writer.uint32(26).fork()).join(); } - for (const v of message.vertices) { - TopologyObjectBase_Vertex.encode(v!, writer.uint32(34).fork()).join(); + for (const v of message.dependencies) { + writer.uint32(34).string(v!); } return writer; }, - decode(input: BinaryReader | Uint8Array, length?: number): TopologyObjectBase { + decode(input: BinaryReader | Uint8Array, length?: number): Vertex { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); let end = length === undefined ? reader.len : reader.pos + length; - const message = createBaseTopologyObjectBase(); + const message = createBaseVertex(); while (reader.pos < end) { const tag = reader.uint32(); switch (tag >>> 3) { @@ -62,28 +62,28 @@ export const TopologyObjectBase = { break; } - message.id = reader.string(); + message.hash = reader.string(); continue; case 2: if (tag !== 18) { break; } - message.abi = reader.string(); + message.nodeId = reader.string(); continue; case 3: if (tag !== 26) { break; } - message.bytecode = reader.bytes(); + message.operation = Vertex_Operation.decode(reader, reader.uint32()); continue; case 4: if (tag !== 34) { break; } - message.vertices.push(TopologyObjectBase_Vertex.decode(reader, reader.uint32())); + message.dependencies.push(reader.string()); continue; } if ((tag & 7) === 4 || tag === 0) { @@ -94,72 +94,68 @@ export const TopologyObjectBase = { return message; }, - fromJSON(object: any): TopologyObjectBase { + fromJSON(object: any): Vertex { return { - id: isSet(object.id) ? globalThis.String(object.id) : "", - abi: isSet(object.abi) ? globalThis.String(object.abi) : undefined, - bytecode: isSet(object.bytecode) ? bytesFromBase64(object.bytecode) : undefined, - vertices: globalThis.Array.isArray(object?.vertices) - ? object.vertices.map((e: any) => TopologyObjectBase_Vertex.fromJSON(e)) + hash: isSet(object.hash) ? globalThis.String(object.hash) : "", + nodeId: isSet(object.nodeId) ? globalThis.String(object.nodeId) : "", + operation: isSet(object.operation) ? Vertex_Operation.fromJSON(object.operation) : undefined, + dependencies: globalThis.Array.isArray(object?.dependencies) + ? object.dependencies.map((e: any) => globalThis.String(e)) : [], }; }, - toJSON(message: TopologyObjectBase): unknown { + toJSON(message: Vertex): unknown { const obj: any = {}; - if (message.id !== "") { - obj.id = message.id; + if (message.hash !== "") { + obj.hash = message.hash; } - if (message.abi !== undefined) { - obj.abi = message.abi; + if (message.nodeId !== "") { + obj.nodeId = message.nodeId; } - if (message.bytecode !== undefined) { - obj.bytecode = base64FromBytes(message.bytecode); + if (message.operation !== undefined) { + obj.operation = Vertex_Operation.toJSON(message.operation); } - if (message.vertices?.length) { - obj.vertices = message.vertices.map((e) => TopologyObjectBase_Vertex.toJSON(e)); + if (message.dependencies?.length) { + obj.dependencies = message.dependencies; } return obj; }, - create, I>>(base?: I): TopologyObjectBase { - return TopologyObjectBase.fromPartial(base ?? ({} as any)); + create, I>>(base?: I): Vertex { + return Vertex.fromPartial(base ?? ({} as any)); }, - fromPartial, I>>(object: I): TopologyObjectBase { - const message = createBaseTopologyObjectBase(); - message.id = object.id ?? ""; - message.abi = object.abi ?? undefined; - message.bytecode = object.bytecode ?? undefined; - message.vertices = object.vertices?.map((e) => TopologyObjectBase_Vertex.fromPartial(e)) || []; + fromPartial, I>>(object: I): Vertex { + const message = createBaseVertex(); + message.hash = object.hash ?? ""; + message.nodeId = object.nodeId ?? ""; + message.operation = (object.operation !== undefined && object.operation !== null) + ? Vertex_Operation.fromPartial(object.operation) + : undefined; + message.dependencies = object.dependencies?.map((e) => e) || []; return message; }, }; -function createBaseTopologyObjectBase_Vertex(): TopologyObjectBase_Vertex { - return { hash: "", nodeId: "", operation: undefined, dependencies: [] }; +function createBaseVertex_Operation(): Vertex_Operation { + return { type: "", value: "" }; } -export const TopologyObjectBase_Vertex = { - encode(message: TopologyObjectBase_Vertex, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { - if (message.hash !== "") { - writer.uint32(10).string(message.hash); - } - if (message.nodeId !== "") { - writer.uint32(18).string(message.nodeId); - } - if (message.operation !== undefined) { - TopologyObjectBase_Vertex_Operation.encode(message.operation, writer.uint32(26).fork()).join(); +export const Vertex_Operation = { + encode(message: Vertex_Operation, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.type !== "") { + writer.uint32(10).string(message.type); } - for (const v of message.dependencies) { - writer.uint32(34).string(v!); + if (message.value !== "") { + writer.uint32(18).string(message.value); } return writer; }, - decode(input: BinaryReader | Uint8Array, length?: number): TopologyObjectBase_Vertex { + decode(input: BinaryReader | Uint8Array, length?: number): Vertex_Operation { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); let end = length === undefined ? reader.len : reader.pos + length; - const message = createBaseTopologyObjectBase_Vertex(); + const message = createBaseVertex_Operation(); while (reader.pos < end) { const tag = reader.uint32(); switch (tag >>> 3) { @@ -168,28 +164,14 @@ export const TopologyObjectBase_Vertex = { break; } - message.hash = reader.string(); + message.type = reader.string(); continue; case 2: if (tag !== 18) { break; } - message.nodeId = reader.string(); - continue; - case 3: - if (tag !== 26) { - break; - } - - message.operation = TopologyObjectBase_Vertex_Operation.decode(reader, reader.uint32()); - continue; - case 4: - if (tag !== 34) { - break; - } - - message.dependencies.push(reader.string()); + message.value = reader.string(); continue; } if ((tag & 7) === 4 || tag === 0) { @@ -200,68 +182,60 @@ export const TopologyObjectBase_Vertex = { return message; }, - fromJSON(object: any): TopologyObjectBase_Vertex { + fromJSON(object: any): Vertex_Operation { return { - hash: isSet(object.hash) ? globalThis.String(object.hash) : "", - nodeId: isSet(object.nodeId) ? globalThis.String(object.nodeId) : "", - operation: isSet(object.operation) ? TopologyObjectBase_Vertex_Operation.fromJSON(object.operation) : undefined, - dependencies: globalThis.Array.isArray(object?.dependencies) - ? object.dependencies.map((e: any) => globalThis.String(e)) - : [], + type: isSet(object.type) ? globalThis.String(object.type) : "", + value: isSet(object.value) ? globalThis.String(object.value) : "", }; }, - toJSON(message: TopologyObjectBase_Vertex): unknown { + toJSON(message: Vertex_Operation): unknown { const obj: any = {}; - if (message.hash !== "") { - obj.hash = message.hash; - } - if (message.nodeId !== "") { - obj.nodeId = message.nodeId; - } - if (message.operation !== undefined) { - obj.operation = TopologyObjectBase_Vertex_Operation.toJSON(message.operation); + if (message.type !== "") { + obj.type = message.type; } - if (message.dependencies?.length) { - obj.dependencies = message.dependencies; + if (message.value !== "") { + obj.value = message.value; } return obj; }, - create, I>>(base?: I): TopologyObjectBase_Vertex { - return TopologyObjectBase_Vertex.fromPartial(base ?? ({} as any)); + create, I>>(base?: I): Vertex_Operation { + return Vertex_Operation.fromPartial(base ?? ({} as any)); }, - fromPartial, I>>(object: I): TopologyObjectBase_Vertex { - const message = createBaseTopologyObjectBase_Vertex(); - message.hash = object.hash ?? ""; - message.nodeId = object.nodeId ?? ""; - message.operation = (object.operation !== undefined && object.operation !== null) - ? TopologyObjectBase_Vertex_Operation.fromPartial(object.operation) - : undefined; - message.dependencies = object.dependencies?.map((e) => e) || []; + fromPartial, I>>(object: I): Vertex_Operation { + const message = createBaseVertex_Operation(); + message.type = object.type ?? ""; + message.value = object.value ?? ""; return message; }, }; -function createBaseTopologyObjectBase_Vertex_Operation(): TopologyObjectBase_Vertex_Operation { - return { type: "", value: "" }; +function createBaseTopologyObjectBase(): TopologyObjectBase { + return { id: "", abi: undefined, bytecode: undefined, vertices: [] }; } -export const TopologyObjectBase_Vertex_Operation = { - encode(message: TopologyObjectBase_Vertex_Operation, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { - if (message.type !== "") { - writer.uint32(10).string(message.type); +export const TopologyObjectBase = { + encode(message: TopologyObjectBase, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.id !== "") { + writer.uint32(10).string(message.id); } - if (message.value !== "") { - writer.uint32(18).string(message.value); + if (message.abi !== undefined) { + writer.uint32(18).string(message.abi); + } + if (message.bytecode !== undefined) { + writer.uint32(26).bytes(message.bytecode); + } + for (const v of message.vertices) { + Vertex.encode(v!, writer.uint32(34).fork()).join(); } return writer; }, - decode(input: BinaryReader | Uint8Array, length?: number): TopologyObjectBase_Vertex_Operation { + decode(input: BinaryReader | Uint8Array, length?: number): TopologyObjectBase { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); let end = length === undefined ? reader.len : reader.pos + length; - const message = createBaseTopologyObjectBase_Vertex_Operation(); + const message = createBaseTopologyObjectBase(); while (reader.pos < end) { const tag = reader.uint32(); switch (tag >>> 3) { @@ -270,14 +244,28 @@ export const TopologyObjectBase_Vertex_Operation = { break; } - message.type = reader.string(); + message.id = reader.string(); continue; case 2: if (tag !== 18) { break; } - message.value = reader.string(); + message.abi = reader.string(); + continue; + case 3: + if (tag !== 26) { + break; + } + + message.bytecode = reader.bytes(); + continue; + case 4: + if (tag !== 34) { + break; + } + + message.vertices.push(Vertex.decode(reader, reader.uint32())); continue; } if ((tag & 7) === 4 || tag === 0) { @@ -288,35 +276,41 @@ export const TopologyObjectBase_Vertex_Operation = { return message; }, - fromJSON(object: any): TopologyObjectBase_Vertex_Operation { + fromJSON(object: any): TopologyObjectBase { return { - type: isSet(object.type) ? globalThis.String(object.type) : "", - value: isSet(object.value) ? globalThis.String(object.value) : "", + id: isSet(object.id) ? globalThis.String(object.id) : "", + abi: isSet(object.abi) ? globalThis.String(object.abi) : undefined, + bytecode: isSet(object.bytecode) ? bytesFromBase64(object.bytecode) : undefined, + vertices: globalThis.Array.isArray(object?.vertices) ? object.vertices.map((e: any) => Vertex.fromJSON(e)) : [], }; }, - toJSON(message: TopologyObjectBase_Vertex_Operation): unknown { + toJSON(message: TopologyObjectBase): unknown { const obj: any = {}; - if (message.type !== "") { - obj.type = message.type; + if (message.id !== "") { + obj.id = message.id; } - if (message.value !== "") { - obj.value = message.value; + if (message.abi !== undefined) { + obj.abi = message.abi; + } + if (message.bytecode !== undefined) { + obj.bytecode = base64FromBytes(message.bytecode); + } + if (message.vertices?.length) { + obj.vertices = message.vertices.map((e) => Vertex.toJSON(e)); } return obj; }, - create, I>>( - base?: I, - ): TopologyObjectBase_Vertex_Operation { - return TopologyObjectBase_Vertex_Operation.fromPartial(base ?? ({} as any)); + create, I>>(base?: I): TopologyObjectBase { + return TopologyObjectBase.fromPartial(base ?? ({} as any)); }, - fromPartial, I>>( - object: I, - ): TopologyObjectBase_Vertex_Operation { - const message = createBaseTopologyObjectBase_Vertex_Operation(); - message.type = object.type ?? ""; - message.value = object.value ?? ""; + fromPartial, I>>(object: I): TopologyObjectBase { + const message = createBaseTopologyObjectBase(); + message.id = object.id ?? ""; + message.abi = object.abi ?? undefined; + message.bytecode = object.bytecode ?? undefined; + message.vertices = object.vertices?.map((e) => Vertex.fromPartial(e)) || []; return message; }, };