diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c7c236..9205a31 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,7 @@ add_custom_command(TARGET sagent POST_BUILD 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 ./reputationd/delegate-hook install && npm run --prefix ./reputationd/delegate-hook build ) target_precompile_headers(sagent PUBLIC src/pchheader.hpp) @@ -84,6 +85,9 @@ add_custom_target(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 mkdir -p ./build/installer/reputationd/delegate/ + COMMAND bash -c "cp -r ./reputationd/delegate-hook/dist/hook-setup/* ./build/installer/reputationd/delegate/" + COMMAND tar cfz ./build/installer.tar.gz --directory=./build/ installer COMMAND rm -r ./build/installer diff --git a/dependencies/user-install.sh b/dependencies/user-install.sh index 1ae18cb..79bcf70 100755 --- a/dependencies/user-install.sh +++ b/dependencies/user-install.sh @@ -169,7 +169,7 @@ for ((i = 0; i < $gp_udp_port_count; i++)); do sed -n -r -e "/${gp_udp_port}\s*ALLOW\s*Anywhere/{q100}" <<<"$rule_list" res=$? if [ ! $res -eq 100 ]; then - gp_udp_port_comment=$comment-gc-udp-$i + gp_udp_port_comment=$comment-gp-udp-$i echo "Adding new rule to allow general purpose udp port for new instance from firewall." sudo ufw allow "$gp_udp_port" comment "$gp_udp_port_comment" else @@ -183,7 +183,7 @@ for ((i = 0; i < $gp_tcp_port_count; i++)); do sed -n -r -e "/${gp_tcp_port}\s*ALLOW\s*Anywhere/{q100}" <<<"$rule_list" res=$? if [ ! $res -eq 100 ]; then - gp_tcp_port_comment=$comment-gc-tcp-$i + gp_tcp_port_comment=$comment-gp-tcp-$i echo "Adding new rule to allow general purpose tcp port for new instance from firewall." sudo ufw allow "$gp_tcp_port" comment "$gp_tcp_port_comment" else diff --git a/dependencies/user-uninstall.sh b/dependencies/user-uninstall.sh index 11bc3c1..1ffdbfb 100755 --- a/dependencies/user-uninstall.sh +++ b/dependencies/user-uninstall.sh @@ -110,7 +110,7 @@ fi # Remove rules for general purpose udp port. for ((i = 0; i < $gp_udp_port_count; i++)); do gp_udp_port=$(expr $gp_udp_port_start + $i) - gp_udp_port_comment=$comment-gc-udp-$i + gp_udp_port_comment=$comment-gp-udp-$i sed -n -r -e "/${gp_udp_port_comment}/{q100}" <<<"$rule_list" res=$? if [ $res -eq 100 ]; then @@ -124,7 +124,7 @@ done # Remove rules for general purpose tcp port. for ((i = 0; i < $gp_tcp_port_count; i++)); do gp_tcp_port=$(expr $gp_tcp_port_start + $i) - gp_tcp_port_comment=$comment-gc-tcp-$i + gp_tcp_port_comment=$comment-gp-tcp-$i sed -n -r -e "/${gp_tcp_port_comment}/{q100}" <<<"$rule_list" res=$? if [ $res -eq 100 ]; then diff --git a/installer/jshelper/package-lock.json b/installer/jshelper/package-lock.json index 0f4a805..1b6190f 100644 --- a/installer/jshelper/package-lock.json +++ b/installer/jshelper/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "evernode-setup-helper", "dependencies": { - "evernode-js-client": "0.6.52", + "evernode-js-client": "0.6.53", "ip6addr": "0.2.5", "ripple-keypairs": "1.3.1" } @@ -405,9 +405,9 @@ } }, "node_modules/evernode-js-client": { - "version": "0.6.52", - "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.52.tgz", - "integrity": "sha512-jBvuL/jBf0XX6f44OwQEuDrKQQyC5pDwa7bohd3pQ3Kicz3KftxZ4qs3scqJyHobm4CY8e+ad9UrGrrp8j31Fg==", + "version": "0.6.53", + "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.53.tgz", + "integrity": "sha512-LsU+IYLdZBLenFYpoojxKVT3I41/JOowl0swMZBM6Yi4/13sv1alzC9cWWaSdpHu5qTuuN+YFGdrJ0MOPot8FQ==", "dependencies": { "elliptic": "6.5.4", "libsodium-wrappers": "0.7.10", diff --git a/installer/jshelper/package.json b/installer/jshelper/package.json index d97fbc0..3980c6c 100644 --- a/installer/jshelper/package.json +++ b/installer/jshelper/package.json @@ -4,7 +4,7 @@ "build": "ncc build index.js --minify -o dist" }, "dependencies": { - "evernode-js-client": "0.6.52", + "evernode-js-client": "0.6.53", "ip6addr": "0.2.5", "ripple-keypairs": "1.3.1" } diff --git a/installer/sashimono-install.sh b/installer/sashimono-install.sh index 46e588d..1436378 100755 --- a/installer/sashimono-install.sh +++ b/installer/sashimono-install.sh @@ -461,7 +461,6 @@ mkdir -p $SASHIMONO_DATA cp "$script_dir"/sashimono-uninstall.sh $SASHIMONO_BIN chmod +x $SASHIMONO_BIN/sashimono-uninstall.sh - ! set_cpu_info && echo "Fetching CPU info failed" && abort # Copy contract template and licence file (delete existing) @@ -517,7 +516,7 @@ 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 +# 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. @@ -594,6 +593,26 @@ WantedBy=multi-user.target" >/etc/systemd/system/$CGCREATE_SERVICE.service echo "Configuring sashimono agent service..." +# Since gp ports are added as new feature we manually configure the default on upgrade mode if not exists. +# TODO: Added because v0.8.4 does not have gp ports. +if [[ "$UPGRADE" == "1" ]] && [ -f "$SASHIMONO_CONFIG" ]; then + cfg_init_gp_tcp_port=$(jq ".hp.init_gp_tcp_port | select( . != null )" "$SASHIMONO_CONFIG") + cfg_init_gp_udp_port=$(jq ".hp.init_gp_udp_port | select( . != null )" "$SASHIMONO_CONFIG") + if [ -z $cfg_init_gp_tcp_port ] || [ -z $cfg_init_gp_udp_port ]; then + if [ -z $cfg_init_gp_tcp_port ]; then + cfg_init_gp_tcp_port=36525 + tmp=$(mktemp) + jq ".hp.init_gp_tcp_port = $cfg_init_gp_tcp_port" "$SASHIMONO_CONFIG" >"$tmp" && mv "$tmp" "$SASHIMONO_CONFIG" + fi + if [ -z $cfg_init_gp_udp_port ]; then + cfg_init_gp_udp_port=39064 + tmp=$(mktemp) + jq ".hp.init_gp_udp_port = $cfg_init_gp_udp_port" "$SASHIMONO_CONFIG" >"$tmp" && mv "$tmp" "$SASHIMONO_CONFIG" + fi + chmod 644 "$SASHIMONO_CONFIG" + fi +fi + # Create sashimono agent config (if not exists). if [ -f $SASHIMONO_DATA/sa.cfg ]; then echo "Existing Sashimono data directory found. Updating..." @@ -698,5 +717,4 @@ fi echo "Sashimono installed successfully." - exit 0 diff --git a/installer/setup.sh b/installer/setup.sh index a827570..428800f 100755 --- a/installer/setup.sh +++ b/installer/setup.sh @@ -26,11 +26,15 @@ log_dir=/tmp/evernode reputationd_script_dir=$(dirname "$(realpath "$0")") root_user="root" - + repo_owner="EvernodeXRPL" repo_name="evernode-resources" desired_branch="main" + # Reputation modes : 0 - "none", 1 - "OneToOne", 2 - "OneToMany" + is_fresh_reputation_acc=false + reputation_account_mode=0 + latest_version_endpoint="https://api.github.com/repos/$repo_owner/$repo_name/releases/latest" latest_version_data=$(curl -s "$latest_version_endpoint") latest_version=$(echo "$latest_version_data" | jq -r '.tag_name') @@ -1033,6 +1037,15 @@ function collect_host_xrpl_account_inputs() { # NOTE this method declares the accounts and secrets for a provided prefix. + if [ -z $rippled_server ]; then + if [ -f "$MB_XRPL_CONFIG" ]; then + local xahau_server=$(jq -r '.xrpl.rippledServer' $MB_XRPL_CONFIG) + else + echo "Message board configuration does not exist." && return 1 + fi + else + local xahau_server="$rippled_server" + fi local xahau_account_address="" local xahau_account_secret="" local prefix="${1:-xrpl}" @@ -1045,7 +1058,7 @@ ! [[ $xahau_account_secret =~ ^s[1-9A-HJ-NP-Za-km-z]{25,35}$ ]] && echo "Invalid account secret." && continue echomult "\nChecking account keys..." - ! exec_jshelper validate-keys $rippled_server $xahau_account_address $xahau_account_secret && xahau_account_secret="" && continue + ! exec_jshelper validate-keys $xahau_server $xahau_account_address $xahau_account_secret && xahau_account_secret="" && continue break done @@ -1217,8 +1230,9 @@ if ! confirm "Do you already have a Xahau account that has been used for reputation assessment?" "n"; then generate_keys "reputationd" + is_fresh_reputation_acc=true else - collect_host_xrpl_account_inputs "reputationd_xrpl" + collect_host_xrpl_account_inputs "reputationd_xrpl" || exit 1 fi echo "{ \"xrpl\": { \"secret\": \"$reputationd_xrpl_secret\" } }" >"$reputationd_key_file_path" && @@ -2109,9 +2123,14 @@ WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer # Configure reputationd users and register host. echomult "Configuring Evernode reputation for reward distribution..." + local override_network=$(jq -r ".xrpl.network | select( . != null )" "$MB_XRPL_CONFIG") + if [ ! -z $override_network ]; then + NETWORK="$override_network" + fi + if [ -f "$REPUTATIOND_CONFIG" ]; then reputationd_secret_path=$(jq -r '.xrpl.secretPath' "$REPUTATIOND_CONFIG") - chown "$REPUTATIOND_USER":"$SASHIADMIN_GROUP" $reputationd_secret_path + [ -f $reputationd_secret_path ] && chown "$REPUTATIOND_USER":"$SASHIADMIN_GROUP" $reputationd_secret_path fi if [ "$upgrade" == "0" ]; then # Account generation, @@ -2119,6 +2138,20 @@ WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer echo "error setting up reputationd account." return 1 fi + + if confirm "\nThe Xahau account that you are going to use with the reputationD service can be configured as a delegate account for multiple hosts. \ + \nPlease note that if you set it up this way, there is a chance of missing the current reputation assessment if it is already being used by a single host. \ + \nAdditionally, using a single delegate account for multiple hosts may lead to simultaneous transaction submissions, which can cause some transaction failures.\ + \nHowever, these transactions will succeed on the next attempt.\ + \nWould you like to configure this account as a delegate account for multiple hosts for the reputationD service?" "n"; then + reputation_account_mode=2 + else + [ !$is_fresh_reputation_acc ] && echomult "Warning !!!.\nIf you are planning to configure the delegate account dedicated to your own host, make sure it is not used by another host before continuing." + confirm "\nContinue?" || exit 1 + + reputation_account_mode=1 + fi + fi reputationd_user_dir=/home/"$REPUTATIOND_USER" @@ -2145,15 +2178,9 @@ WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer \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") - # Format lease amount since jq gives it in exponential format. - local lease_amount=$(awk -v lease_amount="$lease_amount" 'BEGIN { printf("%f\n", lease_amount) }' 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" @@ -2166,25 +2193,23 @@ WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer 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 + wait_call "sudo -u $REPUTATIOND_USER REPUTATIOND_DATA_DIR=$REPUTATIOND_DATA node $REPUTATIOND_BIN wait-for-funds NATIVE $min_reputation_xah_requirement" "Sufficient XAH funds have been received." && 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 amount 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 + ! sudo -u $REPUTATIOND_USER REPUTATIOND_DATA_DIR=$REPUTATIOND_DATA node $REPUTATIOND_BIN prepare $reputation_account_mode && echo "Error preparing account" && return 1 + if [ "$reputation_account_mode" == "2" ]; then + echomult "\nInstalling Delegate Hook..." + ! sudo -u $REPUTATIOND_USER NETWORK=$NETWORK CONFIG_PATH=$REPUTATIOND_CONFIG WASM_PATH="$REPUTATIOND_BIN/delegate" node "$REPUTATIOND_BIN/delegate" && echo "Error when setting up Delegate Hook." && return 1 fi + echomult "\nNOTE: To participate in this reputation assessment process continuously, you need to ensure that your reputation account + \nhas a sufficient EVR balance to perform the instance acquisitions." + if [ "$upgrade" == "1" ]; then ! sudo -u $REPUTATIOND_USER REPUTATIOND_DATA_DIR=$REPUTATIOND_DATA node $REPUTATIOND_BIN upgrade && echo "Error upgrading reputationd" && return 1 fi @@ -2332,7 +2357,7 @@ WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer [ ! -f "$SASHIMONO_CONFIG" ] && set_init_ports echo -e "Using peer port range $init_peer_port-$((init_peer_port + alloc_instcount)) and user port range $init_user_port-$((init_user_port + alloc_instcount))).\n" - + [ ! -f "$SASHIMONO_CONFIG" ] && set_init_gp_ports echo -e "Using General purpose TCP port range $init_gp_tcp_port-$((init_gp_tcp_port + gp_tcp_port_count * alloc_instcount)) and general purpose UDP port range $init_gp_udp_port-$((init_gp_udp_port + gp_udp_port_count * alloc_instcount))).\n" @@ -2393,7 +2418,6 @@ WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer \nThe path where the registration account secret is saved can be found inside the configuration stored at '$MB_XRPL_DATA/mb-xrpl.cfg'. \nIf you have configured a reputation account, the path where that account secret is saved can be found inside the configuration stored at $REPUTATIOND_DATA/reputationd.cfg" - ! confirm "\nAre you sure you want to continue?" && exit 1 fi @@ -2564,10 +2588,13 @@ WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer exit 1 fi elif [ "$2" == "status" ]; then - echo "" - reputationd_info echo "" ! sudo -u $REPUTATIOND_USER REPUTATIOND_DATA_DIR=$REPUTATIOND_DATA node $REPUTATIOND_BIN repinfo && echo "Error getting reputation status" && exit 1 + echo -e "\n" + reputationd_info + + echomult "\nNOTE: To participate in this reputation assessment process continuously, you need to ensure that your reputation account + \nhas a sufficient EVR balance to perform the instance acquisitions." else echomult "ReputationD management tool \nSupported commands: diff --git a/mb-xrpl/lib/appenv.js b/mb-xrpl/lib/appenv.js index 60a4f6a..a2a811d 100644 --- a/mb-xrpl/lib/appenv.js +++ b/mb-xrpl/lib/appenv.js @@ -45,7 +45,7 @@ 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.3', + MB_VERSION: '0.9.0', TOS_HASH: '0801677EBCB2F76EF97D531549D8B27DB2C7A4A8EE7F60032AE40184247F0810', // This is the sha256 hash of EVERNODE-HOSTING-PRINCIPLES.pdf. NETWORK: 'mainnet', REPUTATIOND_CONFIG_PATH: path.join(appenv.DATA_DIR, '../') + "reputationd/reputationd.cfg", diff --git a/mb-xrpl/lib/config-helper.js b/mb-xrpl/lib/config-helper.js index ef837d2..8e5f537 100644 --- a/mb-xrpl/lib/config-helper.js +++ b/mb-xrpl/lib/config-helper.js @@ -25,19 +25,24 @@ 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; + try { + if (reputationDConfigPath && fs.existsSync(reputationDConfigPath)) { + const reputationDConfig = JSON.parse(fs.readFileSync(reputationDConfigPath).toString()); + if (fs.existsSync(reputationDConfig.xrpl.secretPath)) { + config.xrpl.reputationAddress = reputationDConfig.xrpl.address; + if (readSecret) { + const reputationDSecretCfg = JSON.parse(fs.readFileSync(reputationDConfig.xrpl.secretPath).toString()); + config.xrpl.reputationSecret = reputationDSecretCfg.xrpl.secret; + } + config.xrpl = { ...reputationDConfig.xrpl, ...config.xrpl } + } + else { + console.error(`Secret config file does not exist at ${reputationDConfig.xrpl.secretPath}.`); + } } - - config.xrpl = { ...reputationDConfig.xrpl, ...config.xrpl } + } + catch (e) { + console.error('Error while reading the reputation config.', e); } return config; diff --git a/mb-xrpl/lib/message-board.js b/mb-xrpl/lib/message-board.js index 5c14cb2..ba14e33 100644 --- a/mb-xrpl/lib/message-board.js +++ b/mb-xrpl/lib/message-board.js @@ -251,14 +251,6 @@ class MessageBoard { } }); - //Initially prune orphan instances - console.log(`Starting the initial prune job...`); - await this.#acquireLeaseUpdateLock(); - await this.#pruneOrphanLeases().catch(console.error).finally(async () => { - await this.#releaseLeaseUpdateLock(); - }); - console.log(`Ended the initial prune job.`); - // Start a job to expire instances and check for halts this.#startSashimonoClockScheduler(); @@ -537,7 +529,7 @@ class MessageBoard { } // Update the registry with the active instance count. await this.hostClient.updateRegInfo(this.activeInstanceCount, null, null, null, null, null, null, null, null, null, null, { submissionRef: submissionRefs?.refs[0] }); - console.log(`${lease.containerName} queued for expiry.`) + console.log(`${lease.containerName} queued for token expiry.`) }); } @@ -585,10 +577,10 @@ class MessageBoard { #startPruneScheduler() { const timeout = appenv.ORPHAN_PRUNE_SCHEDULER_INTERVAL_HOURS * 3600000; // Hours to millisecs. - const scheduler = async () => { + const scheduler = async (isStartup = false) => { console.log(`Starting the scheduled prune job...`); await this.#acquireLeaseUpdateLock(); - await this.#pruneOrphanLeases().catch(console.error).finally(async () => { + await this.#pruneOrphanLeases(isStartup).catch(console.error).finally(async () => { await this.#releaseLeaseUpdateLock(); }); console.log(`Ended the scheduled prune job.`); @@ -598,8 +590,8 @@ class MessageBoard { }; setTimeout(async () => { - await scheduler(); - }, timeout); + await scheduler(true); + }, 0); } #startSashimonoClockScheduler() { @@ -677,7 +669,7 @@ class MessageBoard { this.#leaseUpdateLock = false; } - async #pruneOrphanLeases() { + async #pruneOrphanLeases(isStartup = false) { // Note: If this is soft deletion we need to handle the destroyed status and replace deleteLeaseRecord with changing the status. // Get the records which are created before an acquire timeout x 2. @@ -706,9 +698,9 @@ class MessageBoard { 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. + // If the URIToken is still owned by the host we destroy the instance since this is not a valid lease. // In these cases, destroy the instance. - if (lease.status === LeaseStatus.ACQUIRING || !uriToken) { + if (lease.status === LeaseStatus.ACQUIRING || !uriToken || uriToken.Owner === this.hostClient.xrplAcc.address) { console.log(`Pruning orphan instance ${instance.name}...`); await this.sashiCli.destroyInstance(instance.name); this.db.open(); @@ -717,7 +709,7 @@ class MessageBoard { this.db.close(); // After destroying, If the URIToken is owned by the tenant, burn the URIToken and recreate and refund the tenant. - if (uriToken) { + if (uriToken && uriToken.Owner != this.hostClient.xrplAcc.address) { const uriInfo = evernode.UtilHelpers.decodeLeaseTokenUri(uriToken.URI); await this.recreateLeaseOffer(instance.name, uriInfo.leaseIndex, uriInfo.outboundIP?.address); await this.#queueAction(async (submissionRefs) => { @@ -731,20 +723,19 @@ class MessageBoard { return; } } - console.log(`Refunding tenant ${lease.tenant_xrp_address}...`); - await this.hostClient.refundTenant(lease.tx_hash, lease.tenant_xrp_address, uriInfo.leaseAmount.toString(), { submissionRef: submissionRefs?.refs[0] }); + console.log(`Refunding tenant ${uriToken.Owner}...`); + await this.hostClient.refundTenant(lease.tx_hash, uriToken.Owner, uriInfo.leaseAmount.toString(), { submissionRef: submissionRefs?.refs[0] }); }); - } else { - // Remove the lease record. - if (lease) { - this.db.open(); - await this.deleteLeaseRecord(lease.tx_hash); - this.db.close(); - - if (lease.status === LeaseStatus.ACQUIRED || lease.status === LeaseStatus.EXTENDED) - activeInstanceCount--; - } } + else { + // Remove the lease record. + this.db.open(); + await this.deleteLeaseRecord(lease.tx_hash); + this.db.close(); + } + + if (lease.status === LeaseStatus.ACQUIRED || lease.status === LeaseStatus.EXTENDED) + activeInstanceCount--; } } } @@ -755,7 +746,10 @@ class MessageBoard { // Remove the leases which are orphan (Does not have an instance). // Only consider the older ones. - for (const lease of leases.filter(l => l.timestamp < timeMargin && (l.status === LeaseStatus.ACQUIRING || l.status === LeaseStatus.ACQUIRED || l.status === LeaseStatus.EXTENDED))) { + // If this is prune call at the startup and there are acquiring records, they won't be handled since there's no data for them in the memory. + // Since above do not have timestamp we do not consider time margin, we just prune them. + for (const lease of leases.filter(l => ((isStartup && l.status === LeaseStatus.ACQUIRING) || l.timestamp < timeMargin) && + (l.status === LeaseStatus.ACQUIRING || l.status === LeaseStatus.ACQUIRED || l.status === LeaseStatus.EXTENDED))) { try { // If lease does not have an instance. this.sashiDb.open(); @@ -764,16 +758,13 @@ class MessageBoard { if (!instances || instances.length === 0) { console.log(`Pruning orphan lease ${lease.container_name}...`); - this.db.open(); - await this.deleteLeaseRecord(lease.tx_hash); + let leaseTxHash = await this.getLeaseTxHash(lease.container_name); + await this.updateLeaseStatus(leaseTxHash, LeaseStatus.DESTROYED); this.db.close(); - if (lease.status === LeaseStatus.ACQUIRED || lease.status === LeaseStatus.EXTENDED) - activeInstanceCount--; - const uriToken = (await this.hostClient.getLeaseByIndex(lease.container_name)); - if (uriToken) { + if (uriToken && uriToken.Owner != this.hostClient.xrplAcc.address) { const uriInfo = evernode.UtilHelpers.decodeLeaseTokenUri(uriToken.URI); await this.recreateLeaseOffer(lease.container_name, uriInfo.leaseIndex, uriInfo.outboundIP?.address); @@ -790,11 +781,21 @@ class MessageBoard { } // If lease is in ACQUIRING status acquire response is not received by the tenant and lease is not in expiry list. if (lease.status === LeaseStatus.ACQUIRING) { - console.log(`Refunding tenant ${lease.tenant_xrp_address}...`); - await this.hostClient.refundTenant(lease.tx_hash, lease.tenant_xrp_address, uriInfo.leaseAmount.toString(), { submissionRef: submissionRefs?.refs[0] }); + console.log(`Refunding tenant ${uriToken.Owner}...`); + await this.hostClient.refundTenant(lease.tx_hash, uriToken.Owner, uriInfo.leaseAmount.toString(), { submissionRef: submissionRefs?.refs[0] }); } }); } + else { + // Remove the lease record. + this.db.open(); + await this.deleteLeaseRecord(lease.tx_hash); + this.db.close(); + } + + + if (lease.status === LeaseStatus.ACQUIRED || lease.status === LeaseStatus.EXTENDED) + activeInstanceCount--; } } catch (e) { @@ -822,6 +823,7 @@ class MessageBoard { } async #catchupMissedLeases() { + console.log("Start catching up missed leases"); const fullHistoryXrplApi = new evernode.XrplApi(); this.db.open(); @@ -893,10 +895,10 @@ class MessageBoard { if (!lease) { const uriToken = (await this.hostClient.getLeaseByIndex(eventInfo.data.uriTokenId)); - if (uriToken) { + if (uriToken && uriToken.Owner != this.hostClient.xrplAcc.address) { 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, uriInfo.leaseIndex, uriInfo.outboundIP?.address); + await this.recreateLeaseOffer(eventInfo.data.uriTokenId, uriInfo.leaseIndex, uriInfo.outboundIP?.address, true); await this.#queueAction(async (submissionRefs) => { submissionRefs.refs ??= [{}]; @@ -923,7 +925,7 @@ class MessageBoard { if (lease) { const uriToken = (await this.hostClient.getLeaseByIndex(eventInfo.data.uriTokenId)); - if (uriToken) { + if (uriToken && uriToken.Owner != this.hostClient.xrplAcc.address) { await this.#queueAction(async (submissionRefs) => { submissionRefs.refs ??= [{}]; // Check again wether the transaction is validated before retry. @@ -958,6 +960,7 @@ class MessageBoard { await fullHistoryXrplApi.disconnect(); } + console.log("End catching up missed leases"); } #getTrxHookParams(txn, paramName) { @@ -969,7 +972,7 @@ class MessageBoard { return null; } - async recreateLeaseOffer(uriTokenId, leaseIndex, outboundIP) { + async recreateLeaseOffer(uriTokenId, leaseIndex, outboundIP, noLeaseRecord = false) { await this.#queueAction(async (submissionRefs) => { submissionRefs.refs ??= [{}, {}]; // Check again wether the transaction is validated before retry. @@ -984,11 +987,13 @@ class MessageBoard { } this.db.open(); - let leaseTxHash = await this.getLeaseTxHash(uriTokenId); - if (retry && await this.getLeaseStatus(leaseTxHash) == LeaseStatus.DESTROYED) { + const leaseTxHash = await this.getLeaseTxHash(uriTokenId); + const status = await this.getLeaseStatus(leaseTxHash); + if (retry && (noLeaseRecord || status === LeaseStatus.DESTROYED || status === LeaseStatus.FAILED || status === LeaseStatus.SASHI_TIMEOUT)) { // Burn the URIToken and recreate the offer. await this.hostClient.expireLease(uriTokenId, { submissionRef: submissionRefs?.refs[0] }).catch(console.error); - await this.updateLeaseStatus(leaseTxHash, LeaseStatus.BURNED); + if (!noLeaseRecord) + await this.updateLeaseStatus(leaseTxHash, LeaseStatus.BURNED); } this.db.close(); @@ -1006,11 +1011,12 @@ class MessageBoard { } } this.db.open(); - if (retry && await this.getLeaseStatus(leaseTxHash) == LeaseStatus.BURNED) { + if (retry && (noLeaseRecord || await this.getLeaseStatus(leaseTxHash) == LeaseStatus.BURNED)) { const leaseAmount = this.cfg.xrpl.leaseAmount ? this.cfg.xrpl.leaseAmount : parseFloat(this.hostClient.config.purchaserTargetPrice); await this.hostClient.offerLease(leaseIndex, leaseAmount, appenv.TOS_HASH, outboundIP, { submissionRef: submissionRefs?.refs[1] }).catch(console.error); //Delete the lease record related to this instance (Permanent Delete). - await this.deleteLeaseRecord(leaseTxHash); + if (!noLeaseRecord) + await this.deleteLeaseRecord(leaseTxHash); console.log(`Destroyed ${uriTokenId}.`); } this.db.close(); @@ -1042,9 +1048,9 @@ class MessageBoard { // Get the existing uriToken of the lease. - const uriToken = (await (new evernode.XrplAccount(tenantAddress)).getURITokens())?.find(n => n.index == uriTokenId); - if (!uriToken) - throw 'Could not find the uriToken for lease acquire request.'; + const uriToken = (await this.hostClient.getLeaseByIndex(uriTokenId)); + if (!uriToken || uriToken.Owner !== tenantAddress) + throw "Could not find the uriToken for lease acquire request."; const uriInfo = evernode.UtilHelpers.decodeLeaseTokenUri(uriToken.URI); instanceOutboundIPAddress = uriInfo?.outboundIP?.address; @@ -1156,8 +1162,8 @@ class MessageBoard { await this.updateLeaseStatus(acquireRefId, LeaseStatus.FAILED).catch(console.error); // Destroy the instance if created. - if (createRes) - await this.sashiCli.destroyInstance(createRes.content.name).catch(console.error); + if (createRes || e.type === 'initiate_error') + await this.sashiCli.destroyInstance(e.content.instance_name).catch(console.error); // Re-create the lease offer (Only if the uriToken belongs to this request has a lease index). if (leaseIndex >= 0) @@ -1206,13 +1212,12 @@ class MessageBoard { if (r.transaction.Destination !== this.cfg.xrpl.address) throw "Invalid destination"; - const tenantAcc = new evernode.XrplAccount(tenantAddress); - const hostingToken = (await tenantAcc.getURITokens()).find(n => n.index === uriTokenId && evernode.EvernodeHelpers.isValidURI(n.URI, evernode.EvernodeConstants.LEASE_TOKEN_PREFIX_HEX)); + const hostingToken = await this.hostClient.getLeaseByIndex(uriTokenId); // Update last watched ledger sequence number. await this.updateLastIndexRecord(r.transaction.LedgerIndex); - if (!hostingToken) + if (!hostingToken || hostingToken.Owner !== tenantAddress) throw "The URIToken ownership verification was failed in the lease extension process"; const uriInfo = evernode.UtilHelpers.decodeLeaseTokenUri(hostingToken.URI); @@ -1225,7 +1230,7 @@ class MessageBoard { if (extendingMoments < 1) throw "The transaction does not satisfy the minimum extendable moments"; - const instanceSearchCriteria = { tenant_xrp_address: tenantAddress, container_name: hostingToken.index }; + const instanceSearchCriteria = { container_name: hostingToken.index }; const instance = (await this.getLeaseRecords(instanceSearchCriteria)).find(i => (i.status === LeaseStatus.ACQUIRED || i.status === LeaseStatus.EXTENDED)); diff --git a/mb-xrpl/lib/sashi-cli.js b/mb-xrpl/lib/sashi-cli.js index ff47f1d..bada010 100644 --- a/mb-xrpl/lib/sashi-cli.js +++ b/mb-xrpl/lib/sashi-cli.js @@ -16,7 +16,7 @@ class SashiCLI { requirements.container_name = containerName; const res = await this.execSashiCli(requirements); - if (res.type === 'create_error') + if (res.type === 'create_error' || res.type === 'initiate_error') throw res; return res; diff --git a/mb-xrpl/package-lock.json b/mb-xrpl/package-lock.json index 9f40fe3..1622237 100644 --- a/mb-xrpl/package-lock.json +++ b/mb-xrpl/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "mb-xrpl", "dependencies": { - "evernode-js-client": "0.6.52", + "evernode-js-client": "0.6.53", "ip6addr": "0.2.5", "sqlite3": "5.0.2" }, @@ -1043,9 +1043,9 @@ } }, "node_modules/evernode-js-client": { - "version": "0.6.52", - "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.52.tgz", - "integrity": "sha512-jBvuL/jBf0XX6f44OwQEuDrKQQyC5pDwa7bohd3pQ3Kicz3KftxZ4qs3scqJyHobm4CY8e+ad9UrGrrp8j31Fg==", + "version": "0.6.53", + "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.53.tgz", + "integrity": "sha512-LsU+IYLdZBLenFYpoojxKVT3I41/JOowl0swMZBM6Yi4/13sv1alzC9cWWaSdpHu5qTuuN+YFGdrJ0MOPot8FQ==", "dependencies": { "elliptic": "6.5.4", "libsodium-wrappers": "0.7.10", diff --git a/mb-xrpl/package.json b/mb-xrpl/package.json index 6096f5e..0354f3e 100644 --- a/mb-xrpl/package.json +++ b/mb-xrpl/package.json @@ -5,7 +5,7 @@ "build": "npm run lint && ncc build app.js --minify -o dist" }, "dependencies": { - "evernode-js-client": "0.6.52", + "evernode-js-client": "0.6.53", "sqlite3": "5.0.2", "ip6addr": "0.2.5" }, diff --git a/reputationd b/reputationd index 399e2f5..ea23d13 160000 --- a/reputationd +++ b/reputationd @@ -1 +1 @@ -Subproject commit 399e2f5d275f8af20f5f2be05e17987f9093e788 +Subproject commit ea23d134354e09d20765ec9c4556162b075923d5 diff --git a/src/comm/comm_handler.cpp b/src/comm/comm_handler.cpp index c7f2bdf..72373be 100644 --- a/src/comm/comm_handler.cpp +++ b/src/comm/comm_handler.cpp @@ -2,12 +2,12 @@ #include "../util/util.hpp" #include "../conf.hpp" -#define __HANDLE_RESPONSE(type, content, ret) \ - { \ - std::string res; \ - msg_parser.build_response(res, type, content, ((void *)type == (void *)msg::MSGTYPE_CREATE_RES || (void *)type == (void *)msg::MSGTYPE_LIST_RES || (void *)type == (void *)msg::MSGTYPE_INSPECT_RES) && ret == 0); \ - send(res); \ - return ret; \ +#define __HANDLE_RESPONSE(type, content, ret) \ + { \ + std::string res; \ + msg_parser.build_response(res, type, content, (((void *)type == (void *)msg::MSGTYPE_CREATE_RES || (void *)type == (void *)msg::MSGTYPE_LIST_RES || (void *)type == (void *)msg::MSGTYPE_INSPECT_RES) && ret == 0) || (void *)type == (void *)msg::MSGTYPE_INITIATE_ERROR); \ + send(res); \ + return ret; \ } namespace comm @@ -215,7 +215,11 @@ namespace comm __HANDLE_RESPONSE(msg::MSGTYPE_CREATE_ERROR, error_msg, -1); if (hp::initiate_instance(error_msg, info.container_name, init_msg) == -1) - __HANDLE_RESPONSE(msg::MSGTYPE_CREATE_ERROR, error_msg, -1); + { + std::string content; + msg_parser.build_error_response(content, info.container_name, error_msg); + __HANDLE_RESPONSE(msg::MSGTYPE_INITIATE_ERROR, content, -1); + } std::string create_res; msg_parser.build_create_response(create_res, info); diff --git a/src/hp_manager.cpp b/src/hp_manager.cpp index d8f3cf0..12a6845 100644 --- a/src/hp_manager.cpp +++ b/src/hp_manager.cpp @@ -181,7 +181,7 @@ namespace hp sqlite::get_max_ports(db, last_assigned_ports); last_port_assign_from_vacant = false; } - instance_ports = {(uint16_t)(last_assigned_ports.peer_port + 1), (uint16_t)(last_assigned_ports.user_port + 1), (uint16_t)(last_assigned_ports.gp_tcp_port_start + 2), (uint16_t)(last_assigned_ports.gp_udp_port_start + 2) }; + instance_ports = {(uint16_t)(last_assigned_ports.peer_port + 1), (uint16_t)(last_assigned_ports.user_port + 1), (uint16_t)(last_assigned_ports.gp_tcp_port_start + 2), (uint16_t)(last_assigned_ports.gp_udp_port_start + 2)}; } int user_id; @@ -313,20 +313,20 @@ namespace hp const std::string user_port = std::to_string(assigned_ports.user_port); const std::string peer_port = std::to_string(assigned_ports.peer_port); const std::string gp_tcp_port_1 = std::to_string(assigned_ports.gp_tcp_port_start); - const std::string gp_tcp_port_2 = std::to_string(assigned_ports.gp_tcp_port_start + 1 ); + const std::string gp_tcp_port_2 = std::to_string(assigned_ports.gp_tcp_port_start + 1); const std::string gp_udp_port_1 = std::to_string(assigned_ports.gp_udp_port_start); - const std::string gp_udp_port_2 = std::to_string(assigned_ports.gp_udp_port_start + 1 ); + const std::string gp_udp_port_2 = std::to_string(assigned_ports.gp_udp_port_start + 1); const std::string timeout = std::to_string(DOCKER_CREATE_TIMEOUT_SECS); const int len = 376 + username.length() + timeout.length() + conf::ctx.exe_dir.length() + container_name.length() + (user_port.length() * 2) + (peer_port.length() * 4) + (gp_tcp_port_1.length() * 2) + (gp_tcp_port_2.length() * 2) + (gp_udp_port_1.length() * 2) + (gp_udp_port_2.length() * 2) + contract_dir.length() + image_name.length(); char command[len]; sprintf(command, DOCKER_CREATE, username.data(), timeout.data(), conf::ctx.exe_dir.data(), container_name.data(), - user_port.data(), user_port.data(), - peer_port.data(), peer_port.data(), - peer_port.data(), peer_port.data(), - gp_tcp_port_1.data(), gp_tcp_port_1.data(), - gp_tcp_port_2.data(), gp_tcp_port_2.data(), - gp_udp_port_1.data(), gp_udp_port_1.data(), - gp_udp_port_2.data(), gp_udp_port_2.data(), + user_port.data(), user_port.data(), + peer_port.data(), peer_port.data(), + peer_port.data(), peer_port.data(), + gp_tcp_port_1.data(), gp_tcp_port_1.data(), + gp_tcp_port_2.data(), gp_tcp_port_2.data(), + gp_udp_port_1.data(), gp_udp_port_1.data(), + gp_udp_port_2.data(), gp_udp_port_2.data(), contract_dir.data(), image_name.data()); LOG_INFO << "Creating the docker container. name: " << container_name; @@ -499,7 +499,19 @@ namespace hp } // Add the port pair of the destroyed container to the vacant port vector. if (std::find(vacant_ports.begin(), vacant_ports.end(), info.assigned_ports) == vacant_ports.end()) - vacant_ports.push_back(info.assigned_ports); + { + if (info.assigned_ports.gp_tcp_port_start == 0) + { + const uint16_t increment = ((info.assigned_ports.peer_port - conf::cfg.hp.init_peer_port) * 2); + const uint16_t gp_tcp_port_start = conf::cfg.hp.init_gp_tcp_port + increment; + const uint16_t gp_udp_port_start = conf::cfg.hp.init_gp_udp_port + increment; + vacant_ports.push_back({info.assigned_ports.user_port, info.assigned_ports.peer_port, gp_tcp_port_start, gp_udp_port_start}); + } + else + { + vacant_ports.push_back(info.assigned_ports); + } + } return 0; } @@ -965,6 +977,8 @@ namespace hp username, std::to_string(assigned_ports.peer_port), std::to_string(assigned_ports.user_port), + std::to_string(assigned_ports.gp_tcp_port_start), + std::to_string(assigned_ports.gp_udp_port_start), instance_name}; std::vector output_params; if (util::execute_bash_file(conf::ctx.user_uninstall_sh, output_params, input_params) == -1) @@ -1039,46 +1053,46 @@ namespace hp */ void get_vacant_ports_list(std::vector &vacant_ports) { - const int gp_tcp_port_count=2; - const int gp_udp_port_count=2; + const int gp_tcp_port_count = 2; + const int gp_udp_port_count = 2; - //get all instances + // get all instances std::vector instances; get_instance_list(instances); - - //no instances - if (instances.empty()) { + + // no instances + if (instances.empty()) + { return; } - //Get the max instance + // Get the max instance const std::vector::iterator element_max_peer_port = std::max_element(instances.begin(), instances.end(), - [](const hp::instance_info& a, const hp::instance_info& b) { - return (uint16_t)(a.assigned_ports.user_port) < (uint16_t)(b.assigned_ports.user_port); - }); - - + [](const hp::instance_info &a, const hp::instance_info &b) + { + return (uint16_t)(a.assigned_ports.user_port) < (uint16_t)(b.assigned_ports.user_port); + }); + ports init_ports = {(uint16_t)(conf::cfg.hp.init_peer_port), (uint16_t)(conf::cfg.hp.init_user_port), (uint16_t)(conf::cfg.hp.init_gp_tcp_port), (uint16_t)(conf::cfg.hp.init_gp_udp_port)}; - - //Keep increasing init port (peer port) until it reaches max port - //If init port values did not match with an item in the instances list, add init port values to vacant ports list. + + // Keep increasing init port (peer port) until it reaches max port + // If init port values did not match with an item in the instances list, add init port values to vacant ports list. while (init_ports.peer_port < element_max_peer_port->assigned_ports.peer_port) { - bool is_item_available = std::find_if(instances.begin(),instances.end(),[init_ports](const instance_info& instance){ - return instance.assigned_ports.peer_port == init_ports.peer_port; - }) != instances.end(); + bool is_item_available = std::find_if(instances.begin(), instances.end(), [init_ports](const instance_info &instance) + { return instance.assigned_ports.peer_port == init_ports.peer_port; }) != instances.end(); - if(!is_item_available){ + if (!is_item_available) + { vacant_ports.push_back(init_ports); } init_ports.peer_port++; init_ports.user_port++; - init_ports.gp_tcp_port_start+=gp_tcp_port_count; - init_ports.gp_udp_port_start+=gp_udp_port_count; + init_ports.gp_tcp_port_start += gp_tcp_port_count; + init_ports.gp_udp_port_start += gp_udp_port_count; } - } /** * Check whether there's a pending reboot and cgrules service is running and configured. diff --git a/src/msg/json/msg_json.cpp b/src/msg/json/msg_json.cpp index c9b15a2..5217aa9 100644 --- a/src/msg/json/msg_json.cpp +++ b/src/msg/json/msg_json.cpp @@ -867,4 +867,31 @@ namespace msg::json msg += std::to_string(instance.assigned_ports.user_port); msg += "}"; } + + /** + * Constructs the response message for inspect message. + * @param msg Buffer to construct the generated json message string into. + * Message format: + * { + * "name": "", + * "error": "error" + * } + * @param instance_name Name of the instance. + * @param error Error. + * + */ + void build_error_response(std::string &msg, std::string_view container_name, std::string_view error) + { + msg.reserve(26 + container_name.size() + error.size()); + msg += "{\""; + msg += "instance_name"; + msg += SEP_COLON; + msg += container_name; + msg += SEP_COMMA; + msg += "error"; + msg += SEP_COLON; + msg += error; + msg += DOUBLE_QUOTE; + msg += "}"; + } } // namespace msg::json diff --git a/src/msg/json/msg_json.hpp b/src/msg/json/msg_json.hpp index 865a02e..371cb4f 100644 --- a/src/msg/json/msg_json.hpp +++ b/src/msg/json/msg_json.hpp @@ -34,6 +34,8 @@ namespace msg::json void build_inspect_response(std::string &msg, const hp::instance_info &instance); + void build_error_response(std::string &msg, std::string_view container_name, std::string_view error); + } // namespace msg::json #endif \ No newline at end of file diff --git a/src/msg/msg_common.hpp b/src/msg/msg_common.hpp index e43539b..c8f1a70 100644 --- a/src/msg/msg_common.hpp +++ b/src/msg/msg_common.hpp @@ -243,6 +243,7 @@ namespace msg constexpr const char *MSGTYPE_CREATE_RES = "create_res"; constexpr const char *MSGTYPE_CREATE_ERROR = "create_error"; constexpr const char *MSGTYPE_INITIATE_RES = "initiate_res"; + constexpr const char *MSGTYPE_INITIATE_ERROR = "initiate_error"; constexpr const char *MSGTYPE_DESTROY_RES = "destroy_res"; constexpr const char *MSGTYPE_DESTROY_ERROR = "destroy_error"; constexpr const char *MSGTYPE_START_RES = "start_res"; diff --git a/src/msg/msg_parser.cpp b/src/msg/msg_parser.cpp index 51c10d0..e12b050 100644 --- a/src/msg/msg_parser.cpp +++ b/src/msg/msg_parser.cpp @@ -64,4 +64,10 @@ namespace msg json::build_inspect_response(msg, instance); } + void msg_parser::build_error_response(std::string &msg, + std::string_view container_name, std::string_view error) const + { + json::build_error_response(msg, container_name, error); + } + } // namespace msg \ No newline at end of file diff --git a/src/msg/msg_parser.hpp b/src/msg/msg_parser.hpp index 6ff1aa7..ffe45e1 100644 --- a/src/msg/msg_parser.hpp +++ b/src/msg/msg_parser.hpp @@ -25,6 +25,8 @@ namespace msg void build_list_response(std::string &msg, const std::vector &instances, const std::vector &leases) const; void build_inspect_response(std::string &msg, const hp::instance_info &instance) const; + void build_error_response(std::string &msg, + std::string_view container_name, std::string_view error) const; }; } // namespace msg diff --git a/src/sqlite.cpp b/src/sqlite.cpp index 932dbdf..897755b 100644 --- a/src/sqlite.cpp +++ b/src/sqlite.cpp @@ -6,6 +6,7 @@ namespace sqlite { constexpr const char *COLUMN_DATA_TYPES[]{"INT", "TEXT", "BLOB"}; + constexpr const char *ALTER_TABLE = "ALTER TABLE "; constexpr const char *CREATE_TABLE = "CREATE TABLE IF NOT EXISTS "; constexpr const char *CREATE_INDEX = "CREATE INDEX "; constexpr const char *CREATE_UNIQUE_INDEX = "CREATE UNIQUE INDEX "; @@ -29,7 +30,7 @@ namespace sqlite "instances WHERE status == ? AND user_port NOT IN" "(SELECT user_port FROM instances WHERE status != ?)"; - constexpr const char *GET_MAX_PORTS_FROM_HP = "SELECT max(peer_port), max(user_port), max(init_gp_tcp_port), max(init_gp_udp_port) FROM instances WHERE status != ?"; + constexpr const char *GET_MAX_PORTS_FROM_HP = "SELECT peer_port, user_port, init_gp_tcp_port, init_gp_udp_port FROM instances WHERE status != ? ORDER BY peer_port DESC LIMIT 1"; constexpr const char *UPDATE_STATUS_IN_HP = "UPDATE instances SET status = ? WHERE name = ?"; @@ -157,6 +158,48 @@ namespace sqlite return ret; } + /** + * Alter a table with given table info. + * @param db Pointer to the db. + * @param table_name Table name to be altered. + * @returns returns 0 on success, or -1 on error. + */ + int alter_table(sqlite3 *db, std::string_view table_name, const std::vector &column_info) + { + std::string sql; + + for (auto itr = column_info.begin(); itr != column_info.end(); ++itr) + { + sql.append(ALTER_TABLE).append(table_name).append(" ADD COLUMN "); + sql.append(itr->name); + sql.append(" "); + sql.append(COLUMN_DATA_TYPES[itr->column_type]); + + if (itr->is_key) + { + sql.append(" "); + sql.append(PRIMARY_KEY); + } + + if (!itr->is_null) + { + sql.append(" "); + sql.append(NOT_NULL); + } + + if (itr != column_info.end() - 1) + sql.append("; "); + } + + const int ret = exec_sql(db, sql); + if (ret == -1) + { + LOG_ERROR << "Error when altering sqlite table " << table_name; + } + + return ret; + } + int create_index(sqlite3 *db, std::string_view table_name, std::string_view column_names, const bool is_unique) { std::string index_name = std::string("idx_").append(table_name).append("_").append(column_names); @@ -265,6 +308,28 @@ namespace sqlite return false; } + /** + * Checks whether column exist in the database. + * @param db Pointer to the db. + * @param table_name Table name to be checked. + * @param column_name Column name to be checked. + * @returns returns true is exist, otherwise false. + */ + bool is_column_exists(sqlite3 *db, std::string_view table_name, std::string_view column_name) + { + std::string sql; + // Reserving the space for the query before construction. + sql.reserve(21 + table_name.size() + column_name.size()); + + sql.append("SELECT "); + sql.append(column_name); + sql.append(" FROM "); + sql.append(table_name); + sql.append(" LIMIT 1"); + + return exec_sql(db, sql) == 0; + } + /** * Closes a connection to a given databse. * @param db Pointer to the db. @@ -314,6 +379,15 @@ namespace sqlite create_index(db, INSTANCE_TABLE, "owner_pubkey", false) == -1) // one user can have multiple instances running. return -1; } + else if (!is_column_exists(db, INSTANCE_TABLE, "init_gp_tcp_port")) // TODO: Added because v0.8.4 does not have gp ports. + { + const std::vector columns{ + table_column_info("init_gp_tcp_port", COLUMN_DATA_TYPE::INT), + table_column_info("init_gp_udp_port", COLUMN_DATA_TYPE::INT)}; + + if (alter_table(db, INSTANCE_TABLE, columns) == -1) + return -1; + } return 0; } @@ -426,10 +500,17 @@ namespace sqlite max_ports = {peer_port, user_port, gp_tcp_port_start, gp_udp_port_start}; } // Initialize with default config values if either of the ports are zero. - if (max_ports.peer_port == 0 || max_ports.user_port == 0 || max_ports.gp_tcp_port_start == 0 || max_ports.gp_udp_port_start == 0) + if (max_ports.peer_port == 0 || max_ports.user_port == 0) { max_ports = {(uint16_t)(conf::cfg.hp.init_peer_port - 1), (uint16_t)(conf::cfg.hp.init_user_port - 1), (uint16_t)(conf::cfg.hp.init_gp_tcp_port - 2), (uint16_t)(conf::cfg.hp.init_gp_udp_port - 2)}; } + else if (max_ports.gp_tcp_port_start == 0 || max_ports.gp_udp_port_start == 0) + { + const uint16_t increment = ((max_ports.peer_port - conf::cfg.hp.init_peer_port) * 2); + const uint16_t gp_tcp_port_start = conf::cfg.hp.init_gp_tcp_port + increment; + const uint16_t gp_udp_port_start = conf::cfg.hp.init_gp_udp_port + increment; + max_ports = {max_ports.user_port, max_ports.peer_port, gp_tcp_port_start, gp_udp_port_start}; + } // Finalize and distroys the statement. sqlite3_finalize(stmt); diff --git a/src/sqlite.hpp b/src/sqlite.hpp index ee99adf..32f281c 100644 --- a/src/sqlite.hpp +++ b/src/sqlite.hpp @@ -51,6 +51,8 @@ namespace sqlite int create_table(sqlite3 *db, std::string_view table_name, const std::vector &column_info); + int alter_table(sqlite3 *db, std::string_view table_name, const std::vector &column_info); + int create_index(sqlite3 *db, std::string_view table_name, std::string_view column_names, const bool is_unique); int insert_rows(sqlite3 *db, std::string_view table_name, std::string_view column_names_string, const std::vector &value_strings); @@ -59,6 +61,8 @@ namespace sqlite bool is_table_exists(sqlite3 *db, std::string_view table_name); + bool is_column_exists(sqlite3 *db, std::string_view table_name, std::string_view column_name); + int close_db(sqlite3 **db); int initialize_hp_db(sqlite3 *db); diff --git a/src/version.hpp b/src/version.hpp index e197923..f04f5b7 100644 --- a/src/version.hpp +++ b/src/version.hpp @@ -6,7 +6,7 @@ namespace version { // Sashimono agent version. Written to new configs. - constexpr const char *AGENT_VERSION = "0.8.3"; + constexpr const char *AGENT_VERSION = "0.9.0"; // Minimum compatible config version (this will be used to validate configs). constexpr const char *MIN_CONFIG_VERSION = "0.5.0";