Browse Source

Merge branch 'feat_dojo_pushtx_checks' into 'develop'

add new optional strict_mode_vouts to pushtx endpoints

See merge request dojo/samourai-dojo!147
use-env-var-docker
kenshin-samourai 5 years ago
parent
commit
f730d74735
  1. 13
      doc/POST_pushtx.md
  2. 2
      docker/my-dojo/.env
  3. 3
      lib/errors.js
  4. 2
      package-lock.json
  5. 2
      package.json
  6. 49
      pushtx/pushtx-processor.js
  7. 26
      pushtx/pushtx-rest-api.js
  8. 35
      pushtx/transactions-scheduler.js

13
doc/POST_pushtx.md

@ -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,18 @@ Status code 200 with JSON response:
#### Failure
Status code 400 with JSON response:
```json
{
"status": "error",
"error": "<error message>"
}
```
or
```json
{
"status": "error",
"error": {
"message": "<error message>",
"code": "<error code>"
"message": [vouts],
"code": "VIOLATION_STRICT_MODE_VOUTS"
}
}
```

2
docker/my-dojo/.env

@ -13,7 +13,7 @@ COMPOSE_CONVERT_WINDOWS_PATHS=1
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

3
lib/errors.js

@ -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'
}
}

2
package-lock.json

@ -1,6 +1,6 @@
{
"name": "samourai-dojo",
"version": "1.6.0",
"version": "1.7.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

2
package.json

@ -1,6 +1,6 @@
{
"name": "samourai-dojo",
"version": "1.6.0",
"version": "1.7.0",
"description": "Backend server for Samourai Wallet",
"main": "accounts/index.js",
"scripts": {

49
pushtx/pushtx-processor.js

@ -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

26
pushtx/pushtx-rest-api.js

@ -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
})
})
}
}
} catch(e) {
return this._traceError(res, e)
}
}
try {
const txid = await pushTxProcessor.pushTx(query.tx)
HttpServer.sendOkData(res, txid)
@ -198,10 +219,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

35
pushtx/transactions-scheduler.js

@ -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': JSON.stringify({
'message': faults,
'code': errors.pushtx.VIOLATION_STRICT_MODE_VOUTS
})
}
}
let parentTxid = null
let parentNlocktime = baseHeight

Loading…
Cancel
Save