Implemented initial steps for contract deployment

This commit is contained in:
chalith
2024-04-02 18:41:58 +05:30
parent a0f5fa2fb5
commit 3cd348c7b7
7 changed files with 180 additions and 18 deletions

View File

@@ -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) {

View File

@@ -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 = {

View File

@@ -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
}

View File

@@ -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);
}
}

View File

@@ -40,7 +40,8 @@ class Setup {
xrpl: {
address: address,
secretPath: secretPath
}
},
contractInstance: {}
};
this.#saveConfig(baseConfig);

View File

@@ -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",

View File

@@ -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"