From 3cd348c7b7908cfd7ee61175328164ba014bc4aa Mon Sep 17 00:00:00 2001 From: chalith Date: Tue, 2 Apr 2024 18:41:58 +0530 Subject: [PATCH] Implemented initial steps for contract deployment --- reputationd/app.js | 2 +- reputationd/lib/appenv.js | 3 +- reputationd/lib/cli-handler.js | 26 ++++++ reputationd/lib/reputationd.js | 146 ++++++++++++++++++++++++++++++--- reputationd/lib/setup.js | 3 +- reputationd/package-lock.json | 15 +++- reputationd/package.json | 3 +- 7 files changed, 180 insertions(+), 18 deletions(-) create mode 100644 reputationd/lib/cli-handler.js diff --git a/reputationd/app.js b/reputationd/app.js index a215127..c34f9b0 100644 --- a/reputationd/app.js +++ b/reputationd/app.js @@ -78,7 +78,7 @@ async function main() { console.log('Data dir: ' + appenv.DATA_DIR); console.log('Using message board config: ' + appenv.MB_XRPL_CONFIG_PATH); - const rep = new ReputationD(appenv.CONFIG_PATH, appenv.SECRET_CONFIG_PATH, appenv.MB_XRPL_CONFIG_PATH); + const rep = new ReputationD(appenv.CONFIG_PATH, appenv.SECRET_CONFIG_PATH, appenv.MB_XRPL_CONFIG_PATH, appenv.INSTANCE_IMAGE); await rep.init(); } catch (err) { diff --git a/reputationd/lib/appenv.js b/reputationd/lib/appenv.js index 78d7112..e669838 100644 --- a/reputationd/lib/appenv.js +++ b/reputationd/lib/appenv.js @@ -5,7 +5,8 @@ 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, - DATA_DIR: process.env.REPUTATIOND_DATA_DIR || __dirname + DATA_DIR: process.env.REPUTATIOND_DATA_DIR || __dirname, + INSTANCE_IMAGE: 'evernode/sashimono:hp.0.6.4-ubt.20.04-njs.20', } appenv = { diff --git a/reputationd/lib/cli-handler.js b/reputationd/lib/cli-handler.js new file mode 100644 index 0000000..162bba9 --- /dev/null +++ b/reputationd/lib/cli-handler.js @@ -0,0 +1,26 @@ +const { Buffer } = require('buffer'); +const { exec } = require("child_process"); + +class CliHelper { + static async listInstances() { + return await this.execCommand('evernode list'); + } + + static execCommand(command) { + return new Promise((resolve, reject) => { + exec(command, { stdio: 'pipe' }, (err, stdout, stderr) => { + if (err || stderr) { + reject(err || stderr); + return; + } + + let message = Buffer.from(stdout).toString(); + resolve(JSON.parse(message.substring(0, message.length - 1))); // Skipping the \n from the result. + }); + }) + } +} + +module.exports = { + CliHelper +} \ No newline at end of file diff --git a/reputationd/lib/reputationd.js b/reputationd/lib/reputationd.js index 50b92d6..f23718e 100644 --- a/reputationd/lib/reputationd.js +++ b/reputationd/lib/reputationd.js @@ -1,6 +1,10 @@ +const fs = require('fs'); const evernode = require('evernode-js-client'); +const crypto = require('crypto'); +const uuid = require('uuid'); const { appenv } = require('./appenv'); const { ConfigHelper } = require('./config-helper'); +const { CliHelper } = require('./cli-handler'); class ReputationD { #concurrencyQueue = { @@ -13,15 +17,22 @@ class ReputationD { #feeUpliftment = 0; #reportTimeQuota = 0.65; // Percentage of moment size. #contractInitTimeQuota = 0.1; // Percentage of moment size. + #scoreFilePath = `/home/#USER#/#INSTANCE#/contract_fs/mnt/opinion.txt` - constructor(configPath, secretConfigPath, mbXrplConfigPath) { - this.configPath = configPath; - this.secretConfigPath = secretConfigPath; - this.mbXrplConfigPath = mbXrplConfigPath; + #configPath; + #secretConfigPath; + #mbXrplConfigPath; + #instanceImage; + + constructor(configPath, secretConfigPath, mbXrplConfigPath, instanceImage) { + this.#configPath = configPath; + this.#secretConfigPath = secretConfigPath; + this.#mbXrplConfigPath = mbXrplConfigPath; + this.#instanceImage = instanceImage; } async init() { - this.readConfig(); + this.#readConfig(); if (!this.cfg.version || !this.cfg.xrpl.address || !this.cfg.xrpl.secret) throw "Required cfg fields cannot be empty."; @@ -279,16 +290,122 @@ class ReputationD { }, startTimeout); } + async #getUniverseInfo() { + // TODO: Collect the universe info. + return { + id: '', + hosts: '' + }; + } + + async #getInstancesInUniverse(universeId) { + // TODO: Collect the universe info. + return []; + } + + // Find the universe id and generate contract id. + async #generateContractId(universeId) { + // Generate a hash from the seed + const hash = crypto.createHash('sha1').update(universeId).digest('hex'); + // Use a portion of the hash to generate a random UUID + const id = uuid.v4({ + random: Buffer.from(hash.substring(0, 16), 'hex') + }); + + return id; + } + // Create and setup reputation contract. async #createReputationContract() { - // TODO: Acquire instance from self host and deploy reputation contract. + await this.#queueAction(async (submissionRefs) => { + submissionRefs.refs ??= [{}]; + // Check again wether the transaction is validated before retry. + const txHash = submissionRefs?.refs[0]?.submissionResult?.result?.tx_json?.hash; + if (txHash) { + const txResponse = await tenantClient.xrplApi.getTransactionValidatedResults(txHash); + if (txResponse && txResponse.code === "tesSUCCESS") { + console.log('Transaction is validated and success, Retry skipped!') + return; + } + } + + const tenantClient = new evernode.TenantClient(this.hostClient.reputationAcc.address, this.hostClient.reputationAcc.secret); + await tenantClient.connect(); + await tenantClient.prepareAccount(); + + const universeInfo = await this.#getUniverseInfo(); + const requirement = { + owner_pubkey: ownerPubkey, + contract_id: await this.#generateContractId(universeInfo.id), + image: this.#instanceImage, + config: { + contract: { + consensus: { + roundtime: 5000 + } + } + } + }; + + // Update the registry with the active instance count. + const result = await tenantClient.acquireLease(this.hostClient.xrplAcc.address, requirement, {}); + + await tenantClient.disconnect(); + + const acquiredTimestamp = Date.now(); + + // Assign ip to domain and outbound_ip for instance created from old sashimono version. + if ('ip' in result.instance) { + result.instance.domain = result.instance.ip; + delete result.instance.ip; + } + + this.cfg.contractInstance = { ...result.instance, created_timestamp: acquiredTimestamp }; + this.#persistConfig(); + + const instances = this.#getInstancesInUniverse(universeInfo.id); + const overrideConfig = { + unl: instances.map(p => `${p.pubkey}`), + contract: { + consensus: { + roundtime: 2000 + } + }, + mesh: { + known_peers: instances.map(p => `${p.domain}:${p.port}`) + } + }; + + // TODO: Deploy the contract. + }); + } + + async #getScores() { + const instanceName = this.cfg.contractInstance.name; + if (!instanceName) + throw 'No available reputation contract running.'; + + const instances = await CliHelper.listInstances(); + + const instance = instances.find(i => i.name === instanceName); + + if (!instance) { + this.cfg.contractInstance = {}; + this.#persistConfig(); + + throw 'No contract instance matching with the configuration.'; + } + + const path = this.#scoreFilePath.replace('#USER#', instance.user).replace('#INSTANCE#', instance.name); + + if (!fs.existsSync(path)) + throw 'Scores file does not exist.'; + + return JSON.parse(path); } // Reputation sender. async #sendReputations() { - // TODO: Get reputation scores from the contract. - const scores = {}; - await this.#queueAction(async (submissionRefs) => { submissionRefs.refs ??= [{}]; // Check again wether the transaction is validated before retry. @@ -301,6 +418,9 @@ class ReputationD { } } + // TODO: Get reputation scores from the contract. + const scores = await this.#getScores(); + let ongoingReputation = false; const currentMoment = await this.hostClient.getMoment(); @@ -329,12 +449,12 @@ class ReputationD { }, this.#reputationRetryCount, this.#reputationRetryDelay); } - readConfig() { - this.cfg = ConfigHelper.readConfig(this.configPath, this.secretConfigPath, this.mbXrplConfigPath); + #readConfig() { + this.cfg = ConfigHelper.readConfig(this.#configPath, this.#secretConfigPath, this.#mbXrplConfigPath); } - persistConfig() { - ConfigHelper.writeConfig(this.cfg, this.configPath); + #persistConfig() { + ConfigHelper.writeConfig(this.cfg, this.#configPath); } } diff --git a/reputationd/lib/setup.js b/reputationd/lib/setup.js index fd37b7e..a946e64 100644 --- a/reputationd/lib/setup.js +++ b/reputationd/lib/setup.js @@ -40,7 +40,8 @@ class Setup { xrpl: { address: address, secretPath: secretPath - } + }, + contractInstance: {} }; this.#saveConfig(baseConfig); diff --git a/reputationd/package-lock.json b/reputationd/package-lock.json index 729d29a..5e3d280 100644 --- a/reputationd/package-lock.json +++ b/reputationd/package-lock.json @@ -6,7 +6,8 @@ "": { "name": "reputationd", "dependencies": { - "evernode-js-client": "0.6.42" + "evernode-js-client": "0.6.42", + "uuid": "9.0.1" }, "devDependencies": { "eslint": "8.3.0" @@ -1934,6 +1935,18 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-compile-cache": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", diff --git a/reputationd/package.json b/reputationd/package.json index ab81132..067f268 100644 --- a/reputationd/package.json +++ b/reputationd/package.json @@ -5,7 +5,8 @@ "build": "npm run lint && ncc build app.js --minify -o dist" }, "dependencies": { - "evernode-js-client": "0.6.42" + "evernode-js-client": "0.6.42", + "uuid": "9.0.1" }, "devDependencies": { "eslint": "8.3.0"