diff --git a/AgamaApp-windows.md b/AgamaApp-windows.md new file mode 100644 index 0000000..b5c3a21 --- /dev/null +++ b/AgamaApp-windows.md @@ -0,0 +1,12 @@ +# Agama application build summary is here: + +Platform: windows + +Version: 0.2.0.1a-beta + +Date: Mon Jun 12 11:57:21 CEST 2017 + + +Download link: +* [https://f001.backblazeb2.com/file/supernet/files/AgamaApp-0.2.0.1a-beta-windows-installer.zip](https://f001.backblazeb2.com/file/supernet/files/AgamaApp-0.2.0.1a-beta-windows-installer.zip) +* [checksum](https://f001.backblazeb2.com/file/supernet/files/AgamaApp-0.2.0.1a-beta-windows.checksum) diff --git a/README.md b/README.md index dafcb25..dc0a57e 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,20 @@ You must have `node.js` and `npm` installed on your machine. Clone Agama Desktop App with EasyDEX-GUI submodule ```shell -git clone --recursive https://github.com/SuperNETorg/Agama.git -cd gui/EasyDEX-GUI/ -git checkout master -git pull -cd ../../ +1) git clone https://github.com/supernetorg/agama --recursive --branch pkg_automation_electrum --single-branch +with this command you git clone agama - but explicitly just the pkg_automation_electrum branch (therefore --single-branch) which we also use for the release packages. +2) cd agama && cd gui/EasyDEX-GUI/ +3) git checkout electrum && git pull origin electrum +4) npm install && npm install webpack +5) ./binary_artifacts.sh +6) npm start in projects' root folder +7) cd gui/EasyDEX-GUI/react/src +8) npm start +8) toggle dev and debug options in settings +9) restart the app +10) sync komodod and/or asset chains + +You're ready to dev ``` Install Agama App @@ -25,34 +34,6 @@ npm start ``` ### Important dev notes -#### Use the following config.json for dev -Place it in ./iguana folder. -``` -{ -"edexGuiOnly": true, -"iguanaGuiOnly": false, -"manualIguanaStart": false, -"skipBasiliskNetworkCheck": true, -"minNotaries": 8, -"host": "127.0.0.1", -"agamaPort": 17777, -"iguanaCorePort": 7778, -"maxDescriptors": { -"darwin": 90000, -"linux": 1000000 -}, -"killIguanaOnStart": true, -"dev": true, -"v2": true, -"useBasiliskInstance": true, -"debug": true, -"iguanaAppPort": 17777, -"forks": { -"basilisk": false, -"all": false -} -} -``` #### Sockets.io In dev mode backend is configured to send/receive messages from/to http://127.0.0.1:3000 address. If you open it as http://localhost:3000 sockets server will reject any messages. diff --git a/assets/bin/linux64/iguana b/assets/bin/linux64/iguana deleted file mode 100755 index d415468..0000000 Binary files a/assets/bin/linux64/iguana and /dev/null differ diff --git a/assets/bin/linux64/komodo-cli b/assets/bin/linux64/komodo-cli deleted file mode 100755 index c374e86..0000000 Binary files a/assets/bin/linux64/komodo-cli and /dev/null differ diff --git a/assets/bin/linux64/komodod b/assets/bin/linux64/komodod deleted file mode 100755 index c7f7816..0000000 Binary files a/assets/bin/linux64/komodod and /dev/null differ diff --git a/assets/bin/osx/iguana b/assets/bin/osx/iguana deleted file mode 100755 index 8fec2b9..0000000 Binary files a/assets/bin/osx/iguana and /dev/null differ diff --git a/assets/bin/osx/komodo-cli b/assets/bin/osx/komodo-cli deleted file mode 100755 index f54422a..0000000 Binary files a/assets/bin/osx/komodo-cli and /dev/null differ diff --git a/assets/bin/osx/komodod b/assets/bin/osx/komodod deleted file mode 100755 index dd61c7e..0000000 Binary files a/assets/bin/osx/komodod and /dev/null differ diff --git a/assets/bin/osx/libgcc_s.1.dylib b/assets/bin/osx/libgcc_s.1.dylib deleted file mode 100644 index 91345fc..0000000 Binary files a/assets/bin/osx/libgcc_s.1.dylib and /dev/null differ diff --git a/assets/bin/osx/libgomp.1.dylib b/assets/bin/osx/libgomp.1.dylib deleted file mode 100644 index 5facb01..0000000 Binary files a/assets/bin/osx/libgomp.1.dylib and /dev/null differ diff --git a/assets/bin/osx/libnanomsg.5.0.0.dylib b/assets/bin/osx/libnanomsg.5.0.0.dylib deleted file mode 100644 index 42ab613..0000000 Binary files a/assets/bin/osx/libnanomsg.5.0.0.dylib and /dev/null differ diff --git a/assets/bin/osx/libstdc++.6.dylib b/assets/bin/osx/libstdc++.6.dylib deleted file mode 100644 index 9175aa5..0000000 Binary files a/assets/bin/osx/libstdc++.6.dylib and /dev/null differ diff --git a/assets/bin/win64/genkmdconf.bat b/assets/bin/win64/genkmdconf.bat deleted file mode 100644 index 6ffc9e9..0000000 --- a/assets/bin/win64/genkmdconf.bat +++ /dev/null @@ -1,19 +0,0 @@ -mkdir %AppData%\Komodo - -@echo off - -IF NOT EXIST %AppData%\Komodo\komodo.conf ( - ( - echo rpcuser=kmdusr%random%%random% - echo rpcpassword=kmdpass%random%%random% - echo rpcbind=127.0.0.1 - echo txindex=1 - echo server=1 - echo addnode=5.9.102.210 - echo addnode=78.47.196.146 - echo addnode=178.63.69.164 - echo addnode=88.198.65.74 - echo addnode=5.9.122.241 - echo addnode=144.76.94.38 - ) > %AppData%\Komodo\komodo.conf -) \ No newline at end of file diff --git a/assets/bin/win64/iguana.exe b/assets/bin/win64/iguana.exe deleted file mode 100644 index 375a6f9..0000000 Binary files a/assets/bin/win64/iguana.exe and /dev/null differ diff --git a/assets/bin/win64/komodo-cli.exe b/assets/bin/win64/komodo-cli.exe deleted file mode 100644 index 81e8d9d..0000000 Binary files a/assets/bin/win64/komodo-cli.exe and /dev/null differ diff --git a/assets/bin/win64/komodod.exe b/assets/bin/win64/komodod.exe deleted file mode 100644 index e11a760..0000000 Binary files a/assets/bin/win64/komodod.exe and /dev/null differ diff --git a/assets/bin/win64/libcrypto-1_1.dll b/assets/bin/win64/libcrypto-1_1.dll deleted file mode 100755 index 4b16b67..0000000 Binary files a/assets/bin/win64/libcrypto-1_1.dll and /dev/null differ diff --git a/assets/bin/win64/libcurl-4.dll b/assets/bin/win64/libcurl-4.dll deleted file mode 100755 index 6a73485..0000000 Binary files a/assets/bin/win64/libcurl-4.dll and /dev/null differ diff --git a/assets/bin/win64/libcurl.dll b/assets/bin/win64/libcurl.dll deleted file mode 100755 index 8c80e23..0000000 Binary files a/assets/bin/win64/libcurl.dll and /dev/null differ diff --git a/assets/bin/win64/libgcc_s_sjlj-1.dll b/assets/bin/win64/libgcc_s_sjlj-1.dll deleted file mode 100755 index 5f5b6a7..0000000 Binary files a/assets/bin/win64/libgcc_s_sjlj-1.dll and /dev/null differ diff --git a/assets/bin/win64/libnanomsg.dll b/assets/bin/win64/libnanomsg.dll deleted file mode 100755 index 5d5dc7e..0000000 Binary files a/assets/bin/win64/libnanomsg.dll and /dev/null differ diff --git a/assets/bin/win64/libssl-1_1.dll b/assets/bin/win64/libssl-1_1.dll deleted file mode 100755 index dee3878..0000000 Binary files a/assets/bin/win64/libssl-1_1.dll and /dev/null differ diff --git a/assets/bin/win64/libwinpthread-1.dll b/assets/bin/win64/libwinpthread-1.dll deleted file mode 100755 index 67b9289..0000000 Binary files a/assets/bin/win64/libwinpthread-1.dll and /dev/null differ diff --git a/assets/bin/win64/nanomsg.dll b/assets/bin/win64/nanomsg.dll deleted file mode 100644 index 5d5dc7e..0000000 Binary files a/assets/bin/win64/nanomsg.dll and /dev/null differ diff --git a/assets/bin/win64/pthreadvc2.dll b/assets/bin/win64/pthreadvc2.dll deleted file mode 100644 index 93f562b..0000000 Binary files a/assets/bin/win64/pthreadvc2.dll and /dev/null differ diff --git a/binary_artifacts.sh b/binary_artifacts.sh index 569b657..e1be581 100755 --- a/binary_artifacts.sh +++ b/binary_artifacts.sh @@ -1,58 +1,54 @@ echo Refreshing binaries from artifacts.supernet.org echo ========================================= echo Step: Removing old binaries -mkdir -p build -cd build -rm -rvf artifacts.supernet.org +pwd +[ ! -d assets ] && \ + mkdir -p assets +cd assets +[ -d artifacts.supernet.org ] && \ + echo Removing old artifacts. && \ + rm -rvf artifacts.supernet.org echo echo Step: Cloning latest binaries for build wget --recursive --no-parent https://artifacts.supernet.org/latest/ -chmod -R +x artifacts.supernet.org/latest/ cd .. echo ========================================= -echo -echo ========================================= -echo Step: Moving osx binaries from artifacts to assets/bin/osx/ -echo -mv -fv build/artifacts.supernet.org/latest/osx/iguana assets/bin/osx/ -mv -fv build/artifacts.supernet.org/latest/osx/komodo-cli assets/bin/osx/ -mv -fv build/artifacts.supernet.org/latest/osx/komodod assets/bin/osx/ -mv -fv build/artifacts.supernet.org/latest/osx/libgcc_s.1.dylib assets/bin/osx/ -mv -fv build/artifacts.supernet.org/latest/osx/libgomp.1.dylib assets/bin/osx/ -mv -fv build/artifacts.supernet.org/latest/osx/libnanomsg.5.0.0.dylib assets/bin/osx/ -mv -fv build/artifacts.supernet.org/latest/osx/libstdc++.6.dylib assets/bin/osx/ -echo +echo +pwd echo ========================================= -echo Step: Moving Win64 binaries from artifacts to assets/bin/win64/ -echo -mv -fv build/artifacts.supernet.org/latest/windows/genkmdconf.bat assets/bin/win64/ -mv -fv build/artifacts.supernet.org/latest/windows/iguana.exe assets/bin/win64/ -mv -fv build/artifacts.supernet.org/latest/windows/index.html assets/bin/win64/ -mv -fv build/artifacts.supernet.org/latest/windows/komodo-cli.exe assets/bin/win64/ -mv -fv build/artifacts.supernet.org/latest/windows/komodo-tx.exe assets/bin/win64/ -mv -fv build/artifacts.supernet.org/latest/windows/komodod.exe assets/bin/win64/ -mv -fv build/artifacts.supernet.org/latest/windows/libcrypto-1_1.dll assets/bin/win64/ -mv -fv build/artifacts.supernet.org/latest/windows/libcurl-4.dll assets/bin/win64/ -mv -fv build/artifacts.supernet.org/latest/windows/libcurl.dll assets/bin/win64/ -mv -fv build/artifacts.supernet.org/latest/windows/libgcc_s_sjlj-1.dll assets/bin/win64/ -mv -fv build/artifacts.supernet.org/latest/windows/libnanomsg.dll assets/bin/win64/ -mv -fv build/artifacts.supernet.org/latest/windows/libssl-1_1.dll assets/bin/win64/ -mv -fv build/artifacts.supernet.org/latest/windows/libwinpthread-1.dll assets/bin/win64/ -mv -fv build/artifacts.supernet.org/latest/windows/nanomsg.dll assets/bin/win64/ -mv -fv build/artifacts.supernet.org/latest/windows/pthreadvc2.dll assets/bin/win64/ -echo +echo Step: Permission +x for OSX binaries from artifacts to assets/bin/osx/ +echo +rm assets/artifacts.supernet.org/latest/osx/iguana +chmod +x assets/artifacts.supernet.org/latest/osx/komodo* + +mkdir assets/bin +mv assets/artifacts.supernet.org/latest/osx assets/bin/osx + +echo Moving legacy libs to assets/bin +wget https://supernetorg.bintray.com/misc/libs_legacy_osx.zip +checksum=`shasum -a 256 libs_legacy_osx.zip | awk '{ print $1 }'` +if [ "$checksum" = "e9474aa243694a2d4c87fccc443e4b16a9a5172a24da76af9e5ecddd006649bb" ]; then + echo "Checksum is correct." + unzip libs_legacy_osx.zip + cp -rvf libs_legacy_osx/* assets/bin/osx/. + else + echo "Checksum is incorrect!" + exit 0 +fi echo ========================================= -echo Step: Moving linux64 binaries from artifacts to assets/bin/linux64 -echo -mv -fv build/artifacts.supernet.org/latest/linux/iguana assets/bin/linux64/ -mv -fv build/artifacts.supernet.org/latest/linux/komodo-cli assets/bin/linux64/ -mv -fv build/artifacts.supernet.org/latest/linux/komodod assets/bin/linux64/ -echo +echo Step: Moving Windows binaries from artifacts to assets/bin/win64/ +#echo +rm assets/artifacts.supernet.org/latest/windows/iguana +mv assets/artifacts.supernet.org/latest/windows assets/bin/win64 +echo echo ========================================= -echo Step: Cleaning artifacts data -echo -rm -rf build/ -echo +echo Step: Permissions +x for linux64 binaries from artifacts to assets/bin/linux64 +echo +rm assets/artifacts.supernet.org/latest/linux/iguana +chmod +x assets/artifacts.supernet.org/latest/linux/komodo* +echo Moving Linux bins to assets/bin +mv assets/artifacts.supernet.org/latest/linux assets/bin/linux64/ +echo echo ========================================= echo Step: Finished Updating binaries from artifacts -echo \ No newline at end of file +echo \ No newline at end of file diff --git a/buildscripts/easydex-build.sh b/buildscripts/easydex-build.sh index 05df38a..164e21a 100755 --- a/buildscripts/easydex-build.sh +++ b/buildscripts/easydex-build.sh @@ -9,13 +9,15 @@ echo "Building EasyDEX-GUI" echo "Actual directory is: ${PWD}" echo "Checkout to redux branch." -git checkout redux -git pull origin redux +git checkout electrum +git pull origin electrum +[ -d react ] && cd react || echo "!!! I can't find react" +echo "Actual directory is: ${PWD}" echo "Installing nodejs modules." npm install npm install webpack echo "Building EasyDEX-GUI app." npm run build -echo "EasyDEX-GUI is built!" \ No newline at end of file +echo "EasyDEX-GUI is built!" diff --git a/buildscripts/electron-build-linux.sh b/buildscripts/electron-build-linux.sh index 15463ed..f906e50 100755 --- a/buildscripts/electron-build-linux.sh +++ b/buildscripts/electron-build-linux.sh @@ -3,25 +3,19 @@ ### Created by mmaxian, 3/2017 [ -z $AGAMA_VERSION ] && echo "AGAMA_VERSION variable is not set." && exit 0 +[ ! -d build ] && mkdir build echo -echo ========================================= -echo Step: Removing old binaries -rm -rvf artifacts.supernet.org -echo -echo Step: Cloning latest binaries for build -wget --recursive --no-parent -q https://artifacts.supernet.org/latest/linux/ -find artifacts.supernet.org/latest/linux/ -exec ls -l {} \; -cd .. -echo ========================================= -echo - echo "Build script for Iguana application for Linux x64 platform." echo "Preparing electron package $AGAMA_VERSION" electron-packager . --platform=linux --arch=x64 \ --icon=assets/icons/agama_icons/128x128.png \ - --out=build/ --buildVersion=$AGAMA_VERSION \ - --ignore=build/artifacts.supernet.org/latest/windows \ - --ignore=build/artifacts.supernet.org/latest/osx \ - --overwrite \ No newline at end of file + --out=build/ \ + --buildVersion=$AGAMA_VERSION \ + --ignore=assets/bin/win64 \ + --ignore=assets/bin/osx \ + --ignore=react/node_modules \ + --ignore=react/src \ + --ignore=react/www \ + --overwrite diff --git a/buildscripts/electron-build-osx.sh b/buildscripts/electron-build-osx.sh index eeba0fe..08765cb 100755 --- a/buildscripts/electron-build-osx.sh +++ b/buildscripts/electron-build-osx.sh @@ -3,25 +3,18 @@ ### Created by mmaxian, 3/2017 [ -z $AGAMA_VERSION ] && echo "AGAMA_VERSION variable is not set." && exit 0 +[ ! -d build ] && mkdir build echo -echo ========================================= -echo Step: Removing old binaries -rm -rvf artifacts.supernet.org -echo -echo Step: Cloning latest binaries for build -wget --recursive --no-parent -q https://artifacts.supernet.org/latest/osx/ -find artifacts.supernet.org/latest/osx/ -exec ls -l {} \; -cd .. -echo ========================================= -echo - echo "Build script for Iguana application for MacOS platform." echo "Preparing electron package $AGAMA_VERSION" electron-packager . --platform=darwin --arch=x64 \ --icon=assets/icons/agama_app_icon.icns \ --out=build/ --buildVersion=$AGAMA_VERSION \ - --ignore=build/artifacts.supernet.org/latest/windows \ - --ignore=build/artifacts.supernet.org/latest/linux \ - --overwrite \ No newline at end of file + --ignore=assets/bin/win64 \ + --ignore=assets/bin/linux64 \ + --ignore=react/node_modules \ + --ignore=react/src \ + --ignore=react/www \ + --overwrite diff --git a/buildscripts/electron-build-windows.sh b/buildscripts/electron-build-windows.sh index e9be400..f767aaf 100755 --- a/buildscripts/electron-build-windows.sh +++ b/buildscripts/electron-build-windows.sh @@ -3,33 +3,26 @@ ### Created by mmaxian, 3/2017 [ -z $AGAMA_VERSION ] && echo "AGAMA_VERSION variable is not set." && exit 0 +[ ! -d build ] && mkdir build echo -echo ========================================= -echo Step: Removing old binaries -rm -rvf artifacts.supernet.org -echo -echo Step: Cloning latest binaries for build -wget --recursive --no-parent -q https://artifacts.supernet.org/latest/windows/ -find artifacts.supernet.org/latest/windows/ -exec ls -l {} \; -cd .. -echo ========================================= -echo - echo "Build script for Iguana application for Windows x64 platform." echo "Preparing electron package $AGAMA_VERSION" electron-packager . --platform=win32 \ --arch=ia32 \ --icon=assets/icons/agama_app_icon.ico \ - --out=build \ + --out=build/ \ --buildVersion=$AGAMA_VERSION \ - --ignore=build/artifacts.supernet.org/latest/osx \ - --ignore=build/artifacts.supernet.org/latest/linux \ + --ignore=assets/bin/osx \ + --ignore=assets/bin/linux64 \ + --ignore=react/node_modules \ + --ignore=react/src \ + --ignore=react/www \ --overwrite \ --version-string.CompanyName="SuperNET" \ --version-string.FileDescription="Agama" \ --version-string.OriginalFilename="Agama" \ --version-string.ProductName="Agama" \ --version-string.InternalName="Agama" \ - --app-copyright="Copyright (C) 2017 SuperNET. All rights reserved." \ No newline at end of file + --app-copyright="Copyright (C) 2017 SuperNET. All rights reserved." diff --git a/gui/startup/agama-instance-error.html b/gui/startup/agama-instance-error.html index 0fda6ba..e16f766 100644 --- a/gui/startup/agama-instance-error.html +++ b/gui/startup/agama-instance-error.html @@ -3,14 +3,12 @@ - - @@ -30,6 +28,5 @@ class="btn btn-primary btn-close-app">Quit - \ No newline at end of file diff --git a/gui/startup/app-closing.html b/gui/startup/app-closing.html index 2365a1a..87618d3 100644 --- a/gui/startup/app-closing.html +++ b/gui/startup/app-closing.html @@ -3,17 +3,15 @@ - - - +
-
App is closing. Please wait...
+
+ App is closing. Please wait...

+ This may take a while depending on your system resources and current state of daemon applications. +
diff --git a/gui/startup/app-settings.html b/gui/startup/app-settings.html index 409022c..9455da9 100644 --- a/gui/startup/app-settings.html +++ b/gui/startup/app-settings.html @@ -3,14 +3,12 @@ - - @@ -38,6 +36,7 @@
+
Any changes to app config require app restart!
- -
Choose Agama mode
+
+ +
+ Choose Agama mode +
+ +
+
+
+ +
Full blockchain mode
-
+
+ +
+ + +
+
- + \ No newline at end of file diff --git a/gui/startup/main.html b/gui/startup/main.html deleted file mode 100644 index 19243b2..0000000 --- a/gui/startup/main.html +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - - - - - - -
-
-

Iguana Wallet

- Open Iguana Wallet

- Launch proxy server

- Komodo Native

- Start Iguana Core -

-
-

EasyDEX

- Open EasyDEX -
-
- - \ No newline at end of file diff --git a/index.html b/index.html deleted file mode 100644 index 21fc4cd..0000000 --- a/index.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - iguanaElectronApp - - -

some version information:

- node.js , - chromium , - electron . - - - - diff --git a/main.js b/main.js index 21085f5..514ce61 100644 --- a/main.js +++ b/main.js @@ -1,39 +1,42 @@ -// main proc for EasyDEX GUI +// main proc for Agama // this app spawns iguana in background in nontech-mode -const electron = require('electron'), - app = electron.app, - BrowserWindow = electron.BrowserWindow, - path = require('path'), - url = require('url'), - os = require('os'), - md5 = require('./routes/md5'), - exec = require('child_process').exec, - { Menu } = require('electron'), - portscanner = require('portscanner'), - osPlatform = os.platform(), - fixPath = require('fix-path'); - -var express = require('express'), - bodyParser = require('body-parser'), - fs = require('fs'), - fsnode = require('fs'), - fs = require('fs-extra'), - pm2 = require('pm2'), - cluster = require('cluster'), - numCPUs = require('os').cpus().length; +const electron = require('electron'); +const app = electron.app; +const BrowserWindow = electron.BrowserWindow; +const path = require('path'); +const url = require('url'); +const os = require('os'); +const md5 = require('./routes/md5'); +const exec = require('child_process').exec; +const { Menu } = require('electron'); +const portscanner = require('portscanner'); +const osPlatform = os.platform(); +const fixPath = require('fix-path'); + +const express = require('express'); +const bodyParser = require('body-parser'); +const fsnode = require('fs'); +const fs = require('fs-extra'); +const numCPUs = require('os').cpus().length; Promise = require('bluebird'); if (osPlatform === 'linux') { process.env.ELECTRON_RUN_AS_NODE = true; - // console.log(process.env); } // GUI APP settings and starting gui on address http://120.0.0.1:17777 var shepherd = require('./routes/shepherd'); var guiapp = express(); +shepherd.createAgamaDirs(); + +var appConfig = shepherd.loadLocalConfig(); // load app config + +const nativeCoindList = shepherd.scanNativeCoindBins(); +shepherd.setVar('nativeCoindList', nativeCoindList); + let localVersion; let localVersionFile = shepherd.readVersionFile(); @@ -51,8 +54,7 @@ const appBasicInfo = { app.setName(appBasicInfo.name); app.setVersion(appBasicInfo.version); -shepherd.binFixRights(); -shepherd.createIguanaDirs(); +shepherd.createAgamaDirs(); const appSessionHash = md5(Date.now().toString()); @@ -67,20 +69,28 @@ shepherd.writeLog(`platform: ${osPlatform}`); shepherd.writeLog(`os_release: ${os.release()}`); shepherd.writeLog(`os_type: ${os.type()}`); -var appConfig = shepherd.loadLocalConfig(); // load app config +shepherd.log(`app init ${appSessionHash}`); +shepherd.log(`app info: ${appBasicInfo.name} ${appBasicInfo.version}`); +shepherd.log('sys info:'); +shepherd.log(`totalmem_readable: ${formatBytes(os.totalmem())}`); +shepherd.log(`arch: ${os.arch()}`); +shepherd.log(`cpu: ${os.cpus()[0].model}`); +shepherd.log(`cpu_cores: ${os.cpus().length}`); +shepherd.log(`platform: ${osPlatform}`); +shepherd.log(`os_release: ${os.release()}`); +shepherd.log(`os_type: ${os.type()}`); + appConfig['daemonOutput'] = false; // shadow setting let __defaultAppSettings = require('./routes/appConfig.js').config; __defaultAppSettings['daemonOutput'] = false; // shadow setting const _defaultAppSettings = __defaultAppSettings; +shepherd.log(`app started in ${(appConfig.dev ? 'dev mode' : ' user mode')}`); shepherd.writeLog(`app started in ${(appConfig.dev ? 'dev mode' : ' user mode')}`); shepherd.setConfKMD(); - -if (appConfig.killIguanaOnStart) { - shepherd.killRogueProcess('iguana'); -} +shepherd.setConfKMD('CHIPS'); guiapp.use(function(req, res, next) { res.header('Access-Control-Allow-Origin', appConfig.dev ? '*' : 'http://127.0.0.1:3000'); @@ -103,9 +113,9 @@ process.once('loaded', () => { process.setFdLimit(appConfig.maxDescriptors.darwin); app.setAboutPanelOptions({ applicationName: app.getName(), - applicationVersion: app.getVersion(), + applicationVersion: `${app.getVersion().replace('version=', '')}-beta`, copyright: 'Released under the MIT license', - credits: 'SuperNET Team' + credits: 'SuperNET Team', }) } if (osPlatform === 'linux') { @@ -148,13 +158,13 @@ let forceQuitApp = false; const _zcashParamsExist = shepherd.zcashParamsExist(); module.exports = guiapp; -let iguanaIcon; +let agamaIcon; if (os.platform() === 'linux') { - iguanaIcon = path.join(__dirname, '/assets/icons/agama_icons/128x128.png'); + agamaIcon = path.join(__dirname, '/assets/icons/agama_icons/128x128.png'); } if (os.platform() === 'win32') { - iguanaIcon = path.join(__dirname, '/assets/icons/agama_app_icon.ico'); + agamaIcon = path.join(__dirname, '/assets/icons/agama_app_icon.ico'); } function createLoadingWindow() { @@ -166,7 +176,7 @@ function createLoadingWindow() { width: 500, height: 355, frame: false, - icon: iguanaIcon, + icon: agamaIcon, show: false, }); } catch(e) {} @@ -183,7 +193,7 @@ function createLoadingWindow() { // start sockets.io io.set('origins', appConfig.dev ? 'http://127.0.0.1:3000' : `http://127.0.0.1:${appConfig.agamaPort}`); // set origin - io.on('connection', function(client) { + /*io.on('connection', function(client) { shepherd.log('EDEX GUI is connected...'); shepherd.writeLog('EDEX GUI is connected...'); @@ -197,7 +207,7 @@ function createLoadingWindow() { shepherd.log(data); client.emit('messages', 'Sockets server is listening'); }); - }); + });*/ }); } else { willQuitApp = true; @@ -219,6 +229,8 @@ function createLoadingWindow() { loadingWindow.forseCloseApp = forseCloseApp; loadingWindow.createAppSettingsWindow = createAppSettingsWindow; loadingWindow.startKMDNative = shepherd.startKMDNative; + loadingWindow.startSPV = shepherd.startSPV; + loadingWindow.arch = os.arch(); // load our index.html (i.e. easyDEX GUI) loadingWindow.loadURL(`http://${appConfig.host}:${appConfig.agamaPort}/gui/startup`); @@ -229,9 +241,6 @@ function createLoadingWindow() { }); shepherd.writeLog('show loading window'); - // DEVTOOLS - only for dev purposes - ca333 - // loadingWindow.webContents.openDevTools() - // if window closed we kill iguana proc loadingWindow.on('hide', function() { // our app does not have multiwindow - so we dereference the window object instead of @@ -274,9 +283,9 @@ function createAppCloseWindow() { // initialise window appCloseWindow = new BrowserWindow({ // dirty hack to prevent main window flash on quit width: 500, - height: 355, + height: 320, frame: false, - icon: iguanaIcon, + icon: agamaIcon, show: false, }); @@ -299,9 +308,9 @@ function createAppSettingsWindow() { // initialise window appSettingsWindow = new BrowserWindow({ // dirty hack to prevent main window flash on quit width: 750, - height: !appConfig.experimentalFeatures ? 570 : 700, + height: 610, frame: false, - icon: iguanaIcon, + icon: agamaIcon, show: false, }); @@ -343,7 +352,7 @@ function createWindow(status) { mainWindow = new BrowserWindow({ // dirty hack to prevent main window flash on quit width: closeAppAfterLoading ? 1 : 1280, height: closeAppAfterLoading ? 1 : 800, - icon: iguanaIcon, + icon: agamaIcon, show: false, }); @@ -371,39 +380,40 @@ function createWindow(status) { ]); // load our index.html (i.e. easyDEX GUI) - if (appConfig.edexGuiOnly) { - if (appConfig.v2) { - shepherd.writeLog('show edex gui'); - mainWindow.appConfig = appConfig; - mainWindow.appConfigSchema = shepherd.appConfigSchema; - mainWindow.appBasicInfo = appBasicInfo; - mainWindow.appSessionHash = appSessionHash; - mainWindow.assetChainPorts = require('./routes/ports.js'); - mainWindow.zcashParamsExist = _zcashParamsExist; - mainWindow.iguanaIcon = iguanaIcon; - mainWindow.testLocation = shepherd.testLocation; - mainWindow.kmdMainPassiveMode = shepherd.kmdMainPassiveMode; - mainWindow.getAppRuntimeLog = shepherd.getAppRuntimeLog; - - if (appConfig.dev) { - mainWindow.loadURL('http://127.0.0.1:3000'); - } else { - mainWindow.loadURL(`http://${appConfig.host}:${appConfig.agamaPort}/gui/EasyDEX-GUI/react/build`); - } - - mainWindow.webContents.on('did-finish-load', function() { - setTimeout(function() { - mainWindow.show(); - }, 40); - }); - } else { - shepherd.writeLog('show edex gui'); - mainWindow.loadURL(`http://${appConfig.host}:${appConfig.agamaPort}/gui/EasyDEX-GUI/`); - } + shepherd.writeLog('show edex gui'); + mainWindow.appConfig = appConfig; + mainWindow.appConfigSchema = shepherd.appConfigSchema; + mainWindow.arch = os.arch(); + mainWindow.appBasicInfo = appBasicInfo; + mainWindow.appSessionHash = appSessionHash; + mainWindow.assetChainPorts = require('./routes/ports.js'); + mainWindow.zcashParamsExist = _zcashParamsExist; + mainWindow.zcashParamsExistPromise = shepherd.zcashParamsExistPromise; + mainWindow.agamaIcon = agamaIcon; + mainWindow.testLocation = shepherd.testLocation; + mainWindow.kmdMainPassiveMode = shepherd.kmdMainPassiveMode; + mainWindow.getAppRuntimeLog = shepherd.getAppRuntimeLog; + mainWindow.nativeCoindList = nativeCoindList; + mainWindow.zcashParamsDownloadLinks = shepherd.zcashParamsDownloadLinks; + mainWindow.isWindows = os.platform() === 'win32' ? true : false; // obsolete(?) + mainWindow.appExit = appExit; + mainWindow.getMaxconKMDConf = shepherd.getMaxconKMDConf; + mainWindow.setMaxconKMDConf = shepherd.setMaxconKMDConf; + mainWindow.getMMCacheData = shepherd.getMMCacheData; + mainWindow.activeSection = 'wallets'; + + if (appConfig.dev) { + mainWindow.loadURL('http://127.0.0.1:3000'); } else { - mainWindow.loadURL(`http://${appConfig.host}:${appConfig.agamaPort}/gui/startup/main.html`); + mainWindow.loadURL(`http://${appConfig.host}:${appConfig.agamaPort}/gui/EasyDEX-GUI/react/build`); } + mainWindow.webContents.on('did-finish-load', function() { + setTimeout(function() { + mainWindow.show(); + }, 40); + }); + mainWindow.webContents.on('context-menu', (e, params) => { // context-menu returns params const { selectionText, isEditable } = params; // params obj @@ -417,25 +427,15 @@ function createWindow(status) { // DEVTOOLS - only for dev purposes - ca333 // mainWindow.webContents.openDevTools() - function pm2Exit() { - const ConnectToPm2 = function() { + function appExit() { + const CloseDaemons = function() { return new Promise(function(resolve, reject) { shepherd.log('Closing Main Window...'); shepherd.writeLog('exiting app...'); - shepherd.dumpCacheBeforeExit(); - shepherd.quitKomodod(1000); - - pm2.connect(true, function(err) { - shepherd.log('connecting to pm2...'); - shepherd.writeLog('connecting to pm2...'); - - if (err) { - shepherd.log(err); - } - }); + shepherd.quitKomodod(appConfig.cliStopTimeout); - const result = 'Connecting To Pm2: done'; + const result = 'Closing daemons: done'; shepherd.log(result); shepherd.writeLog(result); @@ -443,31 +443,6 @@ function createWindow(status) { }) } - const KillPm2 = function() { - return new Promise(function(resolve, reject) { - shepherd.log('killing to pm2...'); - shepherd.writeLog('killing to pm2...'); - - pm2.killDaemon(function(err) { - pm2.disconnect(); - shepherd.log('killed to pm2...'); - shepherd.writeLog('killed to pm2...'); - - if (err) - throw err; - }); - - const result = 'Killing Pm2: done'; - - setTimeout(function() { - shepherd.log(result); - shepherd.writeLog(result); - - resolve(result); - }, 2000); - }) - } - const HideMainWindow = function() { return new Promise(function(resolve, reject) { const result = 'Hiding Main Window: done'; @@ -490,7 +465,6 @@ function createWindow(status) { return new Promise(function(resolve, reject) { const result = 'Quiting App: done'; - KillPm2(); // required for normal app quit in iguana-less mode app.quit(); shepherd.log(result); resolve(result); @@ -498,10 +472,7 @@ function createWindow(status) { } const closeApp = function() { - ConnectToPm2() - .then(function(result) { - return KillPm2(); - }) + CloseDaemons() .then(HideMainWindow) .then(HideAppClosingWindow) .then(QuitApp); @@ -509,11 +480,13 @@ function createWindow(status) { let _appClosingInterval; - if (!Object.keys(shepherd.coindInstanceRegistry).length) { + // shepherd.killRogueProcess('marketmaker'); + if (!Object.keys(shepherd.coindInstanceRegistry).length || + !appConfig.stopNativeDaemonsOnQuit) { closeApp(); } else { createAppCloseWindow(); - shepherd.quitKomodod(1000); + shepherd.quitKomodod(appConfig.cliStopTimeout); _appClosingInterval = setInterval(function() { if (!Object.keys(shepherd.coindInstanceRegistry).length) { closeApp(); @@ -524,7 +497,7 @@ function createWindow(status) { // if window closed we kill iguana proc mainWindow.on('closed', function() { - pm2Exit(); + appExit(); }); } } @@ -543,23 +516,28 @@ app.on('window-all-closed', function() { // Calling event.preventDefault() will prevent the default behaviour, which is terminating the application. app.on('before-quit', function(event) { shepherd.log('before-quit'); - shepherd.killRogueProcess('iguana'); // kill any rogue iguana core instances + // shepherd.killRogueProcess('marketmaker'); - if (!forceQuitApp && mainWindow === null && loadingWindow != null) { // mainWindow not intitialised and loadingWindow not dereferenced + /*if (!forceQuitApp && + mainWindow === null && + loadingWindow != null) { // mainWindow not intitialised and loadingWindow not dereferenced // loading window is still open shepherd.log('before-quit prevented'); shepherd.writeLog('quit app after loading is done'); closeAppAfterLoading = true; + // obsolete(?) let code = `$('#loading_status_text').html('Preparing to shutdown the wallet.
Please wait while all daemons are closed...')`; loadingWindow.webContents.executeJavaScript(code); event.preventDefault(); - } + }*/ }); // Emitted when all windows have been closed and the application will quit. // Calling event.preventDefault() will prevent the default behaviour, which is terminating the application. app.on('will-quit', function(event) { - if (!forceQuitApp && mainWindow === null && loadingWindow != null) { + if (!forceQuitApp && + mainWindow === null && + loadingWindow != null) { // loading window is still open shepherd.log('will-quit while loading window active'); event.preventDefault(); @@ -569,18 +547,20 @@ app.on('will-quit', function(event) { // Emitted when the application is quitting. // Calling event.preventDefault() will prevent the default behaviour, which is terminating the application. app.on('quit', function(event) { - if (!forceQuitApp && mainWindow === null && loadingWindow != null) { + if (!forceQuitApp && + mainWindow === null && + loadingWindow != null) { shepherd.log('quit while loading window active'); event.preventDefault(); } }) app.on('activate', function() { - if (mainWindow === null) { - // createWindow('open'); - } + if (mainWindow === null) {} }); +app.commandLine.appendSwitch('ignore-certificate-errors'); // dirty hack + function formatBytes(bytes, decimals) { if (bytes === 0) { return '0 Bytes'; diff --git a/make-deb.js b/make-deb.js new file mode 100644 index 0000000..3052368 --- /dev/null +++ b/make-deb.js @@ -0,0 +1,26 @@ +// prequsites: https://www.npmjs.com/package/electron-installer-debian + +var installer = require('electron-installer-debian'); + +var options = { + src: 'build/Agama-linux-x64/', + dest: 'build/', + arch: 'amd64', + icon: 'assets/icons/agama_icons/64x64.png', + name: 'agama-app', + bin: 'Agama', + categories: ['Office', 'Internet'], + homepage: 'http://supernet.org', + maintainer: 'SuperNET ', +} + +console.log('Creating package (this may take a while)'); + +installer(options, function (err) { + if (err) { + console.error(err, err.stack); + process.exit(1); + } + + console.log('Successfully created package at ' + options.dest); +}); \ No newline at end of file diff --git a/make-rpm.js b/make-rpm.js new file mode 100644 index 0000000..a37e70e --- /dev/null +++ b/make-rpm.js @@ -0,0 +1,26 @@ +// prequsites: https://www.npmjs.com/package/electron-installer-redhat + +var installer = require('electron-installer-redhat'); + +var options = { + src: 'build/Agama-linux-x64/', + dest: 'build/', + arch: 'x86_64', + icon: 'assets/icons/agama_icons/64x64.png', + name: 'agama-app', + bin: 'Agama', + categories: ['Office', 'Internet'], + homepage: 'http://supernet.org', + maintainer: 'SuperNET ', +}; + +console.log('Creating package (this may take a while)'); + +installer(options, function (err) { + if (err) { + console.error(err, err.stack); + process.exit(1); + } + + console.log('Successfully created package at ' + options.dest); +}); \ No newline at end of file diff --git a/package.json b/package.json index f81d52f..5f06be2 100644 --- a/package.json +++ b/package.json @@ -1,43 +1,52 @@ { - "name": "agama_app", + "name": "agama-app", "productName": "Agama", - "version": "0.2.22", - "description": "Agama Desktop App", + "version": "0.2.24", + "description": "Agama Wallet Desktop App", "main": "main.js", "scripts": { "start": "electron .", - "make-patch": "./make-patch.sh" + "make-patch": "./make-patch.sh", + "make-rpm": "node make-rpm.js", + "make-deb": "node make-deb.js" }, "repository": "https://github.com/SuperNETorg/Agama/", + "homepage": "http://supernet.org", "keywords": [ - "iguana", "agama", - "superNET", + "SuperNET", "komodo", - "easydex", - "instantdex", "pax" ], - "author": "ca333, grewalsatinder", + "author": "SuperNET", "license": "MIT", "devDependencies": { - "electron": "^1.4.1" + "electron": "1.6.5", + "electron-installer-debian": "^0.6.0", + "electron-installer-redhat": "^0.5.0" }, "dependencies": { "adm-zip": "^0.4.7", + "bitcoinjs-lib": "^3.2.0", "bluebird": "^3.4.7", "body-parser": "^1.15.2", + "buffer-reverse": "^1.0.1", + "coinkey": "^2.0.0", + "coinselect": "github:bitcoinjs/coinselect", + "marketmaker": "git://github.com/pbca26/marketmaker", "electron": "1.6.5", "express": "^4.14.0", "fix-path": "^2.1.0", - "fs-extra": "^1.0.0", + "fs-extra": "^4.0.2", "graceful-fs": "^4.1.11", + "js-sha256": "^0.7.1", "nodejs-aes256": "^1.0.1", - "pm2": "^2.4.3", "portscanner": "^2.1.1", "ps-node": "^0.1.5", "remote-file-size": "^3.0.3", "request": "^2.80.0", - "socket.io": "^1.7.3" + "sha256": "^0.2.0", + "socket.io": "^1.7.3", + "wif": "^2.0.6" } } diff --git a/private/kmdcli.js b/private/kmdcli.js index ffc9572..cff8afd 100644 --- a/private/kmdcli.js +++ b/private/kmdcli.js @@ -116,11 +116,11 @@ function parse_kmdcli_commands(callback) { * "notaryid" : -1, * "pubkey" : "000000000000000000000000000000000000000000000000000000000000000000" * } - * + * */ function command(kmd_command, callback) { if (callback) { return this.exec(komodocliBin + " " + kmd_command, - parse_kmdcli_commands(callback)); + parse_kmdcli_commands(callback)); } } diff --git a/routes/appConfig.js b/routes/appConfig.js index f73805d..7cb1ef5 100644 --- a/routes/appConfig.js +++ b/routes/appConfig.js @@ -1,58 +1,24 @@ const appConfig = { config: { // default config - edexGuiOnly: true, - iguanaGuiOnly: false, - manualIguanaStart: false, - skipBasiliskNetworkCheck: true, - minNotaries: 8, host: '127.0.0.1', agamaPort: 17777, - iguanaCorePort: 7778, maxDescriptors: { darwin: 90000, linux: 1000000, }, - killIguanaOnStart: true, dev: false, - v2: true, - useBasiliskInstance: true, debug: false, - cli: { - passthru: true, - default: true, - }, - iguanaLessMode: true, roundValues: false, experimentalFeatures: false, dataDir: '', + dex: { + walletUnlockTimeout: 3600, + }, + cliStopTimeout: 1000, + failedRPCAttemptsThreshold: 10, + stopNativeDaemonsOnQuit: true, }, schema: { - edexGuiOnly: { - display: false, - type: 'boolean', - displayName: 'EDEX GUI only', - }, - iguanaGuiOnly: { - display: false, - type: 'boolean', - displayName: 'Iguana GUI only', - }, - manualIguanaStart: { - display: false, - type: 'boolean', - displayName: 'Manual Iguana Start', - }, - skipBasiliskNetworkCheck: { - display: false, - type: 'boolean', - displayName: 'Skip Basilisk Network Check', - }, - minNotaries: { - display: false, - type: 'number', - displayName: 'Minimum notaries count', - info: 'Minimum number of notaries to connect to on startup', - }, host: { display: true, type: 'string', @@ -65,13 +31,6 @@ const appConfig = { displayName: 'Agama Port', info: 'Agama HTTP port. Required to run GUI.', }, - iguanaCorePort: { - display: true, - initDisplay: true, - type: 'number', - displayName: 'Iguana Core Port', - info: 'Default Iguana Core Port. Change it if you have conflicts with other applications.', - }, maxDescriptors: { display: false, displayName: 'Max Descriptors per Process', @@ -86,12 +45,6 @@ const appConfig = { type: 'number', }, }, - killIguanaOnStart: { - display: true, - displayName: 'Kill Iguana Core Processes on Startup', - info: 'Kill any rogue Iguana Core processes during app startup', - type: 'boolean', - }, dev: { display: true, initDisplay: true, @@ -99,17 +52,6 @@ const appConfig = { info: 'Enable developer mode', type: 'boolean', }, - v2: { - display: false, - type: 'boolean', - }, - useBasiliskInstance: { - display: true, - initDisplay: true, - displayName: 'Iguana Core Basilisk Instance', - info: 'Enable dedicated Iguana Core instance to handle all Basilisk network requests', - type: 'boolean', - }, debug: { display: true, initDisplay: true, @@ -117,28 +59,6 @@ const appConfig = { info: 'Enable debug output', type: 'boolean', }, - cli: { - display: true, - displayName: 'Direct BitcoinRPC passthru interface', - info: 'Enable direct BitcoinRPC passthru interface. It will bypass Iguana Core and send requests directly to Bitcoin JSON server.', - passthru: { - display: true, - displayName: 'Enable Direct Passthru', - type: 'boolean', - }, - default: { - display: true, - displayName: 'Enable CLI passthru', - info: 'Enable komodo-cli passthru. This allows you to send CLI compatible commands directly from UI to komodo-cli.', - type: 'boolean', - }, - }, - iguanaLessMode: { - display: true, - displayName: 'Enable Native Only mode', - info: 'Limited to only Komodo native mode to speed up loading and reduce hardware resources consumption.', - type: 'boolean', - }, roundValues: { display: true, displayName: 'Enable amount rounding', @@ -158,13 +78,33 @@ const appConfig = { info: 'The data directory is the location where Komodo data files are stored, including the wallet data file', type: 'folder', }, - daemonOutput: { + dex: { display: false, - initDisplay: false, - displayName: 'Output daemon prints (debug)', - info: 'Output daemon prints to GUI for debug purposes', + displayName: 'dex', + walletUnlockTimeout: { + display: true, + displayName: 'walletUnlockTimeout', + type: 'number', + }, + }, + cliStopTimeout: { + display: true, + displayName: 'CLI stop timeout', + info: 'Timeout between consequent CLI stop commands', + type: 'number', + }, + stopNativeDaemonsOnQuit: { + display: true, + displayName: 'Stop native daemons on app quit', + info: 'If set to false agama will run in detached coin daemon mode', type: 'boolean', }, + failedRPCAttemptsThreshold: { + display: true, + displayName: 'Failed RPC connect attempts threshold', + info: 'Number of allowed consequent RPC connect failures before the app marks native coin daemon as not running properly', + type: 'number', + }, }, }; diff --git a/routes/cache.js b/routes/cache.js deleted file mode 100644 index 70664cf..0000000 --- a/routes/cache.js +++ /dev/null @@ -1,962 +0,0 @@ -const fs = require('fs-extra'); -const request = require('request'); -const async = require('async'); - -var cache = {}; -var inMemCache; -var inMemPubkey; - -cache.setVar = function(variable, value) { - cache[variable] = value; -} - -/* - * cache data is dumped to disk before app quit or after cache.one call is finished - */ -cache.dumpCacheBeforeExit = function() { - if (inMemCache) { - cache.shepherd.log('dumping cache before exit'); - fs.writeFileSync(`${cache.iguanaDir}/shepherd/cache-${inMemPubkey}.json`, JSON.stringify(inMemCache), 'utf8'); - } -} - -cache.get = function(req, res, next) { - const pubkey = req.query.pubkey; - - if (pubkey) { - inMemPubkey = pubkey; - - if (!inMemCache) { - cache.shepherd.log('serving cache from disk'); - - if (fs.existsSync(`${cache.iguanaDir}/shepherd/cache-${pubkey}.json`)) { - fs.readFile(`${cache.iguanaDir}/shepherd/cache-${pubkey}.json`, 'utf8', function(err, data) { - if (err) { - const errorObj = { - msg: 'error', - result: err, - }; - - res.end(JSON.stringify(errorObj)); - } else { // deprecated - try { - const parsedJSON = JSON.parse(data); - const successObj = { - msg: 'success', - result: parsedJSON, - }; - - inMemCache = parsedJSON; - res.end(JSON.stringify(successObj)); - } catch (e) { - cache.shepherd.log('JSON parse error while reading cache data from disk:'); - cache.shepherd.log(e); - if (e.toString().indexOf('at position') > -1) { - const errorPos = e.toString().split(' '); - - cache.shepherd.log(`JSON error ---> ${data.substring(errorPos[errorPos.length - 1] - 20, errorPos[errorPos.length - 1] + 20)} | error sequence: ${data.substring(errorPos[errorPos.length - 1], errorPos[errorPos.length - 1] + 1)}`); - cache.shepherd.log('attempting to recover JSON data'); - - fs.writeFile(`${cache.iguanaDir}/shepherd/cache-${pubkey}.json`, data.substring(0, errorPos[errorPos.length - 1]), function(err) { - const successObj = { - msg: 'success', - result: data.substring(0, errorPos[errorPos.length - 1]), - }; - - inMemCache = JSON.parse(data.substring(0, errorPos[errorPos.length - 1])); - res.end(JSON.stringify(successObj)); - }); - } - } - } - }); - } else { - const errorObj = { - msg: 'error', - result: `no file with handle ${pubkey}`, - }; - - res.end(JSON.stringify(errorObj)); - } - } else { - const successObj = { - msg: 'success', - result: inMemCache, - }; - - res.end(JSON.stringify(successObj)); - } - } else { - const errorObj = { - msg: 'error', - result: 'no pubkey provided', - }; - - res.end(JSON.stringify(errorObj)); - } -} - -cache.groomGet = function(req, res, next) { - const _filename = req.query.filename; - - if (_filename) { - if (fs.existsSync(`${cache.iguanaDir}/shepherd/cache-${_filename}.json`)) { - fs.readFile(`${cache.iguanaDir}/shepherd/cache-${_filename}.json`, 'utf8', function(err, data) { - if (err) { - const errorObj = { - msg: 'error', - result: err, - }; - - res.end(JSON.stringify(errorObj)); - } else { - const successObj = { - msg: 'success', - result: data ? JSON.parse(data) : '', - }; - - res.end(JSON.stringify(successObj)); - } - }); - } else { - const errorObj = { - msg: 'error', - result: `no file with name ${_filename}`, - }; - - res.end(JSON.stringify(errorObj)); - } - } else { - const errorObj = { - msg: 'error', - result: 'no file name provided', - }; - - res.end(JSON.stringify(errorObj)); - } -} - -cache.groomDelete = function(req, res, next) { - const _filename = req.body.filename; - - if (_filename) { - if (fs.existsSync(`${cache.iguanaDir}/shepherd/cache-${_filename}.json`)) { - inMemCache = null; - - fs.unlink(`${cache.iguanaDir}/shepherd/cache-${_filename}.json`, function(err) { - if (err) { - const errorObj = { - msg: 'error', - result: err, - }; - - res.end(JSON.stringify(errorObj)); - } else { - const successObj = { - msg: 'success', - result: 'deleted', - }; - - res.end(JSON.stringify(successObj)); - } - }); - } else { - const errorObj = { - msg: 'error', - result: `no file with name ${_filename}`, - }; - - res.end(JSON.stringify(errorObj)); - } - } else { - const errorObj = { - msg: 'error', - result: 'no file name provided', - }; - - res.end(JSON.stringify(errorObj)); - } -} - -cache.groomPost = function(req, res) { - const _filename = req.body.filename; - const _payload = req.body.payload; - - if (!cacheCallInProgress) { - cacheCallInProgress = true; - - if (_filename) { - if (!_payload) { - const errorObj = { - msg: 'error', - result: 'no payload provided', - }; - - res.end(JSON.stringify(errorObj)); - } else { - inMemCache = JSON.parse(_payload); - cache.shepherd.log('appending groom post to in mem cache'); - cache.shepherd.log('appending groom post to on disk cache'); - - fs.writeFile(`${cache.iguanaDir}/shepherd/cache-${_filename}.json`, _payload, function(err) { - if (err) { - const errorObj = { - msg: 'error', - result: err, - }; - - cacheCallInProgress = false; - res.end(JSON.stringify(errorObj)); - } else { - const successObj = { - msg: 'success', - result: 'done', - }; - - cacheCallInProgress = false; - res.end(JSON.stringify(successObj)); - } - }); - } - } else { - const errorObj = { - msg: 'error', - result: 'no file name provided', - }; - - res.end(JSON.stringify(errorObj)); - } - } else { - const errorObj = { - msg: 'error', - result: 'another job is in progress', - }; - - res.end(JSON.stringify(errorObj)); - } -} - -var cacheCallInProgress = false; -const cacheGlobLifetime = 600; // sec - -// TODO: reset calls' states on new /cache call start -var mock = require('./mock'); - -var callStack = {}; -const checkCallStack = function() { - let total = 0; - - for (let coin in callStack) { - total =+ callStack[coin]; - } - - if (total / Object.keys(callStack).length === 1) { - cache.dumpCacheBeforeExit(); - cacheCallInProgress = false; - cache.io.emit('messages', { - message: { - shepherd: { - method: 'cache-one', - status: 'done', - resp: 'success', - }, - }, - }); - } -}; - -/* - * type: GET - * params: userpass, pubkey, coin, address, skip - */ -cache.one = function(req, res, next) { - if (req.query.pubkey && - !fs.existsSync(`${cache.iguanaDir}/shepherd/cache-${req.query.pubkey}.json`)) { - cacheCallInProgress = false; - } - - if (cacheCallInProgress) { - checkCallStack(); - } - - if (!cacheCallInProgress) { - cache.dumpCacheBeforeExit(); - - if (fs.existsSync(`${cache.iguanaDir}/shepherd/cache-${req.query.pubkey}.json`)) { - let _data = fs.readFileSync(`${cache.iguanaDir}/shepherd/cache-${req.query.pubkey}.json`, 'utf8'); - - if (_data) { - inMemCache = JSON.parse(_data); - _data = _data.replace('waiting', 'failed'); - cache.dumpCacheBeforeExit(); - } - } - - // TODO: add check to allow only one cache call/sequence in progress - cacheCallInProgress = true; - - let sessionKey = req.query.userpass; - let coin = req.query.coin; - let address = req.query.address; - let addresses = req.query.addresses && req.query.addresses.indexOf(':') > -1 ? req.query.addresses.split(':') : null; - let pubkey = req.query.pubkey; - let mock = req.query.mock; - let skipTimeout = req.query.skip; - let callsArray = req.query.calls.split(':'); - let iguanaCorePort = req.query.port ? req.query.port : cache.appConfig.iguanaCorePort; - let errorObj = { - msg: 'error', - result: 'error', - }; - let outObj = {}; - const writeCache = function(timeStamp) { - if (timeStamp) { - outObj.timestamp = timeStamp; - } - - inMemCache = outObj; - }; - const checkTimestamp = function(dateToCheck) { - const currentEpochTime = new Date(Date.now()) / 1000; - const secondsElapsed = Number(currentEpochTime) - Number(dateToCheck / 1000); - - return Math.floor(secondsElapsed); - }; - let internalError = false; - - inMemPubkey = pubkey; - callStack[coin] = 1; - cache.shepherd.log(callsArray); - cache.shepherd.log(`iguana core port ${iguanaCorePort}`); - - if (!sessionKey) { - const errorObj = { - msg: 'error', - result: 'no session key provided', - }; - - res.end(JSON.stringify(errorObj)); - internalError = true; - } - - if (!pubkey) { - const errorObj = { - msg: 'error', - result: 'no pubkey provided', - }; - - res.end(JSON.stringify(errorObj)); - internalError = true; - } - - cache.shepherd.log('cache-one call started'); - - function fixJSON(data) { - if (data && - data.length) { - try { - const parsedJSON = JSON.parse(data); - - return parsedJSON; - } catch (e) { - cache.shepherd.log(e); - if (e.toString().indexOf('at position') > -1) { - const errorPos = e.toString().split(' '); - - cache.shepherd.log(`JSON error ---> ${data.substring(errorPos[errorPos.length - 1] - 20, errorPos[errorPos.length - 1] + 20)} | error sequence: ${data.substring(errorPos[errorPos.length - 1], errorPos[errorPos.length - 1] + 1)}`); - cache.shepherd.log('attempting to recover JSON data'); - return JSON.parse(data.substring(0, errorPos[errorPos.length - 1])); - } - if (e.toString().indexOf('Unexpected end of JSON input')) { - return {}; - } - } - } else { - return {}; - } - } - - if (fs.existsSync(`${cache.iguanaDir}/shepherd/cache-${pubkey}.json`) && - coin !== 'all') { - if (inMemCache) { - cache.shepherd.log('cache one from mem'); - outObj = inMemCache; - } else { - const _file = fs.readFileSync(`${cache.iguanaDir}/shepherd/cache-${pubkey}.json`, 'utf8'); - - cache.shepherd.log('cache one from disk'); - outObj = fixJSON(_file); - } - - if (!outObj || - !outObj.basilisk) { - cache.shepherd.log('no local basilisk info'); - outObj['basilisk'] = {}; - outObj['basilisk'][coin] = {}; - } else { - if (!outObj['basilisk'][coin]) { - cache.shepherd.log('no local coin info'); - outObj['basilisk'][coin] = {}; - } - } - } else { - outObj['basilisk'] = {}; - outObj['basilisk'][coin] = {}; - } - - res.end(JSON.stringify({ - msg: 'success', - result: 'call is initiated', - })); - - if (!internalError) { - cache.io.emit('messages', { - message: { - shepherd: { - method: 'cache-one', - status: 'in progress', - }, - }, - }); - - function execDEXRequests(coin, address) { - let dexUrls = { - listunspent: `http://${cache.appConfig.host}:${iguanaCorePort}/api/dex/listunspent?userpass=${sessionKey}&symbol=${coin}&address=${address}`, - listtransactions: `http://${cache.appConfig.host}:${iguanaCorePort}/api/dex/listtransactions?userpass=${sessionKey}&count=100&skip=0&symbol=${coin}&address=${address}`, - getbalance: `http://${cache.appConfig.host}:${iguanaCorePort}/api/dex/getbalance?userpass=${sessionKey}&symbol=${coin}&address=${address}`, - refresh: `http://${cache.appConfig.host}:${iguanaCorePort}/api/basilisk/refresh?userpass=${sessionKey}&symbol=${coin}&address=${address}` - }; - let _dexUrls = {}; - - for (let a = 0; a < callsArray.length; a++) { - _dexUrls[callsArray[a]] = dexUrls[callsArray[a]]; - } - - if (coin === 'BTC' || - coin === 'SYS') { - delete _dexUrls.refresh; - delete _dexUrls.getbalance; - } - - cache.shepherd.log(`${coin} address ${address}`); - - if (!outObj.basilisk[coin][address]) { - outObj.basilisk[coin][address] = {}; - writeCache(); - } - - // set current call status - async.forEachOf(_dexUrls, function(dexUrl, key) { - if (!outObj.basilisk[coin][address][key]) { - outObj.basilisk[coin][address][key] = {}; - outObj.basilisk[coin][address][key].status = 'waiting'; - } else { - outObj.basilisk[coin][address][key].status = 'waiting'; - } - }); - writeCache(); - - async.forEachOf(_dexUrls, function(dexUrl, key) { - var tooEarly = false; - - if (outObj.basilisk[coin][address][key] && - outObj.basilisk[coin][address][key].timestamp && - (!skipTimeout && checkTimestamp(outObj.basilisk[coin][address][key].timestamp) < cacheGlobLifetime)) { - tooEarly = true; - outObj.basilisk[coin][address][key].status = 'done'; - cache.io.emit('messages', { - message: { - shepherd: { - method: 'cache-one', - status: 'in progress', - iguanaAPI: { - method: key, - coin: coin, - address: address, - status: 'done', - resp: 'too early', - }, - }, - }, - }); - } - if (!tooEarly) { - cache.io.emit('messages', { - message: { - shepherd: { - method: 'cache-one', - status: 'in progress', - iguanaAPI: { - method: key, - coin: coin, - address: address, - status: 'in progress', - }, - }, - }, - }); - outObj.basilisk[coin][address][key].status = 'in progress'; - request({ - url: mock ? `http://localhost:17777/shepherd/mock?url=${dexUrl}` : dexUrl, - method: 'GET', - timeout: 30000, - }, function(error, response, body) { - if (response && - response.statusCode && - response.statusCode === 200) { - cache.io.emit('messages', { - message: { - shepherd: { - method: 'cache-one', - status: 'in progress', - iguanaAPI: { - method: key, - coin: coin, - address: address, - status: 'done', - resp: body, - }, - }, - }, - }); - - // basilisk fallback - const _parsedJSON = JSON.parse(body); - _explorerURL = { - getbalance: `http://kmd.explorer.supernet.org/api/addr/${address}/?noTxList=1`, - listtransactions: `https://kmd.explorer.supernet.org/api/txs?address=${address}&pageNum=0` - }; - if ((key === 'getbalance' && - coin === 'KMD' && - ((_parsedJSON && _parsedJSON.balance === 0) || body === [])) || - (key === 'listtransactions' && - coin === 'KMD' && - (_parsedJSON === [] || !_parsedJSON.length))) { - cache.shepherd.log('fallback to kmd explorer ======>'); - request({ - url: _explorerURL[key], - method: 'GET' - }, function(error, response, _body) { - if (response && - response.statusCode && - response.statusCode === 200) { - let _parsedExplorerJSON = JSON.parse(_body); - _parsedExplorerJSON['source'] = 'explorer'; - let _formattedTxs = []; - - if (key === 'listtransactions' && - _parsedExplorerJSON['txs'] && - _parsedExplorerJSON['txs'].length) { - const _txList = _parsedExplorerJSON['txs']; - - for (let i = 0; i < _txList.length; i++) { - _formattedTxs.push({ - type: 'unknown', - height: _txList[i].blockheight, - confirmations: _txList[i].confirmations, - timestamp: _txList[i].time, - amount: _txList[i].vout[1].value, - txid: _txList[i].txid, - source: 'explorer', - }); - } - - _parsedExplorerJSON = _formattedTxs; - } - if ((key === 'getbalance' && - Number(_parsedExplorerJSON.balance) !== Number(_parsedJSON.balance) && - Number(_parsedExplorerJSON.txApperances) < 500) || key === 'listtransactions') { - body = JSON.stringify(_parsedExplorerJSON); - } - cache.io.emit('messages', { - 'message': { - 'shepherd': { - 'method': 'cache-one', - 'status': 'in progress', - 'iguanaAPI': { - 'method': key, - 'coin': coin, - 'address': address, - 'status': 'done', - 'resp': _body - } - } - } - }); - - outObj.basilisk[coin][address][key] = {}; - outObj.basilisk[coin][address][key].data = JSON.parse(body); - outObj.basilisk[coin][address][key].timestamp = Date.now(); // add timestamp - outObj.basilisk[coin][address][key].status = 'done'; - cache.shepherd.log(dexUrl); - cache.shepherd.log(body); - callStack[coin]--; - cache.shepherd.log(`${coin} _stack len ${callStack[coin]}`); - cache.io.emit('messages', { - message: { - shepherd: { - method: 'cache-one', - status: 'in progress', - iguanaAPI: { - currentStackLength: callStack[coin], - }, - }, - }, - }); - checkCallStack(); - writeCache(); - } else { - cache.shepherd.log(`explorer ${key} for address ${address} fallback failed`); - } - }); - } else { - outObj.basilisk[coin][address][key] = {}; - outObj.basilisk[coin][address][key].data = JSON.parse(body); - outObj.basilisk[coin][address][key].timestamp = Date.now(); // add timestamp - outObj.basilisk[coin][address][key].status = 'done'; - cache.shepherd.log(dexUrl); - cache.shepherd.log(body); - callStack[coin]--; - cache.shepherd.log(`${coin} _stack len ${callStack[coin]}`); - cache.io.emit('messages', { - message: { - shepherd: { - method: 'cache-one', - status: 'in progress', - iguanaAPI: { - currentStackLength: callStack[coin], - }, - }, - }, - }); - checkCallStack(); - writeCache(); - } - } - if (error || - !body || - !response) { - // basilisk fallback - _explorerURL = { - getbalance: `http://kmd.explorer.supernet.org/api/addr/${address}/?noTxList=1`, - listtransactions: `https://kmd.explorer.supernet.org/api/txs?address=${address}&pageNum=0` - }; - if ((key === 'getbalance' && - coin === 'KMD') || - (key === 'listtransactions' && - coin === 'KMD')) { - cache.shepherd.log('fallback to kmd explorer ======>'); - request({ - url: _explorerURL[key], - method: 'GET' - }, function(error, response, _body) { - if (response && - response.statusCode && - response.statusCode === 200) { - let _parsedExplorerJSON = JSON.parse(_body); - _parsedExplorerJSON['source'] = 'explorer'; - - if ((key === 'getbalance' && - Number(_parsedExplorerJSON.balance) !== Number(_parsedJSON.balance) && - Number(_parsedExplorerJSON.txApperances) < 500) || key === 'listtransactions') { - body = JSON.stringify(_parsedExplorerJSON); - } - cache.io.emit('messages', { - 'message': { - 'shepherd': { - 'method': 'cache-one', - 'status': 'in progress', - 'iguanaAPI': { - 'method': key, - 'coin': coin, - 'address': address, - 'status': 'done', - 'resp': _body - } - } - } - }); - - outObj.basilisk[coin][address][key] = {}; - outObj.basilisk[coin][address][key].data = JSON.parse(body); - outObj.basilisk[coin][address][key].timestamp = Date.now(); // add timestamp - outObj.basilisk[coin][address][key].status = 'done'; - cache.shepherd.log(dexUrl); - cache.shepherd.log(body); - callStack[coin]--; - cache.shepherd.log(`${coin} _stack len ${callStack[coin]}`); - cache.io.emit('messages', { - message: { - shepherd: { - method: 'cache-one', - status: 'in progress', - iguanaAPI: { - currentStackLength: callStack[coin], - }, - }, - }, - }); - checkCallStack(); - writeCache(); - } else { - cache.shepherd.log(`explorer ${key} for address ${address} fallback failed`); - outObj.basilisk[coin][address][key] = {}; - outObj.basilisk[coin][address][key].data = { 'error': 'request failed' }; - outObj.basilisk[coin][address][key].timestamp = 1471620867 // add timestamp - outObj.basilisk[coin][address][key].status = 'done'; - callStack[coin]--; - cache.shepherd.log(`${coin} request ${key} for address ${address} failed`); - cache.shepherd.log(`${coin} _stack len ${callStack[coin]}`); - cache.io.emit('messages', { - message: { - shepherd: { - method: 'cache-one', - status: 'in progress', - iguanaAPI: { - currentStackLength: callStack[coin], - }, - }, - }, - }); - checkCallStack(); - writeCache(); - } - }); - } else { - outObj.basilisk[coin][address][key] = {}; - outObj.basilisk[coin][address][key].data = { 'error': 'request failed' }; - outObj.basilisk[coin][address][key].timestamp = 1471620867 // add timestamp - outObj.basilisk[coin][address][key].status = 'done'; - callStack[coin]--; - cache.shepherd.log(`${coin} request ${key} for address ${address} failed`); - cache.shepherd.log(`${coin} _stack len ${callStack[coin]}`); - cache.io.emit('messages', { - message: { - shepherd: { - method: 'cache-one', - status: 'in progress', - iguanaAPI: { - currentStackLength: callStack[coin], - }, - }, - }, - }); - checkCallStack(); - writeCache(); - } - } - }); - } else { - cache.shepherd.log(`${key} is fresh, check back in ${(cacheGlobLifetime - checkTimestamp(outObj.basilisk[coin][address][key].timestamp))}s`); - callStack[coin]--; - cache.shepherd.log(`${coin} _stack len ${callStack[coin]}`); - cache.io.emit('messages', { - message: { - shepherd: { - method: 'cache-one', - status: 'in progress', - iguanaAPI: { - currentStackLength: callStack[coin], - }, - }, - }, - }); - checkCallStack(); - } - }); - } - - function parseAddresses(coin, addrArray) { - cache.io.emit('messages', { - message: { - shepherd: { - method: 'cache-one', - status: 'in progress', - iguanaAPI: { - method: 'getaddressesbyaccount', - coin: coin, - status: 'done', - resp: addrArray, - }, - }, - }, - }); - outObj.basilisk[coin].addresses = addrArray; - cache.shepherd.log(addrArray); - writeCache(); - - const addrCount = outObj.basilisk[coin].addresses ? outObj.basilisk[coin].addresses.length : 0; - let callsArrayBTC = callsArray.length; - - if (callsArray.indexOf('getbalance') > - 1) { - callsArrayBTC--; - } - if (callsArray.indexOf('refresh') > - 1) { - callsArrayBTC--; - } - callStack[coin] = callStack[coin] + addrCount * (coin === 'BTC' || coin === 'SYS' ? callsArrayBTC : callsArray.length); - cache.shepherd.log(`${coin} stack len ${callStack[coin]}`); - - cache.io.emit('messages', { - message: { - shepherd: { - method: 'cache-one', - status: 'in progress', - iguanaAPI: { - totalStackLength: callStack[coin], - }, - }, - }, - }); - async.each(outObj.basilisk[coin].addresses, function(address) { - execDEXRequests(coin, address); - }); - } - - function getAddresses(coin) { - if (addresses) { - parseAddresses(coin, addresses); - } else { - const tempUrl = `http://${cache.appConfig.host}:${cache.appConfig.iguanaCorePort}/api/bitcoinrpc/getaddressesbyaccount?userpass=${sessionKey}&coin=${coin}&account=*`; - request({ - url: mock ? `http://localhost:17777/shepherd/mock?url=${tempUrl}` : tempUrl, - method: 'GET' - }, function(error, response, body) { - if (response && - response.statusCode && - response.statusCode === 200) { - parseAddresses(coin, JSON.parse(body).result); - } else { - // TODO: error - } - }); - } - } - - // update all available coin addresses - if (!address) { - cache.io.emit('messages', { - message: { - shepherd: { - method: 'cache-one', - status: 'in progress', - iguanaAPI: { - method: 'getaddressesbyaccount', - coin: coin, - status: 'in progress', - }, - }, - }, - }); - - if (coin === 'all') { - const tempUrl = `http://${cache.appConfig.host}:${cache.appConfig.iguanaCorePort}/api/InstantDEX/allcoins?userpass=${sessionKey}`; - request({ - url: mock ? `http://localhost:17777/shepherd/mock?url=${tempUrl}` : tempUrl, - method: 'GET' - }, function(error, response, body) { - if (response && - response.statusCode && - response.statusCode === 200) { - cache.shepherd.log(JSON.parse(body).basilisk); - cache.io.emit('messages', { - message: { - shepherd: { - method: 'cache-one', - status: 'in progress', - iguanaAPI: { - method: 'allcoins', - status: 'done', - resp: body, - }, - }, - }, - }); - body = JSON.parse(body); - // basilisk coins - if (body.basilisk && - body.basilisk.length) { - // get coin addresses - async.each(body.basilisk, function(coin) { - callStack[coin] = 1; - }); - - async.each(body.basilisk, function(coin) { - outObj.basilisk[coin] = {}; - writeCache(); - - cache.io.emit('messages', { - message: { - shepherd: { - method: 'cache-one', - status: 'in progress', - iguanaAPI: { - method: 'getaddressesbyaccount', - coin: coin, - status: 'in progress', - }, - }, - }, - }); - - getAddresses(coin); - }); - } - } - if (error) { // stop further requests on failure, exit - callStack[coin] = 1; - checkCallStack(); - } - }); - } else { - getAddresses(coin); - } - } else { - let callsArrayBTC = callsArray.length; // restrict BTC and SYS only to listunspent and listtransactions calls - - if (callsArray.indexOf('getbalance') > - 1) { - callsArrayBTC--; - } - if (callsArray.indexOf('refresh') > - 1) { - callsArrayBTC--; - } - - callStack[coin] = callStack[coin] + (coin === 'BTC' || coin === 'SYS' ? callsArrayBTC : callsArray.length); - cache.shepherd.log(`${coin} stack len ${callStack[coin]}`); - - cache.io.emit('messages', { - message: { - shepherd: { - method: 'cache-one', - status: 'in progress', - iguanaAPI: { - totalStackLength: callStack[coin], - currentStackLength: callStack[coin], - }, - }, - }, - }); - - execDEXRequests(coin, address); - } - } else { - cache.io.emit('messages', { - message: { - shepherd: { - method: 'cache-all', - status: 'done', - resp: 'internal error', - }, - }, - }); - cacheCallInProgress = false; - } - } else { - res.end(JSON.stringify({ - msg: 'error', - result: 'another call is in progress already', - })); - } -}; - -module.exports = cache; \ No newline at end of file diff --git a/routes/electrumjs/electrumServers.js b/routes/electrumjs/electrumServers.js new file mode 100644 index 0000000..ba69081 --- /dev/null +++ b/routes/electrumjs/electrumServers.js @@ -0,0 +1,262 @@ +let electrumServers = { + /*zcash: { + address: '173.212.225.176', + port: 50032, + proto: 'tcp', + },*/ + coqui: { // !estimatefee + address: 'electrum1.cipig.net', + port: 10011, + proto: 'tcp', + txfee: 10000, + abbr: 'COQUI', + serverList: [ + 'electrum1.cipig.net:10011', + 'electrum2.cipig.net:10011' + ], + }, + revs: { // !estimatefee + address: 'electrum1.cipig.net', + port: 10003, + proto: 'tcp', + txfee: 10000, + abbr: 'REVS', + serverList: [ + 'electrum1.cipig.net:10003', + 'electrum2.cipig.net:10003' + ], + }, + supernet: { // !estimatefee + address: 'electrum1.cipig.net', + port: 10005, + proto: 'tcp', + txfee: 10000, + abbr: 'SUPERNET', + serverList: [ + 'electrum1.cipig.net:10005', + 'electrum2.cipig.net:10005' + ], + }, + dex: { // !estimatefee + address: 'electrum1.cipig.net', + port: 10006, + proto: 'tcp', + txfee: 10000, + abbr: 'DEX', + serverList: [ + 'electrum1.cipig.net:10006', + 'electrum2.cipig.net:10006' + ], + }, + bots: { // !estimatefee + address: 'electrum1.cipig.net', + port: 10007, + proto: 'tcp', + txfee: 10000, + abbr: 'BOTS', + serverList: [ + 'electrum1.cipig.net:10007', + 'electrum2.cipig.net:10007' + ], + }, + crypto: { // !estimatefee + address: 'electrum1.cipig.net', + port: 10008, + proto: 'tcp', + txfee: 10000, + abbr: 'CRYPTO', + serverList: [ + 'electrum1.cipig.net:10008', + 'electrum2.cipig.net:10008' + ], + }, + hodl: { // !estimatefee + address: 'electrum1.cipig.net', + port: 10009, + proto: 'tcp', + txfee: 10000, + abbr: 'HODL', + serverList: [ + 'electrum1.cipig.net:10009', + 'electrum2.cipig.net:10009' + ], + }, + pangea: { // !estimatefee + address: 'electrum1.cipig.net', + port: 10010, + proto: 'tcp', + txfee: 10000, + abbr: 'PANGEA', + serverList: [ + 'electrum1.cipig.net:10010', + 'electrum2.cipig.net:10010' + ], + }, + bet: { // !estimatefee + address: 'electrum1.cipig.net', + port: 10012, + proto: 'tcp', + txfee: 10000, + abbr: 'BET', + serverList: [ + 'electrum1.cipig.net:10012', + 'electrum2.cipig.net:10012' + ], + }, + mshark: { // !estimatefee + address: 'electrum1.cipig.net', + port: 10013, + proto: 'tcp', + txfee: 10000, + abbr: 'MSHARK', + serverList: [ + 'electrum1.cipig.net:10013', + 'electrum2.cipig.net:10013' + ], + }, + mnz: { // !estimatefee + address: 'electrum1.cipig.net', + port: 10002, + proto: 'tcp', + txfee: 10000, + abbr: 'MNZ', + serverList: [ + 'electrum1.cipig.net:10002', + 'electrum2.cipig.net:10002'/*, + '18.216.195.109:10002', + '52.41.58.116:10002', + '52.67.48.29:10002', + '13.124.87.194:10002', + '52.63.107.102:10002'*/ + ], + }, + wlc: { // !estimatefee + address: 'electrum1.cipig.net', + port: 10014, + proto: 'tcp', + txfee: 10000, + abbr: 'WLC', + serverList: [ + 'electrum1.cipig.net:10014', + 'electrum2.cipig.net:10014' + ], + }, + jumblr: { // !estimatefee + address: 'electrum1.cipig.net', + port: 10004, + proto: 'tcp', + txfee: 10000, + abbr: 'JUMBLR', + serverList: [ + 'electrum1.cipig.net:10004', + 'electrum2.cipig.net:10004' + ], + }, + komodo: { // !estimatefee + address: 'electrum1.cipig.net', + port: 10001, + proto: 'tcp', + txfee: 10000, + abbr: 'KMD', + serverList: [ + 'electrum1.cipig.net:10001', + 'electrum2.cipig.net:10001', + ], + }, + dogecoin: { // !estimatefee + address: '173.212.225.176', + port: 50015, + proto: 'tcp', + txfee: 100000000, + abbr: 'DOGE', + }, + viacoin: { // !estimatefee + address: 'vialectrum.bitops.me', + port: 50002, + proto: 'ssl', + txfee: 100000, + abbr: 'VIA', + }, + vertcoin: { + address: '173.212.225.176', + port: 50088, + proto: 'tcp', + txfee: 100000, + abbr: 'VTC', + }, + namecoin: { + address: '173.212.225.176', + port: 50036, + proto: 'tcp', + txfee: 100000, + abbr: 'NMC', + }, + monacoin: { // !estimatefee + address: '173.212.225.176', + port: 50002, + proto: 'tcp', + txfee: 100000, + abbr: 'MONA', + }, + litecoin: { + address: '173.212.225.176', + port: 50012, + proto: 'tcp', + txfee: 10000, + abbr: 'LTC', + }, + faircoin: { + address: '173.212.225.176', + port: 50005, + proto: 'tcp', + txfee: 1000000, + abbr: 'FAIR', + }, + digibyte: { + address: '173.212.225.176', + port: 50022, + proto: 'tcp', + txfee: 100000, + abbr: 'DGB', + }, + dash: { + address: '173.212.225.176', + port: 50098, + proto: 'tcp', + txfee: 10000, + abbr: 'DASH', + }, + crown: { + address: '173.212.225.176', + port: 50041, + proto: 'tcp', + txfee: 10000, + abbr: 'CRW', + }, + bitcoin: { + address: '173.212.225.176', + port: 50001, + proto: 'tcp', + abbr: 'BTC', + }, + argentum: { // !estimatefee + address: '173.212.225.176', + port: 50081, + proto: 'tcp', + txfee: 50000, + abbr: 'ARG', + }, + chips: { // !estimatefee + address: '173.212.225.176', + port: 50076, + proto: 'tcp', + txfee: 10000, + abbr: 'CHIPS', + serverList: [ + '173.212.225.176:50076', + '136.243.45.140:50076' + ], + }, +}; + +module.exports = electrumServers; \ No newline at end of file diff --git a/routes/electrumjs/electrumjs.core.js b/routes/electrumjs/electrumjs.core.js new file mode 100644 index 0000000..282cd12 --- /dev/null +++ b/routes/electrumjs/electrumjs.core.js @@ -0,0 +1,354 @@ +/* +MIT License + +Copyright (c) 2017 Yuki Akiyama, SuperNET + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +const tls = require('tls'); +const net = require('net'); +const EventEmitter = require('events').EventEmitter; +const SOCKET_MAX_TIMEOUT = 10000; + +const makeRequest = function(method, params, id) { + return JSON.stringify({ + jsonrpc : '2.0', + method : method, + params : params, + id : id, + }); +} + +const createRecursiveParser = function(maxDepth, delimiter) { + const MAX_DEPTH = maxDepth; + const DELIMITER = delimiter; + const recursiveParser = function(n, buffer, callback) { + if (buffer.length === 0) { + return { + code: 0, + buffer: buffer, + }; + } + + if (n > MAX_DEPTH) { + return { + code: 1, + buffer: buffer, + }; + } + + const xs = buffer.split(DELIMITER); + + if (xs.length === 1) { + return { + code: 0, + buffer: buffer, + }; + } + + callback(xs.shift(), n); + + return recursiveParser(n + 1, xs.join(DELIMITER), callback); + } + + return recursiveParser; +} + +const createPromiseResult = function(resolve, reject) { + return (err, result) => { + if (err) { + console.log('electrum error:'); + console.log(err); + resolve(err); + // reject(err); + } else { + resolve(result); + } + } +} + +class MessageParser { + constructor(callback) { + this.buffer = ''; + this.callback = callback; + this.recursiveParser = createRecursiveParser(20, '\n'); + } + + run(chunk) { + this.buffer += chunk; + + while (true) { + const res = this.recursiveParser(0, this.buffer, this.callback); + + this.buffer = res.buffer; + + if (res.code === 0) { + break; + } + } + } +} + +const util = { + makeRequest, + createRecursiveParser, + createPromiseResult, + MessageParser, +}; + +const getSocket = function(protocol, options) { + switch (protocol) { + case 'tcp': + return new net.Socket(); + case 'tls': + // todo + case 'ssl': + return new tls.TLSSocket(options); + } + + throw new Error('unknown protocol'); +} + +const initSocket = function(self, protocol, options) { + const conn = getSocket(protocol, options); + + conn.setTimeout(SOCKET_MAX_TIMEOUT); + conn.on('timeout', () => { + console.log('socket timeout'); + self.onError(new Error('socket timeout')); + self.onClose(); + }); + conn.setEncoding('utf8'); + conn.setKeepAlive(true, 0); + conn.setNoDelay(true); + conn.on('connect', () => { + self.onConnect(); + }); + conn.on('close', (e) => { + self.onClose(e); + }); + conn.on('data', (chunk) => { + self.onReceive(chunk); + }); + conn.on('end', (e) => { + self.onEnd(e); + }); + conn.on('error', (e) => { + self.onError(e); + }); + + return conn; +} + +class Client { + constructor(port, host, protocol = 'tcp', options = void 0) { + this.id = 0; + this.port = port; + this.host = host; + this.callbackMessageQueue = {}; + this.subscribe = new EventEmitter(); + this.conn = initSocket(this, protocol, options); + this.mp = new util.MessageParser((body, n) => { + this.onMessage(body, n); + }); + this.status = 0; + } + + connect() { + if (this.status) { + return Promise.resolve(); + } + + this.status = 1; + + return new Promise((resolve, reject) => { + const errorHandler = (e) => reject(e) + + this.conn.connect(this.port, this.host, () => { + this.conn.removeListener('error', errorHandler); + resolve(); + }); + this.conn.on('error', errorHandler); + }); + } + + close() { + if (!this.status) { + return + } + + this.conn.end(); + this.conn.destroy(); + this.status = 0; + } + + request(method, params) { + if (!this.status) { + return Promise.reject(new Error('ESOCKET')); + } + + return new Promise((resolve, reject) => { + const id = ++this.id; + const content = util.makeRequest(method, params, id); + + this.callbackMessageQueue[id] = util.createPromiseResult(resolve, reject); + this.conn.write(`${content}\n`); + }); + } + + response(msg) { + const callback = this.callbackMessageQueue[msg.id]; + + if (callback) { + delete this.callbackMessageQueue[msg.id]; + + if (msg.error) { + callback(msg.error); + } else { + callback(null, msg.result); + } + } else { + // can't get callback + } + } + + onMessage(body, n) { + const msg = JSON.parse(body); + + if (msg instanceof Array) { + // don't support batch request + } else { + if (msg.id !== void 0) { + this.response(msg); + } else { + this.subscribe.emit(msg.method, msg.params); + } + } + } + + onConnect() { + } + + onClose() { + Object.keys(this.callbackMessageQueue).forEach((key) => { + this.callbackMessageQueue[key](new Error('close connect')); + delete this.callbackMessageQueue[key]; + }); + } + + onReceive(chunk) { + this.mp.run(chunk); + } + + onEnd() { + } + + onError(e) { + } +} + +class ElectrumJSCore extends Client { + constructor(protocol, port, host, options) { + super(protocol, port, host, options); + } + + onClose() { + super.onClose(); + const list = [ + 'server.peers.subscribe', + 'blockchain.numblocks.subscribe', + 'blockchain.headers.subscribe', + 'blockchain.address.subscribe' + ]; + + list.forEach(event => this.subscribe.removeAllListeners(event)); + } + + // ref: http://docs.electrum.org/en/latest/protocol.html + serverVersion(client_name, protocol_version) { + return this.request('server.version', [client_name, protocol_version]); + } + + serverBanner() { + return this.request('server.banner', []); + } + + serverDonationAddress() { + return this.request('server.donation_address', []); + } + + serverPeersSubscribe() { + return this.request('server.peers.subscribe', []); + } + + blockchainAddressGetBalance(address) { + return this.request('blockchain.address.get_balance', [address]); + } + + blockchainAddressGetHistory(address) { + return this.request('blockchain.address.get_history', [address]); + } + + blockchainAddressGetMempool(address) { + return this.request('blockchain.address.get_mempool', [address]); + } + + blockchainAddressListunspent(address) { + return this.request('blockchain.address.listunspent', [address]); + } + + blockchainBlockGetHeader(height) { + return this.request('blockchain.block.get_header', [height]); + } + + blockchainBlockGetChunk(index) { + return this.request('blockchain.block.get_chunk', [index]); + } + + blockchainEstimatefee(number) { + return this.request('blockchain.estimatefee', [number]); + } + + blockchainHeadersSubscribe() { + return this.request('blockchain.headers.subscribe', []); + } + + blockchainNumblocksSubscribe() { + return this.request('blockchain.numblocks.subscribe', []); + } + + blockchainRelayfee() { + return this.request('blockchain.relayfee', []); + } + + blockchainTransactionBroadcast(rawtx) { + return this.request('blockchain.transaction.broadcast', [rawtx]); + } + + blockchainTransactionGet(tx_hash, height) { + return this.request('blockchain.transaction.get', [tx_hash, height]); + } + + blockchainTransactionGetMerkle(tx_hash, height) { + return this.request('blockchain.transaction.get_merkle', [tx_hash, height]); + } +} + +module.exports = ElectrumJSCore; \ No newline at end of file diff --git a/routes/electrumjs/electrumjs.networks.js b/routes/electrumjs/electrumjs.networks.js new file mode 100644 index 0000000..d49015c --- /dev/null +++ b/routes/electrumjs/electrumjs.networks.js @@ -0,0 +1,205 @@ +'use strict' +var bitcoin = require('bitcoinjs-lib'); + +var networks = exports; +Object.keys(bitcoin.networks).forEach(function(key){ + networks[key] = bitcoin.networks[key] +}); + +networks.litecoin = { + messagePrefix: '\x19Litecoin Signed Message:\n', + bip32: { + public: 0x019da462, + private: 0x019d9cfe + }, + pubKeyHash: 0x30, + scriptHash: 0x32, + wif: 0xb0, + dustThreshold: 0, // https://github.com/litecoin-project/litecoin/blob/v0.8.7.2/src/main.cpp#L360-L365 +} + +networks.dogecoin = { + messagePrefix: '\x19Dogecoin Signed Message:\n', + bip32: { + public: 0x02facafd, + private: 0x02fac398, + }, + pubKeyHash: 0x1e, + scriptHash: 0x16, + wif: 0x9e, + dustThreshold: 0 // https://github.com/dogecoin/dogecoin/blob/v1.7.1/src/core.h#L155-L160 +}; + +// https://github.com/monacoinproject/monacoin/blob/master-0.10/src/chainparams.cpp#L161 +networks.monacoin = { + messagePrefix: '\x19Monacoin Signed Message:\n', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4, + }, + pubKeyHash: 0x32, + scriptHash: 0x05, + wif: 0xB2, + dustThreshold: 546, // https://github.com/bitcoin/bitcoin/blob/v0.9.2/src/core.h#L151-L162 +}; + + +// https://github.com/gamecredits-project/GameCredits/blob/master/src/chainparams.cpp#L136 +networks.game = { + messagePrefix: '\x19GameCredits Signed Message:\n', + bip32: { + public: 0x043587cf, + private: 0x04358394, + }, + pubKeyHash: 0x6f, + scriptHash: 0xc4, + wif: 0xef, + dustThreshold: 546, // https://github.com/bitcoin/bitcoin/blob/v0.9.2/src/core.h#L151-L162 +}; + +// https://github.com/dashpay/dash/blob/master/src/chainparams.cpp#L171 +networks.dash = { + messagePrefix: '\x19DarkCoin Signed Message:\n', + bip32: { + public: 0x02fe52f8, + private: 0x02fe52cc, + }, + pubKeyHash: 0x4c, + scriptHash: 0x10, + wif: 0xcc, + dustThreshold: 5460, // https://github.com/dashpay/dash/blob/v0.12.0.x/src/primitives/transaction.h#L144-L155 +}; + +// https://github.com/zcoinofficial/zcoin/blob/c93eccb39b07a6132cb3d787ac18be406b24c3fa/src/base58.h#L275 +networks.zcoin = { + messagePrefix: '\x19ZCoin Signed Message:\n', + bip32: { + public: 0x0488b21e, // todo + private: 0x0488ade4, // todo + }, + pubKeyHash: 0x52, + scriptHash: 0x07, + wif: 0x52 + 128, + dustThreshold: 1000, // https://github.com/zcoinofficial/zcoin/blob/f755f95a036eedfef7c96bcfb6769cb79278939f/src/main.h#L59 +}; + +// https://raw.githubusercontent.com/jl777/komodo/beta/src/chainparams.cpp +networks.komodo = { + messagePrefix: '\x19Komodo Signed Message:\n', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4, + }, + pubKeyHash: 0x3c, + scriptHash: 0x55, + wif: 0xbc, + dustThreshold: 1000, +}; + +networks.viacoin = { + messagePrefix: '\x19Viacoin Signed Message:\n', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4, + }, + pubKeyHash: 0x47, + scriptHash: 0x21, + wif: 0xc7, + dustThreshold: 1000, +}; + +networks.vertcoin = { + messagePrefix: '\x19Vertcoin Signed Message:\n', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4, + }, + pubKeyHash: 0x47, + scriptHash: 0x5, + wif: 0x80, + dustThreshold: 1000, +}; + +networks.namecoin = { + messagePrefix: '\x19Namecoin Signed Message:\n', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4, + }, + pubKeyHash: 0x34, + scriptHash: 0xd, + wif: 0xb4, + dustThreshold: 1000, +}; + +networks.faircoin = { + messagePrefix: '\x19Faircoin Signed Message:\n', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4, + }, + pubKeyHash: 0x5f, + scriptHash: 0x24, + wif: 0xdf, + dustThreshold: 1000, +}; + +networks.digibyte = { + messagePrefix: '\x19Digibyte Signed Message:\n', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4, + }, + pubKeyHash: 0x1e, + scriptHash: 0x5, + wif: 0x80, + dustThreshold: 1000, +}; + +networks.crown = { + messagePrefix: '\x19Crown Signed Message:\n', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4, + }, + pubKeyHash: 0x0, + scriptHash: 0x1c, + wif: 0x80, + dustThreshold: 1000, +}; + +networks.argentum = { + messagePrefix: '\x19Argentum Signed Message:\n', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4, + }, + pubKeyHash: 0x17, + scriptHash: 0x5, + wif: 0x97, + dustThreshold: 1000, +}; + +networks.chips = { + messagePrefix: '\x19Chips Signed Message:\n', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4, + }, + pubKeyHash: 0x3c, + scriptHash: 0x55, + wif: 0xbc, + dustThreshold: 1000, +}; + +/*networks.zcash = { + messagePrefix: '\x19Zcash Signed Message:\n', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4, + }, + pubKeyHash: 0x1cb8, + scriptHash: 0x1cbd, + wif: 0x80, + dustThreshold: 1000, +};*/ \ No newline at end of file diff --git a/routes/electrumjs/electrumjs.txdecoder.js b/routes/electrumjs/electrumjs.txdecoder.js new file mode 100644 index 0000000..f956249 --- /dev/null +++ b/routes/electrumjs/electrumjs.txdecoder.js @@ -0,0 +1,120 @@ +/* +MIT License + +Copyright (c) 2017 Yuki Akiyama, SuperNET + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +var bitcoin = require('bitcoinjs-lib'); + +var decodeFormat = function(tx) { + var result = { + txid: tx.getId(), + version: tx.version, + locktime: tx.locktime, + }; + + return result; +} + +var decodeInput = function(tx) { + var result = []; + + tx.ins.forEach(function(input, n) { + var vin = { + txid: input.hash.reverse().toString('hex'), + n: input.index, + script: bitcoin.script.toASM(input.script), + sequence: input.sequence, + }; + + result.push(vin); + }); + + return result; +} + +var decodeOutput = function(tx, network) { + var format = function(out, n, network) { + var vout = { + satoshi: out.value, + value: (1e-8 * out.value).toFixed(8), + n: n, + scriptPubKey: { + asm: bitcoin.script.toASM(out.script), + hex: out.script.toString('hex'), + type: bitcoin.script.classifyOutput(out.script), + addresses: [], + }, + }; + + switch(vout.scriptPubKey.type) { + case 'pubkeyhash': + vout.scriptPubKey.addresses.push(bitcoin.address.fromOutputScript(out.script, network)); + break; + case 'pubkey': + const pubKeyBuffer = new Buffer(vout.scriptPubKey.asm.split(' ')[0], 'hex'); + vout.scriptPubKey.addresses.push(bitcoin.ECPair.fromPublicKeyBuffer(pubKeyBuffer, network).getAddress()); + break; + case 'scripthash': + vout.scriptPubKey.addresses.push(bitcoin.address.fromOutputScript(out.script, network)); + break; + } + + return vout; + } + + var result = []; + + tx.outs.forEach(function(out, n) { + result.push(format(out, n, network)); + }); + + return result; +} + +var TxDecoder = module.exports = function(rawtx, network) { + try { + const _tx = bitcoin.Transaction.fromHex(rawtx); + + return { + tx: _tx, + network: network, + format: decodeFormat(_tx), + inputs: decodeInput(_tx), + outputs: decodeOutput(_tx, network), + }; + } catch (e) { + return false; + } +} + +TxDecoder.prototype.decode = function() { + var result = {}; + var self = this; + + Object.keys(self.format).forEach(function(key) { + result[key] = self.format[key]; + }); + + result.outputs = self.outputs; + + return result; +} \ No newline at end of file diff --git a/routes/fetchparams.js b/routes/fetchparams.js deleted file mode 100644 index 53fd0c9..0000000 --- a/routes/fetchparams.js +++ /dev/null @@ -1,197 +0,0 @@ -var app = require('http').createServer(handler), - io = require('socket.io')(app), - fs = require('fs'), - request = require('request'), - progress = require('request-progress'); -const path = require('path'), - url = require('url'), - os = require('os'), - sha256 = require('sha256'), - crypto = require('crypto'); - -Promise = require('bluebird'); - -app.listen(3000); - -function handler (req, res) { - fs.readFile(__dirname + '/index.html', - function (err, data) { - if (err) { - res.writeHead(500); - return res.end('Error loading index.html'); - } - - res.writeHead(200); - res.end(data); - }); -} - -if (os.platform() === 'darwin') { - var PARAMS_DIR = process.env.HOME + '/Library/Application Support/ZcashParams'; -} -if (os.platform() === 'linux') { - var PARAMS_DIR = process.env.HOME + '/.zcash-params'; -} - -var SPROUT_FILES_DATA = [ - { - 'file': 'sprout-proving.key', - 'hash': '8bc20a7f013b2b58970cddd2e7ea028975c88ae7ceb9259a5344a16bc2c0eef7' - }, { - 'file': 'sprout-verifying.key', - 'hash': '4bd498dae0aacfd8e98dc306338d017d9c08dd0918ead18172bd0aec2fc5df82' - } -]; -var SPROUT_DL_URL = 'https://z.cash/downloads/'; - -SPROUT_FILES_DATA.forEach(function(value, index) { - fs.exists(value.file, function(exists) { - if (exists) { - console.log(value.file + ' already exists at location.'); - - var tmphas, - fd = fs.createReadStream(value.file), - hash = crypto.createHash('sha256'); - - hash.setEncoding('hex'); - - fd.on('end', function() { - hash.end(); - - console.log('hash is: '); - console.log(hash.read()); // the desired sha1sum - console.log(value.hash); - - tmphash = hash.read(); - if (hash.read() === value.hash) { - console.log('File SHA256 sum matches.'); - } else { - console.log('File SHA256 sum does not match.'); - } - }); - // read all file and pipe it (write it) to the hash object - fd.pipe(hash); - } else { - var DLFile = function() { - return new Promise(function(resolve, reject) { - console.log('file not there.'); - - progress(request(SPROUT_DL_URL + value.file), {}) - .on('progress', function (state) { - console.log('progress', state); - }) - .on('error', function (err) { - console.log(err); - }) - .on('end', function () { - // Do something after request finishes - console.log('download finished.'); - var result = 'File ==> ' + value.file + ': DOWNLOADED'; - }) - .pipe(fs.createWriteStream(value.file)); - - console.log(result); - resolve(result); - }) - } - var CheckFileSHA = function() { - return new Promise(function(resolve, reject) { - var fd = fs.createReadStream(value.file), - hash = crypto.createHash('sha256'); - - hash.setEncoding('hex'); - - fd.on('end', function() { - hash.end(); - - console.log('hash is: '); - console.log(hash.read()); // the desired sha1sum - console.log(value.hash); - - if (hash.read() === value.hash) { - console.log('File SHA256 sum matches.'); - } else { - console.log('File SHA256 sum does not match.'); - } - }); - // read all file and pipe it (write it) to the hash object - fd.pipe(hash); - - var result = 'SHA256 SUM Check: DONE'; - - console.log(result); - resolve(result); - }); - } - - DLFile() - .then(function(result) { - return CheckFileSHA(); - }); - } - }); -}); - -function CheckSHASum(file, hashstr) { - console.log(hashstr); - var shasum; - - // the file you want to get the hash - if (shasum === hashstr ) { - return true; - } else { - return false; - } -} - -/*var CheckFileExists = function() { - - return new Promise(function(resolve, reject) { - - if (path.existsSync('foo.txt')) {} - var result = 'Connecting To Pm2: done' - - console.log(result) - resolve(result); - }) -} - -var DLFile = function() { - - return new Promise(function(resolve, reject) { - var result = 'Killing Pm2: done' - - setTimeout(function() { - console.log(result) - resolve(result); - }, 2000) - }) -} - -var CheckSHASum = function() { - - return new Promise(function(resolve, reject) { - var result = 'Hiding Main Window: done' - - console.log(result) - resolve(result); - }) -} - -var MoveFile = function() { - - return new Promise(function(resolve, reject) { - var result = 'Quiting App: done' - - console.log(result) - resolve(result); - }) -} - -ConnectToPm2() -.then(function(result) { - return KillPm2(); -}) -.then(HideMainWindow) -.then(QuitApp) -*/ \ No newline at end of file diff --git a/routes/mock.js b/routes/mock.js deleted file mode 100644 index 163f6f8..0000000 --- a/routes/mock.js +++ /dev/null @@ -1,45 +0,0 @@ -const fs = require('fs-extra'), - request = require('request'), - async = require('async'), - url = require('url'); - -let mock = {}; - -mock.setVar = function(variable, value) { - mock[variable] = value; -} - -mock.get = function(req, res, next) { - const _url = req.query.url; - - if (_url.indexOf('/InstantDEX/allcoins') > -1) { - res.end(JSON.stringify({ - 'native': [], - 'basilisk': [ 'KMD', 'BTC'], - 'full':[], - 'tag': '18430609759584422959' - })); - } - if (_url.indexOf('/bitcoinrpc/getaddressesbyaccount') > -1) { - console.log(_url.indexOf('/bitcoinrpc/getaddressesbyaccount')); - res.end(JSON.stringify({ - 'result': [ - "RDbGxL8QYdEp8sMULaVZS2E6XThcTKT9Jd", - "RL4orv22Xch7PhM5w9jUHhVQhX6kF6GkfS", - "RUrxvPTEKGWEDTvAtgiqbUTTFE53Xdpj8a", - "RPJoLDa7RezvfUUBr7R3U8wrP16AgUsNw3", - "RQPTpRJEeafNx5hkDzgjcsPyU4E8RFVApT" - ] - })); - } - if (_url.indexOf('/api/dex/listunspent') > -1 || - _url.indexOf('/api/dex/listtransactions') > -1 || - _url.indexOf('/api/ss/getbalance') > -1 || - _url.indexOf('/api/ww/refresh') > -1) { - res.end(JSON.stringify({ - 'some key': 'some value' - })); - } -} - -module.exports = mock; \ No newline at end of file diff --git a/routes/nativeCoind.js b/routes/nativeCoind.js new file mode 100644 index 0000000..682888b --- /dev/null +++ b/routes/nativeCoind.js @@ -0,0 +1,98 @@ +const nativeCoind = { + 'btc': { + name: 'Bitcoin', + bin: 'bitcoin', + fullMode: true, + port: 8332, + }, + 'btcd': { + name: 'BitcoinDark', + bin: 'bitcoindarkd', + fullMode: true, + port: 14632, + }, + 'ltc': { + name: 'Litecoin', + bin: 'litecoin', + fullMode: true, + port: 9332, + }, + 'sys': { + name: 'Syscoin', + bin: 'syscoin', + fullMode: true, + port: 8368, + }, + 'uno': { + name: 'Unobtanium', + bin: 'unobtanium', + fullMode: true, + port: 65535, + }, + 'nmc': { + name: 'Namecoin', + bin: 'namecoin', + fullMode: true, + port: 8336, + }, + 'game': { + name: 'GameCredits', + bin: 'gamecredits', + fullMode: true, + port: 40001, + }, + 'mzc': { + name: 'MazaCoin', + bin: 'maza', + fullMode: true, + port: 12832, + }, + 'frk': { + name: 'Franko', + bin: 'franko', + fullMode: true, + port: 7913, + }, + 'doge': { + name: 'Dogecoin', + bin: 'dogecoin', + fullMode: true, + port: 22555, + }, + 'dgb': { + name: 'Digibyte', + bin: 'digibyte', + port: 14022, + }, + 'zet': { + name: 'Zetacoin', + bin: 'zetacoin', + fullMode: true, + port: 17335, + }, + 'btm': { + name: 'Bitmark', + bin: 'bitmark', + fullMode: true, + port: 9266, + }, + 'carb': { + name: 'Carboncoin', + bin: 'carboncoin', + fullMode: true, + port: 9351, + }, + 'anc': { + name: 'Anoncoin', + bin: 'anoncoin', + fullMode: true, + port: 28332, + }, + 'lbc': { + name: 'LBRY Credits', + bin: 'lbrycrd', + port: 9245, + } +}; + +module.exports = nativeCoind; \ No newline at end of file diff --git a/routes/ports.js b/routes/ports.js index e6ea8f4..05526fc 100644 --- a/routes/ports.js +++ b/routes/ports.js @@ -1,7 +1,9 @@ const assetChainPorts = { 'komodod': '7771', + 'CHIPS': '57776', 'SUPERNET': '11341', 'REVS': '10196', + 'MNZ': '14337', 'WLC': '12167', 'PANGEA': '14068', 'DEX': '11890', @@ -9,7 +11,7 @@ const assetChainPorts = { 'BET': '14250', 'CRYPTO': '8516', 'HODL': '14431', - 'SHARK': '10114', + 'MSHARK': '8846', 'BOTS': '11964', 'MGW': '12386', 'COQUI': '14276', diff --git a/routes/shepherd.js b/routes/shepherd.js index 80ecd1e..3fff883 100644 --- a/routes/shepherd.js +++ b/routes/shepherd.js @@ -1,3371 +1,145 @@ -const electron = require('electron'), - app = electron.app, - BrowserWindow = electron.BrowserWindow, - path = require('path'), - url = require('url'), - os = require('os'), - fsnode = require('fs'), - fs = require('fs-extra'), - _fs = require('graceful-fs'), - express = require('express'), - exec = require('child_process').exec, - spawn = require('child_process').spawn, - md5 = require('./md5'), - pm2 = require('pm2'), - request = require('request'), - async = require('async'), - portscanner = require('portscanner'), - aes256 = require('nodejs-aes256'), - AdmZip = require('adm-zip'), - remoteFileSize = require('remote-file-size'), - Promise = require('bluebird'), - {shell} = require('electron'), - {execFile} = require('child_process'); - -const fixPath = require('fix-path'); -var ps = require('ps-node'), - setconf = require('../private/setconf.js'), - assetChainPorts = require('./ports.js'), - _appConfig = require('./appConfig.js'), - shepherd = express.Router(), - iguanaInstanceRegistry = {}, - coindInstanceRegistry = {}, - syncOnlyIguanaInstanceInfo = {}, - syncOnlyInstanceInterval = -1, - guiLog = {}, - rpcConf = {}, - appRuntimeLog = [], - lockDownAddCoin = false; - -shepherd.appConfig = _appConfig.config; - -// IGUANA FILES AND CONFIG SETTINGS -var iguanaConfsDirSrc = path.join(__dirname, '../assets/deps/confs'); - -// SETTING OS DIR TO RUN IGUANA FROM -// SETTING APP ICON FOR LINUX AND WINDOWS -if (os.platform() === 'darwin') { - fixPath(); - var iguanaBin = path.join(__dirname, '../assets/bin/osx/iguana'), - iguanaDir = `${process.env.HOME}/Library/Application Support/iguana`, - iguanaTestDir = `${process.env.HOME}/Library/Application Support/iguana/test`, - iguanaConfsDir = `${iguanaDir}/confs`, - komododBin = path.join(__dirname, '../assets/bin/osx/komodod'), - komodocliBin = path.join(__dirname, '../assets/bin/osx/komodo-cli'), - komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.HOME}/Library/Application Support/Komodo`, - zcashdBin = '/Applications/ZCashSwingWalletUI.app/Contents/MacOS/zcashd', - zcashcliBin = '/Applications/ZCashSwingWalletUI.app/Contents/MacOS/zcash-cli', - zcashDir = `${process.env.HOME}/Library/Application Support/Zcash`, - zcashParamsDir = `${process.env.HOME}/Library/Application Support/ZcashParams`; -} - -if (os.platform() === 'linux') { - var iguanaBin = path.join(__dirname, '../assets/bin/linux64/iguana'), - iguanaDir = `${process.env.HOME}/.iguana`, - iguanaTestDir = `${process.env.HOME}/.iguana/test`, - iguanaConfsDir = `${iguanaDir}/confs`, - iguanaIcon = path.join(__dirname, '/assets/icons/agama_icons/128x128.png'), - komododBin = path.join(__dirname, '../assets/bin/linux64/komodod'), - komodocliBin = path.join(__dirname, '../assets/bin/linux64/komodo-cli'), - komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.HOME}/.komodo`, - zcashParamsDir = `${process.env.HOME}/.zcash-params`; -} - -if (os.platform() === 'win32') { - var iguanaBin = path.join(__dirname, '../assets/bin/win64/iguana.exe'); - iguanaBin = path.normalize(iguanaBin); - iguanaDir = `${process.env.APPDATA}/iguana`; - iguanaDir = path.normalize(iguanaDir); - iguanaTestDir = `${process.env.APPDATA}/iguana/test`; - iguanaTestDir = path.normalize(iguanaTestDir); - iguanaConfsDir = `${process.env.APPDATA}/iguana/confs`; - iguanaConfsDir = path.normalize(iguanaConfsDir); - iguanaIcon = path.join(__dirname, '/assets/icons/agama_icons/agama_app_icon.ico'), - iguanaConfsDirSrc = path.normalize(iguanaConfsDirSrc), - komododBin = path.join(__dirname, '../assets/bin/win64/komodod.exe'), - komododBin = path.normalize(komododBin), - komodocliBin = path.join(__dirname, '../assets/bin/win64/komodo-cli.exe'), - komodocliBin = path.normalize(komodocliBin), - komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.APPDATA}/Komodo`, - komodoDir = path.normalize(komodoDir); - zcashParamsDir = `${process.env.APPDATA}/ZcashParams`; - zcashParamsDir = path.normalize(zcashParamsDir); -} - -shepherd.appConfigSchema = _appConfig.schema; -shepherd.defaultAppConfig = Object.assign({}, shepherd.appConfig); -shepherd.kmdMainPassiveMode = false; - -shepherd.coindInstanceRegistry = coindInstanceRegistry; - -shepherd.getAppRuntimeLog = function() { - return new Promise((resolve, reject) => { - resolve(appRuntimeLog); - }); -}; - -shepherd.log = function(msg) { - if (shepherd.appConfig.dev) { - console.log(msg); - } - - appRuntimeLog.push({ - time: Date.now(), - msg: msg, - }); -}; - -shepherd.startKMDNative = function(selection, isManual) { - if (isManual) { - shepherd.kmdMainPassiveMode = true; - } - - if (selection === 'KMD') { - const herdData = { - 'ac_name': 'komodod', - 'ac_options': [ - '-daemon=0', - '-addnode=78.47.196.146', - ], - }; - - const options = { - url: `http://127.0.0.1:${shepherd.appConfig.agamaPort}/shepherd/herd`, - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - 'herd': 'komodod', - 'options': herdData, - }) - }; - - request(options, function(error, response, body) { - if (response && - response.statusCode && - response.statusCode === 200) { - //resolve(body); - } else { - //resolve(body); - } - }); - } else { - const herdData = [{ - 'ac_name': 'komodod', - 'ac_options': [ - '-daemon=0', - '-addnode=78.47.196.146', - ] - }, { - 'ac_name': 'REVS', - 'ac_options': [ - '-daemon=0', - '-server', - `-ac_name=REVS`, - '-addnode=78.47.196.146', - '-ac_supply=1300000' - ] - }, { - 'ac_name': 'JUMBLR', - 'ac_options': [ - '-daemon=0', - '-server', - `-ac_name=JUMBLR`, - '-addnode=78.47.196.146', - '-ac_supply=999999' - ] - }]; - - for (let i = 0; i < herdData.length; i++) { - setTimeout(() => { - const options = { - url: `http://127.0.0.1:${shepherd.appConfig.agamaPort}/shepherd/herd`, - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - 'herd': 'komodod', - 'options': herdData[i], - }) - }; - - request(options, function(error, response, body) { - if (response && - response.statusCode && - response.statusCode === 200) { - //resolve(body); - } else { - //resolve(body); - } - }); - }, 100); - } - } +const electron = require('electron'); +const express = require('express'); +const app = electron.app; +let shepherd = express.Router(); + +shepherd.path = require('path'); +shepherd.os = require('os'); +shepherd.fsnode = require('fs'); +shepherd.fs = require('fs-extra'); +shepherd._fs = require('graceful-fs'); +shepherd.md5 = require('./md5.js'); +shepherd.request = require('request'); +shepherd.portscanner = require('portscanner'); +shepherd.aes256 = require('nodejs-aes256'); +shepherd.AdmZip = require('adm-zip'); +shepherd.remoteFileSize = require('remote-file-size'); +shepherd.Promise = require('bluebird'); +shepherd.exec = require('child_process').exec; +shepherd.execFile = require('child_process').execFile; +shepherd.sha256 = require('sha256'); +shepherd.CoinKey = require('coinkey'); +shepherd.bitcoinJS = require('bitcoinjs-lib'); +shepherd.coinSelect = require('coinselect'); +shepherd.fixPath = require('fix-path'); +shepherd.crypto = require('crypto'); + +shepherd.setconf = require('../private/setconf.js'); +shepherd.nativeCoind = require('./nativeCoind.js'); +shepherd.nativeCoindList = {}; +shepherd.assetChainPorts = require('./ports.js'); +shepherd._appConfig = require('./appConfig.js'); + +shepherd.coindInstanceRegistry = {}; +shepherd.coindStdout = {}; +shepherd.guiLog = {}; +shepherd.rpcConf = {}; +shepherd.appRuntimeLog = []; +shepherd.appRuntimeSPVLog = []; +shepherd.lockDownAddCoin = false; +shepherd.mmupass = null; +shepherd.mmRatesInterval = null; +shepherd.mmPublic = { + coins: [], + mmupass: null, + swaps: [], + bids: [], + asks: [], + isAuth: false, + rates: {}, + prices: [], + coinsHelper: [], + stats: [], }; -/* - * Combined native dashboard update same as in gui - * type: GET - * params: coin - */ -shepherd.post('/native/dashboard/update', function(req, res, next) { - let _returnObj = { - getinfo: {}, - listtransactions: [], - z_gettotalbalance: {}, - z_getoperationstatus: {}, - listunspent: {}, - addresses: {}, - }; - const _promiseStack = [ - 'getinfo', - 'listtransactions', - 'z_gettotalbalance', - 'z_getoperationstatus' - ]; - const _coin = req.body.coin; - - function getAddressesNative(coin) { - const type = [ - 'public', - 'private' - ]; - - Promise.all(type.map((_type, index) => { - return new Promise((resolve, reject) => { - _bitcoinRPC( - coin, - _type === 'public' ? 'getaddressesbyaccount' : 'z_listaddresses', - [''] - ).then(function(_json) { - if (_json === 'Work queue depth exceeded' || - !_json) { - resolve({ error: 'daemon is busy' }); - } else { - resolve(JSON.parse(_json).result); - } - }); - }); - })) - .then(result => { - if (result[0] && result[0].length) { - function calcBalance(result, json) { - if (json && - json.length) { - const allAddrArray = json.map(res => res.address).filter((x, i, a) => a.indexOf(x) == i); - - for (let a = 0; a < allAddrArray.length; a++) { - const filteredArray = json.filter(res => res.address === allAddrArray[a]).map(res => res.amount); - - let isNewAddr = true; - for (let x = 0; x < result.length && isNewAddr; x++) { - for (let y = 0; y < result[x].length && isNewAddr; y++) { - if (allAddrArray[a] === result[x][y]) { - isNewAddr = false; - } - } - } - - if (isNewAddr && - (allAddrArray[a].substring(0, 2) === 'zc' || - allAddrArray[a].substring(0, 2) === 'zt')) { - result[1][result[1].length] = allAddrArray[a]; - } else { - result[0][result[0].length] = allAddrArray[a]; - } - } - } - - // remove addr duplicates - if (result[0] && - result[0].length) { - result[0] = result[0].filter(function(elem, pos) { - return result[0].indexOf(elem) === pos; - }); - } - if (result[1] && - result[1].length) { - result[1] = result[1].filter(function(elem, pos) { - return result[1].indexOf(elem) === pos; - }); - } - - let newAddressArray = []; - for (let a = 0; a < result.length; a++) { - newAddressArray[a] = []; - - if (result[a]) { - for (let b = 0; b < result[a].length; b++) { - let filteredArray; - - filteredArray = json.filter(res => res.address === result[a][b]).map(res => res.amount); - - let sum = 0; - for (let i = 0; i < filteredArray.length; i++) { - sum += filteredArray[i]; - } - - newAddressArray[a][b] = { - address: result[a][b], - amount: sum, - type: a === 0 ? 'public': 'private', - }; - } - } - } - - // get zaddr balance - if (result[1] && - result[1].length) { - Promise.all(result[1].map((_address, index) => { - return new Promise((resolve, reject) => { - _bitcoinRPC(coin, 'z_getbalance', [_address]) - .then(function(__json) { - __json = JSON.parse(__json); - if (__json && - __json.error) { - resolve(0); - } else { - resolve(__json.result) - newAddressArray[1][index] = { - address: _address, - amount: __json.result, - type: 'private', - }; - } - }); - }); - })) - .then(zresult => { - _returnObj.addresses = { - public: newAddressArray[0], - private: newAddressArray[1], - }; - - const returnObj = { - msg: 'success', - result: _returnObj, - }; - - res.end(JSON.stringify(returnObj)); - }); - } else { - _returnObj.addresses = { - public: newAddressArray[0], - private: newAddressArray[1], - }; - - const returnObj = { - msg: 'success', - result: _returnObj, - }; - - res.end(JSON.stringify(returnObj)); - } - } - - _bitcoinRPC(coin, 'listunspent') - .then(function(__json) { - if (__json === 'Work queue depth exceeded' || - !__json) { - const returnObj = { - msg: 'success', - result: _returnObj, - }; - - res.end(JSON.stringify(returnObj)); - } else { - _returnObj.listunspent = JSON.parse(__json); - - calcBalance( - result, - JSON.parse(__json).result - ); - } - }); - } else { - _returnObj.addresses = { - public: {}, - private: {}, - }; - - const returnObj = { - msg: 'success', - result: _returnObj, - }; - - res.end(JSON.stringify(returnObj)); - } - }) - } - - function _bitcoinRPC(coin, cmd, params) { - return new Promise(function(resolve, reject) { - let _payload; - - if (params) { - _payload = { - mode: null, - chain: coin, - cmd: cmd, - params: params, - }; - } else { - _payload = { - mode: null, - chain: coin, - cmd: cmd, - }; - } - - const options = { - url: `http://127.0.0.1:${shepherd.appConfig.agamaPort}/shepherd/cli`, - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ payload: _payload }), - timeout: 60000, - }; - - request(options, function(error, response, body) { - if (response && - response.statusCode && - response.statusCode === 200) { - resolve(body); - } else { - resolve(body); - } - }); - }); - } - - Promise.all(_promiseStack.map((_call, index) => { - let _params; - if (_call === 'listtransactions') { - _params = [ - '', - 300, - 0 - ]; - } - return new Promise((resolve, reject) => { - _bitcoinRPC( - _coin, - _call, - _params - ) - .then(function(json) { - if (json === 'Work queue depth exceeded' || - !json) { - _returnObj[_call] = { error: 'daemon is busy' }; - } else { - _returnObj[_call] = JSON.parse(json); - } - resolve(json); - }); - }); - })) - .then(result => { - getAddressesNative(_coin); - }); -}); - -// TODO: test functions are wip -shepherd.testNearestIguanaPort = function() { - return new Promise(function(resolve, reject) { - const port = shepherd.appConfig.iguanaCorePort; - portscanner.findAPortNotInUse(port, port + 100, '127.0.0.1', function(error, _port) { - resolve(_port); - }); - }); -} - -shepherd.testClearAll = function() { - return new Promise(function(resolve, reject) { - fs.removeSync(`${iguanaTestDir}`); - resolve('done'); - }); -} - -shepherd.testBins = function(daemonName) { - return new Promise(function(resolve, reject) { - const _bins = { - komodod: komododBin, - komodoCli: komodocliBin, - iguana: iguanaBin, - }; - const _arg = null; - let _pid; - - shepherd.log('testBins exec ' + _bins[daemonName]); - - if (!fs.existsSync(iguanaTestDir)) { - fs.mkdirSync(iguanaTestDir); - } - - if (daemonName === 'iguana') { - shepherd.killRogueProcess('iguana'); - } - - try { - _fs.access(`${iguanaTestDir}/${daemonName}Test.log`, fs.constants.R_OK, function(err) { - if (!err) { - try { - _fs.unlinkSync(`${iguanaTestDir}/${daemonName}Test.log`); - } catch (e) {} - } else { - shepherd.log(`path ${iguanaTestDir}/${daemonName}Test.log doesnt exist`); - } - }); - } catch (e) {} - - if (daemonName === 'iguana') { - pm2.connect(true,function(err) { //start up pm2 god - if (err) { - shepherd.error(err); - process.exit(2); - } - shepherd.log(`iguana core port ${shepherd.appConfig.iguanaCorePort}`); - shepherd.writeLog(`iguana core port ${shepherd.appConfig.iguanaCorePort}`); - - pm2.start({ - script: iguanaBin, // path to binary - name: 'iguana', - exec_mode : 'fork', - args: [`-port=${shepherd.appConfig.iguanaCorePort}`], - output: `${iguanaTestDir}/iguanaTest.log`, - mergeLogs: true, - cwd: iguanaDir // set correct iguana directory - }, function(err, apps) { - if (apps[0] && - apps[0].process && - apps[0].process.pid) { - shepherd.log(`test: got iguana instance pid = ${apps[0].process.pid}`); - shepherd.writeLog(`test: iguana core started at port ${shepherd.appConfig.iguanaCorePort} pid ${apps[0].process.pid}`); - } else { - shepherd.log(`test: unable to start iguana core at port ${shepherd.appConfig.iguanaCorePort}`); - } - - pm2.disconnect(); // Disconnect from PM2 - if (err) { - shepherd.writeLog(`test: iguana core port ${shepherd.appConfig.iguanaCorePort}`); - shepherd.log(`test: iguana fork error: ${err}`); - // throw err; - } - }); - }); - - setTimeout(function() { - pm2.delete('iguana'); - - const _iguanaTestRunLog = fs.readFileSync(`${iguanaTestDir}/iguanaTest.log`, 'utf8'); - - if (_iguanaTestRunLog.indexOf('iguana main') > -1 && - _iguanaTestRunLog.indexOf('Basilisk initialized') > -1) { - let _portTest = 'unknown'; - - if (_iguanaTestRunLog.indexOf(`iguana_rpcloop 127.0.0.1:${shepherd.appConfig.iguanaCorePort}`) > -1) { - _portTest = 'passed'; - } - - if (_iguanaTestRunLog.indexOf(`ERROR BINDING PORT.${shepherd.appConfig.iguanaCorePort}`) > -1) { - _portTest = 'failed'; - } - - resolve({ - res: 'success', - port: _portTest, - iguanaPort: shepherd.appConfig.iguanaCorePort, - }); - } else { - resolve({ res: 'error' }); - } - }, 30000); - } else if (daemonName === 'komodod') { - try { - _fs.access(`${iguanaTestDir}/debug.log`, fs.constants.R_OK, function(err) { - if (!err) { - _fs.unlinkSync(`${iguanaTestDir}/db.log`); - _fs.unlinkSync(`${iguanaTestDir}/debug.log`); - _fs.unlinkSync(`${iguanaTestDir}/komodo.conf`); - _fs.unlinkSync(`${iguanaTestDir}/komodod.pid`); - _fs.unlinkSync(`${iguanaTestDir}/komodostate`); - _fs.unlinkSync(`${iguanaTestDir}/realtime`); - _fs.unlinkSync(`${iguanaTestDir}/wallet.dat`); - _fs.unlinkSync(`${iguanaTestDir}/.lock`); - fs.removeSync(`${iguanaTestDir}/blocks`); - fs.removeSync(`${iguanaTestDir}/chainstate`); - fs.removeSync(`${iguanaTestDir}/database`); - execKomodod(); - } else { - shepherd.log(`test: nothing to remove in ${iguanaTestDir}`); - execKomodod(); - } - }); - } catch (e) {} - - function execKomodod() { - let _komododTest = { - port: 'unknown', - start: 'unknown', - getinfo: 'unknown', - errors: { - assertFailed: false, - zcashParams: false, - }, - }; - const _komodoConf = 'rpcuser=user83f3afba8d714993\n' + - 'rpcpassword=0d4430ca1543833e35bce5a0cc9e16b3\n' + - 'server=1\n' + - 'addnode=78.47.196.146\n' + - 'addnode=5.9.102.210\n' + - 'addnode=178.63.69.164\n' + - 'addnode=88.198.65.74\n' + - 'addnode=5.9.122.241\n' + - 'addnode=144.76.94.3\n' + - 'addnode=144.76.94.38\n' + - 'addnode=89.248.166.91\n' + - 'addnode=148.251.57.148\n' + - 'addnode=149.56.28.84\n' + - 'addnode=176.9.26.39\n' + - 'addnode=94.102.63.199\n' + - 'addnode=94.102.63.200\n' + - 'addnode=104.255.64.3\n' + - 'addnode=221.121.144.140\n' + - 'addnode=103.18.58.150\n' + - 'addnode=103.18.58.146\n' + - 'addnode=213.202.253.10\n' + - 'addnode=185.106.121.32\n' + - 'addnode=27.100.36.201\n'; - - fs.writeFile(`${iguanaTestDir}/komodo.conf`, _komodoConf, function(err) { - if (err) { - shepherd.log(`test: error writing komodo conf in ${iguanaTestDir}`); - } - }); - - portscanner.checkPortStatus('7771', '127.0.0.1', function(error, status) { - // Status is 'open' if currently in use or 'closed' if available - if (status === 'closed') { - _komododTest.port = 'passed'; - } else { - _komododTest.port = 'failed'; - } - }); - - pm2.connect(true,function(err) { //start up pm2 god - if (err) { - shepherd.error(err); - process.exit(2); - } - - pm2.start({ - script: komododBin, // path to binary - name: 'komodod', - exec_mode : 'fork', - args: [ - '-daemon=0', - '-addnode=78.47.196.146', - `-datadir=${iguanaTestDir}/` - ], - output: `${iguanaTestDir}/komododTest.log`, - mergeLogs: true, - }, function(err, apps) { - if (apps[0] && - apps[0].process && - apps[0].process.pid) { - _komododTest.start = 'success'; - shepherd.log(`test: got komodod instance pid = ${apps[0].process.pid}`); - shepherd.writeLog(`test: komodod started with pid ${apps[0].process.pid}`); - } else { - _komododTest.start = 'failed'; - shepherd.log(`unable to start komodod`); - } - - pm2.disconnect(); // Disconnect from PM2 - if (err) { - shepherd.writeLog(`test: error starting komodod`); - shepherd.log(`komodod fork err: ${err}`); - // throw err; - } - }); - }); - - setTimeout(function() { - const options = { - url: `http://localhost:7771`, - method: 'POST', - auth: { - user: 'user83f3afba8d714993', - pass: '0d4430ca1543833e35bce5a0cc9e16b3', - }, - body: JSON.stringify({ - agent: 'bitcoinrpc', - method: 'getinfo', - }), - }; - - request(options, function(error, response, body) { - if (response && - response.statusCode && - response.statusCode === 200) { - // res.end(body); - shepherd.log(JSON.stringify(body, null, '\t')); - } else { - // res.end(body); - shepherd.log(JSON.stringify(body, null, '\t')); - } - }); - }, 10000); - - setTimeout(function() { - pm2.delete('komodod'); - resolve(_komododTest); - }, 20000); - } - // komodod debug.log hooks - -//"{\"result\":{\"version\":1000850,\"protocolversion\":170002,\"KMDversion\":\"0.1.1\",\"notarized\":0,\"notarizedhash\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"notarizedtxid\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"notarizedtxid_height\":\"mempool\",\"notarized_confirms\":0,\"walletversion\":60000,\"balance\":0.00000000,\"interest\":0.00000000,\"blocks\":128,\"longestchain\":472331,\"timeoffset\":0,\"tiptime\":1473827710,\"connections\":1,\"proxy\":\"\",\"difficulty\":1,\"testnet\":false,\"keypoololdest\":1504118047,\"keypoolsize\":101,\"paytxfee\":0.00000000,\"relayfee\":0.00000100,\"errors\":\"\"},\"error\":null,\"id\":null}\n" - - //2017-08-30 17:51:33 Error: Cannot find the Zcash network parameters in the following directory: - //"/home/pbca/.zcash-params" - //Please run 'zcash-fetch-params' or './zcutil/fetch-params.sh' and then restart. - //EXCEPTION: St13runtime_error - //Assertion failed. - //2017-08-30 17:51:14 Using config file /home/pbca/.iguana/test/komodo.conf - //2017-08-30 18:23:43 UpdateTip: new best=0a47c1323f393650f7221c217d19d149d002d35444f47fde61be2dd90fbde8e6 height=1 log2_work=5.0874628 tx=2 date=2016-09-13 19:04:01 progress=0.000001 cache=0.0MiB(1tx) - //2017-08-30 18:23:43 UpdateTip: new best=05076a4e1fc9af0f5fda690257b17ae20c12d4796dfba1624804d012c9ec00be height=2 log2_work=5.6724253 tx=3 date=2016-09-13 19:05:28 progress=0.000001 cache=0.0MiB(2tx) - - /*execFile(`${komododBin}`, _arg, { - maxBuffer: 1024 * 10000 // 10 mb - }, function(error, stdout, stderr) { - shepherd.writeLog(`stdout: ${stdout}`); - shepherd.writeLog(`stderr: ${stderr}`); - - if (error !== null) { - console.log(`exec error: ${error}`); - shepherd.writeLog(`exec error: ${error}`); - - if (error.toString().indexOf('using -reindex') > -1) { - cache.io.emit('service', { - komodod: { - error: 'run -reindex', - } - }); - } - } - });*/ - } - }); -} - -shepherd.testLocation = function(path) { - return new Promise(function(resolve, reject) { - fs.lstat(path, (err, stats) => { - if (err) { - shepherd.log(`error testing path ${path}`); - resolve(-1); - } else { - if (stats.isDirectory()) { - resolve(true); - } else { - shepherd.log(`error testing path ${path} not a folder`); - resolve(false); - } - } - }); - }); -} - -// osx and linux -shepherd.binFixRights = function() { - const osPlatform = os.platform(); - const _bins = [ - iguanaBin, - komododBin, - komodocliBin - ]; - - if (osPlatform === 'darwin' || - osPlatform === 'linux') { - for (let i = 0; i < _bins.length; i++) { - _fs.stat(_bins[i], function(err, stat) { - if (!err) { - if (parseInt(stat.mode.toString(8), 10) !== 100775) { - shepherd.log(`${_bins[i]} fix permissions`); - fsnode.chmodSync(_bins[i], '0775'); - } - } else { - shepherd.log(`error: ${_bins[i]} not found`); - } - }); - } - } -} - -shepherd.killRogueProcess = function(processName) { - // kill rogue process copies on start - let processGrep; - const osPlatform = os.platform(); - - switch (osPlatform) { - case 'darwin': - processGrep = "ps -p $(ps -A | grep -m1 " + processName + " | awk '{print $1}') | grep -i " + processName; - break; - case 'linux': - processGrep = 'ps -p $(pidof ' + processName + ') | grep -i ' + processName; - break; - case 'win32': - processGrep = 'tasklist'; - break; - } - - exec(processGrep, function(error, stdout, stderr) { - if (stdout.indexOf(processName) > -1) { - const pkillCmd = osPlatform === 'win32' ? `taskkill /f /im ${processName}.exe` : `pkill -15 ${processName}`; - - shepherd.log(`found another ${processName} process(es)`); - shepherd.writeLog(`found another ${processName} process(es)`); - - exec(pkillCmd, function(error, stdout, stderr) { - shepherd.log(`${pkillCmd} is issued`); - shepherd.writeLog(`${pkillCmd} is issued`); - - if (error !== null) { - shepherd.log(`${pkillCmd} exec error: ${error}`); - shepherd.writeLog(`${pkillCmd} exec error: ${error}`); - }; - }); - } - - if (error !== null) { - shepherd.log(`${processGrep} exec error: ${error}`); - shepherd.writeLog(`${processGrep} exec error: ${error}`); - }; - }); -} - -shepherd.zcashParamsExist = function() { - let _checkList = { - rootDir: _fs.existsSync(zcashParamsDir), - provingKey: _fs.existsSync(`${zcashParamsDir}/sprout-proving.key`), - provingKeySize: false, - verifyingKey: _fs.existsSync(`${zcashParamsDir}/sprout-verifying.key`), - verifyingKeySize: false, - errors: false, - }; - - if (_checkList.rootDir && - _checkList.provingKey && - _checkList.verifyingKey) { - // verify each key size - const _provingKeySize = fs.lstatSync(`${zcashParamsDir}/sprout-proving.key`); - const _verifyingKeySize = fs.lstatSync(`${zcashParamsDir}/sprout-verifying.key`); - - if (_provingKeySize.size === 910173851) { - _checkList.provingKeySize = true; - } - if (_verifyingKeySize.size === 1449) { - _checkList.verifyingKeySize = true; - } - - shepherd.log('zcashparams exist'); - } else { - shepherd.log('zcashparams doesnt exist'); - } - - if (!_checkList.rootDir || - !_checkList.provingKey || - !_checkList.verifyingKey || - !_checkList.provingKeySize || - !_checkList.verifyingKeySize) { - _checkList.errors = true; - } - - return _checkList; -} - -shepherd.readVersionFile = function() { - // read app version - const rootLocation = path.join(__dirname, '../'); - const localVersionFile = fs.readFileSync(`${rootLocation}version`, 'utf8'); - - return localVersionFile; -} - -shepherd.writeLog = function(data) { - const logLocation = `${iguanaDir}/shepherd`; - const timeFormatted = new Date(Date.now()).toLocaleString('en-US', { hour12: false }); - - if (shepherd.appConfig.debug) { - if (fs.existsSync(`${logLocation}/agamalog.txt`)) { - fs.appendFile(`${logLocation}/agamalog.txt`, `${timeFormatted} ${data}\r\n`, function(err) { - if (err) { - shepherd.log('error writing log file'); - } - }); - } else { - fs.writeFile(`${logLocation}/agamalog.txt`, `${timeFormatted} ${data}\r\n`, function(err) { - if (err) { - shepherd.log('error writing log file'); - } - }); - } - } -} - -shepherd.createIguanaDirs = function() { - if (!fs.existsSync(iguanaDir)) { - fs.mkdirSync(iguanaDir); - - if (fs.existsSync(iguanaDir)) { - shepherd.log(`created iguana folder at ${iguanaDir}`); - shepherd.writeLog(`created iguana folder at ${iguanaDir}`); - } - } else { - shepherd.log('iguana folder already exists'); - } - - if (!fs.existsSync(`${iguanaDir}/shepherd`)) { - fs.mkdirSync(`${iguanaDir}/shepherd`); - - if (fs.existsSync(`${iguanaDir}/shepherd`)) { - shepherd.log(`created shepherd folder at ${iguanaDir}/shepherd`); - shepherd.writeLog(`create shepherd folder at ${iguanaDir}/shepherd`); - } - } else { - shepherd.log('iguana/shepherd folder already exists'); - } - - if (!fs.existsSync(`${iguanaDir}/shepherd/pin`)) { - fs.mkdirSync(`${iguanaDir}/shepherd/pin`); - - if (fs.existsSync(`${iguanaDir}/shepherd/pin`)) { - shepherd.log(`created pin folder at ${iguanaDir}/shepherd/pin`); - shepherd.writeLog(`create pin folder at ${iguanaDir}/shepherd/pin`); - } - } else { - shepherd.log('shepherd/pin folder already exists'); - } -} - -/* - * type: POST - * params: none - */ -shepherd.post('/encryptkey', function(req, res, next) { - if (req.body.key && - req.body.string && - req.body.pubkey) { - const encryptedString = aes256.encrypt(req.body.key, req.body.string); - - // test pin security - // - at least 1 char in upper case - // - at least 1 digit - // - at least one special character - // - min length 8 - - const _pin = req.body.key; - const _pinTest = _pin.match('^(?=.*[A-Z])(?=.*[^<>{}\"/|;:.,~!?@#$%^=&*\\]\\\\()\\[_+]*$)(?=.*[0-9])(?=.*[a-z]).{8}$'); - - fs.writeFile(`${iguanaDir}/shepherd/pin/${req.body.pubkey}.pin`, encryptedString, function(err) { - if (err) { - shepherd.log('error writing pin file'); - } - - const returnObj = { - msg: 'success', - result: encryptedString, - }; - - res.end(JSON.stringify(returnObj)); - }); - } else { - let errorObj = { - msg: 'error', - result: '', - }; - const _paramsList = [ - 'key', - 'string', - 'pubkey' - ]; - let _errorParamsList = []; - - for (let i = 0; i < _paramsList.length; i++) { - if (!req.query[_paramsList[i]]) { - _errorParamsList.push(_paramsList[i]); - } - } - - errorObj.result = `missing param ${_errorParamsList.join(', ')}`; - res.end(JSON.stringify(errorObj)); - } -}); - -shepherd.post('/decryptkey', function(req, res, next) { - if (req.body.key && - req.body.pubkey) { - if (fs.existsSync(`${iguanaDir}/shepherd/pin/${req.body.pubkey}.pin`)) { - fs.readFile(`${iguanaDir}/shepherd/pin/${req.body.pubkey}.pin`, 'utf8', function(err, data) { - if (err) { - const errorObj = { - msg: 'error', - result: err, - }; - - res.end(JSON.stringify(errorObj)); - } else { - const encryptedKey = aes256.decrypt(req.body.key, data); - // test if stored encrypted passphrase is decrypted correctly - // if not then the key is wrong - const _regexTest = encryptedKey.match(/^[0-9a-zA-Z ]+$/g); - let returnObj; - - if (!_regexTest) { - returnObj = { - msg: 'error', - result: 'wrong key', - }; - } else { - returnObj = { - msg: 'success', - result: encryptedKey, - }; - } - - res.end(JSON.stringify(returnObj)); - } - }); - } else { - const errorObj = { - msg: 'error', - result: `file ${req.query.pubkey}.pin doesnt exist`, - }; - - res.end(JSON.stringify(errorObj)); - } - } else { - const errorObj = { - msg: 'error', - result: 'missing key or pubkey param', - }; - - res.end(JSON.stringify(errorObj)); - } -}); - -shepherd.get('/getpinlist', function(req, res, next) { - if (fs.existsSync(`${iguanaDir}/shepherd/pin`)) { - fs.readdir(`${iguanaDir}/shepherd/pin`, function(err, items) { - let _pins = []; - - for (let i = 0; i < items.length; i++) { - if (items[i].substr(items[i].length - 4, 4) === '.pin') { - _pins.push(items[i].substr(0, items[i].length - 4)); - } - } - - if (!items.length) { - const errorObj = { - msg: 'error', - result: 'no pins', - }; - - res.end(JSON.stringify(errorObj)); - } else { - const successObj = { - msg: 'success', - result: _pins, - }; - - res.end(JSON.stringify(successObj)); - } - }); - } else { - const errorObj = { - msg: 'error', - result: 'pin folder doesnt exist', - }; - - res.end(JSON.stringify(errorObj)); - } -}); -/** - * Promise based download file method - */ -function downloadFile(configuration) { - return new Promise(function(resolve, reject) { - // Save variable to know progress - let receivedBytes = 0; - let totalBytes = 0; - - let req = request({ - method: 'GET', - uri: configuration.remoteFile, - agentOptions: { - keepAlive: true, - keepAliveMsecs: 15000, - }, - }); - - let out = fs.createWriteStream(configuration.localFile); - req.pipe(out); - - req.on('response', function(data) { - // Change the total bytes value to get progress later. - totalBytes = parseInt(data.headers['content-length']); - }); - - // Get progress if callback exists - if (configuration.hasOwnProperty('onProgress')) { - req.on('data', function(chunk) { - // Update the received bytes - receivedBytes += chunk.length; - configuration.onProgress(receivedBytes, totalBytes); - }); - } else { - req.on('data', function(chunk) { - // Update the received bytes - receivedBytes += chunk.length; - }); - } - - req.on('end', function() { - resolve(); - }); - }); -} - -const remoteBinLocation = { - win32: 'https://artifacts.supernet.org/latest/windows/', - darwin: 'https://artifacts.supernet.org/latest/osx/', - linux: 'https://artifacts.supernet.org/latest/linux/', -}; -const localBinLocation = { - win32: 'assets/bin/win64/', - darwin: 'assets/bin/osx/', - linux: 'assets/bin/linux64/', +// spv vars and libs +shepherd.electrumCoins = { + auth: false, }; -const latestBins = { - win32: [ - 'iguana.exe', - 'komodo-cli.exe', - 'komodod.exe', - 'libcrypto-1_1.dll', - 'libcurl-4.dll', - 'libcurl.dll', - 'libgcc_s_sjlj-1.dll', - 'libnanomsg.dll', - 'libssl-1_1.dll', - 'libwinpthread-1.dll', - 'nanomsg.dll', - 'pthreadvc2.dll', - ], - darwin: [ - 'iguana', - 'komodo-cli', - 'komodod', - 'libgcc_s.1.dylib', - 'libgomp.1.dylib', - 'libnanomsg.5.0.0.dylib', - 'libstdc++.6.dylib', // encode %2B - ], - linux: [ - 'iguana', - 'komodo-cli', - 'komodod', - ] -}; - -let binsToUpdate = []; - -/* - * Check bins file size - * type: - * params: - */ -shepherd.get('/update/bins/check', function(req, res, next) { - const rootLocation = path.join(__dirname, '../'); - const successObj = { - msg: 'success', - result: 'bins', - }; - - res.end(JSON.stringify(successObj)); - - const _os = os.platform(); - shepherd.log(`checking bins: ${_os}`); - - cache.io.emit('patch', { - patch: { - type: 'bins-check', - status: 'progress', - message: `checking bins: ${_os}`, - }, - }); - // get list of bins/dlls that can be updated to the latest - for (let i = 0; i < latestBins[_os].length; i++) { - remoteFileSize(remoteBinLocation[_os] + latestBins[_os][i], function(err, remoteBinSize) { - const localBinSize = fs.statSync(rootLocation + localBinLocation[_os] + latestBins[_os][i]).size; - - shepherd.log('remote url: ' + (remoteBinLocation[_os] + latestBins[_os][i]) + ' (' + remoteBinSize + ')'); - shepherd.log('local file: ' + (rootLocation + localBinLocation[_os] + latestBins[_os][i]) + ' (' + localBinSize + ')'); - - if (remoteBinSize !== localBinSize) { - shepherd.log(`${latestBins[_os][i]} can be updated`); - binsToUpdate.push({ - name: latestBins[_os][i], - rSize: remoteBinSize, - lSize: localBinSize, - }); - } - - if (i === latestBins[_os].length - 1) { - cache.io.emit('patch', { - patch: { - type: 'bins-check', - status: 'done', - fileList: binsToUpdate, - }, - }); - } - }); - } -}); - -/* - * Update bins - * type: - * params: - */ -shepherd.get('/update/bins', function(req, res, next) { - const rootLocation = path.join(__dirname, '../'); - const _os = os.platform(); - const successObj = { - msg: 'success', - result: { - filesCount: binsToUpdate.length, - list: binsToUpdate, - }, - }; - - res.end(JSON.stringify(successObj)); - - for (let i = 0; i < binsToUpdate.length; i++) { - downloadFile({ - remoteFile: remoteBinLocation[_os] + binsToUpdate[i].name, - localFile: `${rootLocation}${localBinLocation[_os]}patch/${binsToUpdate[i].name}`, - onProgress: function(received, total) { - const percentage = (received * 100) / total; - cache.io.emit('patch', { - msg: { - type: 'bins-update', - status: 'progress', - file: binsToUpdate[i].name, - bytesTotal: total, - bytesReceived: received, - }, - }); - shepherd.log(`${binsToUpdate[i].name} ${percentage}% | ${received} bytes out of ${total} bytes.`); - } - }) - .then(function() { - // verify that remote file is matching to DL'ed file - const localBinSize = fs.statSync(`${rootLocation}${localBinLocation[_os]}patch/${binsToUpdate[i].name}`).size; - shepherd.log('compare dl file size'); - - if (localBinSize === binsToUpdate[i].rSize) { - cache.io.emit('patch', { - msg: { - type: 'bins-update', - file: binsToUpdate[i].name, - status: 'done', - }, - }); - shepherd.log(`file ${binsToUpdate[i].name} succesfully downloaded`); - } else { - cache.io.emit('patch', { - msg: { - type: 'bins-update', - file: binsToUpdate[i].name, - message: 'size mismatch', - }, - }); - shepherd.log(`error: ${binsToUpdate[i].name} file size doesnt match remote!`); - } - }); - } -}); - -/* - * DL app patch - * type: GET - * params: patchList - */ -shepherd.get('/update/patch', function(req, res, next) { - const successObj = { - msg: 'success', - result: 'dl started' - }; - - res.end(JSON.stringify(successObj)); - - shepherd.updateAgama(); -}); - -shepherd.updateAgama = function() { - const rootLocation = path.join(__dirname, '../'); - - downloadFile({ - remoteFile: 'https://github.com/pbca26/dl-test/raw/master/patch.zip', - localFile: rootLocation + 'patch.zip', - onProgress: function(received, total) { - const percentage = (received * 100) / total; - if (Math.floor(percentage) % 5 === 0 || - Math.floor(percentage) % 10 === 0) { - shepherd.log(`patch ${percentage}% | ${received} bytes out of ${total} bytes.`); - cache.io.emit('patch', { - msg: { - status: 'progress', - type: 'ui', - progress: percentage, - bytesTotal: total, - bytesReceived: received, - }, - }); - } - } - }) - .then(function() { - remoteFileSize('https://github.com/pbca26/dl-test/raw/master/patch.zip', function(err, remotePatchSize) { - // verify that remote file is matching to DL'ed file - const localPatchSize = fs.statSync(`${rootLocation}patch.zip`).size; - shepherd.log('compare dl file size'); - - if (localPatchSize === remotePatchSize) { - const zip = new AdmZip(`${rootLocation}patch.zip`); - - shepherd.log('patch succesfully downloaded'); - shepherd.log('extracting contents'); - - if (shepherd.appConfig.dev) { - if (!fs.existsSync(`${rootLocation}/patch`)) { - fs.mkdirSync(`${rootLocation}/patch`); - } - } - - zip.extractAllTo(/*target path*/rootLocation + (shepherd.appConfig.dev ? '/patch' : ''), /*overwrite*/true); - // TODO: extract files in chunks - cache.io.emit('patch', { - msg: { - type: 'ui', - status: 'done', - }, - }); - fs.unlinkSync(`${rootLocation}patch.zip`); - } else { - cache.io.emit('patch', { - msg: { - type: 'ui', - status: 'error', - message: 'size mismatch', - }, - }); - shepherd.log('patch file size doesnt match remote!'); - } - }); - }); -} - -/* - * check latest version - * type: - * params: - */ -shepherd.get('/update/patch/check', function(req, res, next) { - const rootLocation = path.join(__dirname, '../'); - const options = { - url: 'https://github.com/pbca26/dl-test/raw/master/version', - method: 'GET', - }; - - request(options, function(error, response, body) { - if (response && - response.statusCode && - response.statusCode === 200) { - const remoteVersion = body.split('\n'); - const localVersionFile = fs.readFileSync(`${rootLocation}version`, 'utf8'); - let localVersion; - - if (localVersionFile.indexOf('\r\n') > -1) { - localVersion = localVersionFile.split('\r\n'); - } else { - localVersion = localVersionFile.split('\n'); - } - - if (remoteVersion[0] === localVersion[0]) { - const successObj = { - msg: 'success', - result: 'latest', - }; - - res.end(JSON.stringify(successObj)); - } else { - const successObj = { - msg: 'success', - result: 'update', - version: { - local: localVersion[0], - remote: remoteVersion[0], - }, - }; - - res.end(JSON.stringify(successObj)); - } - } else { - res.end({ - err: 'error getting update', - }); - } - }); -}); - -/* - * unpack zip - * type: - * params: - */ -shepherd.get('/unpack', function(req, res, next) { - const dlLocation = path.join(__dirname, '../'); - const zip = new AdmZip(`${dlLocation}patch.zip`); - zip.extractAllTo(/*target path*/ `${dlLocation}/patch/unpack`, /*overwrite*/true); - - const successObj = { - msg: 'success', - result: 'unpack started', - }; - - res.end(JSON.stringify(successObj)); -}); - -/* - * type: GET - * - */ -shepherd.get('/coinslist', function(req, res, next) { - if (fs.existsSync(`${iguanaDir}/shepherd/coinslist.json`)) { - fs.readFile(`${iguanaDir}/shepherd/coinslist.json`, 'utf8', function(err, data) { - if (err) { - const errorObj = { - msg: 'error', - result: err, - }; - - res.end(JSON.stringify(errorObj)); - } else { - const successObj = { - msg: 'success', - result: data ? JSON.parse(data) : '', - }; - - res.end(JSON.stringify(successObj)); - } - }); - } else { - const errorObj = { - msg: 'error', - result: 'coin list doesn\'t exist', - }; - - res.end(JSON.stringify(errorObj)); - } -}); - -/* - * type: POST - * params: payload - */ -shepherd.post('/guilog', function(req, res, next) { - const logLocation = `${iguanaDir}/shepherd`; - - if (!guiLog[shepherd.appSessionHash]) { - guiLog[shepherd.appSessionHash] = {}; - } - - if (guiLog[shepherd.appSessionHash][req.body.timestamp]) { - guiLog[shepherd.appSessionHash][req.body.timestamp].status = req.body.status; - guiLog[shepherd.appSessionHash][req.body.timestamp].response = req.body.response; - } else { - guiLog[shepherd.appSessionHash][req.body.timestamp] = { - function: req.body.function, - type: req.body.type, - url: req.body.url, - payload: req.body.payload, - status: req.body.status, - }; - } - - fs.writeFile(`${logLocation}/agamalog.json`, JSON.stringify(guiLog), function(err) { - if (err) { - shepherd.writeLog('error writing gui log file'); - } - - const returnObj = { - msg: 'success', - result: 'gui log entry is added', - }; - - res.end(JSON.stringify(returnObj)); - }); -}); - -/* - * type: GET - * params: type - */ -shepherd.get('/getlog', function(req, res, next) { - const logExt = req.query.type === 'txt' ? 'txt' : 'json'; - - if (fs.existsSync(`${iguanaDir}/shepherd/agamalog.${logExt}`)) { - fs.readFile(`${iguanaDir}/shepherd/agamalog.${logExt}`, 'utf8', function(err, data) { - if (err) { - const errorObj = { - msg: 'error', - result: err, - }; - - res.end(JSON.stringify(errorObj)); - } else { - const successObj = { - msg: 'success', - result: data ? JSON.parse(data) : '', - }; - - res.end(JSON.stringify(successObj)); - } - }); - } else { - const errorObj = { - msg: 'error', - result: `agama.${logExt} doesnt exist`, - }; - - res.end(JSON.stringify(errorObj)); - } -}); - -/* - * type: POST - * params: payload - */ -shepherd.post('/coinslist', function(req, res, next) { - const _payload = req.body.payload; - - if (!_payload) { - const errorObj = { - msg: 'error', - result: 'no payload provided', - }; - - res.end(JSON.stringify(errorObj)); - } else { - fs.writeFile(`${cache.iguanaDir}/shepherd/coinslist.json`, JSON.stringify(_payload), function(err) { - if (err) { - const errorObj = { - msg: 'error', - result: err, - }; - - res.end(JSON.stringify(errorObj)); - } else { - const successObj = { - msg: 'success', - result: 'done', - }; - - res.end(JSON.stringify(successObj)); - } - }); - } -}); - -// TODO: check if komodod is running -shepherd.quitKomodod = function(timeout = 100) { - // if komodod is under heavy load it may not respond to cli stop the first time - // exit komodod gracefully - let coindExitInterval = {}; - lockDownAddCoin = true; - - for (let key in coindInstanceRegistry) { - const chain = key !== 'komodod' ? key : null; - - function execCliStop() { - let _arg = []; - if (chain) { - _arg.push(`-ac_name=${chain}`); - } - _arg.push('stop'); - execFile(`${komodocliBin}`, _arg, function(error, stdout, stderr) { - shepherd.log(`stdout: ${stdout}`); - shepherd.log(`stderr: ${stderr}`); - - if (stdout.indexOf('EOF reached') > -1 || - stderr.indexOf('EOF reached') > -1 || - (error && error.toString().indexOf('Command failed') > -1 && !stderr) || // win "special snowflake" case - stdout.indexOf('connect to server: unknown (code -1)') > -1 || - stderr.indexOf('connect to server: unknown (code -1)') > -1) { - delete coindInstanceRegistry[key]; - clearInterval(coindExitInterval[key]); - } - - if (error !== null) { - shepherd.log(`exec error: ${error}`); - } - shepherd.killRogueProcess('komodo-cli'); - }); - } - - execCliStop(); - coindExitInterval[key] = setInterval(function() { - execCliStop(); - }, timeout); - } -} - -shepherd.getConf = function(chain) { - const _confLocation = chain === 'komodod' ? `${komodoDir}/komodo.conf` : `${komodoDir}/${chain}/${chain}.conf`; - // komodoDir - - if (fs.existsSync(_confLocation)) { - const _port = assetChainPorts[chain]; - const _rpcConf = fs.readFileSync(_confLocation, 'utf8'); - - if (_rpcConf.length) { - let _match; - let parsedRpcConfig = { - user: '', - pass: '', - port: _port, - }; - - if (_match = _rpcConf.match(/rpcuser=\s*(.*)/)) { - parsedRpcConfig.user = _match[1]; - } - - if ((_match = _rpcConf.match(/rpcpass=\s*(.*)/)) || - (_match = _rpcConf.match(/rpcpassword=\s*(.*)/))) { - parsedRpcConfig.pass = _match[1]; - } - - rpcConf[chain === 'komodod' ? 'KMD' : chain] = parsedRpcConfig; - } else { - shepherd.log(`${_confLocation} is empty`); - } - } else { - shepherd.log(`${_confLocation} doesnt exist`); - } -} - -/* - * type: POST - * params: payload - */ -shepherd.post('/cli', function(req, res, next) { - if (!req.body.payload) { - const errorObj = { - msg: 'error', - result: 'no payload provided', - }; - - res.end(JSON.stringify(errorObj)); - } else if (!req.body.payload.cmd.match(/^[0-9a-zA-Z _\,\.\[\]"'/\\]+$/g)) { - const errorObj = { - msg: 'error', - result: 'wrong cli string format', - }; - - res.end(JSON.stringify(errorObj)); - } else { - const _mode = req.body.payload.mode === 'passthru' ? 'passthru' : 'default'; - const _chain = req.body.payload.chain === 'KMD' ? null : req.body.payload.chain; - const _cmd = req.body.payload.cmd; - const _params = req.body.payload.params ? ` ${req.body.payload.params}` : ''; - - if (!rpcConf[_chain]) { - shepherd.getConf(req.body.payload.chain === 'KMD' ? 'komodod' : req.body.payload.chain); - } - - if (_mode === 'default') { - let _body = { - agent: 'bitcoinrpc', - method: _cmd, - }; - - if (req.body.payload.params) { - _body = { - agent: 'bitcoinrpc', - method: _cmd, - params: req.body.payload.params === ' ' ? [''] : req.body.payload.params, - }; - } - - const options = { - url: `http://localhost:${rpcConf[req.body.payload.chain].port}`, - method: 'POST', - auth: { - user: rpcConf[req.body.payload.chain].user, - pass: rpcConf[req.body.payload.chain].pass, - }, - body: JSON.stringify(_body) - }; - - // send back body on both success and error - // this bit replicates iguana core's behaviour - request(options, function(error, response, body) { - if (response && - response.statusCode && - response.statusCode === 200) { - res.end(body); - } else { - res.end(body); - } - }); - } else { - let _arg = (_chain ? ' -ac_name=' + _chain : '') + ' ' + _cmd + _params; - _arg = _arg.trim().split(' '); - execFile(komodocliBin, _arg, function(error, stdout, stderr) { - shepherd.log(`stdout: ${stdout}`); - shepherd.log(`stderr: ${stderr}`); - - if (error !== null) { - shepherd.log(`exec error: ${error}`); - } - - let responseObj; - - if (stderr) { - responseObj = { - msg: 'error', - result: stderr, - }; - } else { - responseObj = { - msg: 'success', - result: stdout, - }; - } - - res.end(JSON.stringify(responseObj)); - shepherd.killRogueProcess('komodo-cli'); - }); - } - } -}); - -/* - * type: POST - * params: payload - */ -shepherd.post('/appconf', function(req, res, next) { - if (!req.body.payload) { - const errorObj = { - msg: 'error', - result: 'no payload provided', - }; - - res.end(JSON.stringify(errorObj)); - } else { - shepherd.saveLocalAppConf(req.body.payload); - - const successObj = { - msg: 'success', - result: 'config saved', - }; - - res.end(JSON.stringify(successObj)); - } -}); - -/* - * type: POST - * params: none - */ -shepherd.post('/appconf/reset', function(req, res, next) { - shepherd.saveLocalAppConf(shepherd.defaultAppConfig); - - const successObj = { - msg: 'success', - result: 'config saved', - }; - - res.end(JSON.stringify(successObj)); -}); - -shepherd.saveLocalAppConf = function(appSettings) { - let appConfFileName = `${iguanaDir}/config.json`; - - _fs.access(iguanaDir, fs.constants.R_OK, function(err) { - if (!err) { - - const FixFilePermissions = function() { - return new Promise(function(resolve, reject) { - const result = 'config.json file permissions updated to Read/Write'; - - fsnode.chmodSync(appConfFileName, '0666'); - - setTimeout(function() { - shepherd.log(result); - shepherd.writeLog(result); - resolve(result); - }, 1000); - }); - } - - const FsWrite = function() { - return new Promise(function(resolve, reject) { - const result = 'config.json write file is done'; +shepherd.electrumKeys = {}; - fs.writeFile(appConfFileName, - JSON.stringify(appSettings) - .replace(/,/g, ',\n') // format json in human readable form - .replace(/:/g, ': ') - .replace(/{/g, '{\n') - .replace(/}/g, '\n}'), 'utf8', function(err) { - if (err) - return shepherd.log(err); - }); +shepherd.electrumJSCore = require('./electrumjs/electrumjs.core.js'); +shepherd.electrumJSNetworks = require('./electrumjs/electrumjs.networks.js'); +shepherd.electrumJSTxDecoder = require('./electrumjs/electrumjs.txdecoder.js'); +shepherd.electrumServers = require('./electrumjs/electrumServers.js'); - fsnode.chmodSync(appConfFileName, '0666'); - setTimeout(function() { - shepherd.log(result); - shepherd.log(`app conf.json file is created successfully at: ${iguanaConfsDir}`); - shepherd.writeLog(`app conf.json file is created successfully at: ${iguanaConfsDir}`); - resolve(result); - }, 2000); - }); - } +shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA = 'connection error or incomplete data'; - FsWrite() - .then(FixFilePermissions()); - } - }); -} +shepherd.appConfig = shepherd._appConfig.config; -shepherd.loadLocalConfig = function() { - if (fs.existsSync(`${iguanaDir}/config.json`)) { - let localAppConfig = fs.readFileSync(`${iguanaDir}/config.json`, 'utf8'); +// core +shepherd = require('./shepherd/paths.js')(shepherd); - shepherd.log('app config set from local file'); - shepherd.writeLog('app config set from local file'); +shepherd.pathsAgama(); - // find diff between local and hardcoded configs - // append diff to local config - const compareJSON = function(obj1, obj2) { - let result = {}; - - for (let i in obj1) { - if (!obj2.hasOwnProperty(i)) { - result[i] = obj1[i]; - } - } - - return result; - }; - - if (localAppConfig) { - const compareConfigs = compareJSON(shepherd.appConfig, JSON.parse(localAppConfig)); - - if (Object.keys(compareConfigs).length) { - const newConfig = Object.assign(JSON.parse(localAppConfig), compareConfigs); - - shepherd.log('config diff is found, updating local config'); - shepherd.log('config diff:'); - shepherd.log(compareConfigs); - shepherd.writeLog('aconfig diff is found, updating local config'); - shepherd.writeLog('config diff:'); - shepherd.writeLog(compareConfigs); - - shepherd.saveLocalAppConf(newConfig); - return newConfig; - } else { - return JSON.parse(localAppConfig); - } - } else { - return shepherd.appConfig; - } - } else { - shepherd.log('local config file is not found!'); - shepherd.writeLog('local config file is not found!'); - shepherd.saveLocalAppConf(shepherd.appConfig); - - return shepherd.appConfig; - } -}; +// core +shepherd = require('./shepherd/log.js')(shepherd); +shepherd = require('./shepherd/config.js')(shepherd); shepherd.appConfig = shepherd.loadLocalConfig(); -shepherd.log(`iguana dir: ${iguanaDir}`); -shepherd.log(`iguana bin: ${iguanaBin}`); -shepherd.log('--------------------------') -shepherd.log(`iguana dir: ${komododBin}`); -shepherd.log(`iguana bin: ${komodoDir}`); -shepherd.writeLog(`iguana dir: ${iguanaDir}`); -shepherd.writeLog(`iguana bin: ${iguanaBin}`); -shepherd.writeLog(`iguana dir: ${komododBin}`); -shepherd.writeLog(`iguana bin: ${komodoDir}`); +shepherd.pathsDaemons(); -// END IGUANA FILES AND CONFIG SETTINGS -// default route -shepherd.get('/', function(req, res, next) { - res.send('Iguana app server'); -}); - -/* - * type: GET - * - */ -shepherd.get('/appconf', function(req, res, next) { - const obj = shepherd.loadLocalConfig(); - res.send(obj); -}); +shepherd.appConfigSchema = shepherd._appConfig.schema; +shepherd.defaultAppConfig = Object.assign({}, shepherd.appConfig); +shepherd.kmdMainPassiveMode = false; -/* - * type: GET - * - */ -shepherd.get('/sysinfo', function(req, res, next) { - const obj = shepherd.SystemInfo(); - res.send(obj); -}); +// spv +shepherd = require('./shepherd/electrum/network.js')(shepherd); +shepherd = require('./shepherd/electrum/coins.js')(shepherd); +shepherd = require('./shepherd/electrum/keys.js')(shepherd); +shepherd = require('./shepherd/electrum/auth.js')(shepherd); +shepherd = require('./shepherd/electrum/merkle.js')(shepherd); +shepherd = require('./shepherd/electrum/balance.js')(shepherd); +shepherd = require('./shepherd/electrum/transactions.js')(shepherd); +shepherd = require('./shepherd/electrum/block.js')(shepherd); +shepherd = require('./shepherd/electrum/createtx.js')(shepherd); +shepherd = require('./shepherd/electrum/interest.js')(shepherd); +shepherd = require('./shepherd/electrum/listunspent.js')(shepherd); +shepherd = require('./shepherd/electrum/estimate.js')(shepherd); + +// dex +shepherd = require('./shepherd/dex/coind.js')(shepherd); +shepherd = require('./shepherd/dex/mmControl.js')(shepherd); +shepherd = require('./shepherd/dex/mmRequest.js')(shepherd); + +// core +shepherd = require('./shepherd/addCoinShortcuts.js')(shepherd); +shepherd = require('./shepherd/dashboardUpdate.js')(shepherd); +shepherd = require('./shepherd/binsTestUtil.js')(shepherd); +shepherd = require('./shepherd/binsUtils.js')(shepherd); +shepherd = require('./shepherd/downloadUtil.js')(shepherd); +shepherd = require('./shepherd/init.js')(shepherd); +shepherd = require('./shepherd/pin.js')(shepherd); +shepherd = require('./shepherd/downloadBins.js')(shepherd); +shepherd = require('./shepherd/downloadPatch.js')(shepherd); +shepherd = require('./shepherd/downloadZcparams.js')(shepherd); +shepherd = require('./shepherd/coinsList.js')(shepherd); +shepherd = require('./shepherd/quitDaemon.js')(shepherd); +shepherd = require('./shepherd/rpc.js')(shepherd); +shepherd = require('./shepherd/kickstart.js')(shepherd); +shepherd = require('./shepherd/debugLog.js')(shepherd); +shepherd = require('./shepherd/confMaxconnections.js')(shepherd); +shepherd = require('./shepherd/appInfo.js')(shepherd); +shepherd = require('./shepherd/daemonControl.js')(shepherd); +shepherd = require('./shepherd/auth.js')(shepherd); +shepherd = require('./shepherd/coins.js')(shepherd); +shepherd = require('./shepherd/coindWalletKeys.js')(shepherd); + +shepherd.printDirs(); -/* - * type: GET - * - */ -shepherd.get('/appinfo', function(req, res, next) { - const obj = shepherd.appInfo(); - res.send(obj); +// default route +shepherd.get('/', (req, res, next) => { + res.send('Agama app server'); }); -shepherd.dumpCacheBeforeExit = function() { - cache.dumpCacheBeforeExit(); -} - -var cache = require('./cache'); -var mock = require('./mock'); - // expose sockets obj -shepherd.setIO = function(io) { +shepherd.setIO = (io) => { shepherd.io = io; - cache.setVar('io', io); - cache.setVar('shepherd', shepherd); }; -shepherd.setVar = function(_name, _body) { +shepherd.setVar = (_name, _body) => { shepherd[_name] = _body; }; -cache.setVar('iguanaDir', iguanaDir); -cache.setVar('appConfig', shepherd.appConfig); - -// fetch sync only forks info -shepherd.getSyncOnlyForksInfo = function() { - async.forEachOf(iguanaInstanceRegistry, function(data, port) { - if (iguanaInstanceRegistry[port].mode.indexOf('/sync') > -1) { - syncOnlyIguanaInstanceInfo[port] = {}; - request({ - url: `http://localhost:${port}/api/bitcoinrpc/getinfo?userpass=tmpIgRPCUser@${shepherd.appSessionHash}`, - method: 'GET' - }, function(error, response, body) { - if (response && - response.statusCode && - response.statusCode === 200) { - // console.log(body); - try { - syncOnlyIguanaInstanceInfo[port].getinfo = JSON.parse(body); - } catch(e) {} - } else { - // TODO: error - } - }); - request({ - url: `http://localhost:${port}/api/SuperNET/activehandle?userpass=${shepherd.appSessionHash}`, - method: 'GET' - }, function(error, response, body) { - if (response && - response.statusCode && - response.statusCode === 200) { - // console.log(body); - try { - syncOnlyIguanaInstanceInfo[port].activehandle = JSON.parse(body); - } catch(e) {} - } else { - // TODO: error - } - }); - syncOnlyIguanaInstanceInfo[port].registry = iguanaInstanceRegistry[port]; - } - }); -} - -/* - * type: GET - * - */ -shepherd.get('/forks/info/start', function(req, res, next) { - const successObj = { - msg: 'success', - result: 'started', - }; - - res.end(JSON.stringify(successObj)); - shepherd.getSyncOnlyForksInfo(); -}); - -/* - * type: GET - * - */ -shepherd.get('/forks/info/show', function(req, res, next) { - const successObj = { - msg: 'success', - result: JSON.stringify(syncOnlyIguanaInstanceInfo), - }; - - res.end(JSON.stringify(successObj)); -}); - -/* - * type: GET - * - */ -shepherd.get('/forks/restart', function(req, res, next) { - const _pmid = req.query.pmid; - - pm2.connect(function(err) { - if (err) { - shepherd.error(err); - } - - pm2.restart(_pmid, function(err, ret) { - if (err) { - shepherd.error(err); - } - pm2.disconnect(); - - const successObj = { - msg: 'success', - result: 'restarted', - }; - shepherd.writeLog(`iguana fork pmid ${_pmid} restarted`); - - res.end(JSON.stringify(successObj)); - }); - }); -}); - -/* - * type: GET - * - */ -shepherd.get('/forks/stop', function(req, res, next) { - const _pmid = req.query.pmid; - - pm2.connect(function(err) { - if (err) { - shepherd.error(err); - } - - pm2.stop(_pmid, function(err, ret) { - if (err) { - shepherd.error(err); - } - pm2.disconnect(); - - const successObj = { - msg: 'success', - result: 'stopped', - }; - - shepherd.writeLog(`iguana fork pmid ${_pmid} stopped`); - - res.end(JSON.stringify(successObj)); - }); - }); -}); - -/* - * type: GET - * - */ -shepherd.get('/forks', function(req, res, next) { - const successObj = { - msg: 'success', - result: iguanaInstanceRegistry, - }; - - res.end(JSON.stringify(successObj)); -}); - -/* - * type: POST - * params: name - */ -shepherd.post('/forks', function(req, res, next) { - const mode = req.body.mode; - const coin = req.body.coin; - const port = shepherd.appConfig.iguanaCorePort; - - portscanner.findAPortNotInUse(port, port + 100, '127.0.0.1', function(error, _port) { - pm2.connect(true, function(err) { //start up pm2 god - if (err) { - shepherd.error(err); - process.exit(2); - } - - shepherd.log(`iguana core fork port ${_port}`); - shepherd.writeLog(`iguana core fork port ${_port}`); - - pm2.start({ - script: iguanaBin, // path to binary - name: `IGUANA ${_port} ${mode} / ${coin}`, - exec_mode : 'fork', - args: [`-port=${_port}`], - cwd: iguanaDir //set correct iguana directory - }, function(err, apps) { - if (apps && - apps[0] && - apps[0].process && - apps[0].process.pid) { - iguanaInstanceRegistry[_port] = { - mode: mode, - coin: coin, - pid: apps[0].process && apps[0].process.pid, - pmid: apps[0].pm2_env.pm_id, - }; - cache.setVar('iguanaInstances', iguanaInstanceRegistry); - - const successObj = { - msg: 'success', - result: _port, - }; - - res.end(JSON.stringify(successObj)); - } else { - const errorObj = { - msg: 'success', - error: 'iguana start error', - }; - - res.end(JSON.stringify(errorObj)); - } - - // get sync only forks info - if (syncOnlyInstanceInterval === -1) { - setTimeout(function() { - shepherd.getSyncOnlyForksInfo(); - }, 5000); - setInterval(function() { - shepherd.getSyncOnlyForksInfo(); - }, 20000); - } - - pm2.disconnect(); // Disconnect from PM2 - if (err) { - shepherd.writeLog(`iguana fork error: ${err}`); - shepherd.log(`iguana fork error: ${err}`); - // throw err; - } - }); - }); - }); -}); - -/* - * type: GET - * - */ -shepherd.get('/InstantDEX/allcoins', function(req, res, next) { - // TODO: if only native return obj - // else query main iguana instance and return combined response - // http://localhost:7778/api/InstantDEX/allcoins?userpass=tmpIgRPCUser@1234 - let successObj; - let nativeCoindList = []; - - for (let key in coindInstanceRegistry) { - nativeCoindList.push(key === 'komodod' ? 'KMD' : key); - } - - if (Object.keys(iguanaInstanceRegistry).length) { - // call to iguana - request({ - url: `http://localhost:${shepherd.appConfig.iguanaCorePort}/api/InstantDEX/allcoins?userpass=${req.query.userpass}`, - method: 'GET' - }, function(error, response, body) { - if (response && - response.statusCode && - response.statusCode === 200) { - const _body = JSON.parse(body); - _body.native = nativeCoindList; - shepherd.log(_body); - } else { - shepherd.log('main iguana instance is not ready yet'); - } - - res.send(body); - }); - } else { - successObj = { - native: nativeCoindList, - basilisk: [], - full: [], - }; - - res.end(JSON.stringify(successObj)); - } -}); - -/* - * type: GET - * - */ -shepherd.get('/SuperNET/activehandle', function(req, res, next) { // not finished - // TODO: if only native return obj - // else query main iguana instance and return combined response - // http://localhost:7778/api/SuperNET/activehandle?userpass=tmpIgRPCUser@1234 - let successObj; - - if (Object.keys(iguanaInstanceRegistry).length) { - // call to iguana - request({ - url: `http://localhost:${shepherd.appConfig.iguanaCorePort}/api/SuperNET/activehandle?userpass=${req.query.userpass}`, - method: 'GET' - }, function(error, response, body) { - if (response && - response.statusCode && - response.statusCode === 200) { - shepherd.log(body); - } else { - shepherd.log('main iguana instance is not ready yet'); - } - - res.send(body); - }); - } else { - successObj = { - pubkey: 'nativeonly', - result: 'success', - handle: '', - status: Object.keys(coindInstanceRegistry).length ? 'unlocked' : 'locked', - duration: 2507830, - }; - - res.end(JSON.stringify(successObj)); - } -}); - -/* - * type: GET - * params: pubkey - */ -shepherd.get('/cache', function(req, res, next) { - cache.get(req, res, next); -}); - -/* - * type: GET - * params: filename - */ -shepherd.get('/groom', function(req, res, next) { - cache.groomGet(req, res, next); -}) - -/* - * type: DELETE - * params: filename - */ -shepherd.delete('/groom', function(req, res, next) { - cache.groomDelete(req, res, next); -}); - -/* - * type: POST - * params: filename, payload - */ -shepherd.post('/groom', function(req, res, next) { - cache.groomPost(req, res, next); -}); - -/* - * type: GET - * params: userpass, pubkey, skip - */ -shepherd.get('/cache-all', function(req, res, next) { - cache.all(req, res, next); -}); - -/* - * type: GET - * params: userpass, pubkey, coin, address, skip - */ -shepherd.get('/cache-one', function(req, res, next) { - cache.one(req, res, next); -}); - -/* - * type: GET - */ -shepherd.get('/mock', function(req, res, next) { - mock.get(req, res, next); -}); - -/* - * type: GET - * params: herd, lastLines - */ -shepherd.post('/debuglog', function(req, res) { - let _herd = req.body.herdname; - let _ac = req.body.ac; - let _lastNLines = req.body.lastLines; - let _location; - - if (os.platform() === 'darwin') { - komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.HOME}/Library/Application Support/Komodo`; - } - - if (os.platform() === 'linux') { - komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.HOME}/.komodo`; - } - - if (os.platform() === 'win32') { - komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.APPDATA}/Komodo`; - komodoDir = path.normalize(komodoDir); - } - - if (_herd === 'iguana') { - _location = iguanaDir; - } else if (_herd === 'komodo') { - _location = komodoDir; - } - - if (_ac) { - _location = `${komodoDir}/${_ac}`; - } - - shepherd.readDebugLog(`${_location}/debug.log`, _lastNLines) - .then(function(result) { - const _obj = { - msg: 'success', - result: result, - }; - - res.end(JSON.stringify(_obj)); - }, function(result) { - const _obj = { - msg: 'error', - result: result, - }; - - res.end(JSON.stringify(_obj)); - }); -}); - -/* - * type: POST - * params: herd - */ -shepherd.post('/herd', function(req, res) { - shepherd.log('======= req.body ======='); - shepherd.log(req.body); - - if (req.body.options && - !shepherd.kmdMainPassiveMode) { - function testCoindPort(skipError) { - if (!lockDownAddCoin) { - const _port = assetChainPorts[req.body.options.ac_name]; - - portscanner.checkPortStatus(_port, '127.0.0.1', function(error, status) { - // Status is 'open' if currently in use or 'closed' if available - if (status === 'open') { - if (!skipError) { - shepherd.log(`komodod service start error at port ${_port}, reason: port is closed`); - shepherd.writeLog(`komodod service start error at port ${_port}, reason: port is closed`); - cache.io.emit('service', { - komodod: { - error: `error starting ${req.body.herd} ${req.body.options.ac_name} daemon. Port ${_port} is already taken!`, - }, - }); - - const obj = { - msg: 'error', - result: `error starting ${req.body.herd} ${req.body.options.ac_name} daemon. Port ${_port} is already taken!`, - }; - - res.status(500); - res.end(JSON.stringify(obj)); - } else { - shepherd.log(`komodod service start success at port ${_port}`); - shepherd.writeLog(`komodod service start success at port ${_port}`); - } - } else { - if (!skipError) { - herder(req.body.herd, req.body.options); - - const obj = { - msg: 'success', - result: 'result', - }; - - res.end(JSON.stringify(obj)); - } else { - shepherd.log(`komodod service start error at port ${_port}, reason: unknown`); - shepherd.writeLog(`komodod service start error at port ${_port}, reason: unknown`); - } - } - }); - } - } - - if (req.body.herd === 'komodod') { - // check if komodod instance is already running - testCoindPort(); - setTimeout(function() { - testCoindPort(true); - }, 10000); - } else { - herder(req.body.herd, req.body.options); - - const obj = { - msg: 'success', - result: 'result', - }; - - res.end(JSON.stringify(obj)); - } - } else { - herder(req.body.herd, req.body.options); - - const obj = { - msg: 'success', - result: 'result', - }; - - res.end(JSON.stringify(obj)); - } -}); - -/* - * type: POST - * params: herdname - */ -shepherd.post('/herdlist', function(req, res) { - shepherd.log(req.body.herdname); - - pm2.connect(true, function(err) { - if (err) { - shepherd.writeLog(`herdlist err: ${err}`); - shepherd.log(`herdlist err:: ${err}`); - } - pm2.describe(req.body.herdname, function(err, list) { - pm2.disconnect(); // disconnect after getting proc info list - - if (err) { - shepherd.writeLog(`pm2.describe err: ${err}`); - shepherd.log(`pm2.describe err: ${err}`); - } - - shepherd.log(list[0].pm2_env.status) // print status of IGUANA proc - shepherd.log(list[0].pid) // print pid of IGUANA proc - shepherd.writeLog(list[0].pm2_env.status); - shepherd.writeLog(list[0].pid); - - const obj = { - herdname: req.body.herdname, - status: list[0].pm2_env.status, - pid: list[0].pid, - }; - - res.end(JSON.stringify(obj)); - }); - }); -}); - -/* - * type: POST - */ -shepherd.post('/slay', function(req, res) { - shepherd.log('======= req.body ======='); - shepherd.log(req.body); - - slayer(req.body.slay); - const obj = { - msg: 'success', - result: 'result', - }; - - res.end(JSON.stringify(obj)); -}); - -/* - * type: POST - */ -shepherd.post('/setconf', function(req, res) { - shepherd.log('======= req.body ======='); - shepherd.log(req.body); - - if (os.platform() === 'win32' && - req.body.chain == 'komodod') { - setkomodoconf = spawn(path.join(__dirname, '../assets/bin/win64/genkmdconf.bat')); - } else { - setConf(req.body.chain); - } - - const obj = { - msg: 'success', - result: 'result', - }; - - res.end(JSON.stringify(obj)); -}); - -/* - * type: POST - */ -shepherd.post('/getconf', function(req, res) { - shepherd.log('======= req.body ======='); - shepherd.log(req.body); - - const confpath = getConf(req.body.chain); - - shepherd.log('got conf path is:'); - shepherd.log(confpath); - shepherd.writeLog('got conf path is:'); - shepherd.writeLog(confpath); - - const obj = { - msg: 'success', - result: confpath, - }; - - res.end(JSON.stringify(obj)); -}); - -/* - * type: GET - * params: coin, type - */ -/*shepherd.get('/kick', function(req, res, next) { - const _coin = req.query.coin; - const _type = req.query.type; - - if (!_coin) { - const errorObj = { - msg: 'error', - result: 'no coin name provided', - }; - - res.end(JSON.stringify(errorObj)); - } - - if (!_type) { - const errorObj = { - msg: 'error', - result: 'no type provided', - }; - - res.end(JSON.stringify(errorObj)); - } - - const kickStartDirs = { - soft: [ - { - name: 'DB/[coin]', - type: 'pattern', - match: 'balancecrc.', - }, - { - name: 'DB/[coin]/utxoaddrs', - type: 'file', - }, - { - name: 'DB/[coin]/accounts', - type: 'folder', - }, - { - name: 'DB/[coin]/fastfind', - type: 'folder', - }, - { - name: 'tmp/[coin]', - type: 'folder', - } - ], - hard: [ - { - name: 'DB/[coin]', - type: 'pattern', - match: 'balancecrc.', - }, - { - name: 'DB/[coin]/utxoaddrs', - type: 'file', - }, - { - name: 'DB/[coin]', - type: 'pattern', - match: 'utxoaddrs.', - }, - { - name: 'DB/[coin]/accounts', - type: 'folder', - }, - { - name: 'DB/[coin]/fastfind', - type: 'folder', - }, - { - name: 'DB/[coin]/spends', - type: 'folder', - }, - { - name: 'tmp/[coin]', - type: 'folder', - } - ], - brutal: [ // delete all coin related data - { - name: 'DB/[coin]', - type: 'folder', - }, - { - name: 'DB/purgeable/[coin]', - type: 'folder', - }, - { - name: 'DB/ro/[coin]', - type: 'folder', - }, - { - name: 'tmp/[coin]', - type: 'folder', - } - ] - }; - - if (_coin && - _type) { - for (let i = 0; i < kickStartDirs[_type].length; i++) { - let currentKickItem = kickStartDirs[_type][i]; - - shepherd.log('deleting ' + currentKickItem.type + (currentKickItem.match ? ' ' + currentKickItem.match : '') + ' ' + iguanaDir + '/' + currentKickItem.name.replace('[coin]', _coin)); - if (currentKickItem.type === 'folder' || - currentKickItem.type === 'file') { - rimraf(`${iguanaDir}/${currentKickItem.name.replace('[coin]', _coin)}`, function(err) { - if (err) { - shepherd.writeLog(`kickstart err: ${err}`); - shepherd.log(`kickstart err: ${err}`); - } - }); - } else if (currentKickItem.type === 'pattern') { - let dirItems = fs.readdirSync(`${iguanaDir}/currentKickItem.name.replace('[coin]', _coin)`); - - if (dirItems && - dirItems.length) { - for (let j = 0; j < dirItems.length; j++) { - if (dirItems[j].indexOf(currentKickItem.match) > -1) { - rimraf(`${iguanaDir}/${currentKickItem.name.replace('[coin]', _coin)}/${dirItems[j]}`, function(err) { - if (err) { - shepherd.writeLog(`kickstart err: ${err}`); - shepherd.log(`kickstart err: ${err}`); - } - }); - - shepherd.log(`deleting ${dirItems[j]}`); - } - } - } - } - } - - const successObj = { - msg: 'success', - result: 'kickstart: brutal is executed', - }; - - res.end(JSON.stringify(successObj)); - } -});*/ - -shepherd.readDebugLog = function(fileLocation, lastNLines) { - return new Promise( - function(resolve, reject) { - if (lastNLines) { - try { - _fs.access(fileLocation, fs.constants.R_OK, function(err) { - if (err) { - shepherd.log(`error reading ${fileLocation}`); - shepherd.writeLog(`error reading ${fileLocation}`); - reject(`readDebugLog error: ${err}`); - } else { - shepherd.log(`reading ${fileLocation}`); - _fs.readFile(fileLocation, 'utf-8', function(err, data) { - if (err) { - shepherd.writeLog(`readDebugLog err: ${err}`); - shepherd.log(`readDebugLog err: ${err}`); - } - - const lines = data.trim().split('\n'); - const lastLine = lines.slice(lines.length - lastNLines, lines.length).join('\n'); - - resolve(lastLine); - }); - } - }); - } catch (e) { - reject(`readDebugLog error: ${e}`); - } - } else { - reject('readDebugLog error: lastNLines param is not provided!'); - } - } - ); -}; - -function herder(flock, data) { - if (data === undefined) { - data = 'none'; - shepherd.log('it is undefined'); - } - - if (flock === 'iguana') { - shepherd.log('iguana flock selected...'); - shepherd.log(`selected data: ${data}`); - shepherd.writeLog('iguana flock selected...'); - shepherd.writeLog(`selected data: ${data}`); - - // COPY CONFS DIR WITH PEERS FILE TO IGUANA DIR, AND KEEP IT IN SYNC - fs.copy(iguanaConfsDirSrc, iguanaConfsDir, function(err) { - if (err) - return shepherd.error(err); - - shepherd.log(`confs files copied successfully at: ${iguanaConfsDir}`); - shepherd.writeLog(`confs files copied successfully at: ${iguanaConfsDir}`); - }); - - pm2.connect(true,function(err) { //start up pm2 god - if (err) { - shepherd.error(err); - process.exit(2); - } - - shepherd.log(`iguana core port ${shepherd.appConfig.iguanaCorePort}`); - shepherd.writeLog(`iguana core port ${shepherd.appConfig.iguanaCorePort}`); - - pm2.start({ - script: iguanaBin, // path to binary - name: 'IGUANA', - exec_mode : 'fork', - args: [`-port=${shepherd.appConfig.iguanaCorePort}`], - cwd: iguanaDir // set correct iguana directory - }, function(err, apps) { - if (apps[0] && - apps[0].process && - apps[0].process.pid) { - iguanaInstanceRegistry[shepherd.appConfig.iguanaCorePort] = { - mode: 'main', - coin: 'none', - pid: apps[0].process.pid, - pmid: apps[0].pm2_env.pm_id, - }; - shepherd.writeLog(`iguana core started at port ${shepherd.appConfig.iguanaCorePort} pid ${apps[0].process.pid}`); - } else { - shepherd.writeLog(`unable to start iguana core at port ${shepherd.appConfig.iguanaCorePort}`); - shepherd.log(`unable to start iguana core at port ${shepherd.appConfig.iguanaCorePort}`); - } - - pm2.disconnect(); // Disconnect from PM2 - if (err) { - shepherd.writeLog(`iguana core port ${shepherd.appConfig.iguanaCorePort}`); - shepherd.log(`iguana fork error: ${err}`); - // throw err; - } - }); - }); - } - - // TODO: notify gui that reindex/rescan param is used to reflect on the screen - // asset chain debug.log unlink - if (flock === 'komodod') { - let kmdDebugLogLocation = (data.ac_name !== 'komodod' ? komodoDir + '/' + data.ac_name : komodoDir) + '/debug.log'; - - shepherd.log('komodod flock selected...'); - shepherd.log('selected data: ' + JSON.stringify(data, null, '\t')); - shepherd.writeLog('komodod flock selected...'); - shepherd.writeLog(`selected data: ${data}`); - - // truncate debug.log - if (!shepherd.kmdMainPassiveMode) { - try { - const _confFileAccess = _fs.accessSync(kmdDebugLogLocation, fs.R_OK | fs.W_OK); - - if (_confFileAccess) { - shepherd.log(`error accessing ${kmdDebugLogLocation}`); - shepherd.writeLog(`error accessing ${kmdDebugLogLocation}`); - } else { - try { - fs.unlinkSync(kmdDebugLogLocation); - shepherd.log(`truncate ${kmdDebugLogLocation}`); - shepherd.writeLog(`truncate ${kmdDebugLogLocation}`); - } catch (e) { - shepherd.log('cant unlink debug.log'); - } - } - } catch(e) { - shepherd.log(`komodod debug.log access err: ${e}`); - shepherd.writeLog(`komodod debug.log access err: ${e}`); - } - } - - // get komodod instance port - const _port = assetChainPorts[data.ac_name]; - - try { - // check if komodod instance is already running - portscanner.checkPortStatus(_port, '127.0.0.1', function(error, status) { - // Status is 'open' if currently in use or 'closed' if available - if (status === 'closed') { - // start komodod via exec - const _customParamDict = { - silent: '&', - reindex: '-reindex', - change: '-pubkey=', - datadir: '-datadir=', - rescan: '-rescan', - }; - let _customParam = ''; - - if (data.ac_custom_param === 'silent' || - data.ac_custom_param === 'reindex' || - data.ac_custom_param === 'rescan') { - _customParam = ` ${_customParamDict[data.ac_custom_param]}`; - } else if (data.ac_custom_param === 'change' && data.ac_custom_param_value) { - _customParam = ` ${_customParamDict[data.ac_custom_param]}${data.ac_custom_param_value}`; - } - - if (shepherd.appConfig.dataDir.length) { - _customParam = _customParam + ' -datadir=' + shepherd.appConfig.dataDir + '/' + data.ac_name; - } - - shepherd.log(`exec ${komododBin} ${data.ac_options.join(' ')}${_customParam}`); - shepherd.writeLog(`exec ${komododBin} ${data.ac_options.join(' ')}${_customParam}`); - - const isChain = data.ac_name.match(/^[A-Z]*$/); - const coindACParam = isChain ? ` -ac_name=${data.ac_name} ` : ''; - shepherd.log(`daemon param ${data.ac_custom_param}`); - - coindInstanceRegistry[data.ac_name] = true; - if (!shepherd.kmdMainPassiveMode) { - let _arg = `${coindACParam}${data.ac_options.join(' ')}${_customParam}`; - _arg = _arg.trim().split(' '); - execFile(`${komododBin}`, _arg, { - maxBuffer: 1024 * 1000000 // 1000 mb - }, function(error, stdout, stderr) { - shepherd.writeLog(`stdout: ${stdout}`); - shepherd.writeLog(`stderr: ${stderr}`); - - if (error !== null) { - shepherd.log(`exec error: ${error}`); - shepherd.writeLog(`exec error: ${error}`); - - if (error.toString().indexOf('using -reindex') > -1) { - cache.io.emit('service', { - komodod: { - error: 'run -reindex', - }, - }); - } - } - }); - } - } else { - if (shepherd.kmdMainPassiveMode) { - coindInstanceRegistry[data.ac_name] = true; - } - shepherd.log(`port ${_port} (${data.ac_name}) is already in use`); - shepherd.writeLog(`port ${_port} (${data.ac_name}) is already in use`); - } - }); - } catch(e) { - shepherd.log(`failed to start komodod err: ${e}`); - shepherd.writeLog(`failed to start komodod err: ${e}`); - } - } - - if (flock === 'zcashd') { - let kmdDebugLogLocation = `${zcashDir}/debug.log`; - - shepherd.log('zcashd flock selected...'); - shepherd.log(`selected data: ${data}`); - shepherd.writeLog('zcashd flock selected...'); - shepherd.writeLog(`selected data: ${data}`); - - pm2.connect(true, function(err) { // start up pm2 god - if (err) { - shepherd.error(err); - process.exit(2); - } - - pm2.start({ - script: zcashdBin, // path to binary - name: data.ac_name, // REVS, USD, EUR etc. - exec_mode: 'fork', - cwd: zcashDir, - args: data.ac_options - }, function(err, apps) { - shepherd.writeLog(`zcashd fork started ${data.ac_name} ${JSON.stringify(data.ac_options)}`); - - pm2.disconnect(); // Disconnect from PM2 - if (err) { - shepherd.writeLog(`pm2.disconnect err: ${err}`); - shepherd.log(`pm2.disconnect err: ${err}`); - } - // throw err; - }); - }); - } -} - -function slayer(flock) { - shepherd.log(flock); - - pm2.delete(flock, function(err, ret) { - pm2.disconnect(); - shepherd.writeLog(`deleting flock ${flock}`); - shepherd.writeLog(ret); - - shepherd.log(ret); - }); -} - -shepherd.setConfKMD = function() { - let komodoDir; - let zcashDir; - - if (os.platform() === 'darwin') { - komodoDir = `${process.env.HOME}/Library/Application Support/Komodo`; - ZcashDir = `${process.env.HOME}/Library/Application Support/Zcash`; - } - - if (os.platform() === 'linux') { - komodoDir = `${process.env.HOME}/.komodo`; - ZcashDir = `${process.env.HOME}/.zcash`; - } - - if (os.platform() === 'win32') { - komodoDir = `${process.env.APPDATA}/Komodo`; - ZcashDir = `${process.env.APPDATA}/Zcash`; - } - - // check if kmd conf exists - _fs.access(`${komodoDir}/komodo.conf`, fs.constants.R_OK, function(err) { - if (err) { - shepherd.log('creating komodo conf'); - shepherd.writeLog(`creating komodo conf in ${komodoDir}/komodo.conf`); - setConf('komodod'); - } else { - const _komodoConfSize = fs.lstatSync(`${komodoDir}/komodo.conf`); - - if (_komodoConfSize.size === 0) { - shepherd.log('err: komodo conf file is empty, creating komodo conf'); - shepherd.writeLog(`creating komodo conf in ${komodoDir}/komodo.conf`); - setConf('komodod'); - } else { - shepherd.writeLog('komodo conf exists'); - shepherd.log('komodo conf exists'); - } - } - }); -} - -function setConf(flock) { - let komodoDir; - let zcashDir; - - shepherd.log(flock); - shepherd.writeLog(`setconf ${flock}`); - - if (os.platform() === 'darwin') { - komodoDir = `${process.env.HOME}/Library/Application Support/Komodo`; - ZcashDir = `${process.env.HOME}/Library/Application Support/Zcash`; - } - - if (os.platform() === 'linux') { - komodoDir = `${process.env.HOME}/.komodo`; - ZcashDir = `${process.env.HOME}/.zcash`; - } - - if (os.platform() === 'win32') { - komodoDir = `${process.env.APPDATA}/Komodo`; - ZcashDir = `${process.env.APPDATA}/Zcash`; - } - - let DaemonConfPath; - switch (flock) { - case 'komodod': - DaemonConfPath = `${komodoDir}/komodo.conf`; - - if (os.platform() === 'win32') { - DaemonConfPath = path.normalize(DaemonConfPath); - } - break; - case 'zcashd': - DaemonConfPath = `${ZcashDir}/zcash.conf`; - - if (os.platform() === 'win32') { - DaemonConfPath = path.normalize(DaemonConfPath); - } - break; - default: - DaemonConfPath = `${komodoDir}/${flock}/${flock}.conf`; - - if (os.platform() === 'win32') { - DaemonConfPath = path.normalize(DaemonConfPath); - } - } - - shepherd.log(DaemonConfPath); - shepherd.writeLog(`setconf ${DaemonConfPath}`); - - const CheckFileExists = function() { - return new Promise(function(resolve, reject) { - const result = 'Check Conf file exists is done' - - const confFileExist = fs.ensureFileSync(DaemonConfPath); - if (confFileExist) { - shepherd.log(result); - shepherd.writeLog(`setconf ${result}`); - - resolve(result); - } else { - shepherd.log('conf file doesnt exist'); - resolve('conf file doesnt exist'); - } - }); - } - - const FixFilePermissions = function() { - return new Promise(function(resolve, reject) { - const result = 'Conf file permissions updated to Read/Write'; - - fsnode.chmodSync(DaemonConfPath, '0666'); - shepherd.log(result); - shepherd.writeLog(`setconf ${result}`); - - resolve(result); - }); - } - - const RemoveLines = function() { - return new Promise(function(resolve, reject) { - const result = 'RemoveLines is done'; - - fs.readFile(DaemonConfPath, 'utf8', function(err, data) { - if (err) { - shepherd.writeLog(`setconf error ${err}`); - return shepherd.log(err); - } - - const rmlines = data.replace(/(?:(?:\r\n|\r|\n)\s*){2}/gm, '\n'); - - fs.writeFile(DaemonConfPath, rmlines, 'utf8', function(err) { - if (err) - return shepherd.log(err); - - fsnode.chmodSync(DaemonConfPath, '0666'); - shepherd.writeLog(`setconf ${result}`); - shepherd.log(result); - resolve(result); - }); - }); - }); - } - - const CheckConf = function() { - return new Promise(function(resolve, reject) { - const result = 'CheckConf is done'; - - setconf.status(DaemonConfPath, function(err, status) { - const rpcuser = function() { - return new Promise(function(resolve, reject) { - const result = 'checking rpcuser...'; - - if (status[0].hasOwnProperty('rpcuser')) { - shepherd.log('rpcuser: OK'); - shepherd.writeLog('rpcuser: OK'); - } else { - const randomstring = md5((Math.random() * Math.random() * 999).toString()); - - shepherd.log('rpcuser: NOT FOUND'); - shepherd.writeLog('rpcuser: NOT FOUND'); - - fs.appendFile(DaemonConfPath, `\nrpcuser=user${randomstring.substring(0, 16)}`, (err) => { - if (err) { - shepherd.writeLog(`append daemon conf err: ${err}`); - shepherd.log(`append daemon conf err: ${err}`); - } - // throw err; - shepherd.log('rpcuser: ADDED'); - shepherd.writeLog('rpcuser: ADDED'); - }); - } - - resolve(result); - }); - } - - const rpcpass = function() { - return new Promise(function(resolve, reject) { - const result = 'checking rpcpassword...'; - - if (status[0].hasOwnProperty('rpcpassword')) { - shepherd.log('rpcpassword: OK'); - shepherd.writeLog('rpcpassword: OK'); - } else { - var randomstring = md5((Math.random() * Math.random() * 999).toString()); - - shepherd.log('rpcpassword: NOT FOUND'); - shepherd.writeLog('rpcpassword: NOT FOUND'); - - fs.appendFile(DaemonConfPath, `\nrpcpassword=${randomstring}`, (err) => { - if (err) { - shepherd.writeLog(`append daemon conf err: ${err}`); - shepherd.log(`append daemon conf err: ${err}`); - } - // throw err; - shepherd.log('rpcpassword: ADDED'); - shepherd.writeLog('rpcpassword: ADDED'); - }); - } - - resolve(result); - }); - } - - const rpcbind = function() { - return new Promise(function(resolve, reject) { - const result = 'checking rpcbind...'; - - if (status[0].hasOwnProperty('rpcbind')) { - shepherd.log('rpcbind: OK'); - shepherd.writeLog('rpcbind: OK'); - } else { - shepherd.log('rpcbind: NOT FOUND'); - shepherd.writeLog('rpcbind: NOT FOUND'); - - fs.appendFile(DaemonConfPath, '\nrpcbind=127.0.0.1', (err) => { - if (err) { - shepherd.writeLog(`append daemon conf err: ${err}`); - shepherd.log(`append daemon conf err: ${err}`); - } - // throw err; - shepherd.log('rpcbind: ADDED'); - shepherd.writeLog('rpcbind: ADDED'); - }); - } - - resolve(result); - }); - } - - const server = function() { - return new Promise(function(resolve, reject) { - const result = 'checking server...'; - - if (status[0].hasOwnProperty('server')) { - shepherd.log('server: OK'); - shepherd.writeLog('server: OK'); - } else { - shepherd.log('server: NOT FOUND'); - shepherd.writeLog('server: NOT FOUND'); - - fs.appendFile(DaemonConfPath, '\nserver=1', (err) => { - if (err) { - shepherd.writeLog(`append daemon conf err: ${err}`); - shepherd.log(`append daemon conf err: ${err}`); - } - // throw err; - shepherd.log('server: ADDED'); - shepherd.writeLog('server: ADDED'); - }); - } - - resolve(result); - }); - } - - const addnode = function() { - return new Promise(function(resolve, reject) { - const result = 'checking addnode...'; - - if (status[0].hasOwnProperty('addnode')) { - shepherd.log('addnode: OK'); - shepherd.writeLog('addnode: OK'); - } else { - shepherd.log('addnode: NOT FOUND') - fs.appendFile(DaemonConfPath, - '\naddnode=78.47.196.146' + - '\naddnode=5.9.102.210' + - '\naddnode=178.63.69.164' + - '\naddnode=88.198.65.74' + - '\naddnode=5.9.122.241' + - '\naddnode=144.76.94.3', - (err) => { - if (err) { - shepherd.writeLog(`append daemon conf err: ${err}`); - shepherd.log(`append daemon conf err: ${err}`); - } - // throw err; - shepherd.log('addnode: ADDED'); - shepherd.writeLog('addnode: ADDED'); - }); - } - - resolve(result); - }); - } - - rpcuser() - .then(function(result) { - return rpcpass(); - }) - .then(server) - .then(rpcbind) - .then(addnode); - }); - - shepherd.log(result); - shepherd.writeLog(`checkconf addnode ${result}`); - - resolve(result); - }); - } - - CheckFileExists() - .then(function(result) { - return FixFilePermissions(); - }) - .then(RemoveLines) - .then(CheckConf); -} - -function getConf(flock) { - let komodoDir = ''; - let ZcashDir = ''; - let DaemonConfPath = ''; - - shepherd.log(flock); - shepherd.writeLog(`getconf flock: ${flock}`); - - if (os.platform() === 'darwin') { - komodoDir = `${process.env.HOME}/Library/Application Support/Komodo`; - ZcashDir = `${process.env.HOME}/Library/Application Support/Zcash`; - } - - if (os.platform() === 'linux') { - komodoDir = `${process.env.HOME}/.komodo`; - ZcashDir = `${process.env.HOME}/.zcash`; - } - - if (os.platform() === 'win32') { - komodoDir = `${process.env.APPDATA}/Komodo`; - ZcashDir = `${process.env.APPDATA}/Zcash`; - } - - switch (flock) { - case 'komodod': - DaemonConfPath = komodoDir; - if (os.platform() === 'win32') { - DaemonConfPath = path.normalize(DaemonConfPath); - shepherd.log('===>>> SHEPHERD API OUTPUT ===>>>'); - } - break; - case 'zcashd': - DaemonConfPath = ZcashDir; - if (os.platform() === 'win32') { - DaemonConfPath = path.normalize(DaemonConfPath); - } - break; - default: - DaemonConfPath = `${komodoDir}/${flock}`; - if (os.platform() === 'win32') { - DaemonConfPath = path.normalize(DaemonConfPath); - } - } - - shepherd.writeLog(`getconf path: ${DaemonConfPath}`); - shepherd.log(DaemonConfPath); - return DaemonConfPath; -} - -function formatBytes(bytes, decimals) { - if (bytes === 0) - return '0 Bytes'; - - const k = 1000; - const dm = (decimals + 1) || 3; - const sizes = [ - 'Bytes', - 'KB', - 'MB', - 'GB', - 'TB', - 'PB', - 'EB', - 'ZB', - 'YB' - ]; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - - return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`; -} - -shepherd.SystemInfo = function() { - const os_data = { - 'totalmem_bytes': os.totalmem(), - 'totalmem_readable': formatBytes(os.totalmem()), - 'arch': os.arch(), - 'cpu': os.cpus()[0].model, - 'cpu_cores': os.cpus().length, - 'platform': os.platform(), - 'os_release': os.release(), - 'os_type': os.type() - }; - - return os_data; -} - -shepherd.appInfo = function() { - const sysInfo = shepherd.SystemInfo(); - const releaseInfo = shepherd.appBasicInfo; - const dirs = { - iguanaDir, - iguanaBin, - komodoDir, - komododBin, - configLocation: `${iguanaDir}/config.json`, - cacheLocation: `${iguanaDir}/shepherd`, - }; - - return { - sysInfo, - releaseInfo, - dirs, - appSession: shepherd.appSessionHash - }; -} - module.exports = shepherd; \ No newline at end of file diff --git a/routes/shepherd/addCoinShortcuts.js b/routes/shepherd/addCoinShortcuts.js new file mode 100644 index 0000000..7232d37 --- /dev/null +++ b/routes/shepherd/addCoinShortcuts.js @@ -0,0 +1,103 @@ +module.exports = (shepherd) => { + shepherd.startSPV = (coin) => { + if (coin === 'KMD+REVS+JUMBLR') { + shepherd.addElectrumCoin('KMD'); + shepherd.addElectrumCoin('REVS'); + shepherd.addElectrumCoin('JUMBLR'); + } else { + shepherd.addElectrumCoin(coin); + } + } + + shepherd.startKMDNative = (selection, isManual) => { + if (isManual) { + shepherd.kmdMainPassiveMode = true; + } + + if (selection === 'KMD') { + const herdData = { + 'ac_name': 'komodod', + 'ac_options': [ + '-daemon=0', + '-addnode=78.47.196.146', + ], + }; + + const options = { + url: `http://127.0.0.1:${shepherd.appConfig.agamaPort}/shepherd/herd`, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + herd: 'komodod', + options: herdData, + }) + }; + + shepherd.request(options, (error, response, body) => { + if (response && + response.statusCode && + response.statusCode === 200) { + //resolve(body); + } else { + //resolve(body); + } + }); + } else { + const herdData = [{ + 'ac_name': 'komodod', + 'ac_options': [ + '-daemon=0', + '-addnode=78.47.196.146', + ] + }, { + 'ac_name': 'REVS', + 'ac_options': [ + '-daemon=0', + '-server', + `-ac_name=REVS`, + '-addnode=78.47.196.146', + '-ac_supply=1300000' + ] + }, { + 'ac_name': 'JUMBLR', + 'ac_options': [ + '-daemon=0', + '-server', + `-ac_name=JUMBLR`, + '-addnode=78.47.196.146', + '-ac_supply=999999' + ] + }]; + + for (let i = 0; i < herdData.length; i++) { + setTimeout(() => { + const options = { + url: `http://127.0.0.1:${shepherd.appConfig.agamaPort}/shepherd/herd`, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + herd: 'komodod', + options: herdData[i], + }) + }; + + shepherd.request(options, (error, response, body) => { + if (response && + response.statusCode && + response.statusCode === 200) { + //resolve(body); + } else { + //resolve(body); + } + }); + }, 100); + } + } + }; + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/appInfo.js b/routes/shepherd/appInfo.js new file mode 100644 index 0000000..d22f7b1 --- /dev/null +++ b/routes/shepherd/appInfo.js @@ -0,0 +1,78 @@ +const formatBytes = (bytes, decimals) => { + if (bytes === 0) { + return '0 Bytes'; + } + + const k = 1000; + const dm = (decimals + 1) || 3; + const sizes = [ + 'Bytes', + 'KB', + 'MB', + 'GB', + 'TB', + 'PB', + 'EB', + 'ZB', + 'YB' + ]; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`; +} + +module.exports = (shepherd) => { + shepherd.SystemInfo = () => { + const os_data = { + 'totalmem_bytes': shepherd.os.totalmem(), + 'totalmem_readable': formatBytes(shepherd.os.totalmem()), + 'arch': shepherd.os.arch(), + 'cpu': shepherd.os.cpus()[0].model, + 'cpu_cores': shepherd.os.cpus().length, + 'platform': shepherd.os.platform(), + 'os_release': shepherd.os.release(), + 'os_type': shepherd.os.type(), + }; + + return os_data; + } + + shepherd.appInfo = () => { + const sysInfo = shepherd.SystemInfo(); + const releaseInfo = shepherd.appBasicInfo; + const dirs = { + agamaDir: shepherd.agamaDir, + komodoDir: shepherd.komodoDir, + komododBin: shepherd.komododBin, + configLocation: `${shepherd.agamaDir}/config.json`, + cacheLocation: `${shepherd.agamaDir}/shepherd`, + }; + + return { + sysInfo, + releaseInfo, + dirs, + appSession: shepherd.appSessionHash, + }; + } + + /* + * type: GET + * + */ + shepherd.get('/sysinfo', (req, res, next) => { + const obj = shepherd.SystemInfo(); + res.send(obj); + }); + + /* + * type: GET + * + */ + shepherd.get('/appinfo', (req, res, next) => { + const obj = shepherd.appInfo(); + res.send(obj); + }); + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/auth.js b/routes/shepherd/auth.js new file mode 100644 index 0000000..25f9cc0 --- /dev/null +++ b/routes/shepherd/auth.js @@ -0,0 +1,31 @@ +module.exports = (shepherd) => { + /* + * type: GET + * + */ + shepherd.get('/auth/status', (req, res, next) => { // not finished + let successObj; + let _status = false; + + if (Object.keys(shepherd.coindInstanceRegistry).length) { + if (Object.keys(shepherd.electrumCoins).length > 1 && + shepherd.electrumCoins.auth) { + _status = true; + } else if (Object.keys(shepherd.electrumCoins).length === 1 && !shepherd.electrumCoins.auth) { + _status = true; + } + } else if (Object.keys(shepherd.electrumCoins).length > 1 && shepherd.electrumCoins.auth) { + _status = true; + } else if (Object.keys(shepherd.electrumCoins).length === 1 && !Object.keys(shepherd.coindInstanceRegistry).length) { + _status = true; + } + + successObj = { + status: _status ? 'unlocked' : 'locked', + }; + + res.end(JSON.stringify(successObj)); + }); + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/binsTestUtil.js b/routes/shepherd/binsTestUtil.js new file mode 100644 index 0000000..df0e509 --- /dev/null +++ b/routes/shepherd/binsTestUtil.js @@ -0,0 +1,239 @@ +module.exports = (shepherd) => { + shepherd.testClearAll = () => { + return new shepherd.Promise((resolve, reject) => { + shepherd.fs.removeSync(`${iguanaTestDir}`); + resolve('done'); + }); + } + + shepherd.testBins = (daemonName) => { + return new shepherd.Promise((resolve, reject) => { + const _bins = { + komodod: shepherd.komododBin, + komodoCli: shepherd.komodocliBin, + }; + const _arg = null; + let _pid; + + shepherd.log('testBins exec ' + _bins[daemonName]); + + if (!shepherd.fs.existsSync(shepherd.agamaTestDir)) { + shepherd.fs.mkdirSync(shepherd.agamaTestDir); + } + + try { + shepherd._fs.access(`${shepherd.agamaTestDir}/${daemonName}Test.log`, shepherd.fs.constants.R_OK, (err) => { + if (!err) { + try { + shepherd._fs.unlinkSync(`${shepherd.agamaTestDir}/${daemonName}Test.log`); + } catch (e) {} + } else { + shepherd.log(`path ${shepherd.agamaTestDir}/${daemonName}Test.log doesnt exist`); + } + }); + } catch (e) {} + + if (daemonName === 'komodod') { + try { + shepherd._fs.access(`${iguanaTestDir}/debug.log`, shepherd.fs.constants.R_OK, (err) => { + if (!err) { + shepherd._fs.unlinkSync(`${iguanaTestDir}/db.log`); + shepherd._fs.unlinkSync(`${iguanaTestDir}/debug.log`); + shepherd._fs.unlinkSync(`${iguanaTestDir}/komodo.conf`); + shepherd._fs.unlinkSync(`${iguanaTestDir}/komodod.pid`); + shepherd._fs.unlinkSync(`${iguanaTestDir}/komodostate`); + shepherd._fs.unlinkSync(`${iguanaTestDir}/realtime`); + shepherd._fs.unlinkSync(`${iguanaTestDir}/wallet.dat`); + shepherd._fs.unlinkSync(`${iguanaTestDir}/.lock`); + shepherd.fs.removeSync(`${iguanaTestDir}/blocks`); + shepherd.fs.removeSync(`${iguanaTestDir}/chainstate`); + shepherd.fs.removeSync(`${iguanaTestDir}/database`); + execKomodod(); + } else { + shepherd.log(`test: nothing to remove in ${iguanaTestDir}`); + execKomodod(); + } + }); + } catch (e) {} + + const execKomodod = () => { + let _komododTest = { + port: 'unknown', + start: 'unknown', + getinfo: 'unknown', + errors: { + assertFailed: false, + zcashParams: false, + }, + }; + const _komodoConf = 'rpcuser=user83f3afba8d714993\n' + + 'rpcpassword=0d4430ca1543833e35bce5a0cc9e16b3\n' + + 'server=1\n' + + 'addnode=78.47.196.146\n' + + 'addnode=5.9.102.210\n' + + 'addnode=178.63.69.164\n' + + 'addnode=88.198.65.74\n' + + 'addnode=5.9.122.241\n' + + 'addnode=144.76.94.3\n' + + 'addnode=144.76.94.38\n' + + 'addnode=89.248.166.91\n' + + 'addnode=148.251.57.148\n' + + 'addnode=149.56.28.84\n' + + 'addnode=176.9.26.39\n' + + 'addnode=94.102.63.199\n' + + 'addnode=94.102.63.200\n' + + 'addnode=104.255.64.3\n' + + 'addnode=221.121.144.140\n' + + 'addnode=103.18.58.150\n' + + 'addnode=103.18.58.146\n' + + 'addnode=213.202.253.10\n' + + 'addnode=185.106.121.32\n' + + 'addnode=27.100.36.201\n'; + + shepherd.fs.writeFile(`${iguanaTestDir}/komodo.conf`, _komodoConf, (err) => { + if (err) { + shepherd.log(`test: error writing komodo conf in ${iguanaTestDir}`); + } + }); + + shepherd.portscanner.checkPortStatus('7771', '127.0.0.1', (error, status) => { + // Status is 'open' if currently in use or 'closed' if available + if (status === 'closed') { + _komododTest.port = 'passed'; + } else { + _komododTest.port = 'failed'; + } + }); + + /*pm2.connect(true,function(err) { //start up pm2 god + if (err) { + shepherd.error(err); + process.exit(2); + } + + pm2.start({ + script: shepherd.komododBin, // path to binary + name: 'komodod', + exec_mode : 'fork', + args: [ + '-daemon=0', + '-addnode=78.47.196.146', + `-datadir=${iguanaTestDir}/` + ], + output: `${iguanaTestDir}/komododTest.log`, + mergeLogs: true, + }, function(err, apps) { + if (apps[0] && + apps[0].process && + apps[0].process.pid) { + _komododTest.start = 'success'; + shepherd.log(`test: got komodod instance pid = ${apps[0].process.pid}`); + shepherd.writeLog(`test: komodod started with pid ${apps[0].process.pid}`); + } else { + _komododTest.start = 'failed'; + shepherd.log(`unable to start komodod`); + } + + pm2.disconnect(); // Disconnect from PM2 + if (err) { + shepherd.writeLog(`test: error starting komodod`); + shepherd.log(`komodod fork err: ${err}`); + // throw err; + } + }); + });*/ + + setTimeout(() => { + const options = { + url: `http://localhost:7771`, + method: 'POST', + auth: { + user: 'user83f3afba8d714993', + pass: '0d4430ca1543833e35bce5a0cc9e16b3', + }, + body: JSON.stringify({ + agent: 'bitcoinrpc', + method: 'getinfo', + }), + }; + + shepherd.request(options, (error, response, body) => { + if (response && + response.statusCode && + response.statusCode === 200) { + // res.end(body); + shepherd.log(JSON.stringify(body, null, '\t')); + } else { + // res.end(body); + shepherd.log(JSON.stringify(body, null, '\t')); + } + }); + }, 10000); + + setTimeout(() => { + pm2.delete('komodod'); + resolve(_komododTest); + }, 20000); + } + // komodod debug.log hooks + + //"{\"result\":{\"version\":1000850,\"protocolversion\":170002,\"KMDversion\":\"0.1.1\",\"notarized\":0,\"notarizedhash\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"notarizedtxid\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"notarizedtxid_height\":\"mempool\",\"notarized_confirms\":0,\"walletversion\":60000,\"balance\":0.00000000,\"interest\":0.00000000,\"blocks\":128,\"longestchain\":472331,\"timeoffset\":0,\"tiptime\":1473827710,\"connections\":1,\"proxy\":\"\",\"difficulty\":1,\"testnet\":false,\"keypoololdest\":1504118047,\"keypoolsize\":101,\"paytxfee\":0.00000000,\"relayfee\":0.00000100,\"errors\":\"\"},\"error\":null,\"id\":null}\n" + + //2017-08-30 17:51:33 Error: Cannot find the Zcash network parameters in the following directory: + //"/home/pbca/.zcash-params" + //Please run 'zcash-fetch-params' or './zcutil/fetch-params.sh' and then restart. + //EXCEPTION: St13runtime_error + //Assertion failed. + //2017-08-30 17:51:14 Using config file /home/pbca/.iguana/test/komodo.conf + //2017-08-30 18:23:43 UpdateTip: new best=0a47c1323f393650f7221c217d19d149d002d35444f47fde61be2dd90fbde8e6 height=1 log2_work=5.0874628 tx=2 date=2016-09-13 19:04:01 progress=0.000001 cache=0.0MiB(1tx) + //2017-08-30 18:23:43 UpdateTip: new best=05076a4e1fc9af0f5fda690257b17ae20c12d4796dfba1624804d012c9ec00be height=2 log2_work=5.6724253 tx=3 date=2016-09-13 19:05:28 progress=0.000001 cache=0.0MiB(2tx) + + /*shepherd.execFile(`${shepherd.komododBin}`, _arg, { + maxBuffer: 1024 * 10000 // 10 mb + }, function(error, stdout, stderr) { + shepherd.writeLog(`stdout: ${stdout}`); + shepherd.writeLog(`stderr: ${stderr}`); + + if (error !== null) { + console.log(`exec error: ${error}`); + shepherd.writeLog(`exec error: ${error}`); + + if (error.toString().indexOf('using -reindex') > -1) { + shepherd.io.emit('service', { + komodod: { + error: 'run -reindex', + } + }); + } + } + });*/ + } + }); + } + + // komodod datadir location test + shepherd.testLocation = (path) => { + return new shepherd.Promise((resolve, reject) => { + if (path.indexOf(' ') > -1) { + shepherd.log(`error testing path ${path}`); + resolve(-1); + } else { + shepherd.fs.lstat(path, (err, stats) => { + if (err) { + shepherd.log(`error testing path ${path}`); + resolve(-1); + } else { + if (stats.isDirectory()) { + resolve(true); + } else { + shepherd.log(`error testing path ${path} not a folder`); + resolve(false); + } + } + }); + } + }); + } + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/binsUtils.js b/routes/shepherd/binsUtils.js new file mode 100644 index 0000000..c4a7cbe --- /dev/null +++ b/routes/shepherd/binsUtils.js @@ -0,0 +1,70 @@ +module.exports = (shepherd) => { + // osx and linux + shepherd.binFixRights = () => { + const osPlatform = shepherd.os.platform(); + const _bins = [ + shepherd.komododBin, + shepherd.komodocliBin + ]; + + if (osPlatform === 'darwin' || + osPlatform === 'linux') { + for (let i = 0; i < _bins.length; i++) { + shepherd._fs.stat(_bins[i], (err, stat) => { + if (!err) { + if (parseInt(stat.mode.toString(8), 10) !== 100775) { + shepherd.log(`${_bins[i]} fix permissions`); + shepherd.fsnode.chmodSync(_bins[i], '0775'); + } + } else { + shepherd.log(`error: ${_bins[i]} not found`); + } + }); + } + } + } + + shepherd.killRogueProcess = (processName) => { + // kill rogue process copies on start + let processGrep; + const osPlatform = shepherd.os.platform(); + + switch (osPlatform) { + case 'darwin': + processGrep = "ps -p $(ps -A | grep -m1 " + processName + " | awk '{print $1}') | grep -i " + processName; + break; + case 'linux': + processGrep = 'ps -p $(pidof ' + processName + ') | grep -i ' + processName; + break; + case 'win32': + processGrep = 'tasklist'; + break; + } + + shepherd.exec(processGrep, (error, stdout, stderr) => { + if (stdout.indexOf(processName) > -1) { + const pkillCmd = osPlatform === 'win32' ? `taskkill /f /im ${processName}.exe` : `pkill -15 ${processName}`; + + shepherd.log(`found another ${processName} process(es)`); + shepherd.writeLog(`found another ${processName} process(es)`); + + shepherd.exec(pkillCmd, (error, stdout, stderr) => { + shepherd.log(`${pkillCmd} is issued`); + shepherd.writeLog(`${pkillCmd} is issued`); + + if (error !== null) { + shepherd.log(`${pkillCmd} exec error: ${error}`); + shepherd.writeLog(`${pkillCmd} exec error: ${error}`); + }; + }); + } + + if (error !== null) { + shepherd.log(`${processGrep} exec error: ${error}`); + shepherd.writeLog(`${processGrep} exec error: ${error}`); + }; + }); + } + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/coindWalletKeys.js b/routes/shepherd/coindWalletKeys.js new file mode 100644 index 0000000..e82ff74 --- /dev/null +++ b/routes/shepherd/coindWalletKeys.js @@ -0,0 +1,99 @@ +module.exports = (shepherd) => { + /* + * type: GET + * + */ + shepherd.get('/coindwalletkeys', (req, res, next) => { + const wif = require('wif'); + const fs = require('fs'); + const chain = req.query.chain; + + // ref: https://gist.github.com/kendricktan/1e62495150ad236b38616d733aac4eb9 + let _walletDatLocation = chain === 'komodo' || chain === 'null' ? `${shepherd.komodoDir}/wallet.dat` : `${shepherd.komodoDir}/${chain}/wallet.dat`; + _walletDatLocation = chain === 'CHIPS' ? `${shepherd.chipsDir}/wallet.dat` : _walletDatLocation; + + try { + shepherd._fs.access(_walletDatLocation, shepherd.fs.constants.R_OK, (err) => { + if (err) { + shepherd.log(`error reading ${_walletDatLocation}`); + successObj = { + msg: 'error', + result: `error reading ${_walletDatLocation}`, + }; + + res.end(JSON.stringify(successObj)); + } else { + shepherd.log(`reading ${_walletDatLocation}`); + fs.readFile(_walletDatLocation, (err, data) => { + if (err) { + shepherd.log(`read wallet.dat err: ${err}`); + successObj = { + msg: 'error', + result: `error reading ${_walletDatLocation}`, + }; + + res.end(JSON.stringify(successObj)); + } else { + const re = /\x30\x81\xD3\x02\x01\x01\x04\x20(.{32})/gm; + const dataHexStr = data.toString('latin1'); + privateKeys = dataHexStr.match(re); + + if (!privateKeys) { + shepherd.log('wallet is encrypted?'); + + successObj = { + msg: 'error', + result: 'wallet is encrypted?', + }; + + res.end(JSON.stringify(successObj)); + } else { + let _keys = []; + privateKeys = privateKeys.map(x => x.replace('\x30\x81\xD3\x02\x01\x01\x04\x20', '')); + privateKeys = privateKeys.filter((v, i, a) => a.indexOf(v) === i); + shepherd.log(`found ${privateKeys.length} keys`); + + for (let i = 0; i < privateKeys.length; i++) { + const privateKey = new Buffer(Buffer.from(privateKeys[i], 'latin1').toString('hex'), 'hex'); + const key = wif.encode(0xbc, privateKey, true); + const keyObj = wif.decode(key); + const wifKey = wif.encode(keyObj); + + const keyPair = shepherd.bitcoinJS.ECPair.fromWIF(wifKey, shepherd.electrumJSNetworks.komodo); + const _keyPair = { + priv: keyPair.toWIF(), + pub: keyPair.getAddress(), + }; + + if (req.query.search) { + if (_keyPair.pub.indexOf(req.query.search) > -1) { + _keys.push(_keyPair); + } + } else { + _keys.push(_keyPair); + } + } + + successObj = { + msg: 'success', + result: _keys, + }; + + res.end(JSON.stringify(successObj)); + } + } + }); + } + }); + } catch (e) { + successObj = { + msg: 'error', + result: `error reading ${_walletDatLocation}`, + }; + + res.end(JSON.stringify(successObj)); + } + }); + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/coins.js b/routes/shepherd/coins.js new file mode 100644 index 0000000..f74e778 --- /dev/null +++ b/routes/shepherd/coins.js @@ -0,0 +1,31 @@ +module.exports = (shepherd) => { + /* + * type: GET + * + */ + shepherd.get('/InstantDEX/allcoins', (req, res, next) => { + let successObj; + let nativeCoindList = []; + let electrumCoinsList = []; + + for (let key in shepherd.electrumCoins) { + if (key !== 'auth') { + electrumCoinsList.push(shepherd.electrumCoins[key].abbr); + } + } + + for (let key in shepherd.coindInstanceRegistry) { + nativeCoindList.push(key === 'komodod' ? 'KMD' : key); + } + + successObj = { + native: nativeCoindList, + spv: electrumCoinsList, + total: Object.keys(shepherd.electrumCoins).length - 1 + Object.keys(nativeCoindList).length, + }; + + res.end(JSON.stringify(successObj)); + }); + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/coinsList.js b/routes/shepherd/coinsList.js new file mode 100644 index 0000000..dfe13e1 --- /dev/null +++ b/routes/shepherd/coinsList.js @@ -0,0 +1,71 @@ +module.exports = (shepherd) => { + /* + * type: GET + * + */ + shepherd.get('/coinslist', (req, res, next) => { + if (shepherd.fs.existsSync(`${shepherd.agamaDir}/shepherd/coinslist.json`)) { + shepherd.fs.readFile(`${shepherd.agamaDir}/shepherd/coinslist.json`, 'utf8', (err, data) => { + if (err) { + const errorObj = { + msg: 'error', + result: err, + }; + + res.end(JSON.stringify(errorObj)); + } else { + const successObj = { + msg: 'success', + result: data ? JSON.parse(data) : '', + }; + + res.end(JSON.stringify(successObj)); + } + }); + } else { + const errorObj = { + msg: 'error', + result: 'coin list doesn\'t exist', + }; + + res.end(JSON.stringify(errorObj)); + } + }); + + /* + * type: POST + * params: payload + */ + shepherd.post('/coinslist', (req, res, next) => { + const _payload = req.body.payload; + + if (!_payload) { + const errorObj = { + msg: 'error', + result: 'no payload provided', + }; + + res.end(JSON.stringify(errorObj)); + } else { + shepherd.fs.writeFile(`${shepherd.agamaDir}/shepherd/coinslist.json`, JSON.stringify(_payload), (err) => { + if (err) { + const errorObj = { + msg: 'error', + result: err, + }; + + res.end(JSON.stringify(errorObj)); + } else { + const successObj = { + msg: 'success', + result: 'done', + }; + + res.end(JSON.stringify(successObj)); + } + }); + } + }); + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/confMaxconnections.js b/routes/shepherd/confMaxconnections.js new file mode 100644 index 0000000..ebf0f0a --- /dev/null +++ b/routes/shepherd/confMaxconnections.js @@ -0,0 +1,57 @@ +const fs = require('fs-extra'); + +module.exports = (shepherd) => { + shepherd.getMaxconKMDConf = () => { + return new shepherd.Promise((resolve, reject) => { + fs.readFile(`${shepherd.komodoDir}/komodo.conf`, 'utf8', (err, data) => { + if (err) { + shepherd.log('kmd conf maxconnections param read failed'); + resolve('unset'); + } else { + const _maxcon = data.match(/maxconnections=\s*(.*)/); + + if (!_maxcon) { + shepherd.log('kmd conf maxconnections param is unset'); + resolve(false); + } else { + shepherd.log(`kmd conf maxconnections param is already set to ${_maxcon[1]}`); + resolve(_maxcon[1]); + } + } + }); + }); + } + + shepherd.setMaxconKMDConf = (limit) => { + return new shepherd.Promise((resolve, reject) => { + fs.readFile(`${shepherd.komodoDir}/komodo.conf`, 'utf8', (err, data) => { + const _maxconVal = limit ? 1 : 10; + + if (err) { + shepherd.log(`error reading ${shepherd.komodoDir}/komodo.conf`); + resolve(false); + } else { + if (data.indexOf('maxconnections=') > -1) { + const _maxcon = data.match(/maxconnections=\s*(.*)/); + + data = data.replace(`maxconnections=${_maxcon[1]}`, `maxconnections=${_maxconVal}`); + } else { + data = `${data}\nmaxconnections=${_maxconVal}\n`; + } + + fs.writeFile(`${shepherd.komodoDir}/komodo.conf`, data, (err) => { + if (err) { + shepherd.log(`error writing ${shepherd.komodoDir}/komodo.conf maxconnections=${_maxconVal}`); + resolve(false); + } else { + shepherd.log(`kmd conf maxconnections is set to ${_maxconVal}`); + resolve(true); + } + }); + } + }); + }); + } + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/config.js b/routes/shepherd/config.js new file mode 100644 index 0000000..6d4f06c --- /dev/null +++ b/routes/shepherd/config.js @@ -0,0 +1,152 @@ +module.exports = (shepherd) => { + shepherd.loadLocalConfig = () => { + if (shepherd.fs.existsSync(`${shepherd.agamaDir}/config.json`)) { + let localAppConfig = shepherd.fs.readFileSync(`${shepherd.agamaDir}/config.json`, 'utf8'); + + shepherd.log('app config set from local file'); + shepherd.writeLog('app config set from local file'); + + // find diff between local and hardcoded configs + // append diff to local config + const compareJSON = (obj1, obj2) => { + let result = {}; + + for (let i in obj1) { + if (!obj2.hasOwnProperty(i)) { + result[i] = obj1[i]; + } + } + + return result; + }; + + if (localAppConfig) { + const compareConfigs = compareJSON(shepherd.appConfig, JSON.parse(localAppConfig)); + + if (Object.keys(compareConfigs).length) { + const newConfig = Object.assign(JSON.parse(localAppConfig), compareConfigs); + + shepherd.log('config diff is found, updating local config'); + shepherd.log('config diff:'); + shepherd.log(compareConfigs); + shepherd.writeLog('aconfig diff is found, updating local config'); + shepherd.writeLog('config diff:'); + shepherd.writeLog(compareConfigs); + + shepherd.saveLocalAppConf(newConfig); + return newConfig; + } else { + return JSON.parse(localAppConfig); + } + } else { + return shepherd.appConfig; + } + } else { + shepherd.log('local config file is not found!'); + shepherd.writeLog('local config file is not found!'); + shepherd.saveLocalAppConf(shepherd.appConfig); + + return shepherd.appConfig; + } + }; + + shepherd.saveLocalAppConf = (appSettings) => { + let appConfFileName = `${shepherd.agamaDir}/config.json`; + + shepherd._fs.access(shepherd.agamaDir, shepherd.fs.constants.R_OK, (err) => { + if (!err) { + + const FixFilePermissions = () => { + return new shepherd.Promise((resolve, reject) => { + const result = 'config.json file permissions updated to Read/Write'; + + shepherd.fsnode.chmodSync(appConfFileName, '0666'); + + setTimeout(() => { + shepherd.log(result); + shepherd.writeLog(result); + resolve(result); + }, 1000); + }); + } + + const FsWrite = () => { + return new shepherd.Promise((resolve, reject) => { + const result = 'config.json write file is done'; + + shepherd.fs.writeFile(appConfFileName, + JSON.stringify(appSettings) + .replace(/,/g, ',\n') // format json in human readable form + .replace(/":/g, '": ') + .replace(/{/g, '{\n') + .replace(/}/g, '\n}'), 'utf8', (err) => { + if (err) + return shepherd.log(err); + }); + + shepherd.fsnode.chmodSync(appConfFileName, '0666'); + setTimeout(() => { + shepherd.log(result); + shepherd.log(`app conf.json file is created successfully at: ${shepherd.agamaDir}`); + shepherd.writeLog(`app conf.json file is created successfully at: ${shepherd.agamaDir}`); + resolve(result); + }, 2000); + }); + } + + FsWrite() + .then(FixFilePermissions()); + } + }); + } + + /* + * type: POST + * params: payload + */ + shepherd.post('/appconf', (req, res, next) => { + if (!req.body.payload) { + const errorObj = { + msg: 'error', + result: 'no payload provided', + }; + + res.end(JSON.stringify(errorObj)); + } else { + shepherd.saveLocalAppConf(req.body.payload); + + const successObj = { + msg: 'success', + result: 'config saved', + }; + + res.end(JSON.stringify(successObj)); + } + }); + + /* + * type: POST + * params: none + */ + shepherd.post('/appconf/reset', (req, res, next) => { + shepherd.saveLocalAppConf(shepherd.defaultAppConfig); + + const successObj = { + msg: 'success', + result: 'config saved', + }; + + res.end(JSON.stringify(successObj)); + }); + + /* + * type: GET + * + */ + shepherd.get('/appconf', (req, res, next) => { + const obj = shepherd.loadLocalConfig(); + res.send(obj); + }); + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/daemonControl.js b/routes/shepherd/daemonControl.js new file mode 100644 index 0000000..262bd24 --- /dev/null +++ b/routes/shepherd/daemonControl.js @@ -0,0 +1,908 @@ +const spawn = require('child_process').spawn; +const fs = require('fs-extra'); +const _fs = require('graceful-fs'); +const fsnode = require('fs'); +const path = require('path'); +const os = require('os'); +const portscanner = require('portscanner'); +const execFile = require('child_process').execFile; +const Promise = require('bluebird'); +const md5 = require('../md5.js'); + +module.exports = (shepherd) => { + const getConf = (flock, coind) => { + let DaemonConfPath = ''; + let nativeCoindDir; + + if (flock === 'CHIPS') { + flock = 'chipsd'; + } + + shepherd.log(flock); + shepherd.log(`getconf coind ${coind}`); + shepherd.writeLog(`getconf flock: ${flock}`); + + if (coind) { + switch (os.platform()) { + case 'darwin': + nativeCoindDir = `${process.env.HOME}/Library/Application Support/${shepherd.nativeCoindList[coind.toLowerCase()].bin}`; + break; + case 'linux': + nativeCoindDir = coind ? `${process.env.HOME}/.${shepherd.nativeCoindList[coind.toLowerCase()].bin.toLowerCase()}` : null; + break; + case 'win32': + nativeCoindDir = coind ? `${process.env.APPDATA}/${shepherd.nativeCoindList[coind.toLowerCase()].bin}` : null; + break; + } + } + + switch (flock) { + case 'komodod': + DaemonConfPath = shepherd.komodoDir; + if (os.platform() === 'win32') { + DaemonConfPath = path.normalize(DaemonConfPath); + shepherd.log('===>>> SHEPHERD API OUTPUT ===>>>'); + } + break; + case 'zcashd': + DaemonConfPath = shepherd.ZcashDir; + if (os.platform() === 'win32') { + DaemonConfPath = path.normalize(DaemonConfPath); + } + break; + case 'chipsd': + DaemonConfPath = shepherd.chipsDir; + if (os.platform() === 'win32') { + DaemonConfPath = path.normalize(DaemonConfPath); + } + break; + case 'coind': + DaemonConfPath = os.platform() === 'win32' ? shepherd.path.normalize(`${shepherd.coindRootDir}/${coind.toLowerCase()}`) : `${shepherd.coindRootDir}/${coind.toLowerCase()}`; + break; + default: + DaemonConfPath = `${shepherd.komodoDir}/${flock}`; + if (os.platform() === 'win32') { + DaemonConfPath = shepherd.path.normalize(DaemonConfPath); + } + } + + shepherd.writeLog(`getconf path: ${DaemonConfPath}`); + shepherd.log(`daemon path: ${DaemonConfPath}`); + + return DaemonConfPath; + } + + // TODO: json.stringify wrapper + + const herder = (flock, data, coind) => { + if (data === undefined) { + data = 'none'; + shepherd.log('it is undefined'); + } + + shepherd.log(`herder flock: ${flock} coind: ${coind}`); + shepherd.log(`selected data: ${JSON.stringify(data, null, '\t')}`); + + // TODO: notify gui that reindex/rescan param is used to reflect on the screen + // asset chain debug.log unlink + if (flock === 'komodod') { + let kmdDebugLogLocation = (data.ac_name !== 'komodod' ? `${shepherd.komodoDir}/${data.ac_name}` : shepherd.komodoDir) + '/debug.log'; + + shepherd.log('komodod flock selected...'); + shepherd.log(`selected data: ${JSON.stringify(data, null, '\t')}`); + shepherd.writeLog('komodod flock selected...'); + shepherd.writeLog(`selected data: ${data}`); + + // datadir case, check if komodo/chain folder exists + if (shepherd.appConfig.dataDir.length && + data.ac_name !== 'komodod') { + const _dir = data.ac_name !== 'komodod' ? `${shepherd.komodoDir}/${data.ac_name}` : shepherd.komodoDir; + + try { + _fs.accessSync(_dir, fs.R_OK | fs.W_OK); + + shepherd.log(`komodod datadir ${_dir} exists`); + } catch (e) { + shepherd.log(`komodod datadir ${_dir} access err: ${e}`); + shepherd.log(`attempting to create komodod datadir ${_dir}`); + + fs.mkdirSync(_dir); + + if (fs.existsSync(_dir)) { + shepherd.log(`created komodod datadir folder at ${_dir}`); + } else { + shepherd.log(`unable to create komodod datadir folder at ${_dir}`); + } + } + } + + // truncate debug.log + if (!shepherd.kmdMainPassiveMode) { + try { + const _confFileAccess = _fs.accessSync(kmdDebugLogLocation, fs.R_OK | fs.W_OK); + + if (_confFileAccess) { + shepherd.log(`error accessing ${kmdDebugLogLocation}`); + shepherd.writeLog(`error accessing ${kmdDebugLogLocation}`); + } else { + try { + fs.unlinkSync(kmdDebugLogLocation); + shepherd.log(`truncate ${kmdDebugLogLocation}`); + shepherd.writeLog(`truncate ${kmdDebugLogLocation}`); + } catch (e) { + shepherd.log('cant unlink debug.log'); + } + } + } catch (e) { + shepherd.log(`komodod debug.log access err: ${e}`); + shepherd.writeLog(`komodod debug.log access err: ${e}`); + } + } + + // get komodod instance port + const _port = shepherd.assetChainPorts[data.ac_name]; + + try { + // check if komodod instance is already running + portscanner.checkPortStatus(_port, '127.0.0.1', (error, status) => { + // Status is 'open' if currently in use or 'closed' if available + if (status === 'closed' || + !shepherd.appConfig.stopNativeDaemonsOnQuit) { + // start komodod via exec + const _customParamDict = { + silent: '&', + reindex: '-reindex', + change: '-pubkey=', + datadir: '-datadir=', + rescan: '-rescan', + }; + let _customParam = ''; + + if (data.ac_custom_param === 'silent' || + data.ac_custom_param === 'reindex' || + data.ac_custom_param === 'rescan') { + _customParam = ` ${_customParamDict[data.ac_custom_param]}`; + } else if (data.ac_custom_param === 'change' && data.ac_custom_param_value) { + _customParam = ` ${_customParamDict[data.ac_custom_param]}${data.ac_custom_param_value}`; + } + + if (shepherd.appConfig.dataDir.length) { + _customParam = _customParam + ' -datadir=' + shepherd.appConfig.dataDir + (data.ac_name !== 'komodod' ? '/' + data.ac_name : ''); + } + + shepherd.log(`exec ${shepherd.komododBin} ${data.ac_options.join(' ')}${_customParam}`); + shepherd.writeLog(`exec ${shepherd.komododBin} ${data.ac_options.join(' ')}${_customParam}`); + + const isChain = data.ac_name.match(/^[A-Z]*$/); + const coindACParam = isChain ? ` -ac_name=${data.ac_name} ` : ''; + shepherd.log(`daemon param ${data.ac_custom_param}`); + + shepherd.coindInstanceRegistry[data.ac_name] = true; + if (!shepherd.kmdMainPassiveMode) { + let _arg = `${coindACParam}${data.ac_options.join(' ')}${_customParam}`; + _arg = _arg.trim().split(' '); + + const _daemonName = data.ac_name !== 'komodod' ? data.ac_name : 'komodod'; + const _daemonLogName = `${shepherd.agamaDir}/${_daemonName}.log`; + + try { + fs.accessSync(_daemonLogName, fs.R_OK | fs.W_OK); + shepherd.log(`created ${_daemonLogName}`); + fs.unlinkSync(_daemonLogName); + } catch (e) { + shepherd.log(`error accessing ${_daemonLogName}, doesnt exist or another proc is already running`); + } + + if (!shepherd.appConfig.stopNativeDaemonsOnQuit) { + let spawnOut = fs.openSync(_daemonLogName, 'a'); + let spawnErr = fs.openSync(_daemonLogName, 'a'); + + spawn(shepherd.komododBin, _arg, { + stdio: ['ignore', spawnOut, spawnErr], + detached: true, + }).unref(); + } else { + let logStream = fs.createWriteStream(_daemonLogName, { flags: 'a' }); + + let _daemonChildProc = execFile(`${shepherd.komododBin}`, _arg, { + maxBuffer: 1024 * 1000000, // 1000 mb + }, (error, stdout, stderr) => { + shepherd.writeLog(`stdout: ${stdout}`); + shepherd.writeLog(`stderr: ${stderr}`); + + if (error !== null) { + shepherd.log(`exec error: ${error}`); + shepherd.writeLog(`exec error: ${error}`); + + if (error.toString().indexOf('using -reindex') > -1) { + shepherd.io.emit('service', { + komodod: { + error: 'run -reindex', + }, + }); + } + } + }); + + _daemonChildProc.stdout.on('data', (data) => { + // shepherd.log(`${_daemonName} stdout: \n${data}`); + }).pipe(logStream); + + _daemonChildProc.stdout.on('error', (data) => { + // shepherd.log(`${_daemonName} stdout: \n${data}`); + }).pipe(logStream); + + _daemonChildProc.stderr.on('data', (data) => { + // shepherd.error(`${_daemonName} stderr:\n${data}`); + }).pipe(logStream); + + _daemonChildProc.on('exit', (exitCode) => { + const _errMsg = exitCode === 0 ? `${_daemonName} exited with code ${exitCode}` : `${_daemonName} exited with code ${exitCode}, crashed?`; + + fs.appendFile(_daemonLogName, _errMsg, (err) => { + if (err) { + shepherd.writeLog(_errMsg); + shepherd.log(_errMsg); + } + shepherd.log(_errMsg); + }); + }); + } + } + } else { + if (shepherd.kmdMainPassiveMode) { + shepherd.coindInstanceRegistry[data.ac_name] = true; + } + shepherd.log(`port ${_port} (${data.ac_name}) is already in use`); + shepherd.writeLog(`port ${_port} (${data.ac_name}) is already in use`); + } + }); + } catch(e) { + shepherd.log(`failed to start komodod err: ${e}`); + shepherd.writeLog(`failed to start komodod err: ${e}`); + } + } + + // TODO: refactor + if (flock === 'chipsd') { + let kmdDebugLogLocation = `${shepherd.chipsDir}/debug.log`; + + shepherd.log('chipsd flock selected...'); + shepherd.log(`selected data: ${JSON.stringify(data, null, '\t')}`); + shepherd.writeLog('chipsd flock selected...'); + shepherd.writeLog(`selected data: ${data}`); + + // truncate debug.log + try { + const _confFileAccess = _fs.accessSync(kmdDebugLogLocation, fs.R_OK | fs.W_OK); + + if (_confFileAccess) { + shepherd.log(`error accessing ${kmdDebugLogLocation}`); + shepherd.writeLog(`error accessing ${kmdDebugLogLocation}`); + } else { + try { + fs.unlinkSync(kmdDebugLogLocation); + shepherd.log(`truncate ${kmdDebugLogLocation}`); + shepherd.writeLog(`truncate ${kmdDebugLogLocation}`); + } catch (e) { + shepherd.log('cant unlink debug.log'); + } + } + } catch(e) { + shepherd.log(`chipsd debug.log access err: ${e}`); + shepherd.writeLog(`chipsd debug.log access err: ${e}`); + } + + // get komodod instance port + const _port = shepherd.assetChainPorts.chipsd; + + try { + // check if komodod instance is already running + portscanner.checkPortStatus(_port, '127.0.0.1', (error, status) => { + // Status is 'open' if currently in use or 'closed' if available + if (status === 'closed') { + // start komodod via exec + const _customParamDict = { + silent: '&', + reindex: '-reindex', + change: '-pubkey=', + rescan: '-rescan', + }; + let _customParam = ''; + + if (data.ac_custom_param === 'silent' || + data.ac_custom_param === 'reindex' || + data.ac_custom_param === 'rescan') { + _customParam = ` ${_customParamDict[data.ac_custom_param]}`; + } else if (data.ac_custom_param === 'change' && data.ac_custom_param_value) { + _customParam = ` ${_customParamDict[data.ac_custom_param]}${data.ac_custom_param_value}`; + } + + shepherd.log(`exec ${shepherd.chipsBin} ${_customParam}`); + shepherd.writeLog(`exec ${shepherd.chipsBin} ${_customParam}`); + + shepherd.log(`daemon param ${data.ac_custom_param}`); + + shepherd.coindInstanceRegistry['CHIPS'] = true; + let _arg = `${_customParam}`; + _arg = _arg.trim().split(' '); + + if (_arg && + _arg.length > 1) { + execFile(`${shepherd.chipsBin}`, _arg, { + maxBuffer: 1024 * 1000000 // 1000 mb + }, (error, stdout, stderr) => { + shepherd.writeLog(`stdout: ${stdout}`); + shepherd.writeLog(`stderr: ${stderr}`); + + if (error !== null) { + shepherd.log(`exec error: ${error}`); + shepherd.writeLog(`exec error: ${error}`); + + if (error.toString().indexOf('using -reindex') > -1) { + shepherd.io.emit('service', { + komodod: { + error: 'run -reindex', + }, + }); + } + } + }); + } else { + execFile(`${shepherd.chipsBin}`, { + maxBuffer: 1024 * 1000000 // 1000 mb + }, (error, stdout, stderr) => { + shepherd.writeLog(`stdout: ${stdout}`); + shepherd.writeLog(`stderr: ${stderr}`); + + if (error !== null) { + shepherd.log(`exec error: ${error}`); + shepherd.writeLog(`exec error: ${error}`); + + if (error.toString().indexOf('using -reindex') > -1) { + shepherd.io.emit('service', { + komodod: { + error: 'run -reindex', + }, + }); + } + } + }); + } + } + }); + } catch(e) { + shepherd.log(`failed to start chipsd err: ${e}`); + shepherd.writeLog(`failed to start chipsd err: ${e}`); + } + } + + if (flock === 'zcashd') { // TODO: fix(?) + let kmdDebugLogLocation = `${shepherd.zcashDir}/debug.log`; + + shepherd.log('zcashd flock selected...'); + shepherd.log(`selected data: ${data}`); + shepherd.writeLog('zcashd flock selected...'); + shepherd.writeLog(`selected data: ${data}`); + } + + if (flock === 'coind') { + const _osHome = os.platform === 'win32' ? process.env.APPDATA : process.env.HOME; + let coindDebugLogLocation = `${_osHome}/.${shepherd.nativeCoindList[coind.toLowerCase()].bin.toLowerCase()}/debug.log`; + + shepherd.log(`coind ${coind} flock selected...`); + shepherd.log(`selected data: ${JSON.stringify(data, null, '\t')}`); + shepherd.writeLog(`coind ${coind} flock selected...`); + shepherd.writeLog(`selected data: ${data}`); + + // truncate debug.log + try { + _fs.access(coindDebugLogLocation, fs.constants.R_OK, (err) => { + if (err) { + shepherd.log(`error accessing ${coindDebugLogLocation}`); + shepherd.writeLog(`error accessing ${coindDebugLogLocation}`); + } else { + shepherd.log(`truncate ${coindDebugLogLocation}`); + shepherd.writeLog(`truncate ${coindDebugLogLocation}`); + fs.unlink(coindDebugLogLocation); + } + }); + } catch(e) { + shepherd.log(`coind ${coind} debug.log access err: ${e}`); + shepherd.writeLog(`coind ${coind} debug.log access err: ${e}`); + } + + // get komodod instance port + const _port = shepherd.nativeCoindList[coind.toLowerCase()].port; + const coindBin = `${shepherd.coindRootDir}/${coind.toLowerCase()}/${shepherd.nativeCoindList[coind.toLowerCase()].bin.toLowerCase()}d`; + + try { + // check if coind instance is already running + portscanner.checkPortStatus(_port, '127.0.0.1', (error, status) => { + // Status is 'open' if currently in use or 'closed' if available + if (status === 'closed') { + shepherd.log(`exec ${coindBin} ${data.ac_options.join(' ')}`); + shepherd.writeLog(`exec ${coindBin} ${data.ac_options.join(' ')}`); + + shepherd.coindInstanceRegistry[coind] = true; + let _arg = `${data.ac_options.join(' ')}`; + _arg = _arg.trim().split(' '); + execFile(`${coindBin}`, _arg, { + maxBuffer: 1024 * 1000000 // 1000 mb + }, (error, stdout, stderr) => { + shepherd.writeLog(`stdout: ${stdout}`); + shepherd.writeLog(`stderr: ${stderr}`); + + if (error !== null) { + shepherd.log(`exec error: ${error}`); + shepherd.writeLog(`exec error: ${error}`); + } + }); + } else { + shepherd.log(`port ${_port} (${coind}) is already in use`); + shepherd.writeLog(`port ${_port} (${coind}) is already in use`); + } + }); + } catch(e) { + shepherd.log(`failed to start ${coind} err: ${e}`); + shepherd.writeLog(`failed to start ${coind} err: ${e}`); + } + } + } + + const setConf = (flock, coind) => { + let nativeCoindDir; + let DaemonConfPath; + + shepherd.log(flock); + shepherd.writeLog(`setconf ${flock}`); + + if (os.platform() === 'darwin') { + nativeCoindDir = coind ? `${process.env.HOME}/Library/Application Support/${shepherd.nativeCoindList[coind.toLowerCase()].bin}` : null; + } + + if (os.platform() === 'linux') { + nativeCoindDir = coind ? `${process.env.HOME}/.${shepherd.nativeCoindList[coind.toLowerCase()].bin.toLowerCase()}` : null; + } + + if (os.platform() === 'win32') { + nativeCoindDir = coind ? `${process.env.APPDATA}/${shepherd.nativeCoindList[coind.toLowerCase()].bin}` : null; + } + + switch (flock) { + case 'komodod': + DaemonConfPath = `${shepherd.komodoDir}/komodo.conf`; + + if (os.platform() === 'win32') { + DaemonConfPath = path.normalize(DaemonConfPath); + } + break; + case 'zcashd': + DaemonConfPath = `${shepherd.ZcashDir}/zcash.conf`; + + if (os.platform() === 'win32') { + DaemonConfPath = path.normalize(DaemonConfPath); + } + break; + case 'chipsd': + DaemonConfPath = `${shepherd.chipsDir}/chips.conf`; + + if (os.platform() === 'win32') { + DaemonConfPath = path.normalize(DaemonConfPath); + } + break; + case 'coind': + DaemonConfPath = `${nativeCoindDir}/${shepherd.nativeCoindList[coind.toLowerCase()].bin.toLowerCase()}.conf`; + + if (os.platform() === 'win32') { + DaemonConfPath = path.normalize(DaemonConfPath); + } + break; + default: + DaemonConfPath = `${shepherd.komodoDir}/${flock}/${flock}.conf`; + + if (os.platform() === 'win32') { + DaemonConfPath = path.normalize(DaemonConfPath); + } + } + + shepherd.log(DaemonConfPath); + shepherd.writeLog(`setconf ${DaemonConfPath}`); + + const CheckFileExists = () => { + return new Promise((resolve, reject) => { + const result = 'Check Conf file exists is done'; + const confFileExist = fs.ensureFileSync(DaemonConfPath); + + if (confFileExist) { + shepherd.log(result); + shepherd.writeLog(`setconf ${result}`); + + resolve(result); + } else { + shepherd.log('conf file doesnt exist'); + resolve('conf file doesnt exist'); + } + }); + } + + const FixFilePermissions = () => { + return new Promise((resolve, reject) => { + const result = 'Conf file permissions updated to Read/Write'; + + fsnode.chmodSync(DaemonConfPath, '0666'); + shepherd.log(result); + shepherd.writeLog(`setconf ${result}`); + + resolve(result); + }); + } + + const RemoveLines = () => { + return new Promise((resolve, reject) => { + const result = 'RemoveLines is done'; + + fs.readFile(DaemonConfPath, 'utf8', (err, data) => { + if (err) { + shepherd.writeLog(`setconf error ${err}`); + return shepherd.log(err); + } + + const rmlines = data.replace(/(?:(?:\r\n|\r|\n)\s*){2}/gm, '\n'); + + fs.writeFile(DaemonConfPath, rmlines, 'utf8', (err) => { + if (err) + return shepherd.log(err); + + fsnode.chmodSync(DaemonConfPath, '0666'); + shepherd.writeLog(`setconf ${result}`); + shepherd.log(result); + resolve(result); + }); + }); + }); + } + + const CheckConf = () => { + return new Promise((resolve, reject) => { + const result = 'CheckConf is done'; + + shepherd.setconf.status(DaemonConfPath, (err, status) => { + const rpcuser = () => { + return new Promise((resolve, reject) => { + const result = 'checking rpcuser...'; + + if (status[0].hasOwnProperty('rpcuser')) { + shepherd.log('rpcuser: OK'); + shepherd.writeLog('rpcuser: OK'); + } else { + const randomstring = shepherd.md5((Math.random() * Math.random() * 999).toString()); + + shepherd.log('rpcuser: NOT FOUND'); + shepherd.writeLog('rpcuser: NOT FOUND'); + + fs.appendFile(DaemonConfPath, `\nrpcuser=user${randomstring.substring(0, 16)}`, (err) => { + if (err) { + shepherd.writeLog(`append daemon conf err: ${err}`); + shepherd.log(`append daemon conf err: ${err}`); + } + // throw err; + shepherd.log('rpcuser: ADDED'); + shepherd.writeLog('rpcuser: ADDED'); + }); + } + + resolve(result); + }); + } + + const rpcpass = () => { + return new Promise((resolve, reject) => { + const result = 'checking rpcpassword...'; + + if (status[0].hasOwnProperty('rpcpassword')) { + shepherd.log('rpcpassword: OK'); + shepherd.writeLog('rpcpassword: OK'); + } else { + const randomstring = md5((Math.random() * Math.random() * 999).toString()); + + shepherd.log('rpcpassword: NOT FOUND'); + shepherd.writeLog('rpcpassword: NOT FOUND'); + + fs.appendFile(DaemonConfPath, `\nrpcpassword=${randomstring}`, (err) => { + if (err) { + shepherd.writeLog(`append daemon conf err: ${err}`); + shepherd.log(`append daemon conf err: ${err}`); + } + // throw err; + shepherd.log('rpcpassword: ADDED'); + shepherd.writeLog('rpcpassword: ADDED'); + }); + } + + resolve(result); + }); + } + + const rpcbind = () => { + return new Promise((resolve, reject) => { + const result = 'checking rpcbind...'; + + if (status[0].hasOwnProperty('rpcbind')) { + shepherd.log('rpcbind: OK'); + shepherd.writeLog('rpcbind: OK'); + } else { + shepherd.log('rpcbind: NOT FOUND'); + shepherd.writeLog('rpcbind: NOT FOUND'); + + fs.appendFile(DaemonConfPath, '\nrpcbind=127.0.0.1', (err) => { + if (err) { + shepherd.writeLog(`append daemon conf err: ${err}`); + shepherd.log(`append daemon conf err: ${err}`); + } + // throw err; + shepherd.log('rpcbind: ADDED'); + shepherd.writeLog('rpcbind: ADDED'); + }); + } + + resolve(result); + }); + } + + const server = () => { + return new Promise((resolve, reject) => { + const result = 'checking server...'; + + if (status[0].hasOwnProperty('server')) { + shepherd.log('server: OK'); + shepherd.writeLog('server: OK'); + } else { + shepherd.log('server: NOT FOUND'); + shepherd.writeLog('server: NOT FOUND'); + + fs.appendFile(DaemonConfPath, '\nserver=1', (err) => { + if (err) { + shepherd.writeLog(`append daemon conf err: ${err}`); + shepherd.log(`append daemon conf err: ${err}`); + } + // throw err; + shepherd.log('server: ADDED'); + shepherd.writeLog('server: ADDED'); + }); + } + + resolve(result); + }); + } + + const addnode = () => { + return new Promise((resolve, reject) => { + const result = 'checking addnode...'; + + if (flock === 'chipsd' || + flock === 'komodod') { + if (status[0].hasOwnProperty('addnode')) { + shepherd.log('addnode: OK'); + shepherd.writeLog('addnode: OK'); + } else { + let nodesList; + + if (flock === 'chipsd') { + nodesList = '\naddnode=95.110.191.193' + + '\naddnode=144.76.167.66' + + '\naddnode=158.69.248.93' + + '\naddnode=149.202.49.218' + + '\naddnode=95.213.205.222' + + '\naddnode=5.9.253.198' + + '\naddnode=164.132.224.253' + + '\naddnode=163.172.4.66' + + '\naddnode=217.182.194.216' + + '\naddnode=94.130.96.114' + + '\naddnode=5.9.253.195'; + } else if (flock === 'komodod') { + nodesList = '\naddnode=78.47.196.146' + + '\naddnode=5.9.102.210' + + '\naddnode=178.63.69.164' + + '\naddnode=88.198.65.74' + + '\naddnode=5.9.122.241' + + '\naddnode=144.76.94.3'; + } + + shepherd.log('addnode: NOT FOUND'); + fs.appendFile(DaemonConfPath, nodesList, (err) => { + if (err) { + shepherd.writeLog(`append daemon conf err: ${err}`); + shepherd.log(`append daemon conf err: ${err}`); + } + // throw err; + shepherd.log('addnode: ADDED'); + shepherd.writeLog('addnode: ADDED'); + }); + } + } else { + result = 'skip addnode'; + } + + resolve(result); + }); + } + + rpcuser() + .then((result) => { + return rpcpass(); + }) + .then(server) + .then(rpcbind) + .then(addnode); + }); + + shepherd.log(result); + shepherd.writeLog(`checkconf addnode ${result}`); + + resolve(result); + }); + } + + CheckFileExists() + .then((result) => { + return FixFilePermissions(); + }) + .then(RemoveLines) + .then(CheckConf); + } + + /* + * type: POST + * params: herd + */ + shepherd.post('/herd', (req, res) => { + shepherd.log('======= req.body ======='); + shepherd.log(req.body); + + if (req.body.options && + !shepherd.kmdMainPassiveMode) { + const testCoindPort = (skipError) => { + if (!shepherd.lockDownAddCoin) { + const _port = shepherd.assetChainPorts[req.body.options.ac_name]; + + portscanner.checkPortStatus(_port, '127.0.0.1', (error, status) => { + // Status is 'open' if currently in use or 'closed' if available + if (status === 'open' && + shepherd.appConfig.stopNativeDaemonsOnQuit) { + if (!skipError) { + shepherd.log(`komodod service start error at port ${_port}, reason: port is closed`); + shepherd.writeLog(`komodod service start error at port ${_port}, reason: port is closed`); + shepherd.io.emit('service', { + komodod: { + error: `error starting ${req.body.herd} ${req.body.options.ac_name} daemon. Port ${_port} is already taken!`, + }, + }); + + const obj = { + msg: 'error', + result: `error starting ${req.body.herd} ${req.body.options.ac_name} daemon. Port ${_port} is already taken!`, + }; + + res.status(500); + res.end(JSON.stringify(obj)); + } else { + shepherd.log(`komodod service start success at port ${_port}`); + shepherd.writeLog(`komodod service start success at port ${_port}`); + } + } else { + if (!skipError) { + herder(req.body.herd, req.body.options); + + const obj = { + msg: 'success', + result: 'result', + }; + + res.end(JSON.stringify(obj)); + } else { + shepherd.log(`komodod service start error at port ${_port}, reason: unknown`); + shepherd.writeLog(`komodod service start error at port ${_port}, reason: unknown`); + } + } + }); + } + } + + if (req.body.herd === 'komodod') { + // check if komodod instance is already running + testCoindPort(); + setTimeout(() => { + testCoindPort(true); + }, 10000); + } else { + herder(req.body.herd, req.body.options, req.body.coind); + + const obj = { + msg: 'success', + result: 'result', + }; + + res.end(JSON.stringify(obj)); + } + } else { + // (?) + herder(req.body.herd, req.body.options); + + const obj = { + msg: 'success', + result: 'result', + }; + + res.end(JSON.stringify(obj)); + } + }); + + /* + * type: POST + */ + shepherd.post('/setconf', (req, res) => { + shepherd.log('======= req.body ======='); + shepherd.log(req.body); + + if (os.platform() === 'win32' && + req.body.chain == 'komodod') { + setkomodoconf = spawn(path.join(__dirname, '../assets/bin/win64/genkmdconf.bat')); + } else { + shepherd.setConf(req.body.chain); + } + + const obj = { + msg: 'success', + result: 'result', + }; + + res.end(JSON.stringify(obj)); + }); + + /* + * type: POST + */ + shepherd.post('/getconf', (req, res) => { + shepherd.log('======= req.body ======='); + shepherd.log(req.body); + + const confpath = getConf(req.body.chain, req.body.coind); + + shepherd.log('got conf path is:'); + shepherd.log(confpath); + shepherd.writeLog('got conf path is:'); + shepherd.writeLog(confpath); + + const obj = { + msg: 'success', + result: confpath, + }; + + res.end(JSON.stringify(obj)); + }); + + shepherd.setConfKMD = (isChips) => { + // check if kmd conf exists + _fs.access(isChips ? `${shepherd.chipsDir}/chips.conf` : `${shepherd.komodoDir}/komodo.conf`, shepherd.fs.constants.R_OK, (err) => { + if (err) { + shepherd.log(isChips ? 'creating chips conf' : 'creating komodo conf'); + shepherd.writeLog(isChips ? `creating chips conf in ${shepherd.chipsDir}/chips.conf` : `creating komodo conf in ${shepherd.komodoDir}/komodo.conf`); + setConf(isChips ? 'chipsd' : 'komodod'); + } else { + const _confSize = shepherd.fs.lstatSync(isChips ? `${shepherd.chipsDir}/chips.conf` : `${shepherd.komodoDir}/komodo.conf`); + + if (_confSize.size === 0) { + shepherd.log(isChips ? 'err: chips conf file is empty, creating chips conf' : 'err: komodo conf file is empty, creating komodo conf'); + shepherd.writeLog(isChips ? `creating chips conf in ${shepherd.chipsDir}/chips.conf` : `creating komodo conf in ${shepherd.komodoDir}/komodo.conf`); + setConf(isChips ? 'chipsd' : 'komodod'); + } else { + shepherd.writeLog(isChips ? 'chips conf exists' : 'komodo conf exists'); + shepherd.log(isChips ? 'chips conf exists' : 'komodo conf exists'); + } + } + }); + } + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/dashboardUpdate.js b/routes/shepherd/dashboardUpdate.js new file mode 100644 index 0000000..9138138 --- /dev/null +++ b/routes/shepherd/dashboardUpdate.js @@ -0,0 +1,312 @@ +const Promise = require('bluebird'); + +module.exports = (shepherd) => { + /* + * Combined native dashboard update same as in gui + * type: GET + * params: coin + */ + shepherd.post('/native/dashboard/update', (req, res, next) => { + const _coin = req.body.coin; + let _returnObj; + let _promiseStack; + + if (_coin === 'CHIPS') { + _returnObj = { + getinfo: {}, + listtransactions: [], + getbalance: {}, + listunspent: {}, + addresses: {}, + }; + _promiseStack = [ + 'getinfo', + 'listtransactions', + 'getbalance', + ]; + } else { + _returnObj = { + getinfo: {}, + listtransactions: [], + z_gettotalbalance: {}, + z_getoperationstatus: {}, + listunspent: {}, + addresses: {}, + }; + _promiseStack = [ + 'getinfo', + 'listtransactions', + 'z_gettotalbalance', + 'z_getoperationstatus' + ]; + } + + const getAddressesNative = (coin) => { + const type = [ + 'public', + 'private' + ]; + + if (coin === 'CHIPS') { + type.pop(); + } + + Promise.all(type.map((_type, index) => { + return new Promise((resolve, reject) => { + _bitcoinRPC( + coin, + _type === 'public' ? 'getaddressesbyaccount' : 'z_listaddresses', + [''] + ).then((_json) => { + if (_json === 'Work queue depth exceeded' || + !_json) { + resolve({ error: 'daemon is busy' }); + } else { + resolve(JSON.parse(_json).result); + } + }); + }); + })) + .then(result => { + if (result[0] && + result[0].length && + result[0][0].length && + result[0][0].length > 10) { + const calcBalance = (result, json) => { + if (json && + json.length && + json[0] && + json[0].address) { + const allAddrArray = json.map(res => res.address).filter((x, i, a) => a.indexOf(x) == i); + + for (let a = 0; a < allAddrArray.length; a++) { + const filteredArray = json.filter(res => res.address === allAddrArray[a]).map(res => res.amount); + + let isNewAddr = true; + for (let x = 0; x < result.length && isNewAddr; x++) { + for (let y = 0; y < result[x].length && isNewAddr; y++) { + if (allAddrArray[a] === result[x][y]) { + isNewAddr = false; + } + } + } + + if (isNewAddr && + (allAddrArray[a].substring(0, 2) === 'zc' || + allAddrArray[a].substring(0, 2) === 'zt')) { + result[1][result[1].length] = allAddrArray[a]; + } else { + result[0][result[0].length] = allAddrArray[a]; + } + } + } + + // remove addr duplicates + if (result[0] && + result[0].length) { + result[0] = result[0].filter((elem, pos) => { + return result[0].indexOf(elem) === pos; + }); + } + if (result[1] && + result[1].length) { + result[1] = result[1].filter((elem, pos) => { + return result[1].indexOf(elem) === pos; + }); + } + + let newAddressArray = []; + for (let a = 0; a < result.length; a++) { + newAddressArray[a] = []; + + if (result[a]) { + for (let b = 0; b < result[a].length; b++) { + const filteredArraySpends = json.filter(res => res.address === result[a][b]); + const filteredArray = json.filter(res => res.address === result[a][b]).map(res => res.amount); + + let sum = 0; + let spendableSum = 0; + let canspend = true; + + for (let i = 0; i < filteredArray.length; i++) { + sum += filteredArray[i]; + + if (filteredArraySpends[i].spendable) { + spendableSum += filteredArray[i]; + } else { + canspend = false; + } + } + + newAddressArray[a][b] = { + address: result[a][b], + amount: sum, + spendable: spendableSum, + canspend, + type: a === 0 ? 'public': 'private', + }; + } + } + } + + // get zaddr balance + if (result[1] && + result[1].length) { + Promise.all(result[1].map((_address, index) => { + return new Promise((resolve, reject) => { + _bitcoinRPC(coin, 'z_getbalance', [_address]) + .then((__json) => { + __json = JSON.parse(__json); + if (__json && + __json.error) { + resolve(0); + } else { + resolve(__json.result); + newAddressArray[1][index] = { + address: _address, + amount: __json.result, + type: 'private', + }; + } + }); + }); + })) + .then(zresult => { + _returnObj.addresses = { + public: newAddressArray[0], + private: newAddressArray[1], + }; + + const returnObj = { + msg: 'success', + result: _returnObj, + }; + + res.end(JSON.stringify(returnObj)); + }); + } else { + _returnObj.addresses = { + public: newAddressArray[0], + private: newAddressArray[1], + }; + + const returnObj = { + msg: 'success', + result: _returnObj, + }; + + res.end(JSON.stringify(returnObj)); + } + } + + _bitcoinRPC(coin, 'listunspent') + .then((__json) => { + if (__json === 'Work queue depth exceeded' || + !__json) { + const returnObj = { + msg: 'success', + result: _returnObj, + }; + + res.end(JSON.stringify(returnObj)); + } else { + _returnObj.listunspent = JSON.parse(__json); + + calcBalance( + result, + JSON.parse(__json).result + ); + } + }); + } else { + _returnObj.addresses = { + public: {}, + private: {}, + }; + + const returnObj = { + msg: 'success', + result: _returnObj, + }; + + res.end(JSON.stringify(returnObj)); + } + }) + } + + const _bitcoinRPC = (coin, cmd, params) => { + return new Promise((resolve, reject) => { + let _payload; + + if (params) { + _payload = { + mode: null, + chain: coin, + cmd: cmd, + params: params, + }; + } else { + _payload = { + mode: null, + chain: coin, + cmd: cmd, + }; + } + + const options = { + url: `http://127.0.0.1:${shepherd.appConfig.agamaPort}/shepherd/cli`, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ payload: _payload }), + timeout: 120000, + }; + + shepherd.request(options, (error, response, body) => { + if (response && + response.statusCode && + response.statusCode === 200) { + resolve(body); + } else { + resolve(body); + } + }); + }); + } + + Promise.all(_promiseStack.map((_call, index) => { + let _params; + + if (_call === 'listtransactions') { + _params = [ + '*', + 300, + 0 + ]; + } + + return new Promise((resolve, reject) => { + _bitcoinRPC( + _coin, + _call, + _params + ) + .then((json) => { + if (json === 'Work queue depth exceeded' || + !json) { + _returnObj[_call] = { error: 'daemon is busy' }; + } else { + _returnObj[_call] = JSON.parse(json); + } + resolve(json); + }); + }); + })) + .then(result => { + getAddressesNative(_coin); + }); + }); + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/debugLog.js b/routes/shepherd/debugLog.js new file mode 100644 index 0000000..5fd0c88 --- /dev/null +++ b/routes/shepherd/debugLog.js @@ -0,0 +1,119 @@ +module.exports = (shepherd) => { + /* + * type: POST + * params: herd, lastLines + */ + shepherd.post('/debuglog', (req, res) => { + let _herd = req.body.herdname; + let _ac = req.body.ac; + let _lastNLines = req.body.lastLines; + let _location; + + if (shepherd.os.platform() === 'darwin') { + shepherd.komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.HOME}/Library/Application Support/Komodo`; + } + + if (shepherd.os.platform() === 'linux') { + shepherd.komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.HOME}/.komodo`; + } + + if (shepherd.os.platform() === 'win32') { + shepherd.komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.APPDATA}/Komodo`; + shepherd.komodoDir = shepherd.path.normalize(shepherd.komodoDir); + } + + if (_herd === 'komodo') { + _location = shepherd.komodoDir; + } + + if (_ac) { + _location = `${shepherd.komodoDir}/${_ac}`; + + if (_ac === 'CHIPS') { + _location = shepherd.chipsDir; + } + } + + shepherd.readDebugLog(`${_location}/debug.log`, _lastNLines) + .then((result) => { + const _obj = { + msg: 'success', + result: result, + }; + + res.end(JSON.stringify(_obj)); + }, (result) => { + const _obj = { + msg: 'error', + result: result, + }; + + res.end(JSON.stringify(_obj)); + }); + }); + + shepherd.get('/coind/stdout', (req, res) => { + const _daemonName = req.query.chain !== 'komodod' ? req.query.chain : 'komodod'; + const _daemonLogName = `${shepherd.agamaDir}/${_daemonName}.log`; + + shepherd.readDebugLog(_daemonLogName, 'all') + .then((result) => { + const _obj = { + msg: 'success', + result: result, + }; + + res.end(JSON.stringify(_obj)); + }, (result) => { + const _obj = { + msg: 'error', + result: result, + }; + + res.end(JSON.stringify(_obj)); + }); + }); + + shepherd.readDebugLog = (fileLocation, lastNLines) => { + return new shepherd.Promise( + (resolve, reject) => { + if (lastNLines) { + try { + shepherd._fs.access(fileLocation, shepherd.fs.constants.R_OK, (err) => { + if (err) { + shepherd.log(`error reading ${fileLocation}`); + shepherd.writeLog(`error reading ${fileLocation}`); + reject(`readDebugLog error: ${err}`); + } else { + shepherd.log(`reading ${fileLocation}`); + shepherd._fs.readFile(fileLocation, 'utf-8', (err, data) => { + if (err) { + shepherd.writeLog(`readDebugLog err: ${err}`); + shepherd.log(`readDebugLog err: ${err}`); + } + + const lines = data.trim().split('\n'); + let lastLine; + + if (lastNLines === 'all') { + lastLine = data.trim(); + } else { + lastLine = lines.slice(lines.length - lastNLines, lines.length).join('\n'); + } + + resolve(lastLine); + }); + } + }); + } catch (e) { + reject(`readDebugLog error: ${e}`); + } + } else { + reject('readDebugLog error: lastNLines param is not provided!'); + } + } + ); + }; + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/dex/coind.js b/routes/shepherd/dex/coind.js new file mode 100644 index 0000000..1747b82 --- /dev/null +++ b/routes/shepherd/dex/coind.js @@ -0,0 +1,44 @@ +module.exports = (shepherd) => { + /* + * list native coind + * type: + * params: + */ + shepherd.get('/coind/list', (req, res, next) => { + const successObj = { + msg: 'success', + result: shepherd.nativeCoindList, + }; + + res.end(JSON.stringify(successObj)); + }); + + shepherd.scanNativeCoindBins = () => { + let nativeCoindList = {}; + + // check if coind bins are present in agama + for (let key in shepherd.nativeCoind) { + nativeCoindList[key] = { + name: shepherd.nativeCoind[key].name, + port: shepherd.nativeCoind[key].port, + bin: shepherd.nativeCoind[key].bin, + bins: { + daemon: false, + cli: false, + }, + }; + + if (shepherd.fs.existsSync(`${shepherd.coindRootDir}/${key}/${shepherd.nativeCoind[key].bin}d${shepherd.os.platform() === 'win32' ? '.exe' : ''}`)) { + nativeCoindList[key].bins.daemon = true; + } + + if (shepherd.fs.existsSync(`${shepherd.coindRootDir}/${key}/${shepherd.nativeCoind[key].bin}-cli${shepherd.os.platform() === 'win32' ? '.exe' : ''}`)) { + nativeCoindList[key].bins.cli = true; + } + } + + return nativeCoindList; + } + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/dex/coins.json b/routes/shepherd/dex/coins.json new file mode 100644 index 0000000..703afef --- /dev/null +++ b/routes/shepherd/dex/coins.json @@ -0,0 +1 @@ +[{"coin":"MNZ","asset":"MNZ","rpcport":14337},{"coin":"BTCZ","name":"bitcoinz","rpcport":1979,"taddr":28,"pubtype":184,"p2shtype":189,"wiftype":128,"txfee":10000}, {"coin":"MAGA","name":"magacoin","rpcport":5332,"pubtype":23,"p2shtype":50,"wiftype":176,"txfee":100000}, {"coin":"BLK","name":"blackcoin","rpcport":15715,"pubtype":25,"p2shtype":85,"wiftype":153,"txfee":10000}, {"coin":"ZEN","name":"zen","rpcport":8231,"pubtype":137,"taddr":32,"p2shtype":150,"wiftype":128,"txfee":10000},{"coin":"BSD","name":"bitsend","rpcport":8800,"pubtype":102,"p2shtype":5,"wiftype":204,"txfee":10000}, {"coin":"IOP","name":"IoP","rpcport":8337,"pubtype":117,"p2shtype":174,"wiftype":49,"txfee":10000}, {"coin":"BLOCK","name":"blocknetdx","rpcport":41414,"pubtype":26,"p2shtype":28,"wiftype":154,"txfee":10000}, {"coin":"CHIPS", "name": "chips", "rpcport":57776,"pubtype":60, "p2shtype":85, "wiftype":188, "txfee":10000},{"coin":"888","name":"octocoin","rpcport":22888,"pubtype":18,"p2shtype":5,"wiftype":176,"txfee":2000000}, {"coin":"ARG","name":"argentum","rpcport":13581,"pubtype":23,"p2shtype":5,"wiftype":151,"txfee":50000}, {"coin":"GLT","name":"globaltoken","rpcport":9320,"pubtype":38,"p2shtype":5,"wiftype":166,"txfee":10000}, {"coin":"ZER","name":"zero","rpcport":23801,"taddr":28,"pubtype":184,"p2shtype":189,"wiftype":128,"txfee":1000}, {"coin":"HODLC","name":"hodlcoin","rpcport":11989,"pubtype":40,"p2shtype":5,"wiftype":168,"txfee":5000}, {"coin":"UIS","name":"unitus","rpcport":50604,"pubtype":68,"p2shtype":10,"wiftype":132,"txfee":1000000}, {"coin":"CRW","name":"crown","rpcport":9341,"pubtype":0,"p2shtype":5,"wiftype":128,"txfee":10000}, {"coin":"HUC","name":"huntercoin","rpcport":8399,"pubtype":40,"p2shtype":13,"wiftype":168,"txfee":100000}, {"coin":"PIVX","name":"pivx","rpcport":51473,"pubtype":30,"p2shtype":13,"wiftype":212,"txfee":10000}, {"coin":"BDL","name":"bitdeal","rpcport":9332,"pubtype":38,"p2shtype":5,"wiftype":176,"txfee":100000}, {"coin":"ARC","name":"arcticcoin","confpath":"`${process.env.HOME}`/.arcticcore/arcticcoin.conf","rpcport":7208,"pubtype":23,"p2shtype":8,"wiftype":176,"txfee":10000}, {"coin":"ZCL","name":"zclassic","rpcport":8023,"taddr":28,"pubtype":184,"p2shtype":189,"wiftype":128,"txfee":1000}, {"coin":"VIA","name":"viacoin","rpcport":5222,"pubtype":71,"p2shtype":33,"wiftype":199,"txfee":100000}, {"coin":"ERC","name":"europecoin","rpcport":11989,"pubtype":33,"p2shtype":5,"wiftype":168,"txfee":10000},{"coin":"FAIR","name":"faircoin","confpath":"`${process.env.HOME}`/.faircoin2/faircoin.conf","rpcport":40405,"pubtype":95,"p2shtype":36,"wiftype":223,"txfee":1000000}, {"coin":"FLO","name":"florincoin","rpcport":7313,"pubtype":35,"p2shtype":8,"wiftype":176,"txfee":100000}, {"coin":"SXC","name":"sexcoin","rpcport":9561,"pubtype":62,"p2shtype":5,"wiftype":190,"txfee":100000}, {"coin":"CREA","name":"creativecoin","rpcport":17711,"pubtype":28,"p2shtype":5,"wiftype":176,"txfee":100000}, {"coin":"TRC","name":"terracoin","rpcport":13332,"pubtype":0,"p2shtype":5,"wiftype":128,"txfee":1000}, {"coin":"BTA","name":"bata","rpcport":5493,"pubtype":25,"p2shtype":5,"wiftype":188,"txfee":100000}, {"coin":"SMC","name":"smartcoin","rpcport":58583,"pubtype":63,"p2shtype":5,"wiftype":191,"txfee":1000000}, {"coin":"NMC","name":"namecoin","rpcport":8336,"pubtype":52,"p2shtype":13,"wiftype":180,"txfee":100000}, {"coin":"NAV","name":"navcoin","isPoS":1,"confpath":"`${process.env.HOME}`/.navcoin4/navcoin.conf","rpcport":44444,"pubtype":53,"p2shtype":85,"wiftype":150,"txfee":10000}, {"coin":"MOON","name":"Mooncoin","rpcport":44663,"pubtype":3,"p2shtype":22,"wiftype":131,"txfee":100000}, {"coin":"EMC2","name":"einsteinium","rpcport":41879,"pubtype":33,"p2shtype":5,"wiftype":176,"txfee":100000},{"coin":"SYS","name":"syscoin","rpcport":8370,"pubtype":0,"p2shtype":5,"wiftype":128,"txfee":1000}, {"coin":"I0C","name":"i0coin","rpcport":7332,"pubtype":105,"p2shtype":5,"wiftype":128,"txfee":1000}, {"coin":"DASH","confpath":"`${process.env.HOME}`/.dashcore/dash.conf","name":"dashcore","rpcport":9998,"pubtype":76,"p2shtype":16,"wiftype":204,"txfee":10000}, {"coin":"STRAT", "name": "stratis", "active":0, "rpcport":16174,"pubtype":63, "p2shtype":125, "wiftype":191, "txfee":10000}, {"confpath":"`${process.env.HOME}`/.muecore/mue.conf","coin":"MUE","name":"muecore","rpcport":29683,"pubtype":16,"p2shtype":76,"wiftype":126,"txfee":10000}, {"coin":"MONA","name":"monacoin","rpcport":9402,"pubtype":50,"p2shtype":5,"wiftype":176,"txfee":100000},{"coin":"XMY","name":"myriadcoin","rpcport":10889,"pubtype":50,"p2shtype":9,"wiftype":178,"txfee":5000}, {"coin":"MAC","name":"machinecoin","rpcport":40332,"pubtype":50,"p2shtype":5,"wiftype":178,"txfee":50000}, {"coin":"BTX","name":"bitcore","rpcport":8556,"pubtype":0,"p2shtype":5,"wiftype":128,"txfee":50000}, {"coin":"XRE","name":"revolvercoin","rpcport":8775,"pubtype":0,"p2shtype":5,"wiftype":128,"txfee":1000}, {"coin":"LBC","name":"lbrycrd","rpcport":9245,"pubtype":85,"p2shtype":122,"wiftype":28,"txfee":1000}, {"coin":"SIB","name":"sibcoin","rpcport":1944,"pubtype":63,"p2shtype":40,"wiftype":128,"txfee":10000}, {"coin":"VTC", "name":"vertcoin", "rpcport":5888, "pubtype":71, "p2shtype":5, "wiftype":128, "txfee":100000 }, {"coin":"REVS","active":0, "asset":"REVS","rpcport":10196}, {"coin":"JUMBLR","active":0, "asset":"JUMBLR","rpcport":15106}, {"coin":"DOGE","name":"dogecoin","rpcport":22555,"pubtype":30,"p2shtype":22,"wiftype":158,"txfee":100000000}, {"coin":"HUSH","name":"hush","rpcport":8822,"taddr":28,"pubtype":184,"p2shtype":189,"wiftype":128,"txfee":1000 }, {"active":0,"coin":"ZEC","name":"zcash","rpcport":8232,"taddr":28,"pubtype":184,"p2shtype":189,"wiftype":128,"txfee":10000 }, {"coin":"DGB","name":"digibyte","rpcport":14022,"pubtype":30,"p2shtype":5,"wiftype":128,"txfee":100000}, {"coin":"MZC", "name":"mazacoin", "pubtype":50, "p2shtype":9, "wiftype":224, "txfee":10000}, {"coin":"UNO", "name":"unobtanium", "pubtype":130, "p2shtype":30, "wiftype":224, "txfee":1000000}, {"coin":"ZET", "name":"zetacoin", "pubtype":80, "p2shtype":9,"rpcport":8332, "wiftype":224, "txfee":10000}, {"coin":"BTM", "name":"bitmark", "pubtype":85, "p2shtype":5, "wiftype":213, "txfee":10000}, {"coin":"CARB", "name":"carboncoin", "pubtype":47, "p2shtype":5, "wiftype":175, "txfee":10000}, {"coin":"ANC", "name":"anoncoin", "pubtype":23, "p2shtype":5, "wiftype":151, "txfee":2000000}, {"coin":"FRK", "name":"franko", "pubtype":35, "p2shtype":5, "wiftype":163, "txfee":10000}, {"coin":"GAME", "rpcport":40001, "name":"gamecredits", "pubtype":38, "p2shtype":5, "wiftype":166, "txfee":100000}, {"coin":"LTC", "name":"litecoin", "rpcport":9332, "pubtype":48, "p2shtype":5, "wiftype":176, "txfee":100000 }, {"coin":"SUPERNET","asset":"SUPERNET","rpcport":11341}, {"coin":"WLC","asset":"WLC","rpcport":12167}, {"coin":"PANGEA","asset":"PANGEA","rpcport":14068}, {"coin":"DEX","asset":"DEX","rpcport":11890}, {"coin":"BET","asset":"BET","rpcport":14250}, {"coin":"CRYPTO","asset":"CRYPTO","rpcport":8516}, {"coin":"HODL","asset":"HODL","rpcport":14431}, {"coin":"SHARK","asset":"SHARK","rpcport":10114}, {"coin":"BOTS","asset":"BOTS","rpcport":11964}, {"coin":"MGW","asset":"MGW","rpcport":12386}, {"coin":"COQUI","asset":"COQUI","rpcport":14276}, {"coin":"KV","asset":"KV","rpcport":8299}, {"coin":"CEAL","asset":"CEAL","rpcport":11116}, {"coin":"MESH","asset":"MESH","rpcport":9455}] \ No newline at end of file diff --git a/routes/shepherd/dex/mmControl.js b/routes/shepherd/dex/mmControl.js new file mode 100644 index 0000000..a34a7e2 --- /dev/null +++ b/routes/shepherd/dex/mmControl.js @@ -0,0 +1,242 @@ +const os = require('os'); +const fs = require('fs-extra'); +const portscanner = require('portscanner'); +const exec = require('child_process').exec; +const execFile = require('child_process').execFile; +const path = require('path'); +const request = require('request'); +const Promise = require('bluebird'); + +const RATES_UPDATE_INTERVAL = 60000; + +module.exports = (shepherd) => { + shepherd.get('/mm/start', (req, res, next) => { + shepherd.log('mm start is called'); + + shepherd.startMarketMaker({ passphrase: req.query.passphrase }); + shepherd.mmupass = null; + + shepherd.mmupass = setInterval(() => { + const options = { + url: `http://localhost:7783`, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ method: 'balance' }), + }; + + // send back body on both success and error + // this bit replicates iguana core's behaviour + request(options, (error, response, body) => { + if (response && + response.statusCode && + response.statusCode === 200) { + const _parsedBody = JSON.parse(body); + + if (_parsedBody.userpass) { + res.end(body); + clearInterval(shepherd.mmupass); + shepherd.mmupass = _parsedBody.userpass; + shepherd.mmPublic.mmupass = shepherd.mmupass; + shepherd.mmPublic.isAuth = true; + shepherd.mmPublic.coins = _parsedBody.coins; + shepherd.log(`mm start success`); + shepherd.log(`mm userpass ${_parsedBody.userpass}`); + shepherd.getCoinsHelper(); + shepherd.getRates(); + } + } else { + shepherd.log(`mm start responded with error ${error}`); + /*res.end(body ? body : JSON.stringify({ + result: 'error', + error: { + code: -777, + message: `unable to call method balance at port 7783`, + }, + }));*/ + } + }); + }, 500); + }); + + shepherd.getCoinsHelper = () => { + const defaultCoinsListFile = path.join(__dirname, '../dex/coins.json'); + shepherd.mmPublic.coinsHelper = fs.readJsonSync(defaultCoinsListFile, { throws: false }); + } + + shepherd.getRates = () => { + function _getRates() { + const options = { + url: `https://min-api.cryptocompare.com/data/price?fsym=KMD&tsyms=BTC,USD`, + method: 'GET', + }; + + // send back body on both success and error + // this bit replicates iguana core's behaviour + request(options, (error, response, body) => { + if (response && + response.statusCode && + response.statusCode === 200) { + const _parsedBody = JSON.parse(body); + shepherd.log(`rates ${body}`); + shepherd.mmPublic.rates = _parsedBody; + } else { + shepherd.log(`mm unable to retrieve KMD/BTC,USD rate`); + } + }); + } + + _getRates(); + shepherd.mmRatesInterval = setInterval(() => { + _getRates(); + }, RATES_UPDATE_INTERVAL); + } + + shepherd.getMMCacheData = () => { + return new Promise((resolve, reject) => { + resolve(shepherd.mmPublic); + }); + } + + shepherd.get('/mm/stop', (req, res, next) => { + shepherd.log('mm stop is called'); + clearInterval(shepherd.mmRatesInterval); + shepherd.killRogueProcess('marketmaker'); + shepherd.mmPublic = { + coins: [], + mmupass: null, + swaps: [], + bids: [], + asks: [], + isAuth: false, + rates: {}, + }; + + const successObj = { + msg: 'success', + result: 'executed', + }; + + res.end(JSON.stringify(successObj)); + }); + + shepherd.get('/mm/restart', (req, res, next) => { + shepherd.log('mm restart is called'); + shepherd.killRogueProcess('marketmaker'); + shepherd.mmPublic = { + coins: {}, + mmupass: null, + swaps: [], + bids: [], + asks: [], + isAuth: false, + }; + + setTimeout(() => { + shepherd.startMarketMaker({ passphrase: req.query.passphrase }); + + const successObj = { + msg: 'success', + result: 'restarting', + }; + + res.end(JSON.stringify(successObj)); + }, 1000); + }); + + shepherd.startMarketMaker = (data) => { + const defaultCoinsListFile = path.join(__dirname, '../dex/coins.json'); + + try { + // check if marketmaker instance is already running + portscanner.checkPortStatus(7783, '127.0.0.1', (error, status) => { + // Status is 'open' if currently in use or 'closed' if available + if (status === 'closed') { + // add BarterDEX check + const _coinsListFile = shepherd.agamaDir + '/coins.json'; + + fs.pathExists(_coinsListFile, (err, exists) => { + if (exists) { + shepherd.log('dex coins file exist'); + data.coinslist = fs.readJsonSync(_coinsListFile, { throws: false }); + shepherd.execMarketMaker(data); + } else if (!exists) { + shepherd.log(`dex coins file doesnt exist`); + fs.copy(defaultCoinsListFile, _coinsListFile) + .then(() => { + shepherd.log(`dex coins file copied to ${shepherd.agamaDir}`); + data.coinslist = fs.readJsonSync(_coinsListFile, { throws: false }); + shepherd.execMarketMaker(data); + }) + .catch(err => { + shepherd.log(`unable to copy dex coins file, ${err}`); + }); + } else if (err) { + shepherd.log(`dex coins file doesnt exist, ${err}`); + } + }); + } else { + shepherd.log(`port 7783 marketmaker is already in use`); + } + }); + } catch(e) { + shepherd.log(`failed to start marketmaker err: ${e}`); + } + } + + shepherd.execMarketMaker = (data) => { + const _customParam = { + gui: 'agama-buildog', + client: 1, + profitmargin: 0.01, // (?) + userhome: `${process.env.HOME}`, + passphrase: data.passphrase, + coins: data.coinslist, + }; + + //console.log(JSON.stringify(_customParam)) + //console.log(`exec ${BarterDEXBin} ${JSON.stringify(_customParam)}`); + + let params = _customParam; + if (os.platform() !== 'win32') { + params = `'${JSON.stringify(_customParam)}'`; + } else { + shepherd.mmBin = `"${shepherd.mmBin}"`; + params.userhome = process.env.APPDATA; + + if (!!params.coins) { // if not undefined and true + delete params.coins; // for Windows we should use coins.json file, and don't pass coins in command line + } + + params = JSON.stringify(_customParam); + params = params.replace(/"/g, '\\"'); + params = `"${params}"`; + } + + const logStream = fs.createWriteStream(`${shepherd.agamaDir}/logFile.log`, { flags: 'a' }); + + shepherd.log('starting mm'); + const mmid = exec(`${shepherd.mmBin} ${params}`, { + cwd: shepherd.agamaDir, + maxBuffer: 1024 * 50000 // 50 mb + }, function(error, stdout, stderr) { + // console.log(`stdout: ${stdout}`); + // console.log(`stderr: ${stderr}`); + + if (error !== null) { + shepherd.log(`mm exec error: ${error}`); + } + }); + + mmid.stdout.on('data', (data) => { + // console.log(`child stdout:\n${data}`); + }).pipe(logStream); + + mmid.stderr.on('data', (data) => { + // console.error(`child stderr:\n${data}`); + }).pipe(logStream); + } + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/dex/mmRequest.js b/routes/shepherd/dex/mmRequest.js new file mode 100644 index 0000000..c91a9de --- /dev/null +++ b/routes/shepherd/dex/mmRequest.js @@ -0,0 +1,39 @@ +const request = require('request'); + +module.exports = (shepherd) => { + // payload + // record all calls + shepherd.post('/mm/request', (req, res, next) => { + let _payload = req.body.payload; + _payload.userpass = shepherd.mmupass; + const options = { + url: `http://localhost:7783`, + method: 'POST', + body: JSON.stringify(_payload), + }; + + shepherd.log(_payload); + + // send back body on both success and error + // this bit replicates iguana core's behaviour + request(options, (error, response, body) => { + if (response && + response.statusCode && + response.statusCode === 200) { + const _parsedBody = JSON.parse(body); + shepherd.mmPublic[_payload.mapToProp] = _parsedBody; + res.end(body); + } else { + res.end(body ? body : JSON.stringify({ + result: 'error', + error: { + code: -777, + message: `unable to call method ${_payload.method} at port 7783`, + }, + })); + } + }); + }); + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/downloadBins.js b/routes/shepherd/downloadBins.js new file mode 100644 index 0000000..c3e48da --- /dev/null +++ b/routes/shepherd/downloadBins.js @@ -0,0 +1,165 @@ +const remoteBinLocation = { + win32: 'https://artifacts.supernet.org/latest/windows/', + darwin: 'https://artifacts.supernet.org/latest/osx/', + linux: 'https://artifacts.supernet.org/latest/linux/', +}; +const localBinLocation = { + win32: 'assets/bin/win64/', + darwin: 'assets/bin/osx/', + linux: 'assets/bin/linux64/', +}; +const latestBins = { + win32: [ + 'komodo-cli.exe', + 'komodod.exe', + 'libcrypto-1_1.dll', + 'libcurl-4.dll', + 'libcurl.dll', + 'libgcc_s_sjlj-1.dll', + 'libnanomsg.dll', + 'libssl-1_1.dll', + 'libwinpthread-1.dll', + 'nanomsg.dll', + 'pthreadvc2.dll', + ], + darwin: [ + 'komodo-cli', + 'komodod', + 'libgcc_s.1.dylib', + 'libgomp.1.dylib', + 'libnanomsg.5.0.0.dylib', + 'libstdc++.6.dylib', // encode %2B + ], + linux: [ + 'komodo-cli', + 'komodod', + ], +}; + +let binsToUpdate = []; + +module.exports = (shepherd) => { + /* + * Check bins file size + * type: + * params: + */ + // TODO: promises + shepherd.get('/update/bins/check', (req, res, next) => { + const rootLocation = shepherd.path.join(__dirname, '../../'); + const successObj = { + msg: 'success', + result: 'bins', + }; + + res.end(JSON.stringify(successObj)); + + const _os = shepherd.os.platform(); + shepherd.log(`checking bins: ${_os}`); + + shepherd.io.emit('patch', { + patch: { + type: 'bins-check', + status: 'progress', + message: `checking bins: ${_os}`, + }, + }); + // get list of bins/dlls that can be updated to the latest + for (let i = 0; i < latestBins[_os].length; i++) { + shepherd.remoteFileSize(remoteBinLocation[_os] + latestBins[_os][i], (err, remoteBinSize) => { + const localBinSize = shepherd.fs.statSync(rootLocation + localBinLocation[_os] + latestBins[_os][i]).size; + + shepherd.log('remote url: ' + (remoteBinLocation[_os] + latestBins[_os][i]) + ' (' + remoteBinSize + ')'); + shepherd.log('local file: ' + (rootLocation + localBinLocation[_os] + latestBins[_os][i]) + ' (' + localBinSize + ')'); + + if (remoteBinSize !== localBinSize) { + shepherd.log(`${latestBins[_os][i]} can be updated`); + binsToUpdate.push({ + name: latestBins[_os][i], + rSize: remoteBinSize, + lSize: localBinSize, + }); + } + + if (i === latestBins[_os].length - 1) { + shepherd.io.emit('patch', { + patch: { + type: 'bins-check', + status: 'done', + fileList: binsToUpdate, + }, + }); + } + }); + } + }); + + /* + * Update bins + * type: + * params: + */ + shepherd.get('/update/bins', (req, res, next) => { + const rootLocation = shepherd.path.join(__dirname, '../../'); + const _os = shepherd.os.platform(); + const successObj = { + msg: 'success', + result: { + filesCount: binsToUpdate.length, + list: binsToUpdate, + }, + }; + + res.end(JSON.stringify(successObj)); + + for (let i = 0; i < binsToUpdate.length; i++) { + shepherd.downloadFile({ + remoteFile: remoteBinLocation[_os] + binsToUpdate[i].name, + localFile: `${rootLocation}${localBinLocation[_os]}patch/${binsToUpdate[i].name}`, + onProgress: (received, total) => { + const percentage = (received * 100) / total; + + if (percentage.toString().indexOf('.10') > -1) { + shepherd.io.emit('patch', { + msg: { + type: 'bins-update', + status: 'progress', + file: binsToUpdate[i].name, + bytesTotal: total, + bytesReceived: received, + }, + }); + // shepherd.log(`${binsToUpdate[i].name} ${percentage}% | ${received} bytes out of ${total} bytes.`); + } + } + }) + .then(() => { + // verify that remote file is matching to DL'ed file + const localBinSize = shepherd.fs.statSync(`${rootLocation}${localBinLocation[_os]}patch/${binsToUpdate[i].name}`).size; + shepherd.log('compare dl file size'); + + if (localBinSize === binsToUpdate[i].rSize) { + shepherd.io.emit('patch', { + msg: { + type: 'bins-update', + file: binsToUpdate[i].name, + status: 'done', + }, + }); + shepherd.log(`file ${binsToUpdate[i].name} succesfully downloaded`); + } else { + shepherd.io.emit('patch', { + msg: { + type: 'bins-update', + file: binsToUpdate[i].name, + message: 'size mismatch', + }, + }); + shepherd.log(`error: ${binsToUpdate[i].name} file size doesnt match remote!`); + } + }); + } + }); + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/downloadPatch.js b/routes/shepherd/downloadPatch.js new file mode 100644 index 0000000..f8be045 --- /dev/null +++ b/routes/shepherd/downloadPatch.js @@ -0,0 +1,154 @@ +module.exports = (shepherd) => { + /* + * DL app patch + * type: GET + * params: patchList + */ + shepherd.get('/update/patch', (req, res, next) => { + const successObj = { + msg: 'success', + result: 'dl started' + }; + + res.end(JSON.stringify(successObj)); + + shepherd.updateAgama(); + }); + + shepherd.updateAgama = () => { + const rootLocation = shepherd.path.join(__dirname, '../../'); + + shepherd.downloadFile({ + remoteFile: 'https://github.com/pbca26/dl-test/raw/master/patch.zip', + localFile: `${rootLocation}patch.zip`, + onProgress: (received, total) => { + const percentage = (received * 100) / total; + + if (percentage.toString().indexOf('.10') > -1) { + shepherd.io.emit('patch', { + msg: { + status: 'progress', + type: 'ui', + progress: percentage, + bytesTotal: total, + bytesReceived: received, + }, + }); + //shepherd.log(`patch ${percentage}% | ${received} bytes out of ${total} bytes.`); + } + } + }) + .then(() => { + shepherd.remoteFileSize('https://github.com/pbca26/dl-test/raw/master/patch.zip', (err, remotePatchSize) => { + // verify that remote file is matching to DL'ed file + const localPatchSize = shepherd.fs.statSync(`${rootLocation}patch.zip`).size; + shepherd.log('compare dl file size'); + + if (localPatchSize === remotePatchSize) { + const zip = new shepherd.AdmZip(`${rootLocation}patch.zip`); + + shepherd.log('patch succesfully downloaded'); + shepherd.log('extracting contents'); + + if (shepherd.appConfig.dev) { + if (!shepherd.fs.existsSync(`${rootLocation}/patch`)) { + shepherd.fs.mkdirSync(`${rootLocation}/patch`); + } + } + + zip.extractAllTo(/*target path*/rootLocation + (shepherd.appConfig.dev ? '/patch' : ''), /*overwrite*/true); + // TODO: extract files in chunks + shepherd.io.emit('patch', { + msg: { + type: 'ui', + status: 'done', + }, + }); + shepherd.fs.unlinkSync(`${rootLocation}patch.zip`); + } else { + shepherd.io.emit('patch', { + msg: { + type: 'ui', + status: 'error', + message: 'size mismatch', + }, + }); + shepherd.log('patch file size doesnt match remote!'); + } + }); + }); + } + + /* + * check latest version + * type: + * params: + */ + shepherd.get('/update/patch/check', (req, res, next) => { + const rootLocation = shepherd.path.join(__dirname, '../../'); + const options = { + url: 'https://github.com/pbca26/dl-test/raw/master/version', + method: 'GET', + }; + + shepherd.request(options, (error, response, body) => { + if (response && + response.statusCode && + response.statusCode === 200) { + const remoteVersion = body.split('\n'); + const localVersionFile = shepherd.fs.readFileSync(`${rootLocation}version`, 'utf8'); + let localVersion; + + if (localVersionFile.indexOf('\r\n') > -1) { + localVersion = localVersionFile.split('\r\n'); + } else { + localVersion = localVersionFile.split('\n'); + } + + if (remoteVersion[0] === localVersion[0]) { + const successObj = { + msg: 'success', + result: 'latest', + }; + + res.end(JSON.stringify(successObj)); + } else { + const successObj = { + msg: 'success', + result: 'update', + version: { + local: localVersion[0], + remote: remoteVersion[0], + }, + }; + + res.end(JSON.stringify(successObj)); + } + } else { + res.end({ + err: 'error getting update', + }); + } + }); + }); + + /* + * unpack zip + * type: + * params: + */ + shepherd.get('/unpack', (req, res, next) => { + const dlLocation = shepherd.path.join(__dirname, '../../'); + const zip = new shepherd.AdmZip(`${dlLocation}patch.zip`); + zip.extractAllTo(/*target path*/ `${dlLocation}/patch/unpack`, /*overwrite*/true); + + const successObj = { + msg: 'success', + result: 'unpack started', + }; + + res.end(JSON.stringify(successObj)); + }); + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/downloadUtil.js b/routes/shepherd/downloadUtil.js new file mode 100644 index 0000000..dfc4a96 --- /dev/null +++ b/routes/shepherd/downloadUtil.js @@ -0,0 +1,49 @@ +module.exports = (shepherd) => { + /** + * Promise based download file method + */ + shepherd.downloadFile = (configuration) => { + return new shepherd.Promise((resolve, reject) => { + // Save variable to know progress + let receivedBytes = 0; + let totalBytes = 0; + + let req = shepherd.request({ + method: 'GET', + uri: configuration.remoteFile, + agentOptions: { + keepAlive: true, + keepAliveMsecs: 15000, + }, + }); + + let out = shepherd.fs.createWriteStream(configuration.localFile); + req.pipe(out); + + req.on('response', (data) => { + // Change the total bytes value to get progress later. + totalBytes = parseInt(data.headers['content-length']); + }); + + // Get progress if callback exists + if (configuration.hasOwnProperty('onProgress')) { + req.on('data', (chunk) => { + // Update the received bytes + receivedBytes += chunk.length; + configuration.onProgress(receivedBytes, totalBytes); + }); + } else { + req.on('data', (chunk) => { + // Update the received bytes + receivedBytes += chunk.length; + }); + } + + req.on('end', () => { + resolve(); + }); + }); + } + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/downloadZcparams.js b/routes/shepherd/downloadZcparams.js new file mode 100644 index 0000000..0e520d3 --- /dev/null +++ b/routes/shepherd/downloadZcparams.js @@ -0,0 +1,134 @@ +module.exports = (shepherd) => { + shepherd.zcashParamsDownloadLinks = { + 'agama.komodoplatform.com': { + proving: 'https://agama.komodoplatform.com/file/supernet/sprout-proving.key', + verifying: 'https://agama.komodoplatform.com/file/supernet/sprout-verifying.key', + }, + 'agama-yq0ysrdtr.stackpathdns.com': { + proving: 'http://agama-yq0ysrdtr.stackpathdns.com/file/supernet/sprout-proving.key', + verifying: 'http://agama-yq0ysrdtr.stackpathdns.com/file/supernet/sprout-verifying.key', + }, + 'zcash.dl.mercerweiss.com': { + proving: 'https://zcash.dl.mercerweiss.com/sprout-proving.key', + verifying: 'https://zcash.dl.mercerweiss.com/sprout-verifying.key', + }, + }; + + shepherd.zcashParamsExist = () => { + let _checkList = { + rootDir: shepherd._fs.existsSync(shepherd.zcashParamsDir), + provingKey: shepherd._fs.existsSync(`${shepherd.zcashParamsDir}/sprout-proving.key`), + provingKeySize: false, + verifyingKey: shepherd._fs.existsSync(`${shepherd.zcashParamsDir}/sprout-verifying.key`), + verifyingKeySize: false, + errors: false, + }; + + if (_checkList.rootDir && + _checkList.provingKey || + _checkList.verifyingKey) { + // verify each key size + const _provingKeySize = _checkList.provingKey ? shepherd.fs.lstatSync(`${shepherd.zcashParamsDir}/sprout-proving.key`) : 0; + const _verifyingKeySize = _checkList.verifyingKey ? shepherd.fs.lstatSync(`${shepherd.zcashParamsDir}/sprout-verifying.key`) : 0; + + if (Number(_provingKeySize.size) === 910173851) { // bytes + _checkList.provingKeySize = true; + } + if (Number(_verifyingKeySize.size) === 1449) { + _checkList.verifyingKeySize = true; + } + + shepherd.log('zcashparams exist'); + } else { + shepherd.log('zcashparams doesnt exist'); + } + + if (!_checkList.rootDir || + !_checkList.provingKey || + !_checkList.verifyingKey || + !_checkList.provingKeySize || + !_checkList.verifyingKeySize) { + _checkList.errors = true; + } + + return _checkList; + } + + shepherd.zcashParamsExistPromise = () => { + return new shepherd.Promise((resolve, reject) => { + const _verify = shepherd.zcashParamsExist(); + resolve(_verify); + }); + }; + + /* + * Update bins + * type: + * params: + */ + shepherd.get('/zcparamsdl', (req, res, next) => { + // const dlLocation = shepherd.zcashParamsDir + '/test'; + const dlLocation = shepherd.zcashParamsDir; + const dlOption = req.query.dloption; + + const successObj = { + msg: 'success', + result: 'zcash params dl started', + }; + + res.end(JSON.stringify(successObj)); + + for (let key in shepherd.zcashParamsDownloadLinks[dlOption]) { + shepherd.downloadFile({ + remoteFile: shepherd.zcashParamsDownloadLinks[dlOption][key], + localFile: `${dlLocation}/sprout-${key}.key`, + onProgress: (received, total) => { + const percentage = (received * 100) / total; + + if (percentage.toString().indexOf('.10') > -1) { + shepherd.io.emit('zcparams', { + msg: { + type: 'zcpdownload', + status: 'progress', + file: key, + bytesTotal: total, + bytesReceived: received, + progress: percentage, + }, + }); + // shepherd.log(`${key} ${percentage}% | ${received} bytes out of ${total} bytes.`); + } + } + }) + .then(() => { + const checkZcashParams = shepherd.zcashParamsExist(); + + shepherd.log(`${key} dl done`); + + if (checkZcashParams.error) { + shepherd.io.emit('zcparams', { + msg: { + type: 'zcpdownload', + file: key, + status: 'error', + message: 'size mismatch', + progress: 100, + }, + }); + } else { + shepherd.io.emit('zcparams', { + msg: { + type: 'zcpdownload', + file: key, + progress: 100, + status: 'done', + }, + }); + shepherd.log(`file ${key} succesfully downloaded`); + } + }); + } + }); + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/electrum/auth.js b/routes/shepherd/electrum/auth.js new file mode 100644 index 0000000..4f763cd --- /dev/null +++ b/routes/shepherd/electrum/auth.js @@ -0,0 +1,64 @@ +module.exports = (shepherd) => { + shepherd.post('/electrum/login', (req, res, next) => { + for (let key in shepherd.electrumServers) { + const _abbr = shepherd.electrumServers[key].abbr; + let keys; + + if (req.body.seed.length === 52 && + req.body.seed.indexOf(' ') === -1 && + req.body.seed.match(/^[a-zA-Z0-9]*$/)) { + let key = shepherd.bitcoinJS.ECPair.fromWIF(req.body.seed, shepherd.electrumJSNetworks.komodo); + keys = { + priv: key.toWIF(), + pub: key.getAddress(), + }; + } else { + keys = shepherd.seedToWif(req.body.seed, shepherd.findNetworkObj(_abbr), req.body.iguana, req.body.old); + } + + shepherd.electrumKeys[_abbr] = { + priv: keys.priv, + pub: keys.pub, + }; + } + + shepherd.electrumCoins.auth = true; + + // shepherd.log(JSON.stringify(shepherd.electrumKeys, null, '\t'), true); + + const successObj = { + msg: 'success', + result: 'true', + }; + + res.end(JSON.stringify(successObj)); + }); + + shepherd.post('/electrum/lock', (req, res, next) => { + shepherd.electrumCoins.auth = false; + shepherd.electrumKeys = {}; + + const successObj = { + msg: 'success', + result: 'true', + }; + + res.end(JSON.stringify(successObj)); + }); + + shepherd.post('/electrum/logout', (req, res, next) => { + shepherd.electrumCoins = { + auth: false, + }; + shepherd.electrumKeys = {}; + + const obj = { + msg: 'success', + result: 'result', + }; + + res.end(JSON.stringify(obj)); + }); + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/electrum/balance.js b/routes/shepherd/electrum/balance.js new file mode 100644 index 0000000..9291a7f --- /dev/null +++ b/routes/shepherd/electrum/balance.js @@ -0,0 +1,145 @@ +module.exports = (shepherd) => { + shepherd.get('/electrum/getbalance', (req, res, next) => { + const network = req.query.network || shepherd.findNetworkObj(req.query.coin); + const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls + + shepherd.log('electrum getbalance =>', true); + + ecl.connect(); + ecl.blockchainAddressGetBalance(req.query.address) + .then((json) => { + if (json && + json.hasOwnProperty('confirmed') && + json.hasOwnProperty('unconfirmed')) { + if (network === 'komodo') { + ecl.connect(); + ecl.blockchainAddressListunspent(req.query.address) + .then((utxoList) => { + if (utxoList && + utxoList.length) { + // filter out < 10 KMD amounts + let _utxo = []; + + for (let i = 0; i < utxoList.length; i++) { + shepherd.log(`utxo ${utxoList[i]['tx_hash']} sats ${utxoList[i].value} value ${Number(utxoList[i].value) * 0.00000001}`, true); + + if (Number(utxoList[i].value) * 0.00000001 >= 10) { + _utxo.push(utxoList[i]); + } + } + + shepherd.log('filtered utxo list =>', true); + shepherd.log(_utxo, true); + + if (_utxo && + _utxo.length) { + let interestTotal = 0; + + shepherd.Promise.all(_utxo.map((_utxoItem, index) => { + return new shepherd.Promise((resolve, reject) => { + ecl.blockchainTransactionGet(_utxoItem['tx_hash']) + .then((_rawtxJSON) => { + shepherd.log('electrum gettransaction ==>', true); + shepherd.log((index + ' | ' + (_rawtxJSON.length - 1)), true); + shepherd.log(_rawtxJSON, true); + + // decode tx + const _network = shepherd.getNetworkData(network); + const decodedTx = shepherd.electrumJSTxDecoder(_rawtxJSON, _network); + + if (decodedTx && + decodedTx.format && + decodedTx.format.locktime > 0) { + interestTotal += shepherd.kmdCalcInterest(decodedTx.format.locktime, _utxoItem.value); + } + + shepherd.log('decoded tx =>', true); + shepherd.log(decodedTx, true); + + resolve(true); + }); + }); + })) + .then(promiseResult => { + ecl.close(); + + const successObj = { + msg: 'success', + result: { + balance: Number((0.00000001 * json.confirmed).toFixed(8)), + unconfirmed: Number((0.00000001 * json.unconfirmed).toFixed(8)), + unconfirmedSats: json.unconfirmed, + balanceSats: json.confirmed, + interest: Number(interestTotal.toFixed(8)), + interestSats: Math.floor(interestTotal * 100000000), + total: interestTotal > 0 ? Number((0.00000001 * json.confirmed + interestTotal).toFixed(8)) : 0, + totalSats: interestTotal > 0 ?json.confirmed + Math.floor(interestTotal * 100000000) : 0, + }, + }; + + res.end(JSON.stringify(successObj)); + }); + } else { + const successObj = { + msg: 'success', + result: { + balance: Number((0.00000001 * json.confirmed).toFixed(8)), + unconfirmed: Number((0.00000001 * json.unconfirmed).toFixed(8)), + unconfirmedSats: json.unconfirmed, + balanceSats: json.confirmed, + interest: 0, + interestSats: 0, + total: 0, + totalSats: 0, + }, + }; + + res.end(JSON.stringify(successObj)); + } + } else { + const successObj = { + msg: 'success', + result: { + balance: Number((0.00000001 * json.confirmed).toFixed(8)), + unconfirmed: Number((0.00000001 * json.unconfirmed).toFixed(8)), + unconfirmedSats: json.unconfirmed, + balanceSats: json.confirmed, + interest: 0, + interestSats: 0, + total: 0, + totalSats: 0, + }, + }; + + res.end(JSON.stringify(successObj)); + } + }); + } else { + ecl.close(); + shepherd.log('electrum getbalance ==>', true); + shepherd.log(json, true); + + const successObj = { + msg: 'success', + result: { + balance: Number((0.00000001 * json.confirmed).toFixed(8)), + unconfirmed: Number((0.00000001 * json.unconfirmed).toFixed(8)), + unconfirmedSats: json.unconfirmed, + balanceSats: json.confirmed, + }, + }; + + res.end(JSON.stringify(successObj)); + } + } else { + const successObj = { + msg: 'error', + result: shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA, + }; + + res.end(JSON.stringify(successObj)); + } + }); + }); + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/electrum/block.js b/routes/shepherd/electrum/block.js new file mode 100644 index 0000000..3a92911 --- /dev/null +++ b/routes/shepherd/electrum/block.js @@ -0,0 +1,59 @@ +module.exports = (shepherd) => { + shepherd.get('/electrum/getblockinfo', (req, res, next) => { + shepherd.electrumGetBlockInfo(req.query.height, req.query.network) + .then((json) => { + const successObj = { + msg: 'success', + result: json, + }; + + res.end(JSON.stringify(successObj)); + }); + }); + + shepherd.electrumGetBlockInfo = (height, network) => { + return new shepherd.Promise((resolve, reject) => { + const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls + + ecl.connect(); + ecl.blockchainBlockGetHeader(height) + .then((json) => { + ecl.close(); + shepherd.log('electrum getblockinfo ==>', true); + shepherd.log(json, true); + + resolve(json); + }); + }); + } + + shepherd.get('/electrum/getcurrentblock', (req, res, next) => { + shepherd.electrumGetCurrentBlock(req.query.network) + .then((json) => { + const successObj = { + msg: 'success', + result: json, + }; + + res.end(JSON.stringify(successObj)); + }); + }); + + shepherd.electrumGetCurrentBlock = (network) => { + return new shepherd.Promise((resolve, reject) => { + const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls + + ecl.connect(); + ecl.blockchainNumblocksSubscribe() + .then((json) => { + ecl.close(); + shepherd.log('electrum currentblock ==>', true); + shepherd.log(json, true); + + resolve(json); + }); + }); + } + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/electrum/coins.js b/routes/shepherd/electrum/coins.js new file mode 100644 index 0000000..1130163 --- /dev/null +++ b/routes/shepherd/electrum/coins.js @@ -0,0 +1,91 @@ +module.exports = (shepherd) => { + shepherd.findCoinName = (network) => { + for (let key in shepherd.electrumServers) { + if (key === network) { + return shepherd.electrumServers[key].abbr; + } + } + } + + shepherd.addElectrumCoin = (coin) => { + const servers = shepherd.electrumServers[coin === 'KMD' ? 'komodo' : coin.toLowerCase()].serverList; + // select random server + const getRandomIntInclusive = (min, max) => { + min = Math.ceil(min); + max = Math.floor(max); + + return Math.floor(Math.random() * (max - min + 1)) + min; // the maximum is inclusive and the minimum is inclusive + } + let randomServer; + + // pick a random server to communicate with + if (servers && + servers.length > 0) { + const _randomServerId = getRandomIntInclusive(0, servers.length - 1); + const _randomServer = servers[_randomServerId]; + const _serverDetails = _randomServer.split(':'); + + if (_serverDetails.length === 2) { + randomServer = { + ip: _serverDetails[0], + port: _serverDetails[1], + }; + } + } + + for (let key in shepherd.electrumServers) { + if (shepherd.electrumServers[key].abbr === coin) { + shepherd.electrumCoins[coin] = { + name: key, + abbr: coin, + server: { + ip: randomServer ? randomServer.ip : shepherd.electrumServers[key].address, + port: randomServer ? randomServer.port : shepherd.electrumServers[key].port, + }, + serverList: shepherd.electrumServers[key].serverList ? shepherd.electrumServers[key].serverList : 'none', + txfee: 'calculated' /*shepherd.electrumServers[key].txfee*/, + }; + + shepherd.log(`default ${coin} electrum server ${shepherd.electrumServers[key].address + ':' + shepherd.electrumServers[key].port}`, true); + + if (randomServer) { + shepherd.log(`random ${coin} electrum server ${randomServer.ip + ':' + randomServer.port}`, true); + } else { + shepherd.log(`${coin} doesnt have any backup electrum servers`, true); + } + + return true; + } + } + } + + shepherd.get('/electrum/coins/add', (req, res, next) => { + const result = shepherd.addElectrumCoin(req.query.coin); + + const successObj = { + msg: 'success', + result, + }; + + res.end(JSON.stringify(successObj)); + }); + + shepherd.get('/electrum/coins', (req, res, next) => { + let _electrumCoins = JSON.parse(JSON.stringify(shepherd.electrumCoins)); // deep cloning + + for (let key in _electrumCoins) { + if (shepherd.electrumKeys[key]) { + _electrumCoins[key].pub = shepherd.electrumKeys[key].pub; + } + } + + const successObj = { + msg: 'success', + result: _electrumCoins, + }; + + res.end(JSON.stringify(successObj)); + }); + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/electrum/createtx.js b/routes/shepherd/electrum/createtx.js new file mode 100644 index 0000000..8c9c4d6 --- /dev/null +++ b/routes/shepherd/electrum/createtx.js @@ -0,0 +1,487 @@ +module.exports = (shepherd) => { + // unsigned tx + shepherd.buildUnsignedTx = (sendTo, changeAddress, network, utxo, changeValue, spendValue) => { + let tx = new shepherd.bitcoinJS.TransactionBuilder(shepherd.getNetworkData(network)); + + shepherd.log('buildSignedTx'); + // console.log(`buildSignedTx priv key ${wif}`); + shepherd.log(`buildSignedTx pub key ${changeAddress}`, true); + // console.log('buildSignedTx std tx fee ' + shepherd.electrumServers[network].txfee); + + for (let i = 0; i < utxo.length; i++) { + tx.addInput(utxo[i].txid, utxo[i].vout); + } + + tx.addOutput(sendTo, Number(spendValue)); + + if (changeValue > 0) { + tx.addOutput(changeAddress, Number(changeValue)); + } + + if (network === 'komodo' || + network === 'KMD') { + const _locktime = Math.floor(Date.now() / 1000) - 777; + tx.setLockTime(_locktime); + shepherd.log(`kmd tx locktime set to ${_locktime}`, true); + } + + shepherd.log('buildSignedTx unsigned tx data vin', true); + shepherd.log(tx.tx.ins, true); + shepherd.log('buildSignedTx unsigned tx data vout', true); + shepherd.log(tx.tx.outs, true); + shepherd.log('buildSignedTx unsigned tx data', true); + shepherd.log(tx, true); + + const rawtx = tx.buildIncomplete().toHex(); + + shepherd.log('buildUnsignedTx tx hex', true); + shepherd.log(rawtx, true); + + return rawtx; + } + + // single sig + shepherd.buildSignedTx = (sendTo, changeAddress, wif, network, utxo, changeValue, spendValue) => { + let key = shepherd.bitcoinJS.ECPair.fromWIF(wif, shepherd.getNetworkData(network)); + let tx = new shepherd.bitcoinJS.TransactionBuilder(shepherd.getNetworkData(network)); + + shepherd.log('buildSignedTx'); + // console.log(`buildSignedTx priv key ${wif}`); + shepherd.log(`buildSignedTx pub key ${key.getAddress().toString()}`, true); + // console.log('buildSignedTx std tx fee ' + shepherd.electrumServers[network].txfee); + + for (let i = 0; i < utxo.length; i++) { + tx.addInput(utxo[i].txid, utxo[i].vout); + } + + tx.addOutput(sendTo, Number(spendValue)); + + if (changeValue > 0) { + tx.addOutput(changeAddress, Number(changeValue)); + } + + if (network === 'komodo' || + network === 'KMD') { + const _locktime = Math.floor(Date.now() / 1000) - 777; + tx.setLockTime(_locktime); + shepherd.log(`kmd tx locktime set to ${_locktime}`, true); + } + + shepherd.log('buildSignedTx unsigned tx data vin', true); + shepherd.log(tx.tx.ins, true); + shepherd.log('buildSignedTx unsigned tx data vout', true); + shepherd.log(tx.tx.outs, true); + shepherd.log('buildSignedTx unsigned tx data', true); + shepherd.log(tx, true); + + for (let i = 0; i < utxo.length; i++) { + tx.sign(i, key); + } + + const rawtx = tx.build().toHex(); + + shepherd.log('buildSignedTx signed tx hex', true); + shepherd.log(rawtx, true); + + return rawtx; + } + + shepherd.maxSpendBalance = (utxoList, fee) => { + let maxSpendBalance = 0; + + for (let i = 0; i < utxoList.length; i++) { + maxSpendBalance += Number(utxoList[i].value); + } + + if (fee) { + return Number(maxSpendBalance) - Number(fee); + } else { + return maxSpendBalance; + } + } + + shepherd.get('/electrum/createrawtx', (req, res, next) => { + // txid 64 char + const network = req.query.network || shepherd.findNetworkObj(req.query.coin); + const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls + const outputAddress = req.query.address; + const changeAddress = req.query.change; + let value = Number(req.query.value); + const push = req.query.push; + const fee = shepherd.electrumServers[network].txfee; + let wif = req.query.wif; + + if (req.query.gui) { + wif = shepherd.electrumKeys[req.query.coin].priv; + } + + shepherd.log('electrum createrawtx =>', true); + + ecl.connect(); + shepherd.listunspent(ecl, changeAddress, network, true, true) + .then((utxoList) => { + ecl.close(); + + if (utxoList && + utxoList.length) { + let utxoListFormatted = []; + let totalInterest = 0; + let totalInterestUTXOCount = 0; + let interestClaimThreshold = 200; + let utxoVerified = true; + + for (let i = 0; i < utxoList.length; i++) { + if (network === 'komodo') { + utxoListFormatted.push({ + txid: utxoList[i].txid, + vout: utxoList[i].vout, + value: Number(utxoList[i].amountSats), + interestSats: Number(utxoList[i].interestSats), + verified: utxoList[i].verified ? utxoList[i].verified : false, + }); + } else { + utxoListFormatted.push({ + txid: utxoList[i].txid, + vout: utxoList[i].vout, + value: Number(utxoList[i].amountSats), + verified: utxoList[i].verified ? utxoList[i].verified : false, + }); + } + } + + shepherd.log('electrum listunspent unformatted ==>', true); + shepherd.log(utxoList, true); + + shepherd.log('electrum listunspent formatted ==>', true); + shepherd.log(utxoListFormatted, true); + + const _maxSpendBalance = Number(shepherd.maxSpendBalance(utxoListFormatted)); + let targets = [{ + address: outputAddress, + value: value > _maxSpendBalance ? _maxSpendBalance : value, + }]; + shepherd.log('targets =>', true); + shepherd.log(targets, true); + + const feeRate = 20; // sats/byte + + // default coin selection algo blackjack with fallback to accumulative + // make a first run, calc approx tx fee + // if ins and outs are empty reduce max spend by txfee + let { inputs, outputs, fee } = shepherd.coinSelect(utxoListFormatted, targets, feeRate); + + shepherd.log('coinselect res =>', true); + shepherd.log('coinselect inputs =>', true); + shepherd.log(inputs, true); + shepherd.log('coinselect outputs =>', true); + shepherd.log(outputs, true); + shepherd.log('coinselect calculated fee =>', true); + shepherd.log(fee, true); + + if (!outputs) { + targets[0].value = targets[0].value - fee; + shepherd.log('second run', true); + shepherd.log('coinselect adjusted targets =>', true); + shepherd.log(targets, true); + + const secondRun = shepherd.coinSelect(utxoListFormatted, targets, feeRate); + inputs = secondRun.inputs; + outputs = secondRun.outputs; + fee = secondRun.fee; + + shepherd.log('second run coinselect inputs =>', true); + shepherd.log(inputs, true); + shepherd.log('second run coinselect outputs =>', true); + shepherd.log(outputs, true); + shepherd.log('second run coinselect fee =>', true); + shepherd.log(fee, true); + } + + let _change = 0; + + if (outputs && + outputs.length === 2) { + _change = outputs[1].value; + } + + // check if any outputs are unverified + if (inputs && + inputs.length) { + for (let i = 0; i < inputs.length; i++) { + if (!inputs[i].verified) { + utxoVerified = false; + break; + } + } + + for (let i = 0; i < inputs.length; i++) { + if (Number(inputs[i].interestSats) > interestClaimThreshold) { + totalInterest += Number(inputs[i].interestSats); + totalInterestUTXOCount++; + } + } + } + + const _maxSpend = shepherd.maxSpendBalance(utxoListFormatted); + + if (value > _maxSpend) { + const successObj = { + msg: 'error', + result: `Spend value is too large. Max available amount is ${Number((_maxSpend * 0.00000001.toFixed(8)))}`, + }; + + res.end(JSON.stringify(successObj)); + } else { + shepherd.log(`maxspend ${_maxSpend} (${_maxSpend * 0.00000001})`, true); + shepherd.log(`value ${value}`, true); + shepherd.log(`sendto ${outputAddress} amount ${value} (${value * 0.00000001})`, true); + shepherd.log(`changeto ${changeAddress} amount ${_change} (${_change * 0.00000001})`, true); + + // account for KMD interest + if (network === 'komodo' && + totalInterest > 0) { + // account for extra vout + const _feeOverhead = outputs.length === 1 ? shepherd.estimateTxSize(0, 1) * feeRate : 0; + + shepherd.log(`max interest to claim ${totalInterest} (${totalInterest * 0.00000001})`, true); + shepherd.log(`estimated fee overhead ${_feeOverhead}`, true); + shepherd.log(`current change amount ${_change} (${_change * 0.00000001}), boosted change amount ${_change + (totalInterest - _feeOverhead)} (${(_change + (totalInterest - _feeOverhead)) * 0.00000001})`, true); + + if (_maxSpend === value) { + _change = totalInterest -_change - _feeOverhead; + + if (outputAddress === changeAddress) { + value += _change; + _change = 0; + shepherd.log(`send to self ${outputAddress} = ${changeAddress}`, true); + shepherd.log(`send to self old val ${value}, new val ${value + _change}`, true); + } + } else { + _change = _change + (totalInterest - _feeOverhead); + } + } + + if (!inputs && + !outputs) { + const successObj = { + msg: 'error', + result: 'Can\'t find best fit utxo. Try lower amount.', + }; + + res.end(JSON.stringify(successObj)); + } else { + let vinSum = 0; + + for (let i = 0; i < inputs.length; i++) { + vinSum += inputs[i].value; + } + + const _estimatedFee = vinSum - outputs[0].value - _change; + + shepherd.log(`vin sum ${vinSum} (${vinSum * 0.00000001})`, true); + shepherd.log(`estimatedFee ${_estimatedFee} (${_estimatedFee * 0.00000001})`, true); + + let _rawtx; + + if (req.query.unsigned) { + _rawtx = shepherd.buildUnsignedTx( + outputAddress, + changeAddress, + network, + inputs, + _change, + value + ); + } else { + _rawtx = shepherd.buildSignedTx( + outputAddress, + changeAddress, + wif, + network, + inputs, + _change, + value + ); + } + + if (!push || + push === 'false') { + const successObj = { + msg: 'success', + result: { + utxoSet: inputs, + change: _change, + changeAdjusted: _change, + totalInterest, + // wif, + fee, + value, + outputAddress, + changeAddress, + network, + rawtx: _rawtx, + utxoVerified, + }, + }; + + res.end(JSON.stringify(successObj)); + } else { + const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls + + ecl.connect(); + ecl.blockchainTransactionBroadcast(_rawtx) + .then((txid) => { + ecl.close(); + + if (txid && + txid.indexOf('bad-txns-inputs-spent') > -1) { + const successObj = { + msg: 'error', + result: 'Bad transaction inputs spent', + raw: { + utxoSet: inputs, + change: _change, + changeAdjusted: _change, + totalInterest, + fee, + value, + outputAddress, + changeAddress, + network, + rawtx: _rawtx, + txid, + utxoVerified, + }, + }; + + res.end(JSON.stringify(successObj)); + } else { + if (txid && + txid.length === 64) { + if (txid.indexOf('bad-txns-in-belowout') > -1) { + const successObj = { + msg: 'error', + result: 'Bad transaction inputs spent', + raw: { + utxoSet: inputs, + change: _change, + changeAdjusted: _change, + totalInterest, + fee, + value, + outputAddress, + changeAddress, + network, + rawtx: _rawtx, + txid, + utxoVerified, + }, + }; + + res.end(JSON.stringify(successObj)); + } else { + const successObj = { + msg: 'success', + result: { + utxoSet: inputs, + change: _change, + changeAdjusted: _change, + totalInterest, + fee, + // wif, + value, + outputAddress, + changeAddress, + network, + rawtx: _rawtx, + txid, + utxoVerified, + }, + }; + + res.end(JSON.stringify(successObj)); + } + } else { + if (txid && + txid.indexOf('bad-txns-in-belowout') > -1) { + const successObj = { + msg: 'error', + result: 'Bad transaction inputs spent', + raw: { + utxoSet: inputs, + change: _change, + changeAdjusted: _change, + totalInterest, + fee, + value, + outputAddress, + changeAddress, + network, + rawtx: _rawtx, + txid, + utxoVerified, + }, + }; + + res.end(JSON.stringify(successObj)); + } else { + const successObj = { + msg: 'error', + result: 'Can\'t broadcast transaction', + raw: { + utxoSet: inputs, + change: _change, + changeAdjusted: _change, + totalInterest, + fee, + value, + outputAddress, + changeAddress, + network, + rawtx: _rawtx, + txid, + utxoVerified, + }, + }; + + res.end(JSON.stringify(successObj)); + } + } + } + }); + } + } + } + } else { + const successObj = { + msg: 'error', + result: utxoList, + }; + + res.end(JSON.stringify(successObj)); + } + }); + }); + + shepherd.get('/electrum/pushtx', (req, res, next) => { + const rawtx = req.query.rawtx; + const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[req.query.network].port, shepherd.electrumServers[req.query.network].address, shepherd.electrumServers[req.query.network].proto); // tcp or tls + + ecl.connect(); + ecl.blockchainTransactionBroadcast(rawtx) + .then((json) => { + ecl.close(); + shepherd.log('electrum pushtx ==>', true); + shepherd.log(json, true); + + const successObj = { + msg: 'success', + result: json, + }; + + res.end(JSON.stringify(successObj)); + }); + }); + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/electrum/estimate.js b/routes/shepherd/electrum/estimate.js new file mode 100644 index 0000000..fab2b98 --- /dev/null +++ b/routes/shepherd/electrum/estimate.js @@ -0,0 +1,26 @@ +module.exports = (shepherd) => { + shepherd.get('/electrum/estimatefee', (req, res, next) => { + const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[req.query.network].port, shepherd.electrumServers[req.query.network].address, shepherd.electrumServers[req.query.network].proto); // tcp or tls + + ecl.connect(); + ecl.blockchainEstimatefee(req.query.blocks) + .then((json) => { + ecl.close(); + shepherd.log('electrum estimatefee ==>', true); + + const successObj = { + msg: 'success', + result: json, + }; + + res.end(JSON.stringify(successObj)); + }); + }); + + shepherd.estimateTxSize = (numVins, numOuts) => { + // in x 180 + out x 34 + 10 plus or minus in + return numVins * 180 + numOuts * 34 + 11; + } + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/electrum/interest.js b/routes/shepherd/electrum/interest.js new file mode 100644 index 0000000..8a954e7 --- /dev/null +++ b/routes/shepherd/electrum/interest.js @@ -0,0 +1,37 @@ +module.exports = (shepherd) => { + shepherd.kmdCalcInterest = (locktime, value) => { // value in sats + const timestampDiff = Math.floor(Date.now() / 1000) - locktime - 777; + const hoursPassed = Math.floor(timestampDiff / 3600); + const minutesPassed = Math.floor((timestampDiff - (hoursPassed * 3600)) / 60); + const secondsPassed = timestampDiff - (hoursPassed * 3600) - (minutesPassed * 60); + let timestampDiffMinutes = timestampDiff / 60; + let interest = 0; + + shepherd.log('kmdCalcInterest', true); + shepherd.log(`locktime ${locktime}`, true); + shepherd.log(`minutes converted ${timestampDiffMinutes}`, true); + shepherd.log(`passed ${hoursPassed}h ${minutesPassed}m ${secondsPassed}s`, true); + + // calc interest + if (timestampDiffMinutes >= 60) { + if (timestampDiffMinutes > 365 * 24 * 60) { + timestampDiffMinutes = 365 * 24 * 60; + } + timestampDiffMinutes -= 59; + + shepherd.log(`minutes if statement ${timestampDiffMinutes}`, true); + + // TODO: check if interest is > 5% yr + // calc ytd and 5% for 1 yr + // const hoursInOneYear = 365 * 24; + // const hoursDiff = hoursInOneYear - hoursPassed; + + interest = ((Number(value) * 0.00000001) / 10512000) * timestampDiffMinutes; + shepherd.log(`interest ${interest}`, true); + } + + return interest; + } + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/electrum/keys.js b/routes/shepherd/electrum/keys.js new file mode 100644 index 0000000..ff49d89 --- /dev/null +++ b/routes/shepherd/electrum/keys.js @@ -0,0 +1,178 @@ +const sha256 = require('js-sha256'); + +module.exports = (shepherd) => { + shepherd.seedToWif = (seed, network, iguana) => { + let bytes; + + if (process.argv.indexOf('spvold=true') > -1) { + bytes = shepherd.sha256(seed, { asBytes: true }); + } else { + const hash = sha256.create().update(seed); + bytes = hash.array(); + } + + if (iguana) { + bytes[0] &= 248; + bytes[31] &= 127; + bytes[31] |= 64; + } + + const toHexString = (byteArray) => { + return Array.from(byteArray, (byte) => { + return ('0' + (byte & 0xFF).toString(16)).slice(-2); + }).join(''); + } + + const hex = toHexString(bytes); + + const key = new shepherd.CoinKey(new Buffer(hex, 'hex'), { + private: shepherd.getNetworkData(network).wif, + public: shepherd.getNetworkData(network).pubKeyHash, + }); + + key.compressed = true; + + // shepherd.log(`seed: ${seed}`, true); + // shepherd.log(`network ${network}`, true); + // shepherd.log(`seedtowif priv key ${key.privateWif}`, true); + // shepherd.log(`seedtowif pub key ${key.publicAddress}`, true); + + return { + priv: key.privateWif, + pub: key.publicAddress, + }; + } + + shepherd.get('/electrum/seedtowif', (req, res, next) => { + const keys = shepherd.seedToWif(req.query.seed, req.query.network, req.query.iguana); + + const successObj = { + msg: 'success', + result: { + keys, + }, + }; + + res.end(JSON.stringify(successObj)); + }); + + shepherd.post('/electrum/keys', (req, res, next) => { + let _matchingKeyPairs = 0; + let _electrumKeys = {}; + + for (let key in shepherd.electrumServers) { + const _abbr = shepherd.electrumServers[key].abbr; + const { priv, pub } = shepherd.seedToWif(req.body.seed, shepherd.findNetworkObj(_abbr), req.body.iguana); + + if (shepherd.electrumKeys[_abbr].pub === pub && + shepherd.electrumKeys[_abbr].priv === priv) { + _matchingKeyPairs++; + } + } + + if (req.body.active) { + _electrumKeys = JSON.parse(JSON.stringify(shepherd.electrumKeys)); + + for (let key in _electrumKeys) { + if (!shepherd.electrumCoins[key]) { + delete _electrumKeys[key]; + } + } + } else { + _electrumKeys = shepherd.electrumKeys; + } + + // shepherd.log(JSON.stringify(_electrumKeys, null, '\t'), true); + + const successObj = { + msg: 'success', + result: _matchingKeyPairs === Object.keys(shepherd.electrumKeys).length ? _electrumKeys : false, + }; + + res.end(JSON.stringify(successObj)); + }); + + shepherd.post('/electrum/seed/bip39/match', (req, res, next) => { + const bip39 = require('bip39'); // npm i -S bip39 + const crypto = require('crypto'); + const seed = bip39.mnemonicToSeed(req.body.seed); + const hdMaster = shepherd.bitcoinJS.HDNode.fromSeedBuffer(seed, shepherd.electrumJSNetworks.komodo); // seed from above + const matchPattern = req.body.match; + const _defaultAddressDepth = req.body.addressdepth; + const _defaultAccountCount = req.body.accounts; + let _addresses = []; + let _matchingKey; + + for (let i = 0; i < _defaultAccountCount; i++) { + for (let j = 0; j < 1; j++) { + for (let k = 0; k < _defaultAddressDepth; k++) { + const _key = hdMaster.derivePath(`m/44'/141'/${i}'/${j}/${k}`); + + if (_key.keyPair.getAddress() === matchPattern) { + _matchingKey = { + pub: _key.keyPair.getAddress(), + priv: _key.keyPair.toWIF(), + }; + } + /*_addresses.push({ + pub: _key.keyPair.getAddress(), + priv: _key.keyPair.toWIF(), + });*/ + } + } + } + + const successObj = { + msg: 'success', + result: _matchingKey ? _matchingKey : 'address is not found', + }; + + res.end(JSON.stringify(successObj)); + }); + + // spv v2 + /*shepherd.get('/electrum/bip39/seed', (req, res, next) => { + const _seed = ''; + // TODO + const bip39 = require('bip39'); // npm i -S bip39 + const crypto = require('crypto'); + + // what you describe as 'seed' + const randomBytes = crypto.randomBytes(16); // 128 bits is enough + + // your 12 word phrase + const mnemonic = bip39.entropyToMnemonic(randomBytes.toString('hex')); + + // what is accurately described as the wallet seed + // var seed = bip39.mnemonicToSeed(mnemonic) // you'll use this in #3 below + const seed = bip39.mnemonicToSeed(_seed); + + console.log(seed); + + const successObj = { + msg: 'success', + result: { + servers: shepherd.electrumServers, + }, + }; + + res.end(JSON.stringify(successObj)); + + console.log(shepherd.bitcoinJS.networks.komodo); + const hdMaster = shepherd.bitcoinJS.HDNode.fromSeedBuffer(seed, shepherd.electrumJSNetworks.komodo); // seed from above + + const key1 = hdMaster.derivePath("m/44'/141'/0'/0/0"); + const key2 = hdMaster.derivePath('m/1'); + console.log(hdMaster); + + console.log(key1.keyPair.toWIF()); + console.log(key1.keyPair.getAddress()); + console.log(key2.keyPair.toWIF()); + + const hdnode = shepherd.bitcoinJS.HDNode.fromSeedBuffer(seed, shepherd.electrumJSNetworks.komodo).deriveHardened(0).derive(0).derive(1); + console.log(`address: ${hdnode.getAddress()}`); + console.log(`priv (WIF): ${hdnode.keyPair.toWIF()}`); + });*/ + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/electrum/listunspent.js b/routes/shepherd/electrum/listunspent.js new file mode 100644 index 0000000..b0a645b --- /dev/null +++ b/routes/shepherd/electrum/listunspent.js @@ -0,0 +1,195 @@ +module.exports = (shepherd) => { + shepherd.listunspent = (ecl, address, network, full, verify) => { + let _atLeastOneDecodeTxFailed = false; + + if (full) { + return new shepherd.Promise((resolve, reject) => { + ecl.connect(); + ecl.blockchainAddressListunspent(address) + .then((_utxoJSON) => { + if (_utxoJSON && + _utxoJSON.length) { + let formattedUtxoList = []; + let _utxo = []; + + ecl.blockchainNumblocksSubscribe() + .then((currentHeight) => { + if (currentHeight && + Number(currentHeight) > 0) { + // filter out unconfirmed utxos + for (let i = 0; i < _utxoJSON.length; i++) { + if (Number(currentHeight) - Number(_utxoJSON[i].height) !== 0) { + _utxo.push(_utxoJSON[i]); + } + } + + if (!_utxo.length) { // no confirmed utxo + resolve('no valid utxo'); + } else { + shepherd.Promise.all(_utxo.map((_utxoItem, index) => { + return new shepherd.Promise((resolve, reject) => { + ecl.blockchainTransactionGet(_utxoItem['tx_hash']) + .then((_rawtxJSON) => { + shepherd.log('electrum gettransaction ==>', true); + shepherd.log(index + ' | ' + (_rawtxJSON.length - 1), true); + shepherd.log(_rawtxJSON, true); + + // decode tx + const _network = shepherd.getNetworkData(network); + const decodedTx = shepherd.electrumJSTxDecoder(_rawtxJSON, _network); + + shepherd.log('decoded tx =>', true); + shepherd.log(decodedTx, true); + + if (!decodedTx) { + _atLeastOneDecodeTxFailed = true; + resolve('cant decode tx'); + } else { + if (network === 'komodo') { + let interest = 0; + + if (Number(_utxoItem.value) * 0.00000001 >= 10 && + decodedTx.format.locktime > 0) { + interest = shepherd.kmdCalcInterest(decodedTx.format.locktime, _utxoItem.value); + } + + let _resolveObj = { + txid: _utxoItem['tx_hash'], + vout: _utxoItem['tx_pos'], + address, + amount: Number(_utxoItem.value) * 0.00000001, + amountSats: _utxoItem.value, + locktime: decodedTx.format.locktime, + interest: Number(interest.toFixed(8)), + interestSats: Math.floor(interest * 100000000), + confirmations: Number(_utxoItem.height) === 0 ? 0 : currentHeight - _utxoItem.height, + spendable: true, + verified: false, + }; + + // merkle root verification agains another electrum server + if (verify) { + shepherd.verifyMerkleByCoin(shepherd.findCoinName(network), _utxoItem['tx_hash'], _utxoItem.height) + .then((verifyMerkleRes) => { + if (verifyMerkleRes && verifyMerkleRes === shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA) { + verifyMerkleRes = false; + } + + _resolveObj.verified = verifyMerkleRes; + resolve(_resolveObj); + }); + } else { + resolve(_resolveObj); + } + } else { + let _resolveObj = { + txid: _utxoItem['tx_hash'], + vout: _utxoItem['tx_pos'], + address, + amount: Number(_utxoItem.value) * 0.00000001, + amountSats: _utxoItem.value, + confirmations: Number(_utxoItem.height) === 0 ? 0 : currentHeight - _utxoItem.height, + spendable: true, + verified: false, + }; + + // merkle root verification agains another electrum server + if (verify) { + shepherd.verifyMerkleByCoin(shepherd.findCoinName(network), _utxoItem['tx_hash'], _utxoItem.height) + .then((verifyMerkleRes) => { + if (verifyMerkleRes && + verifyMerkleRes === shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA) { + verifyMerkleRes = false; + } + + _resolveObj.verified = verifyMerkleRes; + resolve(_resolveObj); + }); + } else { + resolve(_resolveObj); + } + } + } + }); + }); + })) + .then(promiseResult => { + ecl.close(); + + if (!_atLeastOneDecodeTxFailed) { + shepherd.log(promiseResult, true); + resolve(promiseResult); + } else { + shepherd.log('listunspent error, cant decode tx(s)', true); + resolve('decode error'); + } + }); + } + } else { + resolve('cant get current height'); + } + }); + } else { + ecl.close(); + resolve(shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA); + } + }); + }); + } else { + return new shepherd.Promise((resolve, reject) => { + ecl.connect(); + ecl.blockchainAddressListunspent(address) + .then((json) => { + ecl.close(); + + if (json && + json.length) { + resolve(json); + } else { + resolve(shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA); + } + }); + }); + } + } + + shepherd.get('/electrum/listunspent', (req, res, next) => { + const network = req.query.network || shepherd.findNetworkObj(req.query.coin); + const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls + + if (req.query.full && + req.query.full === 'true') { + shepherd.listunspent( + ecl, + req.query.address, + network, + true, + req.query.verify + ).then((listunspent) => { + shepherd.log('electrum listunspent ==>', true); + + const successObj = { + msg: 'success', + result: listunspent, + }; + + res.end(JSON.stringify(successObj)); + }); + } else { + shepherd.listunspent(ecl, req.query.address, network) + .then((listunspent) => { + ecl.close(); + shepherd.log('electrum listunspent ==>', true); + + const successObj = { + msg: 'success', + result: listunspent, + }; + + res.end(JSON.stringify(successObj)); + }); + } + }); + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/electrum/merkle.js b/routes/shepherd/electrum/merkle.js new file mode 100644 index 0000000..b3fbe03 --- /dev/null +++ b/routes/shepherd/electrum/merkle.js @@ -0,0 +1,145 @@ +module.exports = (shepherd) => { + // get merkle root + shepherd.getMerkleRoot = (txid, proof, pos) => { + const reverse = require('buffer-reverse'); + let hash = txid; + let serialized; + const _sha256 = (data) => { + return shepherd.crypto.createHash('sha256').update(data).digest(); + } + + shepherd.log(`getMerkleRoot txid ${txid}`, true); + shepherd.log(`getMerkleRoot pos ${pos}`, true); + shepherd.log('getMerkleRoot proof', true); + shepherd.log(`getMerkleRoot ${proof}`, true); + + for (i = 0; i < proof.length; i++) { + const _hashBuff = new Buffer(hash, 'hex'); + const _proofBuff = new Buffer(proof[i], 'hex'); + + if ((pos & 1) == 0) { + serialized = Buffer.concat([reverse(_hashBuff), reverse(_proofBuff)]); + } else { + serialized = Buffer.concat([reverse(_proofBuff), reverse(_hashBuff)]); + } + + hash = reverse(_sha256(_sha256(serialized))).toString('hex'); + pos /= 2; + } + + return hash; + } + + shepherd.verifyMerkle = (txid, height, serverList, mainServer) => { + // select random server + const getRandomIntInclusive = (min, max) => { + min = Math.ceil(min); + max = Math.floor(max); + + return Math.floor(Math.random() * (max - min + 1)) + min; // the maximum is inclusive and the minimum is inclusive + } + + const _rnd = getRandomIntInclusive(0, serverList.length - 1); + const randomServer = serverList[_rnd]; + const _randomServer = randomServer.split(':'); + const _mainServer = mainServer.split(':'); + + let ecl = new shepherd.electrumJSCore(_mainServer[1], _mainServer[0], 'tcp'); // tcp or tls + + return new shepherd.Promise((resolve, reject) => { + shepherd.log(`main server: ${mainServer}`, true); + shepherd.log(`verification server: ${randomServer}`, true); + + ecl.connect(); + ecl.blockchainTransactionGetMerkle(txid, height) + .then((merkleData) => { + if (merkleData && + merkleData.merkle && + merkleData.pos) { + shepherd.log('electrum getmerkle =>', true); + shepherd.log(merkleData, true); + ecl.close(); + + const _res = shepherd.getMerkleRoot(txid, merkleData.merkle, merkleData.pos); + shepherd.log(_res, true); + + ecl = new shepherd.electrumJSCore(_randomServer[1], _randomServer[0], 'tcp'); + ecl.connect(); + + ecl.blockchainBlockGetHeader(height) + .then((blockInfo) => { + if (blockInfo && + blockInfo['merkle_root']) { + ecl.close(); + shepherd.log('blockinfo =>', true); + shepherd.log(blockInfo, true); + shepherd.log(blockInfo['merkle_root'], true); + + if (blockInfo && + blockInfo['merkle_root']) { + if (_res === blockInfo['merkle_root']) { + resolve(true); + } else { + resolve(false); + } + } else { + resolve(shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA); + } + } else { + resolve(shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA); + } + }); + } else { + resolve(shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA); + } + }); + }); + } + + shepherd.verifyMerkleByCoin = (coin, txid, height) => { + const _serverList = shepherd.electrumCoins[coin].serverList; + + shepherd.log(`verifyMerkleByCoin`, true); + shepherd.log(shepherd.electrumCoins[coin].server, true); + shepherd.log(shepherd.electrumCoins[coin].serverList, true); + + return new shepherd.Promise((resolve, reject) => { + if (_serverList !== 'none') { + let _filteredServerList = []; + + for (let i = 0; i < _serverList.length; i++) { + if (_serverList[i] !== shepherd.electrumCoins[coin].server.ip + ':' + shepherd.electrumCoins[coin].server.port) { + _filteredServerList.push(_serverList[i]); + } + } + + shepherd.verifyMerkle( + txid, + height, + _filteredServerList, + shepherd.electrumCoins[coin].server.ip + ':' + shepherd.electrumCoins[coin].server.port + ).then((proof) => { + resolve(proof); + }); + } else { + resolve(false); + } + }); + } + + shepherd.get('/electrum/merkle/verify', (req, res, next) => { + shepherd.verifyMerkleByCoin(req.query.coin, req.query.txid, req.query.height) + .then((verifyMerkleRes) => { + const successObj = { + msg: 'success', + result: { + merkleProof: verifyMerkleRes, + }, + }; + + res.end(JSON.stringify(successObj)); + }); + }); + + return shepherd; +}; diff --git a/routes/shepherd/electrum/network.js b/routes/shepherd/electrum/network.js new file mode 100644 index 0000000..ad37672 --- /dev/null +++ b/routes/shepherd/electrum/network.js @@ -0,0 +1,143 @@ +module.exports = (shepherd) => { + shepherd.getNetworkData = (network) => { + const coin = shepherd.findNetworkObj(network) || shepherd.findNetworkObj(network.toUpperCase()) || shepherd.findNetworkObj(network.toLowerCase()); + const coinUC = coin ? coin.toUpperCase() : null; + + if (coin === 'SUPERNET' || + coin === 'REVS' || + coin === 'SUPERNET' || + coin === 'PANGEA' || + coin === 'DEX' || + coin === 'JUMBLR' || + coin === 'BET' || + coin === 'CRYPTO' || + coin === 'COQUI' || + coin === 'HODL' || + coin === 'SHARK' || + coin === 'MSHARK' || + coin === 'BOTS' || + coin === 'MGW' || + coin === 'MVP' || + coin === 'KV' || + coin === 'CEAL' || + coin === 'MESH' || + coin === 'WLC' || + coin === 'MNZ' || + coinUC === 'SUPERNET' || + coinUC === 'REVS' || + coinUC === 'SUPERNET' || + coinUC === 'PANGEA' || + coinUC === 'DEX' || + coinUC === 'JUMBLR' || + coinUC === 'BET' || + coinUC === 'CRYPTO' || + coinUC === 'COQUI' || + coinUC === 'HODL' || + coinUC === 'SHARK' || + coinUC === 'MSHARK' || + coinUC === 'BOTS' || + coinUC === 'MGW' || + coinUC === 'MVP' || + coinUC === 'KV' || + coinUC === 'CEAL' || + coinUC === 'MESH' || + coinUC === 'WLC' || + coinUC === 'MNZ') { + return shepherd.electrumJSNetworks.komodo; + } else { + return shepherd.electrumJSNetworks[network]; + } + } + + shepherd.findNetworkObj = (coin) => { + for (let key in shepherd.electrumServers) { + if (shepherd.electrumServers[key].abbr === coin) { + return key; + } + } + } + + shepherd.get('/electrum/servers', (req, res, next) => { + if (req.query.abbr) { + let _electrumServers = {}; + + for (let key in shepherd.electrumServers) { + _electrumServers[shepherd.electrumServers[key].abbr] = shepherd.electrumServers[key]; + } + + const successObj = { + msg: 'success', + result: { + servers: _electrumServers, + }, + }; + + res.end(JSON.stringify(successObj)); + } else { + const successObj = { + msg: 'success', + result: { + servers: shepherd.electrumServers, + }, + }; + + res.end(JSON.stringify(successObj)); + } + }); + + shepherd.get('/electrum/coins/server/set', (req, res, next) => { + shepherd.electrumCoins[req.query.coin].server = { + ip: req.query.address, + port: req.query.port, + }; + + for (let key in shepherd.electrumServers) { + if (shepherd.electrumServers[key].abbr === req.query.coin) { // a bit risky + shepherd.electrumServers[key].address = req.query.address; + shepherd.electrumServers[key].port = req.query.port; + break; + } + } + + shepherd.log(JSON.stringify(shepherd.electrumCoins[req.query.coin], null, '\t'), true); + + const successObj = { + msg: 'success', + result: true, + }; + + res.end(JSON.stringify(successObj)); + }); + + shepherd.get('/electrum/servers/test', (req, res, next) => { + const ecl = new shepherd.electrumJSCore(req.query.port, req.query.address, 'tcp'); // tcp or tls + + ecl.connect(); + ecl.serverVersion() + .then((serverData) => { + ecl.close(); + shepherd.log('serverData', true); + shepherd.log(serverData, true); + + if (serverData && + typeof serverData === 'string' && + serverData.indexOf('Electrum') > -1) { + const successObj = { + msg: 'success', + result: true, + }; + + res.end(JSON.stringify(successObj)); + } else { + const successObj = { + msg: 'error', + result: false, + }; + + res.end(JSON.stringify(successObj)); + } + }); + }); + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/electrum/transactions.js b/routes/shepherd/electrum/transactions.js new file mode 100644 index 0000000..df5725f --- /dev/null +++ b/routes/shepherd/electrum/transactions.js @@ -0,0 +1,397 @@ +module.exports = (shepherd) => { + shepherd.sortTransactions = (transactions) => { + return transactions.sort((b, a) => { + if (a.height < b.height) { + return -1; + } + + if (a.height > b.height) { + return 1; + } + + return 0; + }); + } + + shepherd.get('/electrum/listtransactions', (req, res, next) => { + const network = req.query.network || shepherd.findNetworkObj(req.query.coin); + const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls + + shepherd.log('electrum listtransactions ==>', true); + + if (!req.query.full) { + ecl.connect(); + ecl.blockchainAddressGetHistory(req.query.address) + .then((json) => { + ecl.close(); + shepherd.log(json, true); + + json = shepherd.sortTransactions(json); + + const successObj = { + msg: 'success', + result: json, + }; + + res.end(JSON.stringify(successObj)); + }); + } else { + // !expensive call! + // TODO: limit e.g. 1-10, 10-20 etc + const MAX_TX = req.query.maxlength || 10; + ecl.connect(); + + ecl.blockchainNumblocksSubscribe() + .then((currentHeight) => { + if (currentHeight && + Number(currentHeight) > 0) { + ecl.blockchainAddressGetHistory(req.query.address) + .then((json) => { + if (json && + json.length) { + json = shepherd.sortTransactions(json); + json = json.length > MAX_TX ? json.slice(0, MAX_TX) : json; + let _rawtx = []; + + shepherd.log(json.length, true); + + shepherd.Promise.all(json.map((transaction, index) => { + return new shepherd.Promise((resolve, reject) => { + ecl.blockchainBlockGetHeader(transaction.height) + .then((blockInfo) => { + if (blockInfo && + blockInfo.timestamp) { + ecl.blockchainTransactionGet(transaction['tx_hash']) + .then((_rawtxJSON) => { + shepherd.log('electrum gettransaction ==>', true); + shepherd.log((index + ' | ' + (_rawtxJSON.length - 1)), true); + shepherd.log(_rawtxJSON, true); + + // decode tx + const _network = shepherd.getNetworkData(network); + const decodedTx = shepherd.electrumJSTxDecoder(_rawtxJSON, _network); + + let txInputs = []; + + shepherd.log('decodedtx =>', true); + shepherd.log(decodedTx.outputs, true); + + if (decodedTx && + decodedTx.inputs) { + shepherd.Promise.all(decodedTx.inputs.map((_decodedInput, index) => { + return new shepherd.Promise((_resolve, _reject) => { + if (_decodedInput.txid !== '0000000000000000000000000000000000000000000000000000000000000000') { + ecl.blockchainTransactionGet(_decodedInput.txid) + .then((rawInput) => { + const decodedVinVout = shepherd.electrumJSTxDecoder(rawInput, _network); + + shepherd.log('electrum raw input tx ==>', true); + + if (decodedVinVout) { + shepherd.log(decodedVinVout.outputs[_decodedInput.n], true); + txInputs.push(decodedVinVout.outputs[_decodedInput.n]); + _resolve(true); + } else { + _resolve(true); + } + }); + } else { + _resolve(true); + } + }); + })) + .then(promiseResult => { + const _parsedTx = { + network: decodedTx.network, + format: decodedTx.format, + inputs: txInputs, + outputs: decodedTx.outputs, + height: transaction.height, + timestamp: Number(transaction.height) === 0 ? Math.floor(Date.now() / 1000) : blockInfo.timestamp, + confirmations: Number(transaction.height) === 0 ? 0 : currentHeight - transaction.height, + }; + + const formattedTx = shepherd.parseTransactionAddresses(_parsedTx, req.query.address, network); + + if (formattedTx.type) { + formattedTx.height = transaction.height; + formattedTx.blocktime = blockInfo.timestamp; + formattedTx.timereceived = blockInfo.timereceived; + formattedTx.hex = _rawtxJSON; + formattedTx.inputs = decodedTx.inputs; + formattedTx.outputs = decodedTx.outputs; + formattedTx.locktime = decodedTx.format.locktime; + _rawtx.push(formattedTx); + } else { + formattedTx[0].height = transaction.height; + formattedTx[0].blocktime = blockInfo.timestamp; + formattedTx[0].timereceived = blockInfo.timereceived; + formattedTx[0].hex = _rawtxJSON; + formattedTx[0].inputs = decodedTx.inputs; + formattedTx[0].outputs = decodedTx.outputs; + formattedTx[0].locktime = decodedTx.format.locktime; + formattedTx[1].height = transaction.height; + formattedTx[1].blocktime = blockInfo.timestamp; + formattedTx[1].timereceived = blockInfo.timereceived; + formattedTx[1].hex = _rawtxJSON; + formattedTx[1].inputs = decodedTx.inputs; + formattedTx[1].outputs = decodedTx.outputs; + formattedTx[1].locktime = decodedTx.format.locktime; + _rawtx.push(formattedTx[0]); + _rawtx.push(formattedTx[1]); + } + resolve(true); + }); + } else { + const _parsedTx = { + network: decodedTx.network, + format: 'cant parse', + inputs: 'cant parse', + outputs: 'cant parse', + height: transaction.height, + timestamp: Number(transaction.height) === 0 ? Math.floor(Date.now() / 1000) : blockInfo.timestamp, + confirmations: Number(transaction.height) === 0 ? 0 : currentHeight - transaction.height, + }; + + const formattedTx = shepherd.parseTransactionAddresses(_parsedTx, req.query.address, network); + _rawtx.push(formattedTx); + resolve(true); + } + }); + } else { + const _parsedTx = { + network: 'cant parse', + format: 'cant parse', + inputs: 'cant parse', + outputs: 'cant parse', + height: transaction.height, + timestamp: 'cant get block info', + confirmations: Number(transaction.height) === 0 ? 0 : currentHeight - transaction.height, + }; + const formattedTx = shepherd.parseTransactionAddresses(_parsedTx, req.query.address, network); + _rawtx.push(formattedTx); + resolve(true); + } + }); + }); + })) + .then(promiseResult => { + ecl.close(); + + const successObj = { + msg: 'success', + result: _rawtx, + }; + + res.end(JSON.stringify(successObj)); + }); + } else { + const successObj = { + msg: 'success', + result: [], + }; + + res.end(JSON.stringify(successObj)); + } + }); + } else { + const successObj = { + msg: 'error', + result: 'cant get current height', + }; + + res.end(JSON.stringify(successObj)); + } + }); + } + }); + + shepherd.get('/electrum/gettransaction', (req, res, next) => { + const network = req.query.network || shepherd.findNetworkObj(req.query.coin); + const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls + + shepherd.log('electrum gettransaction =>', true); + + ecl.connect(); + ecl.blockchainTransactionGet(req.query.txid) + .then((json) => { + ecl.close(); + shepherd.log(json, true); + + const successObj = { + msg: 'success', + result: json, + }; + + res.end(JSON.stringify(successObj)); + }); + }); + + shepherd.parseTransactionAddresses = (tx, targetAddress, network) => { + // TODO: - sum vins / sum vouts to the same address + // - multi vin multi vout + // - detect change address + let result = []; + let _parse = { + inputs: {}, + outputs: {}, + }; + let _sum = { + inputs: 0, + outputs: 0, + }; + let _total = { + inputs: 0, + outputs: 0, + }; + + shepherd.log('parseTransactionAddresses result ==>', true); + + if (tx.format === 'cant parse') { + return { + type: 'unknown', + amount: 'unknown', + address: targetAddress, + timestamp: tx.timestamp, + txid: tx.format.txid, + confirmations: tx.confirmations, + } + } + + for (let key in _parse) { + if (!tx[key].length) { + _parse[key] = []; + _parse[key].push(tx[key]); + } else { + _parse[key] = tx[key]; + } + + for (let i = 0; i < _parse[key].length; i++) { + shepherd.log(`key ==>`, true); + shepherd.log(_parse[key][i], true); + shepherd.log(Number(_parse[key][i].value), true); + + _total[key] += Number(_parse[key][i].value); + + if (_parse[key][i].scriptPubKey && + _parse[key][i].scriptPubKey.addresses && + _parse[key][i].scriptPubKey.addresses[0] === targetAddress && + _parse[key][i].value) { + _sum[key] += Number(_parse[key][i].value); + } + } + } + + if (_sum.inputs > 0 && + _sum.outputs > 0) { + // vin + change, break into two tx + result = [{ // reorder since tx sort by default is from newest to oldest + type: 'sent', + amount: Number(_sum.inputs.toFixed(8)), + address: targetAddress, + timestamp: tx.timestamp, + txid: tx.format.txid, + confirmations: tx.confirmations, + }, { + type: 'received', + amount: Number(_sum.outputs.toFixed(8)), + address: targetAddress, + timestamp: tx.timestamp, + txid: tx.format.txid, + confirmations: tx.confirmations, + }]; + + if (network === 'komodo') { // calc claimed interest amount + const vinVoutDiff = _total.inputs - _total.outputs; + + if (vinVoutDiff < 0) { + result[1].interest = Number(vinVoutDiff.toFixed(8)); + } + } + } else if (_sum.inputs === 0 && _sum.outputs > 0) { + result = { + type: 'received', + amount: Number(_sum.outputs.toFixed(8)), + address: targetAddress, + timestamp: tx.timestamp, + txid: tx.format.txid, + confirmations: tx.confirmations, + }; + } else if (_sum.inputs > 0 && _sum.outputs === 0) { + result = { + type: 'sent', + amount: Number(_sum.inputs.toFixed(8)), + address: targetAddress, + timestamp: tx.timestamp, + txid: tx.format.txid, + confirmations: tx.confirmations, + }; + } else { + // (?) + result = { + type: 'other', + amount: 'unknown', + address: targetAddress, + timestamp: tx.timestamp, + txid: tx.format.txid, + confirmations: tx.confirmations, + }; + } + + shepherd.log(_sum, true); + shepherd.log(result, true); + + return result; + } + + shepherd.get('/electrum/decoderawtx', (req, res, next) => { + const _network = shepherd.getNetworkData(req.query.network); + const _rawtx = req.query.rawtx; + // const _rawtx = '0100000001dd6d064f5665f8454293ecaa9dbb55accf4f7e443d35f3b5ab7760f54b6c15fe000000006a473044022056355585a4a501ec9afc96aa5df124cf29ad3ac6454b47cd07cd7d89ec95ec2b022074c4604ee349d30e5336f210598e4dc576bf16ebeb67eeac3f4e82f56e930fee012103b90ba01af308757054e0484bb578765d5df59c4a57adbb94e2419df5e7232a63feffffff0289fc923b000000001976a91424af38fcb13bbc171b0b42bb017244a53b6bb2fa88ac20a10700000000001976a9142f4c0f91fc06ac228c120aee41741d0d3909683288ac49258b58'; + const decodedTx = shepherd.electrumJSTxDecoder(_rawtx, _network); + + shepherd.log('electrum decoderawtx input tx ==>', true); + + if (req.query.parseonly || + decodedTx.inputs[0].txid === '0000000000000000000000000000000000000000000000000000000000000000') { + const successObj = { + msg: 'success', + result: { + network: decodedTx.network, + format: decodedTx.format, + inputs: decodedTx.inputs, + outputs: decodedTx.outputs, + }, + }; + + shepherd.log(successObj.result, true); + + res.end(JSON.stringify(successObj)); + } else { + const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[req.query.network].port, shepherd.electrumServers[req.query.network].address, shepherd.electrumServers[req.query.network].proto); // tcp or tls + + ecl.connect(); + ecl.blockchainTransactionGet(decodedTx.inputs[0].txid) + .then((json) => { + ecl.close(); + shepherd.log(json, true); + + const decodedVin = shepherd.electrumJSTxDecoder(json, _network); + + const successObj = { + msg: 'success', + result: { + network: decodedTx.network, + format: decodedTx.format, + inputs: decodedVin.outputs[decodedTx.inputs[0].n], + outputs: decodedTx.outputs, + }, + }; + + res.end(JSON.stringify(successObj)); + }); + } + }); + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/init.js b/routes/shepherd/init.js new file mode 100644 index 0000000..1746c40 --- /dev/null +++ b/routes/shepherd/init.js @@ -0,0 +1,71 @@ +const fs = require('fs-extra'); +const path = require('path'); +let _foldersInitRan = false; + +module.exports = (shepherd) => { + shepherd.readVersionFile = () => { + // read app version + const rootLocation = path.join(__dirname, '../../'); + const localVersionFile = fs.readFileSync(`${rootLocation}version`, 'utf8'); + + return localVersionFile; + } + + shepherd.createAgamaDirs = () => { + if (!_foldersInitRan) { + const rootLocation = path.join(__dirname, '../../'); + + fs.readdir(rootLocation, (err, items) => { + for (let i = 0; i < items.length; i++) { + if (items[i].substr(0, 3) === 'gen') { + console.log(items[i]); + fs.unlinkSync(rootLocation + items[i]); + } + } + }); + + if (!fs.existsSync(shepherd.agamaDir)) { + fs.mkdirSync(shepherd.agamaDir); + + if (fs.existsSync(shepherd.agamaDir)) { + shepherd.log(`created agama folder at ${shepherd.agamaDir}`); + shepherd.writeLog(`created agama folder at ${shepherd.agamaDir}`); + } + } else { + shepherd.log('agama folder already exists'); + } + + if (!fs.existsSync(`${shepherd.agamaDir}/shepherd`)) { + fs.mkdirSync(`${shepherd.agamaDir}/shepherd`); + + if (fs.existsSync(`${shepherd.agamaDir}/shepherd`)) { + shepherd.log(`created shepherd folder at ${shepherd.agamaDir}/shepherd`); + shepherd.writeLog(`create shepherd folder at ${shepherd.agamaDir}/shepherd`); + } + } else { + shepherd.log('agama/shepherd folder already exists'); + } + + if (!fs.existsSync(`${shepherd.agamaDir}/shepherd/pin`)) { + fs.mkdirSync(`${shepherd.agamaDir}/shepherd/pin`); + + if (fs.existsSync(`${shepherd.agamaDir}/shepherd/pin`)) { + shepherd.log(`created pin folder at ${shepherd.agamaDir}/shepherd/pin`); + shepherd.writeLog(`create pin folder at ${shepherd.agamaDir}/shepherd/pin`); + } + } else { + shepherd.log('shepherd/pin folder already exists'); + } + + if (!fs.existsSync(shepherd.zcashParamsDir)) { + fs.mkdirSync(shepherd.zcashParamsDir); + } else { + shepherd.log('zcashparams folder already exists'); + } + + _foldersInitRan = true; + } + } + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/kickstart.js b/routes/shepherd/kickstart.js new file mode 100644 index 0000000..99d26e5 --- /dev/null +++ b/routes/shepherd/kickstart.js @@ -0,0 +1,45 @@ +const fs = require('fs-extra'); +const path = require('path'); + +module.exports = (shepherd) => { + /* + * type: GET + * params: coin, type + */ + shepherd.get('/kick', (req, res, next) => { + const _coin = req.query.coin; + const _keepWallet = req.query.keepwallet; + + if (!_coin) { + const errorObj = { + msg: 'error', + result: 'no coin name provided', + }; + + res.end(JSON.stringify(errorObj)); + } else { + const _location = path.join(_coin === 'KMD' ? shepherd.komodoDir : `${shepherd.komodoDir}/${_coin}`); + + if (fs.existsSync(_location)) { + const items = fs.readdirSync(_location); + + for (let i = 0; i < items.length; i++) { + if (items[i].indexOf('wallet.dat') === -1) { + fs.removeSync(`${_location}/${items[i]}`); + } else if (!_keepWallet) { + fs.removeSync(`${_location}/${items[i]}`); + } + } + } + + const successObj = { + msg: 'success', + result: `${_coin} native is kicked`, + }; + + res.end(JSON.stringify(successObj)); + } + }); + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/log.js b/routes/shepherd/log.js new file mode 100644 index 0000000..9bf8472 --- /dev/null +++ b/routes/shepherd/log.js @@ -0,0 +1,141 @@ +module.exports = (shepherd) => { + shepherd.log = (msg, isSpvOut) => { + if (shepherd.appConfig.dev || + shepherd.appConfig.debug) { + console.log(msg); + } + + if (!isSpvOut) { + shepherd.appRuntimeLog.push({ + time: Date.now(), + msg: msg, + }); + } else { + shepherd.appRuntimeSPVLog.push({ + time: Date.now(), + msg: msg, + }); + } + } + + shepherd.writeLog = (data) => { + const logLocation = `${shepherd.agamaDir}/shepherd`; + const timeFormatted = new Date(Date.now()).toLocaleString('en-US', { hour12: false }); + + if (shepherd.appConfig.debug) { + if (shepherd.fs.existsSync(`${logLocation}/agamalog.txt`)) { + shepherd.fs.appendFile(`${logLocation}/agamalog.txt`, `${timeFormatted} ${data}\r\n`, (err) => { + if (err) { + shepherd.log('error writing log file'); + } + }); + } else { + shepherd.fs.writeFile(`${logLocation}/agamalog.txt`, `${timeFormatted} ${data}\r\n`, (err) => { + if (err) { + shepherd.log('error writing log file'); + } + }); + } + } + } + + shepherd.get('/log/runtime', (req, res, next) => { + const successObj = { + msg: 'success', + result: req.query.spv && req.query.spv === 'true' ? shepherd.appRuntimeSPVLog : shepherd.appRuntimeLog, + }; + + res.end(JSON.stringify(successObj)); + }); + + shepherd.getAppRuntimeLog = () => { + return new shepherd.Promise((resolve, reject) => { + resolve(shepherd.appRuntimeLog); + }); + }; + + /* + * type: POST + * params: payload + */ + shepherd.post('/guilog', (req, res, next) => { + const logLocation = `${shepherd.agamaDir}/shepherd`; + + if (!shepherd.guiLog[shepherd.appSessionHash]) { + shepherd.guiLog[shepherd.appSessionHash] = {}; + } + + if (shepherd.guiLog[shepherd.appSessionHash][req.body.timestamp]) { + shepherd.guiLog[shepherd.appSessionHash][req.body.timestamp].status = req.body.status; + shepherd.guiLog[shepherd.appSessionHash][req.body.timestamp].response = req.body.response; + } else { + shepherd.guiLog[shepherd.appSessionHash][req.body.timestamp] = { + function: req.body.function, + type: req.body.type, + url: req.body.url, + payload: req.body.payload, + status: req.body.status, + }; + } + + shepherd.fs.writeFile(`${logLocation}/agamalog.json`, JSON.stringify(shepherd.guiLog), (err) => { + if (err) { + shepherd.writeLog('error writing gui log file'); + } + + const returnObj = { + msg: 'success', + result: 'gui log entry is added', + }; + + res.end(JSON.stringify(returnObj)); + }); + }); + + /* + * type: GET + * params: type + */ + shepherd.get('/getlog', (req, res, next) => { + const logExt = req.query.type === 'txt' ? 'txt' : 'json'; + + if (shepherd.fs.existsSync(`${shepherd.agamaDir}/shepherd/agamalog.${logExt}`)) { + shepherd.fs.readFile(`${shepherd.agamaDir}/shepherd/agamalog.${logExt}`, 'utf8', (err, data) => { + if (err) { + const errorObj = { + msg: 'error', + result: err, + }; + + res.end(JSON.stringify(errorObj)); + } else { + const successObj = { + msg: 'success', + result: data ? JSON.parse(data) : '', + }; + + res.end(JSON.stringify(successObj)); + } + }); + } else { + const errorObj = { + msg: 'error', + result: `agama.${logExt} doesnt exist`, + }; + + res.end(JSON.stringify(errorObj)); + } + }); + + shepherd.printDirs = () => { + shepherd.log(`agama dir: ${shepherd.agamaDir}`); + shepherd.log('--------------------------') + shepherd.log(`komodo dir: ${shepherd.komododBin}`); + shepherd.log(`komodo bin: ${shepherd.komodoDir}`); + shepherd.writeLog(`agama dir: ${shepherd.agamaDir}`); + shepherd.writeLog(`komodo dir: ${shepherd.komododBin}`); + shepherd.writeLog(`komodo bin: ${shepherd.komodoDir}`); + } + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/paths.js b/routes/shepherd/paths.js new file mode 100644 index 0000000..1dafcca --- /dev/null +++ b/routes/shepherd/paths.js @@ -0,0 +1,82 @@ +const path = require('path'); +const fixPath = require('fix-path'); +const os = require('os'); + +module.exports = (shepherd) => { + shepherd.pathsAgama = () => { + switch (os.platform()) { + case 'darwin': + fixPath(); + shepherd.agamaDir = `${process.env.HOME}/Library/Application Support/Agama`; + break; + + case 'linux': + shepherd.agamaDir = `${process.env.HOME}/.agama`; + break; + + case 'win32': + shepherd.agamaDir = `${process.env.APPDATA}/Agama`; + shepherd.agamaDir = path.normalize(shepherd.agamaDir); + break; + } + } + + shepherd.pathsDaemons = () => { + switch (os.platform()) { + case 'darwin': + fixPath(); + shepherd.agamaTestDir = `${process.env.HOME}/Library/Application Support/Agama/test`, + shepherd.komododBin = path.join(__dirname, '../../assets/bin/osx/komodod'), + shepherd.komodocliBin = path.join(__dirname, '../../assets/bin/osx/komodo-cli'), + shepherd.komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.HOME}/Library/Application Support/Komodo`, + shepherd.zcashdBin = '/Applications/ZCashSwingWalletUI.app/Contents/MacOS/zcashd', + shepherd.zcashcliBin = '/Applications/ZCashSwingWalletUI.app/Contents/MacOS/zcash-cli', + shepherd.zcashDir = `${process.env.HOME}/Library/Application Support/Zcash`, + shepherd.zcashParamsDir = `${process.env.HOME}/Library/Application Support/ZcashParams`, + shepherd.chipsBin = path.join(__dirname, '../../assets/bin/osx/chipsd'), + shepherd.chipscliBin = path.join(__dirname, '../../assets/bin/osx/chips-cli'), + shepherd.chipsDir = `${process.env.HOME}/Library/Application Support/Chips`, + shepherd.coindRootDir = path.join(__dirname, '../../assets/bin/osx/dex/coind'), + shepherd.mmBin = path.join(__dirname, '../../node_modules/marketmaker/bin/darwin/x64/marketmaker'); + break; + + case 'linux': + shepherd.agamaTestDir = `${process.env.HOME}/.agama/test`, + shepherd.komododBin = path.join(__dirname, '../../assets/bin/linux64/komodod'), + shepherd.komodocliBin = path.join(__dirname, '../../assets/bin/linux64/komodo-cli'), + shepherd.komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.HOME}/.komodo`, + shepherd.zcashParamsDir = `${process.env.HOME}/.zcash-params`, + shepherd.chipsBin = path.join(__dirname, '../../assets/bin/linux64/chipsd'), + shepherd.chipscliBin = path.join(__dirname, '../../assets/bin/linux64/chips-cli'), + shepherd.chipsDir = `${process.env.HOME}/.chips`, + shepherd.coindRootDir = path.join(__dirname, '../../assets/bin/linux64/dex/coind'), + shepherd.mmBin = path.join(__dirname, '../../node_modules/marketmaker/bin/linux/x64/marketmaker'); + break; + + case 'win32': + shepherd.agamaTestDir = `${process.env.APPDATA}/Agama/test`; + shepherd.agamaTestDir = path.normalize(shepherd.agamaTestDir); + shepherd.komododBin = path.join(__dirname, '../../assets/bin/win64/komodod.exe'), + shepherd.komododBin = path.normalize(shepherd.komododBin), + shepherd.komodocliBin = path.join(__dirname, '../../assets/bin/win64/komodo-cli.exe'), + shepherd.komodocliBin = path.normalize(shepherd.komodocliBin), + shepherd.komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.APPDATA}/Komodo`, + shepherd.komodoDir = path.normalize(shepherd.komodoDir); + shepherd.chipsBin = path.join(__dirname, '../../assets/bin/win64/chipsd.exe'), + shepherd.chipsBin = path.normalize(shepherd.chipsBin), + shepherd.chipscliBin = path.join(__dirname, '../../assets/bin/win64/chips-cli.exe'), + shepherd.chipscliBin = path.normalize(shepherd.chipscliBin), + shepherd.chipsDir = `${process.env.APPDATA}/Chips`, + shepherd.chipsDir = path.normalize(shepherd.chipsDir); + shepherd.zcashParamsDir = `${process.env.APPDATA}/ZcashParams`; + shepherd.zcashParamsDir = path.normalize(shepherd.zcashParamsDir); + shepherd.coindRootDir = path.join(__dirname, '../../assets/bin/osx/dex/coind'); + shepherd.coindRootDir = path.normalize(shepherd.coindRootDir); + shepherd.mmBin = path.join(__dirname, '../../node_modules/marketmaker/bin/win32/x64/marketmaker.exe'); + shepherd.mmBin = path.normalize(shepherd.mmBin); + break; + } + } + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/pin.js b/routes/shepherd/pin.js new file mode 100644 index 0000000..20a47c7 --- /dev/null +++ b/routes/shepherd/pin.js @@ -0,0 +1,146 @@ +module.exports = (shepherd) => { + /* + * type: POST + * params: none + */ + shepherd.post('/encryptkey', (req, res, next) => { + if (req.body.key && + req.body.string && + req.body.pubkey) { + const encryptedString = shepherd.aes256.encrypt(req.body.key, req.body.string); + + // test pin security + // - at least 1 char in upper case + // - at least 1 digit + // - at least one special character + // - min length 8 + + const _pin = req.body.key; + const _pinTest = _pin.match('^(?=.*[A-Z])(?=.*[^<>{}\"/|;:.,~!?@#$%^=&*\\]\\\\()\\[_+]*$)(?=.*[0-9])(?=.*[a-z]).{8}$'); + + shepherd.fs.writeFile(`${shepherd.agamaDir}/shepherd/pin/${req.body.pubkey}.pin`, encryptedString, (err) => { + if (err) { + shepherd.log('error writing pin file'); + } + + const returnObj = { + msg: 'success', + result: encryptedString, + }; + + res.end(JSON.stringify(returnObj)); + }); + } else { + let errorObj = { + msg: 'error', + result: '', + }; + const _paramsList = [ + 'key', + 'string', + 'pubkey' + ]; + let _errorParamsList = []; + + for (let i = 0; i < _paramsList.length; i++) { + if (!req.query[_paramsList[i]]) { + _errorParamsList.push(_paramsList[i]); + } + } + + errorObj.result = `missing param ${_errorParamsList.join(', ')}`; + res.end(JSON.stringify(errorObj)); + } + }); + + shepherd.post('/decryptkey', (req, res, next) => { + if (req.body.key && + req.body.pubkey) { + if (shepherd.fs.existsSync(`${shepherd.agamaDir}/shepherd/pin/${req.body.pubkey}.pin`)) { + shepherd.fs.readFile(`${shepherd.agamaDir}/shepherd/pin/${req.body.pubkey}.pin`, 'utf8', (err, data) => { + if (err) { + const errorObj = { + msg: 'error', + result: err, + }; + + res.end(JSON.stringify(errorObj)); + } else { + const encryptedKey = shepherd.aes256.decrypt(req.body.key, data); + // test if stored encrypted passphrase is decrypted correctly + // if not then the key is wrong + const _regexTest = encryptedKey.match(/^[0-9a-zA-Z ]+$/g); + let returnObj; + + if (!_regexTest) { + returnObj = { + msg: 'error', + result: 'wrong key', + }; + } else { + returnObj = { + msg: 'success', + result: encryptedKey, + }; + } + + res.end(JSON.stringify(returnObj)); + } + }); + } else { + const errorObj = { + msg: 'error', + result: `file ${req.query.pubkey}.pin doesnt exist`, + }; + + res.end(JSON.stringify(errorObj)); + } + } else { + const errorObj = { + msg: 'error', + result: 'missing key or pubkey param', + }; + + res.end(JSON.stringify(errorObj)); + } + }); + + shepherd.get('/getpinlist', (req, res, next) => { + if (shepherd.fs.existsSync(`${shepherd.agamaDir}/shepherd/pin`)) { + shepherd.fs.readdir(`${shepherd.agamaDir}/shepherd/pin`, (err, items) => { + let _pins = []; + + for (let i = 0; i < items.length; i++) { + if (items[i].substr(items[i].length - 4, 4) === '.pin') { + _pins.push(items[i].substr(0, items[i].length - 4)); + } + } + + if (!items.length) { + const errorObj = { + msg: 'error', + result: 'no pins', + }; + + res.end(JSON.stringify(errorObj)); + } else { + const successObj = { + msg: 'success', + result: _pins, + }; + + res.end(JSON.stringify(successObj)); + } + }); + } else { + const errorObj = { + msg: 'error', + result: 'pin folder doesnt exist', + }; + + res.end(JSON.stringify(errorObj)); + } + }); + + return shepherd; +}; \ No newline at end of file diff --git a/routes/shepherd/quitDaemon.js b/routes/shepherd/quitDaemon.js new file mode 100644 index 0000000..4283d99 --- /dev/null +++ b/routes/shepherd/quitDaemon.js @@ -0,0 +1,170 @@ +const portscanner = require('portscanner'); +const execFile = require('child_process').execFile; + +module.exports = (shepherd) => { + shepherd.quitKomodod = (timeout = 100) => { + // if komodod is under heavy load it may not respond to cli stop the first time + // exit komodod gracefully + let coindExitInterval = {}; + shepherd.lockDownAddCoin = true; + + for (let key in shepherd.coindInstanceRegistry) { + if (shepherd.appConfig.stopNativeDaemonsOnQuit) { + const chain = key !== 'komodod' ? key : null; + let _coindQuitCmd = shepherd.komodocliBin; + + // any coind + if (shepherd.nativeCoindList[key.toLowerCase()]) { + _coindQuitCmd = `${shepherd.coindRootDir}/${key.toLowerCase()}/${shepherd.nativeCoindList[key.toLowerCase()].bin.toLowerCase()}-cli`; + } + if (key === 'CHIPS') { + _coindQuitCmd = shepherd.chipscliBin; + } + + const execCliStop = () => { + let _arg = []; + if (chain && + !shepherd.nativeCoindList[key.toLowerCase()] && key !== 'CHIPS') { + _arg.push(`-ac_name=${chain}`); + + if (shepherd.appConfig.dataDir.length) { + _arg.push(`-datadir=${shepherd.appConfig.dataDir + (key !== 'komodod' ? '/' + key : '')}`); + } + } else if (key === 'komodod' && shepherd.appConfig.dataDir.length) { + _arg.push(`-datadir=${shepherd.appConfig.dataDir}`); + } + + _arg.push('stop'); + execFile(`${_coindQuitCmd}`, _arg, (error, stdout, stderr) => { + shepherd.log(`stdout: ${stdout}`); + shepherd.log(`stderr: ${stderr}`); + shepherd.log(`send stop sig to ${key}`); + + if (stdout.indexOf('EOF reached') > -1 || + stderr.indexOf('EOF reached') > -1 || + (error && error.toString().indexOf('Command failed') > -1 && !stderr) || // win "special snowflake" case + stdout.indexOf('connect to server: unknown (code -1)') > -1 || + stderr.indexOf('connect to server: unknown (code -1)') > -1) { + delete shepherd.coindInstanceRegistry[key]; + clearInterval(coindExitInterval[key]); + } + + // workaround for AGT-65 + const _port = shepherd.assetChainPorts[key]; + setTimeout(() => { + portscanner.checkPortStatus(_port, '127.0.0.1', (error, status) => { + // Status is 'open' if currently in use or 'closed' if available + if (status === 'closed') { + delete shepherd.coindInstanceRegistry[key]; + clearInterval(coindExitInterval[key]); + } + }); + }, 100); + + if (error !== null) { + shepherd.log(`exec error: ${error}`); + } + + setTimeout(() => { + shepherd.killRogueProcess(key === 'CHIPS' ? 'chips-cli' : 'komodo-cli'); + }, 100); + }); + } + + execCliStop(); + coindExitInterval[key] = setInterval(() => { + execCliStop(); + }, timeout); + } else { + delete shepherd.coindInstanceRegistry[key]; + } + } + } + + shepherd.post('/coind/stop', (req, res) => { + const _chain = req.body.chain; + let _coindQuitCmd = shepherd.komodocliBin; + let _arg = []; + + if (_chain) { + _arg.push(`-ac_name=${_chain}`); + + if (shepherd.appConfig.dataDir.length) { + _arg.push(`-datadir=${shepherd.appConfig.dataDir + (_chain ? '/' + _chain : '')}`); + } + } else if (!_chain && shepherd.appConfig.dataDir.length) { + _arg.push(`-datadir=${shepherd.appConfig.dataDir}`); + } + + _arg.push('stop'); + execFile(`${_coindQuitCmd}`, _arg, (error, stdout, stderr) => { + shepherd.log(`stdout: ${stdout}`); + shepherd.log(`stderr: ${stderr}`); + shepherd.log(`send stop sig to ${_chain ? _chain : 'komodo'}`); + + if (stdout.indexOf('EOF reached') > -1 || + stderr.indexOf('EOF reached') > -1 || + (error && error.toString().indexOf('Command failed') > -1 && !stderr) || // win "special snowflake" case + stdout.indexOf('connect to server: unknown (code -1)') > -1 || + stderr.indexOf('connect to server: unknown (code -1)') > -1) { + delete shepherd.coindInstanceRegistry[_chain ? _chain : 'komodod']; + + const obj = { + msg: 'success', + result: 'result', + }; + + res.end(JSON.stringify(obj)); + } else { + if (stdout.indexOf('Komodo server stopping') > -1) { + delete shepherd.coindInstanceRegistry[_chain ? _chain : 'komodod']; + + const obj = { + msg: 'success', + result: 'result', + }; + + res.end(JSON.stringify(obj)); + } else { + const obj = { + msg: 'error', + result: 'result', + }; + + res.end(JSON.stringify(obj)); + } + } + }); + }); + + shepherd.post('/coins/remove', (req, res) => { + const _chain = req.body.chain; + + if (req.body.mode === 'native') { + delete shepherd.coindInstanceRegistry[_chain ? _chain : 'komodod']; + + const obj = { + msg: 'success', + result: 'result', + }; + + res.end(JSON.stringify(obj)); + } else { + delete shepherd.electrumCoins[_chain === 'komodo' ? 'KMD' : _chain]; + + if (Object.keys(shepherd.electrumCoins).length - 1 === 0) { + shepherd.electrumCoins.auth = false; + shepherd.electrumKeys = {}; + } + + const obj = { + msg: 'success', + result: 'result', + }; + + res.end(JSON.stringify(obj)); + } + }); + + return shepherd; +}; diff --git a/routes/shepherd/rpc.js b/routes/shepherd/rpc.js new file mode 100644 index 0000000..40d55e4 --- /dev/null +++ b/routes/shepherd/rpc.js @@ -0,0 +1,218 @@ +module.exports = (shepherd) => { + shepherd.getConf = (chain) => { + let _confLocation = chain === 'komodod' ? `${shepherd.komodoDir}/komodo.conf` : `${shepherd.komodoDir}/${chain}/${chain}.conf`; + _confLocation = chain === 'CHIPS' ? `${shepherd.chipsDir}/chips.conf` : _confLocation; + + // any coind + if (chain) { + if (shepherd.nativeCoindList[chain.toLowerCase()]) { + const _osHome = shepherd.os.platform === 'win32' ? process.env.APPDATA : process.env.HOME; + let coindDebugLogLocation = `${_osHome}/.${shepherd.nativeCoindList[chain.toLowerCase()].bin.toLowerCase()}/debug.log`; + + _confLocation = `${_osHome}/.${shepherd.nativeCoindList[chain.toLowerCase()].bin.toLowerCase()}/${shepherd.nativeCoindList[chain.toLowerCase()].bin.toLowerCase()}.conf`; + } + + if (shepherd.fs.existsSync(_confLocation)) { + let _port = shepherd.assetChainPorts[chain]; + const _rpcConf = shepherd.fs.readFileSync(_confLocation, 'utf8'); + + // any coind + if (shepherd.nativeCoindList[chain.toLowerCase()]) { + _port = shepherd.nativeCoindList[chain.toLowerCase()].port; + } + + if (_rpcConf.length) { + let _match; + let parsedRpcConfig = { + user: '', + pass: '', + port: _port, + }; + + if (_match = _rpcConf.match(/rpcuser=\s*(.*)/)) { + parsedRpcConfig.user = _match[1]; + } + + if ((_match = _rpcConf.match(/rpcpass=\s*(.*)/)) || + (_match = _rpcConf.match(/rpcpassword=\s*(.*)/))) { + parsedRpcConfig.pass = _match[1]; + } + + if (shepherd.nativeCoindList[chain.toLowerCase()]) { + shepherd.rpcConf[chain] = parsedRpcConfig; + } else { + shepherd.rpcConf[chain === 'komodod' ? 'KMD' : chain] = parsedRpcConfig; + } + } else { + shepherd.log(`${_confLocation} is empty`); + } + } else { + shepherd.log(`${_confLocation} doesnt exist`); + } + } + } + + /* + * type: POST + * params: payload + */ + shepherd.post('/cli', (req, res, next) => { + if (!req.body.payload) { + const errorObj = { + msg: 'error', + result: 'no payload provided', + }; + + res.end(JSON.stringify(errorObj)); + } else if (!req.body.payload.cmd.match(/^[0-9a-zA-Z _\,\.\[\]"'/\\]+$/g)) { + const errorObj = { + msg: 'error', + result: 'wrong cli string format', + }; + + res.end(JSON.stringify(errorObj)); + } else { + const _mode = req.body.payload.mode === 'passthru' ? 'passthru' : 'default'; + const _chain = req.body.payload.chain === 'KMD' ? null : req.body.payload.chain; + const _params = req.body.payload.params ? ` ${req.body.payload.params}` : ''; + let _cmd = req.body.payload.cmd; + + if (!shepherd.rpcConf[_chain]) { + shepherd.getConf(req.body.payload.chain === 'KMD' || !req.body.payload.chain && shepherd.kmdMainPassiveMode ? 'komodod' : req.body.payload.chain); + } + + if (_mode === 'default') { + if (_cmd === 'debug' && + _chain !== 'CHIPS') { + if (shepherd.nativeCoindList[_chain.toLowerCase()]) { + const _osHome = shepherd.os.platform === 'win32' ? process.env.APPDATA : process.env.HOME; + let coindDebugLogLocation; + + if (_chain === 'CHIPS') { + coindDebugLogLocation = `${shepherd.chipsDir}/debug.log`; + } else { + coindDebugLogLocation = `${_osHome}/.${shepherd.nativeCoindList[_chain.toLowerCase()].bin.toLowerCase()}/debug.log`; + } + + shepherd.readDebugLog(coindDebugLogLocation, 1) + .then((result) => { + const _obj = { + msg: 'success', + result: result, + }; + + // shepherd.log('bitcoinrpc debug ====>'); + // console.log(result); + + res.end(JSON.stringify(_obj)); + }, (result) => { + const _obj = { + error: result, + result: 'error', + }; + + res.end(JSON.stringify(_obj)); + }); + } else { + res.end({ + error: 'bitcoinrpc debug error', + result: 'error', + }); + // console.log('bitcoinrpc debug error'); + } + } else { + if (_chain === 'CHIPS' && + _cmd === 'debug') { + _cmd = 'getblockchaininfo'; + } + + let _body = { + agent: 'bitcoinrpc', + method: _cmd, + }; + + if (req.body.payload.params) { + _body = { + agent: 'bitcoinrpc', + method: _cmd, + params: req.body.payload.params === ' ' ? [''] : req.body.payload.params, + }; + } + + if (req.body.payload.chain) { + const options = { + url: `http://localhost:${shepherd.rpcConf[req.body.payload.chain].port}`, + method: 'POST', + auth: { + user: shepherd.rpcConf[req.body.payload.chain].user, + pass: shepherd.rpcConf[req.body.payload.chain].pass, + }, + body: JSON.stringify(_body), + }; + + // send back body on both success and error + // this bit replicates iguana core's behaviour + shepherd.request(options, (error, response, body) => { + if (response && + response.statusCode && + response.statusCode === 200) { + res.end(body); + } else { + res.end(body ? body : JSON.stringify({ + result: 'error', + error: { + code: -777, + message: `unable to call method ${_cmd} at port ${shepherd.rpcConf[req.body.payload.chain].port}`, + }, + })); + } + }); + } + } + } else { + let _coindCliBin = shepherd.komodocliBin; + + if (shepherd.nativeCoindList && + _chain && + shepherd.nativeCoindList[_chain.toLowerCase()]) { + _coindCliBin = `${shepherd.coindRootDir}/${_chain.toLowerCase()}/${shepherd.nativeCoindList[_chain.toLowerCase()].bin.toLowerCase()}-cli`; + } + + let _arg = (_chain ? ' -ac_name=' + _chain : '') + ' ' + _cmd + _params; + + if (shepherd.appConfig.dataDir.length) { + _arg = `${_arg} -datadir=${shepherd.appConfig.dataDir + (_chain ? '/' + key : '')}`; + } + + _arg = _arg.trim().split(' '); + shepherd.execFile(_coindCliBin, _arg, (error, stdout, stderr) => { + shepherd.log(`stdout: ${stdout}`); + shepherd.log(`stderr: ${stderr}`); + + if (error !== null) { + shepherd.log(`exec error: ${error}`); + } + + let responseObj; + + if (stderr) { + responseObj = { + msg: 'error', + result: stderr, + }; + } else { + responseObj = { + msg: 'success', + result: stdout, + }; + } + + res.end(JSON.stringify(responseObj)); + shepherd.killRogueProcess('komodo-cli'); + }); + } + } + }); + + return shepherd; +}; \ No newline at end of file diff --git a/version b/version index 9ce0cf2..6852fe7 100644 --- a/version +++ b/version @@ -1,3 +1,3 @@ -version=0.2.0.22a +version=0.2.0.25b type=e-beta minversion=0.2.0.2 \ No newline at end of file diff --git a/version_build b/version_build index 34b9960..c82aeff 100644 --- a/version_build +++ b/version_build @@ -1 +1 @@ -0.2.0.22a-beta +0.2.0.25b-beta \ No newline at end of file