diff --git a/installer/jshelper/package-lock.json b/installer/jshelper/package-lock.json index 6a5c0ac..ba831f4 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.19", + "evernode-js-client": "0.6.20", "ip6addr": "0.2.5" } }, @@ -363,9 +363,9 @@ } }, "node_modules/evernode-js-client": { - "version": "0.6.19", - "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.19.tgz", - "integrity": "sha512-E8oEVsEOuX72V8ECmqt08U+Q/U4Kygl/X+my8TtnaSrDuIYu4EMMaFp0441XyNpTZH25rvE9rTwy5ZhvhN3T9g==", + "version": "0.6.20", + "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.20.tgz", + "integrity": "sha512-OC6VNAhwqnNvUc0NhffxwNI9bTDH+BkD/KBTC5Xuwoiq8BhRfYhmfHBnD6M9K5AvLqv+Jxdufc3l1AlzHgILWg==", "dependencies": { "elliptic": "6.5.4", "libsodium-wrappers": "0.7.10", @@ -1597,9 +1597,9 @@ } }, "evernode-js-client": { - "version": "0.6.19", - "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.19.tgz", - "integrity": "sha512-E8oEVsEOuX72V8ECmqt08U+Q/U4Kygl/X+my8TtnaSrDuIYu4EMMaFp0441XyNpTZH25rvE9rTwy5ZhvhN3T9g==", + "version": "0.6.20", + "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.20.tgz", + "integrity": "sha512-OC6VNAhwqnNvUc0NhffxwNI9bTDH+BkD/KBTC5Xuwoiq8BhRfYhmfHBnD6M9K5AvLqv+Jxdufc3l1AlzHgILWg==", "requires": { "elliptic": "6.5.4", "libsodium-wrappers": "0.7.10", diff --git a/installer/jshelper/package.json b/installer/jshelper/package.json index b3a6105..4aad456 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.19", + "evernode-js-client": "0.6.20", "ip6addr": "0.2.5" } } diff --git a/installer/setup.sh b/installer/setup.sh index 6f10e79..8fa6c56 100755 --- a/installer/setup.sh +++ b/installer/setup.sh @@ -978,7 +978,7 @@ function reconfig_sashi() { function reconfig_mb() { echomult "Configuaring 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 && + ! 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 && echo "There was an error in updating message board configuration." && return 1 return 0 } @@ -992,7 +992,9 @@ function config() { alloc_swapKB=0 alloc_diskKB=0 lease_amount=0 - rippled_server='' + rippled_server='-' + ipv6_subnet='-' + ipv6_net_interface='-' local saconfig="$SASHIMONO_DATA/sa.cfg" local max_instance_count=$(jq '.system.max_instance_count' $saconfig) @@ -1004,6 +1006,9 @@ function config() { local cfg_lease_amount=$(jq '.xrpl.leaseAmount' $mbconfig) local cfg_rippled_server=$(jq -r '.xrpl.rippledServer' $mbconfig) + local cfg_ipv6_subnet=$(jq -r '.networking.ipv6.subnet' $mbconfig) + local cfg_ipv6_net_interface=$(jq -r '.networking.ipv6.interface' $mbconfig) + local update_sashi=0 local update_mb=0 @@ -1024,9 +1029,6 @@ function config() { \n Disk space: $(GB $max_storage_kbytes) \n Instance count: $max_instance_count\n" && exit 0 - if ( [[ $occupied_instance_count -gt 0 ]] ); then - echomult "Could not proceed the re-configuration as there are occupied instances." && exit 1 - fi local help_text="Usage: evernode config resources | evernode config resources \n" [ ! -z $ramMB ] && [[ $ramMB != 0 ]] && ! validate_positive_decimal $ramMB && @@ -1064,9 +1066,6 @@ function config() { local amount=${2} # Contract instance lease amount in EVRs. [ -z $amount ] && echomult "Your current lease amount is: $cfg_lease_amount EVRs.\n" && exit 0 - if ( [[ $occupied_instance_count -gt 0 ]] ); then - echomult "Could not proceed the re-configuration as there are occupied instances." && exit 1 - fi ! validate_positive_decimal $amount && echomult "Invalid lease amount.\n Usage: evernode config leaseamt | evernode config leaseamt \n" && @@ -1155,9 +1154,34 @@ function config() { # We do not need to restart services for email update. echomult "\nSuccessfully changed the email address!\n" && exit 0 + elif [ "$sub_mode" == "instance" ] ; then + local attribute=${2} + + if [ "$attribute" == "ipv6" ] ; then + ([ ! -z $cfg_ipv6_subnet ] && [ ! -z $cfg_ipv6_net_interface ]) && + echomult "You have already enabled IPv6 for instance outbound communication. + \n Network Interface: $cfg_ipv6_net_interface + \n Subnet: $cfg_ipv6_subnet" && + ! confirm "\nDo you want to go for a reconfiguration?" && return 0 + + if ( [[ $occupied_instance_count -gt 0 ]] ); then + echomult "Could not proceed the reconfiguration as there are occupied instances." && exit 1 + fi + + set_ipv6_subnet + if [[ "$ipv6_subnet" == "-" || "$ipv6_net_interface" == "-" ]]; then + echo -e "Could not proceed with provided details." && exit 1 + fi + + echo -e "Using $ipv6_subnet IPv6 subnet on $ipv6_net_interface for contract instances.\n" + update_mb=1 + + else + echomult "Invalid arguments.\n Usage: evernode config instance [ipv6]\n" && exit 1 + fi else - echomult "Invalid arguments.\n Usage: evernode config [resources|leaseamt|rippled|email] [arguments]\n" && exit 1 + echomult "Invalid arguments.\n Usage: evernode config [resources|leaseamt|rippled|email|instance] [arguments]\n" && exit 1 fi local mb_user_id=$(id -u "$MB_XRPL_USER") diff --git a/mb-xrpl/app.js b/mb-xrpl/app.js index ac4d367..f407744 100644 --- a/mb-xrpl/app.js +++ b/mb-xrpl/app.js @@ -54,8 +54,12 @@ async function main() { else if (process.argv.length === 4 && process.argv[2] === 'upgrade') { await new Setup().upgrade(process.argv[3]); } - else if ((process.argv.length === 5 || process.argv.length === 6) && process.argv[2] === 'reconfig') { - await new Setup().changeConfig(process.argv[3], process.argv[5], process.argv[4]); + else if ((process.argv.length === 8) && process.argv[2] === 'reconfig') { + if (process.argv[5] == '-') process.argv[5] = null; + if (process.argv[6] == '-') process.argv[6] = null; + if (process.argv[7] == '-') process.argv[7] = null; + + await new Setup().changeConfig(process.argv[3], process.argv[5], process.argv[4], process.argv[6], process.argv[7]); } else if (process.argv.length === 4 && process.argv[2] === 'delete') { await new Setup().deleteInstance(process.argv[3]); diff --git a/mb-xrpl/lib/appenv.js b/mb-xrpl/lib/appenv.js index ffd65e8..d01d100 100644 --- a/mb-xrpl/lib/appenv.js +++ b/mb-xrpl/lib/appenv.js @@ -23,13 +23,12 @@ appenv = { SASHI_CONFIG_PATH: (appenv.IS_DEV_MODE ? "../build/" : path.join(appenv.DATA_DIR, '../')) + "sa.cfg", SASHI_TABLE_NAME: 'instances', LAST_WATCHED_LEDGER: 'last_watched_ledger', - LAST_ASSIGNED_IPV6_ADDRESS: 'last_assigned_ipv6_address', ACQUIRE_LEASE_TIMEOUT_THRESHOLD: 0.8, ACQUIRE_LEASE_WAIT_TIMEOUT_THRESHOLD: 0.4, 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.6.8', + MB_VERSION: '0.6.9', TOS_HASH: '757A0237B44D8B2BBB04AE2BAD5813858E0AECD2F0B217075E27E0630BA74314' // This is the sha256 hash of TOS text. } Object.freeze(appenv); diff --git a/mb-xrpl/lib/message-board.js b/mb-xrpl/lib/message-board.js index d10e97b..cb29b1f 100644 --- a/mb-xrpl/lib/message-board.js +++ b/mb-xrpl/lib/message-board.js @@ -831,7 +831,8 @@ class MessageBoard { // Send the acquire response with created instance info. // Modify Response. createRes.content.domain = createRes.content.ip; - createRes.content.outbound_ip = uriInfo.outboundIP?.address; + if (uriInfo.outboundIP) + createRes.content.outbound_ip = uriInfo.outboundIP.address; delete createRes.content.ip; const options = instanceRequirements?.messageKey ? { messageKey: instanceRequirements.messageKey } : {}; await this.hostClient.acquireSuccess(acquireRefId, tenantAddress, createRes, options); @@ -981,9 +982,6 @@ class MessageBoard { { name: 'value', type: DataTypes.INTEGER, notNull: true } ]); await this.createLastWatchedLedgerEntryIfNotExists(); - if (this.cfg?.networking?.ipv6?.subnet && this.cfg?.networking?.ipv6?.interface) { - await this.createLastAssignedIPEntryIfNotExists(); - } } async createLastWatchedLedgerEntryIfNotExists() { @@ -993,16 +991,6 @@ class MessageBoard { } } - async createLastAssignedIPEntryIfNotExists() { - let ret = await this.db.getValues(this.utilTable, { name: appenv.LAST_ASSIGNED_IPV6_ADDRESS }); - if (ret.length === 0) { - const lastMintedLeaseToken = (await this.hostClient.xrplAcc.getURITokens()).filter(n => evernode.EvernodeHelpers.isValidURI(n.URI, evernode.EvernodeConstants.LEASE_TOKEN_PREFIX_HEX)).sort((a, b) => b.PreviousTxnLgrSeq - a.PreviousTxnLgrSeq)[0]; - const lastMintedLeaseTokenData = evernode.UtilHelpers.decodeLeaseTokenUri(lastMintedLeaseToken.URI); - - await this.db.insertValue(this.utilTable, { name: appenv.LAST_ASSIGNED_IPV6_ADDRESS, value: lastMintedLeaseTokenData.outboundIP?.address }); - } - } - async getAcquiredRecords() { return (await this.db.getValues(this.leaseTable, { status: LeaseStatus.ACQUIRED })); } diff --git a/mb-xrpl/lib/setup.js b/mb-xrpl/lib/setup.js index ceb03af..bf327b7 100644 --- a/mb-xrpl/lib/setup.js +++ b/mb-xrpl/lib/setup.js @@ -204,15 +204,10 @@ class Setup { await hostClient.register(countryCode, cpuMicroSec, Math.floor((ramKb + swapKb) / 1000), Math.floor(diskKb / 1000), totalInstanceCount, cpuModelFormatted.substring(0, 40), cpuCount, cpuSpeed, description.replaceAll('_', ' '), emailAddress); - // Generate IPV6 Address (If the host has done relevant configuration) - let ipV6AddressList = []; - if (config?.networking?.ipv6?.subnet) - ipV6AddressList = UtilHelper.generateIPV6Addresses(config.networking.ipv6.subnet, totalInstanceCount); - // Create lease offers. console.log("Creating lease offers for instance slots..."); for (let i = 0; i < totalInstanceCount; i++) { - await hostClient.offerLease(i, acc.leaseAmount, appenv.TOS_HASH, ipV6AddressList.length > 0 ? ipV6AddressList[i] : null); + await hostClient.offerLease(i, acc.leaseAmount, appenv.TOS_HASH, config?.networking?.ipv6?.subnet ? UtilHelper.generateIPV6Address(config.networking.ipv6.subnet, i) : null); console.log(`Created lease offer ${i + 1} of ${totalInstanceCount}.`); } @@ -403,7 +398,7 @@ class Setup { } // Change the message board configurations. - async changeConfig(leaseAmount, rippledServer, totalInstanceCount) { + async changeConfig(leaseAmount, rippledServer, totalInstanceCount, ipv6Subnet, ipv6NetInterface) { // Update the configuration. const cfg = this.#getConfig(); @@ -421,10 +416,12 @@ class Setup { // Return if not changed. if (!totalInstanceCount && (!leaseAmount || cfg.xrpl.leaseAmount == leaseAmount) && - (!rippledServer || cfg.xrpl.rippledServer == leaseAmount)) + (!rippledServer || cfg.xrpl.rippledServer == rippledServer) && + (!ipv6Subnet) && + (!ipv6NetInterface)) return; - await this.recreateLeases(leaseAmountParsed, totalInstanceCountParsed, rippledServer, cfg); + await this.recreateLeases(leaseAmountParsed, totalInstanceCountParsed, rippledServer, ipv6Subnet, ipv6NetInterface, cfg); if (leaseAmountParsed) cfg.xrpl.leaseAmount = leaseAmountParsed; @@ -434,26 +431,15 @@ class Setup { } // Recreate unsold URITokens - async recreateLeases(leaseAmount, totalInstanceCount, rippledServer, existingCfg) { + async recreateLeases(leaseAmount, totalInstanceCount, rippledServer, outboundSubnet, outboundNetInterface, existingCfg) { // Get sold URITokens. const db = new SqliteDatabase(appenv.DB_PATH); const leaseTable = appenv.DB_TABLE_NAME; - const utilTable = appenv.DB_UTIL_TABLE_NAME; - const config = this.#getConfig(); db.open(); const leaseRecords = (await db.getValues(leaseTable).finally(() => { db.close() })).filter(i => (i.status === "Acquired" || i.status === "Extended")); const soldCount = leaseRecords.length; - // NOTE : This was added after IPV6 address assignment to URI tokens. If we allow to change the instance count - // there may be an issue of loosing the IP address assignment order, because the acquisitions never follows any order. - // Due to that nature there may a chance of having sold instances with intermediate IP addresses. Hence in such kind of a scenario - // we cannot handle the LAST_ASSIGNED_IPV6_ADDRESS property as we expect. - // TODO : Should cater to reconfigure with occupied leases (sold instances). - if (soldCount) - throw `There are ${soldCount} active instances. Hence it is not possible to reconfigure.`; - - if (totalInstanceCount && soldCount > totalInstanceCount) throw `There are ${soldCount} active instances, So max instance count cannot be less than that.`; @@ -484,7 +470,7 @@ class Setup { const unsoldCount = unsoldUriTokens.length; // Return if not changed. - if (!leaseAmount && !rippledServer && (!totalInstanceCount || (soldCount + unsoldCount) == totalInstanceCount)) { + if (!leaseAmount && !rippledServer && (!totalInstanceCount || (soldCount + unsoldCount) == totalInstanceCount) && (!outboundSubnet || !outboundNetInterface)) { await deinitClients(); return; } @@ -545,13 +531,17 @@ class Setup { uriTokenIndexesToCreate = await getVacantLeaseIndexes(false); } } + // If only instance outbound networking was changed. + else if (outboundSubnet && outboundNetInterface) { + uriTokensToBurn = unsoldUriTokens; + uriTokenIndexesToCreate = uriTokensToBurn.map(n => n.leaseIndex); + + // Updating the config object fields. + existingCfg.networking = { ipv6: { subnet: outboundSubnet, interface: outboundNetInterface } } + } - db.open(); - let lastAssignedIPV6 = (config?.networking?.ipv6?.subnet) ? (await db.getValues(utilTable).finally(() => { db.close() })).find(i => (i.name === appenv.LAST_ASSIGNED_IPV6_ADDRESS)).value : null; for (const uriToken of uriTokensToBurn) { try { - if (lastAssignedIPV6) - lastAssignedIPV6 = UtilHelper.generateValidIPV6Address(config.networking.ipv6.subnet, lastAssignedIPV6, true); await hostClient.expireLease(uriToken.uriTokenId); } catch (e) { @@ -564,26 +554,18 @@ class Setup { await initClients(rippledServer); } - for (const idx of uriTokenIndexesToCreate.sort((a, b) => { return a - b; })) { + for (const idx of uriTokenIndexesToCreate) { try { - if (lastAssignedIPV6) - lastAssignedIPV6 = UtilHelper.generateValidIPV6Address(config.networking.ipv6.subnet, lastAssignedIPV6); - await hostClient.offerLease(idx, leaseAmount ? leaseAmount : acc.leaseAmount, appenv.TOS_HASH, - lastAssignedIPV6); + (existingCfg?.networking?.ipv6?.subnet) ? UtilHelper.generateIPV6Address(existingCfg.networking.ipv6.subnet, idx) : null); } catch (e) { console.error(e); } } - if (lastAssignedIPV6) { - db.open(); - (await db.updateValue(utilTable, { value: lastAssignedIPV6 }, { name: appenv.LAST_ASSIGNED_IPV6_ADDRESS }).finally(() => { db.close() })); - } - await deinitClients(); } diff --git a/mb-xrpl/lib/util-helper.js b/mb-xrpl/lib/util-helper.js index e716b4f..c53d6da 100644 --- a/mb-xrpl/lib/util-helper.js +++ b/mb-xrpl/lib/util-helper.js @@ -2,70 +2,21 @@ const ip6addr = require('ip6addr'); class UtilHelper { - static generateIPV6Addresses(subnetStr, addressCount) { - - // Incrementally assign IPv6 addresses - const generatedIPs = []; - - for (let i = 0; generatedIPs.length < addressCount; i++) { - const generatedIP = this.generateValidIPV6Address(subnetStr, (i > 0) ? generatedIPs[i - 1] : null); - if (generatedIP) { - generatedIPs.push(generatedIP); - } - } - - return generatedIPs; - } - - static generateValidIPV6Address(subnetStr, offsetIP = null, isBelowOffset = false) { - // Define your IPv6 subnet + static generateIPV6Address(subnetStr, incrementor) { const subnet = ip6addr.createCIDR(subnetStr); + const firstIP = subnet.first().toString({ zeroElide: false, zeroPad: true }).toUpperCase(); + const ipv6BigInt = BigInt("0x" + firstIP.replace(/:/g, "")); - if (offsetIP && !subnet.contains(offsetIP)) - throw "Invalid offset IP Address." + const resultBigInt = ipv6BigInt + BigInt(incrementor); + const maxIPv6Value = BigInt("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + const resultIPv6BigInt = resultBigInt % (maxIPv6Value + BigInt(1)); - if (offsetIP) { - const newAddressBuf = Buffer.from(offsetIP.split(':').map(v => { - const bytes = []; - for (let i = 0; i < v.length; i += 2) { - bytes.push(parseInt(v.substr(i, 2), 16)); - } - return bytes; - }).flat()); + const resultIPv6 = resultIPv6BigInt.toString(16).toUpperCase().match(/.{1,4}/g).join(":"); - let j = newAddressBuf.length - 1; - while (j >= 0) { - if (isBelowOffset) { - if (newAddressBuf[j] - 1 < 0) { - newAddressBuf[j] = parseInt("0xFF", 16); - j--; - continue; - } - else { - newAddressBuf[j]--; - break; - } - - } else { - if (newAddressBuf[j] + 1 > parseInt("0xFF", 16)) { - newAddressBuf[j] = 0; - j--; - continue; - } - else { - newAddressBuf[j]++; - break; - } - } - } - - const ipString = newAddressBuf.toString('hex').toUpperCase().replace(/(.{4})(?!$)/g, "$1:"); - if (subnet.contains(ipString)) { - return ipString; - } - } else - return subnet.first().toBuffer().toString('hex').toUpperCase().replace(/(.{4})(?!$)/g, "$1:"); + if (subnet.contains(resultIPv6)) + return resultIPv6; + return null; } } diff --git a/mb-xrpl/package-lock.json b/mb-xrpl/package-lock.json index efd2ff8..80aa29d 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.19", + "evernode-js-client": "0.6.20", "ip6addr": "0.2.5", "sqlite3": "5.0.2" }, @@ -980,9 +980,9 @@ } }, "node_modules/evernode-js-client": { - "version": "0.6.19", - "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.19.tgz", - "integrity": "sha512-E8oEVsEOuX72V8ECmqt08U+Q/U4Kygl/X+my8TtnaSrDuIYu4EMMaFp0441XyNpTZH25rvE9rTwy5ZhvhN3T9g==", + "version": "0.6.20", + "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.20.tgz", + "integrity": "sha512-OC6VNAhwqnNvUc0NhffxwNI9bTDH+BkD/KBTC5Xuwoiq8BhRfYhmfHBnD6M9K5AvLqv+Jxdufc3l1AlzHgILWg==", "dependencies": { "elliptic": "6.5.4", "libsodium-wrappers": "0.7.10", @@ -4010,9 +4010,9 @@ "dev": true }, "evernode-js-client": { - "version": "0.6.19", - "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.19.tgz", - "integrity": "sha512-E8oEVsEOuX72V8ECmqt08U+Q/U4Kygl/X+my8TtnaSrDuIYu4EMMaFp0441XyNpTZH25rvE9rTwy5ZhvhN3T9g==", + "version": "0.6.20", + "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.20.tgz", + "integrity": "sha512-OC6VNAhwqnNvUc0NhffxwNI9bTDH+BkD/KBTC5Xuwoiq8BhRfYhmfHBnD6M9K5AvLqv+Jxdufc3l1AlzHgILWg==", "requires": { "elliptic": "6.5.4", "libsodium-wrappers": "0.7.10", diff --git a/mb-xrpl/package.json b/mb-xrpl/package.json index 1d973fe..680ea10 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.19", + "evernode-js-client": "0.6.20", "sqlite3": "5.0.2", "ip6addr": "0.2.5" }, diff --git a/src/version.hpp b/src/version.hpp index c14b038..038573b 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.6.8"; + constexpr const char *AGENT_VERSION = "0.6.9"; // Minimum compatible config version (this will be used to validate configs). constexpr const char *MIN_CONFIG_VERSION = "0.5.0";