From c41ff32b29e8fa28fd1c880af3a3ff2f37786571 Mon Sep 17 00:00:00 2001 From: Krzysztof Szostak Date: Tue, 18 Feb 2025 09:13:57 +0100 Subject: [PATCH] feat: add nomination --- .gitignore | 3 +- .npmignore | 11 +- consensus.html | 23 ++ consensus.js | 233 ++++++++++++++++ energywebx-config.html | 21 +- energywebx-config.js | 66 ++++- icons/nomination.svg | 11 + is-nominated.html | 23 ++ is-nominated.js | 63 +++++ package.json | 18 +- rollup.config.editor.mjs | 79 ------ .../consensus/consensus.html/editor.html | 3 - src/nodes/consensus/consensus.html/help.html | 3 - src/nodes/consensus/consensus.html/index.ts | 21 -- .../consensus/consensus.html/modules/types.ts | 5 - src/nodes/consensus/consensus.ts | 263 ------------------ src/nodes/consensus/icons/consensus.svg | 25 -- src/nodes/consensus/modules/types.ts | 11 - src/nodes/consensus/sample-flow.json | 102 ------- src/nodes/dummy.spec.ts | 5 - .../energywebx-config.html/editor.html | 16 -- .../energywebx-config.html/help.html | 3 - .../energywebx-config.html/index.ts | 27 -- .../energywebx-config.html/modules/types.ts | 6 - .../energywebx-config/energywebx-config.ts | 45 --- .../icons/energywebx-logo.png | Bin 16150 -> 0 bytes src/nodes/energywebx-config/modules/types.ts | 15 - submit-solution.html | 2 +- tsconfig.json | 35 --- tsconfig.runtime.json | 5 - tsconfig.runtime.watch.json | 7 - 31 files changed, 435 insertions(+), 715 deletions(-) create mode 100644 consensus.html create mode 100644 consensus.js create mode 100644 icons/nomination.svg create mode 100644 is-nominated.html create mode 100644 is-nominated.js delete mode 100644 rollup.config.editor.mjs delete mode 100644 src/nodes/consensus/consensus.html/editor.html delete mode 100644 src/nodes/consensus/consensus.html/help.html delete mode 100644 src/nodes/consensus/consensus.html/index.ts delete mode 100644 src/nodes/consensus/consensus.html/modules/types.ts delete mode 100644 src/nodes/consensus/consensus.ts delete mode 100644 src/nodes/consensus/icons/consensus.svg delete mode 100644 src/nodes/consensus/modules/types.ts delete mode 100644 src/nodes/consensus/sample-flow.json delete mode 100644 src/nodes/dummy.spec.ts delete mode 100644 src/nodes/energywebx-config/energywebx-config.html/editor.html delete mode 100644 src/nodes/energywebx-config/energywebx-config.html/help.html delete mode 100644 src/nodes/energywebx-config/energywebx-config.html/index.ts delete mode 100644 src/nodes/energywebx-config/energywebx-config.html/modules/types.ts delete mode 100644 src/nodes/energywebx-config/energywebx-config.ts delete mode 100644 src/nodes/energywebx-config/icons/energywebx-logo.png delete mode 100644 src/nodes/energywebx-config/modules/types.ts delete mode 100644 tsconfig.json delete mode 100644 tsconfig.runtime.json delete mode 100644 tsconfig.runtime.watch.json diff --git a/.gitignore b/.gitignore index 666af41..d3e5076 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ yarn-debug.log* yarn-error.log* node_modules/ *.tsbuildinfo -dist \ No newline at end of file +dist +*.tgz \ No newline at end of file diff --git a/.npmignore b/.npmignore index c6c53ce..907c6a3 100644 --- a/.npmignore +++ b/.npmignore @@ -5,4 +5,13 @@ !package.json !yarn.lock !submit-solution.js -!submit-solution.html \ No newline at end of file +!submit-solution.html +!energywebx-config.js +!energywebx-config.html +!is-nominated.html +!is-nominated.js +!consensus.html +!consensus.js +!icons/consensus.svg +!icons/nomination.svg +!icons/submit-result.svg \ No newline at end of file diff --git a/consensus.html b/consensus.html new file mode 100644 index 0000000..b06611d --- /dev/null +++ b/consensus.html @@ -0,0 +1,23 @@ + + + \ No newline at end of file diff --git a/consensus.js b/consensus.js new file mode 100644 index 0000000..0603280 --- /dev/null +++ b/consensus.js @@ -0,0 +1,233 @@ +module.exports = function (RED) { + const axios = require('axios'); + + const ConsensusStatus = { + NOT_ENOUGH_VOTES: 'NOT_ENOUGH_VOTES', + REACHED: 'REACHED', + UNABLE_TO_REACH_CONSENSUS: 'UNABLE_TO_REACH_CONSENSUS', + FAILED: 'FAILED' + }; + + const gqlQuery = ` + query GetSubmittedResults($solutionNamespace: String!, $votingRoundId: String!, $solutionGroupId: String!, $limit: Int!, $offset: Int!) { + solutionResultSubmitteds(where: {solution: {id_eq: $solutionNamespace}votingRoundId_eq: $votingRoundId, successful_eq: true}, orderBy: blockNumber_ASC, limit: $limit, offset: $offset) { + result + worker { + id + } + } + operatorSubscribedSolutionGroups(where: {solutionGroup: {id_eq: $solutionGroupId}}, limit: $limit, offset: $offset) { + operator { + id + mappings { + worker { + id + } + } + } + } +} + `; + const getKeyWithHighestNumber = (obj) => + + Object.keys(obj).reduce((a, b) => (obj[a] > obj[b] ? a : b)); + + function NodeConstructor(config) { + this.ewxConfig = RED.nodes.getNode(config.ewxConfig); + + RED.nodes.createNode(this, config); + + var node = this; + + node.on('input', async function (msg, send, done) { + if (!msg.payload.votingRoundId) { + this.status({fill: "red", shape: "dot", text: "votingRoundId is missing"}); + + node.error("votingRoundId is missing"); + + return; + } + + let electedLeader = null; + + let page = 0; + const limit = 50; + + const resultCounts = {}; + const operatorsMapping = {}; + const submittedResults = []; + + while (true) { + const response = await axios.post(node.ewxConfig.subsquidUrl, { + query: gqlQuery, + variables: { + votingRoundId: msg.payload.votingRoundId, + solutionGroupId: node.ewxConfig.solutionGroupId, + solutionNamespace: node.ewxConfig.solutionNamespace, + limit, + offset: (page * limit) + } + }).catch((e) => { + console.error(`failed during fetching data, solution: ${node.ewxConfig.solutionNamespace}`, e, e.response?.data); + this.status({fill: "red", shape: "dot", text: "failed to query data"}); + + return null; + }); + + if (response === null) { + send({ + payload: { + leaderAddress: null, + consensusStatus: ConsensusStatus.FAILED, + attempt: msg.payload.attempt ? msg.payload.attempt + 1 : 1, + shouldRetry: true, + result: null + } + }); + + done(); + + return; + } + + const {data} = response; + + const {solutionResultSubmitteds, operatorSubscribedSolutionGroups} = data.data; + + if (solutionResultSubmitteds.length === 0 && operatorSubscribedSolutionGroups.length === 0) { + break; + } + + submittedResults.push(...solutionResultSubmitteds); + + for (const {operator} of operatorSubscribedSolutionGroups) { + if (operatorsMapping[operator.id]) { + continue; + } + + const mappings = operator.mappings; + + if (mappings.length === 0) { + continue; + } + + operatorsMapping[operator.id] = mappings[0].worker.id; + } + + page++; + } + + this.log(`votingRoundId = ${msg.payload.votingRoundId} - finished fetching consensus data`); + + const applicableOperatorsCount = Object.entries(operatorsMapping).length; + + if (applicableOperatorsCount < 3) { + this.log(`votingRoundId = ${msg.payload.votingRoundId} - not enough operators for consensus`); + this.status({fill: "red", shape: "dot", text: "not enough operators"}); + + send({ + payload: { + leaderAddress: null, + consensusStatus: ConsensusStatus.FAILED, + attempt: msg.payload.attempt ? msg.payload.attempt + 1 : 1, + shouldRetry: false, + result: null + } + }); + + return done(); + } + + const hasAnyVotes = submittedResults.length > 0; + + if (!hasAnyVotes) { + this.log(`votingRoundId = ${msg.payload.votingRoundId} - not enough votes for consensus`); + + this.status({fill: "yellow", shape: "dot", text: "not enough votes"}); + + send({ + payload: { + leaderAddress: null, + consensusStatus: ConsensusStatus.NOT_ENOUGH_VOTES, + attempt: msg.payload.attempt ? msg.payload.attempt + 1 : 1, + shouldRetry: true, + result: null + } + }); + + return done(); + } + + + const minVotesRequired = applicableOperatorsCount / 2 + 0.5; + + for (const {result, worker} of submittedResults) { + resultCounts[result] = (resultCounts[result] || 0) + 1; + + if (resultCounts[result] >= minVotesRequired && electedLeader == null) { + electedLeader = worker.id; + } + } + + if (electedLeader) { + this.log(`votingRoundId = ${msg.payload.votingRoundId} - reached consensus`); + + this.status({fill: "green", shape: "dot", text: "reached"}); + const resultHash = getKeyWithHighestNumber(resultCounts); + + send({ + payload: { + leaderAddress: electedLeader, + consensusStatus: ConsensusStatus.REACHED, + attempt: msg.payload.attempt ? msg.payload.attempt + 1 : 1, + shouldRetry: false, + resultHash + } + }); + + return done(); + } + + const highestVote = resultCounts[getKeyWithHighestNumber(resultCounts)]; + + const remainingVotes = applicableOperatorsCount - submittedResults.length; + + const canStillReachConsensus = highestVote + remainingVotes >= minVotesRequired; + + if (!canStillReachConsensus) { + this.status({fill: "red", shape: "dot", text: "unable to reach"}); + this.log(`votingRoundId = ${msg.payload.votingRoundId} - unable to reach consensus`); + + send({ + payload: { + leaderAddress: null, + consensusStatus: ConsensusStatus.UNABLE_TO_REACH_CONSENSUS, + attempt: msg.payload.attempt ? msg.payload.attempt + 1 : 1, + shouldRetry: false, + result: null + } + }); + + return done(); + } else { + this.status({fill: "yellow", shape: "dot", text: "not enough votes"}); + this.log(`votingRoundId = ${msg.payload.votingRoundId} - not enough votes`); + + + send({ + payload: { + leaderAddress: null, + consensusStatus: ConsensusStatus.NOT_ENOUGH_VOTES, + attempt: msg.payload.attempt ? msg.payload.attempt + 1 : 1, + shouldRetry: true, + result: null + } + }); + + return done(); + } + }); + } + + RED.nodes.registerType("consensus", NodeConstructor); +} \ No newline at end of file diff --git a/energywebx-config.html b/energywebx-config.html index 90c4f10..c0c21f0 100644 --- a/energywebx-config.html +++ b/energywebx-config.html @@ -1,16 +1,21 @@ + + \ No newline at end of file diff --git a/energywebx-config.js b/energywebx-config.js index c238b46..7841a0c 100644 --- a/energywebx-config.js +++ b/energywebx-config.js @@ -1,20 +1,58 @@ -module.exports = function(RED) { - +const polkadot = require('@polkadot/api'); + +const URLS = { + PEX: 'https://public-rpc.testnet.energywebx.com', + MAINNET: 'https://public-rpc.mainnet.energywebx.com' +}; + +const matchRpcToSubsquid = (rpcUrl) => { + if (rpcUrl === URLS.PEX) { + return 'https://ewx-subsquid-dev.energyweb.org/graphql' + } else if (rpcUrl === URLS.MAINNET) { + return 'https://ewx-indexer.mainnet.energywebx.com/graphql'; + } + + return process.env.__EWX_SUBSQUID_URL; +} + + +module.exports = function (RED) { function EnergyWebXConfigNode(config) { RED.nodes.createNode(this, config); - this.solutionNamespace = config.solutionNamespace; - - if (this.networkName === 'rex'){ - this.rpcUrl = 'https://rex-rpc.energywebx.org/'; - this.socketUrl = 'https://rex-rpc.energywebx.org/ws'; - this.explorerUrl = 'https://rex-explorer.energywebx.org/api'; - } else if (this.networkName === 'ewx') { - this.rpcUrl = 'https://rpc.energywebx.org/'; - this.socketUrl = 'https://rpc.energywebx.org/ws'; - this.explorerUrl = 'https://explorer.energywebx.org/api'; - } + + const ewxRemoteConfig = config.__envConfig; + + this.workerUrl = 'http://localhost:3002'; + + this.workerAddress = ewxRemoteConfig.EWX_WORKER_ADDRESS; + this.solutionNamespace = ewxRemoteConfig.EWX_SOLUTION_ID; + this.solutionGroupId = ewxRemoteConfig.EWX_SOLUTION_GROUP_ID; + this.rpcUrl = ewxRemoteConfig.EWX_RPC_URL; + this.subsquidUrl = matchRpcToSubsquid(this.rpcUrl); + + this.log(`worker address = ${this.workerAddress}, solution namespace = ${this.solutionNamespace}, solution group id = ${this.solutionGroupId}, rpc url = ${this.rpcUrl}, subsquid url = ${this.subsquidUrl}`) + + const provider = new polkadot.HttpProvider(this.rpcUrl); + + const api = new polkadot.ApiPromise({ + provider, + throwOnUnknown: true, + throwOnConnect: true, + }); + + api.connect() + .then(() => { + this.log(`connected to ${this.rpcUrl}`); + + this.status({fill: "green", shape: "dot", text: "connected"}); + }) + .catch((e) => { + this.log(e); + + this.status({fill: "red", shape: "ring", text: "disconnected"}); + }) } - + RED.nodes.registerType("energywebx-config", EnergyWebXConfigNode); } \ No newline at end of file diff --git a/icons/nomination.svg b/icons/nomination.svg new file mode 100644 index 0000000..5c266cc --- /dev/null +++ b/icons/nomination.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/is-nominated.html b/is-nominated.html new file mode 100644 index 0000000..d8846f9 --- /dev/null +++ b/is-nominated.html @@ -0,0 +1,23 @@ + + + \ No newline at end of file diff --git a/is-nominated.js b/is-nominated.js new file mode 100644 index 0000000..37704fd --- /dev/null +++ b/is-nominated.js @@ -0,0 +1,63 @@ +module.exports = function (RED) { + const axios = require('axios'); + + function NodeConstructor(config) { + this.ewxConfig = RED.nodes.getNode(config.ewxConfig); + + RED.nodes.createNode(this, config); + + var node = this; + + node.on('input', function (msg, send, done) { + const requestPayload = { + query: `query IsWorkerNominated($workerAddress: String!, $solutionNamespace: String!) { + nominatedWorkersMappings(where:{ worker: {id_eq: $workerAddress}, solution:{ id_eq: $solutionNamespace}}) { + id + } + }`, + variables: { + workerAddress: node.ewxConfig.workerAddress, + solutionNamespace: node.ewxConfig.solutionNamespace + } + }; + + axios.post(node.ewxConfig.subsquidUrl, requestPayload) + .then((response) => { + const {nominatedWorkersMappings} = response.data.data; + + if (nominatedWorkersMappings.length > 0) { + this.status({fill: "green", shape: "dot", text: "nominated"}); + this.log(`workerAddress = ${node.ewxConfig.workerAddress} - worker is nominated`); + + send({ + payload: { + isNominated: true, + } + }); + } else { + this.status({fill: "yellow", shape: "dot", text: "not nominated"}); + this.log(`workerAddress = ${node.ewxConfig.workerAddress} - worker is not nominated`); + + send({ + payload: { + isNominated: false, + } + }); + } + }) + .catch((e) => { + console.error(`failed to obtain nominations for solution = ${node.ewxConfig.solutionNamespace}`, e, e.response?.data); + + this.status({fill: "red", shape: "ring", text: "failed to obtain data"}); + + send({ + payload: { + isNominated: null, + } + }); + }) + }); + } + + RED.nodes.registerType("is-nominated", NodeConstructor); +} \ No newline at end of file diff --git a/package.json b/package.json index 23ea6b8..f4f1f74 100644 --- a/package.json +++ b/package.json @@ -9,22 +9,12 @@ "node-red": { "nodes": { "submit-solution": "./submit-solution.js", - "consensus": "dist/nodes/consensus/consensus.js" + "energywebx-config": "./energywebx-config.js", + "is-nominated": "./is-nominated.js", + "consensus": "./consensus.js" } }, - "scripts": { - "copy": "copyfiles -u 2 \"./src/nodes/**/*.{png,svg}\" \"./dist/nodes/\"", - "build:editor": "rollup -c rollup.config.editor.mjs", - "build:editor:watch": "rollup -c rollup.config.editor.mjs -w", - "build:runtime": "tsc -p tsconfig.runtime.json", - "build:runtime:watch": "tsc -p tsconfig.runtime.watch.json --watch --preserveWatchOutput", - "build": "rm -rf dist && yarn copy && npm run build:editor && npm run build:runtime", - "test": "jest --forceExit --detectOpenHandles --colors", - "test:watch": "jest --forceExit --detectOpenHandles --watchAll", - "dev": "rm -rf dist && yarn copy && concurrently --kill-others --names 'COPY,EDITOR,RUNTIME,TEST' --prefix '({name})' --prefix-colors 'yellow.bold,cyan.bold,greenBright.bold,magenta.bold' 'onchange -v \"src/**/*.png\" \"src/**/*.svg\" -- yarn copy' 'yarn build:editor:watch' 'yarn build:runtime:watch' 'sleep 10; yarn test:watch'", - "lint": "prettier --ignore-path .eslintignore --check '**/*.{js,ts,md}'; eslint --ext .js,.ts .", - "lint:fix": "prettier --ignore-path .eslintignore --write '**/*.{js,ts,md}'; eslint --ext .js,.ts . --fix" - }, + "scripts": {}, "keywords": [ "SmartFlow", "Energy", diff --git a/rollup.config.editor.mjs b/rollup.config.editor.mjs deleted file mode 100644 index af9153d..0000000 --- a/rollup.config.editor.mjs +++ /dev/null @@ -1,79 +0,0 @@ -import fs from "fs"; -import glob from "glob"; -import path from "path"; -import typescript from "@rollup/plugin-typescript"; -import { createRequire } from 'node:module' - -const loadJSON = (path) => JSON.parse(fs.readFileSync(new URL(path, import.meta.url))); -const packageJson = loadJSON("./package.json"); -const allNodeRedPackageTypes = Object.keys(packageJson["node-red"].nodes); - -const excludedNodes = [ - "submit-solution" -]; - -const allNodeTypes = allNodeRedPackageTypes.filter(x => !excludedNodes.includes(x)); - -const htmlWatch = () => { - return { - name: "htmlWatch", - load(id) { - const editorDir = path.dirname(id); - const htmlFiles = glob.sync(path.join(editorDir, "*.html")); - htmlFiles.map((file) => this.addWatchFile(file)); - }, - }; -}; - -const htmlBundle = () => { - return { - name: "htmlBundle", - renderChunk(code, chunk, _options) { - const editorDir = path.dirname(chunk.facadeModuleId); - const htmlFiles = glob.sync(path.join(editorDir, "*.html")); - const htmlContents = htmlFiles.map((fPath) => fs.readFileSync(fPath)); - - code = - '\n" + - htmlContents.join("\n"); - - return { - code, - map: { mappings: "" }, - }; - }, - }; -}; - -const makePlugins = (nodeType) => [ - htmlWatch(), - typescript({ - lib: ["es5", "es6", "dom"], - include: [ - `src/nodes/${nodeType}/${nodeType}.html/**/*.ts`, - `src/nodes/${nodeType}/shared/**/*.ts`, - "src/nodes/shared/**/*.ts", - ], - target: "es5", - tsconfig: false, - noEmitOnError: process.env.ROLLUP_WATCH ? false : true, - }), - htmlBundle(), -]; - -const makeConfigItem = (nodeType) => ({ - input: `src/nodes/${nodeType}/${nodeType}.html/index.ts`, - output: { - file: `dist/nodes/${nodeType}/${nodeType}.html`, - format: "iife", - }, - plugins: makePlugins(nodeType), - watch: { - clearScreen: false, - }, -}); - -export default allNodeTypes.map((nodeType) => makeConfigItem(nodeType)); \ No newline at end of file diff --git a/src/nodes/consensus/consensus.html/editor.html b/src/nodes/consensus/consensus.html/editor.html deleted file mode 100644 index 857b4c9..0000000 --- a/src/nodes/consensus/consensus.html/editor.html +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/src/nodes/consensus/consensus.html/help.html b/src/nodes/consensus/consensus.html/help.html deleted file mode 100644 index 19a04e3..0000000 --- a/src/nodes/consensus/consensus.html/help.html +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/src/nodes/consensus/consensus.html/index.ts b/src/nodes/consensus/consensus.html/index.ts deleted file mode 100644 index e1b01a3..0000000 --- a/src/nodes/consensus/consensus.html/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {EditorRED} from "node-red"; -import {ConsensusNodeProperties} from "./modules/types"; - -declare const RED: EditorRED; - -RED.nodes.registerType("consensus-node", { - category: "Energy Web X", - color: "#B8FBF4", - defaults: {}, - inputs: 1, - outputs: 1, - icon: "consensus.svg", - paletteLabel: "Consensus Verification", - label: function () { - if (this.name) { - return this.name; - } - - return "Consensus Verification"; - }, -}); \ No newline at end of file diff --git a/src/nodes/consensus/consensus.html/modules/types.ts b/src/nodes/consensus/consensus.html/modules/types.ts deleted file mode 100644 index 00609e9..0000000 --- a/src/nodes/consensus/consensus.html/modules/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -import {EditorNodeProperties} from "node-red"; - -export interface ConsensusNodeProperties - extends EditorNodeProperties { -} \ No newline at end of file diff --git a/src/nodes/consensus/consensus.ts b/src/nodes/consensus/consensus.ts deleted file mode 100644 index fb2ab97..0000000 --- a/src/nodes/consensus/consensus.ts +++ /dev/null @@ -1,263 +0,0 @@ -import {NodeInitializer} from "node-red"; -import {SubmitSolutionResultNode, SubmitSolutionResultNodeDef} from "./modules/types"; - -import {ApiPromise, HttpProvider} from '@polkadot/api'; - -interface SubmittedResult { - votingRoundId: string; - solutionNamespace: string; - address: string; - resultHash: string; -} - -const getRegisteredOperatorsCount = async ( - api: ApiPromise, - solutionGroupId: string -): Promise => { - const stakeRecords = - await api.query.workerNodePallet.solutionGroupStakeRecords.entries( - solutionGroupId - ); - - const transformedRecords = stakeRecords - .map(([addr, v]) => [addr.toHuman(), v.toHuman()]) - .map((x) => { - if (x.length !== 2) { - return null; - } - - const [addr, v]: [[string, string], { [key: string]: string }] = x as [[string, string], { - [key: string]: string - }]; - - return addr[1]; - }) - .filter(x => !!x); - - const operatorsRecords = await Promise.all(transformedRecords.map(async (x: any) => { - const encodedAccount = api.registry.createType('AccountId', x); - - const operator = await api.query.workerNodePallet.workerNodeToOperator(encodedAccount); - - const humanizedOperator = operator.toHuman() as string | null; - - return humanizedOperator; - })) - - const operators = operatorsRecords.filter(x => !!x).length; - - console.log('solution group operators', operators); - - return operators; -}; -const getSubmittedResults = async ( - api: ApiPromise, - solutionNamespace: string, - votingRoundId: string -): Promise => { - const submittedResults = - await api.query.workerNodePallet.solutionResults.entries( - solutionNamespace, - votingRoundId - ); - - return submittedResults.map(([c, k]) => { - const [solutionNamespace, votingRoundId, address]: [ - string, - string, - string - ] = c.toHuman() as [string, string, string]; - const resultHash: string = k.toHuman() as string; - - return { - address, - resultHash, - solutionNamespace, - votingRoundId, - }; - }); -}; - -enum ConsensusStatus { - REACHED = 'REACHED', - NOT_ENOUGH_VOTES = 'NOT_ENOUGH_VOTES', - UNABLE_TO_REACH_CONSENSUS = 'UNABLE_TO_REACH_CONSENSUS', - FAILED = 'FAILED' -} - -interface ConsensusReached { - consensusStatus: ConsensusStatus.REACHED; - leaderAddress: string; -} - -interface ConsensusNotReached { - leaderAddress: null; - consensusStatus: - | ConsensusStatus.UNABLE_TO_REACH_CONSENSUS - | ConsensusStatus.NOT_ENOUGH_VOTES - | ConsensusStatus.FAILED; -} - -type ConsesusResult = ConsensusReached | ConsensusNotReached; - -const leaderElection = async ( - votingUrl: string, - solutionNamespace: string, - solutionGroupId: string, - votingRoundId: string, -): Promise => { - console.log(votingUrl); - const httpProvider: HttpProvider = new HttpProvider(votingUrl); - - await httpProvider.connect(); - - const api: ApiPromise = await ApiPromise.create({ - provider: httpProvider, - throwOnConnect: true, - throwOnUnknown: true, - }); - - const totalWorkers: number = await getRegisteredOperatorsCount(api, solutionGroupId); - - const submittedResults: SubmittedResult[] = await getSubmittedResults( - api, - solutionNamespace, - votingRoundId - ); - - if (submittedResults.length === 0) { - return { - leaderAddress: null, - consensusStatus: ConsensusStatus.NOT_ENOUGH_VOTES, - }; - } - - console.log('votes', submittedResults.length); - - const usedAddresses: string[] = []; - - const orderedUniqueResults: SubmittedResult[] = submittedResults.filter( - (submittedResult: SubmittedResult) => { - if (!usedAddresses.includes(submittedResult.address)) { - usedAddresses.push(submittedResult.address); - - return true; - } - - return false; - } - ); - - const orderedK: { [resultHash: string]: number } = {}; - const minVotesRequired = totalWorkers / 2 + 0.5; - - if (orderedUniqueResults.length < minVotesRequired) { - return { - leaderAddress: null, - consensusStatus: ConsensusStatus.NOT_ENOUGH_VOTES, - }; - } - - let electedLeader: SubmittedResult | null = null; - - for (const submittedResult of orderedUniqueResults) { - if (!orderedK[submittedResult.resultHash]) { - orderedK[submittedResult.resultHash] = 0; - } - - orderedK[submittedResult.resultHash] += 1; - - if (orderedK[submittedResult.resultHash] >= minVotesRequired) { - electedLeader = submittedResult; - } - } - - if (electedLeader) { - const otherPossibleLeader: [string, number] | undefined = Object.entries( - orderedK - ).find( - ([k, v]) => { - if (!electedLeader) { - return false - } - - return v === orderedK[electedLeader.resultHash] && - k !== electedLeader.resultHash; - } - ); - - if (!otherPossibleLeader) { - return { - leaderAddress: electedLeader.address, - consensusStatus: ConsensusStatus.REACHED, - }; - } - } - - const remainingVotes: number = totalWorkers - orderedUniqueResults.length; - const highestVote: number = orderedK[getKeyWithHighestNumber(orderedK)]; - - const canStillReachConsensus: boolean = - highestVote + remainingVotes >= minVotesRequired; - - if (canStillReachConsensus) { - return { - leaderAddress: null, - consensusStatus: ConsensusStatus.NOT_ENOUGH_VOTES, - }; - } - - return { - leaderAddress: null, - consensusStatus: ConsensusStatus.UNABLE_TO_REACH_CONSENSUS, - }; -}; - -const getKeyWithHighestNumber = (obj: any) => - Object.keys(obj).reduce((a, b) => (obj[a] > obj[b] ? a : b)); - - -const nodeInit: NodeInitializer = (RED): void => { - function ConsensusNode(this: SubmitSolutionResultNode, config: SubmitSolutionResultNodeDef) { - RED.nodes.createNode(this, config); - - this.on('input', async (msg: any, send, done) => { - const payload: any = msg.payload; - - if (typeof payload !== 'object') { - console.error('payload is not an object'); - send(msg); - done(); - return; - } - - await leaderElection( - payload.votingUrl, - payload.solutionNamespace, - payload.solutionGroupId, - payload.votingRoundId, - ).then((result) => { - send({ - payload: result - }); - - done(); - }).catch((e) => { - console.error(e); - - send({ - payload: { - consensusStatus: ConsensusStatus.FAILED, - leaderAddress: null, - } - }); - - done(); - }) - }); - } - - RED.nodes.registerType("consensus-node", ConsensusNode); -}; - -export = nodeInit; \ No newline at end of file diff --git a/src/nodes/consensus/icons/consensus.svg b/src/nodes/consensus/icons/consensus.svg deleted file mode 100644 index 8029a19..0000000 --- a/src/nodes/consensus/icons/consensus.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/nodes/consensus/modules/types.ts b/src/nodes/consensus/modules/types.ts deleted file mode 100644 index 3d4a3d6..0000000 --- a/src/nodes/consensus/modules/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {Node, NodeDef} from "node-red"; - -export interface SubmitSolutionResultConfiguration { - solutionNamespace: string; - publicRpcUrl: string; -} - -export interface SubmitSolutionResultNodeDef extends NodeDef, SubmitSolutionResultConfiguration { -} - -export type SubmitSolutionResultNode = Node & SubmitSolutionResultConfiguration; \ No newline at end of file diff --git a/src/nodes/consensus/sample-flow.json b/src/nodes/consensus/sample-flow.json deleted file mode 100644 index 0021476..0000000 --- a/src/nodes/consensus/sample-flow.json +++ /dev/null @@ -1,102 +0,0 @@ -[ - { - "id": "fbe2c505dffe472b", - "type": "tab", - "label": "Flow 1", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "f214cdf3529276b6", - "type": "function", - "z": "fbe2c505dffe472b", - "name": "Prepare payload", - "func": "msg.payload = {\n votingUrl: 'https://public-rpc.testnet.energywebx.com',\n votingRoundId: '1713262650714',\n solutionNamespace: 'au-edge-oep-ineexs',\n solutionGroupId: 'sxeeni'\n}\n\nreturn msg;", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 400, - "y": 360, - "wires": [ - [ - "878fc9c4dbb2c410" - ] - ] - }, - { - "id": "6000f0c6e2aa3422", - "type": "inject", - "z": "fbe2c505dffe472b", - "name": "", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "payload": "", - "payloadType": "date", - "x": 180, - "y": 360, - "wires": [ - [ - "f214cdf3529276b6" - ] - ] - }, - { - "id": "ed4cb8f37ff6a009", - "type": "catch", - "z": "fbe2c505dffe472b", - "name": "", - "scope": null, - "uncaught": false, - "x": 360, - "y": 480, - "wires": [ - [ - "878fc9c4dbb2c410" - ] - ] - }, - { - "id": "33b05f8315638e10", - "type": "debug", - "z": "fbe2c505dffe472b", - "name": "debug 1", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "false", - "statusVal": "", - "statusType": "auto", - "x": 860, - "y": 360, - "wires": [] - }, - { - "id": "878fc9c4dbb2c410", - "type": "consensus-node", - "z": "fbe2c505dffe472b", - "x": 620, - "y": 360, - "wires": [ - [ - "33b05f8315638e10" - ] - ] - } -] \ No newline at end of file diff --git a/src/nodes/dummy.spec.ts b/src/nodes/dummy.spec.ts deleted file mode 100644 index ef01eeb..0000000 --- a/src/nodes/dummy.spec.ts +++ /dev/null @@ -1,5 +0,0 @@ -describe('Hello World', () => { - it('should be equal', () => { - expect(true).toBeTruthy(); - }) -}) \ No newline at end of file diff --git a/src/nodes/energywebx-config/energywebx-config.html/editor.html b/src/nodes/energywebx-config/energywebx-config.html/editor.html deleted file mode 100644 index c60a0c2..0000000 --- a/src/nodes/energywebx-config/energywebx-config.html/editor.html +++ /dev/null @@ -1,16 +0,0 @@ - \ No newline at end of file diff --git a/src/nodes/energywebx-config/energywebx-config.html/help.html b/src/nodes/energywebx-config/energywebx-config.html/help.html deleted file mode 100644 index 816601a..0000000 --- a/src/nodes/energywebx-config/energywebx-config.html/help.html +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/src/nodes/energywebx-config/energywebx-config.html/index.ts b/src/nodes/energywebx-config/energywebx-config.html/index.ts deleted file mode 100644 index c7d7f78..0000000 --- a/src/nodes/energywebx-config/energywebx-config.html/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { EditorRED } from "node-red"; -import {EnergyWebConfigurationNodeProperties} from "./modules/types"; - -declare const RED: EditorRED; - -RED.nodes.registerType("energywebx-config", { - category: "Energy Web X", - color: "#B8FBF4", - defaults: { - networkName: { value: 'ewx', required: true }, - solutionNamespace: { value: "oep", required: true }, - rpcUrl: { value: "" }, - socketUrl: { value: "" }, - explorerUrl: { value: "" } - }, - inputs: 1, - outputs: 1, - icon: "energywebx-logo.png", - paletteLabel: "Chain config", - label: function () { - if (this.name) { - return this.name; - } - - return "Chain config"; - }, -}); \ No newline at end of file diff --git a/src/nodes/energywebx-config/energywebx-config.html/modules/types.ts b/src/nodes/energywebx-config/energywebx-config.html/modules/types.ts deleted file mode 100644 index 1f2667b..0000000 --- a/src/nodes/energywebx-config/energywebx-config.html/modules/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { EditorNodeProperties } from "node-red"; -import {EnergyWebConfiguration} from "../../modules/types"; - -export interface EnergyWebConfigurationNodeProperties - extends EditorNodeProperties, EnergyWebConfiguration { -} \ No newline at end of file diff --git a/src/nodes/energywebx-config/energywebx-config.ts b/src/nodes/energywebx-config/energywebx-config.ts deleted file mode 100644 index 39fd2fc..0000000 --- a/src/nodes/energywebx-config/energywebx-config.ts +++ /dev/null @@ -1,45 +0,0 @@ -import {NodeInitializer} from "node-red"; -import {EnergyWebConfigurationNode, EnergyWebConfigurationNodeDef, EnergyWebEnvironment} from "./modules/types"; - -const nodeInit: NodeInitializer = (RED): void => { - function EnergyWebXConfigNode(this: EnergyWebConfigurationNode, config: EnergyWebConfigurationNodeDef) { - RED.nodes.createNode(this, config); - - this.solutionNamespace = config.solutionNamespace; - - switch(config.networkName) { - case "ewx": - this.rpcUrl = 'https://rex-rpc.energywebx.org/'; - this.networkName = 'ewx'; - this.socketUrl = 'https://rex-rpc.energywebx.org/ws'; - this.explorerUrl = 'https://rex-explorer.energywebx.org/api'; - break; - case 'rex': - this.networkName = 'rex'; - this.rpcUrl = 'https://rpc.energywebx.org/'; - this.socketUrl = 'https://rpc.energywebx.org/ws'; - this.explorerUrl = 'https://explorer.energywebx.org/api'; - break; - } - - this.on('input', (msg, send, done) => { - RED.nodes.eachNode(r => { - console.log(r); - }) - - send({ - payload: { - rpcUrl: this.rpcUrl, - networkName: this.networkName, - socketUrl: this.socketUrl, - solutionNamespace: this.solutionNamespace - } - }); - done(); - }); - } - - RED.nodes.registerType("energywebx-config", EnergyWebXConfigNode); -}; - -export = nodeInit; \ No newline at end of file diff --git a/src/nodes/energywebx-config/icons/energywebx-logo.png b/src/nodes/energywebx-config/icons/energywebx-logo.png deleted file mode 100644 index 07ce600042fb24ea4455b4166e84b39ff12702d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16150 zcmeHu`9D9>OOO}YSRF;vgMI>2@$+!?g$kG*J zqE(uv5GkR^*j<)18r-kr`h36t!2R2Oe|kJTdN{{a@zXgKM_Ck69EV!CWZcBkh__(a1j$=ZDxWzWGf88F9Pnyhl~-V zJW-7AB8VWni}vp^J{p1<>%MVh?VH-&qwY(A*v^&Gms2oj5J}nfq&;Qpv8DnqB`HD& zg?_$cns$nZ`#x@?ex_z5o;~X}U-LlCmN60aq2+69_j@7+Uhv<4|CPZ1=MrG0^H-Bh zmj3iC$?$$SEbZD)hYP4jCqzQ z$!per&R2L)eB^+hdrL7>$CLLnG}UM~zO1B}nU*rMgr&w#Ei^<=J@T&?mSu<42XDdc zYY`>qq-y__#{03J?xQsIJ`G7P^}pdDAigQ4`;G;j&gQoJm^Hsjxpbi&mFja4fvtWdvw$&u+>s zA9!?XVSe3?IWO5;er#`HZcFSM zgho@+xZ7`;FywVkn=4dUKc0Hv9Hms~jQi#0W!JKvs;6ET>J|&H*yLPLYPulLw^)!b z(;g7)gImLG=~{5mgD8`S*v|XmD>@79^Mj;G#Q}YpPyB@#C*Dd3?d*Gogu^Y5hq2SP ztTG_?lK|}$o0i*D0RlTT@MmIK0mVh36OW|gPe^g@7$%?YJE5N0gOleJn5fM0?p2AW z>$`LAJe8*qPFHvedvBh4f2i9POS&>|U8kTO;NxPhzQiTxi($!*1M<9AUp93QSTK+FPWDbg&O+h` z8S>q$I74;}rv6gM#(mYywZ? z9RfW`xzhH*+)TPWX3<6isJKh|>-b`YS2>)k0p`+b8feGtH8z98S?d_vz@K)lGpyJ1Cd z8r+HsRe3^CCZecudoX@$t6EBSieJ5G?_=?+QEQqdHW%^#IgB#!tfjyvM>5cJ(gaJc zL*KGK#>I#v##5YAk2onLt{Fqt4cUqzI%R(@DQhjNeZJ2& z(>U%}0Vdb`!vV}Ls|F#dYTJAvQ+p#Fk-z&25{PXgx9kX6#r)RHRogkXp6}tsdo7tW^VK`9#!<%HP*)f(!*7Y!>#1UUh z92nR&Z@i32m7h!w$Hnj^e*3BG2BWd_x~RMJpcYxKhz(NCGzEl@Qo5TRhC8g65kRjV zZlf`DDp#?OVYa)&6IKlBSf2>$0lumCI5&sowH0b%V+YJvjRMyOKfTxOD*xtkO9`34 zg(Qdis(1conKhk-#3~)5%0MAi2Gt&59kL)}_g@)I_)>e>e>_)_&YGTf2guub~BBrw>hAAgTdkYWNM$D`q}f=`7+ zLGi;3dB_6@Qb!CvwwIztot`2O`fmalO7)48EiA(6no`9^%YaKxD;;xf=c`gfKv*UM zv~(ta-7&@Q-jUH0ZKYdJRE^YGcn6<}f*>DtRg&X!`QNy-Re!k6qrm&DwrQXEofr|g z@z<`lXHxJc>5lXA2V71WSlj{v?gL%sec89KzV4XxU#OP4mYtH8adtVf&PXM@0BxKUxM#$-N}! zA|+68-gv6hmK-m4K8)G*+xplR7j3YY=z!Q%lMelsYpZGL%H1`S zdqiiwMvZ->)ieh|A?QPoE)#SrZ5w{xATNiiKS$&RWtPROvw_)UF_7^qY*B9onk5B2 z_#cI|+@=$B?0mlg?FMk%o)v>d^gc&8jOQ;zB;Qn$KF0fzh1yllozQn$ML&09nrh#6 zJZwVT4pQPQ`|&SF`2TFB<3S)NeZ(Q_l0jYDXDLp$RG^;S1&^aU1=kKJ&y}&g=)tW_ zV^j6*M<{CqXxu9`O5pY5oP^e8wswAeyN4U4e4dk3qxCS;@gPPTl|8BX&h%c^Bs(U6 z8%HNF{*~YlM^7F457SCN7v{#9y_7gY`_6xcJ9>fgy8R_K24b@J$hzE{I$X}yS+7X| zp3`P$Q=0tD-w~IJB(pE1ht{u~t!qLJI!AOm)8(#XhUme?+ zxpvEIjf`c*A8*{tcI4@g#+6zSnEB!PwaqlHLkw%ax*onKG zmK^SI1Cnnz>TSXCuh9v7G_N&T=;FiQ`h%5B8eqwjW`GH>c^%s&MIPpu}^NoNQN*K+7a z))hi^cN1v8Bj>BGzU`=dl(ztmsjU!GdP zkzZys&{ifq>y=!w9P>cMz3ss2x8m5OM_M#V%8~oq*WTB|I5-ZfR<}0V*2~E13D@0S zP3wI(%*wECNX4qPl^!x1dbEapw$D-z|Lp*r>sl24j%E)U+2LPh`EDl6-aOlKMF`&E zYou}81NErX-rCcHhhOC{YsA`Ho@kBKCD94u_aDl4eG^+d+_CTF^*!tT)XtVgYxJOQC!~~X!0fv80MYRM%d;%Mme^46~9bm7&h8>wIK%;K*6ntlk zyg-A{heS+!nj$FXHd$9Yf_@+5A9TQs%%?1LQ6S+-pU^0Sl(Ed_U&u`K*O;7EU)tB!@pa4`To9JUsP^Qz{w z;WoM>c(c*HVZHj|z0cCfk(p~&vCobk)x!rJqTqks|3%3XvMO-C6vMwn7VXOn+A`{a z`SnaDm^v{P^-CjyFGV@xbW(qMer>{QPUqedA;C_43{f_Q&vLABH4?Ncpv?;gdT#$& ziyuv)6XN%UPi~Ru#$%+1JHo9;+M1go;SEs-a9;W;P9~|kwtwGE2lKGm3Bh2WsM$ff zBq;9#>NC`O;%cc{4aL6|f;u@b=f9mkWk3?bl1Um4U};;q-4}ewT&EJ7iVV74Xb1fR z`dFcI_f5r%cK6Ml@f$PK`jU$1JK)xlts4lkB^MWe1yG!i?y}{{Pb!0mm@!3s#|j;s z>}sH1mpDY*_rfO6^2W>Y>0lo@L`8*(%SlseE1iDzihki>_V=@g^zzH4Gp>%QBm4GI z@T$gUWSuk<>hU7S!GS-E%D@x6NfP|g{sM`yfz89~sZPz;c-GX;*FCe^ydN8wH}bBI zvC{8+Cs>$t|3b4hR$Zp(;Myw7u?@@>;Iz0RT-F)y_R=A9_btuOe|_my&Md`tsr6L7 zMP&5>OmE&uOqub1s8YMy3T^9Kx8Jv1kuHuQrf(n)S$yqKkhQ!VcXiCQ{&~-GShBdL zHO|`5UUF>>rqzbcvVV772@P}h=H>b_f+RURptCiJ{XEy|pUV4pB41XjAjun;xa9jf zcSIy*tNSO;@xCbvR334P?iOS{?*yXlOPnAz-`o0L_J~IFWCP zvdzrMr|(BRc@XHy?rptBhGqfa*iFUgcbd<*kmc4L!)*`v#D&{N(VOg_yz9|5?W6Ap zS)F);>D>?!Dk6(tcxxLL`Ads=LpnoFVo8t{4M|_ifpEQA!mL+((^J3tBO`6+uwCZm zcm7$IiA#p8J3UW9xOqp{Va%VTMytQ8N*}Fbwr<^Q+-yme?*%0@89z zhszpm*C}(oSUIn2ze@H2npUAc6oNFqhWY*KOV9?dHb@#Aq$PL-Ry+_+%RXnYdxh+` z5+FOcC{)Bl1?n#9PXQWpE&1cwbunvf9}p7m9POF1?y*H=WJJLsb8 zTk@%Jh@77%hLep{grt+Ej=#1_m4N+rKYA*!y>3O%CV3sYBsX?7jd^UiBP7wLFH|LN z`eR`DO3up^Kf(;~SabC8My1%B?2(lAx-Y%imhb4h?4Rs}hi+(%Q=qDzK2Y6d$E%%2 zi^9sJQ#wDC1u14g-X(nxh39Up)qKlB8v{af@J~~3CvRgh_2>X5>TJ5-N<;?pe(dV) zfKKgVMj8A5f4ep}bbpuwa0_7oUM*#D7 zsCeV(J6b}JnoUd`e*-?rzuHbXTd&w`T}@iU8t183`zdRWsaE3vbCU|QUebe&ynPVa zMLcr6&pHbtSTgH9Emta|q*~+o_pPXfn3v0mb}Yo>>5VeMbmszF>w%V%gqiwIPltgC z){*p1O9QuE2p@#<`-Xy*<T>1me(B zbKam{=R+GQ_SpdwJ$y*BD48YJ&wA9Xv?LG!rBS)z-a$`>JzAqzLU8Maw2QvxW72M~ z0fo^pt3~$PW}Y!+?PFf8TfQhu8|B2`)h~VJP9u~V!eExEUOwY?ge8vdyo!TyD)@w? zq-06A#E`2Xcf8|tSW|t+D|7IW{8+7Vq_k3(;w5Quhg_wxbSR=($>}Hu%!|GTp8Q5J zoA2))DD#uu#n*}u^Dtw`v%~;?lR~pCkLIh+9>})zDH#`fBC>o(Q<#hR9yR!=eOY~f zpTg+f9QjdJ3Y^qY{({)c@aL$z2O9~V9#rtQtz{q7TUyD9J-q?(r3%8$Vdho`y;l=Q z^&*ZgJCXf;vLdVuIIl^qyS}FK)m!~epqwF4nbf#-6@6#L3nFzLdnVyiOhDOMT~tRW zJ}KWt>LTd`5>x);LD!V|s`G!pb9rX><@l11V$9XC%D{UKzG)a%h6oqIy)}k%TXSgi z*tK___pXjPDSWuc&_Hm0LPRQ+r#!o5hx^CRS<>uh4}FMFSF+FU1+FO&>$cyD`hZ5K z(YwoXOi7vEOIbU|tF0DCgj@uP%%qyXXsbuN%Ka7#GEv1PL3@wI1kJ^In$0_wX%5a_ zF`kBHG0nbqt-2CmY@T!EF)-#eph(R%LL%?A$_Z-gzYhGJ^R!35<#)RQaNUMwA+ z?**r+G8v_f`V=XXpAG@*kBf zm{QiN*NNjgMKrm{C9B6*p|EdWCPEuxDEs2;JU>F&S~&7vKmJ>Cr}x@{K=4t^afV^j zr$7w`o)@eeev!c^quy-ncACp$42un}rp#R^^l7IfK`hRgm!U5z_2n$|@mPwWX%(i* zLLyp0Ev#V_Y15jm^ddAX{WBW8hQ%`)((ODZQ)OF@0{<_ybl$z3Gl`C9AzYGDsk zc(E}0;vAKQB$1D{PKMgG8VT60f3}HJ1%QL|8V=o;#?5yc*Fem1$Bc!@36WN@;hdwd zX1!F+kMVqam$R`!jfkE)1S>0oAyS`{i`$ls!fpa|GPot8gMg!dfO?SneE1G$3h6eb z9zt!VJ{@dk6ZmZU2T2ZQ-^1htnpAF4LgJ1x1NG1zWZ!-YKAp~A7e<_Z=xiV(qqvMU zgL+>?Wg`=3v4`OqzIr;~-cm%ZSTl+zuqF0|?i3tL7~m~sov-!2kWkoWaK~CPG0<}p zX6$!8CO%Vr9FaEXAb+9?xPNlwDN30(v{jT;q9 z92#aksJpnRAE6*v`2*f^1RQ3IIU!=eV%j+V`o;VM40!{;j0Rtuj6)a-$f4!6@cSJ_ z!4IYy-sSUMY?D*1y?^fFuR+i@0ZB(?*!(ciHV~04QGVe(wqzLxQ;>zH-zk}|3KRMv zol&+f)7L5et3ahqy?diROB}$Ub2LA1Vno4HMC?^#V?mG($u}RQJ`MF@%h8hKI-=F6CfeCVmqy|M=FlT3!q03M-STK`C>hy17=FW`ya1kD=Fn@xnx`C zza&6ZPglkenNs@OQ`Nth*wJEQP^NC0m||I*L3Ip|&ZK)L$B(~l_>lwkc(rwUCxHaE zOEHtTSdDVi-s5utGZjo(D-q}!OTxC?Rz8mq<^EQ2ob#`m{SIC~F97WC8=#lnhUY6o ze_Q+2j}I8f!AiSWk`o@OP-Wt11-rQo`Qe8cUBS*Kdif=qpqMc5eazDrW`k3V7B6&1 zq>-!e!)=|BfB0bzLWm=m?_oYoGH+#UwgAL6Hk8#(qeoen=a%h%=iZ_STVZh zgYBF=PU0(*A);{;=mge5$U%k0ym-O3PG>^JJiN}HGN43=U&ZxihmVo&l{z~t4MtDD zS|bOgNudpn3*8J>m#@h@IMBZwg-FrcqW{^3a6y@@9Cz}^lK!w`s6mf`5V>EM1QBm_ z;!t3fUU9!qf6uGI-_t7%LlUj?gJcleyPJB*Z%743DWU<2h%knTgsX!C~G%k>8&_|hTk>%I~JqSYOWHf|I zs{ubJ6W(Je105dgXDZ5_+t<3evy*yus~AuN;<)JU`9oIzll(%cMmyBm@nz zw8I_x229HpbRz&e*Pbwl_L3Vxet_n@;YuXsZ|V$l-@E z#0LK#n5CYcIffvP^F2$8bHR(h4ep;6UwXWh7V*(JVqw-9VIhm!Nbe1QmP3D{_SX2l z79H!kAI=j7=P(EkylY6ZJbP(H-|gLw{eIzV5z;z{cOg|Zy7LaggENKE)3ykUB|t-* zwD2z{M z(L|e$i?_*DBM99PE~!NaT+AC$Kr37f`PfL?Zh@IW=onEgz*f1(OKcnjXEqIFxe~Yl zulQ&>fl~;44J5v$FXme13oQp7BGA}_owu=TNdxOT-7ik2xI^2MX_k-v3eK9A8yg_? zWytnUC#nN$aP71WAn=mz=Rp^9f%6X)mkAt^Ku_9e5#V1M!U&fRR0kL_bv-Ju1Lb^KW;pdQQbih1h zmMtd{>Md}NcaDI(9)eYqTp^ORz?_jBl-0!BhEN+JQE1uoKP>;C8b+*NQV>9r&p_Z3 z`p@e>O$GK&{NAnLKNO558A8sb`q0w5L4^B0f;V`4W!TKd9n+gW89#Me`!Ai1@R)v} zr!B+ieC4c?eScO)%>x2!4_J8Xeajn8DWHbl;3nm_p7VnUbsJE3)f%YV(pKoxKSDX7 z>%8!@p+8qB1W{$()T4`__s#|A?JPG`j5AGD*`odVW%#WKUuPWs2xwCE{-;7mp&4)p zWM0IFCp%lR)EX*G-G5-t0rRSPxYTJv!&$8P zV9jH8mYOCvE=U=PPX<9sL6i3h6o9AceK%EWaq;O8TUQLXahDajOFVAZ^*No-L~1V3 zon!;FK_VEa*RK&<(Y&N%B!uLA7bml>pn!A^&@&Qg4(wgG!|Y_N+am+kYmk5n^g|Nl z&e*l{q+Hd$9;{*atdDs!j^rpqnspPjl4?Rd`K8bAwePXJaA>CcKa6%)9)Kh$P@=a z_UJOQGn#nktsdgcwvP4PeDKe*X9{B8>*4PNZ#8qaw7q?BoAmfZdi7iW#05n}9CkVA z|JoLqUL$YrXKZ2I^!MU(t#~RGz%Gv>@PL~0FxlA|e?Uzed`Ws*GJoQaE+QKbZ0#F1 zeL~i~7^snHbay1McboIzc56i3oPtMd#HSjNG)2YPzlciDpFL=v+>MC^O1ph(E1JuOqOGL9frbocd#Ln6{ub4`^Fl z^PlSO&XHGJ>+G%_(mVV?|J@3Zb{)(hNQlG$VBfF)C%Mcn$m(`O+#jU39S#b9(3I3{ z`sC`^jE8#s69RujOxg~__tZ55)*pDqF{r0iIwB`zTmZ0WJvzYzz(P^$BiKit6dQ1T zk%siz!ZAA^NDLIw7f%k6d;EyMOLFG_V0vR9*$j!|b1vLAI)|MPKpl)9G2}@=trp@0 z4Ze_#%jVeU?DY^=YPWc9982$c1eNmYYR}udZntJgH}dX|urj{3^{ho2pv38o8Q$~# z(Wz=-ZX&q~4$7C#XvkT@(%nzMYo2UN3X$Yw+4S*`piKkca$zv~aA=!^M3S%mR%05W zxKZnXOmHIDDk2ftWeV*IobCad1;gI96zBeIC3|Y3bz)U7n%eVYaJsP>Ee)3m; z%PG+zs@yA(xUQ<(nzO1okP(+gGpH1Pbp=s`4C^o|#IOwqb#}Xb35B{)r zx(g;pGo#=yx>@TyUBzx9+!fK=`kBbR0mr<_bt4DeYvP4jp<$ZjRzy}pn6L)JAtv7) zI*!itn**8NS&J(zp1Crl5Yi>cD;<~PjlZ6eSY2+$edz=@gb2;#b7119=kz-_NBI+f z-!>XupRStR9r+oW8{EcEB(=WGZ@K&cgJTKQ%+Unk7P8CsZ zc6L19`Spm~Yf&->7w9=Nd|dBwlv`}`nc!(@`Uz;AokQ=47Z> zlHK9Wnt`5=KRmfump95hA@I~;?psHL2>lIA#r78kdV=BIhFz6w+t!VmvRgaFAS-CKp>;GS(e`r;2 zonD2$A2?6WvAhA@%A#Sy-KksbbtI?8V0^l`JG>gzYJy91# z`aW&+t4#N+A6jmQCuOetZNR1@&`1|R)0@VdMP@bZ3yBgur@weOS-`gJQ$EFZ{)XYv z4>`c*+Ny0M>w3d1htF-MAK3R$&(;l{phU2Jo-91HENu|=$RGXqTLDpVfianeer5#N6K3HFseIuAuf}S)9eO1v z1}1v;BBOWEF^r+FJ)806oVo7-#$8912CK*G(!83W8ny_+qrrKfF&Z-8X7`eQYjp?* zu|OFUfQJb*{sPSOSMt~JlN@(I%?3tbh%z4KUr&?dq)BJ2TbJ2j<@kA*gok@l#G>wJ zV&@tRu@huP8+Ot35p~+Q#C|v(=i*dZ`P#@COO=3QebHmV8;%WEUYhl)q#d0&f*1^l zlgrjXC{8C=MfQ)F>nB3C&%Ur&597H65E)x;X3wkWX^b4{O~UOTYQdN?nDUp|Jh;W} z^a+1$L=dtqRo|F~#Wr6|?!}iskulb+pJp+^ zB<#WJ60H~Y3Hx&HN$!^L7>oj&xC!i80UJkwjhv(SSx3Rj0J|>-kZPw}ulLSqk>N3P&_^2+q65C67Io3X zZK}_agF)+Jer#bGfb45Sdh^hW1@%^&J79QTX&vOL@BnrMq37QF4rCm(Df6(6VFCmR zt&}thaOw-=l!mN`U+2eu?Wgv4NzTEewk2b9!7sE!cFws9T4dL+=XHQ^l1Zq z5wM1s&@DO`R^T3e7}o_MY1ff_$T?Ke%3Z6F_@<-Qc5&rj9z(mFk8Q!B zO@v8@`H=}G$^wq8K7`#7z21kav6;M`cU7-Jc zwdaEU=d+d21&7p`qpQi~Jh9KO7UjM#_hWmtagM5<%ct**8LoiCK zl?cfzc{6UOclO1ZU@lZ53^XffEBt56>_Ebs@k*LqVityR9(G?)xjVMII8UDA+1E5H znMiA;n=MdPz;or@i!o z$R17C!3k)Q^_4pkx9;(jz*Lcj9~_tPzBJZUq{@GDRFXdBrfC2KU<4DRo5I0#;Ejn zwGcAC1KdNmw@@?#YK^Yg*o8eiG4sDhUT;$ZD&P8P@qf&G9+MmMmdb zqc*pcF~1A3NY@%ComUlXEicud$rP|KuXjshSeik{o^u8RfxqcFNWx~0hoQ-lw$A2x z%E><#_V}^GSt${hz5%ud(Ym@&I%8fVJ+tP~1z9li!GhgNh!m9Ss?b$s94Nojc@L{v zOqkhb5S_|?$E6RU%VtM^amxLxr*>0?rmFbpXxPI{aH9q6Op>;uhX2Hmo!Q?oIKn>< z&FMFA5lgl}t0*YdPc%n#6j?RwGZ?v_M$TNBjsOws(Ku72aEgs}+wA<~?d-xj=8f7= zqMXPK2&rahm|xtMb%nutSk{j+-DBK1|v5Ub%VwEI7wJ|wanvJ6uSug-f#HpsA{djuRWTZc=Ds>g7Ng#G+B|pOv}8^ql?0W zlVVjLdi9u$xAySg2hUYa6j--UkyY=XTI0~lrmfn*s6 zvTN{n-^``jF8aBp+ktr#VO`S<1xB9MEe`Bs%Ga_x{%J+-LS`RCLF4=ycDCWH*LLWF zj9yty^Cy9Y#Rkii0%dbv3fRwgoC!wPdiO2cL?dR@mM|0y8*V|(68_b6aFxr+DZ?RjtJdSkHs`sqPjn3UdfdNG_L*MFg_ZRg zmHw$&VJ!-gm=nfvkp+*lA!t*dS34;}16;4TLV_L)9n2=)D#dV!h6I&kgC@5t8kYns z&!2u|AP(EH7|5}09yjV+M1feKaU`@JmatJfM<;nCIUvkiQr^3_8&>1vmP5k9Yws?Y zesOg1NiA-S|nj*yqpIq7TBnFObnxIdJK?oP01WC}{L-+JX&%wE7!s z&q;Dnrc%v0T>AERDx>s2f_0HvB4dKQz zC2p<)anp>O0jJDdQxf{xNVT>}Ol}ok+iIkZcIjh=bOyOfnl(Cb$+3|7M;gW6Bo;RH zBLU1QCVFw>u%=m_9O}%IXK9OYC)c9R=+V7Hg5JCTu^tQ7Fdy7J@FqRlj ziFK~EwsN9_7Se{$io-RO>u#Tgq48Z)CTyO5FNe7^G+eFkZ&wlxP7LZ95kfVZ;E7#g$g2TCNXckiBBpNuL>P zVeSm%orX09HfflUUF(r#DAJNqua~!y9eK6z z-&#P%+akz)3cd$>n-M~`RrGEz#3Uarbp&u^rjN4Q;M=Y{&~k(^LV9KBidtCUtps-i zDTK~`RJD*e66jYU^1)u!$xm{7N&2zR(PeY)QIWwSBt0qgu(nw{0UMjvSSO=uPI0$J ziT=W;pI1P1Y(_c`NWEPT%Xdqg4+Xg&02*L#fN`L&51YQ#)cI`7!E>rF9(2Ky;q-9~6 zt&VGR&nn0C6+u&;t=6t(E_9KTv7~K8(o@0H4g(mXw?{68@M`!5UsmB9bC1o+RE ZB*?;d&3C5>oj?!Uzt?ilLlf%d{{zTU#%=%r diff --git a/src/nodes/energywebx-config/modules/types.ts b/src/nodes/energywebx-config/modules/types.ts deleted file mode 100644 index 086062f..0000000 --- a/src/nodes/energywebx-config/modules/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Node, NodeDef } from "node-red"; - -export interface EnergyWebConfiguration { - solutionNamespace: string; - networkName: EnergyWebEnvironment; - rpcUrl: string; - socketUrl: string; - explorerUrl: string; -} - -export type EnergyWebEnvironment = 'rex' | 'ewx'; - -export interface EnergyWebConfigurationNodeDef extends NodeDef, EnergyWebConfiguration { -} -export type EnergyWebConfigurationNode = Node & EnergyWebConfiguration; \ No newline at end of file diff --git a/submit-solution.html b/submit-solution.html index b3a7cbc..e17ef4c 100644 --- a/submit-solution.html +++ b/submit-solution.html @@ -4,7 +4,7 @@ color: '#60A0FF', inputs: 1, outputs: 1, - icon: "submit-result.png", + icon: "submit-result.svg", label: function() { return this.name || "submit result"; }, diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 0acd269..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2018", - "module": "commonjs", - "lib": ["es2018", "esnext.asynciterable", "dom"], - "allowJs": false, - "noEmitOnError": true, - "sourceMap": true, - "outDir": "dist", - "rootDir": "src", - "removeComments": false, - "noEmit": false, - "importHelpers": false, - "preserveWatchOutput": true, - - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "skipLibCheck": true, - "noUnusedLocals": false, - "noUnusedParameters": false, - "noFallthroughCasesInSwitch": true, - - "moduleResolution": "node", - "baseUrl": "./", - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "preserveConstEnums": true, - "forceConsistentCasingInFileNames": true, - - "typeRoots": ["./src/types", "./node_modules/@types"] - }, - "include": ["src"], - "exclude": ["node_modules"] -} \ No newline at end of file diff --git a/tsconfig.runtime.json b/tsconfig.runtime.json deleted file mode 100644 index e8337f9..0000000 --- a/tsconfig.runtime.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": ["src"], - "exclude": ["node_modules", "src/__tests__", "src/nodes/*/*.html"] -} \ No newline at end of file diff --git a/tsconfig.runtime.watch.json b/tsconfig.runtime.watch.json deleted file mode 100644 index c2bdb0c..0000000 --- a/tsconfig.runtime.watch.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "./tsconfig.runtime.json", - "compilerOptions": { - "incremental": true, - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo" - } -} \ No newline at end of file