You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
250 lines
6.4 KiB
250 lines
6.4 KiB
/*!
|
|
* pushtx/pushtx-rest-api.js
|
|
* Copyright © 2019 – Katana Cryptographic Ltd. All Rights Reserved.
|
|
*/
|
|
'use strict'
|
|
|
|
const qs = require('querystring')
|
|
const validator = require('validator')
|
|
const bodyParser = require('body-parser')
|
|
const Logger = require('../lib/logger')
|
|
const errors = require('../lib/errors')
|
|
const authMgr = require('../lib/auth/authorizations-manager')
|
|
const HttpServer = require('../lib/http-server/http-server')
|
|
const network = require('../lib/bitcoin/network')
|
|
const keys = require('../keys')[network.key]
|
|
const status = require('./status')
|
|
const pushTxProcessor = require('./pushtx-processor')
|
|
const TransactionsScheduler = require('./transactions-scheduler')
|
|
|
|
|
|
/**
|
|
* PushTx API endpoints
|
|
*/
|
|
class PushTxRestApi {
|
|
|
|
/**
|
|
* Constructor
|
|
* @param {pushtx.HttpServer} httpServer - HTTP server
|
|
*/
|
|
constructor(httpServer) {
|
|
this.httpServer = httpServer
|
|
this.scheduler = new TransactionsScheduler()
|
|
|
|
// Establish routes
|
|
const jsonParser = bodyParser.json()
|
|
|
|
// Establish routes. Proxy server strips /pushtx
|
|
this.httpServer.app.post(
|
|
'/schedule',
|
|
jsonParser,
|
|
authMgr.checkAuthentication.bind(authMgr),
|
|
this.postScheduleTxs.bind(this),
|
|
HttpServer.sendAuthError
|
|
)
|
|
|
|
this.httpServer.app.post(
|
|
'/',
|
|
authMgr.checkAuthentication.bind(authMgr),
|
|
this.postPushTx.bind(this),
|
|
HttpServer.sendAuthError
|
|
)
|
|
|
|
this.httpServer.app.get(
|
|
'/',
|
|
authMgr.checkAuthentication.bind(authMgr),
|
|
this.getPushTx.bind(this),
|
|
HttpServer.sendAuthError
|
|
)
|
|
|
|
this.httpServer.app.get(
|
|
`/${keys.prefixes.statusPushtx}/`,
|
|
authMgr.checkHasAdminProfile.bind(authMgr),
|
|
this.getStatus.bind(this),
|
|
HttpServer.sendAuthError
|
|
)
|
|
|
|
this.httpServer.app.get(
|
|
`/${keys.prefixes.statusPushtx}/schedule`,
|
|
authMgr.checkHasAdminProfile.bind(authMgr),
|
|
this.getStatusSchedule.bind(this),
|
|
HttpServer.sendAuthError
|
|
)
|
|
|
|
// Handle unknown paths, returning a help message
|
|
this.httpServer.app.get(
|
|
'/*',
|
|
authMgr.checkAuthentication.bind(authMgr),
|
|
this.getHelp.bind(this),
|
|
HttpServer.sendAuthError
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Handle Help GET request
|
|
* @param {object} req - http request object
|
|
* @param {object} res - http response object
|
|
*/
|
|
getHelp(req, res) {
|
|
const ret = {endpoints: ['/pushtx', '/pushtx/schedule']}
|
|
HttpServer.sendError(res, ret, 404)
|
|
}
|
|
|
|
/**
|
|
* Handle Status GET request
|
|
* @param {object} req - http request object
|
|
* @param {object} res - http response object
|
|
*/
|
|
async getStatus(req, res) {
|
|
try {
|
|
const currStatus = await status.getCurrent()
|
|
HttpServer.sendOkData(res, currStatus)
|
|
} catch(e) {
|
|
this._traceError(res, e)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle status/schedule GET request
|
|
* @param {object} req - http request object
|
|
* @param {object} res - http response object
|
|
*/
|
|
async getStatusSchedule(req, res) {
|
|
try {
|
|
const ret = await status.getScheduledTransactions()
|
|
HttpServer.sendOkData(res, ret)
|
|
} catch(e) {
|
|
this._traceError(res, e)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle pushTx GET request
|
|
* @param {object} req - http request object
|
|
* @param {object} res - http response object
|
|
*/
|
|
getPushTx(req, res) {
|
|
const ret = errors.get.DISALLOWED
|
|
HttpServer.sendError(res, ret, 405)
|
|
}
|
|
|
|
/**
|
|
* Handle POST requests
|
|
* Push transactions to the Bitcoin network
|
|
* @param {object} req - http request object
|
|
* @param {object} res - http response object
|
|
*/
|
|
postPushTx(req, res) {
|
|
// Accumulate POST data
|
|
const chunks = []
|
|
|
|
req.on('data', chunk => {
|
|
chunks.push(chunk)
|
|
})
|
|
|
|
req.on('end', async () => {
|
|
const body = chunks.join('')
|
|
const query = qs.parse(body)
|
|
|
|
if (!query.tx)
|
|
return this._traceError(res, errors.body.NOTX)
|
|
|
|
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)
|
|
} catch(e) {
|
|
this._traceError(res, e)
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Schedule a list of transactions
|
|
* for delayed pushes
|
|
*/
|
|
async postScheduleTxs(req, res) {
|
|
// Check request arguments
|
|
if (!req.body)
|
|
return this._traceError(res, errors.body.NODATA)
|
|
|
|
if (!req.body.script)
|
|
return this._traceError(res, errors.body.NOSCRIPT)
|
|
|
|
try {
|
|
await this.scheduler.schedule(req.body.script)
|
|
HttpServer.sendOk(res)
|
|
} catch(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)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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, errorCode) {
|
|
let ret = null
|
|
|
|
errorCode = errorCode == null ? 400 : errorCode
|
|
|
|
try {
|
|
if (err.message) {
|
|
let msg = {}
|
|
try {
|
|
msg = JSON.parse(err.message)
|
|
} catch(e) {}
|
|
|
|
if (msg.code && msg.message) {
|
|
Logger.error(null, 'PushTx : Error ' + msg.code + ': ' + msg.message)
|
|
ret = msg
|
|
} else {
|
|
Logger.error(err.message, 'PushTx : ')
|
|
ret = err.message
|
|
}
|
|
} else {
|
|
Logger.error(err, 'PushTx : ')
|
|
ret = err
|
|
}
|
|
} catch (e) {
|
|
Logger.error(e, 'PushTx : ')
|
|
ret = e
|
|
} finally {
|
|
HttpServer.sendError(res, ret, errorCode)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
module.exports = PushTxRestApi
|
|
|