UNPKG

@energyweb/node-red-contrib-green-proof-worker

Version:
789 lines 27.9 kB
[ { "id": "8ee174a8a298654a", "type": "tab", "label": "Flow 1", "disabled": false, "info": "" }, { "id": "9318cc15f9afd75f", "type": "group", "z": "8ee174a8a298654a", "name": "unitsChanged v1", "style": { "label": true }, "nodes": [ "e1f6f9ae2e6910d1", "27566d8948e738f9", "c3c0627c1de5e349", "caada626130afaf6", "ac42d7e5c6032791", "65d289142a86384f", "6ca11a0d57bdc6cc", "b8d668ba7a1a03ed", "ca32fbaad3ee3347", "e9eb48b870d28564", "d4403c151c10fd53", "98a4076a7346f77b" ], "x": 2074, "y": 119, "w": 1032, "h": 262 }, { "id": "35ba395e82c0b1fd", "type": "group", "z": "8ee174a8a298654a", "name": "No protocol (old message format)", "style": { "label": true }, "nodes": [ "126dbca226762933", "58dda468b6e90876", "be6ed27e6faa08cc", "56fd2cd0cc0812f8" ], "x": 74, "y": 399, "w": 892, "h": 82 }, { "id": "04c09f0c2cd6afcc", "type": "group", "z": "8ee174a8a298654a", "name": "Protocol version 1", "style": { "label": true }, "nodes": [ "fb43395da3eb18ac", "746c2dd32cd8fa7f", "af129b9f346b5e93", "0bdc014d7d7e836a", "a986536a19a878b8", "4d6a02a638fceb91" ], "x": 74, "y": 559, "w": 832, "h": 122 }, { "id": "a407a49de3d419a0", "type": "sqlite-config", "name": "", "dbLocation": "" }, { "id": "cbe7827e8b1ce3bd", "type": "function", "z": "8ee174a8a298654a", "name": "Restart source", "func": "node.warn('Force resetting source')\nreturn {\n ...msg,\n topic: 'force-reset'\n}", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 280, "y": 220, "wires": [ [ "8308f574539280d2" ] ] }, { "id": "39c8b0b71a53b058", "type": "link in", "z": "8ee174a8a298654a", "name": "Handle error IN", "links": [ "41758cdba3bbaddc", "5c9c86e7123f5da9" ], "x": 125, "y": 220, "wires": [ [ "cbe7827e8b1ce3bd" ] ] }, { "id": "e6aebb3547ebbd29", "type": "link in", "z": "8ee174a8a298654a", "name": "Finished processing IN", "links": [ "27566d8948e738f9", "e1f6f9ae2e6910d1", "e16ab80308b97075" ], "x": 125, "y": 280, "wires": [ [ "a8f1dc8b2b511da8" ] ] }, { "id": "a8f1dc8b2b511da8", "type": "function", "z": "8ee174a8a298654a", "name": "Finished processing", "func": "node.warn('Finished processing')\nreturn {\n ...msg,\n topic: 'finished-processing'\n}", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 280, "y": 280, "wires": [ [ "8308f574539280d2" ] ] }, { "id": "e1f6f9ae2e6910d1", "type": "link out", "z": "8ee174a8a298654a", "g": "9318cc15f9afd75f", "name": "Finished processing correct OUT", "mode": "link", "links": [ "e6aebb3547ebbd29" ], "x": 3065, "y": 260, "wires": [] }, { "id": "27566d8948e738f9", "type": "link out", "z": "8ee174a8a298654a", "g": "9318cc15f9afd75f", "name": "Finished processing wrong tx OUT", "mode": "link", "links": [ "e6aebb3547ebbd29" ], "x": 3065, "y": 300, "wires": [] }, { "id": "c3c0627c1de5e349", "type": "comment", "z": "8ee174a8a298654a", "g": "9318cc15f9afd75f", "name": "Validation failed", "info": "", "x": 2480, "y": 340, "wires": [] }, { "id": "caada626130afaf6", "type": "comment", "z": "8ee174a8a298654a", "g": "9318cc15f9afd75f", "name": "Validation succeeded", "info": "", "x": 2500, "y": 220, "wires": [] }, { "id": "ac42d7e5c6032791", "type": "voting-marketplace", "z": "8ee174a8a298654a", "g": "9318cc15f9afd75f", "name": "", "workerAddress": "", "indexerUrl": "", "solutionNamespace": "", "x": 2500, "y": 260, "wires": [ [ "ca32fbaad3ee3347" ] ] }, { "id": "65d289142a86384f", "type": "voting-marketplace", "z": "8ee174a8a298654a", "g": "9318cc15f9afd75f", "name": "", "workerAddress": "", "indexerUrl": "", "solutionNamespace": "", "x": 2500, "y": 300, "wires": [ [ "27566d8948e738f9" ] ] }, { "id": "8308f574539280d2", "type": "source-http-api", "z": "8ee174a8a298654a", "name": "", "host": "", "appId": "ggp", "sqliteConfig": "a407a49de3d419a0", "x": 520, "y": 220, "wires": [ [ "737cc9c7a540ab89" ] ] }, { "id": "737cc9c7a540ab89", "type": "switch", "z": "8ee174a8a298654a", "name": "Protocol check", "property": "payload.protocolVersion", "propertyType": "msg", "rules": [ { "t": "eq", "v": "1", "vt": "str" }, { "t": "istype", "v": "undefined", "vt": "undefined" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 3, "x": 760, "y": 220, "wires": [ [ "a487a21a1443f6b7" ], [ "42fe81814bb0c41b" ], [ "90f75c63b182bae9" ] ] }, { "id": "126dbca226762933", "type": "json-schema-validator", "z": "8ee174a8a298654a", "g": "35ba395e82c0b1fd", "name": "Validate old message type", "jsonSchema": "{\n \"type\": \"object\",\n \"required\": [\"type\", \"txLog\"],\n \"properties\": {\n \"type\": { \"const\": \"unitsChanged\" },\n \"txLog\": {\n \"type\": \"object\",\n \"required\": [\"rootUnitId\", \"changes\"],\n \"properties\": {\n \"rootUnitId\": { \"type\": \"string\" },\n \"changes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\"unitId\", \"volume\", \"owner\", \"prevOwner\"],\n \"properties\": {\n \"unitId\": { \"type\": \"string\" },\n \"volume\": { \"type\": \"number\" },\n \"owner\": { \"type\": \"string\" },\n \"prevOwner\": { \"type\": [\"string\", \"null\"] }\n }\n }\n }\n }\n }\n }\n}", "x": 510, "y": 440, "wires": [ [ "56fd2cd0cc0812f8" ] ] }, { "id": "6ca11a0d57bdc6cc", "type": "sqlite-inject", "z": "8ee174a8a298654a", "g": "9318cc15f9afd75f", "name": "", "sqliteConfig": "a407a49de3d419a0", "x": 2170, "y": 160, "wires": [ [ "e9eb48b870d28564" ] ] }, { "id": "b8d668ba7a1a03ed", "type": "function", "z": "8ee174a8a298654a", "g": "9318cc15f9afd75f", "name": "Query ledger for accounts", "func": "const txLog = msg.payload.payload.txLog;\n\nconst accountIds = txLog.changes.flatMap((change) => {\n return [change.owner].concat(change.prevOwner ?? []);\n});\n\nconst accountIdsWithRootIds = accountIds.map(accountId => ({\n accountId,\n rootUnitId: txLog.rootUnitId,\n}));\n\nmsg.payload.sqlite.then(async db => {\n if (accountIdsWithRootIds.length === 0) {\n node.send({\n ...msg,\n payload: {\n ...msg.payload,\n ledger: [],\n }\n });\n\n return;\n }\n \n const result = await db.selectFrom('ledger')\n .selectAll()\n .where(({ eb, refTuple, tuple }) => eb(\n refTuple('account_id', 'root_unit_id'),\n 'in',\n accountIdsWithRootIds.flatMap((accountWithUnit) => {\n return tuple(accountWithUnit.accountId, accountWithUnit.rootUnitId)\n })\n ))\n .execute()\n \n node.send({\n ...msg,\n payload: {\n ...msg.payload,\n ledger: result.map(r => ({\n accountId: r.account_id,\n rootUnitId: r.root_unit_id,\n volume: r.volume\n }))\n }\n })\n}).catch(node.error)\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 2630, "y": 160, "wires": [ [ "98a4076a7346f77b" ] ] }, { "id": "6b974a17d0bf6e5a", "type": "catch", "z": "8ee174a8a298654a", "name": "", "scope": null, "uncaught": false, "x": 100, "y": 160, "wires": [ [ "75e8cba180af61bb" ] ] }, { "id": "6d927d07bd0f86c8", "type": "comment", "z": "8ee174a8a298654a", "name": "No protocol (old message format)", "info": "", "x": 1090, "y": 220, "wires": [] }, { "id": "42fe81814bb0c41b", "type": "link out", "z": "8ee174a8a298654a", "name": "No protocol flow (OUT)", "mode": "link", "links": [ "58dda468b6e90876" ], "x": 935, "y": 220, "wires": [] }, { "id": "58dda468b6e90876", "type": "link in", "z": "8ee174a8a298654a", "g": "35ba395e82c0b1fd", "name": "No protocol flow (IN)", "links": [ "42fe81814bb0c41b" ], "x": 115, "y": 440, "wires": [ [ "be6ed27e6faa08cc" ] ] }, { "id": "ca32fbaad3ee3347", "type": "function", "z": "8ee174a8a298654a", "g": "9318cc15f9afd75f", "name": "Update ledger", "func": "const txLog = msg.payload.payload.txLog;\nconst ledger = msg.payload.ledger;\n\nconst rootUnitId = txLog.rootUnitId;\nconst volumeMap = ledger.reduce((volumeMap, entry) => {\n const key = `${entry.accountId}.${entry.rootUnitId}`;\n const value = entry.volume;\n\n volumeMap[key] ||= value;\n\n return volumeMap;\n}, {});\n\nfor (const change of txLog.changes) {\n if (change.prevOwner) {\n const prevOwnerVolume = volumeMap[`${change.prevOwner}.${rootUnitId}`];\n\n if (prevOwnerVolume === undefined) {\n throw new Error(`Prev owner not found: prevOwnerId=${change.prevOwner}, rootUnitId=${rootUnitId}`);\n }\n\n volumeMap[`${change.prevOwner}.${rootUnitId}`] -= change.volume;\n }\n\n volumeMap[`${change.owner}.${rootUnitId}`] ||= 0;\n volumeMap[`${change.owner}.${rootUnitId}`] += change.volume;\n}\n\nconst ledgerUpdate = Object.entries(volumeMap).map(([key, volume]) => {\n const [accountId, rootUnitId] = key.split('.');\n\n return {\n accountId,\n rootUnitId,\n volume,\n };\n});\n\nif (ledgerUpdate.length === 0) {\n return;\n}\n\nmsg.payload.sqlite.then(async db => {\n await db\n .insertInto('ledger')\n .values(ledgerUpdate.map(e => ({\n account_id: e.accountId,\n root_unit_id: e.rootUnitId,\n volume: e.volume\n })))\n .onConflict(c => c.columns(['account_id', 'root_unit_id']).doUpdateSet((eb) => ({\n volume: eb.ref('excluded.volume'),\n })))\n .execute()\n\n node.send(msg);\n}).catch(node.error);", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 2720, "y": 260, "wires": [ [ "d4403c151c10fd53" ] ] }, { "id": "75e8cba180af61bb", "type": "function", "z": "8ee174a8a298654a", "name": "Log error", "func": "node.error(`[${msg.error.source.type}:${msg.error.source.name}] ${msg.error.message}`);\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 260, "y": 160, "wires": [ [ "cbe7827e8b1ce3bd" ] ] }, { "id": "be6ed27e6faa08cc", "type": "function", "z": "8ee174a8a298654a", "g": "35ba395e82c0b1fd", "name": "No \"type\" compatibility", "func": "return {\n ...msg,\n payload: {\n ...msg.payload,\n type: 'unitsChanged'\n }\n}", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 260, "y": 440, "wires": [ [ "126dbca226762933" ] ] }, { "id": "e9eb48b870d28564", "type": "function", "z": "8ee174a8a298654a", "g": "9318cc15f9afd75f", "name": "Filter processed", "func": "const txLog = msg.payload.payload.txLog;\n\nmsg.payload.sqlite.then(async db => {\n const unitIds = txLog.changes.map(c => c.unitId);\n\n if (unitIds.length === 0) {\n node.send(msg);\n return;\n }\n\n const existingUnits = await db\n .selectFrom('processed')\n .select('unit_id')\n .where('unit_id', 'in', unitIds)\n .execute()\n .then(result => result.map(row => row.unit_id));\n\n const existingUnitsSet = new Set(existingUnits);\n const notExistingUnits = unitIds.filter(unitId => !existingUnitsSet.has(unitId));\n\n if (existingUnits.length !== 0) {\n if (existingUnits.length === unitIds.length) {\n node.log(`Filtered ALL changes (unit ids: ${existingUnits.join(', ')})`);\n } else {\n node.log(`Filtered some changes (unit ids ${existingUnits.join(', ')})`);\n }\n }\n\n node.send({\n ...msg,\n payload: {\n ...msg.payload,\n payload: {\n ...msg.payload.payload,\n txLog: {\n ...txLog,\n changes: txLog.changes.filter(change => notExistingUnits.includes(change.unitId))\n }\n }\n }\n })\n}).catch(node.error);\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 2360, "y": 160, "wires": [ [ "b8d668ba7a1a03ed" ] ] }, { "id": "d4403c151c10fd53", "type": "function", "z": "8ee174a8a298654a", "g": "9318cc15f9afd75f", "name": "Update processed", "func": "const txLog = msg.payload.payload.txLog;\n\nmsg.payload.sqlite.then(async db => {\n const changes = txLog.changes;\n\n if (changes.length === 0) {\n node.send(msg);\n return;\n }\n\n await db\n .insertInto('processed')\n .values(changes.map(c => ({\n owner: c.owner,\n prev_owner: c.prevOwner,\n root_unit_id: txLog.rootUnitId,\n unit_id: c.unitId,\n volume: c.volume,\n created_at: Date.now(),\n })))\n .execute();\n\n node.send(msg);\n}).catch(node.error);\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 2930, "y": 260, "wires": [ [ "e1f6f9ae2e6910d1" ] ] }, { "id": "98a4076a7346f77b", "type": "function", "z": "8ee174a8a298654a", "g": "9318cc15f9afd75f", "name": "Transaction validator", "func": "const VOTE_APPROVE = 'approve';\nconst VOTE_REJECT = 'reject';\n\nconst txLog = msg.payload.payload.txLog;\nconst ledger = msg.payload.ledger;\n\nconst rootUnitId = txLog.rootUnitId;\nconst changes = txLog.changes;\nconst volumeMap = ledger.reduce((volumeMap, entry) => {\n const key = `${entry.accountId}.${entry.rootUnitId}`;\n const value = entry.volume;\n\n volumeMap[key] ||= value;\n\n return volumeMap;\n}, {});\n\nfor (const change of changes) {\n // Ensure entries to be modified exist\n if (change.prevOwner) {\n volumeMap[`${change.prevOwner}.${rootUnitId}`] ||= 0;\n }\n volumeMap[`${change.owner}.${rootUnitId}`] ||= 0;\n\n if (change.prevOwner) {\n const ownedVolume = volumeMap[`${change.prevOwner}.${rootUnitId}`] ?? 0;\n\n if (ownedVolume - change.volume < 0) {\n return [\n undefined,\n {\n ...msg,\n payload: {\n ...msg.payload,\n txFailedReason: `${change.prevOwner} has ${ownedVolume} volume in unit ${rootUnitId}, but ${change.volume} is required`,\n voting: txLog.changes.map(v => ({\n vote: VOTE_REJECT,\n votingId: v.votingId,\n }))\n }\n }\n ];\n\n }\n }\n\n if (change.prevOwner) {\n volumeMap[`${change.prevOwner}.${rootUnitId}`] -= change.volume;\n }\n\n volumeMap[`${change.owner}.${rootUnitId}`] += change.volume;\n}\n\n\nreturn [\n {\n ...msg,\n payload: {\n ...msg.payload,\n voting: txLog.changes.map(v => ({\n vote: VOTE_APPROVE,\n votingId: v.votingId,\n }))\n }\n },\n undefined\n]\n", "outputs": 2, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 2220, "y": 280, "wires": [ [ "ac42d7e5c6032791" ], [ "65d289142a86384f" ] ] }, { "id": "90f75c63b182bae9", "type": "function", "z": "8ee174a8a298654a", "name": "Unknown protocol version", "func": "throw new Error(`Protocol version ${msg.payload.protocolVersion} not supported`);\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1070, "y": 260, "wires": [ [] ] }, { "id": "a487a21a1443f6b7", "type": "link out", "z": "8ee174a8a298654a", "name": "Protocol vresion 1 (OUT)", "mode": "link", "links": [ "fb43395da3eb18ac" ], "x": 935, "y": 180, "wires": [] }, { "id": "fb43395da3eb18ac", "type": "link in", "z": "8ee174a8a298654a", "g": "04c09f0c2cd6afcc", "name": "Protocol version 1 (IN)", "links": [ "a487a21a1443f6b7" ], "x": 115, "y": 620, "wires": [ [ "746c2dd32cd8fa7f" ] ] }, { "id": "8cf263f8817fe694", "type": "comment", "z": "8ee174a8a298654a", "name": "Protocol version 1", "info": "", "x": 1050, "y": 180, "wires": [] }, { "id": "746c2dd32cd8fa7f", "type": "json-schema-validator", "z": "8ee174a8a298654a", "g": "04c09f0c2cd6afcc", "name": "Protocol validator", "jsonSchema": "{\n \"type\": \"object\",\n \"required\": [\"protocolVersion\", \"type\", \"version\", \"payload\"],\n \"properties\": {\n \"protocolVersion\": { \"const\": 1 },\n \"type\": { \"type\": \"string\" },\n \"version\": { \"type\": \"number\" },\n \"payload\": {}\n }\n}", "x": 290, "y": 620, "wires": [ [ "af129b9f346b5e93" ] ] }, { "id": "af129b9f346b5e93", "type": "switch", "z": "8ee174a8a298654a", "g": "04c09f0c2cd6afcc", "name": "Message type check", "property": "payload.type", "propertyType": "msg", "rules": [ { "t": "eq", "v": "unitsChanged", "vt": "str" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 2, "x": 520, "y": 620, "wires": [ [ "a986536a19a878b8" ], [ "0bdc014d7d7e836a" ] ] }, { "id": "0bdc014d7d7e836a", "type": "function", "z": "8ee174a8a298654a", "g": "04c09f0c2cd6afcc", "name": "Unknown message type", "func": "throw new Error(`Message type ${msg.payload.type} not supported`);\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 770, "y": 640, "wires": [ [] ] }, { "id": "04570ad1292f1568", "type": "link in", "z": "8ee174a8a298654a", "name": "unitsChanged (IN)", "links": [ "a986536a19a878b8" ], "x": 1335, "y": 200, "wires": [ [ "7f0c15b61ca09b2f" ] ] }, { "id": "a986536a19a878b8", "type": "link out", "z": "8ee174a8a298654a", "g": "04c09f0c2cd6afcc", "name": "unitsChanged (OUT)", "mode": "link", "links": [ "04570ad1292f1568" ], "x": 675, "y": 600, "wires": [] }, { "id": "4d6a02a638fceb91", "type": "comment", "z": "8ee174a8a298654a", "g": "04c09f0c2cd6afcc", "name": "unitsChanged", "info": "", "x": 770, "y": 600, "wires": [] }, { "id": "a7187e080554af45", "type": "comment", "z": "8ee174a8a298654a", "name": "unitsChanged", "info": "", "x": 1390, "y": 160, "wires": [] }, { "id": "56fd2cd0cc0812f8", "type": "function", "z": "8ee174a8a298654a", "g": "35ba395e82c0b1fd", "name": "Convert old message to new one", "func": "const { txLog, type, ...payload } = msg.payload;\n\nreturn {\n ...msg,\n payload: {\n ...payload,\n protocolVersion: 1,\n type: 'unitsChanged',\n version: 1,\n payload: {\n txLog: {\n ...txLog,\n changes: txLog.changes.map(change => ({\n ...change,\n votingId: change.unitId\n }))\n }\n }\n }\n}", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 800, "y": 440, "wires": [ [ "746c2dd32cd8fa7f" ] ] }, { "id": "7f0c15b61ca09b2f", "type": "switch", "z": "8ee174a8a298654a", "name": "unitsChanged version check", "property": "payload.version", "propertyType": "msg", "rules": [ { "t": "eq", "v": "1", "vt": "num" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 2, "x": 1500, "y": 200, "wires": [ [ "7a9d1c1446786bd7" ], [ "6e4d452be82e6fee" ] ] }, { "id": "6e4d452be82e6fee", "type": "function", "z": "8ee174a8a298654a", "name": "Unknown message version", "func": "throw new Error(`Message type \"unitsChanged\" in version ${msg.payload.version} is not supported`);\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1780, "y": 240, "wires": [ [] ] }, { "id": "7a9d1c1446786bd7", "type": "json-schema-validator", "z": "8ee174a8a298654a", "name": "Validate unitsChanged v1 payload", "jsonSchema": "{\n \"type\": \"object\",\n \"required\": [\"payload\"],\n \"properties\": {\n \"payload\": {\n \"type\": \"object\",\n \"required\": [\"txLog\"],\n \"properties\": {\n \"txLog\": {\n \"type\": \"object\",\n \"required\": [\"rootUnitId\", \"changes\"],\n \"properties\": {\n \"rootUnitId\": { \"type\": \"string\" },\n \"changes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\"unitId\", \"volume\", \"owner\", \"prevOwner\", \"votingId\"],\n \"properties\": {\n \"unitId\": { \"type\": \"string\" },\n \"votingId\": { \"type\": \"string\" },\n \"volume\": { \"type\": \"number\" },\n \"owner\": { \"type\": \"string\" },\n \"prevOwner\": { \"type\": [\"string\", \"null\"] }\n }\n }\n }\n }\n }\n }\n }\n }\n}", "x": 1800, "y": 160, "wires": [ [ "6ca11a0d57bdc6cc" ] ] } ]