Browse Source

Merge pull request #1 from Samourai-Wallet/develop

upstream
use-env-var-docker
Antoine Walter 5 years ago
committed by GitHub
parent
commit
0b407f21f3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .dockerignore
  2. 18
      README.md
  3. 207
      RELEASES.md
  4. 41
      accounts/support-rest-api.js
  5. 127
      doc/DOCKER_advanced_setups.md
  6. 111
      doc/DOCKER_setup.md
  7. 21
      docker/my-dojo/.env
  8. 15
      docker/my-dojo/bitcoin/Dockerfile
  9. 6
      docker/my-dojo/bitcoin/restart.sh
  10. 16
      docker/my-dojo/conf/docker-explorer.conf.tpl
  11. 42
      docker/my-dojo/conf/docker-indexer.conf.tpl
  12. 3
      docker/my-dojo/conf/docker-mysql.conf.tpl
  13. 2
      docker/my-dojo/conf/docker-node.conf.tpl
  14. 13
      docker/my-dojo/docker-compose.yaml
  15. 271
      docker/my-dojo/dojo.sh
  16. 38
      docker/my-dojo/explorer/Dockerfile
  17. 44
      docker/my-dojo/explorer/restart.sh
  18. 43
      docker/my-dojo/indexer/Dockerfile
  19. 22
      docker/my-dojo/indexer/restart.sh
  20. 178
      docker/my-dojo/indexer/wait-for-it.sh
  21. 31
      docker/my-dojo/install/install-scripts.sh
  22. 15
      docker/my-dojo/install/uninstall-scripts.sh
  23. 25
      docker/my-dojo/install/upgrade-scripts.sh
  24. 20
      docker/my-dojo/nginx/Dockerfile
  25. 15
      docker/my-dojo/nginx/explorer.conf
  26. 6
      docker/my-dojo/node/Dockerfile
  27. 12
      docker/my-dojo/node/keys.index.js
  28. 2
      docker/my-dojo/overrides/bitcoind.install.yaml
  29. 30
      docker/my-dojo/overrides/explorer.install.yaml
  30. 27
      docker/my-dojo/overrides/indexer.install.yaml
  31. 11
      docker/my-dojo/tor/Dockerfile
  32. 7
      docker/my-dojo/tor/restart.sh
  33. 4
      lib/bitcoind-rpc/rpc-client.js
  34. 134
      lib/bitcoind-rpc/transactions.js
  35. 58
      lib/db/mysql-db-wrapper.js
  36. 156
      lib/remote-importer/remote-importer.js
  37. 2
      lib/remote-importer/sources.js
  38. 18
      lib/util.js
  39. 809
      package-lock.json
  40. 5
      package.json
  41. 17
      static/admin/css/style.css
  42. 9
      static/admin/lib/api-wrapper.js
  43. 18
      static/admin/tool/index.html
  44. 55
      static/admin/tool/index.js

3
.dockerignore

@ -1,3 +1,4 @@
node_modules
.git
private-tests
private-tests
docker/my-dojo/conf

18
README.md

@ -16,7 +16,9 @@ It provides in a single command the setup of a full Samourai backend composed of
* a bitcoin full node only accessible as an ephemeral Tor hidden service,
* the backend database,
* the backend modules with an API accessible as a static Tor hidden service,
* a maintenance tool accessible through a Tor web browser.
* a maintenance tool accessible through a Tor web browser,
* a block explorer ([BTC RPC Explorer](https://github.com/janoside/btc-rpc-explorer)) accessible through a Tor web browser,
* an optional indexer of Bitcoin addresses ([addrindexrs](https://github.com/Samourai-Wallet/addrindexrs)) providing fast and private rescans of HD accounts and loose addresses.
See [the documentation](./doc/DOCKER_setup.md) for detailed setup instructions.
@ -60,13 +62,16 @@ Authentication is enforced by an API key and Json Web Tokens.
**Import of HD Accounts and data sources**
* First import of an unknown HD account relies on a data source (local bitcoind or OXT). After that, the tracker will keep everything current.
* First import of an unknown HD account relies on a data source (local bitcoind, local indexer or OXT). After that, the tracker will keep everything current.
* Default option relies on the local bitcoind and makes you 100% independent of Samourai Wallet's infrastructure. This option is recommended for better privacy.
* Using the local bitcoind (default option) or the local indexer makes you 100% independent of Samourai Wallet's infrastructure and is recommended for better privacy.
* Activation of bitcoind as the data source:
* Edit /keys/index.js and set "indexer.active" to "local_bitcoind". OXT API will be ignored.
* Activation of the local indexer as the data source:
* Edit /keys/index.js and set "indexer.active" to "local_indexer". OXT API will be ignored.
* Activation of OXT as the data source (through socks5):
* Edit /keys/index.js and set "indexer.active" to "third_party_explorer".
@ -76,5 +81,8 @@ Authentication is enforced by an API key and Json Web Tokens.
* It's slightly slower than using the option relying on the OXT API.
* It may fail to correctly import an existing wallet if this wallet had a large activity.
* If you use bitcoind and if the import seems to return an invalid balance, you can use the "XPUB rescan" function provided by the maintenance tool. This function allows you to force the minimum number of addresses to be derived and the start index for the derivation.
* As a rule of thumb, we recommend to use bitcoind as the source of imports and to setup your Dojo with a new clean wallet. It increases your privacy and it removes all potential issues with the import of a large wallet.
* Main drawbacks of using your local indexer for these imports:
* It requires 120GB of additional disk space during its initialization.
As a rule of thumb, we recommend to use the local indexer as the source of imports and to setup your Dojo with a new clean wallet. It increases your privacy and it removes all potential issues with the import of a large wallet.

207
RELEASES.md

@ -2,11 +2,218 @@
## Releases ##
- [v1.5.0](#1_5_0)
- [v1.4.1](#1_4_1)
- [v1.4.0](#1_4_0)
- [v1.3.0](#1_3_0)
- [v1.2.0](#1_2_0)
- [v1.1.0](#1_1_0)
<a name="1_5_0"/>
## Samourai Dojo v1.5.0 ##
### Notable changes ###
#### Local indexer of Bitcoin addresses ####
Previous versions of Dojo provided the choice between 2 data sources for import and rescan operations, the local bitcoind and OXT. This version introduces a new optional Docker container running a local indexer ([addrindexrs](https://github.com/Samourai-Wallet/addrindexrs)) that can be used as an alternative to the 2 existing options.
The local indexer provides private, fast and exhaustive imports and rescans.
Warning: The local indexer requires around 120GB of additionnal disk space during its installation, and around 60GB after the compaction of its database.
See this [documentation](https://github.com/Samourai-Wallet/samourai-dojo/blob/master/doc/DOCKER_advanced_setups.md#local_indexer) for the detailed procedure allowing to configure and install the indexer.
#### Local Electrum server used as data source for imports/rescans ####
This version of Dojo introduces the support of a local external Electrum server (ElectrumX or Electrs) as the data source of imports and rescans. This option provides the same benefits as the new local indexer to users running an Electrum server.
See this [documentation](https://github.com/Samourai-Wallet/samourai-dojo/blob/master/doc/DOCKER_advanced_setups.md#local_electrum) for the detailed procedure allowing to configure your Electrum server as the data source of imports and rescans.
#### Improved performances of Dojo upgrades ####
By default, the upgrade process will try to reuse the image layers cached by Docker in order to reduce the duration of upgrades.
A new option for the upgrade command allows to force a complete rebuild of all the containers (equivalemt to the former default behavior of the upgrade process).
```
> ./dojo.sh upgrade --nocache
```
#### Additional controls before installation ####
A few controls and confirmations were added to the installation process in order to avoid multiple calls leading to problems with database credentials. Additionally, a full uninstallation is forced before a new installation is allowed.
#### Upgrade of bitcoind to v0.19.1 ####
Upgrade to Bitcoin Core v0.19.1
### Change log ###
#### MyDojo ####
- [#118](https://github.com/Samourai-Wallet/samourai-dojo/pull/118) add support of local indexers as the data source of imports and rescans
- [#119](https://github.com/Samourai-Wallet/samourai-dojo/pull/119) improve performances of dojo upgrades
- [#120](https://github.com/Samourai-Wallet/samourai-dojo/pull/120) upgrade btc-rpc-explorer to v1.1.8
- [#121](https://github.com/Samourai-Wallet/samourai-dojo/pull/121) add controls and confirmations before reinstalls and uninstalls
- [#124](https://github.com/Samourai-Wallet/samourai-dojo/pull/124) upgrade bitcoin v0.19.1
- [#125](https://github.com/Samourai-Wallet/samourai-dojo/pull/125) improve support of --auto option in dojo.sh
- [#127](https://github.com/Samourai-Wallet/samourai-dojo/pull/127) upgrade btc-rpc-explorer to v1.1.9
- [#129](https://github.com/Samourai-Wallet/samourai-dojo/pull/129) fix mydojo buster
#### Bug fixes ####
- [#115](https://github.com/Samourai-Wallet/samourai-dojo/pull/115) backport of fix implemented in 1.4.1
- [#131](https://github.com/Samourai-Wallet/samourai-dojo/pull/131) fix issue 130
#### Security ####
- [#126](https://github.com/Samourai-Wallet/samourai-dojo/pull/126) upgrade nodejs packages
#### Documentation ####
- [#137](https://github.com/Samourai-Wallet/samourai-dojo/pull/137) improved instructions related to config files
#### Credits ###
- BTCxZelko
- Crazyk031
- GuerraMoneta
- kenshin-samourai
- LaurentMT
<a name="1_4_1"/>
## Samourai Dojo v1.4.1 ##
### Notable changes ###
#### Prevents a hang of Dojo on shutdown ####
Since v1.4.0, some users that Dojo is hanging during its shutdown. This release provides a fix for the users affected by this problem.
#### Prevents automatic restarts of bitcoind container ####
This release removes automatic restarts of the bitcoind container when bitcoind has exited with an error.
### Change log ###
#### Bug fixes ####
- [0ff045d](https://github.com/Samourai-Wallet/samourai-dojo/commit/0ff045d1495807902e9fd7dcfbd2fdb4dc21c608) keep bitcoind container up if bitcoind exits with an error
- [bd43526](https://github.com/Samourai-Wallet/samourai-dojo/commit/bd43526bca1f36a1ada07ad799c87b11a897e873) fix for dojo hanging on shutdown
- [3ee85db](https://github.com/Samourai-Wallet/samourai-dojo/commit/3ee85db3bf69f4312204e502c98d414a4180dc53) force kill of docker exec used for testing bitcoind shutdown if command hangs more than 12s
#### Misc. ####
- [21925f7](https://github.com/Samourai-Wallet/samourai-dojo/commit/21925f7c321974ef7eb55c1ad897a5e02ef52bee) bump versions of dojo and bitcoind container
- [08342e3](https://github.com/Samourai-Wallet/samourai-dojo/commit/08342e3995c473b589bb2a517e5bc30cf5f7dc9a) add trace in stop() function of dojo.sh
### Credits ###
- BTCxZelko
- Crazyk031
- GuerraMoneta
- kenshin-samourai
- LaurentMT
- mj
<a name="1_4_0"/>
## Samourai Dojo v1.4.0 ##
### Notable changes ###
#### Local block explorer ####
This release adds a new docker container hosting a local block explorer ([BTC RPC Explorer](https://github.com/janoside/btc-rpc-explorer)).
Access to the block explorer is secured by a password defined in /docker/my-dojo/conf/docker-explorer.conf (see `EXPLORER_KEY` configuration parameter).
*Upgrade procedure*
```
# Stop your Dojo
# Download the Dojo archive for this release
# Override the content of your <dojo_dir> with the content of the Dojo archive
# Edit <dojo_dir>/docker/my-dojo/conf/docker-explorer.conf.tpl and set the value of `EXPLORER_KEY` with a custom password.
# Launch the upgrade of your Dojo with: dojo.sh upgrade
```
This local block explorer is available as a Tor hidden service. Its static onion address can be retrieved with the command
```
dojo.sh onion
```
#### Autostart of Dojo ####
Starting with this release, Dojo is automatically launched when the docker daemon starts.
### Change log ###
#### MyDojo ####
- [#101](https://github.com/Samourai-Wallet/samourai-dojo/pull/101) add --auto and --nolog options to install and upgrade commands
- [#102](https://github.com/Samourai-Wallet/samourai-dojo/pull/102) improve performances of transactions imports
- [#107](https://github.com/Samourai-Wallet/samourai-dojo/pull/107) add optional block explorer
- [#108](https://github.com/Samourai-Wallet/samourai-dojo/pull/108) switch restart policies of containers to always
- [#109](https://github.com/Samourai-Wallet/samourai-dojo/pull/109) use port 80 of keyservers
- [#110](https://github.com/Samourai-Wallet/samourai-dojo/pull/110) replace keyserver
- [#111](https://github.com/Samourai-Wallet/samourai-dojo/pull/111) enable autostart of dojo
- [#113](https://github.com/Samourai-Wallet/samourai-dojo/pull/113) check if dojo is running (start and stop commands)
#### Bug fixes ####
- [#100](https://github.com/Samourai-Wallet/samourai-dojo/pull/100) fix issue caused by sed -i on osx
#### Documentation ####
- [#99](https://github.com/Samourai-Wallet/samourai-dojo/pull/99) doc: installation of dojo on synology
- [b12d24d](https://github.com/Samourai-Wallet/samourai-dojo/commit/b12d24d088a95023a8e1c9e8a1b1c4b40491d4a7) update readme
### Credits ###
- anwfr
- jochemin
- kenshin-samourai
- LaurentMT
<a name="1_3_0"/>
## Samourai Dojo v1.3.0 ##

41
accounts/support-rest-api.js

@ -4,6 +4,7 @@
*/
'use strict'
const fs = require('fs')
const validator = require('validator')
const bodyParser = require('body-parser')
const errors = require('../lib/errors')
@ -68,6 +69,13 @@ class SupportRestApi {
HttpServer.sendAuthError
)
this.httpServer.app.get(
`/${keys.prefixes.support}/pairing/explorer`,
authMgr.checkHasAdminProfile.bind(authMgr),
this.getPairingExplorer.bind(this),
HttpServer.sendAuthError
)
this.httpServer.app.get(
`/${keys.prefixes.support}/pairing`,
authMgr.checkHasAdminProfile.bind(authMgr),
@ -299,6 +307,39 @@ class SupportRestApi {
}
}
/**
* Get pairing info for the local block explorer
*/
async getPairingExplorer(req, res) {
try {
let url = ''
if (process.env.EXPLORER_INSTALL == 'on') {
try {
url = fs.readFileSync('/var/lib/tor/hsv3explorer/hostname', 'utf8')
url = url.replace('\n', '')
} catch(e) {
Logger.error(e, 'SupportRestApi.getPairing() : Cannot read explorer onion address')
}
}
const ret = {
'pairing': {
'type': 'explorer.btcRpcExplorer',
'url': url,
'key': process.env.EXPLORER_KEY
}
}
HttpServer.sendRawData(res, JSON.stringify(ret, null, 2))
} catch(e) {
const ret = {
status: 'error'
}
Logger.error(e, 'SupportRestApi.getPairingExplorer() : Support pairing error')
HttpServer.sendError(res, JSON.stringify(ret, null, 2))
} finally {
debugApi && Logger.info(`Completed GET /pairing/explorer`)
}
}
/**
* Validate arguments related to GET xpub info requests
* @param {object} req - http request object

127
doc/DOCKER_advanced_setups.md

@ -3,10 +3,12 @@
The configuration files of Dojo provide a few advanced options allowing to tune your setup.
A word of caution, though, the default values of these options try to maximize your privacy at a network level. Most of the advanced setups described in this document may damage your privacy. Use at your own risk!
A word of caution, though, the default values of these options try to maximize your privacy at a network level. Some of the advanced setups described in this document may damage your privacy. Use at your own risk!
## Table of Content ##
- [Local indexer of Bitcoin addresses](#local_indexer)
- [Local Electrum server used as data source for imports/rescans](#local_electrum)
- [External Bitcoin full node](#external_bitcoind)
- [bitcoind RPC API ans ZMQ notifications exposed to external apps](#exposed_rpc_zmq)
- [Static onion address for bitcoind hidden service](#static_onion)
@ -14,6 +16,107 @@ A word of caution, though, the default values of these options try to maximize y
- [Support of testnet](#testnet)
<a name="local_indexer"/>
## Local indexer of Bitcoin addresses ##
By default, Dojo uses the local full node as its data source for imports and rescans of HD accounts and addresses. While private, this default option has many limitations. MyDojo allows to install a local indexer ([addrindexrs](https://github.com/Samourai-Wallet/addrindexrs)) providing the best of both worlds (no request sent to a third party, fast and real time rescans, complete transactional history is retrieved).
### Requirements ###
To date, the initial installation of the indexer requires 120GB of additionnal disk space.
### Main benefits ###
- Fast, private and exhaustive real time rescans,
- Allows the block explorer to display the detailed activity of Bitcoin addresses
### Known drawbacks ###
* Additionnal disk space consumed by the index,
* Increased duration of upgrades (from 5 to 20 minutes depending on the machine hosting your Dojo),
* Slight increase of startup duration,
* First indexation will require a few hours.
### Procedure ###
```
# If you're installing a new Dojo or if you're upgrading from a Dojo version <= 1.4.1, edit the docker-indexer.conf.tpl file
nano ./conf/docker-indexer.conf.tpl
# Otherwise, edit the docker-indexer.conf file
nano ./conf/docker-indexer.conf
#
# Set the value of INDEXER_INSTALL to "on"
# Save and exit nano
#
# Edit the nodejs config file (or the corresponding template file if it's your first installation of Dojo)
nano ./conf/docker-node.conf
#
# Set the value of NODE_ACTIVE_INDEXER to "local_indexer"
# Save and exit nano
#
#
# Launch the installation or the upgrade of your Dojo
# with the commands `dojo.sh install` or `dojo.sh upgrade`
#
#
# Be patient!
# First indexation of all Bitcoin addresses will require a few hours.
# Let the indexer complete all these operations before trying to use it for an import or a rescan.
# You can follow the progress made by the indexer with the commands:
# `dojo.sh logs`
# or
# `dojo.sh logs indexer`
#
```
<a name="local_electrum"/>
## Local Electrum server used as data source for imports/rescans ##
If you're running an instance of ElectrumX or Electrs on your local network, Dojo allows you to define this instance as the data source used for imports and rescans. This setup is an alternative to the local indexer provided by MyDojo.
Important: Do not use an Electrum server operated by a third party or hosted on a different local network.
### Procedure ###
```
# If you're installing a new Dojo or if you're upgrading from a Dojo version <= 1.4.1, edit the docker-indexer.conf.tpl file
nano ./conf/docker-indexer.conf.tpl
# Otherwise, edit the docker-indexer.conf file
nano ./conf/docker-indexer.conf
#
# Set the value of INDEXER_INSTALL to "off"
# Set the value of INDEXER_IP with the IP address of your Electrum server
# Set the value of INDEXER_RPC_PORT with the port used by the RPC API of your Electrum server (default= 50001)
# Set the value of INDEXER_BATCH_SUPPORT to "active" if your Electrum server is ElectrumX, otherwise set the value to "inactive"
# Save and exit nano
#
# Edit the nodejs config file (or the corresponding template file if it's your first installation of Dojo)
nano ./conf/docker-node.conf
#
# Set the value of NODE_ACTIVE_INDEXER to "local_indexer"
# Save and exit nano
#
```
<a name="external_bitcoind"/>
## External Bitcoin full node ##
@ -65,9 +168,12 @@ zmqpubrawtx=...
#### Configuration of Dojo ####
```
# Edit the bitcoin config template file
# If you're installing a new Dojo, edit the docker-bitcoind.conf.tpl file
nano ./conf/docker-bitcoind.conf.tpl
# Otherwise, edit the docker-bitcoind.conf file
nano ./conf/docker-bitcoind.conf
#
# Set the value of BITCOIND_INSTALL to "off"
# Set the value of BITCOIND_IP with the IP address of you bitcoin full node
@ -117,7 +223,10 @@ The following steps allow to expose the RPC API and ZMQ notifications to applica
# Stop your Dojo
./dojo.sh stop
# Edit the bitcoin config file
# If you're installing a new Dojo, edit the docker-bitcoind.conf.tpl file
nano ./conf/docker-bitcoind.conf.tpl
# Otherwise, edit the docker-bitcoind.conf file
nano ./conf/docker-bitcoind.conf
#
@ -155,7 +264,10 @@ The following steps allow to keep a static onion address (not recommended).
# Stop your Dojo
./dojo.sh stop
# Edit the bitcoin config file
# If you're installing a new Dojo, edit the docker-bitcoind.conf.tpl file
nano ./conf/docker-bitcoind.conf.tpl
# Otherwise, edit the docker-bitcoind.conf file
nano ./conf/docker-bitcoind.conf
#
@ -189,7 +301,10 @@ The following steps allow to activate the use of Tor bridges by Dojo.
# obfs4 ...
# obfs4 ...
# Edit the tor config file
# If you're installing a new Dojo, edit the docker-tor.conf.tpl file
nano ./conf/docker-tor.conf.tpl
# Otherwise, edit the docker-tor.conf file
nano ./conf/docker-tor.conf
#
@ -215,7 +330,7 @@ By default, Dojo is installed for running on Bitcoin mainnet.
The following steps allow to install an instance of Dojo running on Bitcoin testnet.
```
# Edit the common config template file
# Edit the docker-common.conf.tpl file
nano ./conf/docker-common.conf.tpl
#

111
doc/DOCKER_setup.md

@ -3,8 +3,10 @@
MyDojo is a set of Docker containers providing a full Samourai backend composed of:
* a bitcoin full node accessible as an ephemeral Tor hidden service,
* a backend database,
* a backend modules with an API accessible as a static Tor hidden service,
* a maintenance tool accessible through a Tor web browser.
* backend modules with an API accessible as a static Tor hidden service,
* a maintenance tool accessible through a Tor web browser,
* a block explorer ([BTC RPC Explorer](https://github.com/janoside/btc-rpc-explorer)) accessible as a static Tor hidden service.
* an optional indexer of Bitcoin addresses ([addrindexrs](https://github.com/Samourai-Wallet/addrindexrs)) providing fast and private rescans of HD accounts and loose addresses.
## Table of Content ##
@ -15,6 +17,7 @@ MyDojo is a set of Docker containers providing a full Samourai backend composed
- [Upgrade procedure](#upgrade)
- [Dojo shell script](#shell_script)
- [Dojo maintenance tool](#maintenance_tool)
- [Block explorer](#explorer)
- [Pairing your wallet to your Dojo](#pairing)
- [Network connections](#network)
@ -38,31 +41,39 @@ MyDojo is a set of Docker containers providing a full Samourai backend composed
Host machine | (Tor hidden services)
______________________________ | _____________________________
| | |
| ------------------- |
| | Tor Container | |
| ------------------- |
| | | |
| ------------------- | |
| | Nginx Container | | dmznet |
| ------------------- | |
|- - - - - - - - - - - | - - - - - - - | - - - - - - - - - - - |
| -------------------- -------------------- |
| | Nodejs Container | ------ | Bitcoind Container | |
| -------------------- -------------------- |
| | |
| ------------------- |
| | MySQL Container | dojonet |
| ------------------- |
| --------- dmznet |
| --------| Tor |------------ |
| | --------- | |
| | | |
| --------- | |
| --| Nginx |-------- | |
| | --------- | | |
|- - - - -|- - - - - - - - - - -|- - - - - - - - -|- - - - - - |
| | | | |
| ---------- ---------- ---------- |
| | Nodejs |----------| Explorer |------| Bitcoind | |
| ---------- ---------- ---------- |
| | | | | |
| | ------- | | |
| | | | | |
| ---------- | ---------- | |
| | MySQL | ----| Indexer |----------- |
| ---------- ---------- |
| dojonet |
|______________________________________________________________|
<a name="requirements"/>
## Requirements ##
* A dedicated computer (host machine) connected 24/7 to internet
* OS: Linux is recommended
* Disk: 500GB (minimal) / 1TB (recommended) - SSD is recommended
* Disk: 600GB (minimal) / 1TB (recommended) - SSD is recommended
* RAM: 4GB (minimal)
* Docker and Docker Compose installed on the host machine (be sure to run a recent version supporting v3.2 of docker-compose files, i.e. Docker Engine v17.04.0+)
* Check that the clock of your computer is properly set (required for Tor)
@ -73,11 +84,14 @@ MyDojo is a set of Docker containers providing a full Samourai backend composed
## Configuration files ##
Each new release of Dojo is packaged with 4 template files stored in the `<dojo_dir>/docker/my-dojo/conf` directory:
Each new release of Dojo is packaged with 7 template files stored in the `<dojo_dir>/docker/my-dojo/conf` directory:
- docker-common.conf.tpl
- docker-bitcoin.conf.tpl
- docker-explorer.conf.tpl
- docker-indexer.conf.tpl
- docker-mysql.conf.tpl
- docker-node.conf.tpl
- docker-tor.conf.tpl
These template files define default values for configuration options of your Dojo.
@ -100,6 +114,9 @@ For MacOS, see this detailed [installation guide](./DOCKER_mac_setup.MD).
For Synology, see this detailed [installation guide](./DOCKER_synology_setup.md).
For Raspberry Pi4 and Odroid N2, see the [Ronin Dojo Project](https://github.com/RoninDojo/RoninDojo)
This procedure allows to install a new Dojo from scratch.
* Install [Docker and Docker Compose](https://docs.docker.com/compose/install/) on the host machine and check that your installation is working.
@ -127,6 +144,7 @@ This procedure allows to install a new Dojo from scratch.
* `MYSQL_ROOT_PASSWORD` = password protecting the root account of MySQL,
* `MYSQL_USER` = login of the account used to access the database of your Dojo,
* `MYSQL_PASSWORD` = password of the account used to access the database of your Dojo.
Note: These values can't be changed after the first installation.
* Edit docker-node.conf.tpl and provide a new value for the following parameters:
* `NODE_API_KEY` = API key which will be required from your Samourai Wallet / Sentinel for its interactions with the API of your Dojo,
@ -134,7 +152,14 @@ This procedure allows to install a new Dojo from scratch.
* `NODE_JWT_SECRET` = secret used by your Dojo for the initialization of a cryptographic key signing Json Web Tokens.
These parameters will protect the access to your Dojo. Be sure to provide alphanumeric values with enough entropy.
* Dojo provides a few additional settings for advanced setups:
* Edit docker-explorer.conf.tpl and provide a new value for the following parameter:
* `EXPLORER_KEY` = password that will be required to access the block explorer,
* If you want to deactivate the block explorer, set the value of `EXPLORER_INSTALL` to `off`.
See this [section](#explorer) for more details about the block explorer.
* Dojo provides a few additional settings for advanced setups:
* installation of an address indexer used for fast imports and rescans,
* support of an external electrum server (ElectrumX or electrs) used for fast imports and rescans,
* static onion address for your full node,
* bitcoind RPC API exposed to external apps,
* use of an external full node,
@ -151,7 +176,7 @@ This procedure allows to install a new Dojo from scratch.
./dojo.sh install
```
Docker and Docker Compose are going to build the images and containers of your Dojo. This operation will take a few minutes (download and setup of all required software components). After completion, your Dojo will be launched and will begin the initialization of the full node (Bitcoin Initial Block Download and syncing of the database). This step will take several hours/days according to the specs of your machine. Be patient. Use CTRL+C to stop the display of the full logs.
Docker and Docker Compose are going to build the images and containers of your Dojo. This operation will take several minutes (download and setup of all required software components). After completion, your Dojo will be launched and will begin the initialization of the full node (Bitcoin Initial Block Download and syncing of the database). This step will take several hours/days according to the specs of your machine. Be patient. Use CTRL+C to stop the display of the full logs.
* Monitor the progress made for the initialization of the database with this command displaying the logs of the tracker
@ -163,7 +188,7 @@ Docker and Docker Compose are going to build the images and containers of your D
Exit the logs with CTRL+C when the syncing of the database has completed.
* Retrieve the Tor onion addresses (v2 and v3) of the API of your Dojo
* Retrieve the Tor onion addresses (v3) of the API and block explorer of your Dojo
```
./dojo.sh onion
@ -198,7 +223,7 @@ This procedure allows to upgrade your Dojo with a new version.
Docker and Docker Compose are going to build new images and containers for your Dojo. After completion, the updated version of your Dojo will be launched automatically.
Note: The upgrade process will override all manual modifications of the files stored under the `<dojo_dir>` directory with an exception for the three configuration files stored in the `<dojo_dir>/docker/my-dojo/conf` directory.
Note: The upgrade process will override all manual modifications of the files stored under the `<dojo_dir>` directory with an exception for the configuration files stored in the `<dojo_dir>/docker/my-dojo/conf` directory.
<a name="shell_script"/>
@ -227,17 +252,19 @@ Available commands:
dojo.sh logs bitcoind : display the logs of bitcoind
dojo.sh logs db : display the logs of the MySQL database
dojo.sh logs tor : display the logs of tor
dojo.sh logs indexer : display the logs of the internal indexer
dojo.sh logs api : display the logs of the REST API (nodejs)
dojo.sh logs tracker : display the logs of the Tracker (nodejs)
dojo.sh logs pushtx : display the logs of the pushTx API (nodejs)
dojo.sh logs pushtx-orchest : display the logs of the Orchestrator (nodejs)
dojo.sh logs explorer : display the logs of the Explorer
Available options (for api, tracker, pushtx and pushtx-orchest modules):
Available options (for api, tracker, pushtx, pushtx-orchest and explorer modules):
-d [VALUE] : select the type of log to be displayed.
VALUE can be output (default) or error.
-n [VALUE] : display the last VALUE lines
onion Display the Tor onion address allowing your wallet to access your Dojo.
onion Display the Tor onion addresses allowing to access the API, maintenance tool and block explorer of your Dojo.
restart Restart your Dojo.
@ -264,6 +291,27 @@ The maintenance tool requires that you allow javascript for the site.
Sign in with the value entered for `NODE_ADMIN_KEY`.
<a name="explorer"/>
## Block explorer ##
A block explorer ([BTC RPC Explorer](https://github.com/janoside/btc-rpc-explorer)) is accessible through your Tor browser.
You can retrieve the onion address of the block explorer with the command
```
./dojo.sh onion
```
Sign in with a login (can be any value) and the password set in your Dojo configuration (value entered for `EXPLORER_KEY`).
Notes:
* Current version doesn't support the display of detailed information for a Bitcoin address,
* Calls to the RPC API of your bitcoind are deactivated.
<a name="pairing"/>
## Pairing your wallet to your Dojo ##
@ -272,11 +320,20 @@ 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.
2. Get your smartphone and launch the Samourai Wallet app. Scan the 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.
## Pairing your wallet to your local block explorer (coming "soon") ##
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.
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"/>
## Network connections ##
@ -287,6 +344,8 @@ If OXT is selected as the default source for imports, OXT clearnet API is access
The maintenance tool is accessed as a Tor hidden service (static onion address).
The block explorer is accessed as a Tor hidden service (static onion address).
The Bitcoin node only allows incoming connections from Tor (ephemeral onion address).
The Bitcoin node attempts outgoing connections to both Tor and clearnet nodes (through the Tor local proxy).

21
docker/my-dojo/.env

@ -10,12 +10,14 @@
COMPOSE_CONVERT_WINDOWS_PATHS=1
DOJO_VERSION_TAG=1.4.0
DOJO_DB_VERSION_TAG=1.1.0
DOJO_BITCOIND_VERSION_TAG=1.3.0
DOJO_NODEJS_VERSION_TAG=1.3.0
DOJO_NGINX_VERSION_TAG=1.3.0
DOJO_TOR_VERSION_TAG=1.2.0
DOJO_VERSION_TAG=1.5.0
DOJO_DB_VERSION_TAG=1.1.1
DOJO_BITCOIND_VERSION_TAG=1.5.0
DOJO_NODEJS_VERSION_TAG=1.5.0
DOJO_NGINX_VERSION_TAG=1.4.0
DOJO_TOR_VERSION_TAG=1.3.0
DOJO_EXPLORER_VERSION_TAG=1.2.0
DOJO_INDEXER_VERSION_TAG=1.0.0
#########################################
@ -57,3 +59,10 @@ NODE_PREFIX_STATUS_PUSHTX=status
NODE_TRACKER_MEMPOOL_PERIOD=10000
NODE_TRACKER_UNCONF_TXS_PERIOD=300000
#########################################
# INDEXER
#########################################
INDEXER_BATCH_SIZE=10

15
docker/my-dojo/bitcoin/Dockerfile

@ -1,19 +1,20 @@
FROM debian:stretch
FROM debian:buster
#################################################################
# INSTALL BITCOIN
#################################################################
ENV BITCOIN_HOME /home/bitcoin
ENV BITCOIN_VERSION 0.19.0.1
ENV BITCOIN_URL https://bitcoincore.org/bin/bitcoin-core-0.19.0.1/bitcoin-0.19.0.1-x86_64-linux-gnu.tar.gz
ENV BITCOIN_SHA256 732cc96ae2e5e25603edf76b8c8af976fe518dd925f7e674710c6c8ee5189204
ENV BITCOIN_ASC_URL https://bitcoincore.org/bin/bitcoin-core-0.19.0.1/SHA256SUMS.asc
ENV BITCOIN_VERSION 0.19.1
ENV BITCOIN_URL https://bitcoincore.org/bin/bitcoin-core-0.19.1/bitcoin-0.19.1-x86_64-linux-gnu.tar.gz
ENV BITCOIN_SHA256 5fcac9416e486d4960e1a946145566350ca670f9aaba99de6542080851122e4c
ENV BITCOIN_ASC_URL https://bitcoincore.org/bin/bitcoin-core-0.19.1/SHA256SUMS.asc
ENV BITCOIN_PGP_KS_URI hkp://keyserver.ubuntu.com:80
ENV BITCOIN_PGP_KEY 01EA5486DE18A882D4C2684590C8019E36C2E964
RUN set -ex && \
apt-get update && \
apt-get install -qq --no-install-recommends ca-certificates dirmngr gosu gpg wget && \
apt-get install -qq --no-install-recommends ca-certificates dirmngr gosu gpg gpg-agent wget && \
rm -rf /var/lib/apt/lists/*
# Build and install bitcoin binaries
@ -21,7 +22,7 @@ RUN set -ex && \
cd /tmp && \
wget -qO bitcoin.tar.gz "$BITCOIN_URL" && \
echo "$BITCOIN_SHA256 bitcoin.tar.gz" | sha256sum -c - && \
gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$BITCOIN_PGP_KEY" && \
gpg --batch --keyserver "$BITCOIN_PGP_KS_URI" --recv-keys "$BITCOIN_PGP_KEY" && \
wget -qO bitcoin.asc "$BITCOIN_ASC_URL" && \
gpg --batch --verify bitcoin.asc && \
tar -xzvf bitcoin.tar.gz -C /usr/local --strip-components=1 --exclude=*-qt && \

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

@ -40,3 +40,9 @@ if [ "$COMMON_BTC_NETWORK" == "testnet" ]; then
fi
bitcoind "${bitcoind_options[@]}"
# Keep the container up
while true
do
sleep 1
done

16
docker/my-dojo/conf/docker-explorer.conf.tpl

@ -0,0 +1,16 @@
#########################################
# CONFIGURATION OF EXPLORER CONTAINER
#########################################
# Install and run a block explorer inside Dojo (recommended)
# Value: on | off
EXPLORER_INSTALL=on
# Password required for accessing the block explorer
# (login can be anything)
# Keep this password secret!
# Provide a value with a high entropy!
# Type: alphanumeric
EXPLORER_KEY=myExplorerPassword

42
docker/my-dojo/conf/docker-indexer.conf.tpl

@ -0,0 +1,42 @@
#########################################
# CONFIGURATION OF A LOCAL INDEXER
#########################################
# Install and run a local indexer inside Docker
# Set this option to 'off' for using an indexer hosted outside of Docker
# or when using a different data source (local bitcoind, OXT)
# Value: on | off
INDEXER_INSTALL=off
# IP address of the local indexer used by Dojo
# Set value to 172.28.1.6 if INDEXER_INSTALL is set to 'on'
# Type: string
INDEXER_IP=172.28.1.6
# Port of the RPC API
# Set value to 50001 if INDEXER_INSTALL is set to 'on'
# Type: integer
INDEXER_RPC_PORT=50001
# Support of batch requests by the local indexer
# Set value to inactive if INDEXER_INSTALL is set to 'on'
# Value: active | inactive
INDEXER_BATCH_SUPPORT=inactive
#
# EXPERT SETTINGS
# (ACTIVE IF INDEXER_INSTALL IS SET TO ON)
#
# Number of blocks to get in one JSONRPC request from bitcoind
# Type: integer
INDEXER_BATCH_SIZE=10
# Total size of block txids to cache (in MB)
# Type: integer
INDEXER_BLK_TXIDS_CACHE_SIZE_MB=10
# Number of transactions to lookup before returning an error
# Type: integer
INDEXER_TXID_LIMIT=501

3
docker/my-dojo/conf/docker-mysql.conf.tpl

@ -3,13 +3,16 @@
#########################################
# Password of MySql root account
# Warning: This option must not be modified after the first installation
# Type: alphanumeric
MYSQL_ROOT_PASSWORD=rootpassword
# User account used for db access
# Warning: This option must not be modified after the first installation
# Type: alphanumeric
MYSQL_USER=samourai
# Password of of user account
# Warning: This option must not be modified after the first installation
# Type: alphanumeric
MYSQL_PASSWORD=password

2
docker/my-dojo/conf/docker-node.conf.tpl

@ -21,7 +21,7 @@ NODE_ADMIN_KEY=myAdminKey
NODE_JWT_SECRET=myJwtSecret
# Indexer or third-party service used for imports and rescans of addresses
# Values: local_bitcoind | third_party_explorer
# Values: local_bitcoind | local_indexer | third_party_explorer
NODE_ACTIVE_INDEXER=local_bitcoind
# FEE TYPE USED FOR FEES ESTIMATIONS BY BITCOIND

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

@ -10,7 +10,7 @@ services:
env_file:
- ./.env
- ./conf/docker-mysql.conf
restart: on-failure
restart: always
expose:
- "3306"
volumes:
@ -30,8 +30,10 @@ services:
- ./conf/docker-common.conf
- ./conf/docker-mysql.conf
- ./conf/docker-bitcoind.conf
- ./conf/docker-explorer.conf
- ./conf/docker-indexer.conf
- ./conf/docker-node.conf
restart: on-failure
restart: always
command: "/home/node/app/wait-for-it.sh db:3306 --timeout=720 --strict -- /home/node/app/restart.sh"
expose:
- "8080"
@ -39,6 +41,7 @@ services:
- "8082"
volumes:
- data-nodejs:/data
- data-tor:/var/lib/tor
depends_on:
- db
networks:
@ -53,10 +56,11 @@ services:
env_file:
- ./.env
- ./conf/docker-common.conf
restart: on-failure
restart: always
command: "/wait-for node:8080 --timeout=720 -- nginx"
expose:
- "80"
- "9080"
volumes:
- data-nginx:/data
depends_on:
@ -74,8 +78,9 @@ services:
context: ./tor
env_file:
- ./.env
- ./conf/docker-explorer.conf
- ./conf/docker-tor.conf
restart: on-failure
restart: always
command: /restart.sh
volumes:
- data-tor:/var/lib/tor

271
docker/my-dojo/dojo.sh

@ -14,7 +14,9 @@ source_file() {
}
# Source config files
source_file "$DIR/conf/docker-indexer.conf"
source_file "$DIR/conf/docker-bitcoind.conf"
source_file "$DIR/conf/docker-explorer.conf"
source_file "$DIR/conf/docker-common.conf"
source_file "$DIR/.env"
@ -33,6 +35,14 @@ select_yaml_files() {
fi
fi
if [ "$EXPLORER_INSTALL" == "on" ]; then
yamlFiles="$yamlFiles -f $DIR/overrides/explorer.install.yaml"
fi
if [ "$INDEXER_INSTALL" == "on" ]; then
yamlFiles="$yamlFiles -f $DIR/overrides/indexer.install.yaml"
fi
# Return yamlFiles
echo "$yamlFiles"
}
@ -45,35 +55,67 @@ docker_up() {
# Start
start() {
docker_up --remove-orphans
# 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
docker_up --remove-orphans
else
echo "Dojo is already running."
fi
}
# Stop
stop() {
# 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 "Dojo is already stopped."
exit
fi
# Shutdown the bitcoin daemon
if [ "$BITCOIND_INSTALL" == "on" ]; then
# Renewal of bitcoind onion address
if [ "$BITCOIND_EPHEMERAL_HS" = "on" ]; then
docker exec -it tor rm -rf /var/lib/tor/hsv2bitcoind
fi
# Stop the bitcoin daemon
echo "Preparing shutdown of dojo. Please wait."
docker exec -it bitcoind bitcoin-cli \
-rpcconnect=bitcoind \
--rpcport=28256 \
--rpcuser="$BITCOIND_RPC_USER" \
--rpcpassword="$BITCOIND_RPC_PASSWORD" \
stop
echo "Preparing shutdown of dojo. Please wait."
bitcoindDown=$(timeout 3m docker wait bitcoind)
if [ $bitcoindDown -eq 0 ]; then
echo "Bitcoin server stopped."
else
# Check if the bitcoin daemon is still up
# wait 3mn max
i="0"
while [ $i -lt 18 ]
do
echo "Waiting for shutdown of Bitcoin server."
# Check if bitcoind rpc api is responding
timeout -k 12 10 docker exec -it bitcoind bitcoin-cli \
-rpcconnect=bitcoind \
--rpcport=28256 \
--rpcuser="$BITCOIND_RPC_USER" \
--rpcpassword="$BITCOIND_RPC_PASSWORD" \
getblockchaininfo > /dev/null
# rpc api is down
if [[ $? > 0 ]]; then
echo "Bitcoin server stopped."
break
fi
i=$[$i+1]
done
# Bitcoin daemon is still up
# => force close
if [ $i -eq 18 ]; then
echo "Force shutdown of Bitcoin server."
fi
fi
# Stop docker containers
yamlFiles=$(select_yaml_files)
eval "docker-compose $yamlFiles down"
eval "docker-compose $yamlFiles stop"
}
# Restart dojo
@ -87,35 +129,119 @@ install() {
source "$DIR/install/install-scripts.sh"
launchInstall=1
auto=1
noLog=1
# Extract install options from arguments
if [ $# -gt 0 ]; then
for option in $@
do
case "$option" in
--auto ) auto=0 ;;
--nolog ) noLog=0 ;;
* ) break ;;
esac
done
fi
if [ -z "$1" ]; then
# Confirmation
if [ $auto -eq 0 ]; then
launchInstall=0
else
get_confirmation
launchInstall=$?
else
launchInstall=0
fi
# Detection of past install
if [ $launchInstall -eq 0 ]; then
pastInstallsfound=$(docker image ls | grep samouraiwallet/dojo-db | wc -l)
if [ $pastInstallsfound -ne 0 ]; then
# Past installation found. Ask confirmation forreinstall
echo -e "\nWarning: Found traces of a previous installation of Dojo on this machine."
echo "A new installation requires to remove these elements first."
if [ $auto -eq 0 ]; then
launchReinstall=0
else
get_confirmation_reinstall
launchReinstall=$?
fi
if [ $launchReinstall -eq 0 ]; then
echo ""
# Uninstall
if [ $auto -eq 0 ]; then
uninstall --auto
launchReinstall=$?
else
uninstall
launchReinstall=$?
fi
fi
if [ $launchReinstall -eq 1 ]; then
launchInstall=1
echo -e "\nInstallation was cancelled."
fi
fi
fi
# Installation
if [ $launchInstall -eq 0 ]; then
# Initialize the config files
init_config_files
# Build and start Dojo
docker_up --remove-orphans
logs
# Display the logs
if [ $noLog -eq 1 ]; then
logs
fi
fi
}
# Delete everything
uninstall() {
docker-compose rm
source "$DIR/install/uninstall-scripts.sh"
yamlFiles=$(select_yaml_files)
eval "docker-compose $yamlFiles down"
auto=1
# Extract install options from arguments
if [ $# -gt 0 ]; then
for option in $@
do
case "$option" in
--auto ) auto=0 ;;
* ) break ;;
esac
done
fi
# Confirmation
if [ $auto -eq 0 ]; then
launchUninstall=0
else
get_confirmation
launchUninstall=$?
fi
docker image rm samouraiwallet/dojo-db:"$DOJO_DB_VERSION_TAG"
docker image rm samouraiwallet/dojo-bitcoind:"$DOJO_BITCOIND_VERSION_TAG"
docker image rm samouraiwallet/dojo-nodejs:"$DOJO_NODEJS_VERSION_TAG"
docker image rm samouraiwallet/dojo-nginx:"$DOJO_NGINX_VERSION_TAG"
docker image rm samouraiwallet/dojo-tor:"$DOJO_TOR_VERSION_TAG"
if [ $launchUninstall -eq 0 ]; then
docker-compose rm -f
docker volume prune
yamlFiles=$(select_yaml_files)
eval "docker-compose $yamlFiles down"
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 volume prune -f
return 0
else
return 1
fi
}
# Clean-up (remove old docker images)
@ -133,9 +259,11 @@ clean() {
docker image prune
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-explorer "$DOJO_EXPLORER_VERSION_TAG"
del_images_for samouraiwallet/dojo-nodejs "$DOJO_NODEJS_VERSION_TAG"
del_images_for samouraiwallet/dojo-nginx "$DOJO_NGINX_VERSION_TAG"
del_images_for samouraiwallet/dojo-tor "$DOJO_TOR_VERSION_TAG"
del_images_for samouraiwallet/dojo-indexer "$DOJO_INDEXER_VERSION_TAG"
}
# Upgrade
@ -143,32 +271,68 @@ upgrade() {
source "$DIR/install/upgrade-scripts.sh"
launchUpgrade=1
auto=1
noLog=1
noCache=1
# Extract upgrade options from arguments
if [ $# -gt 0 ]; then
for option in $@
do
case "$option" in
--auto ) auto=0 ;;
--nolog ) noLog=0 ;;
--nocache ) noCache=0 ;;
* ) break ;;
esac
done
fi
if [ -z "$1" ]; then
# Confirmation
if [ $auto -eq 0 ]; then
launchUpgrade=0
else
get_confirmation
launchUpgrade=$?
else
launchUpgrade=0
fi
# Upgrade Dojo
if [ $launchUpgrade -eq 0 ]; then
# Select yaml files
yamlFiles=$(select_yaml_files)
# Update config files
update_config_files
# Cleanup
cleanup
# Load env vars for compose files
source_file "$DIR/conf/docker-bitcoind.conf"
export BITCOIND_RPC_EXTERNAL_IP
eval "docker-compose $yamlFiles build --no-cache"
# Rebuild the images (with or without cache)
if [ $noCache -eq 0 ]; then
eval "docker-compose $yamlFiles build --no-cache"
else
eval "docker-compose $yamlFiles build"
fi
# Start Dojo
docker_up --remove-orphans
# Update the database
update_dojo_db
logs
# Display the logs
if [ $noLog -eq 1 ]; then
logs
fi
fi
}
# Display the onion address
onion() {
if [ "$EXPLORER_INSTALL" == "on" ]; then
V3_ADDR_EXPLORER=$( docker exec -it tor cat /var/lib/tor/hsv3explorer/hostname )
echo "Explorer hidden service address (v3) = $V3_ADDR_EXPLORER"
fi
V2_ADDR=$( docker exec -it tor cat /var/lib/tor/hsv2dojo/hostname )
V3_ADDR=$( docker exec -it tor cat /var/lib/tor/hsv3dojo/hostname )
echo "API hidden service address (v3) = $V3_ADDR"
echo "API hidden service address (v2) = $V2_ADDR"
@ -192,8 +356,17 @@ logs_node() {
fi
}
logs_explorer() {
if [ $3 -eq 0 ]; then
docker exec -ti explorer tail -f /data/logs/$1-$2.log
else
docker exec -ti explorer tail -n $3 /data/logs/$1-$2.log
fi
}
logs() {
source_file "$DIR/conf/docker-bitcoind.conf"
source_file "$DIR/conf/docker-indexer.conf"
source_file "$DIR/conf/docker-common.conf"
case $1 in
@ -212,18 +385,35 @@ logs() {
echo -e "Command not supported for your setup.\nCause: Your Dojo is using an external bitcoind"
fi
;;
indexer )
if [ "$INDEXER_INSTALL" == "on" ]; then
yamlFiles=$(select_yaml_files)
eval "docker-compose $yamlFiles logs --tail=50 --follow indexer"
else
echo -e "Command not supported for your setup.\nCause: Your Dojo is not using an internal indexer"
fi
;;
tor )
docker-compose logs --tail=50 --follow tor
;;
api | pushtx | pushtx-orchest | tracker )
logs_node $1 $2 $3
;;
explorer )
logs_explorer $1 $2 $3
;;
* )
yamlFiles=$(select_yaml_files)
services="nginx node tor db"
if [ "$BITCOIND_INSTALL" == "on" ]; then
services="$services bitcoind"
fi
if [ "$EXPLORER_INSTALL" == "on" ]; then
services="$services explorer"
fi
if [ "$INDEXER_INSTALL" == "on" ]; then
services="$services indexer"
fi
eval "docker-compose $yamlFiles logs --tail=0 --follow $services"
;;
esac
@ -244,6 +434,9 @@ help() {
echo " "
echo " install Install your dojo."
echo " "
echo " Available options:"
echo " --nolog : do not display the logs after Dojo has been laucnhed."
echo " "
echo " logs [module] [options] Display the logs of your dojo. Use CTRL+C to stop the logs."
echo " "
echo " Available modules:"
@ -251,12 +444,14 @@ help() {
echo " dojo.sh logs bitcoind : display the logs of bitcoind"
echo " dojo.sh logs db : display the logs of the MySQL database"
echo " dojo.sh logs tor : display the logs of tor"
echo " dojo.sh logs indexer : display the logs of the internal indexer"
echo " dojo.sh logs api : display the logs of the REST API (nodejs)"
echo " dojo.sh logs tracker : display the logs of the Tracker (nodejs)"
echo " dojo.sh logs pushtx : display the logs of the pushTx API (nodejs)"
echo " dojo.sh logs pushtx-orchest : display the logs of the pushTx Orchestrator (nodejs)"
echo " dojo.sh logs explorer : display the logs of the Explorer"
echo " "
echo " Available options (only available for api, tracker, pushtx and pushtx-orchest modules):"
echo " Available options (only available for api, tracker, pushtx, pushtx-orchest and explorer modules):"
echo " -d [VALUE] : select the type of log to be displayed."
echo " VALUE can be output (default) or error."
echo " -n [VALUE] : display the last VALUE lines"
@ -271,7 +466,11 @@ help() {
echo " "
echo " uninstall Delete your dojo. Be careful! This command will also remove all data."
echo " "
echo " upgrade Upgrade your dojo."
echo " upgrade [options] Upgrade your dojo."
echo " "
echo " Available options:"
echo " --nolog : do not display the logs after Dojo has been restarted."
echo " --nocache : rebuild the docker containers without reusing the cached layers."
echo " "
echo " version Display the version of dojo"
}
@ -318,7 +517,7 @@ case "$subcommand" in
clean
;;
install )
install $1
install "$@"
;;
logs )
module=$1; shift
@ -361,12 +560,12 @@ case "$subcommand" in
stop
;;
uninstall )
uninstall
uninstall "$@"
;;
upgrade )
upgrade $1
upgrade "$@"
;;
version )
version
;;
esac
esac

38
docker/my-dojo/explorer/Dockerfile

@ -0,0 +1,38 @@
FROM node:12-buster
ENV LOGS_DIR /data/logs
ENV APP_DIR /home/node/app
ENV EXPLORER_URL https://github.com/janoside/btc-rpc-explorer/archive
ENV EXPLORER_VERSION 1.1.9
# Install netcat
RUN set -ex && \
apt-get update && \
apt-get install -y netcat
# Create logs and apps directory
RUN mkdir -p "$LOGS_DIR" && \
chown -R node:node "$LOGS_DIR" && \
mkdir "$APP_DIR"
# Download the source code and install it
RUN set -ex && \
wget -qO explorer.tar.gz "$EXPLORER_URL/v$EXPLORER_VERSION.tar.gz" && \
tar -xzvf explorer.tar.gz -C "$APP_DIR/" --strip-components 1 && \
rm explorer.tar.gz && \
cd "$APP_DIR" && \
npm install --only=prod && \
chown -R node:node "$APP_DIR"
# Copy restart script
COPY ./restart.sh "$APP_DIR/restart.sh"
RUN chown node:node "$APP_DIR/restart.sh" && \
chmod u+x "$APP_DIR/restart.sh" && \
chmod g+x "$APP_DIR/restart.sh"
EXPOSE 3002
USER node

44
docker/my-dojo/explorer/restart.sh

@ -0,0 +1,44 @@
#!/bin/bash
cd /home/node/app
explorer_options=(
--port 3002
--host 172.28.1.7
--basic-auth-password "$EXPLORER_KEY"
--coin BTC
--bitcoind-host "$BITCOIND_IP"
--bitcoind-port "$BITCOIND_RPC_PORT"
--bitcoind-user "$BITCOIND_RPC_USER"
--bitcoind-pass "$BITCOIND_RPC_PASSWORD"
--no-rates
--privacy-mode
)
# Blacklist all functions provided by the RPC API
explorer_options+=(--rpc-blacklist "addnode,analyzepsbt,clearbanned,combinepsbt,combinerawtransaction,converttopsbt,createmultisig,createpsbt,createrawtransaction,decodepsbt,decoderawtransaction,decodescript,deriveaddresses,disconnectnode,echo,echojson,estimaterawfee,estimatesmartfee,finalizepsbt,generatetoaddress,generatetodescriptor,getaddednodeinfo,getbestblockhash,getblock,getblockchaininfo,getblockcount,getblockfilter,getblockhash,getblockheader,getblockstats,getblocktemplate,getchaintips,getchaintxstats,getconnectioncount,getdescriptorinfo,getdifficulty,getmemoryinfo,getmempoolancestors,getmempooldescendants,getmempoolentry,getmempoolinfo,getmininginfo,getnettotals,getnetworkhashps,getnetworkinfo,getnodeaddresses,getpeerinfo,getrawmempool,getrawtransaction,getrpcinfo,gettxout,gettxoutproof,gettxoutsetinfo,help,invalidateblock,joinpsbts,listbanned,logging,ping,preciousblock,prioritisetransaction,pruneblockchain,reconsiderblock,savemempool,scantxoutset,sendrawtransaction,setban,setmocktime,setnetworkactive,signmessagewithprivkey,signrawtransactionwithkey,stop,submitblock,submitheader,syncwithvalidationinterfacequeue,testmempoolaccept,uptime,utxoupdatepsbt,validateaddress,verifychain,verifymessage,verifytxoutproof,waitforblock,waitforblockheight,waitfornewblock")
# Use the local indexer if one is defined for Dojo
if [ "$NODE_ACTIVE_INDEXER" == "local_indexer" ]; then
explorer_options+=(--address-api electrumx)
explorer_options+=(--electrumx-servers "tcp://$INDEXER_IP:$INDEXER_RPC_PORT")
# Wait for the local indexer
timeout="720"
i="0"
while [ $i -lt $timeout ]
do
nc -z "$INDEXER_IP" "$INDEXER_RPC_PORT" > /dev/null
if [ $? -eq 0 ] ; then
break
fi
sleep 1
i=$[$i+1]
done
if [ $i -eq $timeout ]; then
echo "Operation timed out"
exit 1
fi
fi
node ./bin/cli.js "${explorer_options[@]}" > /data/logs/explorer-error.log 2> /data/logs/explorer-output.log

43
docker/my-dojo/indexer/Dockerfile

@ -0,0 +1,43 @@
FROM rust:1.37.0-slim
ENV INDEXER_HOME /home/indexer
ENV INDEXER_VERSION 0.1.0
ENV INDEXER_URL https://github.com/Samourai-Wallet/addrindexrs.git
RUN apt-get update && \
apt-get install -y clang cmake git && \
apt-get install -y libsnappy-dev
# Create group and user indexer
RUN addgroup --system -gid 1109 indexer && \
adduser --system --ingroup indexer -uid 1106 indexer
# Create data directory
RUN mkdir "$INDEXER_HOME/addrindexrs" && \
chown -h indexer:indexer "$INDEXER_HOME/addrindexrs"
# Copy restart script
COPY ./restart.sh /restart.sh
RUN chown indexer:indexer /restart.sh && \
chmod 777 /restart.sh
# Copy wait-for-it script
COPY ./wait-for-it.sh /wait-for-it.sh
RUN chown indexer:indexer /wait-for-it.sh && \
chmod u+x /wait-for-it.sh && \
chmod g+x /wait-for-it.sh
USER indexer
# Install addrindexrs
RUN cd "$INDEXER_HOME" && \
git clone "$INDEXER_URL" "$INDEXER_HOME/addrindexrs" && \
cd addrindexrs && \
git checkout "tags/v$INDEXER_VERSION"
RUN cd "$INDEXER_HOME/addrindexrs" && \
cargo install --path .
EXPOSE 50001
STOPSIGNAL SIGINT

22
docker/my-dojo/indexer/restart.sh

@ -0,0 +1,22 @@
#!/bin/bash
set -e
indexer_options=(
-vvvv
--index-batch-size="$INDEXER_BATCH_SIZE"
--jsonrpc-import
--db-dir="/home/indexer/db"
--indexer-rpc-addr="172.28.1.6:50001"
--daemon-rpc-addr="$BITCOIND_IP:$BITCOIND_RPC_PORT"
--cookie="$BITCOIND_RPC_USER:$BITCOIND_RPC_PASSWORD"
--txid-limit="$INDEXER_TXID_LIMIT"
--blocktxids-cache-size-mb="$INDEXER_BLK_TXIDS_CACHE_SIZE_MB"
)
if [ "$COMMON_BTC_NETWORK" == "testnet" ]; then
bitcoind_options+=(--network="testnet")
else
bitcoind_options+=(--network="mainnet")
fi
addrindexrs "${indexer_options[@]}"

178
docker/my-dojo/indexer/wait-for-it.sh

@ -0,0 +1,178 @@
#!/usr/bin/env bash
# Use this script to test if a given TCP host/port are available
WAITFORIT_cmdname=${0##*/}
echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi }
usage()
{
cat << USAGE >&2
Usage:
$WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args]
-h HOST | --host=HOST Host or IP under test
-p PORT | --port=PORT TCP port under test
Alternatively, you specify the host and port as host:port
-s | --strict Only execute subcommand if the test succeeds
-q | --quiet Don't output any status messages
-t TIMEOUT | --timeout=TIMEOUT
Timeout in seconds, zero for no timeout
-- COMMAND ARGS Execute command with args after the test finishes
USAGE
exit 1
}
wait_for()
{
if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
else
echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout"
fi
WAITFORIT_start_ts=$(date +%s)
while :
do
if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then
nc -z $WAITFORIT_HOST $WAITFORIT_PORT
WAITFORIT_result=$?
else
(echo > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1
WAITFORIT_result=$?
fi
if [[ $WAITFORIT_result -eq 0 ]]; then
WAITFORIT_end_ts=$(date +%s)
echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds"
break
fi
sleep 1
done
return $WAITFORIT_result
}
wait_for_wrapper()
{
# In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692
if [[ $WAITFORIT_QUIET -eq 1 ]]; then
timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
else
timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
fi
WAITFORIT_PID=$!
trap "kill -INT -$WAITFORIT_PID" INT
wait $WAITFORIT_PID
WAITFORIT_RESULT=$?
if [[ $WAITFORIT_RESULT -ne 0 ]]; then
echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
fi
return $WAITFORIT_RESULT
}
# process arguments
while [[ $# -gt 0 ]]
do
case "$1" in
*:* )
WAITFORIT_hostport=(${1//:/ })
WAITFORIT_HOST=${WAITFORIT_hostport[0]}
WAITFORIT_PORT=${WAITFORIT_hostport[1]}
shift 1
;;
--child)
WAITFORIT_CHILD=1
shift 1
;;
-q | --quiet)
WAITFORIT_QUIET=1
shift 1
;;
-s | --strict)
WAITFORIT_STRICT=1
shift 1
;;
-h)
WAITFORIT_HOST="$2"
if [[ $WAITFORIT_HOST == "" ]]; then break; fi
shift 2
;;
--host=*)
WAITFORIT_HOST="${1#*=}"
shift 1
;;
-p)
WAITFORIT_PORT="$2"
if [[ $WAITFORIT_PORT == "" ]]; then break; fi
shift 2
;;
--port=*)
WAITFORIT_PORT="${1#*=}"
shift 1
;;
-t)
WAITFORIT_TIMEOUT="$2"
if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi
shift 2
;;
--timeout=*)
WAITFORIT_TIMEOUT="${1#*=}"
shift 1
;;
--)
shift
WAITFORIT_CLI=("$@")
break
;;
--help)
usage
;;
*)
echoerr "Unknown argument: $1"
usage
;;
esac
done
if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then
echoerr "Error: you need to provide a host and port to test."
usage
fi
WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15}
WAITFORIT_STRICT=${WAITFORIT_STRICT:-0}
WAITFORIT_CHILD=${WAITFORIT_CHILD:-0}
WAITFORIT_QUIET=${WAITFORIT_QUIET:-0}
# check to see if timeout is from busybox?
WAITFORIT_TIMEOUT_PATH=$(type -p timeout)
WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH)
if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then
WAITFORIT_ISBUSY=1
WAITFORIT_BUSYTIMEFLAG="-t"
else
WAITFORIT_ISBUSY=0
WAITFORIT_BUSYTIMEFLAG=""
fi
if [[ $WAITFORIT_CHILD -gt 0 ]]; then
wait_for
WAITFORIT_RESULT=$?
exit $WAITFORIT_RESULT
else
if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
wait_for_wrapper
WAITFORIT_RESULT=$?
else
wait_for
WAITFORIT_RESULT=$?
fi
fi
if [[ $WAITFORIT_CLI != "" ]]; then
if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then
echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess"
exit $WAITFORIT_RESULT
fi
exec "${WAITFORIT_CLI[@]}"
else
exit $WAITFORIT_RESULT
fi

31
docker/my-dojo/install/install-scripts.sh

@ -6,6 +6,12 @@ else
source ./conf/docker-bitcoind.conf.tpl
fi
if [ -f ./conf/docker-explorer.conf ]; then
source ./conf/docker-explorer.conf
else
source ./conf/docker-explorer.conf.tpl
fi
if [ -f ./conf/docker-common.conf ]; then
source ./conf/docker-common.conf
else
@ -25,6 +31,18 @@ get_confirmation() {
done
}
# Confirm reinstallation
get_confirmation_reinstall() {
while true; do
read -p "Do you really wish to reinstall Dojo on your computer? [y/n]" yn
case $yn in
[Yy]* ) return 0;;
[Nn]* ) echo "Reinstallation was cancelled."; return 1;;
* ) echo "Please answer yes or no.";;
esac
done
}
# Initialize configuration files from templates
init_config_files() {
# Initialize db scripts
@ -49,9 +67,22 @@ init_config_files() {
cp ./conf/docker-node.conf.tpl ./conf/docker-node.conf
echo "Initialized docker-node.conf"
cp ./conf/docker-explorer.conf.tpl ./conf/docker-explorer.conf
echo "Initialized docker-explorer.conf"
cp ./conf/docker-tor.conf.tpl ./conf/docker-tor.conf
echo "Initialized docker-tor.conf"
cp ./conf/docker-indexer.conf.tpl ./conf/docker-indexer.conf
echo "Initialized docker-indexer.conf"
if [ "$EXPLORER_INSTALL" == "on" ]; then
cp ./nginx/explorer.conf ./nginx/dojo-explorer.conf
else
cp /dev/null ./nginx/dojo-explorer.conf
fi
echo "Initialized dojo-explorer.conf (nginx)"
# Initialize config files for nginx and the maintenance tool
if [ "$COMMON_BTC_NETWORK" == "testnet" ]; then
cp ./nginx/testnet.conf ./nginx/dojo.conf

15
docker/my-dojo/install/uninstall-scripts.sh

@ -0,0 +1,15 @@
#!/bin/bash
# Confirm uninstallation
get_confirmation() {
while true; do
echo "This operation is going to uninstall Dojo from your computer."
echo "Warning: This will delete from disk all the data stored by your Dojo (blockchain data, Dojo db, etc)."
read -p "Do you wish to continue? [y/n]" yn
case $yn in
[Yy]* ) return 0;;
[Nn]* ) echo "Uninstallation was cancelled."; return 1;;
* ) echo "Please answer yes or no.";;
esac
done
}

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

@ -6,6 +6,12 @@ else
source ./conf/docker-common.conf.tpl
fi
if [ -f ./conf/docker-explorer.conf ]; then
source ./conf/docker-explorer.conf
else
source ./conf/docker-explorer.conf.tpl
fi
source ./conf/docker-bitcoind.conf
# Confirm upgrade operation
@ -45,10 +51,23 @@ update_config_files() {
update_config_file ./conf/docker-node.conf ./conf/docker-node.conf.tpl
echo "Initialized docker-node.conf"
update_config_file ./conf/docker-explorer.conf ./conf/docker-explorer.conf.tpl
echo "Initialized docker-explorer.conf"
update_config_file ./conf/docker-tor.conf ./conf/docker-tor.conf.tpl
echo "Initialized docker-tor.conf"
update_config_file ./conf/docker-indexer.conf ./conf/docker-indexer.conf.tpl
echo "Initialized docker-indexer.conf"
# Initialize config files for nginx and the maintenance tool
if [ "$EXPLORER_INSTALL" == "on" ]; then
cp ./nginx/explorer.conf ./nginx/dojo-explorer.conf
else
cp /dev/null ./nginx/dojo-explorer.conf
fi
echo "Initialized dojo-explorer.conf (nginx)"
if [ "$COMMON_BTC_NETWORK" == "testnet" ]; then
cp ./nginx/testnet.conf ./nginx/dojo.conf
echo "Initialized dojo.conf (nginx)"
@ -72,7 +91,11 @@ update_config_file() {
cp -p $2 $1
while IFS='=' read -r key val ; do
sed -i "s~$key=.*~$key=$val~g" "$1"
if [[ $OSTYPE == darwin* ]]; then
sed -i "" "s~$key=.*~$key=$val~g" "$1"
else
sed -i "s~$key=.*~$key=$val~g" "$1"
fi
done < ./original.lines.raw
rm ./original.keys.raw

20
docker/my-dojo/nginx/Dockerfile

@ -1,18 +1,18 @@
FROM nginx:1.15.10-alpine
FROM nginx:1.15.10-alpine
# Create data directory
ENV LOGS_DIR /data/logs
ENV LOGS_DIR /data/logs
RUN mkdir -p "$LOGS_DIR" && \
chown -R nginx:nginx "$LOGS_DIR"
RUN mkdir -p "$LOGS_DIR" && \
chown -R nginx:nginx "$LOGS_DIR"
# Copy configuration files
COPY ./nginx.conf /etc/nginx/nginx.conf
COPY ./dojo.conf /etc/nginx/sites-enabled/dojo.conf
COPY ./nginx.conf /etc/nginx/nginx.conf
COPY ./dojo.conf /etc/nginx/sites-enabled/dojo.conf
COPY ./dojo-explorer.conf /etc/nginx/sites-enabled/dojo-explorer.conf
# Copy wait-for script
COPY ./wait-for /wait-for
COPY ./wait-for /wait-for
RUN chmod u+x /wait-for && \
chmod g+x /wait-for
RUN chmod u+x /wait-for && \
chmod g+x /wait-for

15
docker/my-dojo/nginx/explorer.conf

@ -0,0 +1,15 @@
server {
listen 9080;
server_name _;
resolver 127.0.0.11 valid=30s;
location / {
set $upstream http://explorer:3002;
proxy_pass $upstream;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}

6
docker/my-dojo/node/Dockerfile

@ -1,9 +1,13 @@
FROM node:8.12.0-stretch
FROM node:12-buster
ENV LOGS_DIR /data/logs
ENV APP_DIR /home/node/app
# Add node user to tor group
RUN addgroup --system -gid 1107 tor && \
usermod -a -G tor node
# Install forever
RUN npm install -g forever

12
docker/my-dojo/node/keys.index.js

@ -156,8 +156,18 @@ module.exports = {
*/
indexer: {
// Active indexer
// Values: local_bitcoind | third_party_explorer
// Values: local_bitcoind | local_indexer | third_party_explorer
active: process.env.NODE_ACTIVE_INDEXER,
// Local indexer
localIndexer: {
// IP address or hostname of the local indexer
host: process.env.INDEXER_IP,
// Port
port: parseInt(process.env.INDEXER_RPC_PORT),
// Support of batch requests by the local indexer
// Values: active | inactive
batchRequests: process.env.INDEXER_BATCH_SUPPORT
},
// Use a SOCKS5 proxy for all communications with external services
// Values: null if no socks5 proxy used, otherwise the url of the socks5 proxy
socks5Proxy: 'socks5h://172.28.1.4:9050',

2
docker/my-dojo/overrides/bitcoind.install.yaml

@ -10,7 +10,7 @@ services:
- ./.env
- ./conf/docker-common.conf
- ./conf/docker-bitcoind.conf
restart: on-failure
restart: always
command: "/wait-for-it.sh tor:9050 --timeout=720 --strict -- /restart.sh"
expose:
- "8333"

30
docker/my-dojo/overrides/explorer.install.yaml

@ -0,0 +1,30 @@
version: "3.2"
services:
explorer:
image: "samouraiwallet/dojo-explorer:${DOJO_EXPLORER_VERSION_TAG}"
container_name: explorer
build:
context: ./explorer
env_file:
- ./.env
- ./conf/docker-bitcoind.conf
- ./conf/docker-node.conf
- ./conf/docker-indexer.conf
- ./conf/docker-explorer.conf
restart: always
command: "/home/node/app/restart.sh"
expose:
- "3002"
volumes:
- data-explorer:/data/logs
networks:
dojonet:
ipv4_address: 172.28.1.7
node:
depends_on:
- explorer
volumes:
data-explorer:

27
docker/my-dojo/overrides/indexer.install.yaml

@ -0,0 +1,27 @@
version: "3.2"
services:
indexer:
image: "samouraiwallet/dojo-indexer:${DOJO_INDEXER_VERSION_TAG}"
container_name: indexer
build:
context: ./indexer
env_file:
- ./.env
- ./conf/docker-common.conf
- ./conf/docker-bitcoind.conf
- ./conf/docker-indexer.conf
restart: on-failure
command: "/wait-for-it.sh tor:9050 --timeout=360 --strict -- /restart.sh"
expose:
- "50001"
volumes:
- data-indexer:/home/indexer
depends_on:
- tor
networks:
dojonet:
ipv4_address: 172.28.1.6
volumes:
data-indexer:

11
docker/my-dojo/tor/Dockerfile

@ -1,8 +1,9 @@
FROM debian:stretch
FROM debian:buster
ENV TOR_HOME /var/lib/tor
ENV TOR_URL https://archive.torproject.org/tor-package-archive
ENV TOR_VERSION 0.3.5.8
ENV TOR_GPG_KS_URI hkp://keyserver.ubuntu.com:80
ENV TOR_GPG_KEY1 0xEB5A896A28988BF5
ENV TOR_GPG_KEY2 0xC218525819F78451
ENV TOR_GPG_KEY3 0x21194EBB165733EA
@ -24,10 +25,10 @@ RUN set -ex && \
cd /usr/local/src && \
wget -qO "tor-$TOR_VERSION.tar.gz" "$TOR_URL/tor-$TOR_VERSION.tar.gz" && \
wget -qO "tor-$TOR_VERSION.tar.gz.asc" "$TOR_URL/tor-$TOR_VERSION.tar.gz.asc" && \
gpg --keyserver ipv4.pool.sks-keyservers.net --recv-keys "$TOR_GPG_KEY1" && \
gpg --keyserver ipv4.pool.sks-keyservers.net --recv-keys "$TOR_GPG_KEY2" && \
gpg --keyserver ipv4.pool.sks-keyservers.net --recv-keys "$TOR_GPG_KEY3" && \
gpg --keyserver ipv4.pool.sks-keyservers.net --recv-keys "$TOR_GPG_KEY4" && \
gpg --keyserver "$TOR_GPG_KS_URI" --recv-keys "$TOR_GPG_KEY1" && \
gpg --keyserver "$TOR_GPG_KS_URI" --recv-keys "$TOR_GPG_KEY2" && \
gpg --keyserver "$TOR_GPG_KS_URI" --recv-keys "$TOR_GPG_KEY3" && \
gpg --keyserver "$TOR_GPG_KS_URI" --recv-keys "$TOR_GPG_KEY4" && \
gpg --verify "tor-$TOR_VERSION.tar.gz.asc" && \
tar -xzvf "tor-$TOR_VERSION.tar.gz" -C /usr/local/src && \
cd "/usr/local/src/tor-$TOR_VERSION" && \

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

@ -25,6 +25,13 @@ tor_options=(
--HiddenServiceDirGroupReadable 1
)
if [ "$EXPLORER_INSTALL" == "on" ]; then
tor_options+=(--HiddenServiceDir /var/lib/tor/hsv3explorer)
tor_options+=(--HiddenServiceVersion 3)
tor_options+=(--HiddenServicePort "80 172.29.1.3:9080")
tor_options+=(--HiddenServiceDirGroupReadable 1)
fi
if [ "$TOR_USE_BRIDGES" == "on" ]; then
tor_options+=(--ClientTransportPlugin "obfs4 exec /usr/local/bin/obfs4proxy")
tor_options+=(--UseBridges 1)

4
lib/bitcoind-rpc/rpc-client.js

@ -37,7 +37,9 @@ class RpcClient {
return async function(...args) {
const result = await origMethod.apply(target.client, args)
if (result.result) {
if (Array.isArray(result)) {
return result
} else if (result.result) {
return result.result
} else if (result.error) {
throw result.error

134
lib/bitcoind-rpc/transactions.js

@ -46,6 +46,38 @@ class Transactions {
this.rpcClient = new RpcClient()
}
/**
* Get the transactions for a given array of txids
* @param {string[]} txids - txids of the transaction to be retrieved
* @param {boolean} fees - true if fees must be computed, false otherwise
* @returns {Promise} return an array of transactions (object[])
*/
async getTransactions(txids, fees) {
try {
const rpcCalls = txids.map(txid => {
return {
'method': 'getrawtransaction',
'params': [txid, true]
}
})
const txs = await this.rpcClient.batch(rpcCalls)
return await util.seriesCall(txs, async tx => {
if (tx.result == null) {
Logger.info(` got null for ${txids[tx.id]}`)
return null
} else {
return this._prepareTxResult(tx.result, fees)
}
})
} catch(e) {
Logger.error(e, 'Transaction.getTransactions()')
return Promise.reject(errors.generic.GEN)
}
}
/**
* Get the transaction for a given txid
* @param {string} txid - txid of the transaction to be retrieved
@ -61,64 +93,73 @@ class Transactions {
try {
const tx = await this.rpcClient.getrawtransaction(txid, true)
const ret = await this._prepareTxResult(tx)
// Store the result in cache
if (ret.block && ret.block.hash)
this.txCache.set(txid, ret)
return ret
} catch(e) {
Logger.error(e, 'Transaction.getTransaction()')
return Promise.reject(errors.generic.GEN)
}
}
const ret = {
txid: tx.txid,
size: tx.size,
vsize: tx.vsize,
version: tx.version,
locktime: tx.locktime,
inputs: [],
outputs: []
}
/**
* Formats a transaction object returned by the RPC API
* @param {object} tx - transaction
* @param {boolean} fees - true if fees must be computed, false otherwise
* @returns {Promise} return an array of inputs (object[])
*/
async _prepareTxResult(tx, fees) {
const ret = {
txid: tx.txid,
size: tx.size,
vsize: tx.vsize,
version: tx.version,
locktime: tx.locktime,
inputs: [],
outputs: []
}
if (!ret.vsize)
delete ret.vsize
if (!ret.vsize)
delete ret.vsize
if (tx.time)
ret.created = tx.time
if (tx.time)
ret.created = tx.time
// Process block informations
if (tx.blockhash && tx.confirmations && tx.blocktime) {
ret.block = {
height: rpcLatestBlock.height - tx.confirmations + 1,
hash: tx.blockhash,
time: tx.blocktime
}
// Process block informations
if (tx.blockhash && tx.confirmations && tx.blocktime) {
ret.block = {
height: rpcLatestBlock.height - tx.confirmations + 1,
hash: tx.blockhash,
time: tx.blocktime
}
}
let inAmount = 0
let outAmount = 0
// Process the inputs
ret.inputs = await this._getInputs(tx, fees)
inAmount = ret.inputs.reduce((prev, cur) => prev + cur.outpoint.value, 0)
// Process the outputs
ret.outputs = await this._getOutputs(tx)
outAmount = ret.outputs.reduce((prev, cur) => prev + cur.value, 0)
let inAmount = 0
let outAmount = 0
// Process the fees (if needed)
if (fees) {
ret.fees = inAmount - outAmount
if (ret.fees > 0 && ret.size)
ret.feerate = Math.round(ret.fees / ret.size)
if (ret.fees > 0 && ret.vsize)
ret.vfeerate = Math.round(ret.fees / ret.vsize)
}
// Process the inputs
ret.inputs = await this._getInputs(tx, fees)
inAmount = ret.inputs.reduce((prev, cur) => prev + cur.outpoint.value, 0)
// Store in cache
if (ret.block && ret.block.hash)
this.txCache.set(keyCache, ret)
// Process the outputs
ret.outputs = await this._getOutputs(tx)
outAmount = ret.outputs.reduce((prev, cur) => prev + cur.value, 0)
return ret
} catch(e) {
Logger.error(e, 'Transaction.getTransaction()')
return Promise.reject(errors.generic.GEN)
// Process the fees (if needed)
if (fees) {
ret.fees = inAmount - outAmount
if (ret.fees > 0 && ret.size)
ret.feerate = Math.round(ret.fees / ret.size)
if (ret.fees > 0 && ret.vsize)
ret.vfeerate = Math.round(ret.fees / ret.vsize)
}
return ret
}
/**
* Extract information about the inputs of a transaction
* @param {object} tx - transaction
@ -180,7 +221,6 @@ class Transactions {
/**
* Extract information about the outputs of a transaction
* @param {object} tx - transaction
* @param {boolean} fees - true if fees must be computed, false otherwise
* @returns {Promise} return an array of outputs (object[])
*/
async _getOutputs(tx) {

58
lib/db/mysql-db-wrapper.js

@ -1358,6 +1358,26 @@ class MySqlDbWrapper {
return (result.length == 0) ? null : result[0].txnID
}
/**
* Get the mysql IDs of a collection of transactions
* @param {string[]} txids - txids of the transactions
* @returns {object[]} returns an array of {txnTxid: txnId}
*/
async getTransactionsIds(txids) {
if (txids.length == 0)
return []
const sqlQuery = 'SELECT `txnID`, `txnTxid` FROM `transactions` WHERE `txnTxid` IN (?)'
const params = [txids]
const query = mysql.format(sqlQuery, params)
const results = await this._query(query)
const ret = {}
for (let r of results)
ret[r.txnTxid] = r.txnID
return ret
}
/**
* Get the mysql IDs of a set of transactions
* @param {string[]} txid - array of transactions txids
@ -1396,6 +1416,29 @@ class MySqlDbWrapper {
return this._query(query)
}
/**
* Insert a collection of transactions in db
* @param {object[]} txs - array of {txid, version, locktime}
*/
async addTransactions(txs) {
if (txs.length == 0)
return
const sqlQuery = 'INSERT INTO `transactions` \
(txnTxid, txnCreated, txnVersion, txnLocktime) VALUES ? \
ON DUPLICATE KEY UPDATE txnVersion = VALUES(txnVersion)'
const params = [txs.map(tx => [
tx.txid,
tx.created ? tx.created : util.unix(),
tx.version,
tx.locktime
])]
const query = mysql.format(sqlQuery, params)
return this._query(query)
}
/**
* Get a transaction for a given txid
* @param {string} txid - txid of the transaction
@ -1773,6 +1816,21 @@ class MySqlDbWrapper {
return (result.length == 1) ? result[0] : null
}
/**
* Get a collection of blocks identified by the blocks hashes
* @param {string[]} hashes - blocks hashes
* @returns {object[]} returns the blocks
*/
async getBlocksByHashes(hashes) {
if (hashes.length == 0)
return []
const sqlQuery = 'SELECT * FROM `blocks` WHERE `blockHash` IN (?)'
const params = [hashes]
const query = mysql.format(sqlQuery, params)
return await this._query(query)
}
/**
* Get details about all blocks at a given block height
* @param {integer} height - block height

156
lib/remote-importer/remote-importer.js

@ -115,7 +115,10 @@ class RemoteImporter {
if (txMaps.txMap[txid])
aTxs.push(txMaps.txMap[txid])
return util.seriesCall(aTxs, tx => this.addTransaction(tx, addrIdMap))
// Store the transactions by batches of 200 transactions
const txsChunks = util.splitList(aTxs, 200)
for (let txsChunk of txsChunks)
await this.addTransactions(txsChunk, addrIdMap)
}
/**
@ -260,19 +263,21 @@ class RemoteImporter {
Logger.info(` Got ${scanTx.length} transactions`)
await util.seriesCall(scanTx, async txid => {
try {
const tx = await rpcTxns.getTransaction(txid, false)
if (tx == null) {
Logger.info(` got null for ${txid}`)
return null
// Retrieve the transactions by batches of 200 transactions
const txsChunks = util.splitList(scanTx, 200)
try {
for (let txsChunk of txsChunks) {
const txs = await rpcTxns.getTransactions(txsChunk, false)
for (let tx of txs) {
if (tx != null) {
ret.transactions.push(tx)
txids[tx.txid] = true
}
}
ret.transactions.push(tx)
txids[tx.txid] = true
} catch(e) {
Logger.error(e, `RemoteImporter.xpubScan() : rawTransaction error, txid ${txid}`)
}
})
} catch(e) {
Logger.error(e, `RemoteImporter.xpubScan() : getTransactions error`)
}
if (gotTransactions) {
// We must go deeper
@ -335,15 +340,14 @@ class RemoteImporter {
Logger.info(` Got ${scanTx.length} transactions`)
// Get transaction s data from bitcoind
await util.seriesCall(scanTx, async txid => {
const tx = await rpcTxns.getTransaction(txid, false)
if (tx == null) {
Logger.info(` got null for ${txid}`)
return null
}
txns.push(tx)
})
// Retrieve the transactions by batches of 100 transactions
const txsChunks = util.splitList(scanTx, 100)
for (let txsChunk of txsChunks) {
const txs = await rpcTxns.getTransactions(txsChunk, false)
for (let tx of txs)
if (tx != null)
txns.push(tx)
}
// Import addresses and transactions into the database
await db.addAddresses(imported)
@ -368,70 +372,86 @@ class RemoteImporter {
}
/**
* Add a transaction to the database.
* @param {object} tx - transaction object
* @params {Promise}
* Add a collection of transactions to the database.
* @param {object[]} txs - array of transaction objects
* @params {object} addrIdMap - map address => addrId
* @returns {Promise}
*/
async addTransaction(tx, addrIdMap) {
const outputs = []
async addTransactions(txs, addrIdMap) {
try {
// Store the transaction into the database
await db.addTransaction(tx)
// Confirm the transaction
if (tx.block) {
const block = await db.getBlockByHash(tx.block.hash)
if (block)
await db.confirmTransactions([tx.txid], block.blockID)
// Store the transactions into the database
await db.addTransactions(txs)
// Confirm the transactions if needed
const blocksHashes = new Set()
for (let tx of txs)
if (tx.block)
blocksHashes.add(tx.block.hash)
const blocks = await db.getBlocksByHashes(Array.from(blocksHashes))
for (let block of blocks) {
// Filter the transactions by blockHash
const filteredTxs = txs.filter(tx => (tx.block && tx.block.hash == block.blockHash))
if (filteredTxs.length > 0) {
const txids = filteredTxs.map(tx => tx.txid)
// Asynchronous confirmations
db.confirmTransactions(txids, block.blockID)
}
}
// Retrieve the database id for the transaction
let txnID = await db.ensureTransactionId(tx.txid)
// Process the outputs
for (let output of tx.outputs) {
if (addrIdMap[output.address]) {
outputs.push({
txnID,
addrID: addrIdMap[output.address],
outIndex: output.n,
outAmount: output.value,
outScript: output.scriptpubkey,
})
// Retrieve the database ids for the transactions
const txids = txs.map(tx => tx.txid)
const mapTxsIds = await db.getTransactionsIds(txids)
// Store the outputs in db
const outputs = []
for (let tx of txs) {
for (let output of tx.outputs) {
if (addrIdMap[output.address]) {
outputs.push({
txnID: mapTxsIds[tx.txid],
addrID: addrIdMap[output.address],
outIndex: output.n,
outAmount: output.value,
outScript: output.scriptpubkey,
})
}
}
}
await db.addOutputs(outputs)
// Process the inputs
// Get any outputs spent by the inputs of this transaction, add those
// database outIDs to the corresponding transaction inputs, and store.
const res = await db.getOutputIds(tx.inputs.map(input => input.outpoint))
const spent = {}
// Store the inputs in db
const inputs = []
const spent = {}
// Get any outputs spent by the inputs of this transaction,
// add those database outIDs to the corresponding inputs, and store.
let outpoints = []
for (let tx of txs)
outpoints = outpoints.concat(tx.inputs.map(input => input.outpoint))
const res = await db.getOutputIds(outpoints)
for (let r of res)
spent[`${r.txnTxid}-${r.outIndex}`] = r.outID
for (let input of tx.inputs) {
let key = `${input.outpoint.txid}-${input.outpoint.vout}`
if (spent[key]) {
inputs.push({
outID: spent[key],
txnID,
inIndex: input.n,
inSequence: input.seq
})
for (let tx of txs) {
for (let input of tx.inputs) {
const key = `${input.outpoint.txid}-${input.outpoint.vout}`
if (spent[key]) {
inputs.push({
outID: spent[key],
txnID: mapTxsIds[tx.txid],
inIndex: input.n,
inSequence: input.seq
})
}
}
}
await db.addInputs(inputs)
} catch(e) {
Logger.error(e, `RemoteImporter.addTransaction() : xpub ${tx.txid}`)
Logger.error(null, JSON.stringify(tx,null,2))
Logger.error(e, `RemoteImporter.addTransactions() :`)
}
}

2
lib/remote-importer/sources.js

@ -75,7 +75,7 @@ class Sources {
}
} catch(e) {
Logger.error(e, `Sources.getAddresses() : ${addresses} from ${this.source.base}`)
Logger.error(null, `Sources.getAddresses() : Error while requesting ${addresses} from ${this.source.base}`)
} finally {
return ret
}

18
lib/util.js

@ -101,20 +101,14 @@ class Util {
* Splits a list into a list of lists each with maximum length LIMIT
*/
static splitList(list, limit) {
if (list.length <= limit) {
if (list.length <= limit)
return [list]
} else {
const lists = []
// How many lists to create?
const count = Math.ceil(list.length / limit)
// How many elements per list (max)?
const els = Math.ceil(list.length / count)
for (let i=0; i < count; i++) {
lists.push(list.slice(i * els, (i+1) * els))
}
return lists
const lists = []
while (list.length) {
lists.push(list.splice(0, limit))
}
return lists
}
/**

809
package-lock.json

File diff suppressed because it is too large

5
package.json

@ -1,6 +1,6 @@
{
"name": "samourai-dojo",
"version": "1.4.0",
"version": "1.5.0",
"description": "Backend server for Samourai Wallet",
"main": "accounts/index.js",
"scripts": {
@ -26,6 +26,7 @@
"helmet": "3.12.1",
"lodash": "4.17.14",
"lru-cache": "4.0.2",
"minimist": "1.2.2",
"mysql": "2.16.0",
"passport": "0.4.0",
"passport-localapikey-update": "0.6.0",
@ -37,6 +38,6 @@
"zeromq": "4.2.0"
},
"devDependencies": {
"mocha": "^6.2.0"
"mocha": "^7.1.1"
}
}

17
static/admin/css/style.css

@ -370,11 +370,20 @@ table.spaced tr td {
}
/* PAIRING */
#qr-label {
#qr-label,
#qr-explorer-label {
margin: 0 0 20px 0;
text-align: center;
display: inline-block;
}
#qr-container,
#qr-explorer-container {
display: inline-block;
}
#qr-pairing {
#qr-pairing,
#qr-explorer-pairing {
width: 276px;
height: 276px;
padding: 10px;
@ -390,8 +399,8 @@ table.spaced tr td {
}
.halfwidth {
width: 50%;
min-width: 50%;
width: 49%;
min-width: 49%;
}
.fullwidth {

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

@ -39,6 +39,15 @@ var lib_api = {
return this.sendGetUriEncoded(uri, {});
},
/**
* Get block explorer pairing info
*/
getExplorerPairingInfo: function() {
let prefix = conf['prefixes']['support'];
let uri = this.baseUri + '/' + prefix + '/pairing/explorer';
return this.sendGetUriEncoded(uri, {});
},
/**
* PushTx Status
*/

18
static/admin/tool/index.html

@ -86,10 +86,22 @@
<div class="col-xs-10 json-data-container">
<!-- PAIRING -->
<div id="screen-pairing">
<div class="center" id="qr-label">
PAIR YOUR SAMOURAI WALLET WITH YOUR DOJO BY SCANNING THIS QRCODE
<div class="row">
<div id="qr-label" class="halfwidth">
PAIR YOUR WALLET WITH YOUR DOJO
</div>
<div id="qr-explorer-label" class="halfwidth">
PAIR YOUR WALLET WITH YOUR BLOCK EXPLORER
</div>
</div>
<div class="row">
<div id="qr-container" class="halfwidth">
<div id="qr-pairing"></div>
</div>
<div id="qr-explorer-container" class="halfwidth">
<div id="qr-explorer-pairing"></div>
</div>
</div>
<div class="center" id="qr-pairing"></div>
</div>
<!-- MAINTENANCE -->
<div id="form-maintenance">

55
static/admin/tool/index.js

@ -16,12 +16,25 @@ function displayQRPairing() {
const activeTab = sessionStorage.getItem('activeTab');
processAction(activeTab).then(
function (result) {
if (!result) {return;}
const url = window.location.protocol + '//' + window.location.host + conf['api']['baseUri'];
result['pairing']['url'] = url;
const textJson = JSON.stringify(result, null, 4);
$("#qr-pairing").html('') // clear qrcode first
$('#qr-pairing').qrcode({width: 256, height: 256, text: textJson});
if (result) {
if (result['api']) {
const textJson = JSON.stringify(result['api'], null, 4);
$("#qr-pairing").html('') // clear qrcode first
$('#qr-pairing').qrcode({width: 256, height: 256, text: textJson});
}
if (result['explorer'] && result['explorer']['pairing']['url']) {
const textJson = JSON.stringify(result['explorer'], null, 4);
$("#qr-explorer-pairing").html('') // clear qrcode first
$('#qr-explorer-pairing').qrcode({width: 256, height: 256, text: textJson});
} else {
$("#qr-label").removeClass('halfwidth');
$("#qr-label").addClass('fullwidth');
$("#qr-container").removeClass('halfwidth');
$("#qr-container").addClass('fullwidth');
$("#qr-explorer-label").hide();
$("#qr-explorer-container").hide();
}
}
},
function (jqxhr) {}
);
@ -147,14 +160,34 @@ function preparePage() {
* Process action (api calls)
*/
function processAction(activeTab, args, args2, args3) {
if (activeTab == '#link-pairing')
return lib_api.getPairingInfo();
else if (activeTab == '#link-status-api')
if (activeTab == '#link-pairing') {
//return lib_api.getPairingInfo();
let result = {
'api': null,
'explorer': null
};
return lib_api.getPairingInfo().then(apiInfo => {
if (apiInfo) {
apiInfo['pairing']['url'] = window.location.protocol + '//' + window.location.host + conf['api']['baseUri'];
result['api'] = apiInfo;
}
}).then(() => {
return lib_api.getExplorerPairingInfo();
}).then(explorerInfo => {
if (explorerInfo)
result['explorer'] = explorerInfo;
return result
}).catch(e => {
console.log(e);
return result;
});
} else if (activeTab == '#link-status-api') {
return lib_api.getApiStatus();
else if (activeTab == '#link-status-pushtx')
} else if (activeTab == '#link-status-pushtx') {
return lib_api.getPushtxStatus();
else if (activeTab == '#link-orchestrator')
} else if (activeTab == '#link-orchestrator') {
return lib_api.getOrchestratorStatus();
}
if (args == '') {
alert('Argument is mandatory');

Loading…
Cancel
Save