...
 
Commits (31)
......@@ -18,7 +18,7 @@ It provides in a single command the setup of a full Samourai backend composed of
* the 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 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.
* an optional indexer of Bitcoin addresses ([addrindexrs](hhttps://code.samourai.io/dojo/addrindexrs)) providing fast and private rescans of HD accounts and loose addresses.
See [the documentation](./doc/DOCKER_setup.md) for detailed setup instructions.
......
......@@ -3,6 +3,7 @@
## Releases ##
- [v1.7.0](#1_7_0)
- [v1.6.0](#1_6_0)
- [v1.5.0](#1_5_0)
- [v1.4.1](#1_4_1)
......@@ -12,6 +13,51 @@
- [v1.1.0](#1_1_0)
<a name="1_7_0"/>
## Samourai Dojo v1.7.0 ##
### Notable changes ###
#### New optional strict_mode_vouts added to PushTx endpoints ####
A new optional "strict mode" is added to the /pushtx and /pushtx/schedule endpoints of the API.
This strict mode enforces a few additional checks on a selected subset of the outputs of a transaction before it's pushed on the P2P network or before it's scheduled for a delayed push.
See this [doc](https://code.samourai.io/dojo/samourai-dojo/-/blob/develop/doc/POST_pushtx.md) for detailed information.
#### Upgrade of whirlpool to v0.10.8 ####
Upgrade to [whirlpool-cli](https://code.samourai.io/whirlpool/whirlpool-client-cli) v0.10.8
A new config parameter `WHIRLPOOL_RESYNC` is added to docker-whirlpool.conf. When set to `on`, mix counters are resynchronized on startup of whirlpool-cli.
### Change log ###
#### MyDojo ####
- [#mr142](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/142) add setup of explorer in keys.index.js
- [#mr143](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/143) update doc and package.json with url of new repository
- [#mr144](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/144) switch addrindexrs repo to gitlab
- [#mr145](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/145) explicitely set algo used for jwt signatures
- [#mr146](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/146) upgrade whirlpool to whirlpool-cli 0.10.7
- [#mr147](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/147) add new optional strict_mode_vouts to pushtx endpoints
- [#mr148](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/148) status code pushtx endpoints
- [#mr149](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/149) upgrade whirlpool to whirlpool-cli 0.10.8
#### Credits ###
- kenshin-samourai
- zeroleak
<a name="1_6_0"/>
## Samourai Dojo v1.6.0 ##
......
......@@ -312,20 +312,11 @@ class SupportRestApi {
*/
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, 'API : SupportRestApi.getPairing() : Cannot read explorer onion address')
}
}
const ret = {
'pairing': {
'type': 'explorer.btcRpcExplorer',
'url': url,
'key': process.env.EXPLORER_KEY
'type': `explorer.${keys.explorer.active}`,
'url': keys.explorer.uri,
'key': keys.explorer.password
}
}
HttpServer.sendRawData(res, JSON.stringify(ret, null, 2))
......
......@@ -21,7 +21,7 @@ A word of caution, though, the default values of these options try to maximize y
## 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).
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://code.samourai.io/dojo/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 ###
......@@ -122,7 +122,7 @@ nano ./conf/docker-node.conf
## Local Whirlpool client ##
This setup allows to install and run a [Whirlpool client](https://github.com/Samourai-Wallet/whirlpool-client-cli) inside MyDojo.
This setup allows to install and run a [Whirlpool client](https://code.samourai.io/whirlpool/whirlpool-client-cli) inside MyDojo.
The client can be configured and controlled through a REST API exposed as a Tor hidden service.
......@@ -144,7 +144,7 @@ nano ./conf/docker-whirlpool.conf
### Installation of Whirlpool GUI ###
The [Whirlpool GUI application]((https://github.com/Samourai-Wallet/whirlpool-gui)) provides a graphical interface for your Whirlpool client.
The [Whirlpool GUI application]((https://code.samourai.io/whirlpool/whirlpool-gui)) provides a graphical interface for your Whirlpool client.
These steps describe how to install the Whirlpool GUI application how a computer and how to connect it to your Whirlpool client.
......
......@@ -22,7 +22,7 @@ Follow the instructions in this [video](https://www.youtube.com/watch?v=6M1DivpQ
Also, remember to install the virtual box at a directory where you have __enough free space__ to install the Dojo. Specially if you are running a full node.
After the setup is complete, start the virtual box and open a terminal window then proceed to install the Dojo following these [instructions](https://github.com/Samourai-Wallet/samourai-dojo/blob/develop/doc/DOCKER_setup.md#install).
After the setup is complete, start the virtual box and open a terminal window then proceed to install the Dojo following these [instructions](https://code.samourai.io/dojo/samourai-dojo/-/blob/master/doc/DOCKER_setup.md#install).
......@@ -73,7 +73,7 @@ Note: _This is an important step, otherwise, it's probable that when you run the
(_At 4 CPUs, 8GB of RAM and a 4GiB Swap - the initial block download took 4.5 days at the time of writing_).
### Install the DOJO
Follow the instructions [here](https://github.com/Samourai-Wallet/samourai-dojo/blob/develop/doc/DOCKER_setup.md) starting at the step:
Follow the instructions [here](https://code.samourai.io/dojo/samourai-dojo/-/blob/master/doc/DOCKER_setup.md) starting at the step:
__"Download the most recent release of Dojo from Github"__
_Note: For tracking progress, open Kitematic and follow the bitcoind logs. You'll be able to see the Blockchain verification process under the _progress_ log variable (1.00 = fully validated). This process takes a long time. Just let it do its thing. In my system it took 3 days._
......@@ -105,11 +105,11 @@ This installation was tested on an iMac (late 2014) with a 3.5GHz i5 processor w
4. Click __Advanced__ and increase the CPU count, Memory and Swap sizes. Adjusting these will speed up the blockchain validation process
### Install the DOJO Pointing and Existing bitcoind
Follow the instructions [here](https://github.com/Samourai-Wallet/samourai-dojo/blob/develop/doc/DOCKER_setup.md) starting at the step:
Follow the instructions [here](hhttps://code.samourai.io/dojo/samourai-dojo/-/blob/master/doc/DOCKER_setup.md) starting at the step:
__"Download the most recent release of Dojo from Github"__ until you reach __"Launch the Installation of Your Dojo with"__ ***DO NOT LAUNCH DOJO YET***
Once you Reach Step __"Launch the Installation of Your Dojo with"__ from above you will need to read and follow the instructions from [here](https://github.com/Samourai-Wallet/samourai-dojo/blob/develop/doc/DOCKER_advanced_setups.md)
Once adjustments are made to your external bitcoind bitcoin.conf __(location dependent on what device you have bitcoind)__ and docker-bitcoind.conf.tpl __(dojo_dir > docker > my-dojo > conf)__ you can proceed with Install and revert back to original instructions [here](https://github.com/Samourai-Wallet/samourai-dojo/blob/develop/doc/DOCKER_setup.md) at section __"Launch the Installation of Your Dojo with"__
Once you Reach Step __"Launch the Installation of Your Dojo with"__ from above you will need to read and follow the instructions from [here](https://code.samourai.io/dojo/samourai-dojo/-/blob/master/doc/DOCKER_advanced_setups.md)
Once adjustments are made to your external bitcoind bitcoin.conf __(location dependent on what device you have bitcoind)__ and docker-bitcoind.conf.tpl __(dojo_dir > docker > my-dojo > conf)__ you can proceed with Install and revert back to original instructions [here](https://code.samourai.io/dojo/samourai-dojo/-/blob/master/doc/DOCKER_setup.md) at section __"Launch the Installation of Your Dojo with"__
_Note: For tracking progress, open terminal, change directory to my-dojo and run /dojo.sh logs nodejs
__Some possible optimization tips:__
......
......@@ -6,7 +6,7 @@ MyDojo is a set of Docker containers providing a full Samourai backend composed
* 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.
* an optional indexer of Bitcoin addresses ([addrindexrs](https://code.samourai.io/dojo/addrindexrs)) providing fast and private rescans of HD accounts and loose addresses.
## Table of Content ##
......@@ -113,7 +113,7 @@ 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)
For Raspberry Pi4 and Odroid N2, see the [Ronin Dojo Project](https://code.samourai.io/ronindojo/RoninDojo)
This procedure allows to install a new Dojo from scratch.
......@@ -122,7 +122,7 @@ This procedure allows to install a new Dojo from scratch.
* Install [Tor Browser](https://www.torproject.org/projects/torbrowser.html.en) on the host machine.
* Download the most recent release of Dojo from [Github](https://github.com/Samourai-Wallet/samourai-dojo/archive/master.zip)
* Download the most recent release of Dojo from [Gitlab](https://code.samourai.io/dojo/samourai-dojo/-/archive/master/samourai-dojo-master.zip)
* Uncompress the archive on the host machine in a temporary directory of your choice (named `<tmp_dir>` in this doc)
......@@ -209,7 +209,7 @@ This procedure allows to upgrade your Dojo with a new version.
./dojo.sh stop
```
* Download the most recent release of Dojo from [Github](https://github.com/Samourai-Wallet/samourai-dojo/releases)
* Download the most recent release of Dojo from [Gitlab](https://code.samourai.io/dojo/samourai-dojo/-/releases)
* Uncompress the archive on the host machine in a temporary directory of your choice (named `<tmp_dir>` in this doc)
......
......@@ -76,13 +76,13 @@ cd <dojo_dir>
```
cp -r ./ ../dojo-backup
```
- Download latest Dojo from [GitHub releases](https://github.com/Samourai-Wallet/samourai-dojo/releases)
- Download latest Dojo from [Gitlab releases](https://code.samourai.io/dojo/samourai-dojo/-/releases)
```
mkdir newDojo
cd newDojo
wget https://github.com/Samourai-Wallet/samourai-dojo/archive/v1.5.0.tar.gz
tar xzvf v1.5.0.tar.gz
cp -r samourai-dojo-1.5.0/* ../
wget https://code.samourai.io/dojo/samourai-dojo/-/archive/v1.7.0/samourai-dojo-v1.7.0.tar.gz
tar xzvf samourai-dojo-v1.7.0.tar.gz
cp -r samourai-dojo-v1.7.0/* ../
cd ..
```
- Upgrade
......
......@@ -135,4 +135,4 @@ Status code 400 with JSON response:
```
## Notes
Multiaddr response is consumed by the wallet in the [APIFactory](https://github.com/Samourai-Wallet/samourai-wallet-android/blob/master/app/src/main/java/com/samourai/wallet/api/APIFactory.java)
Multiaddr response is consumed by the wallet in the [APIFactory](https://code.samourai.io/wallet/samourai-wallet-android/-/blob/master/app/src/main/java/com/samourai/wallet/api/APIFactory.java)
......@@ -79,4 +79,4 @@ Status code 400 with JSON response:
```
## Notes
Unspent response is consumed by the wallet in the [APIFactory](https://github.com/Samourai-Wallet/samourai-wallet-android/blob/master/app/src/main/java/com/samourai/wallet/api/APIFactory.java)
Unspent response is consumed by the wallet in the [APIFactory](https://code.samourai.io/wallet/samourai-wallet-android/-/blob/master/app/src/main/java/com/samourai/wallet/api/APIFactory.java)
......@@ -10,6 +10,7 @@ Parameters must be passed in the body of the request as url encoded arguments.
## Parameters
* **tx** - `hex string` - The raw transaction hex
* **at** - `string` (optional) - Access Token (json web token). Required if authentication is activated. Alternatively, the access token can be passed through the `Authorization` HTTP header (with the `Bearer` scheme).
* **strict_mode_vouts** (optional) - `string` - A pipe-separated list of outpoints indices. A strict verification is enforced on these outpoints before the transaction is pushed. Strict mode checks that addresses associated to these outputs aren't reused. If verifications fail, push is aborted and an error is returned.
### Example
......@@ -18,6 +19,7 @@ Parameters must be passed in the body of the request as url encoded arguments.
POST /pushtx/
tx=abcdef0123456789
strict_mode_vouts=0|2|3
```
#### Success
......@@ -32,11 +34,19 @@ Status code 200 with JSON response:
#### Failure
Status code 400 with JSON response:
```json
{
"status": "error",
"error": "<error message>"
}
```
or status code 200 with JSON response:
```json
{
"status": "error",
"error": {
"message": "<error message>",
"code": "<error code>"
"message": [<vout>],
"code": "VIOLATION_STRICT_MODE_VOUTS"
}
}
```
......@@ -29,6 +29,7 @@ If step A and step B have the same **hop** value, then they MAY HAVE different *
The transaction MUST HAVE its nLockTime field filled with the height of a block.
The height of the block MUST BE equal to the value of the **nlocktime** field of the ScriptStep object.
* **strict_mode_vouts** (optional) - `int[]` - An array of outpoints indices. A strict verification is enforced on these outpoints before the transaction is pushed. Strict mode checks that addresses associated to these outputs aren't reused. If verifications fail, the scheduled push is aborted and an error is returned.
### Examples
......@@ -45,7 +46,8 @@ Request Body (JSON-encoded)
"script": [{
"hop": 0,
"nlocktime": 549817,
"tx": "<tx0_raw_hex>"
"tx": "<tx0_raw_hex>",
"strict_mode_vouts": [0,1]
}, {
"hop": 1,
"nlocktime": 549818,
......@@ -122,3 +124,18 @@ Status code 400 with JSON response:
"error": "<error message>"
}
```
or status code 200 with JSON response:
```json
{
"status": "error",
"error": {
"message": [{
"txid": "<txid>",
"hop": <hop>,
"vouts": [<vout>]
}, ...],
"code": "VIOLATION_STRICT_MODE_VOUTS"
}
}
```
......@@ -10,15 +10,15 @@
COMPOSE_CONVERT_WINDOWS_PATHS=1
DOJO_VERSION_TAG=1.6.0
DOJO_VERSION_TAG=1.7.0
DOJO_DB_VERSION_TAG=1.2.0
DOJO_BITCOIND_VERSION_TAG=1.6.0
DOJO_NODEJS_VERSION_TAG=1.6.0
DOJO_NODEJS_VERSION_TAG=1.7.0
DOJO_NGINX_VERSION_TAG=1.5.0
DOJO_TOR_VERSION_TAG=1.4.0
DOJO_EXPLORER_VERSION_TAG=1.3.0
DOJO_INDEXER_VERSION_TAG=1.1.0
DOJO_WHIRLPOOL_VERSION_TAG=1.0.0
DOJO_WHIRLPOOL_VERSION_TAG=1.1.0
#########################################
......
......@@ -6,6 +6,10 @@
# Value: on | off
WHIRLPOOL_INSTALL=on
# Resynchronize mix counters on startup (startup will be slower)
# Value: on | off
WHIRLPOOL_RESYNC=off
#
# EXPERT SETTINGS
......
......@@ -2,7 +2,7 @@ FROM rust:1.42.0-slim-buster
ENV INDEXER_HOME /home/indexer
ENV INDEXER_VERSION 0.3.0
ENV INDEXER_URL https://github.com/Samourai-Wallet/addrindexrs.git
ENV INDEXER_URL https://code.samourai.io/dojo/addrindexrs.git
RUN apt-get update && \
apt-get install -y clang cmake git && \
......
......@@ -2,11 +2,27 @@
* keys/index-example.js
* Copyright (c) 2016-2018, Samourai Wallet (CC BY-NC-ND 4.0 License).
*/
const fs = require('fs')
// Retrieve active bitcoin network from conf files
const bitcoinNetwork = (process.env.COMMON_BTC_NETWORK == 'testnet')
? 'testnet'
: 'bitcoin'
// Retrieve explorer config from conf files
let explorerActive = 'oxt'
let explorerUrl = 'https://oxt.me'
let explorerPassword = ''
if (process.env.EXPLORER_INSTALL == 'on') {
try {
explorerUrl = fs.readFileSync('/var/lib/tor/hsv3explorer/hostname', 'utf8').replace('\n', '')
explorerPassword = process.env.EXPLORER_KEY
explorerActive = 'btc_rpc_explorer'
} catch(e) {}
}
/**
* Desired structure of /keys/index.js, which is ignored in the repository.
*/
......@@ -180,6 +196,18 @@ module.exports = {
// Esplora (testnet)
esplora: process.env.NODE_URL_ESPLORA_API,
},
/*
* Explorer recommended by this Dojo
*/
explorer: {
// Active explorer
// Values: oxt | btc_rpc_explorer
active: explorerActive,
// URI of the explorer
uri: explorerUrl,
// Password (value required for btc_rpc_explorer)
password: explorerPassword
},
/*
* Max number of transactions per address
* accepted during fast scan
......
......@@ -51,10 +51,10 @@ RUN set -ex && \
# Install whirlpool-cli
ENV WHIRLPOOL_URL https://code.samourai.io/whirlpool/whirlpool-client-cli/uploads
ENV WHIRLPOOL_VERSION 0.10.6
ENV WHIRLPOOL_VERSION_HASH a05b443bf9d266702327c99fd8bad5da
ENV WHIRLPOOL_VERSION 0.10.8
ENV WHIRLPOOL_VERSION_HASH 7998ea5a9bb180451616809bc346b9ac
ENV WHIRLPOOL_JAR "whirlpool-client-cli-$WHIRLPOOL_VERSION-run.jar"
ENV WHIRLPOOL_SHA256 eb07ef5637c2bb52b1be57b62941120a689b0c02600c38dbda3b8dd701d03cc8
ENV WHIRLPOOL_SHA256 62e17b6020d0821a98e99ebb773b46191770ec186ceaa3e616a428f5cafe9f49
RUN set -ex && \
cd "$WHIRLPOOL_DIR" && \
......@@ -72,7 +72,7 @@ RUN chown whirlpool:whirlpool /restart.sh && \
chmod u+x /restart.sh && \
chmod g+x /restart.sh
# Expose HTTP API port
# Expose HTTP API port
EXPOSE 8898
# Switch to user whirlpool
......
......@@ -22,6 +22,10 @@ else
whirlpool_options+=(--cli.dojo.url="http://172.30.1.3:80/v2/")
fi
if [ "$WHIRLPOOL_RESYNC" == "on" ]; then
whirlpool_options+=(--resync)
fi
if [ "$WHIRLPOOL_DEBUG" == "on" ]; then
whirlpool_options+=(--debug)
fi
......
......@@ -174,6 +174,18 @@ module.exports = {
// OXT
oxt: 'https://api.oxt.me'
},
/*
* Explorer recommended by this Dojo
*/
explorer: {
// Active explorer
// Values: oxt | btc_rpc_explorer
active: 'oxt',
// URI of the explorer
uri: 'https://oxt.me',
// Password (value required for btc_rpc_explorer)
password: '<password>'
},
/*
* Max number of transactions per address
* accepted during fast scan
......@@ -295,6 +307,11 @@ module.exports = {
socks5Proxy: null,
esplora: 'https://blockstream.info/testnet'
},
explorer: {
active: 'none',
uri: '',
password: '<password>'
},
addrFilterThreshold: 1000,
addrDerivationPool: {
minNbChildren: 1,
......
......@@ -23,6 +23,7 @@ class AuthorizationsManager {
constructor() {
try {
// Constants
this.JWT_ALGO = 'HS256'
this.ISS = 'Samourai Wallet backend'
this.TOKEN_TYPE_ACCESS = 'access-token'
this.TOKEN_TYPE_REFRESH = 'refresh-token'
......@@ -210,7 +211,10 @@ class AuthorizationsManager {
return jwt.sign(
claims,
this._secret,
{expiresIn: this.accessTokenExpires}
{
expiresIn: this.accessTokenExpires,
algorithm: this.JWT_ALGO
}
)
}
......@@ -239,7 +243,11 @@ class AuthorizationsManager {
* @returns {Object} payload of the json web token
*/
_verifyAccessToken(token) {
const payload = jwt.verify(token, this._secret, {})
const payload = jwt.verify(
token,
this._secret,
{algorithms: [this.JWT_ALGO]}
)
if (payload['type'] != this.TOKEN_TYPE_ACCESS)
throw errors.auth.INVALID_JWT
......@@ -263,7 +271,10 @@ class AuthorizationsManager {
return jwt.sign(
claims,
this._secret,
{expiresIn: this.refreshTokenExpires}
{
expiresIn: this.refreshTokenExpires,
algorithm: this.JWT_ALGO
}
)
}
......@@ -292,7 +303,11 @@ class AuthorizationsManager {
* @returns {Object} payload of the json web token
*/
_verifyRefreshToken(token) {
const payload = jwt.verify(token, this._secret, {})
const payload = jwt.verify(
token,
this._secret,
{algorithms: [this.JWT_ALGO]}
)
if (payload['type'] != this.TOKEN_TYPE_REFRESH)
throw errors.auth.INVALID_JWT
......
......@@ -75,6 +75,7 @@ module.exports = {
pushtx: {
NLOCK_MISMATCH: 'nLockTime in script does not match nLockTime in transaction',
SCHEDULED_TOO_FAR: 'nLockTime is set to far in the future',
SCHEDULED_BAD_ORDER: 'Order of hop and nLockTime values must be consistent'
SCHEDULED_BAD_ORDER: 'Order of hop and nLockTime values must be consistent',
VIOLATION_STRICT_MODE_VOUTS: 'VIOLATION_STRICT_MODE_VOUTS'
}
}
{
"name": "samourai-dojo",
"version": "1.6.0",
"version": "1.7.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
......
{
"name": "samourai-dojo",
"version": "1.6.0",
"version": "1.7.0",
"description": "Backend server for Samourai Wallet",
"main": "accounts/index.js",
"scripts": {
......@@ -8,11 +8,11 @@
},
"repository": {
"type": "git",
"url": "git+ssh://git@github.com:Samourai-Wallet/samourai-dojo.git"
"url": "git+ssh://git@code.samourai.io:dojo/samourai-dojo.git"
},
"author": "Katana Cryptographic Ltd.",
"license": "AGPL-3.0-only",
"homepage": "https://github.com/Samourai-Wallet/samourai-dojo",
"homepage": "https://code.samourai.io/dojo/samourai-dojo",
"dependencies": {
"async-sema": "2.1.2",
"bip39": "2.4.0",
......
......@@ -8,11 +8,20 @@ const bitcoin = require('bitcoinjs-lib')
const zmq = require('zeromq')
const Logger = require('../lib/logger')
const errors = require('../lib/errors')
const db = require('../lib/db/mysql-db-wrapper')
const RpcClient = require('../lib/bitcoind-rpc/rpc-client')
const network = require('../lib/bitcoin/network')
const activeNet = network.network
const keys = require('../keys')[network.key]
const status = require('./status')
let Sources
if (network.key == 'bitcoin') {
Sources = require('../lib/remote-importer/sources-mainnet')
} else {
Sources = require('../lib/remote-importer/sources-testnet')
}
/**
* A singleton providing a wrapper
......@@ -25,6 +34,7 @@ class PushTxProcessor {
*/
constructor() {
this.notifSock = null
this.sources = new Sources()
// Initialize the rpc client
this.rpcClient = new RpcClient()
}
......@@ -38,6 +48,45 @@ class PushTxProcessor {
this.notifSock.bindSync(config.uriSocket)
}
/**
* Enforce a strict verification mode on a list of outputs
* @param {string} rawtx - raw bitcoin transaction in hex format
* @param {array} vouts - output indices (integer)
* @returns {array} returns the indices of the faulty outputs
*/
async enforceStrictModeVouts(rawtx, vouts) {
const faultyOutputs = []
const addrMap = {}
let tx
try {
tx = bitcoin.Transaction.fromHex(rawtx)
} catch(e) {
throw errors.tx.PARSE
}
// Check in db if addresses are known and have been used
for (let vout of vouts) {
if (vout >= tx.outs.length)
throw errors.txout.VOUT
const output = tx.outs[vout]
const address = bitcoin.address.fromOutputScript(output.script, activeNet)
const nbTxs = await db.getAddressNbTransactions(address)
if (nbTxs == null || nbTxs > 0)
faultyOutputs.push(vout)
else
addrMap[address] = vout
}
// Checks with indexer if addresses are known and have been used
if (Object.keys(addrMap).length > 0) {
if (keys.indexer.active != 'local_bitcoind') {
const results = await this.sources.getAddresses(Object.keys(addrMap))
for (let r of results)
if (r.ntx > 0)
faultyOutputs.push(addrMap[r.address])
}
}
return faultyOutputs
}
/**
* Push transactions to the Bitcoin network
* @param {string} rawtx - raw bitcoin transaction in hex format
......
......@@ -152,6 +152,27 @@ class PushTxRestApi {
if (!validator.isHexadecimal(query.tx))
return this._traceError(res, errors.body.INVDATA)
if (query.strict_mode_vouts) {
try {
const vouts = query.strict_mode_vouts.split('|').map(v => parseInt(v, 10))
if (vouts.some(isNaN))
throw errors.txout.VOUT
if (vouts.length > 0) {
let faults = await pushTxProcessor.enforceStrictModeVouts(query.tx, vouts)
if (faults.length > 0) {
return this._traceError(res, {
'message': JSON.stringify({
'message': faults,
'code': errors.pushtx.VIOLATION_STRICT_MODE_VOUTS
})
}, 200)
}
}
} catch(e) {
return this._traceError(res, e)
}
}
try {
const txid = await pushTxProcessor.pushTx(query.tx)
HttpServer.sendOkData(res, txid)
......@@ -177,7 +198,13 @@ class PushTxRestApi {
await this.scheduler.schedule(req.body.script)
HttpServer.sendOk(res)
} catch(e) {
this._traceError(res, e)
// Returns code 200 if VIOLATION_STRICT_MODE_VOUTS
if (e.message && e.message.code && e.message.code == errors.pushtx.VIOLATION_STRICT_MODE_VOUTS) {
e.message = JSON.stringify(e.message)
this._traceError(res, e, 200)
} else {
this._traceError(res, e)
}
}
}
......@@ -185,10 +212,13 @@ class PushTxRestApi {
* Trace an error during push
* @param {object} res - http response object
* @param {object} err - error object
* @param {int} errorCode - error code (optional)
*/
_traceError(res, err) {
_traceError(res, err, errorCode) {
let ret = null
errorCode = errorCode == null ? 400 : errorCode
try {
if (err.message) {
let msg = {}
......@@ -198,10 +228,7 @@ class PushTxRestApi {
if (msg.code && msg.message) {
Logger.error(null, 'PushTx : Error ' + msg.code + ': ' + msg.message)
ret = {
message: msg.message,
code: msg.code
}
ret = msg
} else {
Logger.error(err.message, 'PushTx : ')
ret = err.message
......@@ -214,7 +241,7 @@ class PushTxRestApi {
Logger.error(e, 'PushTx : ')
ret = e
} finally {
HttpServer.sendError(res, ret)
HttpServer.sendError(res, ret, errorCode)
}
}
......
......@@ -57,6 +57,7 @@ class TransactionsScheduler {
// Iterate over the transactions for a few validations
let lastHopProcessed = -1
let lastLockTimeProcessed = -1
const faults = []
for (let entry of script) {
// Compute delta height (entry.nlocktime - nltTx0)
......@@ -77,6 +78,30 @@ class TransactionsScheduler {
if (entry.nlocktime < lastLockTimeProcessed)
throw errors.pushtx.SCHEDULED_BAD_ORDER
}
// Enforce strcit_mode_vouts if required
const vouts = entry.strict_mode_vouts
if (vouts) {
try {
if (vouts.some(isNaN))
throw errors.txout.VOUT
if (vouts.length > 0) {
let faultsTx = await pushTxProcessor.enforceStrictModeVouts(entry.tx, vouts)
if (faultsTx.length > 0) {
const txid = bitcoin.Transaction.fromHex(entry.tx).getId()
for (let vout of faultsTx) {
faults.push({
"txid": txid,
"hop": entry.hop,
"vouts": vout
})
}
}
}
} catch(e) {
throw e
}
}
// Prepare verification of next hop
lastHopProcessed = entry.hop
lastLockTimeProcessed = entry.nlocktime
// Update scheduled height if needed
......@@ -84,6 +109,16 @@ class TransactionsScheduler {
entry.nlocktime = baseHeight + entry.delta
}
// Return if strict_mode_vout has detected errors
if (faults.length > 0) {
throw {
'message': {
'message': faults,
'code': errors.pushtx.VIOLATION_STRICT_MODE_VOUTS
}
}
}
let parentTxid = null
let parentNlocktime = baseHeight
......