Setup related implementation

This commit is contained in:
chalith
2024-02-01 17:43:24 +05:30
parent e595d24804
commit 75fead01bc
6 changed files with 1857 additions and 1583 deletions

View File

@@ -37,87 +37,64 @@ const funcs = {
},
'validate-account': async (args) => {
checkParams(args, 3);
const rippledUrl = args[0];
const governorAddress = args[1];
const accountAddress = args[2];
const validateFor = args[3] || "register";
let result = { success: false, result: 'Unknown error.' };
let hostClient, xrplApi;
try {
checkParams(args, 3);
const rippledUrl = args[0];
const governorAddress = args[1];
const accountAddress = args[2];
await evernode.Defaults.useNetwork(NETWORK);
await evernode.Defaults.useNetwork(NETWORK);
evernode.Defaults.set({
rippledServer: rippledUrl,
governorAddress: governorAddress
});
evernode.Defaults.set({
rippledServer: rippledUrl,
governorAddress: governorAddress
});
const xrplApi = new evernode.XrplApi(null, { autoReconnect: false });
await xrplApi.connect();
xrplApi = new evernode.XrplApi(null, { autoReconnect: false });
await xrplApi.connect();
evernode.Defaults.set({
xrplApi: xrplApi
});
evernode.Defaults.set({
xrplApi: xrplApi
});
const hostClient = new evernode.HostClient(accountAddress, null);
hostClient = new evernode.HostClient(accountAddress, null);
if (!await hostClient.xrplAcc.exists())
return { success: false, result: "Account not found." };
if (!await hostClient.xrplAcc.exists())
result = { success: false, result: "Account not found." };
await hostClient.connect();
await hostClient.connect();
if (validateFor === "register" || validateFor === "re-register") {
// Check whether is there any missed NFT sell offers
try {
const registryAcc = new evernode.XrplAccount(hostClient.config.registryAddress, null);
const regUriToken = await hostClient.getRegistrationUriToken();
// Check whether host has a registration token.
const regUriToken = await hostClient.getRegistrationUriToken();
if (regUriToken)
result = { success: true, result: "HAS_REG_TOKEN" };
if (!regUriToken) {
const regInfo = await hostClient.getHostInfo(accountAddress);
const sellOffer = (await registryAcc.getURITokens()).find(o => o.Issuer == registryAcc.address && o.index == regInfo.uriTokenId && o.Amount);
if (sellOffer)
result = { success: true, result: "HAS_SELL_OFFER" };
if (regInfo) {
const sellOffer = (await registryAcc.getURITokens()).find(o => o.index == regInfo.uriTokenId && o.Amount);
if (sellOffer) {
await hostClient.disconnect();
await xrplApi.disconnect();
return { success: true };
}
}
}
} catch (e) {
await hostClient.disconnect();
await xrplApi.disconnect();
return { success: false, result: 'Error occurred in missed sell offers check.' };
}
}
const registered = await hostClient.isRegistered();
// For register validation the host should not be registered in evernode.
// For other validations host should be registered in evernode.
if (validateFor === "register") {
const registered = await hostClient.isRegistered();
if (registered)
return { success: false, result: "Host is already registered." };
}
else if (!registered)
return { success: false, result: "Host is not registered." };
result = { success: true, result: "REGISTERED" };
// Check whether pending transfer exists.
const isTransferPending = await hostClient.isTransferee();
// For register validation check the available balance enough for transfer and non transfer registrations.
// For other validations there should not be a pending transfer for the host.
if (validateFor === "register") {
const minEverBalance = isTransferPending ? 1 : hostClient.config.hostRegFee;
// Check whether pending transfer exists.
const transferPending = await hostClient.isTransferee();
const minEverBalance = transferPending ? 1 : hostClient.config.hostRegFee;
const currentBalance = await hostClient.getEVRBalance();
if (currentBalance < minEverBalance)
return { success: false, result: `The account needs minimum balance of ${minEverBalance} EVR. Current balance is ${currentBalance} EVR.` }
result = { success: false, result: `The account needs minimum balance of ${minEverBalance} EVR. Current balance is ${currentBalance} EVR.` }
} catch (e) {
result = { success: false, result: e };
} finally {
if (hostClient)
await hostClient.disconnect();
if (xrplApi)
await xrplApi.disconnect();
}
else if (isTransferPending)
return { success: false, result: "There's a pending transfer for this host." };
await hostClient.disconnect();
await xrplApi.disconnect();
return { success: true };
return result;
},
'validate-keys': async (args) => {

112
installer/setup-test.sh Executable file
View File

@@ -0,0 +1,112 @@
#!/bin/bash
# Evernode host setup tool to manage Sashimono installation and host registration.
# This script is also used as the 'evernode' cli alias after the installation.
# usage: ./setup.sh install
# surrounding braces are needed make the whole script to be buffered on client before execution.
{
instance_count=10
mb_error="Evernode Xahau message board exiting with error."
choice_result=""
function confirm() {
local prompt=$1
local defaultChoice=${2:-y} #Default choice is set to 'y' if $2 parameter is not provided.
local choiceDisplay="[Y/n]"
if [ "$defaultChoice" == "n" ]; then
choiceDisplay="[y/N]"
fi
echo -en "$prompt $choiceDisplay "
local yn=""
read yn </dev/tty
# Default choice is 'y'
[ -z $yn ] && yn="$defaultChoice"
while ! [[ $yn =~ ^[Yy|Nn]$ ]]; do
read -ep "'y' or 'n' expected: " yn </dev/tty
done
echo "" # Insert new line after answering.
[[ $yn =~ ^[Yy]$ ]] && return 0 || return 1 # 0 means success.
}
function choice() {
local prompt=$1
local choiceDisplay=${2:-y/n}
echo -en "$prompt [$choiceDisplay]? "
read choice_result </dev/tty
IFS='/'
read -ra ADDR <<<"$choiceDisplay"
while ! [[ "${ADDR[@]}" =~ $choice_result ]]; do
read -ep "[$choiceDisplay] expected: " choice_result </dev/tty
done
}
function choice_output() {
echo $choice_result
}
function rollback() {
echo "Rollbacking the instalation.."
exit 0
}
function abort() {
echo "Aborting the instalation.."
exit 0
}
function exec_mb() {
local res=$(MB_DATA_DIR="/home/chalith/Workspace/HotpocketDev/sashimono/mb-xrpl" node "/home/chalith/Workspace/HotpocketDev/sashimono/mb-xrpl/app.js" "$@" | tee /dev/fd/2)
echo $res
}
function burn_leases() {
local res=$(exec_mb burn-leases)
if [[ "$res" == *"$mb_error"* ]]; then
choice "An error occured while burning! What do you want to do" "retry/abort/rollback" && local input=$(choice_output)
if [ "$input" == "retry" ]; then
burn_leases
elif [ "$input" == "rollback" ]; then
rollback
else
abort
fi
fi
}
function mint_leases() {
local res=$(exec_mb mint-leases $instance_count)
if [[ "$res" == *"$mb_error"* ]]; then
res=$(echo "$res" | tail -n 2 | head -n 1)
if [[ "$res" == "LEASE_ERR"* ]]; then
if confirm "Do you want to burn minted tokens. (N will abort the installation)" "n"; then
burn_leases && mint_leases
else
abort
fi
else
choice "An error occured while minting! What do you want to do" "retry/abort/rollback" && local input=$(choice_output)
if [ "$input" == "retry" ]; then
mint_leases
elif [ "$input" == "rollback" ]; then
rollback
else
abort
fi
fi
fi
}
mint_leases
exit 0
# surrounding braces are needed make the whole script to be buffered on client before execution.
}

File diff suppressed because it is too large Load Diff

View File

@@ -35,6 +35,15 @@ async function main() {
await new Setup().register(process.argv[3], parseInt(process.argv[4]), parseInt(process.argv[5]),
parseInt(process.argv[6]), parseInt(process.argv[7]), parseInt(process.argv[8]), process.argv[9], parseInt(process.argv[10]), parseInt(process.argv[11]), process.argv[12], process.argv[13]);
}
else if (process.argv.length >= 3 && process.argv[2] === 'mint-leases') {
await new Setup().mintLeases(process.argv[3]);
}
else if (process.argv.length >= 2 && process.argv[2] === 'offer-leases') {
await new Setup().offerLeases();
}
else if (process.argv.length >= 2 && process.argv[2] === 'burn-leases') {
await new Setup().burnLeases();
}
else if (process.argv.length >= 3 && process.argv[2] === 'transfer') {
(process.argv[3]) ? await new Setup().transfer(process.argv[3]) : await new Setup().transfer();
}

View File

@@ -1,5 +1,4 @@
const https = require('https');
const { appenv } = require('./appenv');
const evernode = require('evernode-js-client');
const fs = require('fs');
@@ -125,13 +124,6 @@ class Setup {
await hostClient.register(countryCode, cpuMicroSec,
Math.floor((ramKb + swapKb) / 1000), Math.floor(diskKb / 1000), totalInstanceCount, cpuModelFormatted.substring(0, 40), cpuCount, cpuSpeed, description.replaceAll('_', ' '), emailAddress, { retryOptions: { maxRetryAttempts: MAX_TX_RETRY_ATTEMPTS, feeUplift: Math.floor(acc.affordableExtraFee / MAX_TX_RETRY_ATTEMPTS) } });
// Create lease offers.
console.log("Creating lease offers for instance slots...");
for (let i = 0; i < totalInstanceCount; i++) {
await hostClient.offerLease(i, acc.leaseAmount, appenv.TOS_HASH, config?.networking?.ipv6?.subnet ? UtilHelper.generateIPV6Address(config.networking.ipv6.subnet, i) : null, { retryOptions: { maxRetryAttempts: MAX_TX_RETRY_ATTEMPTS, feeUplift: Math.floor(acc.affordableExtraFee / MAX_TX_RETRY_ATTEMPTS) } });
console.log(`Created lease offer ${i + 1} of ${totalInstanceCount}.`);
}
break;
}
catch (err) {
@@ -148,6 +140,144 @@ class Setup {
await hostClient.disconnect();
}
async mintLeases(totalInstanceCount) {
const config = this.#getConfig();
const acc = config.xrpl;
await setEvernodeDefaults(acc.network, acc.governorAddress, acc.rippledServer);
const hostClient = new evernode.HostClient(acc.address, acc.secret);
await hostClient.connect();
// Update the Defaults with "xrplApi" of the client.
evernode.Defaults.set({
xrplApi: hostClient.xrplApi
});
try {
const leases = await hostClient.getLeases();
// Terminate if existing leases are inconsistent with current.
let lastIndex = 0;
if (leases.length) {
for (const l of leases) {
if (l.Amount && l.Amount.value !== acc.leaseAmount) {
throw 'LEASE_ERR: Lease amount is inconsistent with existing.';
}
const tokenInfo = evernode.UtilHelpers.decodeLeaseTokenUri(l.URI);
if (tokenInfo.leaseAmount !== acc.leaseAmount) {
throw 'LEASE_ERR: Lease amount is inconsistent with existing.';
}
const leaseIndex = tokenInfo.leaseIndex;
const outboundIP = tokenInfo.outboundIP;
if ((outboundIP && !config?.networking?.ipv6?.subnet) || (!outboundIP && config?.networking?.ipv6?.subnet)) {
throw 'LEASE_ERR: Outbound IP is inconsistent with existing.';
}
else if (outboundIP && config?.networking?.ipv6?.subnet) {
if (!UtilHelper.isSameIPV6Subnet(outboundIP, config?.networking?.ipv6?.subnet)) {
throw 'LEASE_ERR: Outbound IP is inconsistent with existing.';
}
}
if (leaseIndex > lastIndex) {
lastIndex = leaseIndex;
}
}
}
if (totalInstanceCount >= leases.length) {
// Create leases.
console.log("Minting leases for instance slots...");
for (let i = (leases.length > 0 ? (lastIndex + 1) : 0); i < totalInstanceCount; i++) {
await hostClient.mintLease(i, acc.leaseAmount, appenv.TOS_HASH, config?.networking?.ipv6?.subnet ? UtilHelper.generateIPV6Address(config.networking.ipv6.subnet, i) : null, { retryOptions: { maxRetryAttempts: MAX_TX_RETRY_ATTEMPTS, feeUplift: Math.floor(acc.affordableExtraFee / MAX_TX_RETRY_ATTEMPTS) } });
console.log(`Minted lease ${i + 1} of ${totalInstanceCount}.`);
}
}
else {
// Burn leases.
console.log("Burning previous leases...");
for (let i = totalInstanceCount; i < leases.length; i++) {
await hostClient.expireLease(leases[i].index, { retryOptions: { maxRetryAttempts: MAX_TX_RETRY_ATTEMPTS, feeUplift: Math.floor(acc.affordableExtraFee / MAX_TX_RETRY_ATTEMPTS) } });
console.log(`Burned lease ${i + 1} of ${leases.length}.`);
}
}
await hostClient.disconnect();
}
catch (e) {
await hostClient.disconnect();
throw e;
}
}
async offerLeases() {
const config = this.#getConfig();
const acc = config.xrpl;
await setEvernodeDefaults(acc.network, acc.governorAddress, acc.rippledServer);
const hostClient = new evernode.HostClient(acc.address, acc.secret);
await hostClient.connect();
// Update the Defaults with "xrplApi" of the client.
evernode.Defaults.set({
xrplApi: hostClient.xrplApi
});
try {
const unoffered = await hostClient.getUnofferedLeases();
if (unoffered.length > 0) {
// Create lease offers.
console.log("Creating lease offers for instance slots...");
let i = 0;
for (let t of unoffered) {
const uriInfo = evernode.UtilHelpers.decodeLeaseTokenUri(uriToken.URI);
if (uriInfo.leaseAmount == acc.leaseAmount) {
await hostClient.offerMintedLease(t.index, acc.leaseAmount, { retryOptions: { maxRetryAttempts: MAX_TX_RETRY_ATTEMPTS, feeUplift: Math.floor(acc.affordableExtraFee / MAX_TX_RETRY_ATTEMPTS) } });
console.log(`Created lease offer ${i + 1} of ${unoffered.length}.`);
}
else {
throw 'LEASE_ERR: Lease amounts are inconsistent.';
}
i++;
}
}
else {
throw 'LEASE_ERR: No unoffered leases.';
}
await hostClient.disconnect();
}
catch (e) {
await hostClient.disconnect();
throw e;
}
}
async burnLeases() {
const config = this.#getConfig();
const acc = config.xrpl;
await setEvernodeDefaults(acc.network, acc.governorAddress, acc.rippledServer);
const hostClient = new evernode.HostClient(acc.address, acc.secret);
await hostClient.connect();
// Update the Defaults with "xrplApi" of the client.
evernode.Defaults.set({
xrplApi: hostClient.xrplApi
});
try {
await this.burnMintedURITokens(hostClient, { retryOptions: { maxRetryAttempts: MAX_TX_RETRY_ATTEMPTS, feeUplift: Math.floor(acc.affordableExtraFee / MAX_TX_RETRY_ATTEMPTS) } });
await hostClient.disconnect();
}
catch (e) {
await hostClient.disconnect();
throw e;
}
}
async deregister(error = null) {
console.log("Deregistering host...");
const acc = this.#getConfig().xrpl;

View File

@@ -18,6 +18,27 @@ class UtilHelper {
return null;
}
static isSameIPV6Subnet(address1, address2) {
const [ip1, ip1PrefixLen] = address1.split('/');
const [ip2, ip2PrefixLen] = address2.split('/');
if (!(ip1 && ip2 && ip1PrefixLen && ip2PrefixLen && !isNaN(ip1PrefixLen) && !isNaN(ip2PrefixLen)))
return false;
try {
// This will return the normalized abbreviated subnet CIDR notation.
const ip1Cidr = ip6addr.createCIDR(ip1, parseInt(ip1PrefixLen));
const ip2Cidr = ip6addr.createCIDR(ip2, parseInt(ip2PrefixLen));
return (ip1Cidr === ip2Cidr)
}
catch {
// Silent catch so that we don't log exceptions to console.
// This will be treated as ip validation failure.
return false;
}
}
}
module.exports = {