Browse Source

Merge v1.9.0 into umbrel

umbrel v1.9.0-umbrel
Lounès Ksouri 4 years ago
parent
commit
ed3c5d8951
No known key found for this signature in database GPG Key ID: F8DC83D24F68572D
  1. 7
      .gitignore
  2. 146
      RELEASES.md
  3. 38
      accounts/status.js
  4. 92
      accounts/support-rest-api.js
  5. 25
      accounts/xpub-rest-api.js
  6. 10
      doc/DOCKER_setup.md
  7. 509
      doc/DOCKER_ubuntu_setup.md
  8. 11
      doc/GET_xpub_import_status.md
  9. 14
      docker/my-dojo/.env
  10. 8
      docker/my-dojo/bitcoin/Dockerfile
  11. 14
      docker/my-dojo/bitcoin/restart.sh
  12. 5
      docker/my-dojo/conf/docker-bitcoind.conf.tpl
  13. 4
      docker/my-dojo/conf/docker-whirlpool.conf.tpl
  14. 1
      docker/my-dojo/docker-compose.yaml
  15. 175
      docker/my-dojo/dojo.sh
  16. 2
      docker/my-dojo/explorer/Dockerfile
  17. 4
      docker/my-dojo/indexer/Dockerfile
  18. 6
      docker/my-dojo/install/upgrade-scripts.sh
  19. 4
      docker/my-dojo/tor/Dockerfile
  20. 28
      docker/my-dojo/tor/restart.sh
  21. 10
      docker/my-dojo/whirlpool/Dockerfile
  22. 8
      docker/my-dojo/whirlpool/restart.sh
  23. 4
      keys/index-example.js
  24. 6
      lib/bitcoind-rpc/fees.js
  25. 6
      lib/indexer-rpc/rpc-client.js
  26. 14
      lib/remote-importer/bitcoind-wrapper.js
  27. 33
      lib/remote-importer/esplora-wrapper.js
  28. 29
      lib/remote-importer/local-indexer-wrapper.js
  29. 35
      lib/remote-importer/oxt-wrapper.js
  30. 60
      lib/remote-importer/remote-importer.js
  31. 18
      lib/remote-importer/sources.js
  32. 326
      package-lock.json
  33. 5
      package.json
  34. 17
      static/admin/css/style.css
  35. 16
      static/admin/dmt/addresses-tools/addresses-tools.js
  36. 2
      static/admin/dmt/blocks-rescan/blocks-rescan.html
  37. 3
      static/admin/dmt/blocks-rescan/blocks-rescan.js
  38. 1
      static/admin/dmt/index.html
  39. 3
      static/admin/dmt/pairing/pairing.js
  40. 14
      static/admin/dmt/pushtx/pushtx.js
  41. 47
      static/admin/dmt/status/status.html
  42. 123
      static/admin/dmt/status/status.js
  43. 3
      static/admin/dmt/txs-tools/txs-tools.js
  44. 2
      static/admin/dmt/welcome/welcome.html
  45. 9
      static/admin/dmt/xpubs-tools/xpubs-tools.html
  46. 84
      static/admin/dmt/xpubs-tools/xpubs-tools.js
  47. BIN
      static/admin/icons/samourai-logo-loading.png
  48. BIN
      static/admin/icons/samourai-logo-trans@2x.png
  49. BIN
      static/admin/icons/samourai-logo.png
  50. 3
      static/admin/index.html
  51. 3
      static/admin/index.js
  52. 17
      static/admin/lib/api-wrapper.js
  53. 19
      static/admin/lib/auth-utils.js
  54. 25
      static/admin/lib/errors-utils.js
  55. 9
      static/admin/lib/messages.js
  56. 28
      tracker/blockchain-processor.js

7
.gitignore

@ -1,14 +1,13 @@
db-scripts/updates/ db-scripts/updates/
db-scripts/1_db.sql db-scripts/1_db.sql
db-scripts/2_update.sql db-scripts/2_update.sql
docker/my-dojo/conf/docker-bitcoind.conf docker/my-dojo/conf/*.conf
docker/my-dojo/conf/docker-mysql.conf docker/my-dojo/conf/*.conf.save
docker/my-dojo/conf/docker-node.conf
keys/index.js keys/index.js
keys/sslcert/ keys/sslcert/
node_modules/ node_modules/
private-tests/ private-tests/
static/admin/conf/index.js static/admin/conf/index.js
static/admin-legacy
static/admin-legacy/ static/admin-legacy/
*.log *.log
static/admin-legacy

146
RELEASES.md

@ -3,6 +3,7 @@
## Releases ## ## Releases ##
- [v1.9.0](#1_9_0)
- [v1.8.1](#1_8_1) - [v1.8.1](#1_8_1)
- [v1.8.0](#1_8_0) - [v1.8.0](#1_8_0)
- [v1.7.0](#1_7_0) - [v1.7.0](#1_7_0)
@ -15,6 +16,151 @@
- [v1.1.0](#1_1_0) - [v1.1.0](#1_1_0)
<a name="1_9_0"/>
## Samourai Dojo v1.9.0 ##
### Notable changes ###
#### Maintenance Tool: multiple UX improvements ####
*Status screen*
The status screen now displays information related to the Dojo database and to the data source used by Dojo for its imports and rescans. This screen provides a high level view of the state of the Dojo instance, that can be shared for support.
*XPUB Tool*
- Progress made is now displayed during an import or a rescan.
- New feature allowing to delete a XPUB tracked by Dojo.
- Improved management of timeouts by the authentication system.
#### Dojo Shell Script: improvements ####
- Script automatically stops if build fails during install/upgrade operation.
- Script returns a not null exit code if build fails or if install/upgrade operation is cancelled.
- Dojo is automatically stopped if an upgrade operation is launched with Dojo up and running.
- A cleanup of old Dojo versions is automatically processed at the end of successful upgrade operations.
#### New configuration options ####
Addition of two new configuration options:
- BITCOIND_LISTEN_MODE (in docker-bitcoind.conf): When set to `off`, the fullnode will refuse incoming connections. Default = `on`.
- WHIRLPOOL_COORDINATOR_ONION (in docker-whirlpool.conf): When set to `on`, whirlpool-cli will contact the coordinator through its onion address. When set to `off`, clearnet address will be used (through Tor). Default = `on`.
#### Extended support Tor hidden services ####
Dojo now provides a v2 and v3 hidden service for:
- Dojo Maintenance Tool and API
- Whirlpool CLI
- Bitcoind
- Explorer
Tor v3 onion addresses are recommended but v2 addresses can be used in the case of new attacks disrupting v3 hidden services.
These onion addresses can be retrieved thanks to the `onion` command of the Dojo Shell Script
'''
# Display Tor v3 onion addresses (default)
> ./dojo.h onion
# Display Tor v3 onion addresses
> ./dojo.h onion v3
# Display Tor v2 onion addresses
> ./dojo.h onion v2
'''
#### Upgrade of bitcoind to v0.21.0 ####
Upgrade to Bitcoin Core v0.21.0
#### Upgrade of whirlpool to v0.10.9 ####
Upgrade to whirlpool-cli 0.10.9
#### Upgrade of explorer to v2.1.0 ####
Upgrade to btc-rpc-explorer 2.1.0
#### Upgrade of tor to v0.4.4.7 ####
Tor 0.4.4.7 fixes and mitigates multiple issues, including one that made v3 onion services more susceptible to denial-of-service attacks.
#### Upgrade of indexer to v0.4.0 ####
Upgrade to addrindexrs v0.4.0
### Change log ###
#### MyDojo ####
- [#mr165](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/165) improve dmt ux
- [#mr166](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/166) add new configuration property BITCOIND_LISTEN_MODE
- [#mr167](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/167) upgrade explorer to btc-rpc-explorer 2.0.2
- [#mr168](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/168) add new getChaintipHeight() method to remote importer and data sources
- [#mr170](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/170) add indexer info to /status endpoint
- [#mr171](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/171) add db and indexer blocks to status screen of dmt
- [#mr172](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/172) upgrade indexer to addrindexrs 0.4.0
- [#mr174](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/174) upgrade whirlpool to whirlpool-cli 0.10.9
- [#mr175](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/175) track and display progress of import/rescan
- [#mr178](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/178) improve dojo shell script
- [#mr179](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/179) update samourai logo
- [#mr181](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/181) add support of xpub deletion from the dmt
- [#mr182](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/182) upgrade bitcoin container with bitcoin core 0.21.0
- [#mr183](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/183) upgrade explorer to btc-rpc-explorer 2.1.0
- [#mr184](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/184) upgrade tor to v0.4.4.6
- [#mr186](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/186) upgrade tor to v0.4.4.6
- [#mr188](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/188) return exit code 2 if install or upgrade is cancelled
- [#mr190](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/190) add new WHIRLPOOL_COORDINATOR_ONION config option
- [#mr191](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/191) add v2 onion addresses for explorer and whirlpool
- [#mr192](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/192) return exit code 1 instead of 2 for aborted install & upgrade
- [#mr193](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/193) reactivate tor v2 hidden service for bitcoind
- [#mr194](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/194) upgrade tor to v0.4.4.7
- [#mr195](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/195) bump block height defining ibd mode
- [9fe22a35](https://code.samourai.io/dojo/samourai-dojo/-/commit/9fe22a356625e0c1aeb18691d617af9118990c84) update .gitignore
#### Bug fixes ####
- [#mr176](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/176) check that jqxhr['responseJSON']['error'] is a string
- [#mr177](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/177) prevent restart of bitcoin container if bitcoind fails
- [#mr185](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/185) build addrindexrs with --locked argument
- [#mr189](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/189) return a 0 feerate if bitcoind doesn't return an estimate
#### Security ####
- [#mr173](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/173) replace request-promise-native by axios
#### Documentation ####
- [#mr180](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/180) add a detailed installation and upgrade guide for ubuntu
#### Credits ###
- BTCxZelko
- flatcloud0b3
- kenshin-samourai
- LaurentMT
- likewhoa
<a name="1_8_1"/> <a name="1_8_1"/>
## Samourai Dojo v1.8.1 ## ## Samourai Dojo v1.8.1 ##

38
accounts/status.js

@ -4,8 +4,12 @@
*/ */
'use strict' 'use strict'
const network = require('../lib/bitcoin/network')
const keys = require('../keys')[network.key]
const util = require('../lib/util') const util = require('../lib/util')
const Logger = require('../lib/logger')
const db = require('../lib/db/mysql-db-wrapper') const db = require('../lib/db/mysql-db-wrapper')
const remote = require('../lib/remote-importer/remote-importer')
/** /**
@ -32,8 +36,31 @@ class Status {
const memory = `${util.toMb(process.memoryUsage().rss)} MiB` const memory = `${util.toMb(process.memoryUsage().rss)} MiB`
// Get highest block processed by the tracker // Get highest block processed by the tracker
const highest = await db.getHighestBlock() let dbMaxHeight = null
const dbMaxHeight = highest.blockHeight try {
const highest = await db.getHighestBlock()
dbMaxHeight = highest.blockHeight
} catch(e) {
Logger.error(e, 'API : Status.getCurrent() :')
}
// Get info about the indexer
const indexerType = keys.indexer.active
let indexerMaxHeight = null
let indexerUrl = null
if (indexerType == 'third_party_explorer') {
indexerUrl = (network.key == 'bitcoin')
? keys.indexer.oxt
: keys.indexer.esplora
}
try {
const chaintip = await remote.getChainTipHeight()
indexerMaxHeight = chaintip['chainTipHeight']
} catch(e) {
Logger.error(e, 'API : Status.getCurrent() :')
}
return { return {
uptime: uptime, uptime: uptime,
@ -43,7 +70,12 @@ class Status {
sessions: this.sessions, sessions: this.sessions,
max: this.maxConn max: this.maxConn
}, },
blocks: dbMaxHeight blocks: dbMaxHeight,
indexer: {
type: indexerType,
url: indexerUrl,
maxHeight: indexerMaxHeight
}
} }
} }

92
accounts/support-rest-api.js

@ -69,6 +69,14 @@ class SupportRestApi {
HttpServer.sendAuthError HttpServer.sendAuthError
) )
this.httpServer.app.get(
`/${keys.prefixes.support}/xpub/:xpub/delete`,
authMgr.checkHasAdminProfile.bind(authMgr),
this.validateArgsGetXpubDelete.bind(this),
this.getXpubDelete.bind(this),
HttpServer.sendAuthError
)
this.httpServer.app.get( this.httpServer.app.get(
`/${keys.prefixes.support}/pairing/explorer`, `/${keys.prefixes.support}/pairing/explorer`,
authMgr.checkHasAdminProfile.bind(authMgr), authMgr.checkHasAdminProfile.bind(authMgr),
@ -120,32 +128,9 @@ class SupportRestApi {
*/ */
_formatAddressInfoResult(info) { _formatAddressInfoResult(info) {
const res = info.toPojoExtended() const res = info.toPojoExtended()
/*res._endpoints = []
if (info.tracked) {
res._endpoints.push({
task: 'Rescan this address from remote sources',
url: `/${keys.prefixes.support}/address/${info.address}/rescan`
})
}
if (info.xpub != null) {
res._endpoints.push({
task: 'Get information about the HD account that owns this address',
url: `/${keys.prefixes.support}/xpub/${info.xpub}/info`
})
res._endpoints.push({
task: 'Rescan the whole HD account that owns this address',
url: `/${keys.prefixes.support}/xpub/${info.xpub}/rescan`
})
}*/
return JSON.stringify(res, null, 2) return JSON.stringify(res, null, 2)
} }
/** /**
* Rescan the blockchain for a given address * Rescan the blockchain for a given address
* @param {object} req - http request object * @param {object} req - http request object
@ -162,10 +147,6 @@ class SupportRestApi {
const ret = { const ret = {
status: 'Rescan complete', status: 'Rescan complete',
/*_endpoints: [{
task: 'Get updated information about this address',
url: `/${keys.prefixes.support}/address/${address}/info`
}]*/
} }
await addrService.rescan(address) await addrService.rescan(address)
@ -224,12 +205,6 @@ class SupportRestApi {
*/ */
_formatXpubInfoResult(info) { _formatXpubInfoResult(info) {
const res = info.toPojoExtended() const res = info.toPojoExtended()
/*res._endpoints = [{
task: 'Rescan the whole HD account from remote sources',
url: `/${keys.prefixes.support}/xpub/${info.xpub}/rescan`
}]*/
return JSON.stringify(res, null, 2) return JSON.stringify(res, null, 2)
} }
@ -249,10 +224,6 @@ class SupportRestApi {
const ret = { const ret = {
status: 'Rescan complete', status: 'Rescan complete',
/*_endpoints: [{
task: 'Get updated information about this HD account',
url: `/${keys.prefixes.support}/xpub/${xpub}/info`
}]*/
} }
const gapLimit = req.query.gap != null ? parseInt(req.query.gap) : 0 const gapLimit = req.query.gap != null ? parseInt(req.query.gap) : 0
@ -283,6 +254,36 @@ class SupportRestApi {
} }
} }
/**
* Delete all data related to a hd account
* @param {object} req - http request object
* @param {object} res - http response object
*/
async getXpubDelete(req, res) {
try {
// Parse the entities passed as url params
const entities = apiHelper.parseEntities(req.params.xpub).xpubs
if (entities.length == 0)
return HttpServer.sendError(res, errors.xpub.INVALID)
const xpub = entities[0]
try {
await hdaService.deleteHdAccount(xpub)
HttpServer.sendOk(res)
} catch(e) {
HttpServer.sendError(res, e)
}
} catch(e) {
HttpServer.sendError(res, errors.generic.GEN)
} finally {
debugApi && Logger.info(`API : Completed GET /support/xpub/${req.params.xpub}/delete`)
}
}
/** /**
* Get pairing info * Get pairing info
*/ */
@ -366,6 +367,23 @@ class SupportRestApi {
} }
} }
/**
* Validate arguments related to GET xpub delete requests
* @param {object} req - http request object
* @param {object} res - http response object
* @param {function} next - next express middleware
*/
validateArgsGetXpubDelete(req, res, next) {
const isValidXpub = validator.isAlphanumeric(req.params.xpub)
if (!isValidXpub) {
HttpServer.sendError(res, errors.body.INVDATA)
Logger.error(null, `API : SupportRestApi.validateArgsGetXpubDelete() : Invalid xpub ${req.params.xpub}`)
} else {
next()
}
}
/** /**
* Validate arguments related to addresses requests * Validate arguments related to addresses requests
* @param {object} req - http request object * @param {object} req - http request object

25
accounts/xpub-rest-api.js

@ -16,6 +16,7 @@ const RpcClient = require('../lib/bitcoind-rpc/rpc-client')
const HdAccountInfo = require('../lib/wallet/hd-account-info') const HdAccountInfo = require('../lib/wallet/hd-account-info')
const authMgr = require('../lib/auth/authorizations-manager') const authMgr = require('../lib/auth/authorizations-manager')
const HttpServer = require('../lib/http-server/http-server') const HttpServer = require('../lib/http-server/http-server')
const remoteImporter = require('../lib/remote-importer/remote-importer')
const debugApi = !!(process.argv.indexOf('api-debug') > -1) const debugApi = !!(process.argv.indexOf('api-debug') > -1)
const gap = require('../keys/')[network.key].gap const gap = require('../keys/')[network.key].gap
@ -230,8 +231,18 @@ class XPubRestApi {
return HttpServer.sendError(res, e) return HttpServer.sendError(res, e)
} }
const ret = { let ret = {
import_in_progress: hdaService.importInProgress(xpub) import_in_progress: false
}
const status = hdaService.importInProgress(xpub)
if (status != null) {
ret['import_in_progress'] = true
ret['status'] = status['status']
if (ret['status'] == remoteImporter.STATUS_RESCAN)
ret['hits'] = status['txs_int'] + status['txs_ext']
else
ret['hits'] = status['txs']
} }
HttpServer.sendOkData(res, ret) HttpServer.sendOkData(res, ret)
@ -275,7 +286,7 @@ class XPubRestApi {
const argAddr = req.body.address const argAddr = req.body.address
const argSig = req.body.signature const argSig = req.body.signature
const argMsg = req.body.message const argMsg = req.body.message
// Translate xpub if needed // Translate xpub if needed
try { try {
const ret = this.xlatHdAccount(argXpub) const ret = this.xlatHdAccount(argXpub)
@ -323,7 +334,7 @@ class XPubRestApi {
const argXpub = req.params.xpub const argXpub = req.params.xpub
const argAddr = req.body.address const argAddr = req.body.address
const argSig = req.body.signature const argSig = req.body.signature
// Translate xpub if needed // Translate xpub if needed
try { try {
const ret = this.xlatHdAccount(argXpub) const ret = this.xlatHdAccount(argXpub)
@ -398,15 +409,15 @@ class XPubRestApi {
validateArgsPostXpub(req, res, next) { validateArgsPostXpub(req, res, next) {
const isValidXpub = validator.isAlphanumeric(req.body.xpub) const isValidXpub = validator.isAlphanumeric(req.body.xpub)
const isValidSegwit = const isValidSegwit =
!req.body.segwit !req.body.segwit
|| validator.isAlphanumeric(req.body.segwit) || validator.isAlphanumeric(req.body.segwit)
const isValidType = const isValidType =
!req.body.type !req.body.type
|| validator.isAlphanumeric(req.body.type) || validator.isAlphanumeric(req.body.type)
const isValidForce = const isValidForce =
!req.body.force !req.body.force
|| validator.isAlphanumeric(req.body.force) || validator.isAlphanumeric(req.body.force)

10
doc/DOCKER_setup.md

@ -61,7 +61,7 @@ MyDojo is a set of Docker containers providing a full Samourai backend composed
| | ---------- ---------- | | | ---------- ---------- |
| whirlnet | dojonet | | whirlnet | dojonet |
|_________________|______________________________________________________________| |_________________|______________________________________________________________|
Host machine Host machine
@ -109,6 +109,8 @@ Most options provided in the configuration files can be later modified. New valu
## First-time Setup ## ## First-time Setup ##
For Ubuntu 16, see this detailed [installation and upgrade guide](./DOCKER_ubuntu_setup.MD).
For MacOS, see this detailed [installation guide](./DOCKER_mac_setup.MD). For MacOS, see this detailed [installation guide](./DOCKER_mac_setup.MD).
For Synology, see this detailed [installation guide](./DOCKER_synology_setup.md). For Synology, see this detailed [installation guide](./DOCKER_synology_setup.md).
@ -325,7 +327,7 @@ Once the database has finished syncing, you can pair your Samourai Wallet with y
1. Open the maintenance tool in a Tor browser (Tor v3 onion address) and sign in with your admin key. 1. Open the maintenance tool in a Tor browser (Tor v3 onion address) and sign in with your admin key.
2. Get your smartphone and launch the Samourai Wallet app. Scan the first QRCode displayed in the "Pairing" tab of the maintenance tool. 2. Get your smartphone and launch the Samourai Wallet app. Scan the first QRCode displayed in the "Pairing" tab of the maintenance tool.
If you experience any problems when pairing, try re-installing the app and select "Connect to existing Dojo" from the [⋮] menu. If you experience any problems when pairing, try re-installing the app and select "Connect to existing Dojo" from the [⋮] menu.
@ -336,7 +338,7 @@ You can pair your Samourai Wallet with your local block explorer in 2 steps:
1. Open the maintenance tool in a Tor browser (Tor v3 onion address) and sign in with your admin key. 1. Open the maintenance tool in a Tor browser (Tor v3 onion address) and sign in with your admin key.
2. Get your smartphone and launch the Samourai Wallet app. Scan the second QRCode displayed in the "Pairing" tab of the maintenance tool. 2. Get your smartphone and launch the Samourai Wallet app. Scan the second QRCode displayed in the "Pairing" tab of the maintenance tool.
<a name="network"/> <a name="network"/>
@ -353,7 +355,7 @@ The block explorer is accessed as a Tor hidden service (static onion address).
The Whirlpool API is accessed as a Tor hidden service (static onion address). The Whirlpool API is accessed as a Tor hidden service (static onion address).
The Whirlpool client connects to the Whirlpool Coordinator hidden service. The Whirlpool client connects to the Whirlpool Coordinator hidden service.
The Bitcoin node only allows incoming connections from Tor (ephemeral onion address). The Bitcoin node only allows incoming connections from Tor (ephemeral onion address).

509
doc/DOCKER_ubuntu_setup.md

@ -0,0 +1,509 @@
# Installation and Upgrade of Dojo on Ubuntu 16
This procedure is written for the installation of MyDojo on a host machine running Ubuntu 16 but it should be easy to adapt it for Linux distributions running on a x86-64 architecture.
## Table of Content ##
- [Requirements](#requirements)
- [Storage locations](#storage)
- [Installation procedure](#install)
- [Upgrade procedure](#upgrade)
<a name="requirements"/>
## 1/ Requirements ##
The main requirements for the host machine running MyDojo are:
* Processor: x86-64
* OS: Linux 64 bits
* Disk Type: SSD (recommended)
* Disk Size: 600GB (min)/ 1TB (recommended)
* RAM: 2GB (min) / 4GB (recommended)
__Additional Considerations__
* While MyDojo will work fine on a multipurpose computer, a dedicated host machine connected 24/7 to the network is recommended. MyDojo was primarily designed as a server always ready for use
* The clock of the host machine MUST be set properly (required for Tor)
<a name="storage"/>
## 2/ Storage Locations ##
By default, MyDojo stores its code and data in 2 different locations:
* __MyDojo application code__: source code of MyDojo + Dojo Shell Script + Configuration files
* __MyDojo and Docker data__: persistent data required for a functional MyDojo (blockchain data, Dojo database, logs, Docker images)
By default, MyDojo and Docker data will be stored under `/var/lib/docker` directory but the directory can be modified (e.g.: all data stored on an dedicated disk).
<a name="install"/>
## 3/ First Installation
The procedure described in this section will configure and install MyDojo with its own Indexer for fast rescans. See the [Advanced Setups](./DOCKER_advanced_setups.md) for more information about this module or for the additional installation of a Whirlpool client on your Dojo.
### 3.1/ Prepare Host System
First, we must prepare our host system for MyDojo by installing required operating system dependencies and configuring our linux system with privacy and security in mind.
#### 3.1.1/ Install OS dependencies
```
> cd ~
> sudo apt-get update
> sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common unzip
```
#### 3.1.2/ Install Docker & Docker-Compose
For an installation of Docker and Docker Compose with a different Linux distribution, please refer to the official [Docker](https://docs.docker.com/install/) and [Docker Compose](https://docs.docker.com/compose/install/) documentations.
If Docker is already installed on the host machine remove old Docker versions installed on the computer
```
> sudo apt-get remove docker docker-engine docker.io containerd runc
```
__Download Docker's official PGP key__
```
> curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
> sudo apt-key fingerprint 0EBFCD88
```
Verify that you now have the key with the fingerprint `9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88`
__Install Docker__
```
> sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
> sudo apt-get update
> sudo apt-get install docker-ce docker-ce-cli containerd.io
```
__Test the installation of Docker__
```
> sudo docker --version
```
This command should return the version of Docker if installation was successful.
__Install Docker Compose__
```
> sudo curl -L "https://github.com/docker/compose/releases/download/1.25.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
> sudo chmod +x /usr/local/bin/docker-compose
```
__Test the installation of Docker Compose__
```
> sudo docker-compose --version
```
This command should return the version of Docker Compose if installation was successful.
__Create a user account for MyDojo__
Creating a segregated user account for MyDojo is a good idea for security reasons.
#### 3.1.3/ Create a dojo user
```
> sudo useradd -s /bin/bash -d /home/dojo -m -G sudo dojo
> sudo passwd dojo
```
Enter and confirm the password for the `dojo` user.
__Add the user to the docker group__
```
> sudo usermod -aG docker dojo
```
__Restart Host System__
```
> sudo shutdown -r now
```
Log back into the Host System with the `dojo` user and test the Docker installation
```
> docker run hello-world
```
This command should display a hello message if Docker can be run with the `dojo` user.
#### 3.1.4/ Configure Docker data storage directory (optional)
This step should be applied if you don't want to store MyDojo and Docker data under the default `/var/lib/docker` directory (e.g.: all data will be stored on an external SSD).
__Stop the Docker Service__
```
> sudo systemctl stop docker
```
Create the directory that will store Docker data (replace `/path/to/target/directory/` by the correct path).
```
> sudo mkdir /path/to/target/directory/
```
Temporarily switch to root and create the daemon.json file storing the path to your Docker direct (replace `/path/to/target/directory/` by the correct path).
```
> sudo su - root
> sudo echo '{ "data-root": "/path/to/target/directory/" }' > /etc/docker/daemon.json
> exit
```
### 3.2/ Downloading MyDojo
Now that the Host System has been prepared, we will download the latest version of MyDojo source code and configure it before proceeding with install.
#### 3.2.1/ Initialize the dojo app directory
We first create a directory for housing our MyDojo files. In this guide we are naming this directory `dojo-app` and it will be located in the home directory of the `dojo` user.
```
> mkdir ~/dojo-app
```
#### 3.2.2/ Download latest MyDojo files
Download and unpack the source archive for the latest version of MyDojo and copy these files to the newly created `dojo-app` directory with the following commands.
```
> cd ~
> wget https://code.samourai.io/dojo/samourai-dojo/-/archive/master/samourai-dojo-master.zip
> unzip samourai-dojo-master.zip -d .
> cp -a samourai-dojo-master/. dojo-app/
```
Delete the source archive now that we have copied the files to our directory.
```
> rm -rf samourai-dojo-master
> rm samourai-dojo-master.zip
```
### 3.3/ Configuring MyDojo
Change the working directory to the MyDojo configuration directory.
```
> cd ~/dojo-app/docker/my-dojo/conf
```
__Note:__
You will be required to generate various random alphanumeric passwords to secure various aspects of your Dojo installation. You can generate these in any way you wish, but you may wish to use the following command in a terminal session to generate these passwords with sufficient entropy:
```
> cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 64 | head -n 1
```
#### 3.3.1/ Bitcoin configuration
Edit the `docker-bitcoind.conf.tpl` file.
```
> nano docker-bitcoind.conf.tpl
```
Customize the content of the file
```
BITCOIND_RPC_USER=<provide_this_value>
BITCOIND_RPC_PASSWORD=<provide_this_value>
```
__Note:__
If your machine has a lot of RAM, it's recommended that you increase the value of `BITCOIND_DB_CACHE` (e.g 2048) for a faster Initial Block Download.
Save and exit the file with `CTRL+X`, `Y` and `ENTER`.
#### 3.3.2/ Database configuration
Edit the ``docker-mysql.conf.tpl`` file.
```
> nano docker-mysql.conf.tpl
```
Customize the content of the file
```
MYSQL_ROOT_PASSWORD=<provide_this_value>
MYSQL_USER=<provide_this_value>
MYSQL_PASSWORD=<provide_this_value>
```
Save and exit the file with `CTRL+X`, `Y` and `ENTER`.
#### 3.3.3/ NodeJS configuration
Edit the `docker-node.conf.tpl` file.
```
> nano docker-node.conf.tpl
```
Customize the content of the file
```
NODE_API_KEY=<provide_this_value>
NODE_ADMIN_KEY=<provide_this_value>
NODE_JWT_SECRET=<provide_this_value>
NODE_ACTIVE_INDEXER=local_indexer
```
Save and exit the file with `CTRL+X`, `Y` and `ENTER`.
#### 3.3.4/ Indexer configuration
Edit the `docker-indexer.conf.tpl` file.
```
> nano docker-indexer.conf.tpl
```
Customize the content of the file
```
INDEXER_INSTALL=on
```
Save and exit the file with `CTRL+X`, `Y` and `ENTER`.
#### 3.3.5/ Explorer configuration
Edit the `docker-explorer.conf.tpl` file.
```
> nano docker-explorer.conf.tpl
```
Customize the content of the file
```
EXPLORER_KEY=<provide_this_value>
```
Save and exit the file with `CTRL+X`, `Y` and `ENTER`.
### 3.6/ Execute the first installation of MyDojo
From this point on the install process is automatic. Launch the installation of MyDojo
```
> cd ~/dojo-app/docker/my-dojo
> ./dojo.sh install
```
Confirm the installation with `Y` and `ENTER`.
#### 3.6.1/ Understanding the Automatic Install
At this point, your job is done. The Dojo Shell script and Docker are going to take over and a lot of things are going to happen automatically.
__Building of MyDojo containers__
First, the shell script is going to initialize a set of configuration files. Then Docker is going to
initialize the dedicated networks and data volumes that will be used by MyDojo. At last, Docker is going
to build the Docker containers composing MyDojo.
This latest operation may last from a dozen to a few tens of minutes, depending on the specs of the hardware. A lot of logs are displayed and you may be concerned that some of these logs (displayed in red) are the sign of a problem. Don't worry about this. Some of these logs are just informational or warnings. Just be aware that if a blocking error occurs, Docker will stop building the containers and you won't reach the next phase.
__First launch of MyDojo__
If all Docker containers have been successfully built, the Shell Script is going to launch MyDojo (equivalent of the `dojo.sh start` command) and display its logs.
Here you may be able to observe a few things in the logs:
* The schema of the local database is initialized (done once during first installation)
* The Tor container is connecting to the Tor network
* Dojo application modules are started in the NodeJS container
* The Bitcoin daemon is launched, establishes connections to remote full nodes and starts to download block headers.
* The indexer is launched and waits for the completion of the Initial Blocks Download by the Bitcoin daemon.
__Initial Blocks Download__
When all blocks headers have been successfully downloaded and processed, the Bitcoin daemon is going to process its Initial Blocks Download (IBD). In parallel, the `Tracker` module of Dojo is going to process its own IBD thanks to data provided by the Bitcoin daemon.
This phase is going to last from 1 to several days, depending on the specs of the hardware. At this point, all you have to do is wait. The Bitcoin daemon is processing the IBD while the Tracker is importing block headers in parallel.
Note: You can exit the logs with `CTRL+C`. Don't worry, MyDojo is still running in background.
__Address Indexing__
When the Bitcoin daemon has completed its IBD, the Indexer will automatically start the indexation of all Bitcoin addresses. When the indexation is complete, it will compact the database.
These operations should last a few more hours, depending on the specs of the hardware.
Note: You may notice errors returned by the Block Explorer during all these operations. Don't worry. The Block Explorer should be available as soon as the Indexer has completed its tasks.
### 3.7/ Monitor Install Progress with the Maintenance Tool (DMT)
Retrieve the onion address of the DMT with the commands
```
> cd ~/dojo-app/docker/my-dojo
> ./dojo.sh onion
```
Open the DMT with the Tor browser and check the `Status` page.
If a green check is displayed for all modules and if the chaintip displayed for all modules match with the chaintip displayed by a third-party Block Explorer, then your Dojo is ready.
<a name="upgrade"/>
## 4/ Upgrade
The procedures described in this section will upgrade your Dojo to the most recent version or to a specific version of Dojo.
### 4.1/ Upgrade to latest version
This procedure allows to upgrade MyDojo to the latest version.
#### 4.1.1/ Stop MyDojo
```
> cd ~/dojo-app/docker/my-dojo
> ./dojo.sh stop
```
#### 4.1.2/ Update the code of MyDojo
Download the archive of latest version
```
> cd ~
> wget https://code.samourai.io/dojo/samourai-dojo/-/archive/master/samourai-dojo-master.zip
```
Uncompress the archive
```
> unzip samourai-dojo-master.zip -d .
```
Overwrite the dojo-app directory with the content of the archive
```
> cp -a samourai-dojo-master/. dojo-app/
```
#### 4.1.3/ Update Configuration (optional)
Check the [release notes](https://code.samourai.io/dojo/samourai-dojo/-/blob/master/RELEASES.md) of the new vesion for a list of new features that may require to tune the value of new configuration options.
If applicable, edit the templates files stored in `~/dojo-app/docker/my-dojo/conf/` and modify the values set for new configuration options.
#### 4.1.4/ Start Upgrade
```
> cd ~/dojo-app/docker/my-dojo
> ./dojo.sh upgrade
```
Confirm that you want to upgrade MyDojo with `Y` and `ENTER`.
The shell script is going to rebuild the Docker containers. MyDojo will be automatically restarted after the containers have been rebuilt.
#### 4.1.5/ Cleanup
```
> cd ~
> rm -rf samourai-dojo-master
> rm samourai-dojo-master.zip
```
### 4.2/ Upgrade to a specific version
This procedure allows to upgrade MyDojo to a specific version `X.Y.Z`
#### 4.2.1/ Stop MyDojo
```
> cd ~/dojo-app/docker/my-dojo
> ./dojo.sh stop
```
#### 4.2.2/ Update the code of MyDojo
Download the archive of version `X.Y.Z`
```
> cd ~
> wget https://code.samourai.io/dojo/samourai-dojo/-/archive/vX.Y.Z/samourai-dojo-vX.Y.Z.zip
```
Uncompress the archive
```
> unzip samourai-dojo-vX.Y.Z.zip -d .
```
Overwrite the dojo-app directory with the content of the archive
```
> cp -a samourai-dojo-vX.Y.Z/. dojo-app/
```
#### 4.2.3/ Update Configuration (optional)
Check the [release notes](https://code.samourai.io/dojo/samourai-dojo/-/blob/master/RELEASES.md) for a list of new features that may require to tune the value of new configuration options.
If applicable, edit the templates files stored in `~/dojo-app/docker/my-dojo/conf/` and modify the values set for new configuration options.
#### 4.2.4/ Start Upgrade
```
> cd ~/dojo-app/docker/my-dojo
> ./dojo.sh upgrade
```
Confirm that you want to upgrade MyDojo with `Y` and `ENTER`.
The shell script is going to rebuild the Docker containers. MyDojo will be automatically restarted after the containers have been rebuilt.
#### 4.2.5/ Cleanup
```
> cd ~
> rm -rf samourai-dojo-vX.Y.Z
> rm samourai-dojo-vX.Y.Z.zip
```

11
doc/GET_xpub_import_status.md

@ -27,6 +27,17 @@ Status code 200 with JSON response:
} }
``` ```
```json
{
"status": "ok",
"data": {
"import_in_progress": true,
"status": "rescan",
"hits": 1143
}
}
```
#### Failure #### Failure
Status code 400 with JSON response: Status code 400 with JSON response:
```json ```json

14
docker/my-dojo/.env

@ -10,15 +10,15 @@
COMPOSE_CONVERT_WINDOWS_PATHS=1 COMPOSE_CONVERT_WINDOWS_PATHS=1
DOJO_VERSION_TAG=1.8.1 DOJO_VERSION_TAG=1.9.0
DOJO_DB_VERSION_TAG=1.2.0 DOJO_DB_VERSION_TAG=1.2.0
DOJO_BITCOIND_VERSION_TAG=1.8.0 DOJO_BITCOIND_VERSION_TAG=1.11.0
DOJO_NODEJS_VERSION_TAG=1.8.0 DOJO_NODEJS_VERSION_TAG=1.9.0
DOJO_NGINX_VERSION_TAG=1.5.0 DOJO_NGINX_VERSION_TAG=1.5.0
DOJO_TOR_VERSION_TAG=1.5.1 DOJO_TOR_VERSION_TAG=1.7.0
DOJO_EXPLORER_VERSION_TAG=1.3.0 DOJO_EXPLORER_VERSION_TAG=1.4.0
DOJO_INDEXER_VERSION_TAG=1.1.0 DOJO_INDEXER_VERSION_TAG=1.2.0
DOJO_WHIRLPOOL_VERSION_TAG=1.2.1 DOJO_WHIRLPOOL_VERSION_TAG=1.3.0
######################################### #########################################

8
docker/my-dojo/bitcoin/Dockerfile

@ -5,10 +5,10 @@ FROM debian:buster
# INSTALL BITCOIN # INSTALL BITCOIN
################################################################# #################################################################
ENV BITCOIN_HOME /home/bitcoin ENV BITCOIN_HOME /home/bitcoin
ENV BITCOIN_VERSION 0.20.1 ENV BITCOIN_VERSION 0.21.0
ENV BITCOIN_URL https://bitcoincore.org/bin/bitcoin-core-0.20.1/bitcoin-0.20.1-x86_64-linux-gnu.tar.gz ENV BITCOIN_URL https://bitcoincore.org/bin/bitcoin-core-0.21.0/bitcoin-0.21.0-x86_64-linux-gnu.tar.gz
ENV BITCOIN_SHA256 376194f06596ecfa40331167c39bc70c355f960280bd2a645fdbf18f66527397 ENV BITCOIN_SHA256 da7766775e3f9c98d7a9145429f2be8297c2672fe5b118fd3dc2411fb48e0032
ENV BITCOIN_ASC_URL https://bitcoincore.org/bin/bitcoin-core-0.20.1/SHA256SUMS.asc ENV BITCOIN_ASC_URL https://bitcoincore.org/bin/bitcoin-core-0.21.0/SHA256SUMS.asc
ENV BITCOIN_PGP_KS_URI hkp://keyserver.ubuntu.com:80 ENV BITCOIN_PGP_KS_URI hkp://keyserver.ubuntu.com:80
ENV BITCOIN_PGP_KEY 01EA5486DE18A882D4C2684590C8019E36C2E964 ENV BITCOIN_PGP_KEY 01EA5486DE18A882D4C2684590C8019E36C2E964

14
docker/my-dojo/bitcoin/restart.sh

@ -4,22 +4,19 @@ set -e
echo "## Start bitcoind #############################" echo "## Start bitcoind #############################"
bitcoind_options=( bitcoind_options=(
-bind=172.28.1.5
-datadir=/home/bitcoin/.bitcoin -datadir=/home/bitcoin/.bitcoin
-printtoconsole=1 -printtoconsole=1
-dbcache=$BITCOIND_DB_CACHE -dbcache=$BITCOIND_DB_CACHE
-disablewallet=1 -disablewallet=1
-dns=$BITCOIND_DNS -dns=$BITCOIND_DNS
-dnsseed=$BITCOIND_DNSSEED -dnsseed=$BITCOIND_DNSSEED
-externalip=$(cat /var/lib/tor/hsv2bitcoind/hostname)
-listen=1
-maxconnections=$BITCOIND_MAX_CONNECTIONS -maxconnections=$BITCOIND_MAX_CONNECTIONS
-maxmempool=$BITCOIND_MAX_MEMPOOL -maxmempool=$BITCOIND_MAX_MEMPOOL
-mempoolexpiry=$BITCOIND_MEMPOOL_EXPIRY -mempoolexpiry=$BITCOIND_MEMPOOL_EXPIRY
-minrelaytxfee=$BITCOIND_MIN_RELAY_TX_FEE -minrelaytxfee=$BITCOIND_MIN_RELAY_TX_FEE
-port=8333 -port=8333
-proxy=172.28.1.4:9050 -proxy=172.28.1.4:9050
-rpcallowip=::/0 -rpcallowip=0.0.0.0/0
-rpcbind=172.28.1.5 -rpcbind=172.28.1.5
-rpcpassword=$BITCOIND_RPC_PASSWORD -rpcpassword=$BITCOIND_RPC_PASSWORD
-rpcport=28256 -rpcport=28256
@ -32,6 +29,13 @@ bitcoind_options=(
-zmqpubrawtx=tcp://0.0.0.0:9501 -zmqpubrawtx=tcp://0.0.0.0:9501
) )
if [ "$BITCOIND_LISTEN_MODE" == "on" ]; then
bitcoind_options+=(-listen=1)
bitcoind_options+=(-bind=172.28.1.5)
bitcoind_options+=(-externalip=$(cat /var/lib/tor/hsv2bitcoind/hostname))
bitcoind_options+=(-externalip=$(cat /var/lib/tor/hsv3bitcoind/hostname))
fi
if [ "$BITCOIND_RPC_EXTERNAL" == "on" ]; then if [ "$BITCOIND_RPC_EXTERNAL" == "on" ]; then
bitcoind_options+=(-zmqpubhashtx=tcp://0.0.0.0:9500) bitcoind_options+=(-zmqpubhashtx=tcp://0.0.0.0:9500)
bitcoind_options+=(-zmqpubrawblock=tcp://0.0.0.0:9503) bitcoind_options+=(-zmqpubrawblock=tcp://0.0.0.0:9503)
@ -41,7 +45,7 @@ if [ "$COMMON_BTC_NETWORK" == "testnet" ]; then
bitcoind_options+=(-testnet) bitcoind_options+=(-testnet)
fi fi
bitcoind "${bitcoind_options[@]}" bitcoind "${bitcoind_options[@]}" || true
# Keep the container up # Keep the container up
while true while true

5
docker/my-dojo/conf/docker-bitcoind.conf.tpl

@ -39,6 +39,11 @@ BITCOIND_MEMPOOL_EXPIRY=72
# Type: numeric # Type: numeric
BITCOIND_MIN_RELAY_TX_FEE=0.00001 BITCOIND_MIN_RELAY_TX_FEE=0.00001
# Allow incoming connections
# This parameter is inactive if BITCOIND_INSTALL is set to 'off'
# Values: on | off
BITCOIND_LISTEN_MODE=on
# #
# EXPERT SETTINGS # EXPERT SETTINGS

4
docker/my-dojo/conf/docker-whirlpool.conf.tpl

@ -10,6 +10,10 @@ WHIRLPOOL_INSTALL=off
# Value: on | off # Value: on | off
WHIRLPOOL_RESYNC=off WHIRLPOOL_RESYNC=off
# Contact coordinator through its onion address or clearnet address
# Set to on for onion address, set to off for clearnet address
# Value: on | off
WHIRLPOOL_COORDINATOR_ONION=on
# #
# EXPERT SETTINGS # EXPERT SETTINGS

1
docker/my-dojo/docker-compose.yaml

@ -92,6 +92,7 @@ services:
context: ./tor context: ./tor
env_file: env_file:
- ./.env - ./.env
- ./conf/docker-bitcoind.conf
- ./conf/docker-explorer.conf - ./conf/docker-explorer.conf
- ./conf/docker-whirlpool.conf - ./conf/docker-whirlpool.conf
- ./conf/docker-tor.conf - ./conf/docker-tor.conf

175
docker/my-dojo/dojo.sh

@ -55,7 +55,7 @@ select_yaml_files() {
# Docker up # Docker up
docker_up() { docker_up() {
yamlFiles=$(select_yaml_files) yamlFiles=$(select_yaml_files)
eval "docker-compose $yamlFiles up $1 -d" eval "docker-compose $yamlFiles up $@ -d"
} }
# Start # Start
@ -74,7 +74,7 @@ start() {
# Stop # Stop
stop() { stop() {
echo "Preparing shutdown of Dojo. Please wait." echo "Preparing shutdown of Dojo. Please wait."
# Check if dojo is running (check the db container) # Check if dojo is running (check the db container)
isRunning=$(docker inspect --format="{{.State.Running}}" db 2> /dev/null) isRunning=$(docker inspect --format="{{.State.Running}}" db 2> /dev/null)
if [ $? -eq 1 ] || [ "$isRunning" == "false" ]; then if [ $? -eq 1 ] || [ "$isRunning" == "false" ]; then
echo "Dojo is already stopped." echo "Dojo is already stopped."
@ -83,8 +83,11 @@ stop() {
# Shutdown the bitcoin daemon # Shutdown the bitcoin daemon
if [ "$BITCOIND_INSTALL" == "on" ]; then if [ "$BITCOIND_INSTALL" == "on" ]; then
# Renewal of bitcoind onion address # Renewal of bitcoind onion address
if [ "$BITCOIND_EPHEMERAL_HS" = "on" ]; then if [ "$BITCOIND_LISTEN_MODE" == "on" ]; then
$( docker exec -it tor rm -rf /var/lib/tor/hsv2bitcoind ) &> /dev/null if [ "$BITCOIND_EPHEMERAL_HS" = "on" ]; then
$( docker exec -it tor rm -rf /var/lib/tor/hsv2bitcoind ) &> /dev/null
$( docker exec -it tor rm -rf /var/lib/tor/hsv3bitcoind ) &> /dev/null
fi
fi fi
# Stop the bitcoin daemon # Stop the bitcoin daemon
$( docker exec -it bitcoind bitcoin-cli \ $( docker exec -it bitcoind bitcoin-cli \
@ -163,7 +166,7 @@ install() {
if [ $launchInstall -eq 0 ]; then if [ $launchInstall -eq 0 ]; then
pastInstallsfound=$(docker image ls | grep samouraiwallet/dojo-db | wc -l) pastInstallsfound=$(docker image ls | grep samouraiwallet/dojo-db | wc -l)
if [ $pastInstallsfound -ne 0 ]; then if [ $pastInstallsfound -ne 0 ]; then
# Past installation found. Ask confirmation forreinstall # Past installation found. Ask confirmation for reinstall
echo -e "\nWarning: Found traces of a previous installation of Dojo on this machine." echo -e "\nWarning: Found traces of a previous installation of Dojo on this machine."
echo "A new installation requires to remove these elements first." echo "A new installation requires to remove these elements first."
if [ $auto -eq 0 ]; then if [ $auto -eq 0 ]; then
@ -198,10 +201,19 @@ install() {
init_config_files init_config_files
# Build and start Dojo # Build and start Dojo
docker_up --remove-orphans docker_up --remove-orphans
# Display the logs buildResult=$?
if [ $noLog -eq 1 ]; then if [ $buildResult -eq 0 ]; then
logs "" 0 # Display the logs
if [ $noLog -eq 1 ]; then
logs "" 0
fi
else
# Return an error
echo -e "\nInstallation of Dojo failed. See the above error message."
exit $buildResult
fi fi
else
exit 1
fi fi
} }
@ -231,20 +243,8 @@ uninstall() {
fi fi
if [ $launchUninstall -eq 0 ]; then if [ $launchUninstall -eq 0 ]; then
docker-compose rm -f
yamlFiles=$(select_yaml_files) yamlFiles=$(select_yaml_files)
eval "docker-compose $yamlFiles down" eval "docker-compose $yamlFiles down --rmi all"
docker image rm -f samouraiwallet/dojo-db:"$DOJO_DB_VERSION_TAG"
docker image rm -f samouraiwallet/dojo-bitcoind:"$DOJO_BITCOIND_VERSION_TAG"
docker image rm -f samouraiwallet/dojo-explorer:"$DOJO_EXPLORER_VERSION_TAG"
docker image rm -f samouraiwallet/dojo-nodejs:"$DOJO_NODEJS_VERSION_TAG"
docker image rm -f samouraiwallet/dojo-nginx:"$DOJO_NGINX_VERSION_TAG"
docker image rm -f samouraiwallet/dojo-tor:"$DOJO_TOR_VERSION_TAG"
docker image rm -f samouraiwallet/dojo-indexer:"$DOJO_INDEXER_VERSION_TAG"
docker image rm -f samouraiwallet/dojo-whirlpool:"$DOJO_WHIRLPOOL_VERSION_TAG"
docker volume prune -f docker volume prune -f
return 0 return 0
else else
@ -258,13 +258,12 @@ del_images_for() {
# $2: most recent version of the image (do not delete this one) # $2: most recent version of the image (do not delete this one)
docker image ls | grep "$1" | sed "s/ \+/,/g" | cut -d"," -f2 | while read -r version ; do docker image ls | grep "$1" | sed "s/ \+/,/g" | cut -d"," -f2 | while read -r version ; do
if [ "$2" != "$version" ]; then if [ "$2" != "$version" ]; then
docker image rm "$1:$version" docker image rm -f "$1:$version"
fi fi
done done
} }
clean() { clean() {
docker image prune
del_images_for samouraiwallet/dojo-db "$DOJO_DB_VERSION_TAG" del_images_for samouraiwallet/dojo-db "$DOJO_DB_VERSION_TAG"
del_images_for samouraiwallet/dojo-bitcoind "$DOJO_BITCOIND_VERSION_TAG" del_images_for samouraiwallet/dojo-bitcoind "$DOJO_BITCOIND_VERSION_TAG"
del_images_for samouraiwallet/dojo-explorer "$DOJO_EXPLORER_VERSION_TAG" del_images_for samouraiwallet/dojo-explorer "$DOJO_EXPLORER_VERSION_TAG"
@ -273,6 +272,7 @@ clean() {
del_images_for samouraiwallet/dojo-tor "$DOJO_TOR_VERSION_TAG" del_images_for samouraiwallet/dojo-tor "$DOJO_TOR_VERSION_TAG"
del_images_for samouraiwallet/dojo-indexer "$DOJO_INDEXER_VERSION_TAG" del_images_for samouraiwallet/dojo-indexer "$DOJO_INDEXER_VERSION_TAG"
del_images_for samouraiwallet/dojo-whirlpool "$DOJO_WHIRLPOOL_VERSION_TAG" del_images_for samouraiwallet/dojo-whirlpool "$DOJO_WHIRLPOOL_VERSION_TAG"
docker image prune -f
} }
# Upgrade # Upgrade
@ -309,7 +309,16 @@ upgrade() {
if [ $launchUpgrade -eq 0 ]; then if [ $launchUpgrade -eq 0 ]; then
# Select yaml files # Select yaml files
yamlFiles=$(select_yaml_files) yamlFiles=$(select_yaml_files)
# Check if dojo is running (check the db container)
isRunning=$(docker inspect --format="{{.State.Running}}" db 2> /dev/null)
if [ $? -eq 1 ] || [ "$isRunning" == "false" ]; then
echo -e "\nChecked that Dojo isn't running."
else
echo " "
stop
fi
# Update config files # Update config files
echo -e "\nPreparing the upgrade of Dojo.\n"
update_config_files update_config_files
# Cleanup # Cleanup
cleanup cleanup
@ -318,51 +327,105 @@ upgrade() {
export BITCOIND_RPC_EXTERNAL_IP export BITCOIND_RPC_EXTERNAL_IP
# Rebuild the images (with or without cache) # Rebuild the images (with or without cache)
if [ $noCache -eq 0 ]; then if [ $noCache -eq 0 ]; then
eval "docker-compose $yamlFiles build --no-cache" echo -e "\nDeleting Dojo containers and images."
else eval "docker-compose $yamlFiles down --rmi all"
eval "docker-compose $yamlFiles build"
fi fi
# Start Dojo echo -e "\nStarting the upgrade of Dojo.\n"
docker_up --remove-orphans docker_up --build --force-recreate --remove-orphans
# Post start clean-up buildResult=$?
post_start_cleanup if [ $buildResult -eq 0 ]; then
# Update the database # Post start clean-up
update_dojo_db clean
# Display the logs post_start_cleanup
if [ $noLog -eq 1 ]; then # Update the database
logs "" 0 update_dojo_db
# Display the logs
if [ $noLog -eq 1 ]; then
logs "" 0
fi
else
# Return an error
echo -e "\nUpgrade of Dojo failed. See the above error message."
exit $buildResult
fi fi
else
exit 1
fi fi
} }
# Display the onion address # Display the onion addresses
onion() { onion() {
version=3
# Extract version arguments
if [ $# -gt 0 ]; then
for option in $@
do
case "$option" in
v2 ) version=2 ;;
v3 ) version=3 ;;
* ) break ;;
esac
done
fi
echo " " echo " "
echo "WARNING: Do not share these onion addresses with anyone!" echo "WARNING: Do not share these onion addresses with anyone!"
echo " To allow another person to use this Dojo with their Samourai Wallet," echo " To allow another person to use this Dojo with their Samourai Wallet,"
echo " you should share the QRCodes provided by the Maintenance Tool." echo " you should share the QRCodes provided by the Maintenance Tool."
echo " " echo " "
V3_ADDR=$( docker exec -it tor cat /var/lib/tor/hsv3dojo/hostname ) if [ $version -eq 3 ]; then
echo "Dojo API and Maintenance Tool = $V3_ADDR" # V3 onion addresses
echo " " V3_ADDR=$( docker exec -it tor cat /var/lib/tor/hsv3dojo/hostname )
echo "Dojo API and Maintenance Tool = $V3_ADDR"
if [ "$EXPLORER_INSTALL" == "on" ]; then
V3_ADDR_EXPLORER=$( docker exec -it tor cat /var/lib/tor/hsv3explorer/hostname )
echo "Block Explorer = $V3_ADDR_EXPLORER"
echo " " echo " "
fi
if [ "$WHIRLPOOL_INSTALL" == "on" ]; then if [ "$EXPLORER_INSTALL" == "on" ]; then
V3_ADDR_WHIRLPOOL=$( docker exec -it tor cat /var/lib/tor/hsv3whirlpool/hostname ) V3_ADDR_EXPLORER=$( docker exec -it tor cat /var/lib/tor/hsv3explorer/hostname )
echo "Your private Whirlpool client (do not share) = $V3_ADDR_WHIRLPOOL" echo "Block Explorer = $V3_ADDR_EXPLORER"
echo " " echo " "
fi fi
if [ "$BITCOIND_INSTALL" == "on" ]; then if [ "$WHIRLPOOL_INSTALL" == "on" ]; then
V2_ADDR_BTCD=$( docker exec -it tor cat /var/lib/tor/hsv2bitcoind/hostname ) V3_ADDR_WHIRLPOOL=$( docker exec -it tor cat /var/lib/tor/hsv3whirlpool/hostname )
echo "Your local bitcoind (do not share) = $V2_ADDR_BTCD" echo "Your private Whirlpool client (do not share) = $V3_ADDR_WHIRLPOOL"
echo " "
fi
if [ "$BITCOIND_INSTALL" == "on" ]; then
if [ "$BITCOIND_LISTEN_MODE" == "on" ]; then
V3_ADDR_BTCD=$( docker exec -it tor cat /var/lib/tor/hsv3bitcoind/hostname )
echo "Your local bitcoind (do not share) = $V3_ADDR_BTCD"
echo " "
fi
fi
else
# v2 onion addresses
V2_ADDR=$( docker exec -it tor cat /var/lib/tor/hsv2dojo/hostname )
echo "Dojo API and Maintenance Tool = $V2_ADDR"
echo " " echo " "
if [ "$EXPLORER_INSTALL" == "on" ]; then
V2_ADDR_EXPLORER=$( docker exec -it tor cat /var/lib/tor/hsv2explorer/hostname )
echo "Block Explorer = $V2_ADDR_EXPLORER"
echo " "
fi
if [ "$WHIRLPOOL_INSTALL" == "on" ]; then
V2_ADDR_WHIRLPOOL=$( docker exec -it tor cat /var/lib/tor/hsv2whirlpool/hostname )
echo "Your private Whirlpool client (do not share) = $V2_ADDR_WHIRLPOOL"
echo " "
fi
if [ "$BITCOIND_INSTALL" == "on" ]; then
if [ "$BITCOIND_LISTEN_MODE" == "on" ]; then
V2_ADDR_BTCD=$( docker exec -it tor cat /var/lib/tor/hsv2bitcoind/hostname )
echo "Your local bitcoind (do not share) = $V2_ADDR_BTCD"
echo " "
fi
fi
fi fi
} }
@ -498,7 +561,11 @@ help() {
echo " Available options:" echo " Available options:"
echo " -n [VALUE] : display the last VALUE lines" echo " -n [VALUE] : display the last VALUE lines"
echo " " echo " "
echo " onion Display the Tor onion address allowing your wallet to access your dojo." echo " onion [version] Display the Tor onion addresses allowing your wallet to access your dojo."
echo " "
echo " Available versions:"
echo " v2: display Tor v2 onion addresses"
echo " v3 (default): display Tor v3 onion addresses"
echo " " echo " "
echo " restart Restart your dojo." echo " restart Restart your dojo."
echo " " echo " "
@ -592,7 +659,7 @@ case "$subcommand" in
logs "$module" $numlines logs "$module" $numlines
;; ;;
onion ) onion )
onion onion "$@"
;; ;;
restart ) restart )
restart restart

2
docker/my-dojo/explorer/Dockerfile

@ -3,7 +3,7 @@ FROM node:12-buster
ENV APP_DIR /home/node/app ENV APP_DIR /home/node/app
ENV EXPLORER_URL https://github.com/janoside/btc-rpc-explorer/archive ENV EXPLORER_URL https://github.com/janoside/btc-rpc-explorer/archive
ENV EXPLORER_VERSION 2.0.0 ENV EXPLORER_VERSION 2.1.0
# Install netcat # Install netcat
RUN set -ex && \ RUN set -ex && \

4
docker/my-dojo/indexer/Dockerfile

@ -1,7 +1,7 @@
FROM rust:1.42.0-slim-buster FROM rust:1.42.0-slim-buster
ENV INDEXER_HOME /home/indexer ENV INDEXER_HOME /home/indexer
ENV INDEXER_VERSION 0.3.0 ENV INDEXER_VERSION 0.4.0
ENV INDEXER_URL https://code.samourai.io/dojo/addrindexrs.git ENV INDEXER_URL https://code.samourai.io/dojo/addrindexrs.git
RUN apt-get update && \ RUN apt-get update && \
@ -36,7 +36,7 @@ RUN cd "$INDEXER_HOME" && \
git checkout "tags/v$INDEXER_VERSION" git checkout "tags/v$INDEXER_VERSION"
RUN cd "$INDEXER_HOME/addrindexrs" && \ RUN cd "$INDEXER_HOME/addrindexrs" && \
cargo install --path . cargo install --locked --path .
EXPOSE 50001 EXPOSE 50001

6
docker/my-dojo/install/upgrade-scripts.sh

@ -69,7 +69,7 @@ update_config_files() {
update_config_file ./conf/docker-whirlpool.conf ./conf/docker-whirlpool.conf.tpl update_config_file ./conf/docker-whirlpool.conf ./conf/docker-whirlpool.conf.tpl
echo "Initialized docker-whirlpool.conf" echo "Initialized docker-whirlpool.conf"
# Initialize config files for nginx and the maintenance tool # Initialize config files for nginx and the maintenance tool
if [ "$EXPLORER_INSTALL" == "on" ]; then if [ "$EXPLORER_INSTALL" == "on" ]; then
cp ./nginx/explorer.conf ./nginx/dojo-explorer.conf cp ./nginx/explorer.conf ./nginx/dojo-explorer.conf
else else
@ -106,7 +106,7 @@ update_config_file() {
cp -p $1 "$1.save" cp -p $1 "$1.save"
cp -p $2 $1 cp -p $2 $1
while IFS='=' read -r key val ; do while IFS='=' read -r key val ; do
if [[ $OSTYPE == darwin* ]]; then if [[ $OSTYPE == darwin* ]]; then
sed -i "" "s~$key=.*~$key=$val~g" "$1" sed -i "" "s~$key=.*~$key=$val~g" "$1"
else else
@ -179,7 +179,7 @@ cleanup() {
if [ -f ./bitcoin/bitcoin.conf ]; then if [ -f ./bitcoin/bitcoin.conf ]; then
rm ./bitcoin/bitcoin.conf rm ./bitcoin/bitcoin.conf
fi fi
} }
# Post start clean-up # Post start clean-up

4
docker/my-dojo/tor/Dockerfile

@ -1,9 +1,9 @@
FROM debian:buster FROM debian:buster
ENV TOR_HOME /var/lib/tor ENV TOR_HOME /var/lib/tor
ENV TOR_URL https://archive.torproject.org/tor-package-archive ENV TOR_URL https://dist.torproject.org
ENV TOR_MIRROR_URL https://tor.eff.org/dist ENV TOR_MIRROR_URL https://tor.eff.org/dist
ENV TOR_VERSION 0.4.5.4-rc ENV TOR_VERSION 0.4.4.7
ENV TOR_GPG_KS_URI hkp://keyserver.ubuntu.com:80 ENV TOR_GPG_KS_URI hkp://keyserver.ubuntu.com:80
ENV TOR_GPG_KEY1 0xEB5A896A28988BF5 ENV TOR_GPG_KEY1 0xEB5A896A28988BF5
ENV TOR_GPG_KEY2 0xC218525819F78451 ENV TOR_GPG_KEY2 0xC218525819F78451

28
docker/my-dojo/tor/restart.sh

@ -19,13 +19,28 @@ tor_options=(
--HiddenServiceDir /var/lib/tor/hsv3dojo --HiddenServiceDir /var/lib/tor/hsv3dojo
--HiddenServiceVersion 3 --HiddenServiceVersion 3
--HiddenServicePort "80 172.29.1.3:80" --HiddenServicePort "80 172.29.1.3:80"
--HiddenServiceDir /var/lib/tor/hsv2bitcoind
--HiddenServiceVersion 2
--HiddenServicePort "8333 172.28.1.5:8333"
--HiddenServiceDirGroupReadable 1
) )
if [ "$BITCOIND_INSTALL" == "on" ]; then
if [ "$BITCOIND_LISTEN_MODE" == "on" ]; then
tor_options+=(--HiddenServiceDir /var/lib/tor/hsv2bitcoind)
tor_options+=(--HiddenServiceVersion 2)
tor_options+=(--HiddenServicePort "8333 172.28.1.5:8333")
tor_options+=(--HiddenServiceDirGroupReadable 1)
tor_options+=(--HiddenServiceDir /var/lib/tor/hsv3bitcoind)
tor_options+=(--HiddenServiceVersion 3)
tor_options+=(--HiddenServicePort "8333 172.28.1.5:8333")
tor_options+=(--HiddenServiceDirGroupReadable 1)
fi
fi
if [ "$EXPLORER_INSTALL" == "on" ]; then if [ "$EXPLORER_INSTALL" == "on" ]; then
tor_options+=(--HiddenServiceDir /var/lib/tor/hsv2explorer)
tor_options+=(--HiddenServiceVersion 2)
tor_options+=(--HiddenServicePort "80 172.29.1.3:9080")
tor_options+=(--HiddenServiceDirGroupReadable 1)
tor_options+=(--HiddenServiceDir /var/lib/tor/hsv3explorer) tor_options+=(--HiddenServiceDir /var/lib/tor/hsv3explorer)
tor_options+=(--HiddenServiceVersion 3) tor_options+=(--HiddenServiceVersion 3)
tor_options+=(--HiddenServicePort "80 172.29.1.3:9080") tor_options+=(--HiddenServicePort "80 172.29.1.3:9080")
@ -33,6 +48,11 @@ if [ "$EXPLORER_INSTALL" == "on" ]; then
fi fi
if [ "$WHIRLPOOL_INSTALL" == "on" ]; then if [ "$WHIRLPOOL_INSTALL" == "on" ]; then
tor_options+=(--HiddenServiceDir /var/lib/tor/hsv2whirlpool)
tor_options+=(--HiddenServiceVersion 2)
tor_options+=(--HiddenServicePort "80 172.29.1.3:8898")
tor_options+=(--HiddenServiceDirGroupReadable 1)
tor_options+=(--HiddenServiceDir /var/lib/tor/hsv3whirlpool) tor_options+=(--HiddenServiceDir /var/lib/tor/hsv3whirlpool)
tor_options+=(--HiddenServiceVersion 3) tor_options+=(--HiddenServiceVersion 3)
tor_options+=(--HiddenServicePort "80 172.29.1.3:8898") tor_options+=(--HiddenServicePort "80 172.29.1.3:8898")

10
docker/my-dojo/whirlpool/Dockerfile

@ -19,9 +19,9 @@ RUN set -ex && \
mkdir -p "$WHIRLPOOL_DIR" mkdir -p "$WHIRLPOOL_DIR"
# Install Tor # Install Tor
ENV WHIRLPOOL_TOR_URL https://archive.torproject.org/tor-package-archive ENV WHIRLPOOL_TOR_URL https://dist.torproject.org
ENV WHIRLPOOL_TOR_MIRROR_URL https://tor.eff.org/dist ENV WHIRLPOOL_TOR_MIRROR_URL https://tor.eff.org/dist
ENV WHIRLPOOL_TOR_VERSION 0.4.5.4-rc ENV WHIRLPOOL_TOR_VERSION 0.4.4.7
ENV WHIRLPOOL_TOR_GPG_KS_URI hkp://keyserver.ubuntu.com:80 ENV WHIRLPOOL_TOR_GPG_KS_URI hkp://keyserver.ubuntu.com:80
ENV WHIRLPOOL_TOR_GPG_KEY1 0xEB5A896A28988BF5 ENV WHIRLPOOL_TOR_GPG_KEY1 0xEB5A896A28988BF5
ENV WHIRLPOOL_TOR_GPG_KEY2 0xC218525819F78451 ENV WHIRLPOOL_TOR_GPG_KEY2 0xC218525819F78451
@ -60,10 +60,10 @@ RUN set -ex && \
# Install whirlpool-cli # Install whirlpool-cli
ENV WHIRLPOOL_URL https://code.samourai.io/whirlpool/whirlpool-client-cli/uploads ENV WHIRLPOOL_URL https://code.samourai.io/whirlpool/whirlpool-client-cli/uploads
ENV WHIRLPOOL_VERSION 0.10.8 ENV WHIRLPOOL_VERSION 0.10.9
ENV WHIRLPOOL_VERSION_HASH 7998ea5a9bb180451616809bc346b9ac ENV WHIRLPOOL_VERSION_HASH 602666c59f95ce72f1466f72d9c853e3
ENV WHIRLPOOL_JAR "whirlpool-client-cli-$WHIRLPOOL_VERSION-run.jar" ENV WHIRLPOOL_JAR "whirlpool-client-cli-$WHIRLPOOL_VERSION-run.jar"
ENV WHIRLPOOL_SHA256 62e17b6020d0821a98e99ebb773b46191770ec186ceaa3e616a428f5cafe9f49 ENV WHIRLPOOL_SHA256 9de3ceaff6e8cc0849bde58bc9e17b9c602352df8659adc67ab95b39cf046e4c
RUN set -ex && \ RUN set -ex && \
cd "$WHIRLPOOL_DIR" && \ cd "$WHIRLPOOL_DIR" && \

8
docker/my-dojo/whirlpool/restart.sh

@ -9,9 +9,9 @@ whirlpool_options=(
--cli.tor=true --cli.tor=true
--cli.torConfig.executable=/usr/local/bin/tor --cli.torConfig.executable=/usr/local/bin/tor
--cli.torConfig.coordinator.enabled=true --cli.torConfig.coordinator.enabled=true
--cli.torConfig.coordinator.onion=true
--cli.torConfig.backend.enabled=false --cli.torConfig.backend.enabled=false
--cli.torConfig.backend.onion=false --cli.torConfig.backend.onion=false
--cli.mix.liquidityClient=false
) )
if [ "$COMMON_BTC_NETWORK" == "testnet" ]; then if [ "$COMMON_BTC_NETWORK" == "testnet" ]; then
@ -22,6 +22,12 @@ else
whirlpool_options+=(--cli.dojo.url="http://${NGINX_IP:-172.30.1.3}:80/v2/") whirlpool_options+=(--cli.dojo.url="http://${NGINX_IP:-172.30.1.3}:80/v2/")
fi fi
if [ "$WHIRLPOOL_COORDINATOR_ONION" == "on" ]; then
whirlpool_options+=(--cli.torConfig.coordinator.onion=true)
else
whirlpool_options+=(--cli.torConfig.coordinator.onion=false)
fi
if [ "$WHIRLPOOL_RESYNC" == "on" ]; then if [ "$WHIRLPOOL_RESYNC" == "on" ]; then
whirlpool_options+=(--resync) whirlpool_options+=(--resync)
fi fi

4
keys/index-example.js

@ -15,7 +15,7 @@ module.exports = {
/* /*
* Dojo version * Dojo version
*/ */
dojoVersion: '1.8.0', dojoVersion: '1.9.0',
/* /*
* Bitcoind * Bitcoind
*/ */
@ -232,7 +232,7 @@ module.exports = {
* Testnet parameters * Testnet parameters
*/ */
testnet: { testnet: {
dojoVersion: '1.8.0', dojoVersion: '1.9.0',
bitcoind: { bitcoind: {
rpc: { rpc: {
user: 'user', user: 'user',

6
lib/bitcoind-rpc/fees.js

@ -14,7 +14,7 @@ const latestBlock = require('./latest-block')
/** /**
* A singleton providing information about network fees * A singleton providing information about network fees
*/ */
class Fees { class Fees {
@ -56,10 +56,10 @@ class Fees {
await util.seriesCall(this.targets, async tgt => { await util.seriesCall(this.targets, async tgt => {
try { try {
const level = await this.rpcClient.cmd('estimatesmartfee', tgt, this.feeType) const level = await this.rpcClient.cmd('estimatesmartfee', tgt, this.feeType)
this.fees[tgt] = Math.round(level.feerate * 1e5) this.fees[tgt] = (level.errors && level.errors.length > 0) ? 0 : Math.round(level.feerate * 1e5)
} catch(e) { } catch(e) {
Logger.error(e, 'Bitcoind RPC : Fees.refresh()') Logger.error(e, 'Bitcoind RPC : Fees.refresh()')
delete this.fees[tgt] this.fees[tgt] = 0
} }
}) })

6
lib/indexer-rpc/rpc-client.js

@ -181,14 +181,14 @@ class RpcClient {
throw new Error(JSON.stringify(parsed.error)) throw new Error(JSON.stringify(parsed.error))
// Add the parsed reponse to the array of responses // Add the parsed reponse to the array of responses
if (batched) { if (batched) {
responses = parsed.map(p => { return {idxAddr: p.id, txs: p.result} }) responses = parsed.map(p => { return {id: p.id, response: p.result} })
} else { } else {
responses.push({idxAddr: parsed.id, txs: parsed.result}) responses.push({id: parsed.id, response: parsed.result})
} }
// Reset the response // Reset the response
response = '' response = ''
// If all responses have been received // If all responses have been received
// close the connection // close the connection
if (responses.length == data.length) if (responses.length == data.length)
conn.end() conn.end()
} catch (err) { } catch (err) {

14
lib/remote-importer/bitcoind-wrapper.js

@ -6,6 +6,7 @@
const bitcoin = require('bitcoinjs-lib') const bitcoin = require('bitcoinjs-lib')
const RpcClient = require('../bitcoind-rpc/rpc-client') const RpcClient = require('../bitcoind-rpc/rpc-client')
const rpcLatestBlock = require('../bitcoind-rpc/latest-block')
const Logger = require('../logger') const Logger = require('../logger')
const network = require('../bitcoin/network') const network = require('../bitcoin/network')
const activeNet = network.network const activeNet = network.network
@ -77,7 +78,7 @@ class BitcoindWrapper extends Wrapper {
txids: [] txids: []
} }
} }
return ret return ret
} }
@ -113,7 +114,7 @@ class BitcoindWrapper extends Wrapper {
} }
const aRet = Object.values(ret) const aRet = Object.values(ret)
for (let i in aRet) { for (let i in aRet) {
if (filterAddr && aRet[i].ntx > keys.addrFilterThreshold) { if (filterAddr && aRet[i].ntx > keys.addrFilterThreshold) {
Logger.info(`Importer : Import of ${aRet[i].address} rejected (too many transactions - ${aRet[i].ntx})`) Logger.info(`Importer : Import of ${aRet[i].address} rejected (too many transactions - ${aRet[i].ntx})`)
@ -124,6 +125,15 @@ class BitcoindWrapper extends Wrapper {
return aRet return aRet
} }
/**
* Retrieve the height of the chaintip for the remote source
* @returns {Promise} returns an object
* {chainTipHeight: <chaintip_height>}
*/
async getChainTipHeight() {
return {'chainTipHeight': rpcLatestBlock.height}
}
} }
module.exports = BitcoindWrapper module.exports = BitcoindWrapper

33
lib/remote-importer/esplora-wrapper.js

@ -4,7 +4,7 @@
*/ */
'use strict' 'use strict'
const rp = require('request-promise-native') const axios = require('axios')
const addrHelper = require('../bitcoin/addresses-helper') const addrHelper = require('../bitcoin/addresses-helper')
const util = require('../util') const util = require('../util')
const Logger = require('../logger') const Logger = require('../logger')
@ -34,15 +34,21 @@ class EsploraWrapper extends Wrapper {
const params = { const params = {
url: `${this.base}${route}`, url: `${this.base}${route}`,
method: 'GET', method: 'GET',
json: true, responseType: 'json',
timeout: 15000 timeout: 15000,
headers: {
'User-Agent': 'Dojo'
}
} }
// Sets socks proxy agent if required // Sets socks proxy agent if required
if (keys.indexer.socks5Proxy != null) if (keys.indexer.socks5Proxy != null) {
params['agent'] = this.socksProxyAgent params['httpAgent'] = this.socksProxyAgent
params['httpsAgent'] = this.socksProxyAgent
}
return rp(params) const result = await axios(params)
return result.data
} }
/** /**
@ -123,6 +129,19 @@ class EsploraWrapper extends Wrapper {
return ret return ret
} }
/**
* Retrieve the height of the chaintip for the remote source
* @returns {Promise} returns an object
* {chainTipHeight: <chaintip_height>}
*/
async getChainTipHeight() {
let chainTipHeight = null
const result = await this._get(`/api/blocks/tip/height`)
if (result != null)
chainTipHeight = parseInt(result)
return {'chainTipHeight': chainTipHeight}
}
} }
// Esplora returns a max of 25 txs per page // Esplora returns a max of 25 txs per page

29
lib/remote-importer/local-indexer-wrapper.js

@ -16,7 +16,7 @@ const Wrapper = require('./wrapper')
/** /**
* Wrapper for a local indexer * Wrapper for a local indexer
* Currently supports indexers * Currently supports indexers
* providing a RPC API compliant * providing a RPC API compliant
* with a subset of the electrum protocol * with a subset of the electrum protocol
*/ */
class LocalIndexerWrapper extends Wrapper { class LocalIndexerWrapper extends Wrapper {
@ -64,7 +64,7 @@ class LocalIndexerWrapper extends Wrapper {
scriptHash scriptHash
) )
for (let r of results.txs) { for (let r of results.response) {
ret.txids.push(r.tx_hash) ret.txids.push(r.tx_hash)
ret.ntx++ ret.ntx++
} }
@ -77,7 +77,7 @@ class LocalIndexerWrapper extends Wrapper {
txids: [] txids: []
} }
} }
return ret return ret
} }
@ -109,8 +109,8 @@ class LocalIndexerWrapper extends Wrapper {
: await this.client.sendRequests(commands) : await this.client.sendRequests(commands)
for (let r of results) { for (let r of results) {
const addr = addresses[r.idxAddr] const addr = addresses[r.id]
const txids = r.txs.map(t => t.tx_hash) const txids = r.response.map(t => t.tx_hash)
ret[addr] = { ret[addr] = {
address: addr, address: addr,
@ -131,12 +131,31 @@ class LocalIndexerWrapper extends Wrapper {
return aRet return aRet
} }
/**
* Retrieve the height of the chaintip for the remote source
* @returns {Promise} returns an object
* {chainTipHeight: <chaintip_height>}
*/
async getChainTipHeight() {
let chainTipHeight = null
const result = await this.client.sendRequest(
LocalIndexerWrapper.HEADERS_SUBSCRIBE_RPC_CMD,
null
)
if (result != null && result['response'] != null && result['response']['height'] != null)
chainTipHeight = parseInt(result['response']['height'])
return {'chainTipHeight': chainTipHeight}
}
} }
/** /**
* Get history RPC command (Electrum protocol) * Get history RPC command (Electrum protocol)
*/ */
LocalIndexerWrapper.GET_HISTORY_RPC_CMD = 'blockchain.scripthash.get_history' LocalIndexerWrapper.GET_HISTORY_RPC_CMD = 'blockchain.scripthash.get_history'
/**
* Get history RPC command (Electrum protocol)
*/
LocalIndexerWrapper.HEADERS_SUBSCRIBE_RPC_CMD = 'blockchain.headers.subscribe'
module.exports = LocalIndexerWrapper module.exports = LocalIndexerWrapper

35
lib/remote-importer/oxt-wrapper.js

@ -4,7 +4,7 @@
*/ */
'use strict' 'use strict'
const rp = require('request-promise-native') const axios = require('axios')
const Logger = require('../logger') const Logger = require('../logger')
const network = require('../bitcoin/network') const network = require('../bitcoin/network')
const keys = require('../../keys')[network.key] const keys = require('../../keys')[network.key]
@ -20,7 +20,7 @@ class OxtWrapper extends Wrapper {
* Constructor * Constructor
*/ */
constructor(url) { constructor(url) {
super(url, keys.indexer.socks5Proxy) super(url, keys.indexer.socks5Proxy)
} }
/** /**
@ -32,15 +32,21 @@ class OxtWrapper extends Wrapper {
const params = { const params = {
url: `${this.base}${route}`, url: `${this.base}${route}`,
method: 'GET', method: 'GET',
json: true, responseType: 'json',
timeout: 15000 timeout: 15000,
headers: {
'User-Agent': 'Dojo'
}
} }
// Sets socks proxy agent if required // Sets socks proxy agent if required
if (keys.indexer.socks5Proxy != null) if (keys.indexer.socks5Proxy != null) {
params['agent'] = this.socksProxyAgent params['httpAgent'] = this.socksProxyAgent
params['httpsAgent'] = this.socksProxyAgent
}
return rp(params) const result = await axios(params)
return result.data
} }
/** /**
@ -55,7 +61,7 @@ class OxtWrapper extends Wrapper {
// Try to retrieve more txs than the 1000 managed by the backend // Try to retrieve more txs than the 1000 managed by the backend
const uri = `/addresses/${address}/txids?count=${keys.addrFilterThreshold + 1}` const uri = `/addresses/${address}/txids?count=${keys.addrFilterThreshold + 1}`
const result = await this._get(uri) const result = await this._get(uri)
const ret = { const ret = {
address: address, address: address,
ntx: result.count, ntx: result.count,
@ -109,6 +115,19 @@ class OxtWrapper extends Wrapper {
return ret return ret
} }
/**
* Retrieve the height of the chaintip for the remote source
* @returns {Promise} returns an object
* {chainTipHeight: <chaintip_height>}
*/
async getChainTipHeight() {
let chainTipHeight = null
const result = await this._get(`/lastblock`)
if (result != null && result['data'].length == 1)
chainTipHeight = parseInt(result['data'][0]['height'])
return {'chainTipHeight': chainTipHeight}
}
} }
module.exports = OxtWrapper module.exports = OxtWrapper

60
lib/remote-importer/remote-importer.js

@ -25,7 +25,7 @@ if (network.key == 'bitcoin') {
/** /**
* A singleton providing tools * A singleton providing tools
* for importing HD and loose addresses from remote sources * for importing HD and loose addresses from remote sources
*/ */
class RemoteImporter { class RemoteImporter {
@ -34,6 +34,8 @@ class RemoteImporter {
* Constructor * Constructor
*/ */
constructor() { constructor() {
this.STATUS_RESCAN = 'rescan'
this.STATUS_IMPORT = 'import'
// Guard against overlapping imports // Guard against overlapping imports
this.importing = {} this.importing = {}
this.sources = new Sources() this.sources = new Sources()
@ -50,12 +52,12 @@ class RemoteImporter {
/** /**
* Check if a xpub is currently being imported or rescanned by Dojo * Check if a xpub is currently being imported or rescanned by Dojo
* Returns true if import/rescan is in progress, otherwise returns false * Returns infor about the operation if import/rescan is in progress, otherwise returns null
* @param {string} xpub - xpub * @param {string} xpub - xpub
* @returns {boolean} * @returns {object}
*/ */
importInProgress(xpub) { importInProgress(xpub) {
return this.importing[xpub] ? true : false return this.importing[xpub] ? this.importing[xpub] : null
} }
/** /**
@ -81,7 +83,7 @@ class RemoteImporter {
if (!txParents[txid]) if (!txParents[txid])
txParents[txid] = [] txParents[txid] = []
for (let i in tx.inputs) { for (let i in tx.inputs) {
const input = tx.inputs[i] const input = tx.inputs[i]
let prev = input.outpoint.txid let prev = input.outpoint.txid
@ -109,13 +111,13 @@ class RemoteImporter {
* Import a list of transactions associated to a list of addresses * Import a list of transactions associated to a list of addresses
* @param {object[]} addresses - array of addresses objects * @param {object[]} addresses - array of addresses objects
* @param {object[]} txns - array of transaction objects * @param {object[]} txns - array of transaction objects
* @returns {Promise} * @returns {Promise}
*/ */
async _importTransactions(addresses, txns) { async _importTransactions(addresses, txns) {
const addrIdMap = await db.getAddressesIds(addresses) const addrIdMap = await db.getAddressesIds(addresses)
// The transactions array must be topologically ordered, such that // The transactions array must be topologically ordered, such that
// entries earlier in the array MUST NOT depend upon any entry later // entries earlier in the array MUST NOT depend upon any entry later
// in the array. // in the array.
const txMaps = this._processTxsRelations(txns) const txMaps = this._processTxsRelations(txns)
const txOrdered = util.topologicalOrdering(txMaps.txParents, txMaps.txChildren) const txOrdered = util.topologicalOrdering(txMaps.txParents, txMaps.txChildren)
@ -147,7 +149,11 @@ class RemoteImporter {
return Promise.reject(errors.xpub.OVERLAP) return Promise.reject(errors.xpub.OVERLAP)
} }
this.importing[xpub] = true this.importing[xpub] = {
'status': this.STATUS_RESCAN,
'txs_ext': 0,
'txs_int': 0
}
const ts = hdaHelper.typeString(type) const ts = hdaHelper.typeString(type)
Logger.info(`Importer : Importing ${xpub} ${ts}`) Logger.info(`Importer : Importing ${xpub} ${ts}`)
@ -161,7 +167,7 @@ class RemoteImporter {
if (gapLimit if (gapLimit
&& ((keys.indexer.active == 'local_bitcoind') && ((keys.indexer.active == 'local_bitcoind')
|| (keys.indexer.active == 'local_indexer')) || (keys.indexer.active == 'local_indexer'))
) { ) {
gaps = [gapLimit, gapLimit] gaps = [gapLimit, gapLimit]
} }
@ -182,6 +188,11 @@ class RemoteImporter {
addresses = addresses.concat(result.addresses) addresses = addresses.concat(result.addresses)
} }
this.importing[xpub] = {
'status': this.STATUS_IMPORT,
'txs': txns.length
}
// Store the hdaccount and the addresses into the database // Store the hdaccount and the addresses into the database
await db.ensureHDAccountId(xpub, type) await db.ensureHDAccountId(xpub, type)
await db.addAddressesToHDAccount(xpub, addresses) await db.addAddressesToHDAccount(xpub, addresses)
@ -212,12 +223,12 @@ class RemoteImporter {
* 4. Set u = highest chain index of used address, go to 1 * 4. Set u = highest chain index of used address, go to 1
* 5. Store all in database * 5. Store all in database
* *
* @returns {object} returns * @returns {object} returns
* { * {
* addresses: [{address, chain, index}], * addresses: [{address, chain, index}],
* transactions: [{ * transactions: [{
* txid, * txid,
* version, * version,
* locktime, * locktime,
* created, // if known * created, // if known
* block: 'abcdef', // if confirmed * block: 'abcdef', // if confirmed
@ -290,6 +301,10 @@ class RemoteImporter {
} }
if (gotTransactions) { if (gotTransactions) {
if (c == 0)
this.importing[xpub]['txs_ext'] = Object.keys(txids).length
else
this.importing[xpub]['txs_int'] = Object.keys(txids).length
// We must go deeper // We must go deeper
const result = await this.xpubScan(xpub, c, d, u, G, type, txids) const result = await this.xpubScan(xpub, c, d, u, G, type, txids)
// Accumulate results from further down the rabbit hole // Accumulate results from further down the rabbit hole
@ -331,7 +346,7 @@ class RemoteImporter {
return true return true
Logger.info(`Importer : Importing ${addresses.join(',')}`) Logger.info(`Importer : Importing ${addresses.join(',')}`)
try { try {
const scanTx = [] const scanTx = []
const results = await this.sources.getAddresses(addresses, filterAddr) const results = await this.sources.getAddresses(addresses, filterAddr)
@ -373,7 +388,7 @@ class RemoteImporter {
if (N > 0) if (N > 0)
Logger.info(`Importer : Imported ${N} addresses in ${ts}s (${(dt/N).toFixed(0)} ms/addr)`) Logger.info(`Importer : Imported ${N} addresses in ${ts}s (${(dt/N).toFixed(0)} ms/addr)`)
for (let address of addresses) for (let address of addresses)
delete this.importing[address] delete this.importing[address]
@ -405,7 +420,7 @@ class RemoteImporter {
const filteredTxs = txs.filter(tx => (tx.block && tx.block.hash == block.blockHash)) const filteredTxs = txs.filter(tx => (tx.block && tx.block.hash == block.blockHash))
if (filteredTxs.length > 0) { if (filteredTxs.length > 0) {
const txids = filteredTxs.map(tx => tx.txid) const txids = filteredTxs.map(tx => tx.txid)
// Asynchronous confirmations // Asynchronous confirmations
db.confirmTransactions(txids, block.blockID) db.confirmTransactions(txids, block.blockID)
} }
} }
@ -430,12 +445,12 @@ class RemoteImporter {
} }
} }
await db.addOutputs(outputs) await db.addOutputs(outputs)
// Store the inputs in db // Store the inputs in db
const inputs = [] const inputs = []
const spent = {} const spent = {}
// Get any outputs spent by the inputs of this transaction, // Get any outputs spent by the inputs of this transaction,
// add those database outIDs to the corresponding inputs, and store. // add those database outIDs to the corresponding inputs, and store.
let outpoints = [] let outpoints = []
for (let tx of txs) for (let tx of txs)
@ -465,6 +480,15 @@ class RemoteImporter {
} }
} }
/**
* Retrieve the height of the chaintip for the remote source
* @returns {Promise} returns an object
* {chainTipHeight: <chaintip_height>}
*/
async getChainTipHeight() {
return this.sources.getChainTipHeight()
}
} }
module.exports = new RemoteImporter() module.exports = new RemoteImporter()

18
lib/remote-importer/sources.js

@ -38,7 +38,7 @@ class Sources {
try { try {
const result = await this.source.getAddress(address, filterAddr) const result = await this.source.getAddress(address, filterAddr)
if (result.ntx) if (result.ntx)
ret.ntx = result.ntx ret.ntx = result.ntx
else if (result.txids) else if (result.txids)
@ -81,6 +81,22 @@ class Sources {
} }
} }
/**
* Retrieve the height of the chaintip
* @returns {Promise} returns an object
* {chainTipHeight: <chaintip_height>}
*/
async getChainTipHeight() {
let ret = {'chainTipHeight': null}
try {
ret = await this.source.getChainTipHeight()
} catch(e) {
Logger.error(e, `Importer : Sources.getChainTipHeight() : Error while retrieving the chaintip`)
} finally {
return ret
}
}
} }
module.exports = Sources module.exports = Sources

326
package-lock.json

@ -1,6 +1,6 @@
{ {
"name": "samourai-dojo", "name": "samourai-dojo",
"version": "1.8.0", "version": "1.9.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -26,17 +26,6 @@
"es6-promisify": "5.0.0" "es6-promisify": "5.0.0"
} }
}, },
"ajv": {
"version": "6.12.3",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz",
"integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==",
"requires": {
"fast-deep-equal": "3.1.3",
"fast-json-stable-stringify": "2.1.0",
"json-schema-traverse": "0.4.1",
"uri-js": "4.2.2"
}
},
"ansi-colors": { "ansi-colors": {
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz",
@ -124,19 +113,6 @@
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
}, },
"asn1": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
"integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
"requires": {
"safer-buffer": "2.1.2"
}
},
"assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
},
"async": { "async": {
"version": "1.5.2", "version": "1.5.2",
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
@ -150,20 +126,13 @@
"double-ended-queue": "2.1.0-0" "double-ended-queue": "2.1.0-0"
} }
}, },
"asynckit": { "axios": {
"version": "0.4.0", "version": "0.20.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz",
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==",
}, "requires": {
"aws-sign2": { "follow-redirects": "1.13.0"
"version": "0.7.0", }
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
},
"aws4": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz",
"integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA=="
}, },
"babel-runtime": { "babel-runtime": {
"version": "5.8.38", "version": "5.8.38",
@ -187,14 +156,6 @@
"safe-buffer": "5.2.1" "safe-buffer": "5.2.1"
} }
}, },
"bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
"requires": {
"tweetnacl": "0.14.5"
}
},
"bech32": { "bech32": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz",
@ -517,11 +478,6 @@
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz",
"integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs="
}, },
"caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
},
"chalk": { "chalk": {
"version": "2.4.2", "version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@ -639,14 +595,6 @@
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true "dev": true
}, },
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"requires": {
"delayed-stream": "1.0.0"
}
},
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -718,14 +666,6 @@
"sha.js": "2.4.11" "sha.js": "2.4.11"
} }
}, },
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
"requires": {
"assert-plus": "1.0.0"
}
},
"dasherize": { "dasherize": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/dasherize/-/dasherize-2.0.0.tgz", "resolved": "https://registry.npmjs.org/dasherize/-/dasherize-2.0.0.tgz",
@ -767,11 +707,6 @@
"object-keys": "1.1.1" "object-keys": "1.1.1"
} }
}, },
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"delegates": { "delegates": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
@ -818,15 +753,6 @@
"create-hmac": "1.1.7" "create-hmac": "1.1.7"
} }
}, },
"ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
"integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
"requires": {
"jsbn": "0.1.1",
"safer-buffer": "2.1.2"
}
},
"ecdsa-sig-formatter": { "ecdsa-sig-formatter": {
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
@ -1091,26 +1017,6 @@
"resolved": "https://registry.npmjs.org/express-unless/-/express-unless-0.3.1.tgz", "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-0.3.1.tgz",
"integrity": "sha1-JVfBRudb65A+LSR/m1ugFFJpbiA=" "integrity": "sha1-JVfBRudb65A+LSR/m1ugFFJpbiA="
}, },
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"extsprintf": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
},
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
},
"feature-policy": { "feature-policy": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/feature-policy/-/feature-policy-0.3.0.tgz", "resolved": "https://registry.npmjs.org/feature-policy/-/feature-policy-0.3.0.tgz",
@ -1169,20 +1075,10 @@
"is-buffer": "2.0.4" "is-buffer": "2.0.4"
} }
}, },
"forever-agent": { "follow-redirects": {
"version": "0.6.1", "version": "1.13.0",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz",
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA=="
},
"form-data": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
"requires": {
"asynckit": "0.4.0",
"combined-stream": "1.0.8",
"mime-types": "2.1.27"
}
}, },
"forwarded": { "forwarded": {
"version": "0.1.2", "version": "0.1.2",
@ -1244,14 +1140,6 @@
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true "dev": true
}, },
"getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
"requires": {
"assert-plus": "1.0.0"
}
},
"github-from-package": { "github-from-package": {
"version": "0.0.0", "version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
@ -1286,20 +1174,6 @@
"integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
"dev": true "dev": true
}, },
"har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
},
"har-validator": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
"requires": {
"ajv": "6.12.3",
"har-schema": "2.0.0"
}
},
"has": { "has": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@ -1445,16 +1319,6 @@
} }
} }
}, },
"http-signature": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
"requires": {
"assert-plus": "1.0.0",
"jsprim": "1.4.1",
"sshpk": "1.16.1"
}
},
"iconv-lite": { "iconv-lite": {
"version": "0.4.23", "version": "0.4.23",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
@ -1583,11 +1447,6 @@
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
"dev": true "dev": true
}, },
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
},
"js-yaml": { "js-yaml": {
"version": "3.13.1", "version": "3.13.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
@ -1598,26 +1457,6 @@
"esprima": "4.0.1" "esprima": "4.0.1"
} }
}, },
"jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
},
"json-schema": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
},
"json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
},
"jsonwebtoken": { "jsonwebtoken": {
"version": "8.5.1", "version": "8.5.1",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
@ -1642,17 +1481,6 @@
} }
} }
}, },
"jsprim": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
"integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
"requires": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
"json-schema": "0.2.3",
"verror": "1.10.0"
}
},
"jwa": { "jwa": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
@ -1999,11 +1827,6 @@
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
}, },
"oauth-sign": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
},
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@ -2150,11 +1973,6 @@
"sha.js": "2.4.11" "sha.js": "2.4.11"
} }
}, },
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
},
"picomatch": { "picomatch": {
"version": "2.2.2", "version": "2.2.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
@ -2216,11 +2034,6 @@
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
}, },
"psl": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
},
"pump": { "pump": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
@ -2230,11 +2043,6 @@
"once": "1.4.0" "once": "1.4.0"
} }
}, },
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"pushdata-bitcoin": { "pushdata-bitcoin": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz", "resolved": "https://registry.npmjs.org/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz",
@ -2307,51 +2115,6 @@
"resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.2.0.tgz", "resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.2.0.tgz",
"integrity": "sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA==" "integrity": "sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA=="
}, },
"request": {
"version": "2.88.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
"requires": {
"aws-sign2": "0.7.0",
"aws4": "1.10.0",
"caseless": "0.12.0",
"combined-stream": "1.0.8",
"extend": "3.0.2",
"forever-agent": "0.6.1",
"form-data": "2.3.3",
"har-validator": "5.1.3",
"http-signature": "1.2.0",
"is-typedarray": "1.0.0",
"isstream": "0.1.2",
"json-stringify-safe": "5.0.1",
"mime-types": "2.1.27",
"oauth-sign": "0.9.0",
"performance-now": "2.1.0",
"qs": "6.5.2",
"safe-buffer": "5.2.1",
"tough-cookie": "2.4.3",
"tunnel-agent": "0.6.0",
"uuid": "3.4.0"
}
},
"request-promise-core": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz",
"integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=",
"requires": {
"lodash": "4.17.19"
}
},
"request-promise-native": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz",
"integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=",
"requires": {
"request-promise-core": "1.1.1",
"stealthy-require": "1.1.1",
"tough-cookie": "2.4.3"
}
},
"require-directory": { "require-directory": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@ -2514,32 +2277,11 @@
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz",
"integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A="
}, },
"sshpk": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
"integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
"requires": {
"asn1": "0.2.4",
"assert-plus": "1.0.0",
"bcrypt-pbkdf": "1.0.2",
"dashdash": "1.14.1",
"ecc-jsbn": "0.1.2",
"getpass": "0.1.7",
"jsbn": "0.1.1",
"safer-buffer": "2.1.2",
"tweetnacl": "0.14.5"
}
},
"statuses": { "statuses": {
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
}, },
"stealthy-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
},
"string-width": { "string-width": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
@ -2691,22 +2433,6 @@
"is-number": "7.0.0" "is-number": "7.0.0"
} }
}, },
"tough-cookie": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
"requires": {
"psl": "1.8.0",
"punycode": "1.4.1"
},
"dependencies": {
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
}
}
},
"tunnel-agent": { "tunnel-agent": {
"version": "0.6.0", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@ -2715,11 +2441,6 @@
"safe-buffer": "5.2.1" "safe-buffer": "5.2.1"
} }
}, },
"tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
},
"type-is": { "type-is": {
"version": "1.6.18", "version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@ -2752,14 +2473,6 @@
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
}, },
"uri-js": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
"integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
"requires": {
"punycode": "2.1.1"
}
},
"util-deprecate": { "util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@ -2770,11 +2483,6 @@
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
}, },
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
},
"validator": { "validator": {
"version": "10.8.0", "version": "10.8.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-10.8.0.tgz", "resolved": "https://registry.npmjs.org/validator/-/validator-10.8.0.tgz",
@ -2793,16 +2501,6 @@
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
}, },
"verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
"requires": {
"assert-plus": "1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "1.3.0"
}
},
"websocket": { "websocket": {
"version": "1.0.28", "version": "1.0.28",
"resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.28.tgz", "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.28.tgz",

5
package.json

@ -1,6 +1,6 @@
{ {
"name": "samourai-dojo", "name": "samourai-dojo",
"version": "1.8.0", "version": "1.9.0",
"description": "Backend server for Samourai Wallet", "description": "Backend server for Samourai Wallet",
"main": "accounts/index.js", "main": "accounts/index.js",
"scripts": { "scripts": {
@ -15,6 +15,7 @@
"homepage": "https://code.samourai.io/dojo/samourai-dojo", "homepage": "https://code.samourai.io/dojo/samourai-dojo",
"dependencies": { "dependencies": {
"async-sema": "2.1.2", "async-sema": "2.1.2",
"axios": "0.20.0",
"bip39": "2.4.0", "bip39": "2.4.0",
"bitcoind-rpc-client": "0.3.1", "bitcoind-rpc-client": "0.3.1",
"bitcoinjs-lib": "5.1.4", "bitcoinjs-lib": "5.1.4",
@ -30,8 +31,6 @@
"mysql": "2.16.0", "mysql": "2.16.0",
"passport": "0.4.0", "passport": "0.4.0",
"passport-localapikey-update": "0.6.0", "passport-localapikey-update": "0.6.0",
"request": "2.88.0",
"request-promise-native": "1.0.5",
"socks-proxy-agent": "4.0.1", "socks-proxy-agent": "4.0.1",
"validator": "10.8.0", "validator": "10.8.0",
"websocket": "1.0.28", "websocket": "1.0.28",

17
static/admin/css/style.css

@ -265,7 +265,7 @@ td.table-value {
/* PAGES - COMMONS */ /* PAGES - COMMONS */
body.dmt { body.dmt {
min-height: 100vh; min-height: 100vh;
background-image: url("../icons/samourai-logo-loading.png"); background-image: url("../icons/samourai-logo.png");
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
} }
@ -453,6 +453,10 @@ button {
color: #76d776; color: #76d776;
} }
#indexer-status {
padding-bottom: 12px;
}
/* PAGES - PAIRING */ /* PAGES - PAIRING */
#qr-label, #qr-label,
#qr-explorer-label { #qr-explorer-label {
@ -530,6 +534,17 @@ button {
display: inline-block; display: inline-block;
} }
#xpubs-deletion-actions span {
display: inline;
}
#xpubs-deletion-actions input {
width: 50px;
margin-left: 5px;
margin-right: 5px;
display: inline-block;
}
#xpubs-tool-details #xpub-value { #xpubs-tool-details #xpub-value {
overflow: hidden; overflow: hidden;
} }

16
static/admin/dmt/addresses-tools/addresses-tools.js

@ -30,8 +30,7 @@ const screenAddressesToolsScript = {
lib_api.getExplorerPairingInfo().then(explorerInfo => { lib_api.getExplorerPairingInfo().then(explorerInfo => {
this.explorerInfo = explorerInfo this.explorerInfo = explorerInfo
}).catch(e => { }).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) lib_errors.processError(e)
console.log(e)
}) })
}, },
@ -68,14 +67,13 @@ const screenAddressesToolsScript = {
this.showImportForm(false) this.showImportForm(false)
} }
}).catch(e => { }).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) lib_errors.processError(e)
console.log(e)
throw e throw e
}) })
}, },
importAddress: function() { importAddress: function() {
lib_msg.displayMessage('Processing address import...'); lib_msg.displayMessage('Processing address import. Please wait...');
const jsonData = {'active': this.currentAddress} const jsonData = {'active': this.currentAddress}
return lib_api.getWallet(jsonData) return lib_api.getWallet(jsonData)
.then(result => { .then(result => {
@ -83,13 +81,12 @@ const screenAddressesToolsScript = {
lib_msg.displayInfo('Import complete') lib_msg.displayInfo('Import complete')
}) })
}).catch(e => { }).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) lib_errors.processError(e)
console.log(e)
}) })
}, },
rescanAddress: function() { rescanAddress: function() {
lib_msg.displayMessage('Processing address rescan...'); lib_msg.displayMessage('Processing address rescan. Please wait...');
return lib_api.getAddressRescan(this.currentAddress) return lib_api.getAddressRescan(this.currentAddress)
.then(result => { .then(result => {
this.hideRescanForm() this.hideRescanForm()
@ -97,8 +94,7 @@ const screenAddressesToolsScript = {
lib_msg.displayInfo('Rescan complete') lib_msg.displayInfo('Rescan complete')
}) })
}).catch(e => { }).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) lib_errors.processError(e)
console.log(e)
}) })
}, },

2
static/admin/dmt/blocks-rescan/blocks-rescan.html

@ -1,7 +1,7 @@
<div id="blocks-rescan"> <div id="blocks-rescan">
<h1>BLOCKS RESCAN</h1> <h1>BLOCKS RESCAN</h1>
<div class="box-context">Force the Tracker to rescan a range of blocks.</div> <div class="box-context">Force the Tracker to rescan a small range of blocks.</div>
<div class="row box-main"> <div class="row box-main">
<div id="blocks-rescan-form" class="box fullwidth"> <div id="blocks-rescan-form" class="box fullwidth">

3
static/admin/dmt/blocks-rescan/blocks-rescan.js

@ -32,8 +32,7 @@ const screenBlocksRescanScript = {
const msg = `successfully rescanned blocks between height ${fromHeightRes} and height ${toHeightRes}` const msg = `successfully rescanned blocks between height ${fromHeightRes} and height ${toHeightRes}`
lib_msg.displayInfo(msg) lib_msg.displayInfo(msg)
}).catch(e => { }).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) lib_errors.processError(e)
console.log(e)
}).then(() => { }).then(() => {
$('#rescan-from-height').val('') $('#rescan-from-height').val('')
$('#rescan-to-height').val('') $('#rescan-to-height').val('')

1
static/admin/dmt/index.html

@ -15,6 +15,7 @@
<script src="../lib/auth-utils.js"></script> <script src="../lib/auth-utils.js"></script>
<script src="../lib/format-utils.js"></script> <script src="../lib/format-utils.js"></script>
<script src="../lib/messages.js"></script> <script src="../lib/messages.js"></script>
<script src="../lib/errors-utils.js"></script>
<script src="index.js"></script> <script src="index.js"></script>
</head> </head>

3
static/admin/dmt/pairing/pairing.js

@ -27,8 +27,7 @@ const screenPairingScript = {
lib_msg.cleanMessagesUi() lib_msg.cleanMessagesUi()
return result return result
}).catch(e => { }).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) lib_errors.processError(e)
console.log(e)
return result return result
}) })
}, },

14
static/admin/dmt/pushtx/pushtx.js

@ -15,7 +15,7 @@ const pushtxScript = {
}, },
refreshPushTxStatus: function() { refreshPushTxStatus: function() {
lib_msg.displayMessage('Loading PushTx status info...'); //lib_msg.displayMessage('Loading PushTx status info...');
lib_api.getPushtxStatus().then(pushTxStatus => { lib_api.getPushtxStatus().then(pushTxStatus => {
if (pushTxStatus) { if (pushTxStatus) {
const data = pushTxStatus['data'] const data = pushTxStatus['data']
@ -23,19 +23,18 @@ const pushtxScript = {
$('#pushed-uptime').text(uptime) $('#pushed-uptime').text(uptime)
$('#pushed-count').text(data['push']['count']) $('#pushed-count').text(data['push']['count'])
$('#pushed-amount').text(data['push']['amount']) $('#pushed-amount').text(data['push']['amount'])
lib_msg.cleanMessagesUi() //lib_msg.cleanMessagesUi()
} }
}).catch(e => { }).catch(e => {
$('#pushed-uptime').text('-') $('#pushed-uptime').text('-')
$('#pushed-count').text('-') $('#pushed-count').text('-')
$('#pushed-amount').text('-') $('#pushed-amount').text('-')
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) lib_errors.processError(e)
console.log(e)
}) })
}, },
refreshScheduledTxsList: function() { refreshScheduledTxsList: function() {
lib_msg.displayMessage('Loading PushTx orchestrator status info...'); //lib_msg.displayMessage('Loading PushTx orchestrator status info...');
lib_api.getOrchestratorStatus().then(orchestrStatus => { lib_api.getOrchestratorStatus().then(orchestrStatus => {
if(orchestrStatus) { if(orchestrStatus) {
const data = orchestrStatus['data'] const data = orchestrStatus['data']
@ -45,11 +44,10 @@ const pushtxScript = {
this.processedSchedTxs.add(tx['schTxid']) this.processedSchedTxs.add(tx['schTxid'])
} }
} }
lib_msg.cleanMessagesUi() //lib_msg.cleanMessagesUi()
} }
}).catch(e => { }).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) lib_errors.processError(e)
console.log(e)
}) })
}, },

47
static/admin/dmt/status/status.html

@ -5,6 +5,7 @@
<div class="row box-main"> <div class="row box-main">
<div id="left-column" class="two-columns-left"> <div id="left-column" class="two-columns-left">
<div id="bitcoind-status" class="fullwidth box"> <div id="bitcoind-status" class="fullwidth box">
<div class="box-header">FULL NODE</div> <div class="box-header">FULL NODE</div>
<div class="spacer10"></div> <div class="spacer10"></div>
@ -41,8 +42,36 @@
</table> </table>
</div> </div>
</div> </div>
<div id="indexer-status" class="fullwidth box">
<div class="box-header">INDEXER</div>
<div class="spacer10"></div>
<div class="box-body">
<table>
<tr>
<td class="table-label">Status</td>
<td class="table-value" id="indexer-status-ind"></td>
</tr>
<tr>
<td class="table-label">Latest block</td>
<td class="table-value" id="indexer-chaintip"></td>
</tr>
<tr>
<td class="table-label">Indexer type</td>
<td class="table-value" id="indexer-type"></td>
</tr>
<tr>
<td class="table-label">URL</td>
<td class="table-value" id="indexer-url"></td>
</tr>
</table>
</div>
</div>
</div> </div>
<div id="right-column" class="two-columns-right"> <div id="right-column" class="two-columns-right">
<div id="tracker-status" class="fullwidth box"> <div id="tracker-status" class="fullwidth box">
<div class="box-header">TRACKER</div> <div class="box-header">TRACKER</div>
<div class="spacer10"></div> <div class="spacer10"></div>
@ -64,6 +93,23 @@
</div> </div>
</div> </div>
<div id="db-status" class="fullwidth box">
<div class="box-header">DOJO DB</div>
<div class="spacer10"></div>
<div class="box-body">
<table>
<tr>
<td class="table-label">Status</td>
<td class="table-value" id="db-status-ind">&#10003;</td>
</tr>
<tr>
<td class="table-label">Latest block</td>
<td class="table-value" id="db-chaintip"></td>
</tr>
</table>
</div>
</div>
<div id="web-status" class="fullwidth box"> <div id="web-status" class="fullwidth box">
<div class="box-header">WEB</div> <div class="box-header">WEB</div>
<div class="spacer10"></div> <div class="spacer10"></div>
@ -84,6 +130,7 @@
</table> </table>
</div> </div>
</div> </div>
</div> </div>
</div> </div>

123
static/admin/dmt/status/status.js

@ -1,6 +1,9 @@
const statusScript = { const statusScript = {
initPage: function() { initPage: function() {
this.chaintipBitcoind = 0
this.chaintipIndexer = 0
this.chaintipDb = 0
// Refresh API status // Refresh API status
setInterval(() => {this.refreshApiStatus()}, 60000) setInterval(() => {this.refreshApiStatus()}, 60000)
// Refresh PushTx status // Refresh PushTx status
@ -13,56 +16,126 @@ const statusScript = {
}, },
refreshApiStatus: function() { refreshApiStatus: function() {
lib_msg.displayMessage('Loading API status info...'); // Set default values displayed
this.setStatusIndicator('#db-status-ind', 'idle')
this.setStatusIndicator('#tracker-status-ind', 'idle')
this.setStatusIndicator('#indexer-status-ind', 'idle')
$('#tracker-uptime').text('-')
$('#tracker-chaintip').text('-')
$('#db-chaintip').text('-')
$('#indexer-chaintip').text('-')
$('#indexer-type').text('-')
$('#indexer-url').text('-')
//lib_msg.displayMessage('Loading API status info...');
return lib_api.getApiStatus().then(apiStatus => { return lib_api.getApiStatus().then(apiStatus => {
if (apiStatus) { if (apiStatus) {
$('#tracker-status-ind').html('&#10003;')
$('#tracker-status-ind').css('color', '#76d776')
$('#tracker-uptime').text(apiStatus['uptime']) $('#tracker-uptime').text(apiStatus['uptime'])
$('#tracker-chaintip').text(apiStatus['blocks'])
lib_msg.cleanMessagesUi() const blocks = apiStatus['blocks']
if (blocks) {
this.chaintipBitcoind = blocks
this.chaintipDb = blocks
$('#db-chaintip').text(blocks)
$('#tracker-chaintip').text(blocks)
this.setStatusIndicator('#db-status-ind', 'ok')
this.setStatusIndicator('#tracker-status-ind', 'ok')
} else {
this.setStatusIndicator('#db-status-ind', 'ko')
this.setStatusIndicator('#tracker-status-ind', 'ko')
}
if (apiStatus['indexer']) {
const indexerMaxHeight = apiStatus['indexer']['maxHeight']
if (indexerMaxHeight) {
this.chaintipIndexer = indexerMaxHeight
$('#indexer-chaintip').text(indexerMaxHeight)
this.setStatusIndicator('#indexer-status-ind', 'ok')
} else {
this.setStatusIndicator('#indexer-status-ind', 'ko')
}
const indexerType = apiStatus['indexer']['type']
if (indexerType)
$('#indexer-type').text(indexerType.replace(/_/g, ' '))
const indexerUrl = apiStatus['indexer']['url']
if (indexerUrl)
$('#indexer-url').text(indexerUrl)
}
this.checkChaintips()
//lib_msg.cleanMessagesUi()
} }
}).catch(e => { }).catch(e => {
$('#tracker-status-ind').text('X') this.setStatusIndicator('#db-status-ind', 'ko')
$('#tracker-status-ind').css('color', '#f77c7c') this.setStatusIndicator('#tracker-status-ind', 'ko')
$('#tracker-uptime').text('-') this.setStatusIndicator('#indexer-status-ind', 'ko')
$('#tracker-chaintip').text('-') lib_errors.processError(e)
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e))
console.log(e)
}) })
}, },
refreshPushTxStatus: function() { refreshPushTxStatus: function() {
lib_msg.displayMessage('Loading Tracker status info...'); // Set default values displayed
this.setStatusIndicator('#node-status-ind', 'idle')
$('#node-uptime').text('-')
$('#node-chaintip').text('-')
$('#node-version').text('-')
$('#node-network').text('-')
$('#node-conn').text('-')
$('#node-relay-fee').text('-')
//lib_msg.displayMessage('Loading Tracker status info...');
lib_api.getPushtxStatus().then(pushTxStatus => { lib_api.getPushtxStatus().then(pushTxStatus => {
if (pushTxStatus) { if (pushTxStatus) {
const data = pushTxStatus['data'] const data = pushTxStatus['data']
$('#node-status-ind').html('&#10003;') this.setStatusIndicator('#node-status-ind', 'ok')
$('#node-status-ind').css('color', '#76d776')
const uptime = lib_cmn.timePeriod(data['uptime']) const uptime = lib_cmn.timePeriod(data['uptime'])
$('#node-uptime').text(uptime) $('#node-uptime').text(uptime)
this.chaintipBitcoind = data['bitcoind']['blocks']
$('#node-chaintip').text(data['bitcoind']['blocks']) $('#node-chaintip').text(data['bitcoind']['blocks'])
$('#node-version').text(data['bitcoind']['version']) $('#node-version').text(data['bitcoind']['version'])
const network = data['bitcoind']['testnet'] == true ? 'testnet' : 'mainnet' const network = data['bitcoind']['testnet'] == true ? 'testnet' : 'mainnet'
$('#node-network').text(network) $('#node-network').text(network)
$('#node-conn').text(data['bitcoind']['conn']) $('#node-conn').text(data['bitcoind']['conn'])
$('#node-relay-fee').text(data['bitcoind']['relayfee']) $('#node-relay-fee').text(data['bitcoind']['relayfee'])
lib_msg.cleanMessagesUi() this.checkChaintips()
//lib_msg.cleanMessagesUi()
} }
}).catch(e => { }).catch(e => {
$('#node-status-ind').text('-') this.setStatusIndicator('#node-status-ind', 'ko')
$('#node-status-ind').css('color', '#f77c7c') lib_errors.processError(e)
$('#node-uptime').text('-')
$('#node-chaintip').text('-')
$('#node-version').text('-')
$('#node-network').text('-')
$('#node-conn').text('-')
$('#node-relay-fee').text('-')
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e))
console.log(e)
}) })
}, },
checkChaintips: function() {
if (this.chaintipBitcoind > this.chaintipDb) {
this.setStatusIndicator('#db-status-ind', 'desynchronized')
this.setStatusIndicator('#tracker-status-ind', 'desynchronized')
}
if (this.chaintipBitcoind > this.chaintipIndexer) {
this.setStatusIndicator('#indexer-status-ind', 'desynchronized')
} else if (this.chaintipBitcoind < this.chaintipIndexer) {
this.setStatusIndicator('#node-status-ind', 'desynchronized')
this.setStatusIndicator('#db-status-ind', 'desynchronized')
this.setStatusIndicator('#tracker-status-ind', 'desynchronized')
}
},
setStatusIndicator: function(id, status) {
if (status == 'ok') {
$(id).html('&#10003;')
$(id).css('color', '#76d776')
} else if (status == 'ko') {
$(id).html('X')
$(id).css('color', '#f77c7c')
} else if (status == 'desynchronized') {
$(id).html('&#10003;')
$(id).css('color', '#f0c649')
} else {
$(id).html('-')
$(id).css('color', '#efefef')
}
},
} }
screenScripts.set('#screen-status', statusScript) screenScripts.set('#screen-status', statusScript)

3
static/admin/dmt/txs-tools/txs-tools.js

@ -24,8 +24,7 @@ const screenTxsToolsScript = {
lib_api.getExplorerPairingInfo().then(explorerInfo => { lib_api.getExplorerPairingInfo().then(explorerInfo => {
this.explorerInfo = explorerInfo this.explorerInfo = explorerInfo
}).catch(e => { }).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) lib_errors.processError(e)
console.log(e)
}) })
}, },

2
static/admin/dmt/welcome/welcome.html

@ -26,7 +26,7 @@
<span class="item-descr">Check if a transaction is found in a block or in the mempool of your full node.</span> <span class="item-descr">Check if a transaction is found in a block or in the mempool of your full node.</span>
<span class="item">BLOCKS RESCAN</span> <span class="item">BLOCKS RESCAN</span>
<span class="item-descr">Rescan the transactions confirmed by the blocks in a given range.</span> <span class="item-descr">Force the Tracker to rescan a small range of blocks.<br/>For large rescans you should prefer XPUB or address rescans.</span>
<span class="items-category ">HELP</span> <span class="items-category ">HELP</span>

9
static/admin/dmt/xpubs-tools/xpubs-tools.html

@ -61,6 +61,7 @@
<div class="center"> <div class="center">
<button id="btn-xpub-details-rescan" class="btn btn-success" type="button">RESCAN THIS XPUB</button> <button id="btn-xpub-details-rescan" class="btn btn-success" type="button">RESCAN THIS XPUB</button>
<button id="btn-xpub-details-retype" class="btn btn-success" type="button">RETYPE THIS XPUB</button> <button id="btn-xpub-details-retype" class="btn btn-success" type="button">RETYPE THIS XPUB</button>
<button id="btn-xpub-details-delete" class="btn btn-success" type="button">DELETE THIS XPUB</button>
<button id="btn-xpub-details-reset" class="btn btn-success" type="button">SEARCH ANOTHER XPUB</button> <button id="btn-xpub-details-reset" class="btn btn-success" type="button">SEARCH ANOTHER XPUB</button>
</div> </div>
</div> </div>
@ -77,6 +78,14 @@
</div> </div>
</div> </div>
<div id="xpubs-deletion-actions" class="row box-main">
<div class="center">
<span>Do you want to delete this xpub?</span>
<button id="btn-xpub-delete-go" class="btn btn-success" type="button">DELETE</button>
<button id="btn-xpub-delete-cancel" class="btn btn-success" type="button">CANCEL</button>
</div>
</div>
<div id="xpubs-tool-details-row1" class="row box-main"> <div id="xpubs-tool-details-row1" class="row box-main">
<!-- GENERAL INFO --> <!-- GENERAL INFO -->
<div id="box-general" class="halfwidth-left box"> <div id="box-general" class="halfwidth-left box">

84
static/admin/dmt/xpubs-tools/xpubs-tools.js

@ -3,6 +3,7 @@ const screenXpubsToolsScript = {
explorerInfo: null, explorerInfo: null,
currentXpub: null, currentXpub: null,
isReimport: false, isReimport: false,
rescanStatusTimerId: null,
initPage: function() { initPage: function() {
this.getExplorerInfo() this.getExplorerInfo()
@ -10,8 +11,11 @@ const screenXpubsToolsScript = {
$('#btn-xpub-search-go').click(() => {this.searchXpub()}) $('#btn-xpub-search-go').click(() => {this.searchXpub()})
$('#btn-xpub-details-reset').click(() => {this.showSearchForm()}) $('#btn-xpub-details-reset').click(() => {this.showSearchForm()})
$('#btn-xpub-details-rescan').click(() => {this.showRescanForm()}) $('#btn-xpub-details-rescan').click(() => {this.showRescanForm()})
$('#btn-xpub-details-delete').click(() => {this.showDeletionForm()})
$('#btn-xpub-rescan-go').click(() => {this.rescanXpub()}) $('#btn-xpub-rescan-go').click(() => {this.rescanXpub()})
$('#btn-xpub-rescan-cancel').click(() => {this.hideRescanForm()}) $('#btn-xpub-rescan-cancel').click(() => {this.hideRescanForm()})
$('#btn-xpub-delete-go').click(() => {this.deleteXpub()})
$('#btn-xpub-delete-cancel').click(() => {this.hideDeletionForm()})
$('#btn-xpub-import-go').click(() => {this.importXpub()}) $('#btn-xpub-import-go').click(() => {this.importXpub()})
$('#btn-xpub-details-retype').click(() => {this.showImportForm(true)}) $('#btn-xpub-details-retype').click(() => {this.showImportForm(true)})
$('#btn-xpub-import-cancel').click(() => {this.hideImportForm(this.isReimport)}) $('#btn-xpub-import-cancel').click(() => {this.hideImportForm(this.isReimport)})
@ -24,6 +28,7 @@ const screenXpubsToolsScript = {
preparePage: function() { preparePage: function() {
this.hideRescanForm() this.hideRescanForm()
this.hideDeletionForm()
this.showSearchForm() this.showSearchForm()
$("#xpub").focus() $("#xpub").focus()
}, },
@ -32,8 +37,7 @@ const screenXpubsToolsScript = {
lib_api.getExplorerPairingInfo().then(explorerInfo => { lib_api.getExplorerPairingInfo().then(explorerInfo => {
this.explorerInfo = explorerInfo this.explorerInfo = explorerInfo
}).catch(e => { }).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) lib_errors.processError(e)
console.log(e)
}) })
}, },
@ -70,14 +74,13 @@ const screenXpubsToolsScript = {
this.showImportForm(false) this.showImportForm(false)
} }
}).catch(e => { }).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) lib_errors.processError(e)
console.log(e)
throw e throw e
}) })
}, },
importXpub: function() { importXpub: function() {
lib_msg.displayMessage('Processing xpub import...'); lib_msg.displayMessage('Processing xpub import. Please wait...');
const jsonData = { const jsonData = {
'xpub': this.currentXpub, 'xpub': this.currentXpub,
@ -95,35 +98,75 @@ const screenXpubsToolsScript = {
jsonData['segwit'] = 'bip84' jsonData['segwit'] = 'bip84'
} }
return lib_api.postXpub(jsonData) try {
.then(result => { lib_api.postXpub(jsonData)
// Wait for import completion and display progress
this.checkRescanStatus(() => {
this._searchXpub(this.currentXpub).then(() => { this._searchXpub(this.currentXpub).then(() => {
lib_msg.displayInfo('Import complete') lib_msg.displayInfo('Import complete')
}) })
}).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e))
console.log(e)
}) })
} catch(e) {
lib_errors.processError(e)
}
}, },
rescanXpub: function() { rescanXpub: function() {
lib_msg.displayMessage('Processing xpub rescan...'); lib_msg.displayMessage('Processing xpub rescan. Please wait...');
let startIdx = $('#rescan-start-idx').val() let startIdx = $('#rescan-start-idx').val()
startIdx = (startIdx == null) ? 0 : parseInt(startIdx) startIdx = (startIdx == null) ? 0 : parseInt(startIdx)
let lookahead = $('#rescan-lookahead').val() let lookahead = $('#rescan-lookahead').val()
lookahead = (lookahead == null) ? 100 : parseInt(lookahead) lookahead = (lookahead == null) ? 100 : parseInt(lookahead)
return lib_api.getXpubRescan(this.currentXpub, lookahead, startIdx)
.then(result => { try {
lib_api.getXpubRescan(this.currentXpub, lookahead, startIdx)
// Wait for rescan completion and display progress
this.checkRescanStatus(() => {
this.hideRescanForm() this.hideRescanForm()
this._searchXpub(this.currentXpub).then(() => { this._searchXpub(this.currentXpub).then(() => {
lib_msg.displayInfo('Rescan complete') lib_msg.displayInfo('Rescan complete')
}) })
})
} catch(e) {
lib_errors.processError(e)
}
},
deleteXpub: function() {
lib_msg.displayMessage('Deleting a xpub. Please wait...')
return lib_api.getXpubDelete(this.currentXpub)
.then(result => {
this.currentXpub = null
this.preparePage()
lib_msg.displayInfo('Xpub successfully deleted')
}).catch(e => { }).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) lib_errors.processError(e)
console.log(e)
}) })
}, },
checkRescanStatus: function(callback) {
this.rescanStatusTimerId = setTimeout(() => {
lib_api.getXpubRescanStatus(this.currentXpub)
.then(result => {
const data = result['data']
if (data['import_in_progress']) {
const lblOp = (data['status'] == 'rescan') ? 'Rescan' : 'Import'
const lblHits = (data['status'] == 'rescan') ? 'hits detected' : 'transactions imported'
const msg = `${lblOp} in progress (${data['hits']} ${lblHits})`
lib_msg.displayMessage(msg)
return this.checkRescanStatus(callback)
} else {
clearTimeout(this.rescanStatusTimerId)
return callback()
}
}).catch(e => {
lib_errors.processError(e)
lib_msg.displayMessage('Rescan in progress. Please wait...')
return this.checkRescanStatus(callback)
})
}, 1000)
},
setXpubDetails: function(xpubInfo) { setXpubDetails: function(xpubInfo) {
$('tr.tx-row').remove() $('tr.tx-row').remove()
$('tr.utxo-row').remove() $('tr.utxo-row').remove()
@ -254,6 +297,17 @@ const screenXpubsToolsScript = {
$('#xpubs-tool-actions').show() $('#xpubs-tool-actions').show()
}, },
showDeletionForm: function() {
$('#xpubs-tool-actions').hide()
$('#xpubs-deletion-actions').show()
lib_msg.cleanMessagesUi()
},
hideDeletionForm: function() {
$('#xpubs-deletion-actions').hide()
$('#xpubs-tool-actions').show()
},
} }
screenScripts.set('#screen-xpubs-tools', screenXpubsToolsScript) screenScripts.set('#screen-xpubs-tools', screenXpubsToolsScript)

BIN
static/admin/icons/samourai-logo-loading.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

BIN
static/admin/icons/samourai-logo-trans@2x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

BIN
static/admin/icons/samourai-logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

3
static/admin/index.html

@ -14,6 +14,7 @@
<script src="lib/api-wrapper.js"></script> <script src="lib/api-wrapper.js"></script>
<script src="lib/auth-utils.js"></script> <script src="lib/auth-utils.js"></script>
<script src="lib/messages.js"></script> <script src="lib/messages.js"></script>
<script src="lib/errors-utils.js"></script>
<script src="index.js"></script> <script src="index.js"></script>
</head> </head>
@ -22,7 +23,7 @@
<!-- WELCOME MESSAGE --> <!-- WELCOME MESSAGE -->
<div id="welcome-msg" class="row"> <div id="welcome-msg" class="row">
<div class="col-xs-12"> <div class="col-xs-12">
<img src="icons/samourai-logo-trans@2x.png" class="medium-icon"/> <img src="icons/samourai-logo.png" class="medium-icon"/>
<h1 class="title"><span>DOJO // MAINTENANCE TOOL</span> <span class="beta">beta</span></h1> <h1 class="title"><span>DOJO // MAINTENANCE TOOL</span> <span class="beta">beta</span></h1>
</div> </div>
</div> </div>

3
static/admin/index.js

@ -33,8 +33,7 @@ function login() {
} }
}, },
function (jqxhr) { function (jqxhr) {
let msg = lib_msg.extractJqxhrErrorMsg(jqxhr) lib_errors.processError(jqxhr)
lib_msg.displayErrors(msg)
} }
) )
} }

17
static/admin/lib/api-wrapper.js

@ -110,6 +110,23 @@ const lib_api = {
) )
}, },
/**
* Deletes a xpub
*/
getXpubDelete: function(xpub) {
let prefix = conf['prefixes']['support']
let uri = this.baseUri + '/' + prefix + '/xpub/' + xpub + '/delete'
return this.sendGetUriEncoded(uri, {})
},
/**
* Gets the status of a xpub rescan
*/
getXpubRescanStatus: function(xpub) {
let uri = this.baseUri + '/xpub/' + xpub + '/import/status'
return this.sendGetUriEncoded(uri, {})
},
/** /**
* Notifies the server of the new HD account for tracking. * Notifies the server of the new HD account for tracking.
*/ */

19
static/admin/lib/auth-utils.js

@ -9,6 +9,9 @@ const lib_auth = {
/* SessionStorage Key used for refresh token */ /* SessionStorage Key used for refresh token */
SESSION_STORE_REFRESH_TOKEN: 'refresh_token', SESSION_STORE_REFRESH_TOKEN: 'refresh_token',
/* SessionStorage Key used for the timestamp of the refresh token */
SESSION_STORE_REFRESH_TOKEN_TS: 'refresh_token_ts',
/* JWT Scheme */ /* JWT Scheme */
JWT_SCHEME: 'Bearer', JWT_SCHEME: 'Bearer',
@ -43,6 +46,8 @@ const lib_auth = {
* Stores refresh token in session storage * Stores refresh token in session storage
*/ */
setRefreshToken: function(token) { setRefreshToken: function(token) {
const now = new Date();
sessionStorage.setItem(this.SESSION_STORE_REFRESH_TOKEN_TS, now.getTime())
sessionStorage.setItem(this.SESSION_STORE_REFRESH_TOKEN, token) sessionStorage.setItem(this.SESSION_STORE_REFRESH_TOKEN, token)
}, },
@ -56,17 +61,23 @@ const lib_auth = {
const now = new Date(); const now = new Date();
const atts = sessionStorage.getItem(this.SESSION_STORE_ACCESS_TOKEN_TS) const atts = sessionStorage.getItem(this.SESSION_STORE_ACCESS_TOKEN_TS)
const timeElapsed = (now.getTime() - atts) / 1000 let timeElapsed = (now.getTime() - atts) / 1000
// Refresh the access token if more than 5mn // Refresh the access token if more than 5mn
if (timeElapsed > 300) { if (timeElapsed > 300) {
const dataJson = { // Check if refresh token has expired or is about to expire
'rt': this.getRefreshToken() const rtts = sessionStorage.getItem(this.SESSION_STORE_REFRESH_TOKEN_TS)
if ((now.getTime() - rtts) / 1000 > 7200 - 60) {
// Force user to sign in again
this.logout()
return
} }
let self = this let self = this
let deferred = lib_api.refreshToken(dataJson) let deferred = lib_api.refreshToken({
'rt': this.getRefreshToken()
})
deferred.then( deferred.then(
function (result) { function (result) {

25
static/admin/lib/errors-utils.js

@ -0,0 +1,25 @@
const lib_errors = {
// Extract jqxhr error message
extractJqxhrErrorMsg: function(jqxhr) {
let hasErrorMsg = ('responseJSON' in jqxhr) &&
(jqxhr['responseJSON'] != null) &&
('error' in jqxhr['responseJSON']) &&
(typeof jqxhr['responseJSON']['error'] == 'string')
return hasErrorMsg ? jqxhr['responseJSON']['error'] : jqxhr.statusText
},
// Manage errors
processError: function(e) {
const errorMsg = this.extractJqxhrErrorMsg(e)
// Redirect to sign in page if authentication error
if (errorMsg == 'Invalid JSON Web Token' || errorMsg == 'Missing JSON Web Token') {
lib_auth.logout()
} else {
lib_msg.displayErrors(errorMsg)
console.log(e)
}
},
}

9
static/admin/lib/messages.js

@ -1,14 +1,5 @@
const lib_msg = { const lib_msg = {
// Extracts jqxhr error message
extractJqxhrErrorMsg: function(jqxhr) {
let hasErrorMsg = ('responseJSON' in jqxhr) &&
(jqxhr['responseJSON'] != null) &&
('error' in jqxhr['responseJSON'])
return hasErrorMsg ? jqxhr['responseJSON']['error'] : jqxhr.statusText
},
// UI functions // UI functions
addTextinID: function(text, id){ addTextinID: function(text, id){
$(id).html(text.toUpperCase()) $(id).html(text.toUpperCase())

28
tracker/blockchain-processor.js

@ -60,7 +60,7 @@ class BlockchainProcessor extends AbstractProcessor {
const daemonNbHeaders = info.headers const daemonNbHeaders = info.headers
// Consider that we are in IBD mode if Dojo is far in the past (> 13,000 blocks) // Consider that we are in IBD mode if Dojo is far in the past (> 13,000 blocks)
this.isIBD = (highest.blockHeight < 612000) || (highest.blockHeight < daemonNbHeaders - 13000) this.isIBD = (highest.blockHeight < 655000) || (highest.blockHeight < daemonNbHeaders - 13000)
if (this.isIBD) if (this.isIBD)
return this.catchupIBDMode() return this.catchupIBDMode()
@ -169,7 +169,7 @@ class BlockchainProcessor extends AbstractProcessor {
try { try {
const hash = await this.client.getblockhash(height) const hash = await this.client.getblockhash(height)
const header = await this.client.getblockheader(hash) const header = await this.client.getblockheader(hash)
return this.processBlock(header) return this.processBlock(header)
} catch(e) { } catch(e) {
Logger.error(e, 'Tracker : BlockchainProcessor.catchupNormalMode()') Logger.error(e, 'Tracker : BlockchainProcessor.catchupNormalMode()')
process.exit() process.exit()
@ -206,25 +206,25 @@ class BlockchainProcessor extends AbstractProcessor {
/** /**
* Upon receipt of a new block hash, retrieve the block header from bitcoind via * Upon receipt of a new block hash, retrieve the block header from bitcoind via
* RPC. Continue pulling block headers back through the chain until the database * RPC. Continue pulling block headers back through the chain until the database
* contains header.previousblockhash, adding the headers to a stack. If the * contains header.previousblockhash, adding the headers to a stack. If the
* previousblockhash is not found on the first call, this is either a chain * previousblockhash is not found on the first call, this is either a chain
* re-org or the tracker missed blocks during a shutdown. * re-org or the tracker missed blocks during a shutdown.
* *
* Once the chain has bottomed out with a known block in the database, delete * Once the chain has bottomed out with a known block in the database, delete
* all known database transactions confirmed in blocks at heights greater than * all known database transactions confirmed in blocks at heights greater than
* the last known block height. These transactions are orphaned but may reappear * the last known block height. These transactions are orphaned but may reappear
* in the new chain. Notify relevant accounts of balance updates / * in the new chain. Notify relevant accounts of balance updates /
* transaction confirmation counts. * transaction confirmation counts.
* *
* Delete block entries not on the main chain. * Delete block entries not on the main chain.
* *
* Forward-scan through the block headers, pulling the full raw block hex via * Forward-scan through the block headers, pulling the full raw block hex via
* RPC. The raw block contains all transactions and is parsed by bitcoinjs-lib. * RPC. The raw block contains all transactions and is parsed by bitcoinjs-lib.
* Add the block to the database. Run checkTransaction for each transaction in * Add the block to the database. Run checkTransaction for each transaction in
* the block that is not in the database. Confirm all transactions in the block. * the block that is not in the database. Confirm all transactions in the block.
* *
* After each block, query bitcoin against all database unconfirmed outputs * After each block, query bitcoin against all database unconfirmed outputs
* to see if they remain in the mempool or have been confirmed in blocks. * to see if they remain in the mempool or have been confirmed in blocks.
* Malleated transactions entering the wallet will disappear from the mempool on * Malleated transactions entering the wallet will disappear from the mempool on
* block confirmation. * block confirmation.
* *
@ -247,7 +247,7 @@ class BlockchainProcessor extends AbstractProcessor {
} catch(err) { } catch(err) {
Logger.error(err, `Tracker : BlockchainProcessor.onBlockHash() : error in getblockheader(${blockHash})`) Logger.error(err, `Tracker : BlockchainProcessor.onBlockHash() : error in getblockheader(${blockHash})`)
} }
if(headers == null) if(headers == null)
return null return null
@ -263,7 +263,7 @@ class BlockchainProcessor extends AbstractProcessor {
// Process the blocks // Process the blocks
return await util.seriesCall(headers, header => { return await util.seriesCall(headers, header => {
return this.processBlock(header) return this.processBlock(header)
}) })
} catch(e) { } catch(e) {
@ -302,7 +302,7 @@ class BlockchainProcessor extends AbstractProcessor {
} }
/** /**
* Cancel confirmation of transactions * Cancel confirmation of transactions
* and delete blocks after a given height * and delete blocks after a given height
* @param {integer} height - height of last block maintained * @param {integer} height - height of last block maintained
* @returns {Promise} * @returns {Promise}
@ -348,7 +348,7 @@ class BlockchainProcessor extends AbstractProcessor {
Logger.info(`Tracker : Rescanning block ${height}`) Logger.info(`Tracker : Rescanning block ${height}`)
const hash = await this.client.getblockhash(height) const hash = await this.client.getblockhash(height)
const header = await this.client.getblockheader(hash) const header = await this.client.getblockheader(hash)
return this.processBlock(header) return this.processBlock(header)
} catch(e) { } catch(e) {
Logger.error(e, 'Tracker : BlockchainProcessor.rescan()') Logger.error(e, 'Tracker : BlockchainProcessor.rescan()')
throw e throw e
@ -367,7 +367,7 @@ class BlockchainProcessor extends AbstractProcessor {
const hex = await this.client.getblock(header.hash, false) const hex = await this.client.getblock(header.hash, false)
const block = new Block(hex, header) const block = new Block(hex, header)
const txsForBroadcast = await block.checkBlock() const txsForBroadcast = await block.checkBlock()
// Send notifications // Send notifications

Loading…
Cancel
Save