mirror of
https://github.com/EvernodeXRPL/sashimono.git
synced 2026-07-26 08:30:31 +00:00
Automation scripts for Evernode cluster deployment. (#67)
* Message board install automation with faucet host account generation. * Deregister Evernode host on uninstall. * Used Evernode client lib in message board. * Removed file logging by default in Sashimono services. * Evernode cluster spin-up script. * Cleaned up hp.cfg template.
This commit is contained in:
@@ -69,7 +69,7 @@ add_custom_command(TARGET sagent POST_BUILD
|
||||
COMMAND cp ./bootstrap-contract/bootstrap_upgrade.sh ./build/contract_template/contract_fs/seed/state/
|
||||
COMMAND mv ./build/bootstrap_contract ./build/contract_template/contract_fs/seed/state/
|
||||
COMMAND ./installer/docker-install.sh ./build/dockerbin
|
||||
COMMAND npm run --prefix ./mb-xrpl build
|
||||
COMMAND npm --prefix ./mb-xrpl install && npm run --prefix ./mb-xrpl build
|
||||
)
|
||||
|
||||
target_precompile_headers(sagent PUBLIC src/pchheader.hpp)
|
||||
@@ -80,7 +80,7 @@ add_custom_target(installer
|
||||
COMMAND bash -c "cp -r ./build/{sagent,sashi,hpfs,user-install.sh,user-uninstall.sh,contract_template} ./build/sashimono-installer/"
|
||||
COMMAND bash -c "cp -r ./installer/{docker-install.sh,registry-install.sh,registry-uninstall.sh,sashimono-install.sh,sashimono-uninstall.sh} ./build/sashimono-installer/"
|
||||
COMMAND bash -c "cp -r ./dependencies/{user-cgcreate.sh,libblake3.so} ./build/sashimono-installer/"
|
||||
COMMAND bash -c "cp -r ./mb-xrpl/dist/mb-xrpl ./build/sashimono-installer/"
|
||||
COMMAND bash -c "cp -r ./mb-xrpl/dist ./build/sashimono-installer/mb-xrpl"
|
||||
COMMAND tar cfz ./build/sashimono-installer.tar.gz --directory=./build/ sashimono-installer
|
||||
COMMAND rm -r ./build/sashimono-installer
|
||||
)
|
||||
|
||||
18
dependencies/hp.cfg
vendored
18
dependencies/hp.cfg
vendored
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"hp_version": "1.0.0",
|
||||
"node": {
|
||||
"public_key": "ed903ba38b1fda781e048aee8cf805afa3e8dfb39dac4cd97ee5d00976d3a4808b",
|
||||
"private_key": "ed433d9ad4d6294254e4e307081508fc37327f62a35d6bd0c765187c77cff978e2903ba38b1fda781e048aee8cf805afa3e8dfb39dac4cd97ee5d00976d3a4808b",
|
||||
"public_key": "",
|
||||
"private_key": "",
|
||||
"role": "validator",
|
||||
"history": "custom",
|
||||
"history_config": {
|
||||
"max_primary_shards": 1,
|
||||
"max_raw_shards": 1
|
||||
"max_primary_shards": 2,
|
||||
"max_raw_shards": 2
|
||||
}
|
||||
},
|
||||
"contract": {
|
||||
"id": "f15abd89-ea82-4d8b-8d11-29fbc665dcf6",
|
||||
"id": "",
|
||||
"execute": true,
|
||||
"run_as": "",
|
||||
"log": {
|
||||
@@ -20,11 +20,9 @@
|
||||
"max_file_count": 10
|
||||
},
|
||||
"version": "1.0",
|
||||
"unl": [
|
||||
"ed903ba38b1fda781e048aee8cf805afa3e8dfb39dac4cd97ee5d00976d3a4808b"
|
||||
],
|
||||
"bin_path": "/usr/bin/node",
|
||||
"bin_args": "/contract/echo_contract.js",
|
||||
"unl": [],
|
||||
"bin_path": "",
|
||||
"bin_args": "",
|
||||
"roundtime": 2000,
|
||||
"stage_slice": 25,
|
||||
"consensus": "public",
|
||||
|
||||
@@ -81,6 +81,7 @@ sudo apt-get install -y \
|
||||
sudo cp $scriptdir/dependencies/libblake3.so /usr/local/lib/
|
||||
|
||||
# NodeJs
|
||||
sudo apt-get install -y ca-certificates # In case nodejs package certitficates are renewed.
|
||||
curl -fsSL https://deb.nodesource.com/setup_14.x | sudo -E bash -
|
||||
sudo apt-get install -y nodejs
|
||||
|
||||
|
||||
@@ -5,14 +5,15 @@
|
||||
|
||||
user_bin=/usr/bin
|
||||
sashimono_bin=/usr/bin/sashimono-agent
|
||||
docker_bin=/usr/bin/sashimono-agent/dockerbin
|
||||
mb_xrpl_bin=$sashimono_bin/mb-xrpl
|
||||
docker_bin=$sashimono_bin/dockerbin
|
||||
sashimono_data=/etc/sashimono
|
||||
mb_xrpl_data=$sashimono_data/mb-xrpl
|
||||
mb_xrpl_conf=$mb_xrpl_data/mb-xrpl.cfg
|
||||
sashimono_service="sashimono-agent"
|
||||
cgcreate_service="sashimono-cgcreate"
|
||||
mb_xrpl_service="sashimono-mb-xrpl"
|
||||
mb_xrpl_dir="$sashimono_bin"/mb-xrpl
|
||||
mb_xrpl_conf="$mb_xrpl_dir"/mb-xrpl.cfg
|
||||
hook_xrpl_addr="rb4H5w7H1QA2qKjHCRSuUey2fnMBGbN2c"
|
||||
hook_address="rwGLw5uSGYm2couHZnrbCDKaQZQByvamj8"
|
||||
group="sashimonousers"
|
||||
admin_group="sashiadmin"
|
||||
cgroupsuffix="-cg"
|
||||
@@ -21,9 +22,6 @@ registryport=4444
|
||||
script_dir=$(dirname "$(realpath "$0")")
|
||||
quiet=$1
|
||||
|
||||
xrpl_server_url="wss://hooks-testnet.xrpl-labs.com"
|
||||
xrpl_fauset_url="https://hooks-testnet.xrpl-labs.com/newcreds"
|
||||
|
||||
[ -d $sashimono_bin ] && [ -n "$(ls -A $sashimono_bin)" ] &&
|
||||
echo "Aborting installation. Previous Sashimono installation detected at $sashimono_bin" && exit 1
|
||||
|
||||
@@ -37,6 +35,8 @@ mkdir -p $docker_bin
|
||||
[ "$?" == "1" ] && echo "Could not create '$docker_bin'. Make sure you are running as sudo." && exit 1
|
||||
mkdir -p $sashimono_data
|
||||
[ "$?" == "1" ] && echo "Could not create '$sashimono_data'. Make sure you are running as sudo." && exit 1
|
||||
mkdir -p $mb_xrpl_data
|
||||
[ "$?" == "1" ] && echo "Could not create '$mb_xrpl_data'. Make sure you are running as sudo." && exit 1
|
||||
|
||||
echo "Installing Sashimono..."
|
||||
|
||||
@@ -140,12 +140,43 @@ systemctl enable $sashimono_service
|
||||
systemctl start $sashimono_service
|
||||
# Both of these services needed to be restarted if sa.cfg max instance resources are manually changed.
|
||||
|
||||
if [ "$quiet" != "-q" ]; then
|
||||
# Setup xrpl message board.
|
||||
cp -r "$script_dir"/mb-xrpl $sashimono_bin
|
||||
chmod -R +x "$sashimono_bin"/mb-xrpl
|
||||
# Setup xrpl message board.
|
||||
echo "Installing Evernode xrpl message board..."
|
||||
if [ "$quiet"=="-q" ]; then
|
||||
|
||||
echo "Please answer following questions to setup xrpl message board.."
|
||||
# We are in the quiet mode. Hence we auto-generate an XRPL test account and token details for the host.
|
||||
# (This is done for testing purposes during development)
|
||||
|
||||
xrpl_faucet_url="https://hooks-testnet.xrpl-labs.com/newcreds"
|
||||
hook_secret="shaXJCUZeE37nCe5VpiT8xiVFjFmY"
|
||||
func_url="https://func-hotpocket.azurewebsites.net/api/evrfaucet?code=pPUyV1q838ryrihA5NVlobVXj8ZGgn9HsQjGGjl6Vhgxlfha4/xCgQ=="
|
||||
|
||||
# Generate new fauset account.
|
||||
echo "Generating XRP faucet account..."
|
||||
new_acc=$(curl -X POST $xrpl_faucet_url)
|
||||
# If result is not a json, account generation failed.
|
||||
[[ ! "$new_acc" =~ \{.+\} ]] && echo "Xrpl faucet account generation failed." && rollback
|
||||
xrp_address=$(echo $new_acc | jq -r '.address')
|
||||
xrp_secret=$(echo $new_acc | jq -r '.secret')
|
||||
([ "$xrp_address" == "" ] || [ "$xrp_address" == "null" ] ||
|
||||
[ "$xrp_secret" == "" ] || [ "$xrp_secret" == "null" ]) && echo "Invalid generated xrpl account details: $new_acc" && rollback
|
||||
|
||||
# Setup the host xrpl account with an EVR balance and default rippling flag.
|
||||
echo "Setting up host XRP account..."
|
||||
acc_setup_func="$func_url&action=setuphost&hookaddr=$hook_address&hooksecret=$hook_secret&addr=$xrp_address&secret=$xrp_secret"
|
||||
func_code=$(curl -o /dev/null -s -w "%{http_code}\n" -d "" -X POST $acc_setup_func)
|
||||
[ "$func_code" != "200" ] && echo "Host XRP account setup failed. code:$func_code" && rollback
|
||||
|
||||
# Generate random details for instance size, location and token.
|
||||
instance_size="AUTO InstSize "$(tr -dc A-Z </dev/urandom | head -c 10)
|
||||
location="AUTO Loc "$(tr -dc A-Z </dev/urandom | head -c 5)
|
||||
token=$(tr -dc A-Z </dev/urandom | head -c 3)
|
||||
|
||||
echo "Auto-generated host information."
|
||||
|
||||
else
|
||||
|
||||
echo "Please answer following questions to setup Evernode xrpl message board."
|
||||
# Ask for input until a correct value is given
|
||||
while [ -z "$instance_size" ] || [[ "$instance_size" =~ .*\;.* ]]; do
|
||||
read -p "Instance size? " instance_size </dev/tty
|
||||
@@ -159,41 +190,40 @@ if [ "$quiet" != "-q" ]; then
|
||||
read -p "Token name? " token </dev/tty
|
||||
[[ ! "$token" =~ ^[A-Z]{3}$ ]] && echo "Token name should be 3 UPPERCASE letters."
|
||||
done
|
||||
|
||||
# Generate new fauset account.
|
||||
new_acc=$(curl -X POST $xrpl_fauset_url)
|
||||
# If result is not a json, account generation failed.
|
||||
[[ ! "$new_acc" =~ \{.+\} ]] && echo "Xrpl fauset account generation failed." && rollback
|
||||
|
||||
address=$(echo $new_acc | jq -r '.address')
|
||||
secret=$(echo $new_acc | jq -r '.secret')
|
||||
([ "$address" == "" ] || [ "$address" == "null" ] ||
|
||||
[ "$secret" == "" ] || [ "$secret" == "null" ]) && echo "Invalid xrpl account details: $new_acc" && rollback
|
||||
|
||||
(! echo "{\"host\":{\"name\":\"\",\"location\":\"$location\",\"instanceSize\":\"$instance_size\"},\"xrpl\":{\"address\":\"$address\",\"secret\":\"$secret\",\"token\":\"$token\",\"hookAddress\":\"$hook_xrpl_addr\",\"regTrustHash\":\"\",\"regFeeHash\":\"\"}}" | jq . >$mb_xrpl_conf) && rollback
|
||||
|
||||
# Install xrpl message board systemd service.
|
||||
# StartLimitIntervalSec=0 to make unlimited retries. RestartSec=5 is to keep 5 second gap between restarts.
|
||||
echo "[Unit]
|
||||
Description=Running and monitoring evernode xrpl transactions.
|
||||
After=network.target
|
||||
StartLimitIntervalSec=0
|
||||
[Service]
|
||||
User=root
|
||||
Group=root
|
||||
Type=simple
|
||||
WorkingDirectory=$mb_xrpl_dir
|
||||
ExecStart=node $mb_xrpl_dir $xrpl_server_url --enable-logging
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
[Install]
|
||||
WantedBy=multi-user.target" >/etc/systemd/system/$mb_xrpl_service.service
|
||||
|
||||
# This service needed to be restarted when mb-xrpl.cfg is changed.
|
||||
systemctl enable $mb_xrpl_service
|
||||
systemctl start $mb_xrpl_service
|
||||
while [[ -z "$xrp_address" ]]; do
|
||||
read -p "XRPL account address? " xrp_address </dev/tty
|
||||
done
|
||||
while [[ -z "$xrp_secret" ]]; do
|
||||
read -p "XRPL account secret? " xrp_secret </dev/tty
|
||||
done
|
||||
fi
|
||||
|
||||
cp -r "$script_dir"/mb-xrpl $sashimono_bin
|
||||
(! echo "{\"host\":{\"location\":\"$location\",\"instanceSize\":\"$instance_size\"},\"xrpl\":{\"address\":\"$xrp_address\",\"secret\":\"$xrp_secret\",\"token\":\"$token\",\"hookAddress\":\"$hook_address\",\"regFeeHash\":\"\"}}" | jq . >$mb_xrpl_conf) && rollback
|
||||
|
||||
# Install xrpl message board systemd service.
|
||||
# StartLimitIntervalSec=0 to make unlimited retries. RestartSec=5 is to keep 5 second gap between restarts.
|
||||
echo "[Unit]
|
||||
Description=Running and monitoring evernode xrpl transactions.
|
||||
After=network.target
|
||||
StartLimitIntervalSec=0
|
||||
[Service]
|
||||
User=root
|
||||
Group=root
|
||||
Type=simple
|
||||
WorkingDirectory=$mb_xrpl_bin
|
||||
Environment=\"MB_DATA_DIR=$mb_xrpl_data\"
|
||||
ExecStart=node $mb_xrpl_bin
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
[Install]
|
||||
WantedBy=multi-user.target" >/etc/systemd/system/$mb_xrpl_service.service
|
||||
|
||||
# This service needs to be restarted when mb-xrpl.cfg is changed.
|
||||
systemctl enable $mb_xrpl_service
|
||||
systemctl start $mb_xrpl_service
|
||||
echo "Installed Evernode xrpl message board."
|
||||
|
||||
echo "Sashimono installed successfully."
|
||||
echo "Please restart your cgroup rule generator service or reboot your server for changes to apply."
|
||||
exit 0
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
user_bin=/usr/bin
|
||||
sashimono_bin=/usr/bin/sashimono-agent
|
||||
docker_bin=/usr/bin/sashimono-agent/dockerbin
|
||||
mb_xrpl_bin=$sashimono_bin/mb-xrpl
|
||||
docker_bin=$sashimono_bin/dockerbin
|
||||
sashimono_data=/etc/sashimono
|
||||
mb_xrpl_data=$sashimono_data/mb-xrpl
|
||||
sashimono_service="sashimono-agent"
|
||||
cgcreate_service="sashimono-cgcreate"
|
||||
mb_xrpl_service="sashimono-mb-xrpl"
|
||||
@@ -23,6 +25,35 @@ if [ "$quiet" != "-q" ]; then
|
||||
[ "$confirmation" != "yes" ] && echo "Uninstall cancelled." && exit 0
|
||||
fi
|
||||
|
||||
# Remove xrpl message board service if exists.
|
||||
if [ -f /etc/systemd/system/$mb_xrpl_service.service ]; then
|
||||
systemctl stop $mb_xrpl_service
|
||||
systemctl disable $mb_xrpl_service
|
||||
rm /etc/systemd/system/$mb_xrpl_service.service
|
||||
fi
|
||||
if [ "$quiet" == "-q" ]; then
|
||||
# We only perform this for our testing setup during development.
|
||||
echo "Cleaning up host XRP account..."
|
||||
|
||||
hook_address="rwGLw5uSGYm2couHZnrbCDKaQZQByvamj8"
|
||||
func_url="https://func-hotpocket.azurewebsites.net/api/evrfaucet?code=pPUyV1q838ryrihA5NVlobVXj8ZGgn9HsQjGGjl6Vhgxlfha4/xCgQ=="
|
||||
|
||||
mb_xrpl_conf=$mb_xrpl_data/mb-xrpl.cfg
|
||||
xrp_address=$(jq -r '.xrpl.address' $mb_xrpl_conf)
|
||||
xrp_secret=$(jq -r '.xrpl.secret' $mb_xrpl_conf)
|
||||
|
||||
if [ "$xrp_address" != "" ] && [ "$xrp_secret" != "" ]; then
|
||||
acc_clean_func="$func_url&action=cleanhost&hookaddr=$hook_address&addr=$xrp_address&secret=$xrp_secret"
|
||||
func_code=$(curl -o /dev/null -s -w "%{http_code}\n" -d "" -X POST $acc_clean_func)
|
||||
[ "$func_code" != "200" ] && echo "Host XRP account cleanup failed. code:$func_code"
|
||||
[ "$func_code" == "200" ] && echo "Cleaned up host XRP account."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Deregister evernode message board host registration.
|
||||
echo "Attempting Evernode xrpl message board host deregistration..."
|
||||
MB_DATA_DIR=$mb_xrpl_data MB_LOG=1 MB_DEREGISTER=1 node $mb_xrpl_bin
|
||||
|
||||
# Uninstall all contract instance users
|
||||
prefix="sashi"
|
||||
users=$(cut -d: -f1 /etc/passwd | grep "^$prefix" | sort)
|
||||
@@ -60,13 +91,6 @@ if [ $ucount -gt 0 ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Remove xrpl message board service if exists.
|
||||
if [ -f /etc/systemd/system/$mb_xrpl_service.service ]; then
|
||||
systemctl stop $mb_xrpl_service
|
||||
systemctl disable $mb_xrpl_service
|
||||
rm /etc/systemd/system/$mb_xrpl_service.service
|
||||
fi
|
||||
|
||||
echo "Removing Sashimono cgroup creation service..."
|
||||
systemctl stop $cgcreate_service
|
||||
systemctl disable $cgcreate_service
|
||||
|
||||
@@ -6,26 +6,35 @@ const formatText = (text, logType = 'dbg') => {
|
||||
const date = new Date().toISOString().
|
||||
replace(/T/, ' '). // Replace T with a space.
|
||||
replace(/\..+/, ''). // Delete the dot and everything after.
|
||||
replace(/-/, ''); // Delete the dashes.
|
||||
replace(/-/g, ''); // Delete the dashes.
|
||||
|
||||
return `${date} [${logType}] ${text}\n`;
|
||||
}
|
||||
|
||||
exports.init = (logPath) => {
|
||||
const dirname = path.dirname(logPath);
|
||||
if (!fs.existsSync(dirname))
|
||||
fs.mkdirSync(dirname, { recursive: true });
|
||||
exports.init = (logPath, fileLogEnabled) => {
|
||||
|
||||
let flog = null;
|
||||
|
||||
if (fileLogEnabled) {
|
||||
const dirname = path.dirname(logPath);
|
||||
if (!fs.existsSync(dirname))
|
||||
fs.mkdirSync(dirname, { recursive: true });
|
||||
|
||||
flog = fs.createWriteStream(logPath, { flags: 'a' });
|
||||
}
|
||||
|
||||
// Formating logs and printing.
|
||||
const ws = fs.createWriteStream(logPath, { flags: 'a' });
|
||||
console.log = function () {
|
||||
const text = formatText(util.format.apply(this, arguments));
|
||||
ws.write(text);
|
||||
process.stdout.write(text);
|
||||
|
||||
if (flog)
|
||||
flog.write(text);
|
||||
};
|
||||
console.error = function () {
|
||||
const text = formatText(util.format.apply(this, arguments), 'err');
|
||||
ws.write(text);
|
||||
process.stderr.write(text);
|
||||
|
||||
if (flog)
|
||||
flog.write(text);
|
||||
};
|
||||
}
|
||||
@@ -1,358 +0,0 @@
|
||||
const eccrypto = require("eccrypto");
|
||||
const RippleAPI = require('ripple-lib').RippleAPI;
|
||||
|
||||
const CONNECTION_RETRY_THREASHOLD = 60;
|
||||
const CONNECTION_RETRY_INTERVAL = 1000;
|
||||
|
||||
const DISABLE_MASTERKEY = 0x100000;
|
||||
|
||||
const maxLedgerOffset = 10;
|
||||
|
||||
const MemoTypes = {
|
||||
REDEEM: 'evnRedeem',
|
||||
REDEEM_REF: 'evnRedeemRef',
|
||||
REDEEM_RESP: 'evnRedeemResp',
|
||||
HOST_REG: 'evnHostReg'
|
||||
}
|
||||
|
||||
const MemoFormats = {
|
||||
TEXT: 'text/plain',
|
||||
JSON: 'text/json',
|
||||
BINARY: 'binary'
|
||||
}
|
||||
|
||||
const Events = {
|
||||
RECONNECTED: 'reconnected',
|
||||
LEDGER: 'ledger',
|
||||
PAYMENT: 'payment'
|
||||
}
|
||||
|
||||
const ErrorCodes = {
|
||||
REDEEM_ERR: 'REDEEM_ERR'
|
||||
}
|
||||
|
||||
const hexToASCII = (hex) => {
|
||||
let str = "";
|
||||
for (let n = 0; n < hex.length; n += 2) {
|
||||
str += String.fromCharCode(parseInt(hex.substr(n, 2), 16));
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
class EventEmitter {
|
||||
constructor() {
|
||||
this.handlers = {};
|
||||
}
|
||||
|
||||
on(event, handler) {
|
||||
if (!this.handlers[event])
|
||||
this.handlers[event] = [];
|
||||
this.handlers[event].push(handler);
|
||||
}
|
||||
|
||||
emit(event, value, error = null) {
|
||||
if (this.handlers[event])
|
||||
this.handlers[event].forEach(handler => handler(value, error));
|
||||
}
|
||||
}
|
||||
|
||||
class RippleAPIWarpper {
|
||||
constructor(rippleServer) {
|
||||
this.connectionRetryCount = 0;
|
||||
this.connected = false;
|
||||
this.rippleServer = rippleServer;
|
||||
this.events = new EventEmitter();
|
||||
|
||||
this.api = new RippleAPI({ server: this.rippleServer });
|
||||
this.api.on('error', (errorCode, errorMessage) => {
|
||||
console.log(errorCode + ': ' + errorMessage);
|
||||
});
|
||||
this.api.on('connected', () => {
|
||||
console.log(`Connected to ${this.rippleServer}`);
|
||||
this.connectionRetryCount = 0;
|
||||
this.connected = true;
|
||||
});
|
||||
this.api.on('disconnected', async (code) => {
|
||||
if (!this.connected)
|
||||
return;
|
||||
|
||||
this.connected = false;
|
||||
console.log(`Disconnected from ${this.rippleServer} code:`, code);
|
||||
try {
|
||||
await this.connect();
|
||||
this.events.emit(Events.RECONNECTED, `Reconnected to ${this.rippleServer}`);
|
||||
}
|
||||
catch (e) { console.error(e); };
|
||||
});
|
||||
this.api.on('ledger', (ledger) => {
|
||||
this.events.emit(Events.LEDGER, ledger);
|
||||
});
|
||||
}
|
||||
|
||||
async connect() {
|
||||
if (this.connected)
|
||||
return;
|
||||
|
||||
let retryInterval = CONNECTION_RETRY_INTERVAL;
|
||||
// If failed, Keep retrying and increasing the retry timeout.
|
||||
while (true) {
|
||||
try {
|
||||
this.connectionRetryCount++;
|
||||
console.log(`Trying to connect ${this.rippleServer}`);
|
||||
await this.api.connect();
|
||||
return;
|
||||
}
|
||||
catch (e) {
|
||||
console.log(`Couldn't connect ${this.rippleServer} : `, e);
|
||||
// If threashold reaches, increase the retry interval.
|
||||
if (this.connectionRetryCount % CONNECTION_RETRY_THREASHOLD === 0)
|
||||
retryInterval += CONNECTION_RETRY_INTERVAL;
|
||||
// Wait before retry.
|
||||
await new Promise(resolve => setTimeout(resolve, retryInterval));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deriveAddress(publicKey) {
|
||||
return this.api.deriveAddress(publicKey);
|
||||
}
|
||||
|
||||
async getAccountInfo(address) {
|
||||
return (await this.api.request('account_info', { account: address }));
|
||||
}
|
||||
|
||||
async getLedgerVersion() {
|
||||
return (await this.api.getLedgerVersion());
|
||||
}
|
||||
|
||||
async isValidAddress(publicKey, address) {
|
||||
const derivedAddress = this.deriveAddress(publicKey);
|
||||
const info = await this.getAccountInfo(address);
|
||||
const accountFlags = info.account_data.Flags;
|
||||
const regularKey = info.account_data.RegularKey;
|
||||
const masterKeyDisabled = (DISABLE_MASTERKEY & accountFlags) === DISABLE_MASTERKEY;
|
||||
|
||||
// If the master key is disabled the derived address should be the regular key.
|
||||
// Otherwise it could be master key or the regular key
|
||||
if (masterKeyDisabled)
|
||||
return regularKey && (derivedAddress === regularKey);
|
||||
else
|
||||
return derivedAddress === address || (regularKey && derivedAddress === regularKey);
|
||||
}
|
||||
}
|
||||
|
||||
class XrplAccount {
|
||||
constructor(rippleAPI, address, secret = null) {
|
||||
this.rippleAPI = rippleAPI;
|
||||
this.address = address;
|
||||
this.secret = secret;
|
||||
this.events = new EventEmitter();
|
||||
this.subscribed = false;
|
||||
}
|
||||
|
||||
deriveKeypair() {
|
||||
if (!this.secret)
|
||||
throw 'Cannot derive key pair: Account secret is empty.';
|
||||
|
||||
return this.rippleAPI.api.deriveKeypair(this.secret);
|
||||
}
|
||||
|
||||
async makePayment(toAddr, amount, currency, issuer, memos = null) {
|
||||
// Get current ledger.
|
||||
const ledger = await (await this.rippleAPI.api.getLedger()).ledgerVersion;
|
||||
const maxLedger = ledger + maxLedgerOffset;
|
||||
|
||||
const amountObj = {
|
||||
currency: currency,
|
||||
counterparty: issuer,
|
||||
value: amount.toString()
|
||||
}
|
||||
|
||||
// Delete counterparty key if issuer is empty.
|
||||
if (!amountObj.counterparty)
|
||||
delete amountObj.counterparty;
|
||||
|
||||
const prepared = await this.rippleAPI.api.preparePayment(this.address, {
|
||||
source: {
|
||||
address: this.address,
|
||||
maxAmount: amountObj
|
||||
},
|
||||
destination: {
|
||||
address: toAddr,
|
||||
amount: amountObj
|
||||
},
|
||||
memos: this.getMemoCollection(memos)
|
||||
}, {
|
||||
maxLedgerVersion: maxLedger
|
||||
})
|
||||
|
||||
const signed = this.rippleAPI.api.sign(prepared.txJSON, this.secret);
|
||||
|
||||
await this.rippleAPI.api.submit(signed.signedTransaction);
|
||||
const verified = await this.verifyTransaction(signed.id, ledger, maxLedger);
|
||||
return verified ? verified : false;
|
||||
}
|
||||
|
||||
async createTrustline(currency, issuer, limit, allowRippling, memos = null) {
|
||||
const res = await this.createTrustlines([{
|
||||
issuer: issuer,
|
||||
limit: limit,
|
||||
currency: currency,
|
||||
allowRippling: allowRippling,
|
||||
memos: this.getMemoCollection(memos)
|
||||
}]);
|
||||
return res[0];
|
||||
}
|
||||
|
||||
getMemoCollection(memos) {
|
||||
return memos ? memos.filter(m => m.data).map(m => {
|
||||
return {
|
||||
type: m.type,
|
||||
format: m.format,
|
||||
data: (typeof m.data === "object") ? JSON.stringify(m.data) : m.data
|
||||
}
|
||||
}) : [];
|
||||
}
|
||||
|
||||
async createTrustlines(lines) {
|
||||
// Get current ledger.
|
||||
const ledger = await (await this.rippleAPI.api.getLedger()).ledgerVersion;
|
||||
const maxLedger = ledger + maxLedgerOffset;
|
||||
|
||||
// Create and verify multiple trust lines in parallel.
|
||||
const tasks = [];
|
||||
for (const line of lines) {
|
||||
tasks.push(new Promise(async (resolve) => {
|
||||
const prepared = await this.rippleAPI.api.prepareTrustline(this.address, {
|
||||
counterparty: line.issuer,
|
||||
currency: line.currency,
|
||||
limit: line.limit.toString(),
|
||||
memos: line.memos,
|
||||
ripplingDisabled: !line.allowRippling
|
||||
}, {
|
||||
maxLedgerVersion: maxLedger
|
||||
})
|
||||
|
||||
const signed = this.rippleAPI.api.sign(prepared.txJSON, this.secret);
|
||||
|
||||
await this.rippleAPI.api.submit(signed.signedTransaction);
|
||||
console.log("Submitted trust line.");
|
||||
const verified = await this.verifyTransaction(signed.id, ledger, maxLedger);
|
||||
verified ? resolve(verified) : resolve(false);
|
||||
}));
|
||||
}
|
||||
|
||||
const results = await Promise.all(tasks);
|
||||
return results;
|
||||
}
|
||||
|
||||
verifyTransaction(txHash, minLedger, maxLedger) {
|
||||
return new Promise(resolve => {
|
||||
this.rippleAPI.api.getTransaction(txHash, {
|
||||
minLedgerVersion: minLedger,
|
||||
maxLedgerVersion: maxLedger
|
||||
}).then(data => {
|
||||
console.log(data.outcome.result);
|
||||
if (data.outcome.result !== 'tesSUCCESS')
|
||||
console.log("Transaction verification failed. Result: " + data.outcome.result);
|
||||
resolve(data.outcome.result === 'tesSUCCESS' ? { txHash: data.id, ledgerVersion: data.outcome.ledgerVersion } : false);
|
||||
}).catch(error => {
|
||||
// If transaction not in latest validated ledger, try again until max ledger is hit.
|
||||
if (error instanceof this.rippleAPI.api.errors.PendingLedgerVersionError || error instanceof this.rippleAPI.api.errors.NotFoundError) {
|
||||
console.log("Waiting for verification...");
|
||||
setTimeout(() => {
|
||||
this.verifyTransaction(txHash, minLedger, maxLedger).then(result => resolve(result));
|
||||
}, 1000);
|
||||
}
|
||||
else {
|
||||
console.log(error);
|
||||
console.log("Transaction verification failed.");
|
||||
resolve(false); // give up.
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
deserializeMemo(memo) {
|
||||
return {
|
||||
type: memo.MemoType ? hexToASCII(memo.MemoType) : null,
|
||||
format: memo.MemoFormat ? hexToASCII(memo.MemoFormat) : null,
|
||||
data: memo.MemoData ? hexToASCII(memo.MemoData) : null
|
||||
};
|
||||
}
|
||||
|
||||
subscribe() {
|
||||
// Subscribe only once. Otherwise event handlers will be duplicated.
|
||||
if (this.subscribed)
|
||||
return;
|
||||
|
||||
this.rippleAPI.api.connection.on("transaction", (data) => {
|
||||
const eventName = data.transaction.TransactionType.toLowerCase();
|
||||
// Emit the event only for successful transactions, Otherwise emit error.
|
||||
if (data.engine_result === "tesSUCCESS") {
|
||||
// Convert memo fields to ASCII before emitting the event.
|
||||
if (data.transaction.Memos)
|
||||
data.transaction.Memos = data.transaction.Memos.filter(m => m.Memo).map(m => this.deserializeMemo(m.Memo));
|
||||
this.events.emit(eventName, data.transaction);
|
||||
}
|
||||
else
|
||||
this.events.emit(eventName, null, data.engine_result_message);
|
||||
});
|
||||
|
||||
const request = {
|
||||
command: 'subscribe',
|
||||
accounts: [this.address]
|
||||
}
|
||||
const message = `Subscribed to transactions on ${this.address}`;
|
||||
|
||||
// Subscribe to transactions when api is reconnected.
|
||||
// Because API will be automatically reconnected if it's disconnected.
|
||||
this.rippleAPI.events.on(Events.RECONNECTED, (e) => {
|
||||
this.rippleAPI.api.connection.request(request);
|
||||
console.log(message);
|
||||
});
|
||||
|
||||
this.rippleAPI.api.connection.request(request);
|
||||
console.log(message);
|
||||
|
||||
this.subscribed = true;
|
||||
}
|
||||
}
|
||||
|
||||
class EncryptionHelper {
|
||||
// Offsets of the properties in the encrypted buffer.
|
||||
static ivOffset = 65;
|
||||
static macOffset = this.ivOffset + 16;
|
||||
static ciphertextOffset = this.macOffset + 32;
|
||||
static contentFormat = 'base64';
|
||||
static keyFormat = 'hex';
|
||||
|
||||
static async encrypt(publicKey, json) {
|
||||
// For the encryption library, both keys and data should be buffers.
|
||||
const encrypted = await eccrypto.encrypt(Buffer.from(publicKey, this.keyFormat), Buffer.from(JSON.stringify(json)));
|
||||
// Concat all the properties of the encrypted object to a single buffer.
|
||||
return Buffer.concat([encrypted.ephemPublicKey, encrypted.iv, encrypted.mac, encrypted.ciphertext]).toString(this.contentFormat);
|
||||
}
|
||||
|
||||
static async decrypt(privateKey, encrypted) {
|
||||
// Extract the buffer from the string and prepare encrypt object from buffer offsets for decryption.
|
||||
const encryptedBuf = Buffer.from(encrypted, this.contentFormat);
|
||||
const encryptedObj = {
|
||||
ephemPublicKey: encryptedBuf.slice(0, this.ivOffset),
|
||||
iv: encryptedBuf.slice(this.ivOffset, this.macOffset),
|
||||
mac: encryptedBuf.slice(this.macOffset, this.ciphertextOffset),
|
||||
ciphertext: encryptedBuf.slice(this.ciphertextOffset)
|
||||
}
|
||||
return JSON.parse((await eccrypto.decrypt(Buffer.from(privateKey, this.keyFormat).slice(1), encryptedObj)).toString());
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
XrplAccount,
|
||||
RippleAPIWarpper,
|
||||
EncryptionHelper,
|
||||
MemoFormats,
|
||||
MemoTypes,
|
||||
Events,
|
||||
ErrorCodes
|
||||
}
|
||||
@@ -1,21 +1,28 @@
|
||||
const fs = require('fs');
|
||||
const { exec } = require("child_process");
|
||||
const logger = require('./lib/logger');
|
||||
const { XrplAccount, RippleAPIWarpper, Events, MemoFormats, MemoTypes, ErrorCodes, EncryptionHelper } = require('./lib/ripple-handler');
|
||||
const { XrplAccount, RippleAPIWrapper, RippleAPIEvents, RippleConstants, MemoFormats, MemoTypes, ErrorCodes, EncryptionHelper } = require('evernode-js-client');
|
||||
const { SqliteDatabase, DataTypes } = require('./lib/sqlite-handler');
|
||||
|
||||
const CONFIG_PATH = 'mb-xrpl.cfg';
|
||||
const DB_PATH = 'mb-xrpl.sqlite';
|
||||
// Environment variables.
|
||||
const IS_DEV_MODE = process.env.MB_DEV === "1";
|
||||
const FILE_LOG_ENABLED = process.env.MB_FILE_LOG === "1";
|
||||
const IS_DEREGISTER = process.env.MB_DEREGISTER === "1";
|
||||
const RIPPLED_URL = process.env.MB_RIPPLED_URL || "wss://hooks-testnet.xrpl-labs.com";
|
||||
const DATA_DIR = process.env.MB_DATA_DIR || ".";
|
||||
|
||||
const CONFIG_PATH = DATA_DIR + '/mb-xrpl.cfg';
|
||||
const LOG_PATH = DATA_DIR + '/log/mb-xrpl.log';
|
||||
const DB_PATH = DATA_DIR + '/mb-xrpl.sqlite';
|
||||
const DB_TABLE_NAME = 'redeem_ops';
|
||||
const DB_UTIL_TABLE_NAME = 'util_data';
|
||||
const LAST_WATCHED_LEDGER = 'last_watched_ledger';
|
||||
const EVR_CUR_CODE = 'EVR';
|
||||
const EVR_LIMIT = 99999999;
|
||||
const REG_FEE = 5;
|
||||
const RES_FEE = 0.000001;
|
||||
const REG_FEE = '5';
|
||||
const LEDGERS_PER_MOMENT = 72;
|
||||
const REDEEM_TIMEOUT_WINDOW = 24; // Max no. of ledgers within which a redeem operation has to be served.
|
||||
const REDEEM_TIMEOUT_THRESHOLD = 0.8;
|
||||
const SASHI_CLI_PATH = IS_DEV_MODE ? "../build/sashi" : "/usr/bin/sashi";
|
||||
|
||||
const RedeemStatus = {
|
||||
REDEEMING: 'Redeeming',
|
||||
@@ -25,11 +32,9 @@ const RedeemStatus = {
|
||||
SASHI_TIMEOUT: 'SashiTimeout',
|
||||
}
|
||||
|
||||
const SASHI_CLI_PATH_DEV = "../build/sashi";
|
||||
const SASHI_CLI_PATH_PROD = "/usr/bin/sashi";
|
||||
|
||||
class MessageBoard {
|
||||
constructor(configPath, dbPath, sashiCliPath, rippleServer) {
|
||||
constructor(configPath, dbPath, sashiCliPath, rippledServer) {
|
||||
this.configPath = configPath;
|
||||
this.redeemTable = DB_TABLE_NAME;
|
||||
this.utilTable = DB_UTIL_TABLE_NAME;
|
||||
@@ -41,7 +46,7 @@ class MessageBoard {
|
||||
throw `Sashi CLI does not exist in ${sashiCliPath}.`;
|
||||
|
||||
this.sashiCli = new SashiCLI(sashiCliPath);
|
||||
this.ripplAPI = new RippleAPIWarpper(rippleServer);
|
||||
this.rippleAPI = new RippleAPIWrapper(rippledServer);
|
||||
this.db = new SqliteDatabase(dbPath);
|
||||
}
|
||||
|
||||
@@ -50,15 +55,23 @@ class MessageBoard {
|
||||
if (!this.cfg.xrpl.address || !this.cfg.xrpl.secret || !this.cfg.xrpl.token || !this.cfg.xrpl.hookAddress)
|
||||
throw "Required cfg fields cannot be empty.";
|
||||
|
||||
try { await this.ripplAPI.connect(); }
|
||||
try { await this.rippleAPI.connect(); }
|
||||
catch (e) { throw e; }
|
||||
|
||||
this.xrplAcc = new XrplAccount(this.rippleAPI, this.cfg.xrpl.address, this.cfg.xrpl.secret);
|
||||
|
||||
if (IS_DEREGISTER) {
|
||||
await this.deregisterHost();
|
||||
this.rippleAPI.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
this.db.open();
|
||||
// Create redeem table if not exist.
|
||||
await this.createRedeemTableIfNotExists();
|
||||
await this.createUtilDataTableIfNotExists();
|
||||
|
||||
this.lastValidatedLedgerSequence = await this.ripplAPI.getLedgerVersion();
|
||||
this.lastValidatedLedgerSequence = await this.rippleAPI.ledgerVersion;
|
||||
|
||||
const redeems = await this.getRedeemedRecords();
|
||||
for (const redeem of redeems)
|
||||
@@ -67,7 +80,7 @@ class MessageBoard {
|
||||
this.db.close();
|
||||
|
||||
// Check for instance expiry.
|
||||
this.ripplAPI.events.on(Events.LEDGER, async (e) => {
|
||||
this.rippleAPI.events.on(RippleAPIEvents.LEDGER, async (e) => {
|
||||
this.lastValidatedLedgerSequence = e.ledgerVersion;
|
||||
|
||||
// Filter out instances which needed to be expired and destroy them.
|
||||
@@ -91,15 +104,13 @@ class MessageBoard {
|
||||
}
|
||||
});
|
||||
|
||||
this.xrplAcc = new XrplAccount(this.ripplAPI, this.cfg.xrpl.address, this.cfg.xrpl.secret);
|
||||
|
||||
// Check whether registration fee is already payed and trustline is made.
|
||||
await this.checkForRegistration();
|
||||
|
||||
this.evernodeXrplAcc = new XrplAccount(this.ripplAPI, this.cfg.xrpl.hookAddress);
|
||||
this.evernodeXrplAcc = new XrplAccount(this.rippleAPI, this.cfg.xrpl.hookAddress);
|
||||
|
||||
// Handle the transactions on evernode account and filter out redeem operations.
|
||||
this.evernodeXrplAcc.events.on(Events.PAYMENT, async (data, error) => {
|
||||
this.evernodeXrplAcc.events.on(RippleAPIEvents.PAYMENT, async (data, error) => {
|
||||
if (error)
|
||||
console.error(error);
|
||||
else if (!data)
|
||||
@@ -179,20 +190,14 @@ class MessageBoard {
|
||||
}
|
||||
|
||||
async checkForRegistration() {
|
||||
// Create trustline with evernode account.
|
||||
if (!this.cfg.xrpl.regTrustHash) {
|
||||
const res = await this.xrplAcc.createTrustline(EVR_CUR_CODE, this.cfg.xrpl.hookAddress, EVR_LIMIT, false);
|
||||
if (res) {
|
||||
this.cfg.xrpl.regTrustHash = res.txHash;
|
||||
this.persistConfig();
|
||||
console.log(`Created ${EVR_CUR_CODE} trustline with evernode account.`);
|
||||
}
|
||||
}
|
||||
|
||||
// We assume host account already posseses some EVR balance along with EVR trust line.
|
||||
|
||||
// Make registration fee evernode account.
|
||||
if (!this.cfg.xrpl.regFeeHash) {
|
||||
const memoData = `${this.cfg.xrpl.token};${this.cfg.host.instanceSize};${this.cfg.host.location}`
|
||||
// For now we comment EVR reg fee transaction and make XRP transaction instead.
|
||||
console.log(`Making Evernode host registration payment of ${REG_FEE} ${EVR_CUR_CODE}...`)
|
||||
const res = await this.xrplAcc.makePayment(this.cfg.xrpl.hookAddress,
|
||||
REG_FEE,
|
||||
EVR_CUR_CODE,
|
||||
@@ -206,9 +211,21 @@ class MessageBoard {
|
||||
}
|
||||
}
|
||||
|
||||
async deregisterHost() {
|
||||
// Sends evernode host de-registration transaction.
|
||||
console.log(`Performing Evernode host deregistration...`);
|
||||
const res = await this.xrplAcc.makePayment(this.cfg.xrpl.hookAddress,
|
||||
RippleConstants.MIN_XRP_AMOUNT,
|
||||
"XRP",
|
||||
null,
|
||||
[{ type: MemoTypes.HOST_DEREG, format: MemoFormats.TEXT, data: "" }]);
|
||||
if (res)
|
||||
console.log('Deregistration complete.');
|
||||
}
|
||||
|
||||
async sendRedeemResponse(txHash, txPubkey, txAccount, response, encrypt = true) {
|
||||
// Verifying the pubkey.
|
||||
if (!(await this.ripplAPI.isValidAddress(txPubkey, txAccount)))
|
||||
if (!(await this.rippleAPI.isValidAddress(txPubkey, txAccount)))
|
||||
throw 'Invalid public key for encryption';
|
||||
|
||||
// Encrypt response with user pubkey.
|
||||
@@ -216,7 +233,7 @@ class MessageBoard {
|
||||
response = await EncryptionHelper.encrypt(txPubkey, response);
|
||||
|
||||
return (await this.xrplAcc.makePayment(this.cfg.xrpl.hookAddress,
|
||||
RES_FEE,
|
||||
RippleConstants.MIN_XRP_AMOUNT,
|
||||
"XRP",
|
||||
null,
|
||||
[{ type: MemoTypes.REDEEM_REF, format: MemoFormats.BINARY, data: txHash },
|
||||
@@ -305,10 +322,10 @@ class MessageBoard {
|
||||
}
|
||||
|
||||
async getMissedPaymentTransactions(lastWatchedLedger) {
|
||||
return await this.ripplAPI.api.getTransactions(this.cfg.xrpl.hookAddress, {
|
||||
return await this.rippleAPI.api.getTransactions(this.cfg.xrpl.hookAddress, {
|
||||
excludeFailures: true,
|
||||
minLedgerVersion: lastWatchedLedger,
|
||||
types: [Events.PAYMENT]
|
||||
types: [RippleAPIEvents.PAYMENT]
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -373,26 +390,16 @@ class SashiCLI {
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = process.argv;
|
||||
|
||||
// This is used for logging purposes.
|
||||
// Logs are formatted with the timestamp and a log file will be created inside log directory.
|
||||
if (args.includes('--enable-logging'))
|
||||
logger.init('log/mb-xrpl.log');
|
||||
logger.init(LOG_PATH, FILE_LOG_ENABLED);
|
||||
|
||||
if (args.length < 3)
|
||||
throw "Arguments mismatch.\n Usage: node mb-xrpl <ripple server url>";
|
||||
console.log('Starting the Evernode xrpl message board.' + (IS_DEV_MODE ? ' (in dev mode)' : ''));
|
||||
console.log('Data dir: ' + DATA_DIR);
|
||||
console.log('Rippled server: ' + RIPPLED_URL);
|
||||
console.log('Using Sashimono cli: ' + SASHI_CLI_PATH);
|
||||
|
||||
let sashiCliPath = SASHI_CLI_PATH_PROD;
|
||||
// Use sashi CLI in the build folder for dev environment.
|
||||
if (args.includes('--dev'))
|
||||
sashiCliPath = SASHI_CLI_PATH_DEV;
|
||||
|
||||
console.log('Starting the xrpl message board' + (args[3] == '--dev' ? ' (in dev mode)' : ''));
|
||||
|
||||
// Read Ripple Server Url.
|
||||
const rippleServer = args[2];
|
||||
const mb = new MessageBoard(CONFIG_PATH, DB_PATH, sashiCliPath, rippleServer);
|
||||
const mb = new MessageBoard(CONFIG_PATH, DB_PATH, SASHI_CLI_PATH, RIPPLED_URL);
|
||||
await mb.init();
|
||||
}
|
||||
|
||||
|
||||
3434
mb-xrpl/package-lock.json
generated
3434
mb-xrpl/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,10 @@
|
||||
{
|
||||
"name": "mb-xrpl",
|
||||
"scripts": {
|
||||
"build": "ncc build mb-xrpl.js -o dist/mb-xrpl"
|
||||
},
|
||||
"dependencies": {
|
||||
"eccrypto": "^1.1.6",
|
||||
"ripple-lib": "^1.9.8",
|
||||
"sqlite3": "^5.0.2"
|
||||
}
|
||||
"name": "mb-xrpl",
|
||||
"scripts": {
|
||||
"build": "ncc build mb-xrpl.js -o dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"evernode-js-client": "0.0.19-alpha",
|
||||
"sqlite3": "5.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
10
prereq.sh
10
prereq.sh
@@ -16,13 +16,13 @@ cp $originalfstab "$tmpfstab"
|
||||
backup=$originalfstab.sashi.bk
|
||||
|
||||
apt-get update
|
||||
apt install uidmap -y
|
||||
apt-get install uidmap -y
|
||||
|
||||
# Install node 14 if not exists.
|
||||
# Install nodejs 14 if not exists.
|
||||
if ! command -v node &>/dev/null; then
|
||||
apt update
|
||||
apt-get -y install ca-certificates # In case nodejs package certitficates are renewed.
|
||||
curl -sL https://deb.nodesource.com/setup_14.x | bash -
|
||||
apt -y install nodejs
|
||||
apt-get -y install nodejs
|
||||
else
|
||||
version=$(node -v)
|
||||
if [[ ! $version =~ v14\..* ]]; then
|
||||
@@ -32,7 +32,7 @@ fi
|
||||
|
||||
# Install slirp4netns if not exists (required for high performance rootless networking).
|
||||
if ! command -v slirp4netns &>/dev/null; then
|
||||
apt -y install slirp4netns
|
||||
apt-get -y install slirp4netns
|
||||
fi
|
||||
|
||||
# Check for pattern <Not starting with a comment><Not whitespace(Device)><Whitespace></><Whitespace><Not whitespace(FS type)><Whitespace><No whitespace(Options)><Whitespace><Number(Dump)><Whitespace><Number(Pass)>
|
||||
|
||||
@@ -62,11 +62,11 @@ namespace conf
|
||||
cfg.hp.init_peer_port = 22861;
|
||||
cfg.hp.init_user_port = 8081;
|
||||
|
||||
cfg.system.max_instance_count = 5;
|
||||
cfg.system.max_instance_count = 3;
|
||||
cfg.system.max_mem_kbytes = 1048576; // Total 1GB RAM
|
||||
cfg.system.max_swap_kbytes = 3145728; // Total 3GB swap.
|
||||
cfg.system.max_cpu_us = 5000000; // CPU cfs period cannot be less than 1ms (i.e. 1000) or larger than 1s (i.e. 1000000) per instance.
|
||||
cfg.system.max_storage_kbytes = 5242880; // Total 5GB
|
||||
cfg.system.max_storage_kbytes = 5242880; // Total 5GB disk storage.
|
||||
|
||||
const std::string img_prefix = registry_addr.empty() ? "hotpocketdev" : std::string(registry_addr);
|
||||
cfg.docker.images["hp.latest-ubt.20.04"] = img_prefix + "/sashimono:hp.latest-ubt.20.04";
|
||||
@@ -76,7 +76,10 @@ namespace conf
|
||||
cfg.log.max_mbytes_per_file = 10;
|
||||
cfg.log.log_level = "inf";
|
||||
cfg.log.loggers.emplace("console");
|
||||
cfg.log.loggers.emplace("file");
|
||||
|
||||
// We don't enable file logging by default because Sashimono running as a systemd service
|
||||
// would automatically log console output to journal log.
|
||||
// cfg.log.loggers.emplace("file");
|
||||
|
||||
// Save the default settings into the config file.
|
||||
if (write_config(cfg) != 0)
|
||||
|
||||
@@ -496,14 +496,6 @@ namespace hp
|
||||
|
||||
const std::string pubkey_hex = util::to_hex(pubkey);
|
||||
|
||||
// Default hp.cfg configs.
|
||||
d["node"]["history_config"]["max_primary_shards"] = 2;
|
||||
d["node"]["history_config"]["max_raw_shards"] = 2;
|
||||
d["hpfs"]["log"]["log_level"] = "err";
|
||||
d["log"]["log_level"] = "inf";
|
||||
d["log"]["max_mbytes_per_file"] = 5;
|
||||
d["log"]["max_file_count"] = 10;
|
||||
|
||||
d["node"]["public_key"] = pubkey_hex;
|
||||
d["node"]["private_key"] = util::to_hex(seckey);
|
||||
d["contract"]["id"] = contract_id;
|
||||
|
||||
3
test/vm-cluster/.gitignore
vendored
3
test/vm-cluster/.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
config.json
|
||||
hp.cfg
|
||||
contract_fs
|
||||
ledger_fs
|
||||
ledger_fs
|
||||
node_modules
|
||||
@@ -221,6 +221,12 @@ if [ $mode == "reconfig" ]; then
|
||||
if [ $nodeid = -1 ]; then
|
||||
for i in "${!hostaddrs[@]}"; do
|
||||
reconfig $i &
|
||||
|
||||
# In reinstall mode, leave a time gap between reinstall initiation to avoid host faucet wallet generation
|
||||
# overload on XRPL testnet.
|
||||
if [ ! -z $reinstall ] && [ $reinstall == "R" ]; then
|
||||
sleep 2
|
||||
fi
|
||||
done
|
||||
wait
|
||||
else
|
||||
@@ -612,17 +618,36 @@ if [ $mode == "sshu" ]; then
|
||||
|
||||
function sshwithuser() {
|
||||
hostaddr=${hostaddrs[$1]}
|
||||
execute_command=$2
|
||||
nodeno=$(expr $1 + 1)
|
||||
containername=$(echo $continfo | jq -r ".hosts.\"$hostaddr\".name")
|
||||
username=$(sshskp $sshuser@$hostaddr "sashi list | grep $containername | awk '{ print \$2 }'")
|
||||
|
||||
ssh_command="cd /home/$username/$containername ; sudo -u $username bash"
|
||||
sshskp -t $sshuser@$hostaddr $ssh_command
|
||||
user_shell="cd /home/$username/$containername ; sudo -u $username bash"
|
||||
|
||||
if [ "$execute_command" == "" ]; then
|
||||
sshskp -t $sshuser@$hostaddr $user_shell
|
||||
else
|
||||
echo "node"$n":" $(sshskp $sshuser@$hostaddr $user_shell -c "'$execute_command'")
|
||||
fi
|
||||
}
|
||||
|
||||
if [ $nodeid = -1 ]; then
|
||||
echo "Must specify node no."
|
||||
exit 1
|
||||
if [ -n "$2" ]; then
|
||||
# Interpret second arg as a command to execute on all nodes.
|
||||
command=${*:2}
|
||||
echo "Executing '$command' on user shell in all nodes..."
|
||||
for i in "${!hostaddrs[@]}"; do
|
||||
hostaddr=${hostaddrs[i]}
|
||||
let n=$i+1
|
||||
sshwithuser $n $command &
|
||||
done
|
||||
wait
|
||||
exit 0
|
||||
else
|
||||
echo "Please specify node no. or command to execute on all nodes."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
sshwithuser $nodeid
|
||||
fi
|
||||
|
||||
281
test/vm-cluster/evcluster.js
Normal file
281
test/vm-cluster/evcluster.js
Normal file
@@ -0,0 +1,281 @@
|
||||
import fetch from "node-fetch";
|
||||
import { exec } from "child_process";
|
||||
import fs from "fs";
|
||||
import evernode from "evernode-js-client";
|
||||
const { EvernodeClient, XrplAccount, RippleAPIWrapper } = evernode;
|
||||
|
||||
const REDEEM_AMOUNT = 18000; // 18000 Moments ~ 60days
|
||||
const PEER_SUBSET_SIZE = 5;
|
||||
|
||||
const configFile = "config.json";
|
||||
const config = JSON.parse(fs.readFileSync(configFile));
|
||||
const currentContract = config.contracts.filter(c => c.name === config.selected)[0];
|
||||
if (!currentContract)
|
||||
throw "Invalid contract selected.";
|
||||
|
||||
const userAddr = config.xrpl.userAddress;
|
||||
const userSecret = config.xrpl.userSecret;
|
||||
let rippleAPI = null;
|
||||
let evernodeClient = null;
|
||||
let userAcc = null;
|
||||
let shouldAbort = false;
|
||||
const hostAccounts = {};
|
||||
|
||||
async function issueRedeem(host, hostId, elem, peers, unl) {
|
||||
|
||||
if (Object.keys(elem).length > 0) {
|
||||
console.log(`Instance in host ${hostId} (${host}) already created.`)
|
||||
return [true];
|
||||
}
|
||||
|
||||
// Take a subset of peers based on host id. (Take last subset up to previous host)
|
||||
const subsetPeers = peers ? peers.slice(0, hostId - 1).slice(-PEER_SUBSET_SIZE) : null;
|
||||
|
||||
// Copy defined config from config.json to instance requirements config.
|
||||
const config = JSON.parse(JSON.stringify(currentContract.config)) || {};
|
||||
if (unl)
|
||||
config.contract = { ...config.contract, unl: unl };
|
||||
if (subsetPeers)
|
||||
config.mesh = { ...config.mesh, known_peers: subsetPeers };
|
||||
|
||||
// Redeem
|
||||
const acc = hostAccounts[host].hostAccount;
|
||||
console.log(`------Host ${hostId} (${host}): Redeeming ${acc.token}-${acc.address}...`);
|
||||
const res = await evernodeClient.redeemSubmit(acc.token, acc.address, REDEEM_AMOUNT, {
|
||||
image: currentContract.docker.image,
|
||||
contract_id: currentContract.contract_id,
|
||||
owner_pubkey: currentContract.owner_pubkey,
|
||||
config: config
|
||||
}).catch(err => console.log(err));
|
||||
|
||||
if (!res) {
|
||||
console.log(`Redeem issuing failued for host ${hostId}.`);
|
||||
return [false]
|
||||
}
|
||||
else {
|
||||
return [true, evernodeClient.watchRedeemResponse(res)]
|
||||
}
|
||||
}
|
||||
|
||||
async function processRedeemResponse(hostId, redeemOp, elem) {
|
||||
const instanceInfo = await redeemOp;
|
||||
if (instanceInfo) {
|
||||
for (var k in instanceInfo)
|
||||
elem[k] = instanceInfo[k];
|
||||
|
||||
console.log(`Created instance in host ${hostId}.`);
|
||||
saveConfig();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
console.log(`Instance creation failed in host ${hostId}.`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function createInstancesSequentially() {
|
||||
await createEvernodeConnections();
|
||||
await initHosts();
|
||||
|
||||
let peers = null, unl = null;
|
||||
|
||||
let idx = 1;
|
||||
for (const [host, elem] of Object.entries(currentContract.hosts)) {
|
||||
|
||||
if (shouldAbort)
|
||||
return;
|
||||
|
||||
const hostId = idx++;
|
||||
const [success, redeemOp] = await issueRedeem(host, hostId, elem, peers, unl);
|
||||
if (!success)
|
||||
return;
|
||||
if (redeemOp && !await processRedeemResponse(hostId, redeemOp, elem))
|
||||
return;
|
||||
|
||||
if (!unl)
|
||||
unl = [elem.pubkey]; // Insert first instance's pubkey into all other instance's unl.
|
||||
|
||||
if (!peers)
|
||||
peers = [];
|
||||
peers.push(`${host}:${elem.peer_port}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function createInstancesParallely(peerPort) {
|
||||
await createEvernodeConnections();
|
||||
await initHosts();
|
||||
|
||||
// Create first instance and then create all other instances parallely assuming they all have the same peer port.
|
||||
|
||||
let unl = null;
|
||||
const peers = Object.keys(currentContract.hosts).map(h => `${h}:${peerPort}`);
|
||||
const tasks = [];
|
||||
|
||||
let idx = 1;
|
||||
for (const [host, elem] of Object.entries(currentContract.hosts)) {
|
||||
|
||||
if (shouldAbort)
|
||||
return;
|
||||
|
||||
const hostId = idx++;
|
||||
|
||||
if (!unl) {
|
||||
const [success, redeemOp] = await issueRedeem(host, hostId, elem, peers, null);
|
||||
if (!success)
|
||||
return;
|
||||
if (redeemOp && !await processRedeemResponse(hostId, redeemOp, elem))
|
||||
return;
|
||||
|
||||
unl = [elem.pubkey]; // Insert first instance's pubkey into all other instance's unl.
|
||||
}
|
||||
else {
|
||||
const [success, redeemOp] = await issueRedeem(host, hostId, elem, peers, unl);
|
||||
if (redeemOp)
|
||||
tasks.push(processRedeemResponse(hostId, redeemOp, elem)); // Add to parallel response watcher tasks.
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(tasks);
|
||||
}
|
||||
|
||||
async function initHosts() {
|
||||
|
||||
if (Object.keys(currentContract.hosts).length == 0) {
|
||||
const ips = await getVultrHosts(currentContract.vultr_group);
|
||||
ips.forEach(ip => currentContract.hosts[ip] = {});
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
const hosts = Object.keys(currentContract.hosts);
|
||||
console.log(`${hosts.length} hosts loaded.`);
|
||||
|
||||
await Promise.all(hosts.map(host => initHostAccountData(host)))
|
||||
|
||||
for (const host of hosts) {
|
||||
const { ownsTokens, acc } = hostAccounts[host];
|
||||
if (!ownsTokens)
|
||||
await transferHostingTokens(acc.token, acc.address, acc.secret);
|
||||
}
|
||||
|
||||
console.log(`${Object.keys(hostAccounts).length} host accounts data initialized.`)
|
||||
}
|
||||
|
||||
async function initHostAccountData(host) {
|
||||
const output = await execSsh(host, "cat /etc/sashimono/mb-xrpl/mb-xrpl.cfg");
|
||||
if (!output || output.trim() === "") {
|
||||
console.log("ERROR: No output from mb-xrpl config read.")
|
||||
return;
|
||||
}
|
||||
|
||||
const conf = JSON.parse(output);
|
||||
const acc = {
|
||||
address: conf.xrpl.address,
|
||||
secret: conf.xrpl.secret,
|
||||
token: conf.xrpl.token
|
||||
}
|
||||
|
||||
// Check whether user owns hosting tokens already.
|
||||
console.log(`Checking user's ${acc.token} balance...`);
|
||||
const lines = await userAcc.getTrustLines(acc.token, acc.address);
|
||||
hostAccounts[host] = { ownsTokens: lines.length > 0, hostAccount: acc };
|
||||
}
|
||||
|
||||
// Get hosting tokens from host account to user account.
|
||||
async function transferHostingTokens(token, hostAddr, hostSecret) {
|
||||
|
||||
console.log(`Transfering ${token} to user...`);
|
||||
const trustRes = await userAcc.createTrustLine(token, hostAddr, 9999999);
|
||||
if (!trustRes)
|
||||
return false;
|
||||
|
||||
const hostAcc = new XrplAccount(rippleAPI, hostAddr, hostSecret);
|
||||
const payRes = await hostAcc.makePayment(userAddr, 9999999, token, hostAddr);
|
||||
if (!payRes)
|
||||
return false;
|
||||
|
||||
console.log(`Transfering of ${token} complete.`);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function getVultrHosts(group) {
|
||||
|
||||
return new Promise(async (resolve) => {
|
||||
|
||||
if (!group || group.trim().length === 0)
|
||||
resolve([]);
|
||||
|
||||
const resp = await fetch(`https://api.vultr.com/v2/instances?tag=${group}`, {
|
||||
method: 'GET',
|
||||
headers: { "Authorization": `Bearer ${config.vultr.api_key}` }
|
||||
});
|
||||
|
||||
const vms = (await resp.json()).instances;
|
||||
if (!vms) {
|
||||
console.log("Failed to get vultr instances.");
|
||||
resolve([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const ips = vms.sort((a, b) => (a.label < b.label) ? -1 : 1).map(i => i.main_ip);
|
||||
console.log(`${ips.length} ips retrieved from Vultr.`)
|
||||
resolve(ips);
|
||||
})
|
||||
}
|
||||
|
||||
async function createEvernodeConnections() {
|
||||
rippleAPI = new RippleAPIWrapper();
|
||||
await rippleAPI.connect();
|
||||
|
||||
evernodeClient = new EvernodeClient(userAddr, userSecret, { rippleAPI: rippleAPI });
|
||||
await evernodeClient.connect();
|
||||
|
||||
userAcc = new XrplAccount(rippleAPI, userAddr, userSecret);
|
||||
}
|
||||
|
||||
function saveConfig() {
|
||||
fs.writeFileSync(configFile, JSON.stringify(config, null, 4));
|
||||
}
|
||||
|
||||
function execSsh(host, command) {
|
||||
return new Promise(resolve => {
|
||||
const cmd = `ssh -o StrictHostKeychecking=no root@${host} ${command}`;
|
||||
exec(cmd, (err, stdout, stderr) => {
|
||||
resolve(stdout);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
async function main() {
|
||||
var args = process.argv.slice(2);
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log('Received SIGINT. Aborting...');
|
||||
shouldAbort = true;
|
||||
});
|
||||
|
||||
const mode = args[0];
|
||||
if (mode === "create") {
|
||||
if (args.length === 1) {
|
||||
await createInstancesSequentially();
|
||||
}
|
||||
else {
|
||||
console.log("Invalid args for 'create'.");
|
||||
}
|
||||
}
|
||||
else if (mode === "createall") {
|
||||
const peerPort = parseInt(args[1]);
|
||||
if (!isNaN(peerPort))
|
||||
await createInstancesParallely(peerPort);
|
||||
else
|
||||
console.log("Specify peer port for 'createall'.");
|
||||
}
|
||||
else {
|
||||
console.log("Specifiy args: create | createall <peerport>")
|
||||
}
|
||||
|
||||
if (rippleAPI)
|
||||
await rippleAPI.disconnect();
|
||||
}
|
||||
|
||||
main();
|
||||
849
test/vm-cluster/package-lock.json
generated
Normal file
849
test/vm-cluster/package-lock.json
generated
Normal file
@@ -0,0 +1,849 @@
|
||||
{
|
||||
"name": "evcluster",
|
||||
"requires": true,
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"@types/lodash": {
|
||||
"version": "4.14.175",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.175.tgz",
|
||||
"integrity": "sha512-XmdEOrKQ8a1Y/yxQFOMbC47G/V2VDO1GvMRnl4O75M4GW/abC5tnfzadQYkqEveqRM1dEJGFFegfPNA2vvx2iw=="
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "16.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.3.tgz",
|
||||
"integrity": "sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ=="
|
||||
},
|
||||
"@types/ws": {
|
||||
"version": "7.4.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz",
|
||||
"integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"acorn": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz",
|
||||
"integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg=="
|
||||
},
|
||||
"agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"requires": {
|
||||
"debug": "4"
|
||||
}
|
||||
},
|
||||
"assert": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz",
|
||||
"integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==",
|
||||
"requires": {
|
||||
"es6-object-assign": "^1.1.0",
|
||||
"is-nan": "^1.2.1",
|
||||
"object-is": "^1.0.1",
|
||||
"util": "^0.12.0"
|
||||
}
|
||||
},
|
||||
"available-typed-arrays": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
|
||||
"integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw=="
|
||||
},
|
||||
"base-x": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz",
|
||||
"integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==",
|
||||
"requires": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
|
||||
},
|
||||
"big-integer": {
|
||||
"version": "1.6.49",
|
||||
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.49.tgz",
|
||||
"integrity": "sha512-KJ7VhqH+f/BOt9a3yMwJNmcZjG53ijWMTjSAGMveQWyLwqIiwkjNP5PFgDob3Snnx86SjDj6I89fIbv0dkQeNw=="
|
||||
},
|
||||
"bignumber.js": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz",
|
||||
"integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA=="
|
||||
},
|
||||
"bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"bip66": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz",
|
||||
"integrity": "sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"bn.js": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||
},
|
||||
"brorand": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
|
||||
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
|
||||
},
|
||||
"browserify-aes": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
|
||||
"integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"buffer-xor": "^1.0.3",
|
||||
"cipher-base": "^1.0.0",
|
||||
"create-hash": "^1.1.0",
|
||||
"evp_bytestokey": "^1.0.3",
|
||||
"inherits": "^2.0.1",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
|
||||
"integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
|
||||
"requires": {
|
||||
"base64-js": "^1.0.2",
|
||||
"ieee754": "^1.1.4"
|
||||
}
|
||||
},
|
||||
"buffer-xor": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
|
||||
"integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
|
||||
"optional": true
|
||||
},
|
||||
"call-bind": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1",
|
||||
"get-intrinsic": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"cipher-base": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
|
||||
"integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.1",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"create-hash": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
|
||||
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
|
||||
"requires": {
|
||||
"cipher-base": "^1.0.1",
|
||||
"inherits": "^2.0.1",
|
||||
"md5.js": "^1.3.4",
|
||||
"ripemd160": "^2.0.1",
|
||||
"sha.js": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"create-hmac": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
|
||||
"integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"cipher-base": "^1.0.3",
|
||||
"create-hash": "^1.1.0",
|
||||
"inherits": "^2.0.1",
|
||||
"ripemd160": "^2.0.0",
|
||||
"safe-buffer": "^5.0.1",
|
||||
"sha.js": "^2.4.8"
|
||||
}
|
||||
},
|
||||
"data-uri-to-buffer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz",
|
||||
"integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
|
||||
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"decimal.js": {
|
||||
"version": "10.3.1",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz",
|
||||
"integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ=="
|
||||
},
|
||||
"define-properties": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
|
||||
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
|
||||
"requires": {
|
||||
"object-keys": "^1.0.12"
|
||||
}
|
||||
},
|
||||
"drbg.js": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz",
|
||||
"integrity": "sha1-Pja2xCs3BDgjzbwzLVjzHiRFSAs=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"browserify-aes": "^1.0.6",
|
||||
"create-hash": "^1.1.2",
|
||||
"create-hmac": "^1.1.4"
|
||||
}
|
||||
},
|
||||
"eccrypto": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/eccrypto/-/eccrypto-1.1.6.tgz",
|
||||
"integrity": "sha512-d78ivVEzu7Tn0ZphUUaL43+jVPKTMPFGtmgtz1D0LrFn7cY3K8CdrvibuLz2AAkHBLKZtR8DMbB2ukRYFk987A==",
|
||||
"requires": {
|
||||
"acorn": "7.1.1",
|
||||
"elliptic": "6.5.4",
|
||||
"es6-promise": "4.2.8",
|
||||
"nan": "2.14.0",
|
||||
"secp256k1": "3.7.1"
|
||||
}
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "6.5.4",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
|
||||
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
|
||||
"requires": {
|
||||
"bn.js": "^4.11.9",
|
||||
"brorand": "^1.1.0",
|
||||
"hash.js": "^1.0.0",
|
||||
"hmac-drbg": "^1.0.1",
|
||||
"inherits": "^2.0.4",
|
||||
"minimalistic-assert": "^1.0.1",
|
||||
"minimalistic-crypto-utils": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"es-abstract": {
|
||||
"version": "1.19.1",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz",
|
||||
"integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"es-to-primitive": "^1.2.1",
|
||||
"function-bind": "^1.1.1",
|
||||
"get-intrinsic": "^1.1.1",
|
||||
"get-symbol-description": "^1.0.0",
|
||||
"has": "^1.0.3",
|
||||
"has-symbols": "^1.0.2",
|
||||
"internal-slot": "^1.0.3",
|
||||
"is-callable": "^1.2.4",
|
||||
"is-negative-zero": "^2.0.1",
|
||||
"is-regex": "^1.1.4",
|
||||
"is-shared-array-buffer": "^1.0.1",
|
||||
"is-string": "^1.0.7",
|
||||
"is-weakref": "^1.0.1",
|
||||
"object-inspect": "^1.11.0",
|
||||
"object-keys": "^1.1.1",
|
||||
"object.assign": "^4.1.2",
|
||||
"string.prototype.trimend": "^1.0.4",
|
||||
"string.prototype.trimstart": "^1.0.4",
|
||||
"unbox-primitive": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"es-to-primitive": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
|
||||
"integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
|
||||
"requires": {
|
||||
"is-callable": "^1.1.4",
|
||||
"is-date-object": "^1.0.1",
|
||||
"is-symbol": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"es6-object-assign": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz",
|
||||
"integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw="
|
||||
},
|
||||
"es6-promise": {
|
||||
"version": "4.2.8",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
|
||||
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
|
||||
},
|
||||
"evernode-js-client": {
|
||||
"version": "0.0.19-alpha",
|
||||
"resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.0.19-alpha.tgz",
|
||||
"integrity": "sha512-0ErIuURPkYdt3eOhy8RV2H5+OrLtcrWS0+ARhSadQvkM6svQRd4bsS9Scvp6gikRjcZ6qPPSDP1IUVTnJkFkSw==",
|
||||
"requires": {
|
||||
"eccrypto": "1.1.6",
|
||||
"ripple-lib": "1.9.8"
|
||||
}
|
||||
},
|
||||
"evp_bytestokey": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
|
||||
"integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"md5.js": "^1.3.4",
|
||||
"safe-buffer": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"fetch-blob": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.2.tgz",
|
||||
"integrity": "sha512-hunJbvy/6OLjCD0uuhLdp0mMPzP/yd2ssd1t2FCJsaA7wkWhpbp9xfuNVpv7Ll4jFhzp6T4LAupSiV9uOeg0VQ==",
|
||||
"requires": {
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
}
|
||||
},
|
||||
"file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||
"optional": true
|
||||
},
|
||||
"foreach": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
|
||||
"integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k="
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||
},
|
||||
"get-intrinsic": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
|
||||
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1",
|
||||
"has": "^1.0.3",
|
||||
"has-symbols": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"get-symbol-description": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
|
||||
"integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"get-intrinsic": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"has-bigints": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz",
|
||||
"integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA=="
|
||||
},
|
||||
"has-symbols": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
|
||||
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
|
||||
},
|
||||
"has-tostringtag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
|
||||
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
|
||||
"requires": {
|
||||
"has-symbols": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"hash-base": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
|
||||
"integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.6.0",
|
||||
"safe-buffer": "^5.2.0"
|
||||
}
|
||||
},
|
||||
"hash.js": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
|
||||
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"minimalistic-assert": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"hmac-drbg": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
|
||||
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
|
||||
"requires": {
|
||||
"hash.js": "^1.0.3",
|
||||
"minimalistic-assert": "^1.0.0",
|
||||
"minimalistic-crypto-utils": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"https-proxy-agent": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
|
||||
"integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
|
||||
"requires": {
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
}
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"internal-slot": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz",
|
||||
"integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==",
|
||||
"requires": {
|
||||
"get-intrinsic": "^1.1.0",
|
||||
"has": "^1.0.3",
|
||||
"side-channel": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"is-arguments": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
|
||||
"integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"has-tostringtag": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"is-bigint": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
|
||||
"integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
|
||||
"requires": {
|
||||
"has-bigints": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"is-boolean-object": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
|
||||
"integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"has-tostringtag": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"is-callable": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz",
|
||||
"integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w=="
|
||||
},
|
||||
"is-date-object": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
|
||||
"integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
|
||||
"requires": {
|
||||
"has-tostringtag": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"is-generator-function": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
|
||||
"integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==",
|
||||
"requires": {
|
||||
"has-tostringtag": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"is-nan": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz",
|
||||
"integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.0",
|
||||
"define-properties": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"is-negative-zero": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz",
|
||||
"integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w=="
|
||||
},
|
||||
"is-number-object": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz",
|
||||
"integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==",
|
||||
"requires": {
|
||||
"has-tostringtag": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"is-regex": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
|
||||
"integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"has-tostringtag": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"is-shared-array-buffer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz",
|
||||
"integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA=="
|
||||
},
|
||||
"is-string": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
|
||||
"integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
|
||||
"requires": {
|
||||
"has-tostringtag": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"is-symbol": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
|
||||
"integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
|
||||
"requires": {
|
||||
"has-symbols": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"is-typed-array": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.8.tgz",
|
||||
"integrity": "sha512-HqH41TNZq2fgtGT8WHVFVJhBVGuY3AnP3Q36K8JKXUxSxRgk/d+7NjmwG2vo2mYmXK8UYZKu0qH8bVP5gEisjA==",
|
||||
"requires": {
|
||||
"available-typed-arrays": "^1.0.5",
|
||||
"call-bind": "^1.0.2",
|
||||
"es-abstract": "^1.18.5",
|
||||
"foreach": "^2.0.5",
|
||||
"has-tostringtag": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"is-weakref": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz",
|
||||
"integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"jsonschema": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.2.tgz",
|
||||
"integrity": "sha512-iX5OFQ6yx9NgbHCwse51ohhKgLuLL7Z5cNOeZOPIlDUtAMrxlruHLzVZxbltdHE5mEDXN+75oFOwq6Gn0MZwsA=="
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
"integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
|
||||
"requires": {
|
||||
"hash-base": "^3.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
"safe-buffer": "^5.1.2"
|
||||
}
|
||||
},
|
||||
"minimalistic-assert": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
|
||||
},
|
||||
"minimalistic-crypto-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
|
||||
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.14.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
|
||||
"integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.0.0.tgz",
|
||||
"integrity": "sha512-bKMI+C7/T/SPU1lKnbQbwxptpCrG9ashG+VkytmXCPZyuM9jB6VU+hY0oi4lC8LxTtAeWdckNCTa3nrGsAdA3Q==",
|
||||
"requires": {
|
||||
"data-uri-to-buffer": "^3.0.1",
|
||||
"fetch-blob": "^3.1.2"
|
||||
}
|
||||
},
|
||||
"object-inspect": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz",
|
||||
"integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg=="
|
||||
},
|
||||
"object-is": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
|
||||
"integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"object-keys": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
|
||||
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
|
||||
},
|
||||
"object.assign": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
|
||||
"integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.0",
|
||||
"define-properties": "^1.1.3",
|
||||
"has-symbols": "^1.0.1",
|
||||
"object-keys": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"ripemd160": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
|
||||
"integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
|
||||
"requires": {
|
||||
"hash-base": "^3.0.0",
|
||||
"inherits": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"ripple-address-codec": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-4.1.3.tgz",
|
||||
"integrity": "sha512-9mymOhfCUyLZGwotGPg3I2wMfrwHof0W8ygjhW46UdNgFW6J+OvDB/VS9dbHlgED/41mzECp41IXvTrkc1fTwA==",
|
||||
"requires": {
|
||||
"base-x": "3.0.8",
|
||||
"create-hash": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"ripple-binary-codec": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ripple-binary-codec/-/ripple-binary-codec-1.1.3.tgz",
|
||||
"integrity": "sha512-NnFNZZ+225BxdDdHtcEn4GiGzup+V0DGAbtKygZIwbqA5116oZBt6uY3g43gYpdDMISsEbM7NewBij8+7jdlvA==",
|
||||
"requires": {
|
||||
"assert": "^2.0.0",
|
||||
"big-integer": "^1.6.48",
|
||||
"buffer": "5.6.0",
|
||||
"create-hash": "^1.2.0",
|
||||
"decimal.js": "^10.2.0",
|
||||
"ripple-address-codec": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"ripple-keypairs": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-1.0.3.tgz",
|
||||
"integrity": "sha512-Na5q8sUdxjd5DXBM88ocJgL2Ig0I1USyO3bvI0SMxJPp7F9DHvqLdPX45PVXs7HUq0Dj691Z9Uz9NeD/K7/eOA==",
|
||||
"requires": {
|
||||
"bn.js": "^5.1.1",
|
||||
"brorand": "^1.0.5",
|
||||
"elliptic": "^6.5.2",
|
||||
"hash.js": "^1.0.3",
|
||||
"ripple-address-codec": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"bn.js": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz",
|
||||
"integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"ripple-lib": {
|
||||
"version": "1.9.8",
|
||||
"resolved": "https://registry.npmjs.org/ripple-lib/-/ripple-lib-1.9.8.tgz",
|
||||
"integrity": "sha512-86wAP4GD6ZVMHIZeqrzn9m8BZSWSpMr06ULaAKNZbN6mxvh7SwcabJjcINRZNpI73Sb3oibWoOsCQ+Vf64WSTA==",
|
||||
"requires": {
|
||||
"@types/lodash": "^4.14.136",
|
||||
"@types/ws": "^7.2.0",
|
||||
"bignumber.js": "^9.0.0",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"jsonschema": "1.2.2",
|
||||
"lodash": "^4.17.4",
|
||||
"ripple-address-codec": "^4.1.1",
|
||||
"ripple-binary-codec": "^1.1.3",
|
||||
"ripple-keypairs": "^1.0.3",
|
||||
"ripple-lib-transactionparser": "0.8.2",
|
||||
"ws": "^7.2.0"
|
||||
}
|
||||
},
|
||||
"ripple-lib-transactionparser": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.8.2.tgz",
|
||||
"integrity": "sha512-1teosQLjYHLyOQrKUQfYyMjDR3MAq/Ga+MJuLUfpBMypl4LZB4bEoMcmG99/+WVTEiZOezJmH9iCSvm/MyxD+g==",
|
||||
"requires": {
|
||||
"bignumber.js": "^9.0.0",
|
||||
"lodash": "^4.17.15"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
|
||||
},
|
||||
"secp256k1": {
|
||||
"version": "3.7.1",
|
||||
"resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.7.1.tgz",
|
||||
"integrity": "sha512-1cf8sbnRreXrQFdH6qsg2H71Xw91fCCS9Yp021GnUNJzWJS/py96fS4lHbnTnouLp08Xj6jBoBB6V78Tdbdu5g==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"bindings": "^1.5.0",
|
||||
"bip66": "^1.1.5",
|
||||
"bn.js": "^4.11.8",
|
||||
"create-hash": "^1.2.0",
|
||||
"drbg.js": "^1.0.1",
|
||||
"elliptic": "^6.4.1",
|
||||
"nan": "^2.14.0",
|
||||
"safe-buffer": "^5.1.2"
|
||||
}
|
||||
},
|
||||
"sha.js": {
|
||||
"version": "2.4.11",
|
||||
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
|
||||
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.1",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"side-channel": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.0",
|
||||
"get-intrinsic": "^1.0.2",
|
||||
"object-inspect": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"string.prototype.trimend": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
|
||||
"integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"string.prototype.trimstart": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz",
|
||||
"integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"requires": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"unbox-primitive": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
|
||||
"integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==",
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1",
|
||||
"has-bigints": "^1.0.1",
|
||||
"has-symbols": "^1.0.2",
|
||||
"which-boxed-primitive": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"util": {
|
||||
"version": "0.12.4",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz",
|
||||
"integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"is-arguments": "^1.0.4",
|
||||
"is-generator-function": "^1.0.7",
|
||||
"is-typed-array": "^1.1.3",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"which-typed-array": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"web-streams-polyfill": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.1.1.tgz",
|
||||
"integrity": "sha512-Czi3fG883e96T4DLEPRvufrF2ydhOOW1+1a6c3gNjH2aIh50DNFBdfwh2AKoOf1rXvpvavAoA11Qdq9+BKjE0Q=="
|
||||
},
|
||||
"which-boxed-primitive": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
|
||||
"integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
|
||||
"requires": {
|
||||
"is-bigint": "^1.0.1",
|
||||
"is-boolean-object": "^1.1.0",
|
||||
"is-number-object": "^1.0.4",
|
||||
"is-string": "^1.0.5",
|
||||
"is-symbol": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"which-typed-array": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.7.tgz",
|
||||
"integrity": "sha512-vjxaB4nfDqwKI0ws7wZpxIlde1XrLX5uB0ZjpfshgmapJMD7jJWhZI+yToJTqaFByF0eNBcYxbjmCzoRP7CfEw==",
|
||||
"requires": {
|
||||
"available-typed-arrays": "^1.0.5",
|
||||
"call-bind": "^1.0.2",
|
||||
"es-abstract": "^1.18.5",
|
||||
"foreach": "^2.0.5",
|
||||
"has-tostringtag": "^1.0.0",
|
||||
"is-typed-array": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"ws": {
|
||||
"version": "7.5.5",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz",
|
||||
"integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w=="
|
||||
}
|
||||
}
|
||||
}
|
||||
8
test/vm-cluster/package.json
Normal file
8
test/vm-cluster/package.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "evcluster",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"evernode-js-client": "0.0.19-alpha",
|
||||
"node-fetch": "3.0.0"
|
||||
}
|
||||
}
|
||||
@@ -3,39 +3,44 @@
|
||||
# This must be executed with root privileges.
|
||||
|
||||
# This script performs following activities.
|
||||
# 1. Install Sashimono prerequisites and reboots.
|
||||
# 2. Install Sashimono after reboot.
|
||||
# 3. Restarts cgroup engine service after installing Sashimono.
|
||||
# 1. Install debugging tools.
|
||||
# 2. Install Sashimono prerequisites and reboots.
|
||||
# 3. Install Sashimono after reboot.
|
||||
# 4. Reboot after installing Sashimono.
|
||||
|
||||
# Install debugging tools
|
||||
# sqlite is used to fetch lcl from cluster script
|
||||
# smem is a memory reporting tool used to analyze instance users memory usage.
|
||||
apt-get -y install sqlite3 smem
|
||||
|
||||
cloudstorage="https://sthotpocket.blob.core.windows.net/sashimono"
|
||||
bootscript="/usr/local/bin/sashimono-install.sh"
|
||||
cgrulesengd_service="cgrulesengdsvc"
|
||||
boot_service="sashimonoboot"
|
||||
|
||||
# Create boot script.
|
||||
echo "#!/bin/bash
|
||||
|
||||
# Install Sashimono and restart cgrules service.
|
||||
curl -fsSL $cloudstorage/install.sh | bash
|
||||
echo Restarting $cgrulesengd_service
|
||||
systemctl restart $cgrulesengd_service
|
||||
|
||||
# Remove ourselves.
|
||||
rm $bootscript
|
||||
systemctl disable $boot_service
|
||||
rm /etc/systemd/system/$boot_service.service
|
||||
systemctl daemon-reload" >$bootscript
|
||||
systemctl daemon-reload
|
||||
|
||||
# Install Sashimono and reboot.
|
||||
curl -fsSL $cloudstorage/install.sh | bash -s -- -q
|
||||
reboot" >$bootscript
|
||||
|
||||
chmod +x $bootscript
|
||||
|
||||
echo "[Unit]
|
||||
Description=Sashimono test vm setup one-time boot script
|
||||
ConditionPathExists=$bootscript
|
||||
[Service]
|
||||
ExecStart=$bootscript
|
||||
ExecStart=$bootscript -q
|
||||
[Install]
|
||||
WantedBy=multi-user.target" >/etc/systemd/system/$boot_service.service
|
||||
# Schedule the boot script to run at startup.
|
||||
systemctl enable $boot_service
|
||||
|
||||
# Install Sashimono prerequisites and reboot.
|
||||
curl -fsSL $cloudstorage/prereq.sh | bash -s -- -q
|
||||
curl -fsSL $cloudstorage/prereq.sh | bash -s -- -q
|
||||
Reference in New Issue
Block a user