From 52b7ac9f1bf5ca951b422d8c9b95b6f9fcd6eecb Mon Sep 17 00:00:00 2001 From: Chalith Desaman Date: Fri, 31 May 2024 12:13:23 +0530 Subject: [PATCH] Modifications to reputation contract deployment (#384) --- .gitmodules | 3 - CMakeLists.txt | 4 - evernode-reputation-contract | 1 - installer/jshelper/index.js | 4 +- installer/jshelper/package-lock.json | 8 +- installer/jshelper/package.json | 2 +- installer/sashimono-install.sh | 1 - installer/setup.sh | 3 - mb-xrpl/lib/config-helper.js | 5 + mb-xrpl/lib/message-board.js | 11 +- mb-xrpl/package-lock.json | 8 +- mb-xrpl/package.json | 2 +- reputationd/app.js | 4 - reputationd/lib/appenv.js | 2 +- reputationd/lib/config-helper.js | 6 + reputationd/lib/contract-instance-manager.js | 41 ----- reputationd/lib/lobby-manager.js | 118 +++++++++++++++ reputationd/lib/reputationd.js | 149 ++++++------------- reputationd/lib/setup.js | 14 +- reputationd/lib/util-helper.js | 78 ---------- reputationd/package-lock.json | 39 +++-- reputationd/package.json | 5 +- src/hp_manager.cpp | 14 +- 23 files changed, 229 insertions(+), 293 deletions(-) delete mode 160000 evernode-reputation-contract create mode 100644 reputationd/lib/lobby-manager.js diff --git a/.gitmodules b/.gitmodules index cffb3f4..8b1a17a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,6 +2,3 @@ path = evernode-bootstrap-contract url = https://github.com/HotPocketDev/evernode-bootstrap-contract.git branch = release -[submodule "evernode-reputation-contract"] - path = evernode-reputation-contract - url = https://github.com/EvernodeXRPL/evernode-reputation-contract diff --git a/CMakeLists.txt b/CMakeLists.txt index 99b88ab..3c7c236 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,7 +71,6 @@ add_custom_command(TARGET sagent POST_BUILD COMMAND ./installer/docker-install.sh ./build/dockerbin COMMAND npm --prefix ./mb-xrpl install && npm run --prefix ./mb-xrpl build COMMAND npm --prefix ./reputationd install && npm run --prefix ./reputationd build - COMMAND npm --prefix ./evernode-reputation-contract install && npm run --prefix ./evernode-reputation-contract build ) target_precompile_headers(sagent PUBLIC src/pchheader.hpp) @@ -85,11 +84,8 @@ add_custom_target(installer COMMAND bash -c "cp -r ./evernode-license.pdf ./build/installer/" COMMAND bash -c "cp -r ./mb-xrpl/dist ./build/installer/mb-xrpl" COMMAND bash -c "cp -r ./reputationd/dist ./build/installer/reputationd" - COMMAND bash -c "cp -r ./evernode-reputation-contract/dist ./build/reputation-contract" - COMMAND tar cfz ./build/reputation-contract.tar.gz --directory=./build/ reputation-contract COMMAND tar cfz ./build/installer.tar.gz --directory=./build/ installer COMMAND rm -r ./build/installer - COMMAND rm -r ./build/reputation-contract # js helper for installer setup. COMMAND npm --prefix ./installer/jshelper install && npm run --prefix ./installer/jshelper build diff --git a/evernode-reputation-contract b/evernode-reputation-contract deleted file mode 160000 index e8165e6..0000000 --- a/evernode-reputation-contract +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e8165e69caf112135a94e96000b2b386d9e7d4dc diff --git a/installer/jshelper/index.js b/installer/jshelper/index.js index 3ad73e6..0d0235f 100644 --- a/installer/jshelper/index.js +++ b/installer/jshelper/index.js @@ -617,8 +617,8 @@ const funcs = { catch (err) { await xrplApi.disconnect(); if (err.data?.error === 'actNotFound') { - const governorClient = await evernode.HookClientFactory.create(evernode.HookTypes.governor); - await governorClient.connect(); + const governorClient = await evernode.HookClientFactory.create(evernode.HookTypes.governor, { config: hostClient.config }); + await governorClient.connect({ skipConfigs: true }); return { success: true, result: `${governorClient.config.hostRegFee}` }; } return { success: false, result: "Error occurred in websocket connection." }; diff --git a/installer/jshelper/package-lock.json b/installer/jshelper/package-lock.json index 138c31e..2e104f3 100644 --- a/installer/jshelper/package-lock.json +++ b/installer/jshelper/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "evernode-setup-helper", "dependencies": { - "evernode-js-client": "0.6.49", + "evernode-js-client": "0.6.51", "ip6addr": "0.2.5", "ripple-keypairs": "1.3.1" } @@ -405,9 +405,9 @@ } }, "node_modules/evernode-js-client": { - "version": "0.6.49", - "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.49.tgz", - "integrity": "sha512-h1OUH3Z+g0mtpbQPKelYxs62OUufDBJTo+AA21ymOzQ/3twSLIL3M5X5rDC6hPr0w2lT3HoyQTcXOJxlAneZBA==", + "version": "0.6.51", + "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.51.tgz", + "integrity": "sha512-152deKlva1zkV47LFqKenBjT2xlx37nXs0apyf3uBt4I694daaoIbyor2ymkEhSK1O0Mw7Yje9+WRh2YKh6i7A==", "dependencies": { "elliptic": "6.5.4", "libsodium-wrappers": "0.7.10", diff --git a/installer/jshelper/package.json b/installer/jshelper/package.json index 24511c9..bdd30b9 100644 --- a/installer/jshelper/package.json +++ b/installer/jshelper/package.json @@ -4,7 +4,7 @@ "build": "ncc build index.js --minify -o dist" }, "dependencies": { - "evernode-js-client": "0.6.49", + "evernode-js-client": "0.6.51", "ip6addr": "0.2.5", "ripple-keypairs": "1.3.1" } diff --git a/installer/sashimono-install.sh b/installer/sashimono-install.sh index e66afa8..2695c8a 100755 --- a/installer/sashimono-install.sh +++ b/installer/sashimono-install.sh @@ -526,7 +526,6 @@ echo "configuring host setup on Evernode..." cp -r "$script_dir"/mb-xrpl $SASHIMONO_BIN cp -r "$script_dir"/reputationd $SASHIMONO_BIN -cp -r "$script_dir"/reputation-contract $REPUTATIOND_DATA/ # Create MB_XRPL_USER if does not exists.. if ! grep -q "^$MB_XRPL_USER:" /etc/passwd; then diff --git a/installer/setup.sh b/installer/setup.sh index 2893aeb..046ec44 100755 --- a/installer/setup.sh +++ b/installer/setup.sh @@ -46,7 +46,6 @@ resource_storage="https://github.com/$repo_owner/$repo_name/releases/download/$latest_version" licence_url="https://raw.githubusercontent.com/$repo_owner/$repo_name/$desired_branch/license/evernode-license.pdf" config_url="https://raw.githubusercontent.com/$repo_owner/$repo_name/$desired_branch/definitions/definitions.json" - reputation_contract_url="https://raw.githubusercontent.com/$repo_owner/$repo_name/$desired_branch/sashimono/installer/reputation-contract.tar.gz" setup_script_url="$resource_storage/setup.sh" installer_url="$resource_storage/installer.tar.gz" jshelper_url="$resource_storage/setup-jshelper.tar.gz" @@ -2159,8 +2158,6 @@ WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer ! sudo -u $REPUTATIOND_USER REPUTATIOND_DATA_DIR=$REPUTATIOND_DATA node $REPUTATIOND_BIN upgrade && echo "Error upgrading reputationd" && return 1 fi - ! sudo -u $REPUTATIOND_USER REPUTATIOND_DATA_DIR=$REPUTATIOND_DATA node $REPUTATIOND_BIN update-config $reputation_contract_url && echo "Error configuring reputation contract URL." && return 1 - # Setup env variable for the reputationd user. echo " export XDG_RUNTIME_DIR=$reputationd_user_runtime_dir" >>"$reputationd_user_dir"/.bashrc diff --git a/mb-xrpl/lib/config-helper.js b/mb-xrpl/lib/config-helper.js index 57695b1..ef837d2 100644 --- a/mb-xrpl/lib/config-helper.js +++ b/mb-xrpl/lib/config-helper.js @@ -47,6 +47,11 @@ class ConfigHelper { let publicCfg = JSON.parse(JSON.stringify(config)); // Make a copy. So, referenced object won't get changed. if ('secret' in publicCfg.xrpl) delete publicCfg.xrpl.secret; + // Remove reputation related props. + for (const e of Object.entries(publicCfg.xrpl)) { + if (e[0].startsWith('reputation')) + delete publicCfg.xrpl[e[0]]; + } fs.writeFileSync(configPath, JSON.stringify(publicCfg, null, 2), { mode: 0o644 }); // Set file permission so only current user can read/write and others can read. } diff --git a/mb-xrpl/lib/message-board.js b/mb-xrpl/lib/message-board.js index a3d5a5f..a490754 100644 --- a/mb-xrpl/lib/message-board.js +++ b/mb-xrpl/lib/message-board.js @@ -95,9 +95,9 @@ class MessageBoard { if (!hostInfo) throw "Host is not registered."; - this.regClient = await evernode.HookClientFactory.create(evernode.HookTypes.registry); + this.regClient = await evernode.HookClientFactory.create(evernode.HookTypes.registry, { config: this.hostClient.config }); - await this.#connectRegistry(); + await this.#connectRegistry({ skipConfigs: true }); await this.regClient.subscribe(); // Get moment only if heartbeat info is not 0. @@ -530,9 +530,8 @@ class MessageBoard { } // Update the registry with the active instance count. await this.hostClient.updateRegInfo(this.activeInstanceCount, null, null, null, null, null, null, null, null, null, null, { submissionRef: submissionRefs?.refs[0] }); + console.log(`Destroyed ${lease.containerName}`); }); - console.log(`Destroyed ${lease.containerName}`); - } catch (e) { console.error(e); @@ -571,8 +570,8 @@ class MessageBoard { await this.#connect(this.hostClient, { reputationAddress: this.cfg.xrpl.reputationAddress, reputationSecret: this.cfg.xrpl.reputationSecret }); } - async #connectRegistry() { - await this.#connect(this.regClient); + async #connectRegistry(options = {}) { + await this.#connect(this.regClient, options); } #startPruneScheduler() { diff --git a/mb-xrpl/package-lock.json b/mb-xrpl/package-lock.json index 8cc2060..17851b2 100644 --- a/mb-xrpl/package-lock.json +++ b/mb-xrpl/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "mb-xrpl", "dependencies": { - "evernode-js-client": "0.6.49", + "evernode-js-client": "0.6.51", "ip6addr": "0.2.5", "sqlite3": "5.0.2" }, @@ -1043,9 +1043,9 @@ } }, "node_modules/evernode-js-client": { - "version": "0.6.49", - "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.49.tgz", - "integrity": "sha512-h1OUH3Z+g0mtpbQPKelYxs62OUufDBJTo+AA21ymOzQ/3twSLIL3M5X5rDC6hPr0w2lT3HoyQTcXOJxlAneZBA==", + "version": "0.6.51", + "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.51.tgz", + "integrity": "sha512-152deKlva1zkV47LFqKenBjT2xlx37nXs0apyf3uBt4I694daaoIbyor2ymkEhSK1O0Mw7Yje9+WRh2YKh6i7A==", "dependencies": { "elliptic": "6.5.4", "libsodium-wrappers": "0.7.10", diff --git a/mb-xrpl/package.json b/mb-xrpl/package.json index dadaee8..c0b97f4 100644 --- a/mb-xrpl/package.json +++ b/mb-xrpl/package.json @@ -5,7 +5,7 @@ "build": "npm run lint && ncc build app.js --minify -o dist" }, "dependencies": { - "evernode-js-client": "0.6.49", + "evernode-js-client": "0.6.51", "sqlite3": "5.0.2", "ip6addr": "0.2.5" }, diff --git a/reputationd/app.js b/reputationd/app.js index d3beec1..7e8eff2 100644 --- a/reputationd/app.js +++ b/reputationd/app.js @@ -43,10 +43,6 @@ async function main() { else if (process.argv.length >= 2 && process.argv[2] === 'prepare') { await new Setup().prepareReputationAccount(); } - else if (process.argv.length >= 4 && process.argv[2] === 'update-config') { - const contractUrl = process.argv[3]; - await new Setup().updateConfig(contractUrl); - } else if (process.argv.length >= 3 && process.argv[2] === 'upgrade') { await new Setup().upgrade(); } diff --git a/reputationd/lib/appenv.js b/reputationd/lib/appenv.js index 8203fcf..5d540b1 100644 --- a/reputationd/lib/appenv.js +++ b/reputationd/lib/appenv.js @@ -5,7 +5,7 @@ let appenv = { IS_DEV_MODE: process.env.REPUTATIOND_DEV === "1", FILE_LOG_ENABLED: process.env.REPUTATIOND_FILE_LOG === "1", DATA_DIR: process.env.REPUTATIOND_DATA_DIR || __dirname, - INSTANCE_IMAGE: 'evernode/sashimono:hp.test-ubt.20.04-njs.20', + INSTANCE_IMAGE: 'evernodedev/reputation:hp.latest-ubt.20.04', } appenv = { diff --git a/reputationd/lib/config-helper.js b/reputationd/lib/config-helper.js index b1dfaf3..4e28875 100644 --- a/reputationd/lib/config-helper.js +++ b/reputationd/lib/config-helper.js @@ -42,6 +42,12 @@ class ConfigHelper { let publicCfg = JSON.parse(JSON.stringify(config)); // Make a copy. So, referenced object won't get changed. if ('secret' in publicCfg.xrpl) delete publicCfg.xrpl.secret; + if ('network' in publicCfg.xrpl) + delete publicCfg.xrpl.network; + if ('governorAddress' in publicCfg.xrpl) + delete publicCfg.xrpl.governorAddress; + if ('rippledServer' in publicCfg.xrpl) + delete publicCfg.xrpl.rippledServer; // Remove host related props. for (const e of Object.entries(publicCfg.xrpl)) { if (e[0].startsWith('host')) diff --git a/reputationd/lib/contract-instance-manager.js b/reputationd/lib/contract-instance-manager.js index 3aa9eae..1f16e18 100644 --- a/reputationd/lib/contract-instance-manager.js +++ b/reputationd/lib/contract-instance-manager.js @@ -1,11 +1,9 @@ -const fs = require('fs'); const bson = require('bson'); const HotPocket = require('hotpocket-js-client'); const { CommonHelper } = require('./util-helper'); const DEFAULT_TIMEOUT = 120000; -const INSTALL_SCRIPT_FAILURE = "Input failed. reason: InstallScriptFailed"; const INPUT_PROTOCOLS = HotPocket.protocols; class ContractInstanceManager { @@ -98,45 +96,6 @@ class ContractInstanceManager { const result = protocol === INPUT_PROTOCOLS.bson ? bson.deserialize(output) : JSON.parse(output); return result?.message; } - - async checkBootstrapStatus() { - const res = await this.sendContractInput({ - type: "status" - }); - if (res) - return res; - else - return null; - } - - async uploadBundle(bundlePath, handleScriptFailure = false) { - try { - const status = await this.checkBootstrapStatus(); - if (status) - console.log(status); - else - throw 'Status response is empty'; - } - catch (e) { - throw `Bootstrap contact is not available: ${e}`; - } - - const fileContent = fs.readFileSync(bundlePath); - - try { - const res = await this.sendContractInput({ - type: "upload", - content: fileContent - }); - return res; - - } catch (e) { - if (!handleScriptFailure || e !== INSTALL_SCRIPT_FAILURE) - throw e; - } - - return null; - } } module.exports = { diff --git a/reputationd/lib/lobby-manager.js b/reputationd/lib/lobby-manager.js new file mode 100644 index 0000000..3231466 --- /dev/null +++ b/reputationd/lib/lobby-manager.js @@ -0,0 +1,118 @@ + +const { CommonHelper } = require('./util-helper'); +const WebSocket = require('ws'); + +const DEFAULT_TIMEOUT = 120000; + +class LobbyManager { + #ip; + #userPort; + #userPrivateKey; + #userKeys; + #wsClient; + + constructor(options = {}) { + this.#ip = options.ip; + this.#userPort = options.userPort; + this.#userPrivateKey = options.userPrivateKey; + } + + async init() { + if (!this.#ip) + throw "Instance IP is missing!"; + else if (!this.#userPort) + throw "Instance user port is missing!"; + else if (!this.#userPrivateKey) + throw "Instance user private key is missing!"; + + this.#userKeys = await CommonHelper.generateKeys(this.#userPrivateKey, 'binary'); + console.log('My public key is: ' + Buffer.from(this.#userKeys.publicKey).toString('hex')); + + const server = `wss://${this.#ip}:${this.#userPort}`; + this.#wsClient = new WebSocket(server, { + rejectUnauthorized: false + }); + } + + terminate() { + if (this.#wsClient) + this.#wsClient.close() + } + + #handleMessage(message) { + var message = JSON.parse(message); + switch (message.type) { + case 'upgrade': + if (message.status === 'SUCCESS') + return true; + else + throw message.data ?? 'UNKNOWN_ERROR'; + default: + throw 'UNHANDLED_MESSAGE'; + } + } + + async upgradeContract(unl, peers, timeoutMs = DEFAULT_TIMEOUT) { + return new Promise(async (resolve, reject) => { + const inputTimer = setTimeout(() => { + clearTimeout(inputTimer); + reject("Input timeout."); + }, timeoutMs); + + const failure = (e) => { + clearTimeout(inputTimer); + reject(e); + } + + const success = (result) => { + clearTimeout(inputTimer); + resolve(result); + } + + if (!this.#wsClient) + failure('Web socket connection is not initiated'); + + try { + // This will get fired when contract sends an output. + this.#wsClient.on('message', (data) => { + console.log('Received from server:', data.toString()); + try { + const res = this.#handleMessage(data); + if (res) + success('CONTRACT_UPGRADED'); + else + throw 'UNKNOWN_ERROR' + } + catch (e) { + failure(e); + } + }); + + this.#wsClient.on('open', () => { + console.log('Connection opened. Sending upgrade request...'); + try { + this.#wsClient.send(JSON.stringify({ + type: 'upgrade', + user: this.#userKeys.publicKey, + data: { + unl: unl, + peers: peers + } + })); + } + catch (e) { + failure(e); + } + }); + } + catch (e) { + failure(e); + } + + }); + } +} + +module.exports = { + LobbyManager +} \ No newline at end of file diff --git a/reputationd/lib/reputationd.js b/reputationd/lib/reputationd.js index a75cbab..62bdda3 100644 --- a/reputationd/lib/reputationd.js +++ b/reputationd/lib/reputationd.js @@ -1,12 +1,11 @@ -const fs = require('fs'); const evernode = require('evernode-js-client'); const crypto = require('crypto'); const uuid = require('uuid'); -const path = require('path'); const { appenv } = require('./appenv'); const { ConfigHelper } = require('./config-helper'); -const { ContractHelper, CommonHelper } = require('./util-helper'); +const { CommonHelper } = require('./util-helper'); const { ContractInstanceManager, INPUT_PROTOCOLS } = require('./contract-instance-manager'); +const { LobbyManager } = require('./lobby-manager'); const ContractStatus = { Created: 1, @@ -24,18 +23,16 @@ class ReputationD { #reputationRetryCount = 3; #feeUpliftment = 0; #preparationTimeQuota = 0.9; // Percentage of moment size. - #reputationSendTimeSlice = 0.2; // Slice of preparationTimeQuota for reputation registration. - #deploymentStartTimeQuota = 0.8; + #reputationRegTimeQuota = 0.2; // Percentage of (1 - preparationTimeQuota) for reputation registration. + #lobbyTimeQuota = 0.8; // Percentage of (1 - reputationRegTimeQuota) for reputation contract deployment. #universeSize = 64; #readScoreCmd = 'read_scores'; - #consensusRoundTime = 10000; - #consensusThreshold = 50; #configPath; #mbXrplConfigPath; #instanceImage; - constructor(configPath, mbXrplConfigPath, instanceImage, contractPath) { + constructor(configPath, mbXrplConfigPath, instanceImage) { this.#configPath = configPath; this.#mbXrplConfigPath = mbXrplConfigPath; this.#instanceImage = instanceImage; @@ -82,9 +79,9 @@ class ReputationD { if (!hostInfo) throw "Host is not registered."; - this.reputationClient = await evernode.HookClientFactory.create(evernode.HookTypes.reputation); + this.reputationClient = await evernode.HookClientFactory.create(evernode.HookTypes.reputation, { config: this.hostClient.config }); - await this.#connectReputation(); + await this.#connectReputation({ skipConfigs: true }); const repInfo = await this.hostClient.getReputationInfo(); // Last registered moment n means reputation is sent in n-1 moment. @@ -168,8 +165,10 @@ class ReputationD { this.#feeUpliftment = 0; } catch (e) { + console.error(e); if (action.attempts < action.maxAttempts) { action.attempts++; + console.log(`Retry attempt ${action.attempts}`); if (this.cfg.xrpl.affordableExtraFee > 0 && e.status === "TOOK_LONG") { this.#applyFeeUpliftment = true; this.#feeUpliftment = Math.floor((this.cfg.xrpl.affordableExtraFee * action.attempts) / action.maxAttempts); @@ -188,7 +187,7 @@ class ReputationD { toKeep.push(action); } else { - console.error(e); + console.error('Max retry attempts reached. Abandoned.'); } } } @@ -229,8 +228,8 @@ class ReputationD { await this.#connect(this.hostClient, { reputationAddress: this.cfg.xrpl.address, reputationSecret: this.cfg.xrpl.secret }); } - async #connectReputation() { - await this.#connect(this.reputationClient); + async #connectReputation(options = {}) { + await this.#connect(this.reputationClient, options); } async #startReputationClockScheduler() { @@ -267,7 +266,7 @@ class ReputationD { // Set time relative to current passed time. const timeQuota = momentSize * (1 - this.#preparationTimeQuota); - const upperBound = Math.floor(momentStartTimestamp + momentSize - (timeQuota * (1 - this.#reputationSendTimeSlice))); + const upperBound = Math.floor(momentStartTimestamp + momentSize - (timeQuota * (1 - this.#reputationRegTimeQuota))); const lowerBound = Math.floor(momentStartTimestamp + momentSize - timeQuota); if (currentTimestamp < lowerBound || currentTimestamp >= upperBound) startTimeout = Math.floor(lowerBound + (Math.random() * ((upperBound - lowerBound) / 2)) - currentTimestamp) * 1000 // Converting seconds to milliseconds. @@ -302,7 +301,7 @@ class ReputationD { const timeQuota = momentSize * (1 - this.#preparationTimeQuota); const upperBound = Math.floor(momentStartTimestamp + momentSize); - const lowerBound = Math.floor(momentStartTimestamp + momentSize - (timeQuota * (1 - this.#reputationSendTimeSlice))); + const lowerBound = Math.floor(momentStartTimestamp + momentSize - (timeQuota * (1 - this.#reputationRegTimeQuota))); if (currentTimestamp < lowerBound) startTimeout = Math.floor(lowerBound - currentTimestamp) * 1000; @@ -329,43 +328,24 @@ class ReputationD { }, startTimeout); } - async #waitForHostInstance(orderedId, moment) { - const momentSize = this.hostClient.config.momentSize; - const momentStartTimestamp = await this.hostClient.getMomentStartIndex(); - - const timeQuota = momentSize * (1 - this.#preparationTimeQuota); - const upperBound = Math.floor(momentStartTimestamp + momentSize); - const lowerBound = Math.floor(momentStartTimestamp + momentSize - (timeQuota * (1 - this.#reputationSendTimeSlice))); - const prepareEndTimestamp = Math.floor(lowerBound + ((upperBound - lowerBound) * this.#deploymentStartTimeQuota)); - - while (true) { - const currentTimestamp = evernode.UtilHelpers.getCurrentUnixTime(); - if (currentTimestamp > prepareEndTimestamp) - throw 'Maximum timeout reached for preparation'; - - const repInfo = await this.reputationClient.getReputationInfoByOrderedId(orderedId, moment).catch((e) => { }); - if (repInfo && repInfo.contract) - return repInfo.contract; - await new Promise(resolve => setTimeout(resolve, 500)); - } - } - async #getUniverseInfo(moment) { - const repInfo = await this.hostClient.getReputationInfo(moment); + if (!this.hostClient.reputationAcc) + return null; - if (!repInfo || !('orderedId' in repInfo)) + const orderInfo = await this.reputationClient.getReputationOrderByAddress(this.hostClient.reputationAcc.address, moment); + + if (!orderInfo || !('orderedId' in orderInfo)) return null; return { - ...repInfo, - universeIndex: Math.floor(repInfo.orderedId / this.#universeSize) + universeIndex: Math.floor(orderInfo.orderedId / this.#universeSize) }; } async #getInstancesInUniverse(universeIndex, moment) { const minOrderedId = universeIndex * this.#universeSize; return (await Promise.all(Array.from({ length: this.#universeSize }, (_, i) => i + minOrderedId).map(async (orderedId) => { - const repInfo = await this.reputationClient.getReputationInfoByOrderedId(orderedId, moment); + const repInfo = await this.reputationClient.getReputationContractInfoByOrderedId(orderedId, moment); if (!repInfo) return null; @@ -388,29 +368,24 @@ class ReputationD { return id; } - async #deployContract(instanceIp, instanceUserPort, userPrivateKey, hpOverrideCfg) { - const bundlePath = await ContractHelper.prepareContractBundle(this.cfg.contractUrl, hpOverrideCfg); - console.log(`Prepared contract bundle at ${bundlePath}`); - - let instanceMgr; + async #upgradeContract(instanceIp, instanceUserPort, userPrivateKey, unl, peers) { + let lobbyMgr; try { - instanceMgr = new ContractInstanceManager({ + lobbyMgr = new LobbyManager({ ip: instanceIp, userPort: instanceUserPort, userPrivateKey: userPrivateKey }); - await instanceMgr.init(); - await instanceMgr.uploadBundle(bundlePath); + await lobbyMgr.init(); + await lobbyMgr.upgradeContract(unl, peers); - fs.rmSync(path.dirname(bundlePath), { recursive: true, force: true }); - if (instanceMgr) - await instanceMgr.terminate(); + if (lobbyMgr) + lobbyMgr.terminate(); console.log(`Contract bundle uploaded!`); } catch (e) { - fs.rmSync(path.dirname(bundlePath), { recursive: true, force: true }); - if (instanceMgr) - await instanceMgr.terminate(); + if (lobbyMgr) + lobbyMgr.terminate(); throw e; } } @@ -475,50 +450,20 @@ class ReputationD { } if (retry) { - const ownerKeys = await CommonHelper.generateKeys(); - - const contractId = this.#generateContractId(universeInfo.universeIndex); - - // If we are the first in order acquire instance, Wait for first one if not. - const firstOrderedId = universeInfo.universeIndex * this.#universeSize; - let firstInstanceInfo; - if (universeInfo.orderedId != firstOrderedId) { - console.log(`Waiting until ${firstOrderedId} node is acquired...`); - firstInstanceInfo = await this.#waitForHostInstance(firstOrderedId, curMoment + 1).catch(console.error); - if (!firstInstanceInfo) { - console.log(`Skipping since first host failed for the moment ${curMoment + 1}.`) - return; - } - } - if (curMoment > createdMoment || curMoment > acquireSentMoment) { - console.log(`Acquiring the reputation contract instance${firstInstanceInfo ? ' (Observer)' : ''}...`); + console.log(`Acquiring the reputation contract instance...`); + + const ownerKeys = await CommonHelper.generateKeys(); + const contractId = this.#generateContractId(universeInfo.universeIndex); let requirement = { owner_pubkey: ownerKeys.publicKey, contract_id: contractId, image: this.#instanceImage, - config: { - contract: { - consensus: { - roundtime: this.#consensusRoundTime, - threshold: this.#consensusThreshold - } - }, - mesh: { - peer_discovery: { - enabled: false - } - } - } + config: {} }; - if (firstInstanceInfo) { - requirement.config.contract.unl = [firstInstanceInfo.pubkey]; - requirement.config.mesh.known_peers = [`${firstInstanceInfo.domain}:${firstInstanceInfo.peerPort}`]; - } - // Update the registry with the active instance count. const transaction = await tenantClient.acquireLeaseSubmit(this.hostClient.xrplAcc.address, requirement, { submissionRef: submissionRefs?.refs[1], ...this.#prepareHostClientFunctionOptions() }); if (!transaction) @@ -549,7 +494,7 @@ class ReputationD { this.cfg.contractInstance = { ...result.instance, created_moment: createdMoment, - owner_privatekey: ownerKeys.privateKey, + owner_privatekey: this.cfg.contractInstance.owner_privatekey, status: ContractStatus.Created }; this.#persistConfig(); @@ -582,8 +527,8 @@ class ReputationD { const timeQuota = momentSize * (1 - this.#preparationTimeQuota); const upperBound = Math.floor(momentStartTimestamp + momentSize); - const lowerBound = Math.floor(momentStartTimestamp + momentSize - (timeQuota * (1 - this.#reputationSendTimeSlice))); - const startTimestamp = Math.floor(lowerBound + ((upperBound - lowerBound) * this.#deploymentStartTimeQuota)); + const lowerBound = Math.floor(momentStartTimestamp + momentSize - (timeQuota * (1 - this.#reputationRegTimeQuota))); + const startTimestamp = Math.floor(lowerBound + ((upperBound - lowerBound) * this.#lobbyTimeQuota)); let startTimeout = 0; if (startTimestamp > currentTimestamp) @@ -593,20 +538,12 @@ class ReputationD { await new Promise((resolve) => setTimeout(resolve, startTimeout)); const instances = await this.#getInstancesInUniverse(universeInfo.universeIndex, curMoment + 1); - const overrideConfig = { - contract: { - unl: instances.map(p => `${p.pubkey}`), - bin_path: '/usr/bin/node', - bin_args: 'index.js' - }, - mesh: { - known_peers: instances.map(p => `${p.domain}:${p.peerPort}`) - } - }; + const unl = instances.map(p => `${p.pubkey}`); + const peers = instances.map(p => `${p.domain}:${p.peerPort}`); - console.log(`Deploying the reputation contract instance.`); - await this.#deployContract(this.cfg.contractInstance.domain, this.cfg.contractInstance.user_port, this.cfg.contractInstance.owner_privatekey, overrideConfig); - console.log(`Reputation contract instance deployed.`); + console.log(`Upgrading the reputation contract instance.`); + await this.#upgradeContract(this.cfg.contractInstance.domain, this.cfg.contractInstance.user_port, this.cfg.contractInstance.owner_privatekey, unl, peers); + console.log(`Reputation contract instance upgraded.`); // Mark as deployed. this.cfg.contractInstance.status = ContractStatus.Deployed; diff --git a/reputationd/lib/setup.js b/reputationd/lib/setup.js index 6c7a6d2..dc46421 100644 --- a/reputationd/lib/setup.js +++ b/reputationd/lib/setup.js @@ -46,15 +46,6 @@ class Setup { this.#saveConfig(baseConfig); } - async updateConfig(contractUrl) { - const cfg = this.#getConfig(false, false); - cfg.contractUrl = contractUrl - - this.#saveConfig(cfg); - - await Promise.resolve(); // async placeholder. - } - async prepareReputationAccount() { const config = this.#getConfig(); @@ -168,6 +159,7 @@ class Setup { try { const repInfo = await hostClient.getReputationInfo(); + const config = hostClient.config; await hostClient.disconnect(); const moment = await hostClient.getMoment(); @@ -179,8 +171,8 @@ class Setup { repInfo.moment = moment; } - const repClient = await evernode.HookClientFactory.create(evernode.HookTypes.reputation); - await repClient.connect(); + const repClient = await evernode.HookClientFactory.create(evernode.HookTypes.reputation, { config: config }); + await repClient.connect({ skipConfigs: true }); const globalInfo = await repClient.getReputationInfo(); diff --git a/reputationd/lib/util-helper.js b/reputationd/lib/util-helper.js index ce038fc..b512630 100644 --- a/reputationd/lib/util-helper.js +++ b/reputationd/lib/util-helper.js @@ -1,80 +1,4 @@ -const archiver = require('archiver'); -const fs = require('fs'); -const os = require('os'); const HotPocket = require('hotpocket-js-client'); -const { execSync } = require('child_process'); -const path = require('path'); - -const PREREQ_SCRIPT_CONTENT = `#!/bin/bash\n` + - `echo "Prerequisite installer script"\n` + - `exit 0` - -const CONSTANTS = { - contractCfgFile: "contract.config", - prerequisiteInstaller: "install.sh", - hpCfgOverrideFile: "hp.cfg.override", -}; - -class FsHelper { - static async archiveDirectory(sourcePath, destinationPath = null) { - return new Promise((resolve, reject) => { - if (!sourcePath) - reject("Invalid path was provided."); - - // Create a file to stream archive data to - const target = (destinationPath) ? `${destinationPath}/bundle.zip` : `${sourcePath}/bundle.zip` - const output = fs.createWriteStream(target); - const archive = archiver('zip', { - zlib: { level: 9 } - }); - - // Callbacks - output.on('close', () => { - resolve(target); - }); - - archive.on('error', (err) => { - reject(err); - }); - - // Pipe and append files - archive.pipe(output); - archive.directory(sourcePath, false); - - // Finalize - archive.finalize(); - }); - } -} - -class ContractHelper { - static async prepareContractBundle(contractUrl, hpOverrideCfg) { - const contractPrepPath = fs.mkdtempSync(path.join(os.tmpdir(), 'reputation-bundle-')); - const hpCfgOverridePath = path.resolve(contractPrepPath, CONSTANTS.hpCfgOverrideFile); - const prerequisiteInstaller = path.resolve(contractPrepPath, CONSTANTS.prerequisiteInstaller); - - // Download content inside contract directory; - execSync(`curl --silent -L ${contractUrl} --output ${contractPrepPath}/reputation-contract.tgz && - tar zxf ${contractPrepPath}/reputation-contract.tgz -C ${contractPrepPath}/ --strip-components=1 && - rm ${contractPrepPath}/reputation-contract.tgz`); - console.log("Placed contract content."); - - // Write hp.cfg.override file content. - fs.writeFileSync(hpCfgOverridePath, JSON.stringify(hpOverrideCfg, null, 4)); - console.log(`Prepared ${CONSTANTS.hpCfgOverrideFile} file.`); - - // Add prerequisite install script. - fs.writeFileSync(prerequisiteInstaller, PREREQ_SCRIPT_CONTENT, null); - - // Change permission pre-requisite installer. - fs.chmodSync(prerequisiteInstaller, 0o755); - console.log("Added prerequisite installer script."); - - const bundleTargetPath = fs.mkdtempSync(path.join(os.tmpdir(), 'reputation-bundle-target-')); - - return await FsHelper.archiveDirectory(contractPrepPath, bundleTargetPath); - } -} class CommonHelper { static async generateKeys(privateKey = null, format = 'hex') { @@ -87,7 +11,5 @@ class CommonHelper { } module.exports = { - FsHelper, - ContractHelper, CommonHelper } \ No newline at end of file diff --git a/reputationd/package-lock.json b/reputationd/package-lock.json index 8cd4be7..0697de8 100644 --- a/reputationd/package-lock.json +++ b/reputationd/package-lock.json @@ -7,9 +7,10 @@ "name": "reputationd", "dependencies": { "archiver": "5.3.1", - "evernode-js-client": "0.6.49", + "evernode-js-client": "0.6.51", "hotpocket-js-client": "0.5.6", - "uuid": "9.0.1" + "uuid": "9.0.1", + "ws": "8.17.0" }, "devDependencies": { "eslint": "8.3.0" @@ -996,9 +997,9 @@ } }, "node_modules/evernode-js-client": { - "version": "0.6.49", - "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.49.tgz", - "integrity": "sha512-h1OUH3Z+g0mtpbQPKelYxs62OUufDBJTo+AA21ymOzQ/3twSLIL3M5X5rDC6hPr0w2lT3HoyQTcXOJxlAneZBA==", + "version": "0.6.51", + "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.51.tgz", + "integrity": "sha512-152deKlva1zkV47LFqKenBjT2xlx37nXs0apyf3uBt4I694daaoIbyor2ymkEhSK1O0Mw7Yje9+WRh2YKh6i7A==", "dependencies": { "elliptic": "6.5.4", "libsodium-wrappers": "0.7.10", @@ -1302,6 +1303,26 @@ "libsodium": "^0.7.0" } }, + "node_modules/hotpocket-js-client/node_modules/ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -2361,15 +2382,15 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", + "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { diff --git a/reputationd/package.json b/reputationd/package.json index e14d621..df67e8b 100644 --- a/reputationd/package.json +++ b/reputationd/package.json @@ -5,10 +5,11 @@ "build": "npm run lint && ncc build app.js --minify -o dist" }, "dependencies": { - "evernode-js-client": "0.6.49", + "archiver": "5.3.1", + "evernode-js-client": "0.6.51", "hotpocket-js-client": "0.5.6", "uuid": "9.0.1", - "archiver": "5.3.1" + "ws": "8.17.0" }, "devDependencies": { "eslint": "8.3.0" diff --git a/src/hp_manager.cpp b/src/hp_manager.cpp index 078972e..2efee50 100644 --- a/src/hp_manager.cpp +++ b/src/hp_manager.cpp @@ -478,26 +478,18 @@ namespace hp return -1; } - LOG_INFO << "Destroying container " << container_name; - if (docker_remove(info.username, container_name) == -1 || + LOG_INFO << "Deleting instance " << container_name; + if (uninstall_user(info.username, info.assigned_ports, container_name) == -1 || // sqlite::update_status_in_container(db, container_name, CONTAINER_STATES[STATES::DESTROYED]) == -1) // Soft Deletion. sqlite::delete_hp_instance(db, container_name) == -1) // Permanent Deletion. { - error_msg = CONTAINER_DESTROY_ERROR; - LOG_ERROR << errno << ": Error destroying container " << container_name; + error_msg = USER_UNINSTALL_ERROR; return -1; } // Add the port pair of the destroyed container to the vacant port vector. if (std::find(vacant_ports.begin(), vacant_ports.end(), info.assigned_ports) == vacant_ports.end()) vacant_ports.push_back(info.assigned_ports); - // Remove user after destroying. - if (uninstall_user(info.username, info.assigned_ports, container_name) == -1) - { - error_msg = USER_UNINSTALL_ERROR; - return -1; - } - return 0; }