mirror of
https://github.com/EvernodeXRPL/sashimono.git
synced 2026-08-19 01:11:22 +00:00
Reputationd service implementation (#373)
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -8,4 +8,5 @@ build
|
||||
**/CMakeCache.txt
|
||||
**/cmake_install.cmake
|
||||
**/CMakeFiles
|
||||
.vscode
|
||||
.vscode
|
||||
data-dir
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -2,3 +2,6 @@
|
||||
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
|
||||
|
||||
@@ -70,6 +70,8 @@ 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 ./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)
|
||||
@@ -82,6 +84,8 @@ add_custom_target(installer
|
||||
COMMAND bash -c "cp -r ./dependencies/{user-cgcreate.sh,libblake3.so} ./build/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/installer/reputation-contract"
|
||||
COMMAND tar cfz ./build/installer.tar.gz --directory=./build/ installer
|
||||
COMMAND rm -r ./build/installer
|
||||
|
||||
|
||||
1
evernode-reputation-contract
Submodule
1
evernode-reputation-contract
Submodule
Submodule evernode-reputation-contract added at 8240bafcfb
8
installer/jshelper/package-lock.json
generated
8
installer/jshelper/package-lock.json
generated
@@ -6,7 +6,7 @@
|
||||
"": {
|
||||
"name": "evernode-setup-helper",
|
||||
"dependencies": {
|
||||
"evernode-js-client": "0.6.42",
|
||||
"evernode-js-client": "0.6.49",
|
||||
"ip6addr": "0.2.5",
|
||||
"ripple-keypairs": "1.3.1"
|
||||
}
|
||||
@@ -405,9 +405,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/evernode-js-client": {
|
||||
"version": "0.6.42",
|
||||
"resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.42.tgz",
|
||||
"integrity": "sha512-lonrEXLdLJtLmxmkMztfLRRG/N2GaNJVecDTJPG0hEOUUBFPmFh6rchsjGv8H1oJOmIW/IPL/rkGtbVtn1rzag==",
|
||||
"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==",
|
||||
"dependencies": {
|
||||
"elliptic": "6.5.4",
|
||||
"libsodium-wrappers": "0.7.10",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"build": "ncc build index.js --minify -o dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"evernode-js-client": "0.6.42",
|
||||
"evernode-js-client": "0.6.49",
|
||||
"ip6addr": "0.2.5",
|
||||
"ripple-keypairs": "1.3.1"
|
||||
}
|
||||
|
||||
@@ -459,8 +459,6 @@ mkdir -p $SASHIMONO_DATA
|
||||
cp "$script_dir"/sashimono-uninstall.sh $SASHIMONO_BIN
|
||||
chmod +x $SASHIMONO_BIN/sashimono-uninstall.sh
|
||||
|
||||
# Setting up Sashimono admin group.
|
||||
! grep -q $SASHIADMIN_GROUP /etc/group && ! groupadd $SASHIADMIN_GROUP && echo "$SASHIADMIN_GROUP group creation failed." && abort
|
||||
|
||||
! set_cpu_info && echo "Fetching CPU info failed" && abort
|
||||
|
||||
@@ -517,10 +515,18 @@ fi
|
||||
# If installing with sudo, add current logged-in user to Sashimono admin group.
|
||||
[ -n "$SUDO_USER" ] && usermod -a -G $SASHIADMIN_GROUP $SUDO_USER
|
||||
|
||||
# First create the folder from root and then transfer ownership to the user
|
||||
# since the folder is created in /etc/sashimono directory.
|
||||
! mkdir -p $REPUTATIOND_DATA && echo "Could not create '$REPUTATIOND_DATA'. Make sure you are running as sudo." && exit 1
|
||||
# Change ownership to reputationd user.
|
||||
chown -R "$REPUTATIOND_USER":"$REPUTATIOND_USER" $REPUTATIOND_DATA
|
||||
|
||||
# Configure message board users and register host.
|
||||
echo "Configuaring host setup on Evernode..."
|
||||
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
|
||||
@@ -528,7 +534,7 @@ if ! grep -q "^$MB_XRPL_USER:" /etc/passwd; then
|
||||
|
||||
# Setting the ownership of the MB_XRPL_USER's home to MB_XRPL_USER expilcity.
|
||||
# NOTE : There can be user id mismatch, as we do not delete MB_XRPL_USER's home in the uninstallation even though the user is removed.
|
||||
chown -R "$MB_XRPL_USER":"$MB_XRPL_USER" /home/$MB_XRPL_USER
|
||||
chown -R "$MB_XRPL_USER":"$SASHIADMIN_GROUP" /home/$MB_XRPL_USER
|
||||
|
||||
secret_path=$(jq -r '.xrpl.secretPath' "$MB_XRPL_CONFIG")
|
||||
chown "$MB_XRPL_USER": $secret_path
|
||||
@@ -634,7 +640,7 @@ mb_user_runtime_dir="/run/user/$mb_user_id"
|
||||
|
||||
# Setting the ownership of the MB_XRPL_USER's home to MB_XRPL_USER expilcity.
|
||||
# NOTE : There can be user id mismatch, as we do not delete MB_XRPL_USER's home in the uninstallation even though the user is removed.
|
||||
chown -R "$MB_XRPL_USER":"$MB_XRPL_USER" $mb_user_dir
|
||||
chown -R "$MB_XRPL_USER":"$SASHIADMIN_GROUP" $mb_user_dir
|
||||
|
||||
# Setup env variable for the message board user.
|
||||
echo "
|
||||
@@ -691,4 +697,5 @@ fi
|
||||
|
||||
echo "Sashimono installed successfully."
|
||||
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -196,6 +196,22 @@ if grep -q "^$MB_XRPL_USER:" /etc/passwd; then
|
||||
|
||||
fi
|
||||
|
||||
# Reputatind---------------------
|
||||
# Check whether reputationd user exists. If so stop the reputationd service.
|
||||
if grep -q "^$REPUTATIOND_USER:" /etc/passwd; then
|
||||
|
||||
reputationd_user_dir=/home/"$REPUTATIOND_USER"
|
||||
reputationd_user_id=$(id -u "$REPUTATIOND_USER")
|
||||
reputationd_user_runtime_dir="/run/user/$reputationd_user_id"
|
||||
reputationd_service_path="$reputationd_user_dir"/.config/systemd/user/$REPUTATIOND_SERVICE.service
|
||||
# Remove reputationd service if exists.
|
||||
if [ -f $reputationd_service_path ]; then
|
||||
sudo -u "$REPUTATIOND_USER" XDG_RUNTIME_DIR="$reputationd_user_runtime_dir" systemctl --user stop $REPUTATIOND_SERVICE
|
||||
sudo -u "$REPUTATIOND_USER" XDG_RUNTIME_DIR="$reputationd_user_runtime_dir" systemctl --user disable $REPUTATIOND_SERVICE
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
# Uninstall all contract instance users---------------------------
|
||||
if [ "$UPGRADE" == "0" ]; then
|
||||
users=$(cut -d: -f1 /etc/passwd | grep "^$SASHIUSER_PREFIX" | sort)
|
||||
@@ -305,6 +321,14 @@ if grep -q "^$MB_XRPL_USER:" /etc/passwd; then
|
||||
sleep 0.5
|
||||
userdel -f "$MB_XRPL_USER"
|
||||
|
||||
echo "Deleting reputationd user..."
|
||||
# Killall command is not found in every linux systems, therefore pkill command is used.
|
||||
# A small timeout(0.5 second) is applied before deleting the user because it takes some time to kill all the processes
|
||||
loginctl disable-linger $REPUTATIOND_USER
|
||||
pkill -u $REPUTATIOND_USER # Kill any running processes.
|
||||
sleep 0.5
|
||||
userdel -f "$REPUTATIOND_USER"
|
||||
|
||||
fi
|
||||
|
||||
# Delete all the data and bin directories.
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
min_swap_mb=0
|
||||
evernode_alias=/usr/bin/evernode
|
||||
log_dir=/tmp/evernode
|
||||
reputationd_script_dir=$(dirname "$(realpath "$0")")
|
||||
root_user="root"
|
||||
|
||||
repo_owner="EvernodeXRPL"
|
||||
@@ -59,6 +60,8 @@
|
||||
|
||||
xrpl_address="-"
|
||||
xrpl_secret="-"
|
||||
reputationd_xrpl_secret="-"
|
||||
reputationd_xrpl_address="-"
|
||||
countrycode="-"
|
||||
email_address="-"
|
||||
tls_key_file="self"
|
||||
@@ -71,25 +74,32 @@
|
||||
export USER_BIN=/usr/bin
|
||||
export SASHIMONO_BIN=/usr/bin/sashimono
|
||||
export MB_XRPL_BIN=$SASHIMONO_BIN/mb-xrpl
|
||||
export REPUTATIOND_BIN=$SASHIMONO_BIN/reputationd
|
||||
export DOCKER_BIN=$SASHIMONO_BIN/dockerbin
|
||||
export SASHIMONO_DATA=/etc/sashimono
|
||||
export SASHIMONO_CONFIG="$SASHIMONO_DATA/sa.cfg"
|
||||
export MB_XRPL_DATA=$SASHIMONO_DATA/mb-xrpl
|
||||
export REPUTATIOND_DATA=$SASHIMONO_DATA/reputationd
|
||||
export MB_XRPL_CONFIG="$MB_XRPL_DATA/mb-xrpl.cfg"
|
||||
export REPUTATIOND_CONFIG="$REPUTATIOND_DATA/reputationd.cfg"
|
||||
export SASHIMONO_SERVICE="sashimono-agent"
|
||||
export CGCREATE_SERVICE="sashimono-cgcreate"
|
||||
export MB_XRPL_SERVICE="sashimono-mb-xrpl"
|
||||
export REPUTATIOND_SERVICE="sashimono-reputationd"
|
||||
export SASHIADMIN_GROUP="sashiadmin"
|
||||
export SASHIUSER_GROUP="sashiuser"
|
||||
export SASHIUSER_PREFIX="sashi"
|
||||
export MB_XRPL_USER="sashimbxrpl"
|
||||
export REPUTATIOND_USER="sashireputationd"
|
||||
export CG_SUFFIX="-cg"
|
||||
export EVERNODE_AUTO_UPDATE_SERVICE="evernode-auto-update"
|
||||
export MIN_OPERATIONAL_COST_PER_MONTH=5
|
||||
# 3 Month minimum operational duration is considered.
|
||||
export MIN_OPERATIONAL_DURATION=3
|
||||
export MIN_REPUTATION_COST_PER_MONTH=10
|
||||
|
||||
export NETWORK="${NETWORK:-mainnet}"
|
||||
#export NETWORK="${NETWORK:-mainnet}"
|
||||
export NETWORK="${NETWORK:-devnet}"
|
||||
|
||||
# Private docker registry (not used for now)
|
||||
export DOCKER_REGISTRY_USER="sashidockerreg"
|
||||
@@ -102,6 +112,9 @@
|
||||
# Default key path is set to a path in MB_XRPL_USER home
|
||||
default_key_filepath="/home/$MB_XRPL_USER/.evernode-host/.host-account-secret.key"
|
||||
|
||||
# Default reputationd key path is set to a path in REPUTATIOND_USER home
|
||||
default_reputationd_key_filepath="/home/$REPUTATIOND_USER/.evernode-host/.host-reputationd-secret.key"
|
||||
|
||||
# Helper to print multi line text.
|
||||
# (When passed as a parameter, bash auto strips spaces and indentation which is what we want)
|
||||
function echomult() {
|
||||
@@ -177,7 +190,7 @@
|
||||
echo "$evernode is already installed on your host. You cannot deregister without uninstalling. Use the 'evernode' command to manage your host." &&
|
||||
exit 1
|
||||
|
||||
[ "$1" != "uninstall" ] && [ "$1" != "status" ] && [ "$1" != "list" ] && [ "$1" != "update" ] && [ "$1" != "log" ] && [ "$1" != "applyssl" ] && [ "$1" != "transfer" ] && [ "$1" != "config" ] && [ "$1" != "delete" ] && [ "$1" != "governance" ] && [ "$1" != "regkey" ] && [ "$1" != "offerlease" ] &&
|
||||
[ "$1" != "uninstall" ] && [ "$1" != "status" ] && [ "$1" != "list" ] && [ "$1" != "update" ] && [ "$1" != "log" ] && [ "$1" != "applyssl" ] && [ "$1" != "transfer" ] && [ "$1" != "config" ] && [ "$1" != "delete" ] && [ "$1" != "governance" ] && [ "$1" != "regkey" ] && [ "$1" != "offerlease" ] && [ "$1" != "reputationd" ] &&
|
||||
echomult "$evernode host management tool
|
||||
\nYour have $evernode installed on your machine.
|
||||
\nSupported commands:
|
||||
@@ -192,7 +205,8 @@
|
||||
\nuninstall - Uninstall and deregister from $evernode.
|
||||
\ngovernance - Governance candidate management.
|
||||
\nregkey - Regular key management.
|
||||
\nofferlease - Create Lease offers for the instances." &&
|
||||
\nofferlease - Create Lease offers for the instances.
|
||||
\nreputationd - opt-in / opt-out for the Evernode reputation for reward distribution." &&
|
||||
exit 1
|
||||
else
|
||||
[ "$1" != "install" ] && [ "$1" != "transfer" ] && [ "$1" != "deregister" ] &&
|
||||
@@ -604,8 +618,8 @@
|
||||
|
||||
# Uncomment this if we want the user to manually change the auto-detected country code.
|
||||
# if [ -n "$countrycode" ] && ! confirm "Based on the internet address '$inetaddr' we have detected that your country
|
||||
# code is '$countrycode'. Do you want to specify a different country code" ; then
|
||||
# return 0
|
||||
# code is '$countrycode'. Do you want to specify a different country code" ; then
|
||||
# return 0
|
||||
# fi
|
||||
# countrycode=""
|
||||
|
||||
@@ -721,24 +735,30 @@
|
||||
! [[ $alloc_instcount -gt 0 ]] && echo "Invalid instance count." || break
|
||||
done
|
||||
|
||||
local max_ram_mb=$((ramKB / 1000))
|
||||
while true; do
|
||||
read -ep "Specify the total memory in megabytes to distribute among all contract instances: " ramMB </dev/tty
|
||||
! [[ $ramMB -gt 0 ]] && echo "Invalid memory size." && continue
|
||||
[[ $ramMB -lt $min_ram_mb ]] && echo "Minimum memory size shoule be "$min_ram_mb" MB." && continue
|
||||
[[ $ramMB -lt $min_ram_mb ]] && echo "Minimum memory size should be "$min_ram_mb" MB." && continue
|
||||
[[ $ramMB -gt $max_ram_mb ]] && echo "Insufficient memory on your host. Maximum available memory is "$max_ram_mb" MB." && continue
|
||||
break
|
||||
done
|
||||
|
||||
local max_swap_mb=$((swapKB / 1000))
|
||||
while true; do
|
||||
read -ep "Specify the total Swap in megabytes to distribute among all contract instances: " swapMB </dev/tty
|
||||
! [[ $swapMB -gt 0 ]] && echo "Invalid swap size." && continue
|
||||
[[ $swapMB -lt $min_swap_mb ]] && echo "Minimum swap size shoule be "$min_swap_mb" MB." && continue
|
||||
[[ $swapMB -lt $min_swap_mb ]] && echo "Minimum swap size should be "$min_swap_mb" MB." && continue
|
||||
[[ $swapMB -gt $max_swap_mb ]] && echo "Insufficient swap on your host. Maximum available swap is "$max_swap_mb" MB." && continue
|
||||
break
|
||||
done
|
||||
|
||||
local max_disk_mb=$((diskKB / 1000))
|
||||
while true; do
|
||||
read -ep "Specify the total disk space in megabytes to distribute among all contract instances: " diskMB </dev/tty
|
||||
! [[ $diskMB -gt 0 ]] && echo "Invalid disk size." && continue
|
||||
[[ $diskMB -lt $min_disk_mb ]] && echo "Minimum disk size shoule be "$min_disk_mb" MB." && continue
|
||||
[[ $diskMB -lt $min_disk_mb ]] && echo "Minimum disk size should be "$min_disk_mb" MB." && continue
|
||||
[[ $diskMB -gt $max_disk_mb ]] && echo "Insufficient disk on your host. Maximum available disk is "$max_disk_mb" MB." && continue
|
||||
break
|
||||
done
|
||||
|
||||
@@ -865,13 +885,22 @@
|
||||
}
|
||||
|
||||
function generate_keys() {
|
||||
local is_reputationd=false
|
||||
if [[ "$1" == "reputationd" ]]; then
|
||||
is_reputationd=true
|
||||
fi
|
||||
while true; do
|
||||
account_json=$(exec_jshelper generate-account) && break
|
||||
echo "Error occurred in account setting up."
|
||||
confirm "\nDo you want to retry?\nPressing 'n' would terminate the installation." || exit 1
|
||||
done
|
||||
xrpl_address=$(jq -r '.address' <<<"$account_json")
|
||||
xrpl_secret=$(jq -r '.secret' <<<"$account_json")
|
||||
if $is_reputationd; then
|
||||
reputationd_xrpl_address=$(jq -r '.address' <<<"$account_json")
|
||||
reputationd_xrpl_secret=$(jq -r '.secret' <<<"$account_json")
|
||||
else
|
||||
xrpl_address=$(jq -r '.address' <<<"$account_json")
|
||||
xrpl_secret=$(jq -r '.secret' <<<"$account_json")
|
||||
fi
|
||||
}
|
||||
|
||||
read_fallback_rippled_servers_res="-"
|
||||
@@ -1025,7 +1054,7 @@
|
||||
|
||||
if [ "$key_file_path" == "$default_key_filepath" ]; then
|
||||
parent_directory=$(dirname "$key_file_path")
|
||||
chmod -R 500 "$parent_directory" &&
|
||||
chmod -R 550 "$parent_directory" &&
|
||||
chown -R $MB_XRPL_USER: "$parent_directory" || {
|
||||
echomult "Error occurred in permission and ownership assignment of key file directory."
|
||||
exit 1
|
||||
@@ -1046,7 +1075,7 @@
|
||||
xrpl_address=$(jq -r '.address' <<<"$account_json")
|
||||
xrpl_secret=$(jq -r '.secret' <<<"$account_json")
|
||||
|
||||
chmod 400 "$key_file_path" &&
|
||||
chmod 440 "$key_file_path" &&
|
||||
chown $MB_XRPL_USER: $key_file_path || {
|
||||
echomult "Error occurred in permission and ownership assignment of key file."
|
||||
exit 1
|
||||
@@ -1072,7 +1101,7 @@
|
||||
fi
|
||||
|
||||
echo "{ \"xrpl\": { \"secret\": \"$xrpl_secret\" } }" >"$key_file_path" &&
|
||||
chmod 400 "$key_file_path" &&
|
||||
chmod 440 "$key_file_path" &&
|
||||
chown $MB_XRPL_USER: $key_file_path &&
|
||||
echomult "Key file saved successfully at $key_file_path" || {
|
||||
echomult "Error occurred in permission and ownership assignment of key file."
|
||||
@@ -1082,6 +1111,83 @@
|
||||
fi
|
||||
}
|
||||
|
||||
function set_host_reputationd_account() {
|
||||
|
||||
confirm "\nDo you want to use the default key file path ${default_reputationd_key_filepath} to save the new account key?" && reputationd_key_file_path=$default_reputationd_key_filepath
|
||||
|
||||
if [ "$reputationd_key_file_path" != "$default_reputationd_key_filepath" ]; then
|
||||
while true; do
|
||||
read -ep "Specify the preferred key file path: " key_file_path </dev/tty
|
||||
parent_directory=$(dirname "$reputationd_key_file_path")
|
||||
|
||||
canonicalized_directory=$(realpath "$parent_directory")
|
||||
root_directory="/root"
|
||||
canonicalized_root=$(realpath "$root_directory")
|
||||
|
||||
if [[ "$canonicalized_directory" == "$canonicalized_root"* ]]; then
|
||||
echo "Key should not be located in /root directory." && continue
|
||||
fi
|
||||
|
||||
! [ -e "$parent_directory" ] && echo "Invalid directory path." || break
|
||||
done
|
||||
fi
|
||||
|
||||
reputationd_key_dir=$(dirname "$reputationd_key_file_path")
|
||||
if [ ! -d "$reputationd_key_dir" ]; then
|
||||
mkdir -p "$reputationd_key_dir"
|
||||
fi
|
||||
|
||||
if [ "$reputationd_key_file_path" == "$default_reputationd_key_filepath" ]; then
|
||||
parent_directory=$(dirname "$reputationd_key_file_path")
|
||||
chmod -R 550 "$parent_directory" &&
|
||||
chown -R $REPUTATIOND_USER: "$parent_directory" || {
|
||||
echomult "Error occurred in permission and ownership assignment of key file directory."
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
|
||||
if [ -e "$reputationd_key_file_path" ]; then
|
||||
if confirm "The file '$reputationd_key_file_path' already exists. Do you want to continue using that key file?\nPressing 'n' would terminate the installation."; then
|
||||
echomult "Continuing with the existing key file."
|
||||
reputationd_existing_secret=$(jq -r '.xrpl.secret' "$reputationd_key_file_path" 2>/dev/null)
|
||||
if [ "$reputationd_existing_secret" != "null" ] && [ "$reputationd_existing_secret" != "-" ]; then
|
||||
while true; do
|
||||
account_json=$(exec_jshelper generate-account $reputationd_existing_secret) && break
|
||||
echo "Error occurred when existing account retrieval."
|
||||
confirm "\nDo you want to retry?\nPressing 'n' would terminate the installation." || return 1
|
||||
done
|
||||
|
||||
reputationd_xrpl_address=$(jq -r '.address' <<<"$account_json")
|
||||
reputationd_xrpl_secret=$(jq -r '.secret' <<<"$account_json")
|
||||
|
||||
chmod 440 "$reputationd_key_file_path" &&
|
||||
chown $REPUTATIOND_USER: $reputationd_key_file_path || {
|
||||
echomult "Error occurred in permission and ownership assignment of key file."
|
||||
exit 1
|
||||
}
|
||||
|
||||
echomult "Retrived account details via secret.\n"
|
||||
return 0
|
||||
else
|
||||
echomult "Error: Existing secret file does not have the expected format."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
generate_keys "reputationd"
|
||||
|
||||
echo "{ \"xrpl\": { \"secret\": \"$reputationd_xrpl_secret\" } }" >"$reputationd_key_file_path" &&
|
||||
chmod 440 "$reputationd_key_file_path" &&
|
||||
chown $REPUTATIOND_USER: $reputationd_key_file_path &&
|
||||
echomult "Key file saved successfully at $reputationd_key_file_path" || {
|
||||
echomult "Error occurred in permission and ownership assignment of key file."
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
}
|
||||
|
||||
function prepare_host() {
|
||||
([ -z $rippled_server ] || [ -z $xrpl_address ] || [ -z $key_file_path ] || [ -z $xrpl_secret ] || [ -z $inetaddr ]) && echo "No params specified." && return 1
|
||||
|
||||
@@ -1127,6 +1233,9 @@
|
||||
break
|
||||
confirm "\nDo you want to re-check the balance?\nPressing 'n' would terminate the installation." || exit 1
|
||||
done
|
||||
|
||||
# Adding 2 second sleep to avoid account not found.
|
||||
sleep 2
|
||||
fi
|
||||
|
||||
echomult "\nPreparing host account..."
|
||||
@@ -1214,7 +1323,7 @@ WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer
|
||||
|
||||
local service_removed=false
|
||||
|
||||
# Remove Xahau message board service if exists.
|
||||
# Remove auto updater service if exists.
|
||||
local service_path="/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer"
|
||||
if [ -f $service_path ]; then
|
||||
echo "Removing Evernode auto update timer..."
|
||||
@@ -1279,6 +1388,24 @@ WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer
|
||||
done
|
||||
fi
|
||||
|
||||
# Reputationd
|
||||
# Create REPUTATIOND_USER if does not exists..
|
||||
if ! grep -q "^$REPUTATIOND_USER:" /etc/passwd; then
|
||||
useradd --shell /usr/sbin/nologin -m $REPUTATIOND_USER 2>/dev/null
|
||||
|
||||
# Setting the ownership of the REPUTATIOND_USER's home to REPUTATIOND_USER expilcity.
|
||||
# NOTE : There can be user id mismatch, as we do not delete REPUTATIOND_USER's home in the uninstallation even though the user is removed.
|
||||
chown -R "$REPUTATIOND_USER":"$SASHIADMIN_GROUP" /home/$REPUTATIOND_USER
|
||||
|
||||
fi
|
||||
|
||||
# Assign reputationd user priviledges.
|
||||
if ! id -nG "$REPUTATIOND_USER" | grep -qw "$SASHIADMIN_GROUP"; then
|
||||
usermod --lock $REPUTATIOND_USER
|
||||
usermod -a -G $SASHIADMIN_GROUP $REPUTATIOND_USER
|
||||
loginctl enable-linger $REPUTATIOND_USER # Enable lingering to support service installation.
|
||||
fi
|
||||
|
||||
# Filter logs with STAGE prefix and ommit the prefix when echoing.
|
||||
# If STAGE log contains -p arg, move the cursor to previous log line and overwrite the log.
|
||||
! UPGRADE=$upgrade EVERNODE_REGISTRY_ADDRESS=$registry_address ./sashimono-install.sh $inetaddr $init_peer_port $init_user_port $countrycode $alloc_instcount \
|
||||
@@ -1298,6 +1425,30 @@ WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer
|
||||
|
||||
# Write the verison timestamp to a file for later updated version comparison.
|
||||
echo $installer_version_timestamp >$SASHIMONO_DATA/$installer_version_timestamp_file
|
||||
if [ "$upgrade" == "0" ]; then
|
||||
if confirm "\nWould you like to opt-in to the Evernode reputation and reward system?"; then
|
||||
if ! configure_reputationd 0; then
|
||||
echomult "\nError occured configuring ReputationD!!\n You can retry opting-in by executing 'evernode reputationd' after installation.\n"
|
||||
else
|
||||
echomult "\nReputationD configuration successfull!!\n"
|
||||
fi
|
||||
else
|
||||
echomult "\nSkipped from opting-in Evernode reputation and reward system.\nYou can opt-in later by using 'evernode reputationd' command.\n"
|
||||
fi
|
||||
else
|
||||
#[ "$upgrade" == "1" ]
|
||||
if sudo -u "$REPUTATIOND_USER" [ -f "/home/$REPUTATIOND_USER/.config/systemd/user/$REPUTATIOND_SERVICE.service" ]; then
|
||||
#reputationd_enabled=true
|
||||
echo "Configuring Evernode reputation and reward system."
|
||||
if ! configure_reputationd 1; then
|
||||
echomult "\nError occured configuring ReputationD!!\n You can retry opting-in by executing 'evernode reputationd' after installation.\n"
|
||||
else
|
||||
echomult "\nReputationD configuration successfull!!\n"
|
||||
fi
|
||||
else
|
||||
echo "You are not opted-in to Evernode reputation and reward system."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function check_exisiting_contracts() {
|
||||
@@ -1372,6 +1523,11 @@ WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer
|
||||
}
|
||||
|
||||
function create_log() {
|
||||
if sudo -u "$REPUTATIOND_USER" [ -f "/home/$REPUTATIOND_USER/.config/systemd/user/$REPUTATIOND_SERVICE.service" ]; then
|
||||
reputationd_enabled=true
|
||||
else
|
||||
reputationd_enabled=false
|
||||
fi
|
||||
tempfile=$(mktemp /tmp/evernode.XXXXXXXXX.log)
|
||||
{
|
||||
echo "System:"
|
||||
@@ -1389,6 +1545,13 @@ WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer
|
||||
echo ""
|
||||
echo "Message board log:"
|
||||
sudo -u sashimbxrpl bash -c journalctl --user -u sashimono-mb-xrpl | tail -n 200
|
||||
echo ""
|
||||
if [[ "$reputationd_enabled" == "true" ]]; then
|
||||
echo "Reputationd log:"
|
||||
sudo -u sashireputationd bash -c journalctl --user -u sashimono-reputationd | tail -n 200
|
||||
else
|
||||
echo "Reputation and reward system is not enabled."
|
||||
fi
|
||||
} >"$tempfile" 2>&1
|
||||
echo "Evernode log saved to $tempfile"
|
||||
}
|
||||
@@ -1444,7 +1607,20 @@ WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer
|
||||
local sashimono_mb_xrpl_status=$(sudo -u "$MB_XRPL_USER" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user is-active $MB_XRPL_SERVICE)
|
||||
echo "Sashimono agent status: $sashimono_agent_status"
|
||||
echo "Sashimono message board status: $sashimono_mb_xrpl_status"
|
||||
echo -e "\nYour account details are stored in $MB_XRPL_DATA/mb-xrpl.cfg"
|
||||
echo -e "\nYour registration account details are stored in $MB_XRPL_DATA/mb-xrpl.cfg"
|
||||
echo ""
|
||||
if sudo -u "$REPUTATIOND_USER" [ -f "/home/$REPUTATIOND_USER/.config/systemd/user/$REPUTATIOND_SERVICE.service" ]; then
|
||||
reputationd_enabled=true
|
||||
else
|
||||
reputationd_enabled=false
|
||||
fi
|
||||
local reputationd_user_id=$(id -u "$REPUTATIOND_USER")
|
||||
local reputationd_user_runtime_dir="/run/user/$reputationd_user_id"
|
||||
local evernode_reputationd_status=$(sudo -u "$REPUTATIOND_USER" XDG_RUNTIME_DIR="$reputationd_user_runtime_dir" systemctl --user is-active $REPUTATIOND_SERVICE)
|
||||
echo "Evernode reputationd status: $evernode_reputationd_status"
|
||||
if [[ $reputationd_enabled == true ]]; then
|
||||
echo -e "\nYour reputationd account details are stored in $REPUTATIOND_DATA/reputation.cfg"
|
||||
fi
|
||||
}
|
||||
|
||||
function get_country_code() {
|
||||
@@ -1503,7 +1679,7 @@ WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer
|
||||
}
|
||||
|
||||
function reconfig_sashi() {
|
||||
echomult "Configuaring sashimono...\n"
|
||||
echomult "configuring sashimono...\n"
|
||||
|
||||
! $SASHIMONO_BIN/sagent reconfig $SASHIMONO_DATA $alloc_instcount $alloc_cpu $alloc_ramKB $alloc_swapKB $alloc_diskKB &&
|
||||
echomult "There was an error in updating sashimono configuration." && return 1
|
||||
@@ -1540,7 +1716,7 @@ WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer
|
||||
}
|
||||
|
||||
function reconfig_mb() {
|
||||
echomult "Configuaring message board...\n"
|
||||
echomult "configuring message board...\n"
|
||||
|
||||
! sudo -u $MB_XRPL_USER MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN reconfig $lease_amount $alloc_instcount $rippled_server $ipv6_subnet $ipv6_net_interface $extra_txn_fee $fallback_rippled_servers &&
|
||||
echo "There was an error in updating message board configuration." && return 1
|
||||
@@ -1598,12 +1774,31 @@ WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer
|
||||
\n Instance count: $max_instance_count\n" && exit 0
|
||||
|
||||
local help_text="Usage: evernode config resources | evernode config resources <memory MB> <swap MB> <disk MB> <max instance count>\n"
|
||||
[ ! -z $ramMB ] && [[ $ramMB != 0 ]] && (! validate_positive_decimal $ramMB || [[ $ramMB -lt $min_ram_mb ]]) &&
|
||||
echomult "Invalid memory size $([[ $min_ram_mb != 0 ]] && echo "(Minimum should be "$min_ram_mb" MB)" || echo "").\n $help_text" && exit 1
|
||||
[ ! -z $swapMB ] && [[ $swapMB != 0 ]] && (! validate_positive_decimal $swapMB || [[ $swapMB -lt $min_swap_mb ]]) &&
|
||||
echomult "Invalid swap size $([[ $min_swap_mb != 0 ]] && echo "(Minimum should be "$min_swap_mb" MB)" || echo "").\n $help_text" && exit 1
|
||||
[ ! -z $diskMB ] && [[ $diskMB != 0 ]] && (! validate_positive_decimal $diskMB || [[ $diskMB -lt $min_disk_mb ]]) &&
|
||||
echomult "Invalid disk size $([[ $min_disk_mb != 0 ]] && echo "(Minimum should be "$min_disk_mb" MB)" || echo "").\n $help_text" && exit 1
|
||||
if ([ ! -z $ramMB ] && [[ $ramMB != 0 ]]); then
|
||||
local ramKB=$(free | grep Mem | awk '{print $2}')
|
||||
local max_ram_mb=$((ramKB / 1000))
|
||||
! validate_positive_decimal $ramMB && echomult "Invalid memory size.\n $help_text" && exit 1
|
||||
[[ $ramMB -lt $min_ram_mb ]] &&
|
||||
echomult "Minimum memory size should be "$min_ram_mb" MB.\n" && exit 1
|
||||
[[ $ramMB -gt $max_ram_mb ]] && echomult "Insufficient memory on your host. Maximum available memory is "$max_ram_mb" MB.\n" && exit 1
|
||||
fi
|
||||
if ([ ! -z $swapMB ] && [[ $swapMB != 0 ]]); then
|
||||
local swapKB=$(free | grep -i Swap | awk '{print $2}')
|
||||
local max_swap_mb=$((swapKB / 1000))
|
||||
! validate_positive_decimal $swapMB && echomult "Invalid swap size.\n $help_text" && exit 1
|
||||
[[ $swapMB -lt $min_swap_mb ]] &&
|
||||
echomult "Minimum swap size should be "$min_swap_mb" MB.\n " && exit 1
|
||||
[[ $swapMB -gt $max_swap_mb ]] && echomult "Insufficient swap on your host. Maximum available swap is "$max_swap_mb" MB.\n" && exit 1
|
||||
fi
|
||||
if ([ ! -z $diskMB ] && [[ $diskMB != 0 ]]); then
|
||||
local diskKB=$(df | grep -w /home | head -1 | awk '{print $4}')
|
||||
[ -z "$diskKB" ] && local diskKB=$(df | grep -w / | head -1 | awk '{print $4}')
|
||||
local max_disk_mb=$((diskKB / 1000))
|
||||
! validate_positive_decimal $diskMB && echomult "Invalid disk size.\n $help_text" && exit 1
|
||||
[[ $diskMB -lt $min_disk_mb ]] &&
|
||||
echomult "Minimum disk size should be "$min_disk_mb" MB.\n" && exit 1
|
||||
[[ $diskMB -gt $max_disk_mb ]] && echomult "Insufficient disk on your host. Maximum available disk is "$max_disk_mb" MB.\n" && exit 1
|
||||
fi
|
||||
[ ! -z $instcount ] && [[ $instcount != 0 ]] && ! validate_positive_decimal $instcount &&
|
||||
echomult "Invalid instance count.\n $help_text" && exit 1
|
||||
|
||||
@@ -1859,6 +2054,154 @@ WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer
|
||||
[ $has_error == 0 ] && echo "Lease offer creation for minted lease tokens was completed."
|
||||
}
|
||||
|
||||
function configure_reputationd() {
|
||||
local upgrade=$1
|
||||
[ "$EUID" -ne 0 ] && echo "Please run with root privileges (sudo)." && return 1
|
||||
|
||||
# Configure reputationd users and register host.
|
||||
echomult "configuring Evernode reputation for reward distribution..."
|
||||
|
||||
if [ -f "$REPUTATIOND_CONFIG" ]; then
|
||||
reputationd_secret_path=$(jq -r '.xrpl.secretPath' "$REPUTATIOND_CONFIG")
|
||||
chown "$REPUTATIOND_USER": $reputationd_secret_path
|
||||
fi
|
||||
if [ "$upgrade" == "0" ]; then
|
||||
#account generation,
|
||||
if ! set_host_reputationd_account; then
|
||||
echo "error setting up reputationd account."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
reputationd_user_dir=/home/"$REPUTATIOND_USER"
|
||||
reputationd_user_id=$(id -u "$REPUTATIOND_USER")
|
||||
reputationd_user_runtime_dir="/run/user/$reputationd_user_id"
|
||||
|
||||
# Setting the ownership of the REPUTATIOND_USER's home to REPUTATIOND_USER expilcity.
|
||||
# NOTE : There can be user id mismatch, as we do not delete REPUTATIOND_USER's home in the uninstallation even though the user is removed.
|
||||
chown -R "$REPUTATIOND_USER":"$SASHIADMIN_GROUP" $reputationd_user_dir
|
||||
|
||||
# Setting group ownership for the host secret.
|
||||
local host_key_file_path=$(jq -r ".xrpl.secretPath | select( . != null )" "$MB_XRPL_CONFIG")
|
||||
local host_key_parent_directory=$(dirname "$host_key_file_path")
|
||||
[ $(stat -c "%a" "$host_key_parent_directory") != "550" ] && chmod -R 550 "$host_key_parent_directory"
|
||||
[ $(stat -c "%a" "$host_key_file_path") != "440" ] && chmod 440 "$host_key_file_path"
|
||||
|
||||
if [ "$upgrade" == "0" ]; then
|
||||
echo -e "\nAccount setup is complete."
|
||||
|
||||
local message="Your host account with the address $reputationd_xrpl_address will be on Xahau $NETWORK.
|
||||
\nThe secret key of the account is located at $reputationd_key_file_path.
|
||||
\nNOTE: It is your responsibility to safeguard/backup this file in a secure manner.
|
||||
\nIf you lose it, you will not be able to access any funds in your Host account. NO ONE else can recover it.
|
||||
\n\nThis is the account that will represent this host on the Evernode host registry. You need to load up the account with following funds in order to continue with the installation."
|
||||
|
||||
local min_reputation_xah_requirement=$(echo "$MIN_REPUTATION_COST_PER_MONTH*$MIN_OPERATIONAL_DURATION + 1.2" | bc)
|
||||
local lease_amount=$(jq ".xrpl.leaseAmount | select( . != null )" "$MB_XRPL_CONFIG")
|
||||
local min_reputation_evr_requirement=$(echo "$lease_amount*24*30*$MIN_OPERATIONAL_DURATION" | bc)
|
||||
|
||||
local need_xah=$(echo "$min_reputation_xah_requirement > 0" | bc -l)
|
||||
local need_evr=$(echo "$min_reputation_evr_requirement > 0" | bc -l)
|
||||
[[ "$need_xah" -eq 1 ]] && message="$message\n(*) At least $min_reputation_xah_requirement XAH to cover regular transaction fees for the first three months."
|
||||
[[ "$need_evr" -eq 1 ]] && message="$message\n(*) At least $min_reputation_evr_requirement EVR to cover Evernode registration."
|
||||
|
||||
message="$message\n\nYou can scan the following QR code in your wallet app to send funds based on the account condition:\n"
|
||||
|
||||
echomult "$message"
|
||||
|
||||
generate_qrcode "$reputationd_xrpl_address"
|
||||
|
||||
! sudo -u $REPUTATIOND_USER REPUTATIOND_DATA_DIR=$REPUTATIOND_DATA node $REPUTATIOND_BIN new $reputationd_xrpl_address $reputationd_key_file_path && echo "Error creating configs" && return 1
|
||||
|
||||
echomult "To set up your reputationd host account, ensure a deposit of $min_reputation_xah_requirement XAH to cover the regular transaction fees for the first three months."
|
||||
echomult "\nChecking the reputationd account condition."
|
||||
while true; do
|
||||
wait_call "sudo -u $REPUTATIOND_USER REPUTATIOND_DATA_DIR=$REPUTATIOND_DATA node $REPUTATIOND_BIN wait-for-funds NATIVE $min_reputation_xah_requirement" && break
|
||||
confirm "\nDo you want to retry?\nPressing 'n' would terminate the opting-in." || return 1
|
||||
done
|
||||
|
||||
sleep 2
|
||||
fi
|
||||
! sudo -u $REPUTATIOND_USER REPUTATIOND_DATA_DIR=$REPUTATIOND_DATA node $REPUTATIOND_BIN prepare && echo "Error preparing account" && return 1
|
||||
|
||||
if [ "$upgrade" == "0" ]; then
|
||||
echomult "\n\nIn order to register in reputation and reward system you need to have $min_reputation_evr_requirement EVR balance in your host account. Please deposit the required registration fee in EVRs.
|
||||
\nYou can scan the provided QR code in your wallet app to send funds."
|
||||
|
||||
while true; do
|
||||
wait_call "sudo -u $REPUTATIOND_USER REPUTATIOND_DATA_DIR=$REPUTATIOND_DATA node $REPUTATIOND_BIN wait-for-funds ISSUED $min_reputation_evr_requirement" && break
|
||||
confirm "\nDo you want to retry?\nPressing 'n' would terminate the opting-in." || return 1
|
||||
done
|
||||
|
||||
fi
|
||||
# Setup env variable for the reputationd user.
|
||||
echo "
|
||||
export XDG_RUNTIME_DIR=$reputationd_user_runtime_dir" >>"$reputationd_user_dir"/.bashrc
|
||||
echo "Updated reputationd user .bashrc."
|
||||
|
||||
reputationd_user_systemd=""
|
||||
for ((i = 0; i < 30; i++)); do
|
||||
sleep 0.1
|
||||
reputationd_user_systemd=$(sudo -u "$REPUTATIOND_USER" XDG_RUNTIME_DIR="$reputationd_user_runtime_dir" systemctl --user is-system-running 2>/dev/null)
|
||||
[ "$reputationd_user_systemd" == "running" ] && break
|
||||
done
|
||||
[ "$reputationd_user_systemd" != "running" ] && echo "NO_REPUTATIOND_USER_SYSTEMD" && abort
|
||||
|
||||
# Configure reputationd service
|
||||
echomult "Configuring reputationd service"
|
||||
! (sudo -u $REPUTATIOND_USER mkdir -p "$reputationd_user_dir"/.config/systemd/user/) && echo "ReputationD user systemd folder creation failed" && abort
|
||||
# StartLimitIntervalSec=0 to make unlimited retries. RestartSec=5 is to keep 5 second gap between restarts.
|
||||
echo "[Unit]
|
||||
Description=Running Evernode reputation for reward distribution.
|
||||
After=network.target
|
||||
StartLimitIntervalSec=0
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=$REPUTATIOND_BIN
|
||||
Environment=\"REPUTATIOND_DATA_DIR=$REPUTATIOND_DATA\"
|
||||
ExecStart=/usr/bin/node $REPUTATIOND_BIN
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
[Install]
|
||||
WantedBy=default.target" | sudo -u $REPUTATIOND_USER tee "$reputationd_user_dir"/.config/systemd/user/$REPUTATIOND_SERVICE.service >/dev/null
|
||||
|
||||
# This service needs to be restarted whenever reputation.cfg or secret.cfg is changed.
|
||||
sudo -u "$REPUTATIOND_USER" XDG_RUNTIME_DIR="$reputationd_user_runtime_dir" systemctl --user enable $REPUTATIOND_SERVICE
|
||||
# We only enable this service. It'll be started after pending reboot checks at the bottom of this script.
|
||||
|
||||
# If there's no pending reboot, start the reputationd services now. Otherwise
|
||||
# they'll get started at next startup.
|
||||
if [ ! -f /run/reboot-required.pkgs ] || [ ! -n "$(grep sashimono /run/reboot-required.pkgs)" ]; then
|
||||
echo "Starting the reputationd service."
|
||||
|
||||
sudo -u "$REPUTATIOND_USER" XDG_RUNTIME_DIR="$reputationd_user_runtime_dir" systemctl --user restart $REPUTATIOND_SERVICE
|
||||
fi
|
||||
|
||||
echo "Opted-in to the Evernode reputation for reward distribution."
|
||||
}
|
||||
|
||||
function remove_reputationd() {
|
||||
[ "$EUID" -ne 0 ] && echo "Please run with root privileges (sudo)." && return 1
|
||||
|
||||
reputationd_user_dir=/home/"$REPUTATIOND_USER"
|
||||
reputationd_user_id=$(id -u "$REPUTATIOND_USER")
|
||||
reputationd_user_runtime_dir="/run/user/$reputationd_user_id"
|
||||
|
||||
# Remove auto updater service if exists.
|
||||
local service_path="$reputationd_user_dir"/.config/systemd/user/$REPUTATIOND_SERVICE.service
|
||||
if [ -f $service_path ]; then
|
||||
echo "Removing Evernode reputation for reward distribution..."
|
||||
sudo -u "$REPUTATIOND_USER" XDG_RUNTIME_DIR="$reputationd_user_runtime_dir" systemctl --user stop $REPUTATIOND_SERVICE
|
||||
sudo -u "$REPUTATIOND_USER" XDG_RUNTIME_DIR="$reputationd_user_runtime_dir" systemctl --user disable $REPUTATIOND_SERVICE
|
||||
rm -f $service_path
|
||||
local service_removed=true
|
||||
else
|
||||
echo "Evernode reputation for reward distribution is not configured."
|
||||
fi
|
||||
|
||||
$service_removed && echo "Opted-out from the Evernode reputation for reward distribution."
|
||||
}
|
||||
|
||||
# Begin setup execution flow --------------------
|
||||
|
||||
if [ "$mode" == "install" ]; then
|
||||
@@ -1886,6 +2229,9 @@ WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer
|
||||
|
||||
download_public_config && set_environment_configs
|
||||
|
||||
# Setting up Sashimono admin group.
|
||||
! grep -q $SASHIADMIN_GROUP /etc/group && ! groupadd $SASHIADMIN_GROUP && echo "$SASHIADMIN_GROUP group creation failed." && abort
|
||||
|
||||
# Create MB_XRPL_USER as we require that user for secret key ownership management.
|
||||
if ! grep -q "^$MB_XRPL_USER:" /etc/passwd; then
|
||||
echomult "Creating Message-board User..."
|
||||
@@ -1893,7 +2239,7 @@ WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer
|
||||
|
||||
# Setting the ownership of the MB_XRPL_USER's home to MB_XRPL_USER expilcity.
|
||||
# NOTE : There can be user id mismatch, as we do not delete MB_XRPL_USER's home in the uninstallation even though the user is removed.
|
||||
chown -R "$MB_XRPL_USER":"$MB_XRPL_USER" /home/$MB_XRPL_USER
|
||||
chown -R "$MB_XRPL_USER":"$SASHIADMIN_GROUP" /home/$MB_XRPL_USER
|
||||
fi
|
||||
|
||||
# Check if message board config and sa.cfg exists.
|
||||
@@ -2090,9 +2436,8 @@ WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer
|
||||
update_evernode
|
||||
|
||||
echomult "Upgrade complete!
|
||||
\n\nNOTE: This update includes following commands for you to configure extra transaction fee and fallback xahaud servers.
|
||||
\n evernode config extrafee <fee amount in XAH Drops> - Configure extra transaction fee.
|
||||
\n evernode config xahaud-fallback <fallback xahaud servers (comma seperated)> - Configure fallback xahaud servers."
|
||||
\n\nNOTE: This update includes following commands for you to configure reputation for reward distribution.
|
||||
\n evernode reputationd <opt-in|opt-out> - Opt-in or opt-out for Evernode reputation for reward distribution."
|
||||
|
||||
elif [ "$mode" == "log" ]; then
|
||||
create_log
|
||||
@@ -2131,10 +2476,8 @@ WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer
|
||||
echo "Regular key is invalid." && exit 1
|
||||
fi
|
||||
set_regular_key $3
|
||||
exit 0
|
||||
elif [ "$2" == "delete" ]; then
|
||||
set_regular_key
|
||||
exit 0
|
||||
else
|
||||
echomult "Regular key management tool
|
||||
\nSupported commands:
|
||||
@@ -2144,6 +2487,26 @@ WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer
|
||||
|
||||
elif [ "$mode" == "offerlease" ]; then
|
||||
offerlease
|
||||
|
||||
elif [ "$mode" == "reputationd" ]; then
|
||||
if [ "$2" == "opt-in" ]; then
|
||||
init_setup_helpers
|
||||
if ! configure_reputationd 0; then
|
||||
echomult "\nError occured configuring ReputationD. Retry with the same command again."
|
||||
exit 1
|
||||
fi
|
||||
elif [ "$2" == "opt-out" ]; then
|
||||
! confirm "Are you sure you want to opt out from Evernode reputation for reward distribution?" "n" && exit 1
|
||||
if ! remove_reputationd; then
|
||||
echomult "\nError occured removing ReputationD. Retry with the same command again."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echomult "ReputationD management tool
|
||||
\nSupported commands:
|
||||
\nopt-in - Opt in to the Evernode reputation for reward distribution.
|
||||
\ndelete - Opt out from the Evernode reputation for reward distribution." && exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
$installed && check_installer_pending_finish
|
||||
|
||||
@@ -65,7 +65,7 @@ async function main() {
|
||||
await new Setup().checkBalance();
|
||||
}
|
||||
else if (process.argv.length >= 4 && process.argv[2] === 'wait-for-funds') {
|
||||
await new Setup().waitForFunds(process.argv[3], parseInt(process.argv[4]));
|
||||
await new Setup().waitForFunds(process.argv[3], parseFloat(process.argv[4]));
|
||||
}
|
||||
else if (process.argv.length >= 3 && process.argv[2] === 'prepare') {
|
||||
await new Setup().prepareHostAccount(process.argv[3]);
|
||||
@@ -171,7 +171,7 @@ async function main() {
|
||||
console.log('Data dir: ' + appenv.DATA_DIR);
|
||||
console.log('Using Sashimono cli: ' + appenv.SASHI_CLI_PATH);
|
||||
|
||||
const mb = new MessageBoard(appenv.CONFIG_PATH, appenv.SECRET_CONFIG_PATH, appenv.DB_PATH, appenv.SASHI_CLI_PATH, appenv.SASHI_DB_PATH, appenv.SASHI_CONFIG_PATH);
|
||||
const mb = new MessageBoard(appenv.CONFIG_PATH, appenv.DB_PATH, appenv.SASHI_CLI_PATH, appenv.SASHI_DB_PATH, appenv.SASHI_CONFIG_PATH, appenv.REPUTATIOND_CONFIG_PATH);
|
||||
await mb.init();
|
||||
}
|
||||
catch (err) {
|
||||
|
||||
@@ -16,8 +16,28 @@ appenv = {
|
||||
DB_PATH: appenv.DATA_DIR + '/mb-xrpl.sqlite',
|
||||
DB_TABLE_NAME: 'leases',
|
||||
DB_UTIL_TABLE_NAME: 'util_data',
|
||||
SASHI_DB_PATH: (appenv.IS_DEV_MODE ? "../build/" : path.join(appenv.DATA_DIR, '../')) + "sa.sqlite",
|
||||
SASHI_CONFIG_PATH: (appenv.IS_DEV_MODE ? "../build/" : path.join(appenv.DATA_DIR, '../')) + "sa.cfg",
|
||||
SASHI_DB_PATH: (() => {
|
||||
if (appenv.IS_DEV_MODE) {
|
||||
const devPath = "../build/sa.sqlite";
|
||||
if (!fs.existsSync(devPath))
|
||||
return path.join(appenv.DATA_DIR, '../') + "sa.sqlite";
|
||||
return devPath;
|
||||
}
|
||||
else {
|
||||
return path.join(appenv.DATA_DIR, '../') + "sa.sqlite"
|
||||
}
|
||||
})(),
|
||||
SASHI_CONFIG_PATH: (() => {
|
||||
if (appenv.IS_DEV_MODE) {
|
||||
const devPath = "../build/sa.cfg";
|
||||
if (!fs.existsSync(devPath))
|
||||
return path.join(appenv.DATA_DIR, '../') + "sa.cfg";
|
||||
return devPath;
|
||||
}
|
||||
else {
|
||||
return path.join(appenv.DATA_DIR, '../') + "sa.cfg"
|
||||
}
|
||||
})(),
|
||||
SASHI_TABLE_NAME: 'instances',
|
||||
LAST_WATCHED_LEDGER: 'last_watched_ledger',
|
||||
ACQUIRE_LEASE_TIMEOUT_THRESHOLD: 0.8,
|
||||
@@ -25,17 +45,12 @@ appenv = {
|
||||
ORPHAN_PRUNE_SCHEDULER_INTERVAL_HOURS: 4,
|
||||
SASHIMONO_SCHEDULER_INTERVAL_SECONDS: 2,
|
||||
SASHI_CLI_PATH: appenv.IS_DEV_MODE ? "../build/sashi" : "/usr/bin/sashi",
|
||||
MB_VERSION: '0.8.2',
|
||||
MB_VERSION: '0.8.3',
|
||||
TOS_HASH: '0801677EBCB2F76EF97D531549D8B27DB2C7A4A8EE7F60032AE40184247F0810', // This is the sha256 hash of EVERNODE-HOSTING-PRINCIPLES.pdf.
|
||||
NETWORK: 'mainnet'
|
||||
NETWORK: 'mainnet',
|
||||
REPUTATIOND_CONFIG_PATH: path.join(appenv.DATA_DIR, '../') + "reputationd/reputationd.cfg",
|
||||
}
|
||||
|
||||
const getSecretPath = () => {
|
||||
return fs.existsSync(appenv.CONFIG_PATH) ? JSON.parse(fs.readFileSync(appenv.CONFIG_PATH).toString()).xrpl.secretPath : "";
|
||||
}
|
||||
|
||||
appenv = { ...appenv, SECRET_CONFIG_PATH: getSecretPath() }
|
||||
|
||||
Object.freeze(appenv);
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
const fs = require('fs');
|
||||
|
||||
class ConfigHelper {
|
||||
static readConfig(configPath, secretConfigPath = null) {
|
||||
static readConfig(configPath, reputationDConfigPath = null, readSecret = false) {
|
||||
if (!fs.existsSync(configPath))
|
||||
throw `Config file does not exist at ${configPath}`;
|
||||
|
||||
let config = JSON.parse(fs.readFileSync(configPath).toString());
|
||||
|
||||
if (secretConfigPath) {
|
||||
if (!fs.existsSync(secretConfigPath))
|
||||
throw `Secret config file does not exist at ${secretConfigPath}`;
|
||||
if (readSecret) {
|
||||
if (!fs.existsSync(config.xrpl.secretPath))
|
||||
throw `Secret config file does not exist at ${config.xrpl.secretPath}`;
|
||||
|
||||
const secretCfg = JSON.parse(fs.readFileSync(secretConfigPath).toString());
|
||||
const secretCfg = JSON.parse(fs.readFileSync(config.xrpl.secretPath).toString());
|
||||
config.xrpl = { ...config.xrpl, ...secretCfg.xrpl };
|
||||
}
|
||||
|
||||
@@ -25,6 +25,21 @@ class ConfigHelper {
|
||||
if (config.xrpl.leaseAmount && config.xrpl.leaseAmount < 0)
|
||||
throw "Lease amount should be a positive value";
|
||||
|
||||
if (reputationDConfigPath && fs.existsSync(reputationDConfigPath)) {
|
||||
const reputationDConfig = JSON.parse(fs.readFileSync(reputationDConfigPath).toString());
|
||||
config.xrpl.reputationAddress = reputationDConfig.xrpl.address;
|
||||
|
||||
if (readSecret) {
|
||||
if (!fs.existsSync(reputationDConfig.xrpl.secretPath))
|
||||
throw `Secret config file does not exist at ${reputationDConfig.xrpl.secretPath}`;
|
||||
|
||||
const reputationDSecretCfg = JSON.parse(fs.readFileSync(reputationDConfig.xrpl.secretPath).toString());
|
||||
config.xrpl.reputationSecret = reputationDSecretCfg.xrpl.secret;
|
||||
}
|
||||
|
||||
config.xrpl = { ...reputationDConfig.xrpl, ...config.xrpl }
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
@@ -191,7 +191,7 @@ class GovernanceManager {
|
||||
if (command == 'propose' || command === 'withdraw' || command === 'vote' || command === 'status' || command === 'report') {
|
||||
// Secret is needed for propose, withdraw, and report in order to send the transaction
|
||||
const sashiMBConfig = ConfigHelper.readConfig(appenv.CONFIG_PATH,
|
||||
(command == 'propose' || command === 'withdraw' || command === 'report') ? appenv.SECRET_CONFIG_PATH : null);
|
||||
appenv.REPUTATIOND_CONFIG_PATH, (command == 'propose' || command === 'withdraw' || command === 'report'));
|
||||
await setEvernodeDefaults(sashiMBConfig.xrpl.network, sashiMBConfig.xrpl.governorAddress, sashiMBConfig.xrpl.rippledServer, sashiMBConfig.xrpl.fallbackRippledServers);
|
||||
hostClient = new evernode.HostClient(sashiMBConfig.xrpl.address, sashiMBConfig.xrpl.secret);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ const { appenv } = require('./appenv');
|
||||
const { SashiCLI } = require('./sashi-cli');
|
||||
const { ConfigHelper } = require('./config-helper');
|
||||
const { GovernanceManager } = require('./governance-manager');
|
||||
const path = require('path');
|
||||
|
||||
const LeaseStatus = {
|
||||
ACQUIRING: 'Acquiring',
|
||||
@@ -33,9 +34,9 @@ class MessageBoard {
|
||||
#feeUpliftment = 0;
|
||||
#rebateMaxDelay = 60000; // 1 min
|
||||
|
||||
constructor(configPath, secretConfigPath, dbPath, sashiCliPath, sashiDbPath, sashiConfigPath) {
|
||||
constructor(configPath, dbPath, sashiCliPath, sashiDbPath, sashiConfigPath, reputationDConfigPath = null) {
|
||||
this.configPath = configPath;
|
||||
this.secretConfigPath = secretConfigPath;
|
||||
this.reputationDConfigPath = reputationDConfigPath;
|
||||
this.leaseTable = appenv.DB_TABLE_NAME;
|
||||
this.utilTable = appenv.DB_UTIL_TABLE_NAME;
|
||||
this.expiryList = [];
|
||||
@@ -44,7 +45,7 @@ class MessageBoard {
|
||||
if (!fs.existsSync(sashiCliPath))
|
||||
throw `Sashi CLI does not exist in ${sashiCliPath}.`;
|
||||
|
||||
this.sashiCli = new SashiCLI(sashiCliPath);
|
||||
this.sashiCli = new SashiCLI(sashiCliPath, appenv.IS_DEV_MODE ? { DATA_DIR: path.join(appenv.DATA_DIR, '../') } : {});
|
||||
this.db = new SqliteDatabase(dbPath);
|
||||
this.sashiDb = new SqliteDatabase(sashiDbPath);
|
||||
this.sashiTable = appenv.SASHI_TABLE_NAME;
|
||||
@@ -229,7 +230,7 @@ class MessageBoard {
|
||||
this.regClient.on(evernode.RegistryEvents.HostRegistered, async r => {
|
||||
if (rebateRequestPending)
|
||||
return;
|
||||
|
||||
|
||||
try {
|
||||
await this.hostClient.refreshConfig();
|
||||
if (hostRegFee != this.hostClient.config.hostRegFee) {
|
||||
@@ -495,13 +496,13 @@ class MessageBoard {
|
||||
try {
|
||||
console.log(`Moments exceeded (current timestamp:${currentTime}, expiry timestamp:${lease.expiryTimestamp}). Destroying ${lease.containerName}`);
|
||||
// Expire the current lease agreement (Burn the instance URIToken) and re-minting and creating sell offer for the same lease index.
|
||||
const uriToken = (await (new evernode.XrplAccount(lease.tenant)).getURITokens())?.find(n => n.index == lease.containerName);
|
||||
const uriToken = (await this.hostClient.getLeaseByIndex(lease.containerName));
|
||||
// If there's no uriToken for this record it should be already burned and instance is destroyed, So we only delete the record.
|
||||
if (!uriToken)
|
||||
console.log(`Cannot find an URIToken for ${lease.containerName}`);
|
||||
else {
|
||||
const uriInfo = evernode.UtilHelpers.decodeLeaseTokenUri(uriToken.URI);
|
||||
await this.destroyInstance(lease.containerName, lease.tenant, uriInfo.leaseIndex, uriInfo?.outboundIP?.address);
|
||||
await this.destroyInstance(lease.containerName, uriInfo.leaseIndex, uriInfo?.outboundIP?.address);
|
||||
}
|
||||
|
||||
this.activeInstanceCount--;
|
||||
@@ -540,13 +541,13 @@ class MessageBoard {
|
||||
|
||||
// Connect the host and trying to reconnect in the event of account not found error.
|
||||
// Account not found error can be because of a network reset. (Dev and test nets)
|
||||
async #connect(client) {
|
||||
async #connect(client, options = null) {
|
||||
let attempts = 0;
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
try {
|
||||
attempts++;
|
||||
const ret = await client.connect();
|
||||
const ret = options ? await client.connect(options) : await client.connect();
|
||||
if (ret)
|
||||
break;
|
||||
} catch (error) {
|
||||
@@ -567,7 +568,7 @@ class MessageBoard {
|
||||
}
|
||||
|
||||
async #connectHost() {
|
||||
await this.#connect(this.hostClient);
|
||||
await this.#connect(this.hostClient, { reputationAddress: this.cfg.xrpl.reputationAddress, reputationSecret: this.cfg.xrpl.reputationSecret });
|
||||
}
|
||||
|
||||
async #connectRegistry() {
|
||||
@@ -695,7 +696,7 @@ class MessageBoard {
|
||||
// If there's a lease record this is created from message board.
|
||||
if (lease) {
|
||||
leases.splice(leaseIndex, 1);
|
||||
const uriToken = (await (new evernode.XrplAccount(lease.tenant_xrp_address)).getURITokens())?.find(n => n.index == instance.name);
|
||||
const uriToken = (await this.hostClient.getLeaseByIndex(instance.name));
|
||||
|
||||
// If lease is in ACQUIRING status acquire response is not received by the tenant and lease is not in expiry list.
|
||||
// If the URIToken is not owned by the tenant we destroy the instance since this is not a valid lease.
|
||||
@@ -707,7 +708,7 @@ class MessageBoard {
|
||||
// After destroying, If the URIToken is owned by the tenant, burn the URIToken and recreate and refund the tenant.
|
||||
if (uriToken) {
|
||||
const uriInfo = evernode.UtilHelpers.decodeLeaseTokenUri(uriToken.URI);
|
||||
await this.recreateLeaseOffer(instance.name, lease.tenant_xrp_address, uriInfo.leaseIndex, uriInfo.outboundIP?.address);
|
||||
await this.recreateLeaseOffer(instance.name, uriInfo.leaseIndex, uriInfo.outboundIP?.address);
|
||||
|
||||
await this.#queueAction(async (submissionRefs) => {
|
||||
submissionRefs.refs ??= [{}];
|
||||
@@ -761,10 +762,10 @@ class MessageBoard {
|
||||
if (lease.status === LeaseStatus.ACQUIRED || lease.status === LeaseStatus.EXTENDED)
|
||||
activeInstanceCount--;
|
||||
|
||||
const uriToken = (await (new evernode.XrplAccount(lease.tenant_xrp_address)).getURITokens())?.find(n => n.index == lease.container_name);
|
||||
const uriToken = (await this.hostClient.getLeaseByIndex(lease.container_name));
|
||||
if (uriToken) {
|
||||
const uriInfo = evernode.UtilHelpers.decodeLeaseTokenUri(uriToken.URI);
|
||||
await this.recreateLeaseOffer(lease.container_name, lease.tenant_xrp_address, uriInfo.leaseIndex, uriInfo.outboundIP?.address);
|
||||
await this.recreateLeaseOffer(lease.container_name, uriInfo.leaseIndex, uriInfo.outboundIP?.address);
|
||||
|
||||
await this.#queueAction(async (submissionRefs) => {
|
||||
submissionRefs.refs ??= [{}];
|
||||
@@ -881,12 +882,11 @@ class MessageBoard {
|
||||
const lease = leases.find(l => l.container_name === eventInfo.data.uriTokenId && (l.status === LeaseStatus.ACQUIRED || l.status === LeaseStatus.EXTENDED));
|
||||
|
||||
if (!lease) {
|
||||
const tenantXrplAcc = new evernode.XrplAccount(eventInfo.data.tenant);
|
||||
const uriToken = (await tenantXrplAcc.getURITokens()).find(n => n.Issuer == this.cfg.xrpl.address && evernode.EvernodeHelpers.isValidURI(n.URI, evernode.EvernodeConstants.LEASE_TOKEN_PREFIX_HEX) && n.index === eventInfo.data.uriTokenId);
|
||||
const uriToken = (await this.hostClient.getLeaseByIndex(eventInfo.data.uriTokenId));
|
||||
if (uriToken) {
|
||||
const uriInfo = evernode.UtilHelpers.decodeLeaseTokenUri(uriToken.URI);
|
||||
// Have to recreate the URIToken Offer for the lease as previous one was not utilized.
|
||||
await this.recreateLeaseOffer(eventInfo.data.uriTokenId, eventInfo.data.tenant, uriInfo.leaseIndex, uriInfo.outboundIP?.address);
|
||||
await this.recreateLeaseOffer(eventInfo.data.uriTokenId, uriInfo.leaseIndex, uriInfo.outboundIP?.address);
|
||||
|
||||
await this.#queueAction(async (submissionRefs) => {
|
||||
submissionRefs.refs ??= [{}];
|
||||
@@ -912,8 +912,7 @@ class MessageBoard {
|
||||
const lease = leases.find(l => l.container_name === eventInfo.data.uriTokenId && (l.status === LeaseStatus.ACQUIRED || l.status === LeaseStatus.EXTENDED));
|
||||
|
||||
if (lease) {
|
||||
const tenantXrplAcc = new evernode.XrplAccount(eventInfo.data.tenant);
|
||||
const uriToken = (await tenantXrplAcc.getURITokens()).find(n => n.Issuer == this.cfg.xrpl.address && evernode.EvernodeHelpers.isValidURI(n.URI, evernode.EvernodeConstants.LEASE_TOKEN_PREFIX_HEX) && n.index === eventInfo.data.uriTokenId);
|
||||
const uriToken = (await this.hostClient.getLeaseByIndex(eventInfo.data.uriTokenId));
|
||||
if (uriToken) {
|
||||
await this.#queueAction(async (submissionRefs) => {
|
||||
submissionRefs.refs ??= [{}];
|
||||
@@ -960,7 +959,7 @@ class MessageBoard {
|
||||
return null;
|
||||
}
|
||||
|
||||
async recreateLeaseOffer(uriTokenId, tenantAddress, leaseIndex, outboundIP) {
|
||||
async recreateLeaseOffer(uriTokenId, leaseIndex, outboundIP) {
|
||||
await this.#queueAction(async (submissionRefs) => {
|
||||
submissionRefs.refs ??= [{}, {}];
|
||||
// Check again wether the transaction is validated before retry.
|
||||
@@ -1057,7 +1056,7 @@ class MessageBoard {
|
||||
console.error(`Sashimono busy timeout. Took: ${diff} seconds. Threshold: ${threshold} seconds`);
|
||||
// Update the lease status of the request to 'SashiTimeout'.
|
||||
await this.updateAcquireStatus(acquireRefId, LeaseStatus.SASHI_TIMEOUT);
|
||||
await this.recreateLeaseOffer(uriTokenId, tenantAddress, leaseIndex, uriInfo?.outboundIP?.address);
|
||||
await this.recreateLeaseOffer(uriTokenId, leaseIndex, uriInfo?.outboundIP?.address);
|
||||
}
|
||||
else {
|
||||
const instanceRequirements = { ...r.payload, outbound_ipv6: (uriInfo.outboundIP?.family == 6) ? uriInfo.outboundIP?.address : "-", outbound_net_interface: (uriInfo.outboundIP?.family == 6) ? this.cfg.networking.ipv6.interface : "-" };
|
||||
@@ -1071,7 +1070,7 @@ class MessageBoard {
|
||||
console.error(`Instance creation timeout. Took: ${diff} seconds. Threshold: ${threshold} seconds`);
|
||||
// Update the lease status of the request to 'SashiTimeout'.
|
||||
await this.updateLeaseStatus(acquireRefId, LeaseStatus.SASHI_TIMEOUT);
|
||||
await this.destroyInstance(createRes.content.name, tenantAddress, leaseIndex, instanceOutboundIPAddress);
|
||||
await this.destroyInstance(createRes.content.name, leaseIndex, instanceOutboundIPAddress);
|
||||
} else {
|
||||
console.log(`Instance created for ${tenantAddress}`);
|
||||
|
||||
@@ -1142,7 +1141,7 @@ class MessageBoard {
|
||||
|
||||
// Re-create the lease offer (Only if the uriToken belongs to this request has a lease index).
|
||||
if (leaseIndex >= 0)
|
||||
await this.recreateLeaseOffer(uriTokenId, tenantAddress, leaseIndex, instanceOutboundIPAddress).catch(console.error);
|
||||
await this.recreateLeaseOffer(uriTokenId, leaseIndex, instanceOutboundIPAddress).catch(console.error);
|
||||
|
||||
await this.#queueAction(async (submissionRefs) => {
|
||||
submissionRefs.refs ??= [{}];
|
||||
@@ -1165,10 +1164,10 @@ class MessageBoard {
|
||||
}
|
||||
}
|
||||
|
||||
async destroyInstance(containerName, tenantAddress, leaseIndex, outboundIP = null) {
|
||||
async destroyInstance(containerName, leaseIndex, outboundIP = null) {
|
||||
// Destroy the instance.
|
||||
await this.sashiCli.destroyInstance(containerName);
|
||||
await this.recreateLeaseOffer(containerName, tenantAddress, leaseIndex, outboundIP).catch(console.error);
|
||||
await this.recreateLeaseOffer(containerName, leaseIndex, outboundIP).catch(console.error);
|
||||
}
|
||||
|
||||
async handleExtendLease(r) {
|
||||
@@ -1380,7 +1379,7 @@ class MessageBoard {
|
||||
|
||||
|
||||
readConfig() {
|
||||
this.cfg = ConfigHelper.readConfig(this.configPath, this.secretConfigPath);
|
||||
this.cfg = ConfigHelper.readConfig(this.configPath, this.reputationDConfigPath, true);
|
||||
}
|
||||
|
||||
persistConfig() {
|
||||
|
||||
@@ -5,8 +5,9 @@ class SashiCLI {
|
||||
|
||||
#waiting = false;
|
||||
|
||||
constructor(cliPath) {
|
||||
constructor(cliPath, env = {}) {
|
||||
this.cliPath = cliPath;
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
async createInstance(containerName, requirements) {
|
||||
@@ -49,7 +50,7 @@ class SashiCLI {
|
||||
execSashiCli(msg) {
|
||||
this.#waiting = true;
|
||||
return new Promise((resolve, reject) => {
|
||||
let command = `${this.cliPath} json -m '${JSON.stringify(msg)}'`;
|
||||
let command = (Object.keys(this.env).length > 0 ? `${Object.entries(this.env).map(e => `${e[0]}=${e[1]}`)} ` : '') + `${this.cliPath} json -m '${JSON.stringify(msg)}'`;
|
||||
|
||||
if (msg.type === "create") {
|
||||
command = `DEV_MODE=1 ${command}`;
|
||||
|
||||
@@ -6,6 +6,7 @@ const { SqliteDatabase } = require('./sqlite-handler');
|
||||
const { ConfigHelper } = require('./config-helper');
|
||||
const { SashiCLI } = require('./sashi-cli');
|
||||
const { UtilHelper } = require('./util-helper');
|
||||
const path = require('path');
|
||||
|
||||
async function setEvernodeDefaults(network, governorAddress, rippledServer, fallbackRippledServers) {
|
||||
await evernode.Defaults.useNetwork(network || appenv.NETWORK);
|
||||
@@ -31,7 +32,7 @@ const MAX_TX_RETRY_ATTEMPTS = 10;
|
||||
class Setup {
|
||||
|
||||
#getConfig(readSecret = true) {
|
||||
return ConfigHelper.readConfig(appenv.CONFIG_PATH, readSecret ? appenv.SECRET_CONFIG_PATH : null);
|
||||
return ConfigHelper.readConfig(appenv.CONFIG_PATH, appenv.REPUTATIOND_CONFIG_PATH, readSecret);
|
||||
}
|
||||
|
||||
#saveConfig(cfg) {
|
||||
@@ -93,7 +94,6 @@ class Setup {
|
||||
|
||||
// Prepare host account.
|
||||
const hostClient = new evernode.HostClient(acc.address, acc.secret);
|
||||
await hostClient.connect();
|
||||
|
||||
// Update the Defaults with "xrplApi" of the client.
|
||||
evernode.Defaults.set({
|
||||
@@ -744,9 +744,8 @@ class Setup {
|
||||
let vacant = [];
|
||||
for (const l of leaseRecords) {
|
||||
try {
|
||||
const tenantAddress = l.tenant_xrp_address;
|
||||
const uriTokenId = l.container_name;
|
||||
const uriToken = (await (new evernode.XrplAccount(tenantAddress, null, { xrplApi: xrplApi })).getURITokens())?.find(n => n.index == uriTokenId);
|
||||
const uriToken = (await hostClient.getLeaseByIndex(uriTokenId));
|
||||
if (uriToken) {
|
||||
const index = evernode.UtilHelpers.decodeLeaseTokenUri(uriToken.URI).leaseIndex;
|
||||
acquired.push(index);
|
||||
@@ -847,7 +846,7 @@ class Setup {
|
||||
console.log(`Destroying the instance...`);
|
||||
|
||||
// Destroy the instance.
|
||||
const sashiCli = new SashiCLI(sashiCliPath);
|
||||
const sashiCli = new SashiCLI(sashiCliPath, appenv.IS_DEV_MODE ? { DATA_DIR: path.join(appenv.DATA_DIR, '../') } : {});
|
||||
await sashiCli.destroyInstance(containerName);
|
||||
|
||||
db = new SqliteDatabase(appenv.DB_PATH);
|
||||
@@ -869,13 +868,13 @@ class Setup {
|
||||
xrplApi: xrplApi
|
||||
});
|
||||
|
||||
hostClient = new evernode.HostClient(acc.address, acc.secret, { xrplApi: xrplApi });
|
||||
await hostClient.connect();
|
||||
|
||||
// Get the existing uriToken of the lease.
|
||||
const uriToken = (await (new evernode.XrplAccount(lease.tenant_xrp_address, null, { xrplApi: xrplApi }).getURITokens()))?.find(n => n.index == lease.container_name);
|
||||
const uriToken = (await hostClient.getLeaseByIndex(lease.container_name));
|
||||
|
||||
if (uriToken) {
|
||||
hostClient = new evernode.HostClient(acc.address, acc.secret, { xrplApi: xrplApi });
|
||||
await hostClient.connect();
|
||||
|
||||
// Delete instance from sashiDB and burn the token
|
||||
const uriInfo = evernode.UtilHelpers.decodeLeaseTokenUri(uriToken.URI);
|
||||
|
||||
|
||||
8
mb-xrpl/package-lock.json
generated
8
mb-xrpl/package-lock.json
generated
@@ -6,7 +6,7 @@
|
||||
"": {
|
||||
"name": "mb-xrpl",
|
||||
"dependencies": {
|
||||
"evernode-js-client": "0.6.42",
|
||||
"evernode-js-client": "0.6.49",
|
||||
"ip6addr": "0.2.5",
|
||||
"sqlite3": "5.0.2"
|
||||
},
|
||||
@@ -1021,9 +1021,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/evernode-js-client": {
|
||||
"version": "0.6.42",
|
||||
"resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.42.tgz",
|
||||
"integrity": "sha512-lonrEXLdLJtLmxmkMztfLRRG/N2GaNJVecDTJPG0hEOUUBFPmFh6rchsjGv8H1oJOmIW/IPL/rkGtbVtn1rzag==",
|
||||
"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==",
|
||||
"dependencies": {
|
||||
"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.6.42",
|
||||
"evernode-js-client": "0.6.49",
|
||||
"sqlite3": "5.0.2",
|
||||
"ip6addr": "0.2.5"
|
||||
},
|
||||
|
||||
17
reputationd/.eslintrc.json
Normal file
17
reputationd/.eslintrc.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"commonjs": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 13
|
||||
},
|
||||
"rules": {
|
||||
"no-async-promise-executor": "off"
|
||||
},
|
||||
"globals": {
|
||||
"__dirname": true
|
||||
}
|
||||
}
|
||||
5
reputationd/.gitignore
vendored
Normal file
5
reputationd/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
dist
|
||||
log
|
||||
reputationd.cfg
|
||||
secret.cfg
|
||||
109
reputationd/app.js
Normal file
109
reputationd/app.js
Normal file
@@ -0,0 +1,109 @@
|
||||
const process = require('process');
|
||||
// Uncaught Exception Handling.
|
||||
process.on('uncaughtException', (err) => {
|
||||
process.removeAllListeners('uncaughtException');
|
||||
process.removeAllListeners('unhandledRejection');
|
||||
console.error('Unhandled exception occurred:', err?.message);
|
||||
console.error('Stack trace:', err?.stack);
|
||||
console.log("REPUTATIOND_EXITED");
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Unhandled Rejection Handling.
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
process.removeAllListeners('unhandledRejection');
|
||||
process.removeAllListeners('uncaughtException');
|
||||
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
||||
console.log("REPUTATIOND_EXITED");
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
const logger = require('./lib/logger');
|
||||
const { appenv } = require('./lib/appenv');
|
||||
const { Setup } = require('./lib/setup');
|
||||
const { ReputationD } = require('./lib/reputationd');
|
||||
|
||||
async function main() {
|
||||
|
||||
if (process.argv[2] === 'version') {
|
||||
console.log(appenv.REPUTATIOND_VERSION);
|
||||
}
|
||||
|
||||
if (process.argv.length >= 3) {
|
||||
try {
|
||||
if (process.argv.length >= 5 && process.argv[2] === 'new') {
|
||||
const accountAddress = process.argv[3];
|
||||
const accountSecretPath = process.argv[4];
|
||||
const setup = new Setup();
|
||||
setup.newConfig(accountAddress, accountSecretPath);
|
||||
}
|
||||
else if (process.argv.length >= 4 && process.argv[2] === 'wait-for-funds') {
|
||||
await new Setup().waitForFunds(process.argv[3], parseFloat(process.argv[4]));
|
||||
}
|
||||
else if (process.argv.length >= 2 && process.argv[2] === 'prepare') {
|
||||
await new Setup().prepareReputationAccount();
|
||||
}
|
||||
else if (process.argv.length >= 3 && process.argv[2] === 'upgrade') {
|
||||
await new Setup().upgrade();
|
||||
}
|
||||
else if (process.argv[2] === 'help') {
|
||||
console.log(`Usage:
|
||||
node index.js - Run message board.
|
||||
node index.js version - Print version.
|
||||
node index.js new [address] [secretPath] - Create new config files.
|
||||
node index.js wait-for-funds [currencyType] [expectedBalance] - Wait until the funds are received.
|
||||
node index.js upgrade [governorAddress] - Upgrade message board data.
|
||||
node index.js help - Print help.`);
|
||||
}
|
||||
else {
|
||||
throw "Invalid args.";
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
process.removeAllListeners('uncaughtException');
|
||||
process.removeAllListeners('unhandledRejection');
|
||||
|
||||
// If error is a RippledError show internal error message, Otherwise show err.
|
||||
console.log(err?.data?.error_message || err);
|
||||
console.log("REPUTATIOND_EXITED");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
// Logs are formatted with the timestamp and a log file will be created inside log directory.
|
||||
logger.init(appenv.LOG_PATH, appenv.FILE_LOG_ENABLED);
|
||||
|
||||
console.log('Starting the Evernode Xahau reputationd.' + (appenv.IS_DEV_MODE ? ' (in dev mode)' : ''));
|
||||
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.MB_XRPL_CONFIG_PATH, appenv.INSTANCE_IMAGE, appenv.CONTRACT_PATH);
|
||||
await rep.init();
|
||||
}
|
||||
catch (err) {
|
||||
process.removeAllListeners('uncaughtException');
|
||||
process.removeAllListeners('unhandledRejection');
|
||||
|
||||
// If error is a RippledError show internal error message, Otherwise show err.
|
||||
console.log(err?.data?.error_message || err);
|
||||
console.log("Evernode ReputationD exiting with error.");
|
||||
console.log("REPUTATIOND_EXITED");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().then(() => {
|
||||
process.removeAllListeners('uncaughtException');
|
||||
process.removeAllListeners('unhandledRejection');
|
||||
|
||||
console.log("REPUTATIOND_SUCCESS");
|
||||
}).catch((e) => {
|
||||
process.removeAllListeners('uncaughtException');
|
||||
process.removeAllListeners('unhandledRejection');
|
||||
|
||||
console.error(e);
|
||||
console.log("REPUTATIOND_EXITED");
|
||||
process.exit(1);
|
||||
});
|
||||
25
reputationd/lib/appenv.js
Normal file
25
reputationd/lib/appenv.js
Normal file
@@ -0,0 +1,25 @@
|
||||
const process = require('process');
|
||||
const path = require('path');
|
||||
|
||||
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',
|
||||
}
|
||||
|
||||
appenv = {
|
||||
...appenv,
|
||||
CONFIG_PATH: appenv.DATA_DIR + '/reputationd.cfg',
|
||||
LOG_PATH: appenv.DATA_DIR + '/log/reputationd.log',
|
||||
REPUTATIOND_VERSION: '0.8.3',
|
||||
REPUTATIOND_SCHEDULER_INTERVAL_SECONDS: 2,
|
||||
CONTRACT_PATH: appenv.IS_DEV_MODE ? "../evernode-reputation-contract/dist" : (appenv.DATA_DIR + "/reputation-contract"),
|
||||
MB_XRPL_CONFIG_PATH: path.join(appenv.DATA_DIR, '../') + "mb-xrpl/mb-xrpl.cfg",
|
||||
}
|
||||
|
||||
Object.freeze(appenv);
|
||||
|
||||
module.exports = {
|
||||
appenv
|
||||
}
|
||||
56
reputationd/lib/config-helper.js
Normal file
56
reputationd/lib/config-helper.js
Normal file
@@ -0,0 +1,56 @@
|
||||
const fs = require('fs');
|
||||
|
||||
const COMMON_CONFIG_KEYS = ['network', 'governorAddress', 'rippledServer'];
|
||||
|
||||
class ConfigHelper {
|
||||
static readConfig(configPath, mbXrplConfigPath = null, readSecret = false) {
|
||||
if (!fs.existsSync(configPath))
|
||||
throw `Config file does not exist at ${configPath}`;
|
||||
|
||||
let config = JSON.parse(fs.readFileSync(configPath).toString());
|
||||
|
||||
if (readSecret) {
|
||||
if (!fs.existsSync(config.xrpl.secretPath))
|
||||
throw `Secret config file does not exist at ${config.xrpl.secretPath}`;
|
||||
|
||||
const secretCfg = JSON.parse(fs.readFileSync(config.xrpl.secretPath).toString());
|
||||
config.xrpl = { ...config.xrpl, ...secretCfg.xrpl };
|
||||
}
|
||||
|
||||
if (mbXrplConfigPath && fs.existsSync(mbXrplConfigPath)) {
|
||||
let mbXrplConfig = JSON.parse(fs.readFileSync(mbXrplConfigPath).toString());
|
||||
|
||||
if (readSecret) {
|
||||
if (!fs.existsSync(mbXrplConfig.xrpl.secretPath))
|
||||
throw `Secret config file does not exist at ${mbXrplConfig.xrpl.secretPath}`;
|
||||
|
||||
const mbXrplSecretCfg = JSON.parse(fs.readFileSync(mbXrplConfig.xrpl.secretPath).toString());
|
||||
mbXrplConfig.xrpl = { ...mbXrplConfig.xrpl, ...mbXrplSecretCfg.xrpl }
|
||||
}
|
||||
|
||||
for (const e of Object.entries(mbXrplConfig.xrpl)) {
|
||||
const key = COMMON_CONFIG_KEYS.includes(e[0]) ? e[0] : `host${e[0].charAt(0).toUpperCase()}${e[0].slice(1)}`;
|
||||
if (!(key in config.xrpl))
|
||||
config.xrpl[key] = e[1];
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
static writeConfig(config, configPath) {
|
||||
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 host related props.
|
||||
for (const e of Object.entries(publicCfg.xrpl)) {
|
||||
if (e[0].startsWith('host'))
|
||||
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.
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ConfigHelper
|
||||
}
|
||||
145
reputationd/lib/contract-instance-manager.js
Normal file
145
reputationd/lib/contract-instance-manager.js
Normal file
@@ -0,0 +1,145 @@
|
||||
|
||||
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 {
|
||||
#ip;
|
||||
#userPort;
|
||||
#userPrivateKey;
|
||||
#hpClient;
|
||||
|
||||
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!";
|
||||
|
||||
const userKeys = await CommonHelper.generateKeys(this.#userPrivateKey, 'binary');
|
||||
console.log('My public key is: ' + Buffer.from(userKeys.publicKey).toString('hex'));
|
||||
|
||||
const server = `wss://${this.#ip}:${this.#userPort}`;
|
||||
this.#hpClient = await HotPocket.createClient([server], userKeys, {
|
||||
protocol: HotPocket.protocols.bson
|
||||
});
|
||||
|
||||
// Establish HotPocket connection.
|
||||
if (!await this.#hpClient.connect())
|
||||
throw `${server} connection failed.`;
|
||||
}
|
||||
|
||||
async terminate() {
|
||||
if (this.#hpClient)
|
||||
await this.#hpClient.close()
|
||||
}
|
||||
|
||||
async sendContractInput(input, timeoutMs = DEFAULT_TIMEOUT, protocol = HotPocket.protocols.bson) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
|
||||
const inputTimer = setTimeout(() => {
|
||||
clearTimeout(inputTimer);
|
||||
this.#hpClient.clear(HotPocket.events.contractOutput);
|
||||
reject("Input timeout.");
|
||||
}, timeoutMs);
|
||||
|
||||
const failure = (e) => {
|
||||
clearTimeout(inputTimer);
|
||||
this.#hpClient.clear(HotPocket.events.contractOutput);
|
||||
reject(e);
|
||||
}
|
||||
const success = (result) => {
|
||||
clearTimeout(inputTimer);
|
||||
resolve(result);
|
||||
}
|
||||
|
||||
// This will get fired when contract sends an output.
|
||||
this.#hpClient.on(HotPocket.events.contractOutput, (r) => {
|
||||
|
||||
r.outputs.forEach(output => {
|
||||
let result;
|
||||
try {
|
||||
result = protocol === INPUT_PROTOCOLS.bson ? bson.deserialize(output) : JSON.parse(output);
|
||||
}
|
||||
catch (e) {
|
||||
failure(e);
|
||||
}
|
||||
if (result?.type == `${input.type}Result`) {
|
||||
if (result.status == "ok")
|
||||
success(result.message);
|
||||
else
|
||||
failure(`Input failed. reason: ${result.message}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const res = await this.#hpClient.submitContractInput(protocol === INPUT_PROTOCOLS.bson ? bson.serialize(input) : JSON.stringify(input));
|
||||
|
||||
const submission = await res.submissionStatus;
|
||||
if (submission.status != "accepted")
|
||||
failure("Input submission failed. reason: " + submission.reason);
|
||||
});
|
||||
}
|
||||
|
||||
async sendContractReadRequest(input, protocol = HotPocket.protocols.bson) {
|
||||
const output = await this.#hpClient.submitContractReadRequest(protocol === INPUT_PROTOCOLS.bson ? bson.serialize(input) : JSON.stringify(input));
|
||||
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 = {
|
||||
ContractInstanceManager,
|
||||
INPUT_PROTOCOLS
|
||||
}
|
||||
40
reputationd/lib/logger.js
Normal file
40
reputationd/lib/logger.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const util = require('util');
|
||||
|
||||
const formatText = (text, logType = 'dbg') => {
|
||||
const date = new Date().toISOString().
|
||||
replace(/T/, ' '). // Replace T with a space.
|
||||
replace(/\..+/, ''). // Delete the dot and everything after.
|
||||
replace(/-/g, ''); // Delete the dashes.
|
||||
|
||||
return `${date} [${logType}] ${text}\n`;
|
||||
}
|
||||
|
||||
exports.init = (logPath, fileLogEnabled) => {
|
||||
|
||||
let flog = null;
|
||||
|
||||
if (fileLogEnabled) {
|
||||
const dirname = path.dirname(logPath);
|
||||
if (!fs.existsSync(dirname))
|
||||
fs.mkdirSync(dirname, { recursive: true });
|
||||
|
||||
flog = fs.createWriteStream(logPath, { flags: 'a' });
|
||||
}
|
||||
|
||||
console.log = function () {
|
||||
const text = formatText(util.format.apply(this, arguments));
|
||||
process.stdout.write(text);
|
||||
|
||||
if (flog)
|
||||
flog.write(text);
|
||||
};
|
||||
console.error = function () {
|
||||
const text = formatText(util.format.apply(this, arguments), 'err');
|
||||
process.stderr.write(text);
|
||||
|
||||
if (flog)
|
||||
flog.write(text);
|
||||
};
|
||||
}
|
||||
718
reputationd/lib/reputationd.js
Normal file
718
reputationd/lib/reputationd.js
Normal file
@@ -0,0 +1,718 @@
|
||||
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 { ContractInstanceManager, INPUT_PROTOCOLS } = require('./contract-instance-manager');
|
||||
|
||||
const ContractStatus = {
|
||||
Created: 1,
|
||||
Updated: 2,
|
||||
Deployed: 3
|
||||
}
|
||||
|
||||
class ReputationD {
|
||||
#concurrencyQueue = {
|
||||
processing: false,
|
||||
queue: []
|
||||
};
|
||||
#applyFeeUpliftment = false;
|
||||
#reputationRetryDelay = 300000; // 5 mins
|
||||
#reputationRetryCount = 3;
|
||||
#feeUpliftment = 0;
|
||||
#preparationTimeQuota = 0.9; // Percentage of moment size.
|
||||
#reputationSendTimeSlice = 0.2; // Slice of preparationTimeQuota for reputation registration.
|
||||
#deploymentStartTimeQuota = 0.8;
|
||||
#universeSize = 64;
|
||||
#readScoreCmd = 'read_scores';
|
||||
|
||||
#configPath;
|
||||
#mbXrplConfigPath;
|
||||
#instanceImage;
|
||||
#contractPath;
|
||||
|
||||
constructor(configPath, mbXrplConfigPath, instanceImage, contractPath) {
|
||||
this.#configPath = configPath;
|
||||
this.#mbXrplConfigPath = mbXrplConfigPath;
|
||||
this.#instanceImage = instanceImage;
|
||||
this.#contractPath = contractPath;
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.#readConfig();
|
||||
if (!this.cfg.version || !this.cfg.xrpl.address || !this.cfg.xrpl.secret)
|
||||
throw "Required cfg fields cannot be empty.";
|
||||
|
||||
await evernode.Defaults.useNetwork(this.cfg.xrpl.network || appenv.NETWORK);
|
||||
|
||||
if (this.cfg.xrpl.governorAddress)
|
||||
evernode.Defaults.set({
|
||||
governorAddress: this.cfg.xrpl.governorAddress
|
||||
});
|
||||
|
||||
if (this.cfg.xrpl.rippledServer)
|
||||
evernode.Defaults.set({
|
||||
rippledServer: this.cfg.xrpl.rippledServer
|
||||
});
|
||||
|
||||
if (this.cfg.xrpl.fallbackRippledServers && this.cfg.xrpl.fallbackRippledServers.length)
|
||||
evernode.Defaults.set({
|
||||
fallbackRippledServers: this.cfg.xrpl.fallbackRippledServers
|
||||
});
|
||||
|
||||
this.xrplApi = new evernode.XrplApi();
|
||||
evernode.Defaults.set({
|
||||
xrplApi: this.xrplApi
|
||||
})
|
||||
await this.xrplApi.connect();
|
||||
|
||||
this.hostClient = new evernode.HostClient(this.cfg.xrpl.hostAddress, this.cfg.xrpl.hostSecret);
|
||||
await this.#connectHost();
|
||||
|
||||
console.log("Using,");
|
||||
console.log("\tGovernor account " + this.cfg.xrpl.governorAddress);
|
||||
console.log("\tReputation account " + this.hostClient.config.reputationAddress);
|
||||
console.log("Using xahaud " + this.cfg.xrpl.rippledServer);
|
||||
|
||||
// Get last heartbeat moment from the host info.
|
||||
let hostInfo = await this.hostClient.getRegistration();
|
||||
if (!hostInfo)
|
||||
throw "Host is not registered.";
|
||||
|
||||
this.reputationClient = await evernode.HookClientFactory.create(evernode.HookTypes.reputation);
|
||||
|
||||
await this.#connectReputation();
|
||||
|
||||
const repInfo = await this.hostClient.getReputationInfo();
|
||||
// Last registered moment n means reputation is sent in n-1 moment.
|
||||
this.lastReputationMoment = repInfo ? (repInfo.lastRegisteredMoment - 1) : 0;
|
||||
|
||||
this.xrplApi.on(evernode.XrplApiEvents.DISCONNECTED, async (e) => {
|
||||
console.log(`Exiting due to server disconnect (code ${e})...`);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
|
||||
this.xrplApi.on(evernode.XrplApiEvents.SERVER_DESYNCED, async (e) => {
|
||||
console.log(`Exiting due to server desync condition...`);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
this.xrplApi.on(evernode.XrplApiEvents.LEDGER, async (e) => {
|
||||
this.lastValidatedLedgerIndex = e.ledger_index;
|
||||
this.lastLedgerTime = evernode.UtilHelpers.getCurrentUnixTime('milli');
|
||||
});
|
||||
|
||||
// Start queue processor job.
|
||||
this.#startReputationClockScheduler();
|
||||
|
||||
// Schedule reputation jobs.
|
||||
this.#startReputationSendScheduler();
|
||||
|
||||
// Schedule reputation contract jobs.
|
||||
this.#startReputationContractScheduler();
|
||||
}
|
||||
|
||||
#prepareHostClientFunctionOptions() {
|
||||
let options = {}
|
||||
if (this.#applyFeeUpliftment) {
|
||||
options.transactionOptions = { feeUplift: this.#feeUpliftment }
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
// Try to acquire the lease update lock.
|
||||
async #acquireConcurrencyQueue() {
|
||||
await new Promise(async resolve => {
|
||||
while (this.#concurrencyQueue.processing) {
|
||||
await new Promise(resolveSleep => {
|
||||
setTimeout(resolveSleep, 1000);
|
||||
})
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
this.#concurrencyQueue.processing = true;
|
||||
}
|
||||
|
||||
// Release the lease update lock.
|
||||
async #releaseConcurrencyQueue() {
|
||||
this.#concurrencyQueue.processing = false;
|
||||
}
|
||||
|
||||
async #queueAction(action, maxAttempts = 5, delay = 0) {
|
||||
await this.#acquireConcurrencyQueue();
|
||||
|
||||
this.#concurrencyQueue.queue.push({
|
||||
callback: action,
|
||||
submissionRefs: {},
|
||||
attempts: 0,
|
||||
maxAttempts: maxAttempts,
|
||||
delay: delay
|
||||
});
|
||||
|
||||
await this.#releaseConcurrencyQueue();
|
||||
}
|
||||
|
||||
async #processConcurrencyQueue() {
|
||||
await this.#acquireConcurrencyQueue();
|
||||
|
||||
let toKeep = [];
|
||||
for (let action of this.#concurrencyQueue.queue) {
|
||||
try {
|
||||
await action.callback(action.submissionRefs);
|
||||
this.#applyFeeUpliftment = false;
|
||||
this.#feeUpliftment = 0;
|
||||
}
|
||||
catch (e) {
|
||||
if (action.attempts < action.maxAttempts) {
|
||||
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);
|
||||
}
|
||||
if (action.delay > 0) {
|
||||
new Promise((resolve) => {
|
||||
const checkFlagInterval = setInterval(() => {
|
||||
if (!this.#concurrencyQueue.processing) {
|
||||
this.#concurrencyQueue.queue.push(action);
|
||||
clearInterval(checkFlagInterval);
|
||||
resolve();
|
||||
}
|
||||
}, action.delay);
|
||||
});
|
||||
} else
|
||||
toKeep.push(action);
|
||||
}
|
||||
else {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.#concurrencyQueue.queue = toKeep;
|
||||
|
||||
await this.#releaseConcurrencyQueue();
|
||||
}
|
||||
|
||||
// Connect the host and trying to reconnect in the event of account not found error.
|
||||
// Account not found error can be because of a network reset. (Dev and test nets)
|
||||
async #connect(client, options = null) {
|
||||
let attempts = 0;
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
try {
|
||||
attempts++;
|
||||
const ret = options ? await client.connect(options) : await client.connect();
|
||||
if (ret)
|
||||
break;
|
||||
} catch (error) {
|
||||
if (error?.data?.error === 'actNotFound') {
|
||||
let delaySec;
|
||||
// The maximum delay will be 5 minutes.
|
||||
if (attempts > 150) {
|
||||
delaySec = 300;
|
||||
} else {
|
||||
delaySec = 2 * attempts;
|
||||
}
|
||||
console.log(`Network reset detected. Attempt ${attempts} failed. Retrying in ${delaySec}s...`);
|
||||
await new Promise(resolve => setTimeout(resolve, delaySec * 1000));
|
||||
} else
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async #connectHost() {
|
||||
await this.#connect(this.hostClient, { reputationAddress: this.cfg.xrpl.address, reputationSecret: this.cfg.xrpl.secret });
|
||||
}
|
||||
|
||||
async #connectReputation() {
|
||||
await this.#connect(this.reputationClient);
|
||||
}
|
||||
|
||||
async #startReputationClockScheduler() {
|
||||
const timeout = appenv.REPUTATIOND_SCHEDULER_INTERVAL_SECONDS * 1000; // Seconds to millisecs.
|
||||
|
||||
const scheduler = async () => {
|
||||
await this.#processConcurrencyQueue();
|
||||
setTimeout(async () => {
|
||||
await scheduler();
|
||||
}, timeout);
|
||||
};
|
||||
|
||||
setTimeout(async () => {
|
||||
await scheduler();
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
async #startReputationSendScheduler() {
|
||||
const momentSize = this.hostClient.config.momentSize;
|
||||
|
||||
const timeout = momentSize * 1000; // Converting seconds to milliseconds.
|
||||
|
||||
const scheduler = async () => {
|
||||
setTimeout(async () => {
|
||||
await scheduler();
|
||||
}, timeout);
|
||||
await this.#sendReputations();
|
||||
};
|
||||
|
||||
let startTimeout = 0;
|
||||
const momentStartTimestamp = await this.hostClient.getMomentStartIndex();
|
||||
const currentTimestamp = evernode.UtilHelpers.getCurrentUnixTime();
|
||||
const currentMoment = await this.hostClient.getMoment();
|
||||
|
||||
// Set time relative to current passed time.
|
||||
const timeQuota = momentSize * (1 - this.#preparationTimeQuota);
|
||||
const upperBound = Math.floor(momentStartTimestamp + momentSize - (timeQuota * (1 - this.#reputationSendTimeSlice)));
|
||||
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.
|
||||
|
||||
// If already registered for this moment, Schedule for next moment.
|
||||
if (startTimeout < 0 || this.lastReputationMoment === currentMoment)
|
||||
startTimeout += (momentSize * 1000);
|
||||
|
||||
console.log(`Reputation sender scheduled to start in ${startTimeout} milliseconds.`);
|
||||
|
||||
setTimeout(async () => {
|
||||
await scheduler();
|
||||
}, startTimeout);
|
||||
}
|
||||
|
||||
async #startReputationContractScheduler() {
|
||||
const momentSize = this.hostClient.config.momentSize;
|
||||
|
||||
const timeout = momentSize * 1000; // Converting seconds to milliseconds.
|
||||
|
||||
const scheduler = async () => {
|
||||
setTimeout(async () => {
|
||||
await scheduler();
|
||||
}, timeout);
|
||||
await this.#createReputationContract();
|
||||
};
|
||||
|
||||
let startTimeout = 0;
|
||||
const momentStartTimestamp = await this.hostClient.getMomentStartIndex();
|
||||
const currentTimestamp = evernode.UtilHelpers.getCurrentUnixTime();
|
||||
const currentMoment = await this.hostClient.getMoment();
|
||||
|
||||
const timeQuota = momentSize * (1 - this.#preparationTimeQuota);
|
||||
const upperBound = Math.floor(momentStartTimestamp + momentSize);
|
||||
const lowerBound = Math.floor(momentStartTimestamp + momentSize - (timeQuota * (1 - this.#reputationSendTimeSlice)));
|
||||
|
||||
if (currentTimestamp < lowerBound)
|
||||
startTimeout = Math.floor(lowerBound - currentTimestamp) * 1000;
|
||||
|
||||
// If deploy widow has passed or, not registered for next moment, Schedule for next moment.
|
||||
if (currentTimestamp > upperBound || (startTimeout === 0 && this.lastReputationMoment !== currentMoment))
|
||||
startTimeout = Math.floor(lowerBound + momentSize - currentTimestamp) * 1000;
|
||||
|
||||
// If zero, We are in the deploy window. Try to deploy now and schedule the next in start of next moments window.
|
||||
if (startTimeout === 0) {
|
||||
console.log(`Reputation contract creation will be done now since we are in the window.`);
|
||||
setTimeout(async () => {
|
||||
await this.#createReputationContract();
|
||||
}, 0);
|
||||
startTimeout = Math.floor(lowerBound + momentSize - currentTimestamp) * 1000;
|
||||
console.log(`Next reputation contract creation scheduled to start in ${startTimeout} milliseconds.`);
|
||||
}
|
||||
else {
|
||||
console.log(`Reputation contract creation scheduled to start in ${startTimeout} milliseconds.`);
|
||||
}
|
||||
|
||||
setTimeout(async () => {
|
||||
await scheduler();
|
||||
}, 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 (!repInfo || !('orderedId' in repInfo))
|
||||
return null;
|
||||
|
||||
return {
|
||||
...repInfo,
|
||||
universeIndex: Math.floor(repInfo.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);
|
||||
if (!repInfo)
|
||||
return null;
|
||||
|
||||
return repInfo.contract;
|
||||
}))).filter(i => i);
|
||||
}
|
||||
|
||||
// Find the universe id and generate contract id.
|
||||
#generateContractId(universeIndex) {
|
||||
const buf = Buffer.alloc(4, 0);
|
||||
buf.writeUint32LE(universeIndex);
|
||||
|
||||
// Generate a hash from the seed
|
||||
const hash = crypto.createHash('sha1').update(buf.toString('hex')).digest('hex');
|
||||
// Use a portion of the hash to generate a random UUID
|
||||
const id = uuid.v4({
|
||||
random: Buffer.from(hash, 'hex')
|
||||
});
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
async #deployContract(instanceIp, instanceUserPort, userPrivateKey, hpOverrideCfg) {
|
||||
const bundlePath = await ContractHelper.prepareContractBundle(this.#contractPath, hpOverrideCfg);
|
||||
console.log(`Prepared contract bundle at ${bundlePath}`);
|
||||
|
||||
let instanceMgr;
|
||||
try {
|
||||
instanceMgr = new ContractInstanceManager({
|
||||
ip: instanceIp,
|
||||
userPort: instanceUserPort,
|
||||
userPrivateKey: userPrivateKey
|
||||
});
|
||||
|
||||
await instanceMgr.init();
|
||||
await instanceMgr.uploadBundle(bundlePath);
|
||||
|
||||
fs.rmSync(path.dirname(bundlePath), { recursive: true, force: true });
|
||||
if (instanceMgr)
|
||||
await instanceMgr.terminate();
|
||||
console.log(`Contract bundle uploaded!`);
|
||||
} catch (e) {
|
||||
fs.rmSync(path.dirname(bundlePath), { recursive: true, force: true });
|
||||
if (instanceMgr)
|
||||
await instanceMgr.terminate();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// Create and setup reputation contract.
|
||||
async #createReputationContract() {
|
||||
const scheduledMoment = await this.hostClient.getMoment();
|
||||
|
||||
await this.#queueAction(async (submissionRefs) => {
|
||||
const curMoment = await this.reputationClient.getMoment();
|
||||
|
||||
const universeInfo = await this.#getUniverseInfo(curMoment + 1);
|
||||
|
||||
if (!universeInfo) {
|
||||
console.log(`Skipping reputation contract preparation since there's no universe info for the moment ${curMoment + 1}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (scheduledMoment != curMoment) {
|
||||
console.log(`Skipping since scheduled moment has passed. Scheduled in ${scheduledMoment}, Current moment ${curMoment}.`);
|
||||
return;
|
||||
}
|
||||
else if (this.lastReputationMoment !== curMoment) {
|
||||
console.log(`Skipping reputation contract preparation since not registered for the moment ${curMoment + 1}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Preparing reputation contract for the Moment ${curMoment + 1}...`);
|
||||
|
||||
let createdMoment = this.cfg.contractInstance?.created_moment ?? -1;
|
||||
let acquireSentMoment = this.cfg.contractInstance?.transaction ? (this.cfg.contractInstance?.acquire_sent_moment ?? -1) : -1;
|
||||
|
||||
if (curMoment > createdMoment) {
|
||||
const tenantClient = new evernode.TenantClient(this.hostClient.reputationAcc.address, this.hostClient.reputationAcc.secret);
|
||||
await tenantClient.connect();
|
||||
|
||||
submissionRefs.refs ??= [{}, {}];
|
||||
// Check again wether the transaction is validated before retry.
|
||||
const txHash1 = submissionRefs?.refs[0]?.submissionResult?.result?.tx_json?.hash;
|
||||
let retry = true;
|
||||
if (txHash1) {
|
||||
const txResponse = await tenantClient.xrplApi.getTransactionValidatedResults(txHash1);
|
||||
if (txResponse && txResponse.code === "tesSUCCESS") {
|
||||
console.log('Transaction is validated and success, Retry skipped!');
|
||||
retry = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (retry) {
|
||||
await tenantClient.prepareAccount({ submissionRef: submissionRefs?.refs[0], ...this.#prepareHostClientFunctionOptions() });
|
||||
}
|
||||
|
||||
// Check again wether the transaction is validated before retry.
|
||||
const txHash2 = submissionRefs?.refs[1]?.submissionResult?.result?.tx_json?.hash;
|
||||
retry = true;
|
||||
if (txHash2) {
|
||||
const txResponse = await tenantClient.xrplApi.getTransactionValidatedResults(txHash2);
|
||||
if (txResponse && txResponse.code === "tesSUCCESS") {
|
||||
console.log('Transaction is validated and success, Retry skipped!')
|
||||
retry = false;
|
||||
}
|
||||
}
|
||||
|
||||
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)' : ''}...`);
|
||||
|
||||
let requirement = {
|
||||
owner_pubkey: ownerKeys.publicKey,
|
||||
contract_id: contractId,
|
||||
image: this.#instanceImage,
|
||||
config: {
|
||||
contract: {
|
||||
consensus: {
|
||||
roundtime: 5000,
|
||||
threshold: 50
|
||||
}
|
||||
},
|
||||
mesh: {
|
||||
peer_discovery: {
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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)
|
||||
throw 'Error on acquire submit';
|
||||
|
||||
acquireSentMoment = await this.reputationClient.getMoment();
|
||||
|
||||
this.cfg.contractInstance = {
|
||||
transaction: transaction,
|
||||
acquire_sent_moment: acquireSentMoment,
|
||||
owner_privatekey: ownerKeys.privateKey,
|
||||
status: ContractStatus.AcquireSent
|
||||
};
|
||||
this.#persistConfig();
|
||||
}
|
||||
|
||||
const result = await tenantClient.watchAcquireResponse(this.cfg.contractInstance.transaction);
|
||||
createdMoment = await this.reputationClient.getMoment();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
console.log('Reputation contract created in instance', result.instance);
|
||||
|
||||
this.cfg.contractInstance = {
|
||||
...result.instance,
|
||||
created_moment: createdMoment,
|
||||
owner_privatekey: ownerKeys.privateKey,
|
||||
status: ContractStatus.Created
|
||||
};
|
||||
this.#persistConfig();
|
||||
}
|
||||
|
||||
await tenantClient.disconnect();
|
||||
}
|
||||
else {
|
||||
console.log(`Skipping acquire since there is already created instance for the moment ${curMoment + 1}.`);
|
||||
}
|
||||
|
||||
if (curMoment === createdMoment) {
|
||||
|
||||
if (this.cfg.contractInstance.status === ContractStatus.Created) {
|
||||
// Set reputation contract info in domain.
|
||||
console.log(`Updating host reputation domain info...`);
|
||||
await this.hostClient.setReputationContractInfo(this.cfg.contractInstance.peer_port, this.cfg.contractInstance.pubkey, curMoment + 1);
|
||||
console.log(`Updated host reputation domain info.`);
|
||||
|
||||
// Mark as updated.
|
||||
this.cfg.contractInstance.status = ContractStatus.Updated;
|
||||
this.#persistConfig();
|
||||
}
|
||||
|
||||
if (this.cfg.contractInstance.status === ContractStatus.Updated) {
|
||||
// Wait for some time to let others to prepare.
|
||||
const momentSize = this.hostClient.config.momentSize;
|
||||
const momentStartTimestamp = await this.hostClient.getMomentStartIndex();
|
||||
const currentTimestamp = evernode.UtilHelpers.getCurrentUnixTime();
|
||||
|
||||
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));
|
||||
|
||||
let startTimeout = 0;
|
||||
if (startTimestamp > currentTimestamp)
|
||||
startTimeout = (startTimestamp - currentTimestamp) * 1000;
|
||||
|
||||
console.log(`Waiting ${startTimeout} milliseconds until other hosts are ready.`);
|
||||
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',
|
||||
consensus: {
|
||||
roundtime: 5000
|
||||
}
|
||||
},
|
||||
mesh: {
|
||||
known_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.`);
|
||||
|
||||
// Mark as deployed.
|
||||
this.cfg.contractInstance.status = ContractStatus.Deployed;
|
||||
this.#persistConfig();
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log(`Skipping deploy since instance is not created in the moment ${curMoment}.`)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async #getScores() {
|
||||
if (!this.cfg.contractInstance?.domain || !this.cfg.contractInstance?.user_port)
|
||||
return null;
|
||||
|
||||
let instanceMgr;
|
||||
try {
|
||||
instanceMgr = new ContractInstanceManager({
|
||||
ip: this.cfg.contractInstance.domain,
|
||||
userPort: this.cfg.contractInstance.user_port,
|
||||
userPrivateKey: this.cfg.contractInstance.owner_privatekey
|
||||
});
|
||||
|
||||
await instanceMgr.init();
|
||||
const res = await instanceMgr.sendContractReadRequest({ command: this.#readScoreCmd }, INPUT_PROTOCOLS.json);
|
||||
return res;
|
||||
} catch (e) {
|
||||
console.error('Error occurred while reading the scores:', e);
|
||||
return null;
|
||||
}
|
||||
finally {
|
||||
if (instanceMgr)
|
||||
await instanceMgr.terminate();
|
||||
}
|
||||
}
|
||||
|
||||
// Reputation sender.
|
||||
async #sendReputations() {
|
||||
const scheduledMoment = await this.hostClient.getMoment();
|
||||
|
||||
await this.#queueAction(async (submissionRefs) => {
|
||||
// Skip if host is not registered.
|
||||
const hostInfo = await this.hostClient.getRegistration();
|
||||
if (!hostInfo.active) {
|
||||
console.log(`Skipping reputation sender since host is not active.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const currentMoment = await this.hostClient.getMoment();
|
||||
|
||||
if (scheduledMoment == currentMoment) {
|
||||
// Sending reputations every moment.
|
||||
if (this.lastReputationMoment === 0 || currentMoment !== this.lastReputationMoment) {
|
||||
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 this.hostClient.xrplApi.getTransactionValidatedResults(txHash);
|
||||
if (txResponse && txResponse.code === "tesSUCCESS") {
|
||||
console.log('Transaction is validated and success, Retry skipped!')
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let scores = null;
|
||||
const createdMoment = this.cfg.contractInstance?.created_moment ?? -2;
|
||||
if (currentMoment === (createdMoment + 1))
|
||||
scores = await this.#getScores();
|
||||
|
||||
console.log(`Reporting reputations at Moment ${currentMoment} ${scores ? 'With scores' : 'Without scores'}...`);
|
||||
|
||||
try {
|
||||
await this.hostClient.sendReputations(scores, { submissionRef: submissionRefs?.refs[0], ...this.#prepareHostClientFunctionOptions() });
|
||||
this.lastReputationMoment = await this.hostClient.getMoment();
|
||||
}
|
||||
catch (err) {
|
||||
if (err.code === 'tecHOOK_REJECTED') {
|
||||
console.log("Reputation rejected by the hook.");
|
||||
}
|
||||
else {
|
||||
console.log("Reputation tx error", err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log(`Skipping reputation sender since scheduled moment has passed. Scheduled in ${scheduledMoment}, Current moment ${curMoment}.`);
|
||||
}
|
||||
|
||||
}, this.#reputationRetryCount, this.#reputationRetryDelay);
|
||||
}
|
||||
|
||||
#readConfig() {
|
||||
this.cfg = ConfigHelper.readConfig(this.#configPath, this.#mbXrplConfigPath, true);
|
||||
}
|
||||
|
||||
#persistConfig() {
|
||||
ConfigHelper.writeConfig(this.cfg, this.#configPath);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ReputationD
|
||||
}
|
||||
151
reputationd/lib/setup.js
Normal file
151
reputationd/lib/setup.js
Normal file
@@ -0,0 +1,151 @@
|
||||
const { appenv } = require('./appenv');
|
||||
const evernode = require('evernode-js-client');
|
||||
const { ConfigHelper } = require('./config-helper');
|
||||
|
||||
async function setEvernodeDefaults(network, governorAddress, rippledServer, fallbackRippledServers) {
|
||||
await evernode.Defaults.useNetwork(network || appenv.NETWORK);
|
||||
|
||||
if (governorAddress)
|
||||
evernode.Defaults.set({
|
||||
governorAddress: governorAddress
|
||||
});
|
||||
|
||||
if (rippledServer)
|
||||
evernode.Defaults.set({
|
||||
rippledServer: rippledServer
|
||||
});
|
||||
|
||||
if (fallbackRippledServers && fallbackRippledServers.length)
|
||||
evernode.Defaults.set({
|
||||
fallbackRippledServers: fallbackRippledServers
|
||||
});
|
||||
}
|
||||
|
||||
const MAX_TX_RETRY_ATTEMPTS = 10;
|
||||
|
||||
class Setup {
|
||||
|
||||
#getConfig(readSecret = true) {
|
||||
return ConfigHelper.readConfig(appenv.CONFIG_PATH, appenv.MB_XRPL_CONFIG_PATH, readSecret);
|
||||
}
|
||||
|
||||
#saveConfig(cfg) {
|
||||
ConfigHelper.writeConfig(cfg, appenv.CONFIG_PATH);
|
||||
}
|
||||
|
||||
newConfig(address = "", secretPath = "") {
|
||||
const baseConfig = {
|
||||
version: appenv.REPUTATIOND_VERSION,
|
||||
xrpl: {
|
||||
address: address,
|
||||
secretPath: secretPath
|
||||
},
|
||||
contractInstance: {}
|
||||
};
|
||||
|
||||
this.#saveConfig(baseConfig);
|
||||
}
|
||||
|
||||
async prepareReputationAccount() {
|
||||
|
||||
const config = this.#getConfig();
|
||||
const acc = config.xrpl;
|
||||
await setEvernodeDefaults(acc.network, acc.governorAddress, acc.rippledServer, acc.fallbackRippledServers);
|
||||
|
||||
// Prepare host account.
|
||||
const hostClient = new evernode.HostClient(acc.hostAddress, acc.hostSecret);
|
||||
await hostClient.connect();
|
||||
|
||||
// Update the Defaults with "xrplApi" of the client.
|
||||
evernode.Defaults.set({
|
||||
xrplApi: hostClient.xrplApi
|
||||
});
|
||||
|
||||
try {
|
||||
console.log(`Preparing reputation account:${acc.address} reputation:${hostClient.config.reputationAddress}`);
|
||||
await hostClient.prepareReputationAccount(acc.address, acc.secret, { 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 waitForFunds(currencyType, expectedBalance, waitPeriod = 120) {
|
||||
|
||||
const config = this.#getConfig(false);
|
||||
const acc = config.xrpl;
|
||||
await setEvernodeDefaults(acc.network, acc.governorAddress, acc.rippledServer, acc.fallbackRippledServers);
|
||||
|
||||
// Prepare host account.
|
||||
const hostClient = new evernode.HostClient(acc.hostAddress);
|
||||
|
||||
// Update the Defaults with "xrplApi" of the client.
|
||||
evernode.Defaults.set({
|
||||
xrplApi: hostClient.xrplApi
|
||||
});
|
||||
|
||||
try {
|
||||
let attempts = 0;
|
||||
let balance = 0;
|
||||
while (attempts >= 0) {
|
||||
try {
|
||||
// In order to handle the account not found issue via catch block.
|
||||
await hostClient.connect();
|
||||
|
||||
// Prepare reputation account.
|
||||
const reputationAcc = new evernode.XrplAccount(acc.address);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
if (currencyType === 'NATIVE')
|
||||
balance = Number((await reputationAcc.getInfo()).Balance) / 1000000;
|
||||
else {
|
||||
const lines = await reputationAcc.getTrustLines(evernode.EvernodeConstants.EVR, hostClient.config.evrIssuerAddress);
|
||||
balance = lines.length > 0 ? Number(lines[0].balance) : 0;
|
||||
}
|
||||
|
||||
if (balance < expectedBalance) {
|
||||
if (++attempts <= waitPeriod)
|
||||
continue;
|
||||
|
||||
await hostClient.disconnect();
|
||||
throw "NOT_ENOUGH_FUNDS";
|
||||
}
|
||||
|
||||
break;
|
||||
} catch (err) {
|
||||
if (err.data?.error === 'actNotFound' && ++attempts <= waitPeriod) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
continue;
|
||||
}
|
||||
await hostClient.disconnect();
|
||||
throw (err.data?.error === 'actNotFound' || err === 'NOT_ENOUGH_FUNDS') ? "Funds not received within timeout." : "Error occurred in account balance check.";
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`${balance} ${currencyType == 'NATIVE' ? 'XAH' : 'EVR'} balance is there in your host account.`);
|
||||
await hostClient.disconnect();
|
||||
}
|
||||
catch (e) {
|
||||
await hostClient.disconnect();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// Upgrades existing message board data to the new version.
|
||||
async upgrade() {
|
||||
|
||||
// Do a simple version change in the config.
|
||||
const cfg = this.#getConfig(false);
|
||||
cfg.version = appenv.REPUTATIOND_VERSION;
|
||||
|
||||
this.#saveConfig(cfg);
|
||||
|
||||
await Promise.resolve(); // async placeholder.
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Setup
|
||||
}
|
||||
91
reputationd/lib/util-helper.js
Normal file
91
reputationd/lib/util-helper.js
Normal file
@@ -0,0 +1,91 @@
|
||||
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(contractPath, 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);
|
||||
|
||||
// Copy content inside contract directory;
|
||||
execSync(`cp -r ${contractPath}/* ${contractPrepPath}`);
|
||||
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') {
|
||||
const keys = await HotPocket.generateKeys(privateKey);
|
||||
return format === 'hex' ? {
|
||||
privateKey: Buffer.from(keys.privateKey).toString('hex'),
|
||||
publicKey: Buffer.from(keys.publicKey).toString('hex')
|
||||
} : keys;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
FsHelper,
|
||||
ContractHelper,
|
||||
CommonHelper
|
||||
}
|
||||
2727
reputationd/package-lock.json
generated
Normal file
2727
reputationd/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
reputationd/package.json
Normal file
16
reputationd/package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "reputationd",
|
||||
"scripts": {
|
||||
"lint": "./node_modules/.bin/eslint ./app.js",
|
||||
"build": "npm run lint && ncc build app.js --minify -o dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"evernode-js-client": "0.6.49",
|
||||
"hotpocket-js-client": "0.5.6",
|
||||
"uuid": "9.0.1",
|
||||
"archiver": "5.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "8.3.0"
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,9 @@
|
||||
#include "version.hpp"
|
||||
|
||||
#define DEV_MODE "DEV_MODE"
|
||||
#define DATA_DIR "DATA_DIR"
|
||||
|
||||
std::string exec_dir;
|
||||
std::string data_dir;
|
||||
|
||||
/**
|
||||
* Performs any cleanup on graceful application termination.
|
||||
@@ -57,7 +58,7 @@ void std_terminate() noexcept
|
||||
template <typename executor>
|
||||
int execute_cli(executor const &func)
|
||||
{
|
||||
if (cli::init(exec_dir) == -1)
|
||||
if (cli::init(data_dir) == -1)
|
||||
return -1;
|
||||
|
||||
const int ret = func();
|
||||
@@ -92,7 +93,7 @@ int parse_cmd(int argc, char **argv)
|
||||
std::string json_message;
|
||||
json->add_option("-m,--message", json_message, "JSON message");
|
||||
|
||||
create->group(""); //Hides 'create' command from help-all
|
||||
create->group(""); // Hides 'create' command from help-all
|
||||
std::string owner, contract_id, image, outbound_ipv6, outbound_net_interface;
|
||||
create->add_option("-o,--owner", owner, "Hex (ed-prefixed) public key of the instance owner");
|
||||
create->add_option("-c,--contract-id", contract_id, "Contract Id (GUID) of the instance");
|
||||
@@ -112,9 +113,14 @@ int parse_cmd(int argc, char **argv)
|
||||
// Take the realpath of sashi cli exec path.
|
||||
{
|
||||
std::array<char, PATH_MAX> buffer;
|
||||
if (realpath(argv[0], buffer.data()))
|
||||
const char *env_var = getenv(DATA_DIR);
|
||||
if (env_var != nullptr)
|
||||
{
|
||||
exec_dir = dirname(buffer.data());
|
||||
data_dir = std::string(env_var);
|
||||
}
|
||||
else if (realpath(argv[0], buffer.data()))
|
||||
{
|
||||
data_dir = dirname(buffer.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -124,7 +130,7 @@ int parse_cmd(int argc, char **argv)
|
||||
std::cerr << errno << ": Error in executable path." << std::endl;
|
||||
return -1;
|
||||
}
|
||||
exec_dir = buffer.data();
|
||||
data_dir = buffer.data();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,11 +140,13 @@ int parse_cmd(int argc, char **argv)
|
||||
|
||||
if (!is_dev_mode)
|
||||
{
|
||||
if(create->parsed()){
|
||||
if (create->parsed())
|
||||
{
|
||||
std::cout << "Command not supported: Run with --help or --help-all for more information." << std::endl;
|
||||
return -1;
|
||||
}
|
||||
if(json->parsed() && !json_message.empty()){
|
||||
if (json->parsed() && !json_message.empty())
|
||||
{
|
||||
jsoncons::json json_data = jsoncons::json::parse(json_message);
|
||||
if (json_data.contains("type") && json_data["type"].as_string() == "create")
|
||||
{
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace hp
|
||||
constexpr const char *COPY_DIR = "cp -r %s %s";
|
||||
constexpr const char *MOVE_DIR = "mv %s %s";
|
||||
constexpr const char *CHOWN_DIR = "chown -R %s:%s %s";
|
||||
constexpr const char *CHMOD_DIR = "chmod -R %s %s";
|
||||
|
||||
// Error codes used in create and initiate instance.
|
||||
constexpr const char *DB_READ_ERROR = "db_read_error";
|
||||
@@ -157,12 +158,13 @@ namespace hp
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (image.substr(0, conf::cfg.docker.image_prefix.size()) != conf::cfg.docker.image_prefix)
|
||||
{
|
||||
error_msg = DOCKER_IMAGE_INVALID;
|
||||
LOG_ERROR << "Provided docker image is not allowed.";
|
||||
return -1;
|
||||
}
|
||||
// Allow any image outside of Evernode labs
|
||||
// if (image.substr(0, conf::cfg.docker.image_prefix.size()) != conf::cfg.docker.image_prefix)
|
||||
// {
|
||||
// error_msg = DOCKER_IMAGE_INVALID;
|
||||
// LOG_ERROR << "Provided docker image is not allowed.";
|
||||
// return -1;
|
||||
// }
|
||||
|
||||
const std::string image_name = image;
|
||||
|
||||
@@ -592,7 +594,11 @@ namespace hp
|
||||
len = 12 + (username.length() * 2) + contract_dir.length();
|
||||
char own_command[len];
|
||||
sprintf(own_command, CHOWN_DIR, username.data(), username.data(), contract_dir.data());
|
||||
if (system(own_command) != 0 || chmod(contract_dir.data(), util::DIR_PERMS) == -1)
|
||||
len = 11 + 4 + contract_dir.length();
|
||||
// Give group write access to the contract directory, So contract user can write into it.
|
||||
char perm_command[len];
|
||||
sprintf(perm_command, CHMOD_DIR, "0775", contract_dir.data());
|
||||
if (system(own_command) != 0 || system(perm_command) != 0)
|
||||
{
|
||||
LOG_ERROR << "Changing contract ownership and permissions failed " << contract_dir;
|
||||
return -1;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
namespace version
|
||||
{
|
||||
// Sashimono agent version. Written to new configs.
|
||||
constexpr const char *AGENT_VERSION = "0.8.2";
|
||||
constexpr const char *AGENT_VERSION = "0.8.3";
|
||||
|
||||
// Minimum compatible config version (this will be used to validate configs).
|
||||
constexpr const char *MIN_CONFIG_VERSION = "0.5.0";
|
||||
|
||||
Reference in New Issue
Block a user