IPV6 related Message board changes. (#284)

This commit is contained in:
Kithmini Gunawardhana
2023-09-23 10:00:58 +05:30
committed by GitHub
parent 20cfbbfad0
commit 8bcb2db9b0
22 changed files with 798 additions and 1250 deletions

View File

@@ -170,13 +170,13 @@ echo "[Service]
Environment=DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=slirp4netns" >$docker_service_override_conf
# We need to enable ipv6 configurations if outbound ipv6 address is specified.
if [ "$outbound_ipv6" != "-"] && [ "$outbound_net_interface" != "-"]; then
if [ "$outbound_ipv6" != "-" ] && [ "$outbound_net_interface" != "-" ]; then
# Pass the relevant ipv6 parameters to rootlesskit flags. rootlesskit will in turn pass these to slirp4nets.
# Also apply ipv6 route configuration patch in the dockerd process namespace (credits: https://github.com/containers/podman/issues/15850#issuecomment-1320028298)
echo "
Environment=\"DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS=--ipv6 --outbound-addr6=$outbound_ipv6\"
ExecStartPost=/bin/bash -c 'nsenter -U --preserve-credentials -n -t $(pgrep -u $user dockerd) /bin/bash -c \"ip addr add fd00::100/64 dev tap0 && ip route add default via fd00::2 dev tap0\"'
ExecStartPost=/bin/bash -c 'nsenter -U --preserve-credentials -n -t $""(pgrep -u $user dockerd) /bin/bash -c \"ip addr add fd00::100/64 dev tap0 && ip route add default via fd00::2 dev tap0\"'
" >>$docker_service_override_conf
# Set the predefined ipv6 parameters to docker daemon config.
@@ -190,10 +190,10 @@ if [ "$outbound_ipv6" != "-"] && [ "$outbound_net_interface" != "-"]; then
}" >$user_dir/.config/docker/daemon.json
# Add the outbound ipv6 address to the specified network interface.
ip addr add $outbound_ipv6 $outbound_net_interface
ip addr add $outbound_ipv6 dev $outbound_net_interface
# Add instructions to the cleanup script so the outbound ip assignment will be removed upon user install.
echo "ip addr del $outbound_ipv6 $outbound_net_interface" >>$cleanup_script
echo "ip addr del $outbound_ipv6 dev $outbound_net_interface" >>$cleanup_script
fi
# Overwrite docker-rootless cli args on the docker service unit file (ExecStart is not supported by override.conf).

View File

@@ -23,7 +23,7 @@ rm -r $tmp
# Override rootlesskit with our own version based on original rootlesskit v1.1.1
# We need this custom version to have outbound ipv6 address support (https://github.com/EvernodeXRPL/rootlesskit/tree/outbound-addr-support)
curl -s https://github.com/EvernodeXRPL/rootlesskit/releases/download/v1.1.1-evernode-patch1/rootlesskit --output $docker_bin/rootlesskit
curl -fsSL https://github.com/EvernodeXRPL/rootlesskit/releases/download/v1.1.1-evernode-patch1/rootlesskit --output $docker_bin/rootlesskit
chmod +x $docker_bin/rootlesskit
chown -R $(id -u):$(id -g) $docker_bin/*

View File

@@ -6,7 +6,7 @@
"": {
"name": "evernode-setup-helper",
"dependencies": {
"evernode-js-client": "0.6.18",
"evernode-js-client": "0.6.19",
"ip6addr": "0.2.5"
}
},
@@ -111,9 +111,9 @@
}
},
"node_modules/bignumber.js": {
"version": "9.1.1",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz",
"integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==",
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz",
"integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==",
"engines": {
"node": "*"
}
@@ -363,9 +363,9 @@
}
},
"node_modules/evernode-js-client": {
"version": "0.6.18",
"resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.18.tgz",
"integrity": "sha512-0q6rp6qM/54zcOVi5GDftZ/ZvP5BeBZvwhH9dN+B5REvtNdaXeN2d0Jkj50LPaTiU6a/LCRMziRFUdV3eXtakA==",
"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==",
"dependencies": {
"elliptic": "6.5.4",
"libsodium-wrappers": "0.7.10",
@@ -1026,9 +1026,9 @@
}
},
"node_modules/ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.14.0.tgz",
"integrity": "sha512-WR0RJE9Ehsio6U4TuM+LmunEsjQ5ncHlw4sn9ihD6RoJKZrVyH9FWV3dmnwu8B2aNib1OvG2X6adUCyFpQyWcg==",
"engines": {
"node": ">=10.0.0"
},
@@ -1172,9 +1172,9 @@
}
},
"node_modules/xrpl-client": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/xrpl-client/-/xrpl-client-2.1.1.tgz",
"integrity": "sha512-JKq6bMd50kdjyfrputVq8S7GI8Ttl1AxO7ALA6oRleAhoRfeicGbRjS5IFcL9pgsLIVzVC/7eKAtdhK+lf0ywA==",
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/xrpl-client/-/xrpl-client-2.1.2.tgz",
"integrity": "sha512-Lg9aq8CSeL/9nx7THJThoYpSZVQ9Eq7M4YrXbLv8nN91izoUYMM6qcx2b1qeIQFb3oxQdVYkkqqxfjoiwp05/w==",
"dependencies": {
"debug": "^4.1.1",
"websocket": "^1.0.34"
@@ -1390,9 +1390,9 @@
"integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg=="
},
"bignumber.js": {
"version": "9.1.1",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz",
"integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig=="
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz",
"integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug=="
},
"bindings": {
"version": "1.5.0",
@@ -1597,9 +1597,9 @@
}
},
"evernode-js-client": {
"version": "0.6.18",
"resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.18.tgz",
"integrity": "sha512-0q6rp6qM/54zcOVi5GDftZ/ZvP5BeBZvwhH9dN+B5REvtNdaXeN2d0Jkj50LPaTiU6a/LCRMziRFUdV3eXtakA==",
"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==",
"requires": {
"elliptic": "6.5.4",
"libsodium-wrappers": "0.7.10",
@@ -2113,9 +2113,9 @@
}
},
"ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.14.0.tgz",
"integrity": "sha512-WR0RJE9Ehsio6U4TuM+LmunEsjQ5ncHlw4sn9ihD6RoJKZrVyH9FWV3dmnwu8B2aNib1OvG2X6adUCyFpQyWcg==",
"requires": {}
},
"xrpl": {
@@ -2258,9 +2258,9 @@
}
},
"xrpl-client": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/xrpl-client/-/xrpl-client-2.1.1.tgz",
"integrity": "sha512-JKq6bMd50kdjyfrputVq8S7GI8Ttl1AxO7ALA6oRleAhoRfeicGbRjS5IFcL9pgsLIVzVC/7eKAtdhK+lf0ywA==",
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/xrpl-client/-/xrpl-client-2.1.2.tgz",
"integrity": "sha512-Lg9aq8CSeL/9nx7THJThoYpSZVQ9Eq7M4YrXbLv8nN91izoUYMM6qcx2b1qeIQFb3oxQdVYkkqqxfjoiwp05/w==",
"requires": {
"debug": "^4.1.1",
"websocket": "^1.0.34"

View File

@@ -4,7 +4,7 @@
"build": "ncc build index.js --minify -o dist"
},
"dependencies": {
"evernode-js-client": "0.6.18",
"evernode-js-client": "0.6.19",
"ip6addr": "0.2.5"
}
}

View File

@@ -274,7 +274,7 @@ if [ "$NO_MB" == "" ]; then
# ! sudo -u $MB_XRPL_USER MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN betagen $EVERNODE_GOVERNOR_ADDRESS $inetaddr $lease_amount $rippled_server $xrpl_account_secret && echo "XRPLACC_FAILURE" && rollback
# doreg=1
! sudo -u $MB_XRPL_USER MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN new $xrpl_account_address $xrpl_account_secret $EVERNODE_GOVERNOR_ADDRESS $inetaddr $lease_amount $rippled_server && echo "XRPLACC_FAILURE" && rollback
! sudo -u $MB_XRPL_USER MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN new $xrpl_account_address $xrpl_account_secret $EVERNODE_GOVERNOR_ADDRESS $inetaddr $lease_amount $rippled_server $ipv6_subnet $ipv6_net_interface && echo "XRPLACC_FAILURE" && rollback
doreg=1
fi

View File

@@ -483,14 +483,14 @@ function set_ipv6_subnet() {
[ -z "$primary_subnet" ] && echo "Invalid ipv6 subnet specified. It must be a valid ipv6 subnet in the CIDR format of \"xxxx:xxxx:xxxx:xxxx::/NN\"." && continue
# For further validation, we check whether the subnet prefix is actually assigned to any network interfaces of the host.
local subnet_prefix="$(cut -d'/' -f1 <<<$primary_subnet)"
local subnet_prefix="$(cut -d'/' -f1 <<<$primary_subnet | sed 's/::*$//g')"
local prefix_len="$(cut -d'/' -f2 <<<$primary_subnet)"
local net_interfaces=$(ip -6 -br addr | grep $subnet_prefix)
local interface_count=$(echo "$net_interfaces" | wc -l)
[ "$prefix_len" -gt $max_ipv6_prefix_len ] && echo "Maximum allowed prefix length for $evernode is $max_ipv6_prefix_len." && continue
[ -z "$net_interfaces" ] && echo "Could not find a network interface with the specified ipv6 subnet." && continue
[ "$interface_count" -gt 1 ] && echo "Found more than 1 network interface with the specified upv6 subnet." && echo "$net_interfaces" && continue
[ "$interface_count" -gt 1 ] && echo "Found more than 1 network interface with the specified ipv6 subnet." && echo "$net_interfaces" && continue
ipv6_subnet=$primary_subnet
ipv6_net_interface=$(echo "$net_interfaces" | awk '{ print $1 }')
@@ -1008,6 +1008,8 @@ function config() {
local update_mb=0
local sub_mode=${1}
local occupied_instance_count=$(sashi list | jq length)
if [ "$sub_mode" == "resources" ] ; then
local ramMB=${2} # memory to allocate for contract instances.
@@ -1022,6 +1024,10 @@ 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 <memory MB> <swap MB> <disk MB> <max instance count>\n"
[ ! -z $ramMB ] && [[ $ramMB != 0 ]] && ! validate_positive_decimal $ramMB &&
echomult "Invalid memory size.\n $help_text" && exit 1
@@ -1057,7 +1063,11 @@ 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 <lease amount>\n" &&
exit 1

View File

@@ -13,16 +13,18 @@ async function main() {
try {
if (process.argv.length >= 3) {
if (process.argv.length == 9 && process.argv[2] === 'new') {
if (process.argv.length >= 9 && process.argv[2] === 'new') {
const accountAddress = process.argv[3];
const accountSecret = process.argv[4];
const governorAddress = process.argv[5];
const domain = process.argv[6];
const leaseAmount = process.argv[7];
const rippledServer = process.argv[8];
const ipv6Subnet = (process.argv[9] === '-') ? null : process.argv[9];
const ipv6NetInterface = (process.argv[10] === '-') ? null : process.argv[10];
const setup = new Setup();
const acc = await setup.setupHostAccount(accountAddress, accountSecret, rippledServer, governorAddress, domain);
setup.newConfig(acc.address, acc.secret, governorAddress, parseFloat(leaseAmount), rippledServer);
setup.newConfig(acc.address, acc.secret, governorAddress, parseFloat(leaseAmount), rippledServer, ipv6Subnet, ipv6NetInterface);
}
else if (process.argv.length === 7 && process.argv[2] === 'betagen') {
const governorAddress = process.argv[3];
@@ -71,7 +73,7 @@ async function main() {
console.log(`Usage:
node index.js - Run message board.
node index.js version - Print version.
node index.js new [address] [secret] [governorAddress] [leaseAmount] - Create new config files.
node index.js new [address] [secret] [governorAddress] [leaseAmount] [rippledServer] [ipv6Subnet] [ipv6Interface] - Create new config files.
node index.js betagen [governorAddress] [domain or ip] [leaseAmount] [rippledServer] - Generate beta host account and populate the configs.
node index.js register [countryCode] [cpuMicroSec] [ramKb] [swapKb] [diskKb] [totalInstanceCount] [description] - Register the host on Evernode.
node index.js transfer [transfereeAddress] - Initiate a transfer.

View File

@@ -23,12 +23,13 @@ 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.7',
MB_VERSION: '0.6.8',
TOS_HASH: '757A0237B44D8B2BBB04AE2BAD5813858E0AECD2F0B217075E27E0630BA74314' // This is the sha256 hash of TOS text.
}
Object.freeze(appenv);

View File

@@ -348,7 +348,7 @@ class MessageBoard {
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);
await this.destroyInstance(lease.containerName, lease.tenant, uriInfo.leaseIndex, uriInfo?.outboundIP?.address);
}
this.activeInstanceCount--;
@@ -528,7 +528,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);
await this.recreateLeaseOffer(instance.name, lease.tenant_xrp_address, uriInfo.leaseIndex, uriInfo.outboundIP?.address);
await this.#queueAction(async () => {
console.log(`Refunding tenant ${lease.tenant_xrp_address}...`);
@@ -575,7 +575,7 @@ class MessageBoard {
const uriToken = (await (new evernode.XrplAccount(lease.tenant_xrp_address)).getURITokens())?.find(n => n.index == lease.container_name);
if (uriToken) {
const uriInfo = evernode.UtilHelpers.decodeLeaseTokenUri(uriToken.URI);
await this.recreateLeaseOffer(lease.container_name, lease.tenant_xrp_address, uriInfo.leaseIndex);
await this.recreateLeaseOffer(lease.container_name, lease.tenant_xrp_address, uriInfo.leaseIndex, uriInfo.outboundIP?.address);
await this.#queueAction(async () => {
// If lease is in ACQUIRING status acquire response is not received by the tenant and lease is not in expiry list.
@@ -669,7 +669,7 @@ class MessageBoard {
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);
await this.recreateLeaseOffer(eventInfo.data.uriTokenId, eventInfo.data.tenant, uriInfo.leaseIndex, uriInfo.outboundIP?.address);
await this.#queueAction(async () => {
console.log(`Refunding tenant ${eventInfo.data.tenant} for acquire...`);
@@ -723,14 +723,14 @@ class MessageBoard {
return null;
}
async recreateLeaseOffer(uriTokenId, tenantAddress, leaseIndex) {
async recreateLeaseOffer(uriTokenId, tenantAddress, leaseIndex, outboundIP) {
await this.#queueAction(async () => {
// Burn the URIToken and recreate the offer.
await this.hostClient.expireLease(uriTokenId, tenantAddress).catch(console.error);
// We refresh the config here, So if the purchaserTargetPrice is updated by the purchaser service, the new value will be taken.
await this.hostClient.refreshConfig();
const leaseAmount = this.cfg.xrpl.leaseAmount ? this.cfg.xrpl.leaseAmount : parseFloat(this.hostClient.config.purchaserTargetPrice);
await this.hostClient.offerLease(leaseIndex, leaseAmount, appenv.TOS_HASH).catch(console.error);
await this.hostClient.offerLease(leaseIndex, leaseAmount, appenv.TOS_HASH, outboundIP).catch(console.error);
});
}
@@ -743,6 +743,8 @@ class MessageBoard {
let requestValidated = false;
let createRes;
let leaseIndex = -1; // Lease index cannot be negative, So we keep initial non populated value as -1.
let instanceOutboundIPAddress = null;
this.db.open();
@@ -762,6 +764,7 @@ class MessageBoard {
throw 'Could not find the uriToken for lease acquire request.';
const uriInfo = evernode.UtilHelpers.decodeLeaseTokenUri(uriToken.URI);
instanceOutboundIPAddress = uriInfo?.outboundIP?.address;
if (leaseAmount != uriInfo.leaseAmount)
throw 'URIToken embedded lease amount and acquire lease amount does not match.';
@@ -790,10 +793,10 @@ 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);
await this.recreateLeaseOffer(uriTokenId, tenantAddress, leaseIndex, uriInfo?.outboundIP?.address);
}
else {
const instanceRequirements = r.payload;
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 : "-" };
createRes = await this.sashiCli.createInstance(containerName, instanceRequirements);
// Number of validated ledgers passed while the instance is created.
@@ -804,7 +807,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);
await this.destroyInstance(createRes.content.name, tenantAddress, leaseIndex, instanceOutboundIPAddress);
} else {
console.log(`Instance created for ${tenantAddress}`);
@@ -826,6 +829,10 @@ class MessageBoard {
await this.hostClient.updateRegInfo(this.activeInstanceCount);
// Send the acquire response with created instance info.
// Modify Response.
createRes.content.domain = createRes.content.ip;
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);
});
@@ -845,7 +852,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).catch(console.error);
await this.recreateLeaseOffer(uriTokenId, tenantAddress, leaseIndex, instanceOutboundIPAddress).catch(console.error);
await this.#queueAction(async () => {
// Send error transaction with received leaseAmount.
@@ -858,10 +865,10 @@ class MessageBoard {
}
}
async destroyInstance(containerName, tenantAddress, leaseIndex) {
async destroyInstance(containerName, tenantAddress, leaseIndex, outboundIP = null) {
// Destroy the instance.
await this.sashiCli.destroyInstance(containerName);
await this.recreateLeaseOffer(containerName, tenantAddress, leaseIndex).catch(console.error);
await this.recreateLeaseOffer(containerName, tenantAddress, leaseIndex, outboundIP).catch(console.error);
}
async handleExtendLease(r) {
@@ -974,6 +981,9 @@ 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() {
@@ -983,6 +993,16 @@ 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 }));
}

View File

@@ -6,6 +6,7 @@ const fs = require('fs');
const { SqliteDatabase } = require('./sqlite-handler');
const { ConfigHelper } = require('./config-helper');
const { SashiCLI } = require('./sashi-cli');
const { UtilHelper } = require('./util-helper');
function setEvernodeDefaults(governorAddress, rippledServer, xrplApi = null) {
evernode.Defaults.set({
@@ -56,8 +57,8 @@ class Setup {
ConfigHelper.writeConfig(cfg, appenv.CONFIG_PATH, appenv.SECRET_CONFIG_PATH);
}
newConfig(address = "", secret = "", governorAddress = "", leaseAmount = 0, rippledServer = null) {
this.#saveConfig({
newConfig(address = "", secret = "", governorAddress = "", leaseAmount = 0, rippledServer = null, ipv6Subnet = null, ipv6NetInterface = null) {
const baseConfig = {
version: appenv.MB_VERSION,
xrpl: {
address: address,
@@ -66,7 +67,9 @@ class Setup {
rippledServer: rippledServer || appenv.DEFAULT_RIPPLED_SERVER,
leaseAmount: leaseAmount
}
});
};
this.#saveConfig(ipv6NetInterface ? { ...baseConfig, networking: { ipv6: { subnet: ipv6Subnet, interface: ipv6NetInterface } } } : baseConfig);
}
async setupHostAccount(address, secret, rippledServer, governorAddress, domain) {
@@ -176,7 +179,8 @@ class Setup {
async register(countryCode, cpuMicroSec, ramKb, swapKb, diskKb, totalInstanceCount, cpuModel, cpuCount, cpuSpeed, emailAddress, description) {
console.log("Registering host...");
let cpuModelFormatted = cpuModel.replaceAll('_', ' ');
const acc = this.#getConfig().xrpl;
const config = this.#getConfig();
const acc = config.xrpl;
setEvernodeDefaults(acc.governorAddress, acc.rippledServer);
const hostClient = new evernode.HostClient(acc.address, acc.secret);
@@ -200,10 +204,15 @@ 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);
await hostClient.offerLease(i, acc.leaseAmount, appenv.TOS_HASH, ipV6AddressList.length > 0 ? ipV6AddressList[i] : null);
console.log(`Created lease offer ${i + 1} of ${totalInstanceCount}.`);
}
@@ -316,7 +325,7 @@ class Setup {
setEvernodeDefaults(acc.governorAddress, acc.rippledServer, hostClient.xrplApi);
const hostInfo = await hostClient.getHostInfo();
await hostClient.disconnect();
console.log(JSON.stringify(hostInfo, null, 2));
@@ -429,11 +438,22 @@ class Setup {
// 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.`;
@@ -526,8 +546,12 @@ class Setup {
}
}
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) {
@@ -540,17 +564,26 @@ class Setup {
await initClients(rippledServer);
}
for (const idx of uriTokenIndexesToCreate) {
for (const idx of uriTokenIndexesToCreate.sort((a, b) => { return a - b; })) {
try {
if (lastAssignedIPV6)
lastAssignedIPV6 = UtilHelper.generateValidIPV6Address(config.networking.ipv6.subnet, lastAssignedIPV6);
await hostClient.offerLease(idx,
leaseAmount ? leaseAmount : acc.leaseAmount,
appenv.TOS_HASH);
appenv.TOS_HASH,
lastAssignedIPV6);
}
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();
}
@@ -601,7 +634,7 @@ class Setup {
// Burn the URITokens and recreate the offer.
await hostClient.expireLease(containerName, lease.tenant_xrp_address).catch(console.error);
await hostClient.offerLease(uriInfo.leaseIndex, acc.leaseAmount, appenv.TOS_HASH).catch(console.error);
await hostClient.offerLease(uriInfo.leaseIndex, acc.leaseAmount, appenv.TOS_HASH, uriInfo?.outboundIP?.address).catch(console.error);
// Refund EVRs to the tenant.
const currentTime = evernode.UtilHelpers.getCurrentUnixTime();

View File

@@ -0,0 +1,74 @@
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
const subnet = ip6addr.createCIDR(subnetStr);
if (offsetIP && !subnet.contains(offsetIP))
throw "Invalid offset IP Address."
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());
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:");
}
}
module.exports = {
UtilHelper
}

1725
mb-xrpl/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
"build": "npm run lint && ncc build app.js --minify -o dist"
},
"dependencies": {
"evernode-js-client": "0.6.18",
"evernode-js-client": "0.6.19",
"sqlite3": "5.0.2",
"ip6addr": "0.2.5"
},

View File

@@ -10,7 +10,7 @@ namespace cli
constexpr const int BUFFER_SIZE = 4096; // Max read buffer size.
constexpr const char *MSG_LIST = "{\"type\": \"list\"}";
constexpr const char *MSG_BASIC = "{\"type\":\"%s\",\"container_name\":\"%s\"}";
constexpr const char *MSG_CREATE = "{\"type\":\"create\",\"container_name\":\"%s\",\"owner_pubkey\":\"%s\",\"contract_id\":\"%s\",\"image\":\"%s\",\"config\":{}}";
constexpr const char *MSG_CREATE = "{\"type\":\"create\",\"container_name\":\"%s\",\"owner_pubkey\":\"%s\",\"contract_id\":\"%s\",\"image\":\"%s\",\"outbound_ipv6\":\"%s\",\"outbound_net_interface\":\"%s\",\"config\":{}}";
constexpr const char *DOCKER_ATTACH = "DOCKER_HOST=unix:///run/user/$(id -u %s)/docker.sock %s/dockerbin/docker attach --detach-keys=\"ctrl-c\" %s";
@@ -163,7 +163,7 @@ namespace cli
}
// Read the length of the message to a buffer
uint8_t length_buffer[8];
uint8_t length_buffer[8];
int res = read(ctx.socket_fd, length_buffer, 8);
if (res == -1)
{
@@ -171,7 +171,7 @@ namespace cli
return -1;
}
const uint32_t message_length = uint32_from_bytes(length_buffer);
const uint32_t message_length = uint32_from_bytes(length_buffer);
// Resize the message buffer to fit to the message length
message.resize(message_length);
@@ -214,11 +214,11 @@ namespace cli
return ret;
}
int create(std::string_view container_name, std::string_view owner, std::string_view contract_id, std::string_view image)
int create(std::string_view container_name, std::string_view owner, std::string_view contract_id, std::string_view image, std::string_view outbound_ipv6, std::string_view outbound_net_interface)
{
std::string msg, output;
msg.resize(95 + container_name.size() + owner.size() + contract_id.size() + image.size());
sprintf(msg.data(), MSG_CREATE, container_name.data(), owner.data(), contract_id.data(), image.data());
msg.resize(142 + container_name.size() + owner.size() + contract_id.size() + image.size() + outbound_ipv6.size() + outbound_net_interface.size());
sprintf(msg.data(), MSG_CREATE, container_name.data(), owner.data(), contract_id.data(), image.data(), outbound_ipv6.data(), outbound_net_interface.data());
const int ret = get_json_output(msg, output);
if (ret == 0)

View File

@@ -27,7 +27,7 @@ namespace cli
int execute_basic(std::string_view type, std::string_view container_name);
int create(std::string_view container_name, std::string_view owner, std::string_view contract_id, std::string_view image);
int create(std::string_view container_name, std::string_view owner, std::string_view contract_id, std::string_view image, std::string_view outbound_ipv6, std::string_view outbound_net_interface);
int list();
@@ -38,7 +38,7 @@ namespace cli
const std::string value_to_string(const jsoncons::json &val);
void deinit();
uint32_t uint32_from_bytes(const uint8_t *data);
}

View File

@@ -90,10 +90,12 @@ int parse_cmd(int argc, char **argv)
std::string json_message;
json->add_option("-m,--message", json_message, "JSON message");
std::string owner, contract_id, image;
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");
create->add_option("-i,--image", image, "Container image to use");
create->add_option("-a,--outbound-ipv6", outbound_ipv6, "Outbound IPV6 Address");
create->add_option("-f,--outbound-net-interface", outbound_net_interface, "Outbound IPV6 Network Interface (Façade)");
std::string container_name;
create->add_option("-n,--name", container_name, "Instance name");
@@ -134,8 +136,7 @@ int parse_cmd(int argc, char **argv)
return execute_cli([]()
{
std::cout << cli::ctx.socket_path << std::endl;
return 0;
});
return 0; });
}
else if (list->parsed())
{
@@ -146,8 +147,7 @@ int parse_cmd(int argc, char **argv)
std::cerr << "Failed to list instances." << std::endl;
return -1;
}
return 0;
});
return 0; });
}
else if (json->parsed() && !json_message.empty())
{
@@ -158,13 +158,12 @@ int parse_cmd(int argc, char **argv)
return -1;
std::cout << output << std::endl;
return 0;
});
return 0; });
}
else if (create->parsed() && !container_name.empty() && !owner.empty() && !contract_id.empty() && !image.empty())
{
return execute_cli([&]()
{ return cli::create(container_name, owner, contract_id, image); });
{ return cli::create(container_name, owner, contract_id, image, outbound_ipv6, outbound_net_interface); });
}
else if (start->parsed() && !container_name.empty())
{

View File

@@ -211,7 +211,7 @@ namespace comm
hp::instance_info info;
std::string error_msg;
if (hp::create_new_instance(error_msg, info, msg.container_name, msg.pubkey, msg.contract_id, msg.image) == -1)
if (hp::create_new_instance(error_msg, info, msg.container_name, msg.pubkey, msg.contract_id, msg.image, msg.outbound_ipv6, msg.outbound_net_interface) == -1)
__HANDLE_RESPONSE(msg::MSGTYPE_CREATE_ERROR, error_msg, -1);
if (hp::initiate_instance(error_msg, info.container_name, init_msg) == -1)
@@ -283,7 +283,7 @@ namespace comm
}
else
__HANDLE_RESPONSE("error", TYPE_ERROR, -1);
return 0;
}
@@ -302,7 +302,8 @@ namespace comm
uint32_to_bytes(length_buffer, message.length());
int res = write(ctx.data_socket, length_buffer, 8);
if (res == -1) {
if (res == -1)
{
disconnect();
return -1;
}
@@ -310,16 +311,15 @@ namespace comm
res = write(ctx.data_socket, message.data(), message.length());
// Close connection after sending the response to the client.
disconnect();
return res == -1 ? -1 : 0;
}
return res == -1 ? -1 : 0;
}
/**
* Convert the given uint32_t number to bytes in big endian format.
* @param dest Byte array pointer.
* @param x Number to be converted.
*/
*/
void uint32_to_bytes(uint8_t *dest, const uint32_t x)
{
dest[0] = (uint8_t)((x >> 24) & 0xff);

View File

@@ -121,7 +121,7 @@ namespace hp
* @param image Docker image name to use (image prefix name must exists).
* @return 0 on success and -1 on error.
*/
int create_new_instance(std::string &error_msg, instance_info &info, std::string_view container_name, std::string_view owner_pubkey, const std::string &contract_id, const std::string &image)
int create_new_instance(std::string &error_msg, instance_info &info, std::string_view container_name, std::string_view owner_pubkey, const std::string &contract_id, const std::string &image, std::string_view outbound_ipv6, std::string_view outbound_net_interface)
{
// Creating an instance with same name is not allowed.
hp::instance_info existing_instance;
@@ -186,8 +186,8 @@ namespace hp
int user_id;
std::string username;
if (install_user(
user_id, username, instance_resources.cpu_us, instance_resources.mem_kbytes, instance_resources.swap_kbytes,
instance_resources.storage_kbytes, container_name, instance_ports, image_name, "-", "-") == -1)
user_id, username, instance_resources.cpu_us, instance_resources.mem_kbytes, instance_resources.swap_kbytes,
instance_resources.storage_kbytes, container_name, instance_ports, image_name, outbound_ipv6, outbound_net_interface) == -1)
{
error_msg = USER_INSTALL_ERROR;
return -1;

View File

@@ -68,7 +68,7 @@ namespace hp
void deinit();
int create_new_instance(std::string &error_msg, instance_info &info, std::string_view container_name, std::string_view owner_pubkey, const std::string &contract_id, const std::string &image_key);
int create_new_instance(std::string &error_msg, instance_info &info, std::string_view container_name, std::string_view owner_pubkey, const std::string &contract_id, const std::string &image_key, std::string_view outbound_ipv6, std::string_view outbound_net_interface);
int initiate_instance(std::string &error_msg, std::string_view container_name, const msg::initiate_msg &config_msg);

View File

@@ -135,6 +135,8 @@ namespace msg::json
msg.pubkey = d[msg::FLD_PUBKEY].as<std::string>();
msg.contract_id = d[msg::FLD_CONTRACT_ID].as<std::string>();
msg.image = d[msg::FLD_IMAGE].as<std::string>();
msg.outbound_ipv6 = d[msg::FLD_OUTBOUND_IPV6].is<std::string>() ? d[msg::FLD_OUTBOUND_IPV6].as<std::string>() : "-";
msg.outbound_net_interface = d[msg::FLD_OUTBOUND_NET_INTERFACE].is<std::string>() ? d[msg::FLD_OUTBOUND_NET_INTERFACE].as<std::string>() : "-";
return 0;
}

View File

@@ -13,6 +13,8 @@ namespace msg
std::string pubkey;
std::string contract_id;
std::string image;
std::string outbound_ipv6;
std::string outbound_net_interface;
};
struct history_configuration
@@ -172,6 +174,8 @@ namespace msg
constexpr const char *FLD_CONTAINER_NAME = "container_name";
constexpr const char *FLD_CONTRACT_ID = "contract_id";
constexpr const char *FLD_IMAGE = "image";
constexpr const char *FLD_OUTBOUND_IPV6 = "outbound_ipv6";
constexpr const char *FLD_OUTBOUND_NET_INTERFACE = "outbound_net_interface";
constexpr const char *FLD_KNOWN_PEERS = "known_peers";
constexpr const char *FLD_MESH = "mesh";
constexpr const char *FLD_USER = "user";

View File

@@ -6,7 +6,7 @@
namespace version
{
// Sashimono agent version. Written to new configs.
constexpr const char *AGENT_VERSION = "0.6.7";
constexpr const char *AGENT_VERSION = "0.6.8";
// Minimum compatible config version (this will be used to validate configs).
constexpr const char *MIN_CONFIG_VERSION = "0.5.0";