mirror of
https://github.com/EvernodeXRPL/sashimono.git
synced 2026-07-30 10:30:23 +00:00
Governance candidate vote implementation (#232)
This commit is contained in:
@@ -70,7 +70,6 @@ add_custom_command(TARGET sagent POST_BUILD
|
||||
COMMAND mv ./build/bootstrap_contract ./build/contract_template/contract_fs/seed/state/
|
||||
COMMAND ./installer/docker-install.sh ./build/dockerbin
|
||||
COMMAND npm --prefix ./mb-xrpl install && npm run --prefix ./mb-xrpl build
|
||||
COMMAND npm --prefix ./governance-helper install && npm run --prefix ./governance-helper build
|
||||
)
|
||||
|
||||
target_precompile_headers(sagent PUBLIC src/pchheader.hpp)
|
||||
@@ -82,7 +81,6 @@ add_custom_target(installer
|
||||
COMMAND bash -c "cp -r ./installer/{docker-install.sh,docker-registry-install.sh,docker-registry-uninstall.sh,prereq.sh,sashimono-install.sh,sashimono-uninstall.sh} ./build/installer/"
|
||||
COMMAND bash -c "cp -r ./dependencies/{user-cgcreate.sh,libblake3.so,licence.txt} ./build/installer/"
|
||||
COMMAND bash -c "cp -r ./mb-xrpl/dist ./build/installer/mb-xrpl"
|
||||
COMMAND bash -c "cp -r ./governance-helper/dist ./build/installer/governance-helper"
|
||||
COMMAND tar cfz ./build/installer.tar.gz --directory=./build/ installer
|
||||
COMMAND rm -r ./build/installer
|
||||
|
||||
|
||||
2
governance-helper/.gitignore
vendored
2
governance-helper/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
node_modules
|
||||
dist
|
||||
@@ -1,74 +0,0 @@
|
||||
const evernode = require("evernode-js-client");
|
||||
const process = require("process");
|
||||
const fs = require("fs");
|
||||
|
||||
const DATA_DIR = process.env.DATA_DIR || __dirname;
|
||||
const CONFIG_PATH = DATA_DIR + '/mb-xrpl.cfg';
|
||||
const SECRET_CONFIG_PATH = DATA_DIR + '/secret.cfg';
|
||||
|
||||
const getConfig = (skipSecret = false) => {
|
||||
// Only root user can access the secret config.
|
||||
if (!skipSecret) {
|
||||
if (process.getuid() != 0)
|
||||
throw `Please run with root privileges (sudo).`;
|
||||
}
|
||||
|
||||
if (!fs.existsSync(CONFIG_PATH))
|
||||
throw `Config file does not exist at ${CONFIG_PATH}`;
|
||||
else if (!fs.existsSync(SECRET_CONFIG_PATH))
|
||||
throw `Config file does not exist at ${SECRET_CONFIG_PATH}`;
|
||||
|
||||
let config = JSON.parse(fs.readFileSync(CONFIG_PATH).toString());
|
||||
const secretCfg = JSON.parse(fs.readFileSync(SECRET_CONFIG_PATH).toString());
|
||||
config.xrpl = { ...config.xrpl, ...secretCfg.xrpl };
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
const propose = async (hashFilePath, shortName) => {
|
||||
if (!hashFilePath)
|
||||
throw `Hash file param cannot be empty.`;
|
||||
else if (!fs.existsSync(hashFilePath))
|
||||
throw `Hash file ${hashFilePath} does not exist.`;
|
||||
else if (!shortName)
|
||||
throw `Short name cannot be empty.`;
|
||||
|
||||
const hashes = fs.readFileSync(hashFilePath).toString();
|
||||
|
||||
const config = getConfig();
|
||||
|
||||
const xrplApi = new evernode.XrplApi(config.xrpl.rippledServer);
|
||||
evernode.Defaults.set({
|
||||
governorAddress: config.xrpl.governorAddress,
|
||||
xrplApi: xrplApi
|
||||
})
|
||||
await xrplApi.connect();
|
||||
const hostClient = new evernode.HostClient(config.xrpl.address, config.xrpl.secret);
|
||||
|
||||
try {
|
||||
await hostClient.connect();
|
||||
await hostClient.propose(hashes, shortName);
|
||||
} finally {
|
||||
await hostClient.disconnect();
|
||||
await xrplApi.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
const app = async () => {
|
||||
const command = process.argv[2];
|
||||
const params = process.argv.slice(3);
|
||||
|
||||
if (command == "propose" && params.length == 2)
|
||||
await propose(params[0], params[1]);
|
||||
else {
|
||||
console.error("Supported governance commands:\n propose <hash_file> <short_name> - Propose new hook candidate");
|
||||
process.exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
app().then(() => {
|
||||
process.exit(0);
|
||||
}).catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
1585
governance-helper/package-lock.json
generated
1585
governance-helper/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "governance-helper",
|
||||
"scripts": {
|
||||
"build": "ncc build index.js --minify -o dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"evernode-js-client": "0.5.17"
|
||||
}
|
||||
}
|
||||
15
installer/jshelper/package-lock.json
generated
15
installer/jshelper/package-lock.json
generated
@@ -6,7 +6,7 @@
|
||||
"": {
|
||||
"name": "evernode-setup-helper",
|
||||
"dependencies": {
|
||||
"evernode-js-client": "0.5.16"
|
||||
"evernode-js-client": "0.5.17-beta-v3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
@@ -270,9 +270,10 @@
|
||||
"integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw=="
|
||||
},
|
||||
"node_modules/evernode-js-client": {
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.5.16.tgz",
|
||||
"integrity": "sha512-DZxbbc+RPnQXLYlipNIYcZUndOgIfEW4f+fB1cNA5ImAQL6mCeE2cpWrz6GULfOS8X99A2Fcxy7X7ny66QfT9g==",
|
||||
"version": "0.5.17-beta-v3.0",
|
||||
"resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.5.17-beta-v3.0.tgz",
|
||||
"integrity": "sha512-7FYBqh99uwOKBCABmSDDQF1VZHknU9sqOSAT/T68M3NsU7F+QrC6vFvHLvghPZJ83g6eyzqipZRIuHflRHLmtw==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"elliptic": "6.5.4",
|
||||
"libsodium-wrappers": "0.7.10",
|
||||
@@ -1109,9 +1110,9 @@
|
||||
"integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw=="
|
||||
},
|
||||
"evernode-js-client": {
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.5.16.tgz",
|
||||
"integrity": "sha512-DZxbbc+RPnQXLYlipNIYcZUndOgIfEW4f+fB1cNA5ImAQL6mCeE2cpWrz6GULfOS8X99A2Fcxy7X7ny66QfT9g==",
|
||||
"version": "0.5.17-beta-v3.0",
|
||||
"resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.5.17-beta-v3.0.tgz",
|
||||
"integrity": "sha512-7FYBqh99uwOKBCABmSDDQF1VZHknU9sqOSAT/T68M3NsU7F+QrC6vFvHLvghPZJ83g6eyzqipZRIuHflRHLmtw==",
|
||||
"requires": {
|
||||
"elliptic": "6.5.4",
|
||||
"libsodium-wrappers": "0.7.10",
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
"build": "ncc build index.js --minify -o dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"evernode-js-client": "0.5.17"
|
||||
"evernode-js-client": "0.5.17-beta-v3.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ if [ "$NO_MB" == "" ]; then
|
||||
# Configure message board users and register host.
|
||||
echo "Configuaring host registration on Evernode..."
|
||||
|
||||
cp -r "$script_dir"/{mb-xrpl,governance-helper} $SASHIMONO_BIN
|
||||
cp -r "$script_dir"/mb-xrpl $SASHIMONO_BIN
|
||||
|
||||
# Creating message board user (if not exists).
|
||||
if ! grep -q "^$MB_XRPL_USER:" /etc/passwd; then
|
||||
|
||||
@@ -32,7 +32,6 @@ export USER_BIN=/usr/bin
|
||||
export SASHIMONO_BIN=/usr/bin/sashimono
|
||||
export MB_XRPL_BIN=$SASHIMONO_BIN/mb-xrpl
|
||||
export DOCKER_BIN=$SASHIMONO_BIN/dockerbin
|
||||
export GOVERNANCE_HELPER_BIN=$SASHIMONO_BIN/governance-helper
|
||||
export SASHIMONO_DATA=/etc/sashimono
|
||||
export MB_XRPL_DATA=$SASHIMONO_DATA/mb-xrpl
|
||||
export SASHIMONO_SERVICE="sashimono-agent"
|
||||
@@ -90,26 +89,21 @@ if [ -f /etc/systemd/system/$SASHIMONO_SERVICE.service ] && [ -d $SASHIMONO_BIN
|
||||
&& echo "$evernode is already installed on your host. Use the 'evernode' command to manage your host." \
|
||||
&& exit 1
|
||||
|
||||
if ([ "$1" != "uninstall" ] && [ "$1" != "status" ] && [ "$1" != "list" ] && [ "$1" != "update" ] &&
|
||||
[ "$1" != "log" ] && [ "$1" != "applyssl" ] && [ "$1" != "transfer" ] && [ "$1" != "config" ] && [ "$1" != "delete" ]); then
|
||||
|
||||
output=$(DATA_DIR=$MB_XRPL_DATA /usr/bin/node $GOVERNANCE_HELPER_BIN "$@" 2>&1)
|
||||
exit_code=$?
|
||||
[ $exit_code -eq 2 ] && echomult "$evernode host management tool
|
||||
[ "$1" != "uninstall" ] && [ "$1" != "status" ] && [ "$1" != "list" ] && [ "$1" != "update" ] && [ "$1" != "log" ] && [ "$1" != "applyssl" ] && [ "$1" != "transfer" ] && [ "$1" != "config" ] && [ "$1" != "delete" ] && [ "$1" != "governance" ] \
|
||||
&& echomult "$evernode host management tool
|
||||
\nYour host is registered on $evernode.
|
||||
\nSupported commands:
|
||||
\n status - View $evernode registration info
|
||||
\n list - View contract instances running on this system
|
||||
\n log - Generate evernode log file.
|
||||
\n applyssl - Apply new SSL certificates for contracts.
|
||||
\n config - View and update host configuration.
|
||||
\n update - Check and install $evernode software updates
|
||||
\n transfer - Initiate an $evernode transfer for your machine
|
||||
\n delete - Remove an instance from the system and recreate the lease
|
||||
\n uninstall - Uninstall and deregister from $evernode"
|
||||
echo "$output"
|
||||
[ ! $exit_code -eq 0 ] && exit 1
|
||||
fi
|
||||
\nstatus - View $evernode registration info
|
||||
\nlist - View contract instances running on this system
|
||||
\nlog - Generate evernode log file.
|
||||
\napplyssl - Apply new SSL certificates for contracts.
|
||||
\nconfig - View and update host configuration.
|
||||
\nupdate - Check and install $evernode software updates
|
||||
\ntransfer - Initiate an $evernode transfer for your machine
|
||||
\ndelete - Remove an instance from the system and recreate the lease
|
||||
\nuninstall - Uninstall and deregister from $evernode
|
||||
\ngovernance - Governance candidate management" \
|
||||
&& exit 1
|
||||
elif [ -d $SASHIMONO_BIN ] ; then
|
||||
[ "$1" != "install" ] && [ "$1" != "uninstall" ] \
|
||||
&& echomult "$evernode host management tool
|
||||
@@ -1190,6 +1184,17 @@ elif [ "$mode" == "delete" ]; then
|
||||
|
||||
delete_instance "$2"
|
||||
|
||||
elif [ "$mode" == "governance" ]; then
|
||||
[ $2 == "help" ] && echomult "Governance management tool
|
||||
\nSupported commands:
|
||||
\npropose [hashFile] [shortName] - Propose new governance candidate.
|
||||
\nwithdraw [candidateId] - Withdraw proposed governance candidate.
|
||||
\nvote [candidateId] - Vote for a governance candidate.
|
||||
\nunvote [candidateId] - Remove vote from voted governance candidate.
|
||||
\nstatus - Get governance info of this host.
|
||||
\nhelp - Print help." && exit 0
|
||||
! MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN ${*:1} && exit 1
|
||||
|
||||
fi
|
||||
|
||||
[ "$mode" != "uninstall" ] && check_installer_pending_finish
|
||||
|
||||
1
mb-xrpl/.gitignore
vendored
1
mb-xrpl/.gitignore
vendored
@@ -3,4 +3,5 @@ dist
|
||||
log
|
||||
mb-xrpl.cfg
|
||||
secret.cfg
|
||||
governance.cfg
|
||||
mb-xrpl.sqlite
|
||||
@@ -3,6 +3,7 @@ const logger = require('./lib/logger');
|
||||
const { appenv } = require('./lib/appenv');
|
||||
const { Setup } = require('./lib/setup');
|
||||
const { MessageBoard } = require('./lib/message-board');
|
||||
const { GovernanceManager } = require('./lib/governance-manager');
|
||||
|
||||
async function main() {
|
||||
|
||||
@@ -20,9 +21,8 @@ async function main() {
|
||||
const leaseAmount = process.argv[7];
|
||||
const rippledServer = process.argv[8];
|
||||
const setup = new Setup();
|
||||
const secondaryAddrsCfg = {};
|
||||
const acc = await setup.setupHostAccount(accountAddress, accountSecret, rippledServer, governorAddress, domain, secondaryAddrsCfg);
|
||||
setup.newConfig(acc.address, acc.secret, governorAddress, parseFloat(leaseAmount), secondaryAddrsCfg, rippledServer);
|
||||
const acc = await setup.setupHostAccount(accountAddress, accountSecret, rippledServer, governorAddress, domain);
|
||||
setup.newConfig(acc.address, acc.secret, governorAddress, parseFloat(leaseAmount), rippledServer);
|
||||
}
|
||||
else if (process.argv.length === 7 && process.argv[2] === 'betagen') {
|
||||
const governorAddress = process.argv[3];
|
||||
@@ -30,9 +30,8 @@ async function main() {
|
||||
const leaseAmount = process.argv[5];
|
||||
const rippledServer = process.argv[6];
|
||||
const setup = new Setup();
|
||||
const secondaryAddrsCfg = {};
|
||||
const acc = await setup.generateBetaHostAccount(rippledServer, governorAddress, domain, secondaryAddrsCfg);
|
||||
setup.newConfig(acc.address, acc.secret, governorAddress, parseFloat(leaseAmount), secondaryAddrsCfg, rippledServer);
|
||||
const acc = await setup.generateBetaHostAccount(rippledServer, governorAddress, domain);
|
||||
setup.newConfig(acc.address, acc.secret, governorAddress, parseFloat(leaseAmount), rippledServer);
|
||||
}
|
||||
else if (process.argv.length === 13 && process.argv[2] === 'register') {
|
||||
await new Setup().register(process.argv[3], parseInt(process.argv[4]), parseInt(process.argv[5]),
|
||||
@@ -59,6 +58,9 @@ async function main() {
|
||||
else if (process.argv.length === 4 && process.argv[2] === 'delete') {
|
||||
await new Setup().deleteInstance(process.argv[3]);
|
||||
}
|
||||
else if (process.argv.length >= 4 && process.argv[2] === 'governance') {
|
||||
await GovernanceManager.handleCommand(process.argv[3], ...process.argv.slice(4));
|
||||
}
|
||||
else if (process.argv[2] === 'help') {
|
||||
console.log(`Usage:
|
||||
node index.js - Run message board.
|
||||
@@ -72,6 +74,7 @@ async function main() {
|
||||
node index.js upgrade [governorAddress] - Upgrade message board data.
|
||||
node index.js reconfig [leaseAmount] [totalInstanceCount] [rippledServer] - Update message board configuration.
|
||||
node index.js delete [containerName] - Delete an instance and recreate the lease offer
|
||||
node index.js governance [command] [args] - Governance handling.
|
||||
node index.js help - Print help.`);
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -14,6 +14,7 @@ appenv = {
|
||||
...appenv,
|
||||
CONFIG_PATH: appenv.DATA_DIR + '/mb-xrpl.cfg',
|
||||
SECRET_CONFIG_PATH: appenv.DATA_DIR + '/secret.cfg',
|
||||
GOVERNANCE_CONFIG_PATH: appenv.DATA_DIR + '/governance.cfg',
|
||||
LOG_PATH: appenv.DATA_DIR + '/log/mb-xrpl.log',
|
||||
DB_PATH: appenv.DATA_DIR + '/mb-xrpl.sqlite',
|
||||
DB_TABLE_NAME: 'leases',
|
||||
|
||||
181
mb-xrpl/lib/governance-manager.js
Normal file
181
mb-xrpl/lib/governance-manager.js
Normal file
@@ -0,0 +1,181 @@
|
||||
|
||||
const evernode = require('evernode-js-client');
|
||||
const fs = require('fs');
|
||||
const { appenv } = require('./appenv');
|
||||
const { ConfigHelper } = require('./config-helper');
|
||||
|
||||
function setEvernodeDefaults(governorAddress, rippledServer, xrplApi) {
|
||||
evernode.Defaults.set({
|
||||
governorAddress: governorAddress,
|
||||
rippledServer: rippledServer,
|
||||
xrplApi: xrplApi
|
||||
});
|
||||
}
|
||||
|
||||
class GovernanceManager {
|
||||
#cfgPath;
|
||||
|
||||
constructor(cfgPath) {
|
||||
this.#cfgPath = cfgPath;
|
||||
if (!fs.existsSync(cfgPath)) {
|
||||
this.#writeConfig({
|
||||
votes: {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#writeConfig(cfg) {
|
||||
fs.writeFileSync(this.#cfgPath, JSON.stringify(cfg, null, 2), { mode: 0o644 }); // Set file permission so only current user can read/write and others can read.
|
||||
}
|
||||
|
||||
async proposeCandidate(hashFilePath, shortName, hostClient) {
|
||||
if (!hashFilePath)
|
||||
throw `Hash file param cannot be empty.`;
|
||||
else if (!fs.existsSync(hashFilePath))
|
||||
throw `Hash file ${hashFilePath} does not exist.`;
|
||||
else if (!shortName)
|
||||
throw `Short name cannot be empty.`;
|
||||
|
||||
const hashes = fs.readFileSync(hashFilePath).toString();
|
||||
|
||||
let candidateId = null;
|
||||
try {
|
||||
await hostClient.connect();
|
||||
candidateId = await hostClient.propose(hashes, shortName).catch(e => {
|
||||
let err;
|
||||
if (e.code === "tecHOOK_REJECTED" && e.hookExecutionResult) {
|
||||
err = e.hookExecutionResult.map(o => o.message).join(', ');
|
||||
}
|
||||
throw err || e.code || 'PROPOSE_TX_ERR';
|
||||
});
|
||||
} finally {
|
||||
await hostClient.disconnect();
|
||||
}
|
||||
return candidateId;
|
||||
}
|
||||
|
||||
async withdrawCandidate(candidateId, hostClient) {
|
||||
if (!candidateId)
|
||||
throw `Candidate id cannot be empty.`;
|
||||
|
||||
try {
|
||||
await hostClient.connect();
|
||||
|
||||
const candidate = await hostClient.getCandidateById(candidateId);
|
||||
if (!candidate)
|
||||
throw `There's no governance candidate for the given candidate id.`;
|
||||
else if (candidate.ownerAddress !== hostClient.xrplAcc.address)
|
||||
throw `Trying to remove governance candidate which is not owned by host.`;
|
||||
|
||||
await hostClient.withdraw(candidateId).catch(e => {
|
||||
let err;
|
||||
if (e.code === "tecHOOK_REJECTED" && e.hookExecutionResult) {
|
||||
err = e.hookExecutionResult.map(o => o.message).join(', ');
|
||||
}
|
||||
throw err || e.code || 'WITHDRAW_TX_ERR';
|
||||
});
|
||||
} finally {
|
||||
await hostClient.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
async voteCandidate(candidateId, hostClient) {
|
||||
try {
|
||||
await hostClient.connect();
|
||||
|
||||
const candidate = await hostClient.getCandidateById(candidateId);
|
||||
if (!candidate)
|
||||
throw `There's no governance candidate for the given candidate id.`;
|
||||
|
||||
let cfg = this.getConfig();
|
||||
cfg.votes[candidateId] = evernode.EvernodeConstants.CandidateVote.Support;
|
||||
this.#writeConfig(cfg);
|
||||
} finally {
|
||||
await hostClient.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
clearCandidate(candidateId) {
|
||||
let cfg = this.getConfig();
|
||||
delete cfg.votes[candidateId];
|
||||
this.#writeConfig(cfg);
|
||||
}
|
||||
|
||||
unvoteCandidate(candidateId) {
|
||||
let cfg = this.getConfig();
|
||||
cfg.votes[candidateId] = evernode.EvernodeConstants.CandidateVote.Reject;
|
||||
this.#writeConfig(cfg);
|
||||
}
|
||||
|
||||
getVotes() {
|
||||
const cfg = this.getConfig();
|
||||
return cfg?.votes;
|
||||
}
|
||||
|
||||
async getStatus(hostClient) {
|
||||
let status = this.getConfig();
|
||||
try {
|
||||
await hostClient.connect();
|
||||
const candidate = await hostClient.getCandidateByOwner();
|
||||
|
||||
if (candidate)
|
||||
status = { ...status, candidate: candidate.uniqueId };
|
||||
} finally {
|
||||
await hostClient.disconnect();
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
getConfig() {
|
||||
return JSON.parse(fs.readFileSync(this.#cfgPath).toString());
|
||||
}
|
||||
|
||||
static async handleCommand(command, ...args) {
|
||||
let hostClient = null;
|
||||
|
||||
// Secret is needed for propose and withdraw in order to send the transaction.
|
||||
// Root access is needed in order to access the secret config.
|
||||
// Vote and unvote need write access for the governance config.
|
||||
if ((command == 'propose' || command === 'withdraw' || command === 'vote' || command === 'unvote') && process.getuid() !== 0)
|
||||
throw "Please run with root privileges (sudo).";
|
||||
|
||||
// Host client is only needed for some commands.
|
||||
if (command == 'propose' || command === 'withdraw' || command === 'vote' || command === 'status') {
|
||||
// Secret is needed for propose and withdraw in order to send the transaction
|
||||
const sashiMBConfig = ConfigHelper.readConfig(appenv.CONFIG_PATH,
|
||||
(command == 'propose' || command === 'withdraw') ? appenv.SECRET_CONFIG_PATH : null);
|
||||
setEvernodeDefaults(sashiMBConfig.xrpl.governorAddress, sashiMBConfig.xrpl.rippledServer);
|
||||
hostClient = new evernode.HostClient(sashiMBConfig.xrpl.address, sashiMBConfig.xrpl.secret);
|
||||
}
|
||||
const mgr = new GovernanceManager(appenv.GOVERNANCE_CONFIG_PATH);
|
||||
|
||||
if (args.length === 2 && command === 'propose') {
|
||||
const id = await mgr.proposeCandidate(args[0], args[1], hostClient);
|
||||
console.log(`Successfully proposed the candidate ${id}.`);
|
||||
}
|
||||
else if (args.length === 1 && command === 'withdraw') {
|
||||
await mgr.withdrawCandidate(args[0], hostClient);
|
||||
console.log(`Successfully withdrawn the candidate ${args[0]}.`);
|
||||
}
|
||||
else if (args.length === 1 && command === 'vote') {
|
||||
await mgr.voteCandidate(args[0], hostClient);
|
||||
console.log(`Voted for candidate ${args[0]}.`);
|
||||
}
|
||||
else if (args.length === 1 && command === 'unvote') {
|
||||
mgr.clearCandidate(args[0]);
|
||||
console.log(`Rejected vote for candidate ${args[0]}.`);
|
||||
}
|
||||
else if (args.length === 0 && command === 'status') {
|
||||
const status = await mgr.getStatus(hostClient);
|
||||
console.log(JSON.stringify(status, null, 2));
|
||||
}
|
||||
else {
|
||||
throw "Invalid args.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
GovernanceManager
|
||||
}
|
||||
@@ -4,6 +4,7 @@ const { SqliteDatabase, DataTypes } = require('./sqlite-handler');
|
||||
const { appenv } = require('./appenv');
|
||||
const { SashiCLI } = require('./sashi-cli');
|
||||
const { ConfigHelper } = require('./config-helper');
|
||||
const { GovernanceManager } = require('./governance-manager');
|
||||
|
||||
const LeaseStatus = {
|
||||
ACQUIRING: 'Acquiring',
|
||||
@@ -39,19 +40,14 @@ class MessageBoard {
|
||||
this.sashiDb = new SqliteDatabase(sashiDbPath);
|
||||
this.sashiTable = appenv.SASHI_TABLE_NAME;
|
||||
this.sashiConfigPath = sashiConfigPath;
|
||||
this.governanceManager = new GovernanceManager(appenv.GOVERNANCE_CONFIG_PATH);
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.readConfig();
|
||||
if (!this.cfg.version || !this.cfg.xrpl.address || !this.cfg.xrpl.secret || !this.cfg.xrpl.governorAddress || !this.cfg.xrpl.registryAddress ||
|
||||
!this.cfg.xrpl.heartbeatAddress)
|
||||
if (!this.cfg.version || !this.cfg.xrpl.address || !this.cfg.xrpl.secret || !this.cfg.xrpl.governorAddress)
|
||||
throw "Required cfg fields cannot be empty.";
|
||||
|
||||
console.log("Using,\n\tGovernor account " + this.cfg.xrpl.governorAddress);
|
||||
console.log("\tRegistry account " + this.cfg.xrpl.registryAddress);
|
||||
console.log("\tHeartbeat account " + this.cfg.xrpl.heartbeatAddress);
|
||||
console.log("Using rippled " + this.cfg.xrpl.rippledServer);
|
||||
|
||||
this.xrplApi = new evernode.XrplApi(this.cfg.xrpl.rippledServer);
|
||||
evernode.Defaults.set({
|
||||
governorAddress: this.cfg.xrpl.governorAddress,
|
||||
@@ -61,6 +57,13 @@ class MessageBoard {
|
||||
|
||||
this.hostClient = new evernode.HostClient(this.cfg.xrpl.address, this.cfg.xrpl.secret);
|
||||
await this.#connectHost();
|
||||
|
||||
console.log("Using,");
|
||||
console.log("\tGovernor account " + this.cfg.xrpl.governorAddress);
|
||||
console.log("\tRegistry account " + this.hostClient.config.registryAddress);
|
||||
console.log("\tHeartbeat account " + this.hostClient.config.heartbeatAddress);
|
||||
console.log("Using rippled " + this.cfg.xrpl.rippledServer);
|
||||
|
||||
// Get last heartbeat moment from the host info.
|
||||
let hostInfo = await this.hostClient.getRegistration();
|
||||
if (!hostInfo)
|
||||
@@ -211,6 +214,47 @@ class MessageBoard {
|
||||
ongoingHeartbeat = true;
|
||||
console.log(`Reporting heartbeat at Moment ${currentMoment}...`);
|
||||
|
||||
// Send heartbeat with votes, if there are votes in the config.
|
||||
let heartbeatSent = false;
|
||||
const votes = this.governanceManager.getVotes();
|
||||
if (votes) {
|
||||
const voteArr = (await Promise.all(Object.entries(votes).map(async ([key, value]) => {
|
||||
const candidate = await this.hostClient.getCandidateById(key);
|
||||
// Delete candidate vote if there's no such candidate.
|
||||
if (!candidate) {
|
||||
this.governanceManager.clearCandidate(key);
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
candidate: candidate.uniqueId,
|
||||
vote: value === evernode.EvernodeConstants.CandidateVote.Support ?
|
||||
evernode.EvernodeConstants.CandidateVote.Support :
|
||||
evernode.EvernodeConstants.CandidateVote.Reject,
|
||||
idx: candidate.index
|
||||
};
|
||||
}))).filter(v => v).sort((a, b) => a.idx - b.idx);
|
||||
if (voteArr && voteArr.length) {
|
||||
for (const vote of voteArr) {
|
||||
try {
|
||||
await this.hostClient.heartbeat(vote);
|
||||
heartbeatSent = true;
|
||||
}
|
||||
catch (e) {
|
||||
// Remove candidate from config in vote validation from the hook failed.
|
||||
if (e.code === 'VOTE_VALIDATION_ERR') {
|
||||
console.error(e.error);
|
||||
this.governanceManager.clearCandidate(vote.candidate);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return if at-least one heartbeat has been sent. Otherwise send heartbeat without votes.
|
||||
if (heartbeatSent)
|
||||
return;
|
||||
|
||||
try {
|
||||
await this.hostClient.heartbeat();
|
||||
this.lastHeartbeatMoment = currentMoment;
|
||||
|
||||
@@ -56,21 +56,20 @@ class Setup {
|
||||
ConfigHelper.writeConfig(cfg, appenv.CONFIG_PATH, appenv.SECRET_CONFIG_PATH);
|
||||
}
|
||||
|
||||
newConfig(address = "", secret = "", governorAddress = "", leaseAmount = 0, secondaryAddrsCfg = { registryAddress: "", heartbeatAddress: "" }, rippledServer = null) {
|
||||
newConfig(address = "", secret = "", governorAddress = "", leaseAmount = 0, rippledServer = null) {
|
||||
this.#saveConfig({
|
||||
version: appenv.MB_VERSION,
|
||||
xrpl: {
|
||||
address: address,
|
||||
secret: secret,
|
||||
governorAddress: governorAddress,
|
||||
...secondaryAddrsCfg,
|
||||
rippledServer: rippledServer || appenv.DEFAULT_RIPPLED_SERVER,
|
||||
leaseAmount: leaseAmount
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async setupHostAccount(address, secret, rippledServer, governorAddress, domain, secondaryAddrsCfg) {
|
||||
async setupHostAccount(address, secret, rippledServer, governorAddress, domain) {
|
||||
|
||||
setEvernodeDefaults(governorAddress, rippledServer);
|
||||
|
||||
@@ -84,9 +83,6 @@ class Setup {
|
||||
|
||||
console.log(`Preparing host account:${acc.address} (domain:${domain} registry:${hostClient.config.registryAddress})`);
|
||||
|
||||
secondaryAddrsCfg.registryAddress = hostClient.config.registryAddress;
|
||||
secondaryAddrsCfg.heartbeatAddress = hostClient.config.heartbeatAddress;
|
||||
|
||||
// Sometimes we may get 'account not found' error from rippled when some servers in the cluster
|
||||
// haven't still updated the ledger. In such cases, we retry several times before giving up.
|
||||
{
|
||||
@@ -114,7 +110,7 @@ class Setup {
|
||||
return acc;
|
||||
}
|
||||
|
||||
async generateBetaHostAccount(rippledServer, governorAddress, domain, secondaryAddrsCfg) {
|
||||
async generateBetaHostAccount(rippledServer, governorAddress, domain) {
|
||||
|
||||
setEvernodeDefaults(governorAddress, rippledServer);
|
||||
|
||||
@@ -125,9 +121,6 @@ class Setup {
|
||||
const hostClient = new evernode.HostClient(acc.address, acc.secret);
|
||||
await hostClient.connect();
|
||||
|
||||
secondaryAddrsCfg.registryAddress = hostClient.config.registryAddress;
|
||||
secondaryAddrsCfg.heartbeatAddress = hostClient.config.heartbeatAddress;
|
||||
|
||||
console.log(`Preparing host account:${acc.address} (domain:${domain} registry:${hostClient.config.registryAddress})`);
|
||||
|
||||
// Sometimes we may get 'account not found' error from rippled when some servers in the testnet cluster
|
||||
@@ -250,8 +243,6 @@ class Setup {
|
||||
const acc = this.#getConfig(false).xrpl;
|
||||
console.log(`Host account address: ${acc.address}`);
|
||||
console.log(`Governor address: ${acc?.governorAddress}`);
|
||||
console.log(`Registry address: ${acc?.registryAddress}`);
|
||||
console.log(`Heartbeat address: ${acc?.heartbeatAddress}`);
|
||||
|
||||
if (!isBasic) {
|
||||
setEvernodeDefaults(acc.governorAddress, acc.rippledServer);
|
||||
@@ -259,6 +250,8 @@ class Setup {
|
||||
try {
|
||||
const hostClient = new evernode.HostClient(acc.address);
|
||||
await hostClient.connect();
|
||||
console.log(`Registry address: ${hostClient.config.registryAddress}`);
|
||||
console.log(`Heartbeat address: ${hostClient.config.heartbeatAddress}`);
|
||||
|
||||
setEvernodeDefaults(acc.governorAddress, acc.rippledServer, hostClient.xrplApi);
|
||||
|
||||
@@ -300,8 +293,6 @@ class Setup {
|
||||
setEvernodeDefaults(governorAddress, cfg.xrpl.rippledServer, hostClient.xrplApi);
|
||||
|
||||
cfg.xrpl.governorAddress = governorAddress;
|
||||
cfg.xrpl.registryAddress = hostClient.config.registryAddress;
|
||||
cfg.xrpl.heartbeatAddress = hostClient.config.heartbeatAddress;
|
||||
|
||||
await hostClient.disconnect();
|
||||
}
|
||||
|
||||
15
mb-xrpl/package-lock.json
generated
15
mb-xrpl/package-lock.json
generated
@@ -6,7 +6,7 @@
|
||||
"": {
|
||||
"name": "mb-xrpl",
|
||||
"dependencies": {
|
||||
"evernode-js-client": "0.5.16",
|
||||
"evernode-js-client": "0.5.17-beta-v3.0",
|
||||
"sqlite3": "5.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -937,9 +937,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/evernode-js-client": {
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.5.16.tgz",
|
||||
"integrity": "sha512-DZxbbc+RPnQXLYlipNIYcZUndOgIfEW4f+fB1cNA5ImAQL6mCeE2cpWrz6GULfOS8X99A2Fcxy7X7ny66QfT9g==",
|
||||
"version": "0.5.17-beta-v3.0",
|
||||
"resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.5.17-beta-v3.0.tgz",
|
||||
"integrity": "sha512-7FYBqh99uwOKBCABmSDDQF1VZHknU9sqOSAT/T68M3NsU7F+QrC6vFvHLvghPZJ83g6eyzqipZRIuHflRHLmtw==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"elliptic": "6.5.4",
|
||||
"libsodium-wrappers": "0.7.10",
|
||||
@@ -3931,9 +3932,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"evernode-js-client": {
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.5.16.tgz",
|
||||
"integrity": "sha512-DZxbbc+RPnQXLYlipNIYcZUndOgIfEW4f+fB1cNA5ImAQL6mCeE2cpWrz6GULfOS8X99A2Fcxy7X7ny66QfT9g==",
|
||||
"version": "0.5.17-beta-v3.0",
|
||||
"resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.5.17-beta-v3.0.tgz",
|
||||
"integrity": "sha512-7FYBqh99uwOKBCABmSDDQF1VZHknU9sqOSAT/T68M3NsU7F+QrC6vFvHLvghPZJ83g6eyzqipZRIuHflRHLmtw==",
|
||||
"requires": {
|
||||
"elliptic": "6.5.4",
|
||||
"libsodium-wrappers": "0.7.10",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"build": "npm run lint && ncc build app.js --minify -o dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"evernode-js-client": "0.5.17",
|
||||
"evernode-js-client": "0.5.17-beta-v3.0",
|
||||
"sqlite3": "5.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
Reference in New Issue
Block a user