diff --git a/browser/server.js b/browser/server.js index 616c6ccc..e0e680c9 100644 --- a/browser/server.js +++ b/browser/server.js @@ -22,28 +22,28 @@ proxy.on('error', function(err) { server = new HTTPBase(); -server.get('/favicon.ico', function(req, res, send, next) { - send(404, '', 'txt'); +server.get('/favicon.ico', function(req, res) { + res.send(404, '', 'txt'); }); -server.get('/', function(req, res, send, next) { - send(200, index, 'html'); +server.get('/', function(req, res) { + res.send(200, index, 'html'); }); -server.get('/index.js', function(req, res, send, next) { - send(200, indexjs, 'js'); +server.get('/index.js', function(req, res) { + res.send(200, indexjs, 'js'); }); -server.get('/bcoin.js', function(req, res, send, next) { - send(200, bcoin, 'js'); +server.get('/bcoin.js', function(req, res) { + res.send(200, bcoin, 'js'); }); -server.get('/bcoin-master.js', function(req, res, send, next) { - send(200, master, 'js'); +server.get('/bcoin-master.js', function(req, res) { + res.send(200, master, 'js'); }); -server.get('/bcoin-worker.js', function(req, res, send, next) { - send(200, worker, 'js'); +server.get('/bcoin-worker.js', function(req, res) { + res.send(200, worker, 'js'); }); server.on('error', function(err) { diff --git a/lib/http/base.js b/lib/http/base.js index 323a357d..6fce6847 100644 --- a/lib/http/base.js +++ b/lib/http/base.js @@ -11,6 +11,7 @@ var assert = require('assert'); var AsyncObject = require('../utils/async'); var util = require('../utils/util'); var URL = require('url'); +var co = require('../utils/co'); /** * HTTPBase @@ -35,6 +36,9 @@ function HTTPBase(options) { this.routes = new Routes(); this.stack = []; + this.keyLimit = 100; + this.bodyLimit = 20 << 20; + this.server = options.key ? require('https').createServer(options) : require('http').createServer(); @@ -89,85 +93,194 @@ HTTPBase.prototype._init = function _init() { HTTPBase.prototype._initRouter = function _initRouter() { var self = this; - - this.server.on('request', function(req, res) { - var i, j, routes, route, match, item; - - function send(code, msg, type) { - sendResponse(res, code, msg, type); - } - - function done(err) { - if (err) { - send(err.statusCode || 400, { error: err.message + '' }); - - try { - req.destroy(); - req.socket.destroy(); - } catch (e) { - ; - } - - self.emit('error', err); - } - } - + this.server.on('request', co(function* (req, res) { try { - parsePath(req, 100); + yield self.handleRequest(req, res); } catch (e) { - return done(e); + if (!res.sent) + res.error(e); + + self.emit('error', e); + } + })); +}; + +/** + * Handle a request. + * @private + * @param {ServerRequest} req + * @param {ServerResponse} res + * @returns {Promise} + */ + +HTTPBase.prototype.handleRequest = co(function* handleRequest(req, res) { + var i, routes, route, params; + + initRequest(req, res, this.keyLimit); + + this.emit('request', req, res); + + req.body = yield this.parseBody(req); + + routes = this.routes.getHandlers(req.method); + + if (!routes) + throw new Error('No routes found for method: ' + req.method); + + for (i = 0; i < routes.length; i++) { + route = routes[i]; + params = route.match(req.pathname); + + if (!params) + continue; + + req.params = params; + + if (yield this.handleStack(req, res)) + return; + + if (yield route.call(req, res)) + return; + } + + throw new Error('No routes found for path: ' + req.pathname); +}); + +/** + * Parse request body. + * @private + * @param {ServerRequest} req + * @returns {Promise} + */ + +HTTPBase.prototype.parseBody = co(function* parseBody(req) { + var body = Object.create(null); + var data; + + if (req.method === 'GET') + return body; + + data = yield this.readBody(req, 'utf8'); + + if (!data) + return body; + + switch (req.contentType) { + case 'json': + body = JSON.parse(data); + break; + case 'form': + body = parsePairs(data, this.keyLimit); + break; + default: + break; + } + + return body; +}); + +/** + * Handle middleware stack. + * @private + * @param {HTTPRequest} req + * @param {HTTPResponse} res + * @returns {Promise} + */ + +HTTPBase.prototype.handleStack = co(function* handleStack(req, res) { + var i, route; + + for (i = 0; i < this.stack.length; i++) { + route = this.stack[i]; + + if (!route.hasPrefix(req.pathname)) + continue; + + if (yield route.call(req, res)) + return true; + } + + return false; +}); + +/** + * Read and buffer request body. + * @param {ServerRequest} req + * @param {String} enc + * @returns {Promise} + */ + +HTTPBase.prototype.readBody = function readBody(req, enc) { + var self = this; + return new Promise(function(resolve, reject) { + return self._readBody(req, enc, resolve, reject); + }); +}; + +/** + * Read and buffer request body. + * @private + * @param {ServerRequest} req + * @param {String} enc + * @param {Function} resolve + * @param {Function} reject + */ + +HTTPBase.prototype._readBody = function _readBody(req, enc, resolve, reject) { + var self = this; + var StringDecoder = require('string_decoder').StringDecoder; + var decode = new StringDecoder(enc); + var hasData = false; + var total = 0; + var body = ''; + var timer; + + timer = setTimeout(function() { + timer = null; + cleanup(); + reject(new Error('Request body timed out.')); + }, 10 * 1000); + + function cleanup() { + req.removeListener('data', onData); + req.removeListener('error', onError); + req.removeListener('end', onEnd); + + if (timer != null) + clearTimeout(timer); + } + + function onData(data) { + total += data.length; + hasData = true; + + if (total > self.bodyLimit) { + reject(new Error('Request body overflow.')); + return; } - self.emit('request', req, res); + body += decode.write(data); + } - i = 0; - routes = self.routes.getHandlers(req.method); + function onError(err) { + cleanup(); + reject(err); + } - if (!routes) - return done(new Error('No routes found.')); + function onEnd() { + cleanup(); - parseBody(req, function(err) { - if (err) - return done(err); + if (hasData) { + resolve(body); + return; + } - (function next(err) { - if (err) - return done(err); + resolve(null); + } - if (i === routes.length) - return done(new Error('Route not found.')); - - route = routes[i++]; - match = route.match(req.pathname); - - if (!match) - return next(); - - req.params = Object.create(null); - - for (j = 0; j < match.length; j++) { - item = match[j]; - if (route.map[j]) - req.params[route.map[j]] = item; - req.params[j] = item; - } - - self.handleStack(req, res, send, function(err) { - if (err) - return done(err); - - // Avoid stack overflows - util.nextTick(function() { - try { - route.call(req, res, send, next); - } catch (e) { - done(e); - } - }); - }); - })(); - }); - }); + req.on('data', onData); + req.on('error', onError); + req.on('end', onEnd); }; /** @@ -219,7 +332,7 @@ HTTPBase.prototype._open = function open() { * @returns {Promise} */ -HTTPBase.prototype._close = function close(callback) { +HTTPBase.prototype._close = function close() { var self = this; return new Promise(function(resolve, reject) { @@ -230,104 +343,72 @@ HTTPBase.prototype._close = function close(callback) { } self.server.close(function(err) { - if (err) - return reject(err); + if (err) { + reject(err); + return; + } resolve(); }); }); }; -/** - * Handle middleware stack. - * @private - * @param {HTTPRequest} req - * @param {HTTPResponse} res - * @param {Function} send - * @returns {Promise} - */ - -HTTPBase.prototype.handleStack = function handleStack(req, res, send, callback) { - var self = this; - var i = 0; - var route; - - (function next(err) { - if (err) - return callback(err); - - if (i === self.stack.length) - return callback(); - - route = self.stack[i++]; - - util.nextTick(function() { - if (!route.hasPrefix(req.pathname)) - return next(); - - try { - route.call(req, res, send, next); - } catch (e) { - next(e); - } - }); - })(); -}; - /** * Add a middleware to the stack. * @param {String?} path - * @param {RouteCallback} callback + * @param {Function} handler + * @param {Object?} ctx */ -HTTPBase.prototype.use = function use(path, callback, ctx) { - var i; - - if (!callback) { - callback = path; +HTTPBase.prototype.use = function use(path, handler, ctx) { + if (!handler) { + handler = path; path = null; } - - this.stack.push(new Route(ctx, path, callback)); + this.stack.push(new Route(ctx, path, handler)); }; /** * Add a GET route. - * @param {String?} path - * @param {RouteCallback} callback + * @param {String} path + * @param {Function} handler + * @param {Object?} ctx */ -HTTPBase.prototype.get = function get(path, callback, ctx) { - this.routes.get.push(new Route(ctx, path, callback)); +HTTPBase.prototype.get = function get(path, handler, ctx) { + this.routes.get.push(new Route(ctx, path, handler)); }; /** * Add a POST route. - * @param {String?} path - * @param {RouteCallback} callback + * @param {String} path + * @param {Function} handler + * @param {Object?} ctx */ -HTTPBase.prototype.post = function post(path, callback, ctx) { - this.routes.post.push(new Route(ctx, path, callback)); +HTTPBase.prototype.post = function post(path, handler, ctx) { + this.routes.post.push(new Route(ctx, path, handler)); }; /** * Add a PUT route. - * @param {String?} path - * @param {RouteCallback} callback + * @param {String} path + * @param {Function} handler + * @param {Object?} ctx */ -HTTPBase.prototype.put = function put(path, callback, ctx) { - this.routes.put.push(new Route(ctx, path, callback)); +HTTPBase.prototype.put = function put(path, handler, ctx) { + this.routes.put.push(new Route(ctx, path, handler)); }; /** * Add a DELETE route. - * @param {String?} path - * @param {RouteCallback} callback + * @param {String} path + * @param {Function} handler + * @param {Object?} ctx */ -HTTPBase.prototype.del = function del(path, callback, ctx) { - this.routes.del.push(new Route(ctx, path, callback)); +HTTPBase.prototype.del = function del(path, handler, ctx) { + this.routes.del.push(new Route(ctx, path, handler)); }; /** @@ -342,7 +423,7 @@ HTTPBase.prototype.address = function address() { /** * Listen on port and host. * @param {Number} port - * @param {String?} host + * @param {String} host * @returns {Promise} */ @@ -369,13 +450,13 @@ HTTPBase.prototype.listen = function listen(port, host) { * @constructor */ -function Route(ctx, path, callback) { +function Route(ctx, path, handler) { if (!(this instanceof Route)) - return new Route(ctx, path, callback); + return new Route(ctx, path, handler); this.ctx = null; this.path = null; - this.callback = null; + this.handler = null; this.regex = /^/; this.map = []; @@ -396,8 +477,8 @@ function Route(ctx, path, callback) { } } - assert(typeof callback === 'function'); - this.callback = callback; + assert(typeof handler === 'function'); + this.handler = handler; } Route.prototype.compile = function compile() { @@ -426,7 +507,7 @@ Route.prototype.compile = function compile() { }; Route.prototype.match = function match(pathname) { - var match; + var i, match, item, params, key; this.compile(); @@ -437,7 +518,19 @@ Route.prototype.match = function match(pathname) { if (!match) return; - return match.slice(1); + params = Object.create(null); + + for (i = 1; i < match.length; i++) { + item = match[i]; + key = this.map[i - 1]; + + if (key) + params[key] = item; + + params[i] = item; + } + + return params; }; Route.prototype.hasPrefix = function hasPrefix(pathname) { @@ -447,9 +540,10 @@ Route.prototype.hasPrefix = function hasPrefix(pathname) { return pathname.indexOf(this.path) === 0; }; -Route.prototype.call = function call(req, res, send, next) { - this.callback.call(this.ctx, req, res, send, next); -}; +Route.prototype.call = co(function* call(req, res) { + yield this.handler.call(this.ctx, req, res); + return res.sent; +}); /** * Routes @@ -490,9 +584,60 @@ Routes.prototype.getHandlers = function getHandlers(method) { * Helpers */ +function nop() {} + +function initRequest(req, res, limit) { + req.on('error', nop); + + assert(req.contentType == null); + assert(req.pathname == null); + assert(req.path == null); + assert(req.query == null); + assert(req.params == null); + assert(req.body == null); + + req.contentType = parseType(req.headers['content-type']); + req.pathname = ''; + req.path = []; + req.query = Object.create(null); + req.params = Object.create(null); + req.body = Object.create(null); + + assert(req.options == null); + assert(req.username == null); + assert(req.password == null); + assert(req.admin == null); + assert(req.wallet == null); + + req.options = Object.create(null); + req.username = null; + req.password = null; + req.admin = false; + req.wallet = null; + + assert(res.sent == null); + assert(res.send == null); + assert(res.error == null); + + res.sent = false; + res.send = makeSend(res); + res.error = makeSendError(req, res); + + parsePath(req, limit); +} + +function makeSend(res) { + return function send(code, msg, type) { + return sendResponse(res, code, msg, type); + }; +} + function sendResponse(res, code, msg, type) { var len; + if (res.sent) + return; + assert(typeof code === 'number', 'Code must be a number.'); if (msg == null) @@ -510,6 +655,7 @@ function sendResponse(res, code, msg, type) { res.statusCode = code; res.setHeader('Content-Type', getType(type)); + res.sent = true; if (typeof msg === 'string') { len = Buffer.byteLength(msg, 'utf8'); @@ -537,45 +683,35 @@ function sendResponse(res, code, msg, type) { assert(false, 'Bad object passed to send.'); } -function parseBody(req, callback) { - var StringDecoder = require('string_decoder').StringDecoder; - var decode = new StringDecoder('utf8'); - var total = 0; - var body = ''; +function makeSendError(req, res) { + return function error(err) { + return sendError(req, res, err); + }; +} - req.body = Object.create(null); +function sendError(req, res, err) { + var code, msg; - if (req.method === 'GET') - return callback(); + if (res.sent) + return; - req.on('data', function(data) { - total += data.length; + code = err.statusCode; + msg = err.message; - if (total > 20 * 1024 * 1024) - return callback(new Error('Overflow.')); + if (!code) + code = 400; - body += decode.write(data); - }); + if (typeof msg !== 'string') + msg += ''; - req.on('error', function(err) { - try { - req.destroy(); - req.socket.destroy(); - } catch (e) { - ; - } - callback(err); - }); + res.send(code, { error: msg }); - req.on('end', function() { - try { - if (body) - req.body = JSON.parse(body); - } catch (e) { - return callback(e); - } - callback(); - }); + try { + req.destroy(); + req.socket.destroy(); + } catch (e) { + ; + } } function parsePairs(str, limit) { @@ -672,7 +808,6 @@ function parsePath(req, limit) { req.pathname = pathname; req.path = parts; req.query = query; - req.params = Object.create(null); } function unescape(str) { @@ -703,6 +838,35 @@ function getType(type) { } } +function parseType(type) { + type = type || ''; + type = type.split(';')[0]; + type = type.toLowerCase(); + type = type.trim(); + + switch (type) { + case 'text/x-json': + case 'application/json': + return 'json'; + case 'application/x-www-form-urlencoded': + return 'form'; + case 'text/html': + case 'application/xhtml+xml': + return 'html'; + case 'text/javascript': + case 'application/javascript': + return 'js'; + case 'text/css': + return 'css'; + case 'text/plain': + return 'txt'; + case 'application/octet-stream': + return 'bin'; + default: + return 'bin'; + } +} + /* * Expose */ diff --git a/lib/http/server.js b/lib/http/server.js index 39ed5e89..6b551f2e 100644 --- a/lib/http/server.js +++ b/lib/http/server.js @@ -26,7 +26,7 @@ var HD = require('../hd/hd'); var Script = require('../script/script'); var crypto = require('../crypto/crypto'); var pkg = require('../../package.json'); -var con = co.con; +var cob = co.cob; var RPC; /** @@ -114,7 +114,7 @@ HTTPServer.prototype._init = function _init() { address.address, address.port); }); - this.use(function(req, res, send, next) { + this.use(co(function* (req, res) { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Credentials', 'true'); res.setHeader( @@ -123,7 +123,9 @@ HTTPServer.prototype._init = function _init() { if (req.method === 'OPTIONS') { res.statusCode = 200; - return res.end(); + res.sent = true; + res.end(); + return; } res.setHeader('X-Bcoin-Version', pkg.version); @@ -131,18 +133,16 @@ HTTPServer.prototype._init = function _init() { res.setHeader('X-Bcoin-Network', this.network.type); res.setHeader('X-Bcoin-Height', this.chain.height + ''); res.setHeader('X-Bcoin-Tip', util.revHex(this.chain.tip.hash)); + })); - next(); - }); - - this.use(function(req, res, send, next) { + this.use(co(function* (req, res) { var auth = req.headers['authorization']; var parts; if (!auth) { req.username = null; req.password = null; - return next(); + return; } parts = auth.split(' '); @@ -154,16 +154,14 @@ HTTPServer.prototype._init = function _init() { req.username = parts.shift(); req.password = parts.join(':'); + })); - next(); - }); - - this.use(function(req, res, send, next) { + this.use(co(function* (req, res) { var hash; if (this.options.noAuth) { req.admin = true; - return next(); + return; } hash = hash256(req.password); @@ -171,21 +169,21 @@ HTTPServer.prototype._init = function _init() { // Regular API key gives access to everything. if (crypto.ccmp(hash, this.apiHash)) { req.admin = true; - return next(); + return; } // If they're hitting the wallet services, // they can use the less powerful API key. if (isWalletPath(req)) { if (crypto.ccmp(hash, this.serviceHash)) - return next(); + return; } res.setHeader('WWW-Authenticate', 'Basic realm="node"'); if (req.method === 'POST' && req.pathname === '/') { - send(401, { + res.send(401, { result: null, error: { message: 'Bad auth.', @@ -196,15 +194,15 @@ HTTPServer.prototype._init = function _init() { return; } - send(401, { error: 'Bad API key.' }); - }); + res.send(401, { error: 'Bad API key.' }); + })); - this.use(function(req, res, send, next) { + this.use(co(function* (req, res) { var i, params, options, censored, output, address; if (req.method === 'POST' && req.pathname === '/') { req.options = Object.create(null); - return next(); + return; } params = Object.create(null); @@ -496,30 +494,25 @@ HTTPServer.prototype._init = function _init() { } req.options = options; + })); - next(); - }); - - this.use(con(function* (req, res, send, next) { + this.use(co(function* (req, res) { var options = req.options; var wallet; - if (req.path.length < 2 || req.path[0] !== 'wallet') { - next(); + if (req.path.length < 2 || req.path[0] !== 'wallet') return; - } if (!this.options.walletAuth) { wallet = yield this.walletdb.get(options.id); if (!wallet) { - send(404); + res.send(404); return; } req.wallet = wallet; - next(); return; } @@ -528,24 +521,22 @@ HTTPServer.prototype._init = function _init() { } catch (err) { this.logger.info('Auth failure for %s: %s.', req.options.id, err.message); - send(403, { error: err.message }); + res.send(403, { error: err.message }); return; } if (!wallet) { - send(404); + res.send(404); return; } req.wallet = wallet; this.logger.info('Successful auth for %s.', options.id); - - next(); })); // JSON RPC - this.post('/', con(function* (req, res, send, next) { + this.post('/', co(function* (req, res) { var out = []; var cmds = req.body; var array = true; @@ -630,18 +621,18 @@ HTTPServer.prototype._init = function _init() { } if (!array) { - send(200, out[0]); + res.send(200, out[0]); return; } - send(200, out); + res.send(200, out); })); - this.get('/', function(req, res, send, next) { + this.get('/', co(function* (req, res) { var totalTX = this.mempool ? this.mempool.totalTX : 0; var size = this.mempool ? this.mempool.getSize() : 0; - send(200, { + res.send(200, { version: pkg.version, agent: this.pool.userAgent, network: this.network.type, @@ -669,23 +660,23 @@ HTTPServer.prototype._init = function _init() { }, memory: getMemory() }); - }); + })); // UTXO by address - this.get('/coin/address/:address', con(function* (req, res, send, next) { + this.get('/coin/address/:address', co(function* (req, res) { var coins; enforce(req.options.address, 'Address is required.'); coins = yield this.node.getCoinsByAddress(req.options.address); - send(200, coins.map(function(coin) { + res.send(200, coins.map(function(coin) { return coin.getJSON(this.network); }, this)); })); // UTXO by id - this.get('/coin/:hash/:index', con(function* (req, res, send, next) { + this.get('/coin/:hash/:index', co(function* (req, res) { var coin; enforce(req.options.hash, 'Hash is required.'); @@ -693,67 +684,71 @@ HTTPServer.prototype._init = function _init() { coin = yield this.node.getCoin(req.options.hash, req.options.index); - if (!coin) - return send(404); + if (!coin) { + res.send(404); + return; + } - send(200, coin.getJSON(this.network)); + res.send(200, coin.getJSON(this.network)); })); // Bulk read UTXOs - this.post('/coin/address', con(function* (req, res, send, next) { + this.post('/coin/address', co(function* (req, res) { var coins; enforce(req.options.address, 'Address is required.'); coins = yield this.node.getCoinsByAddress(req.options.address); - send(200, coins.map(function(coin) { + res.send(200, coins.map(function(coin) { return coin.getJSON(this.network); }, this)); })); // TX by hash - this.get('/tx/:hash', con(function* (req, res, send, next) { + this.get('/tx/:hash', co(function* (req, res) { var tx; enforce(req.options.hash, 'Hash is required.'); tx = yield this.node.getMeta(req.options.hash); - if (!tx) - return send(404); + if (!tx) { + res.send(404); + return; + } - send(200, tx.getJSON(this.network)); + res.send(200, tx.getJSON(this.network)); })); // TX by address - this.get('/tx/address/:address', con(function* (req, res, send, next) { + this.get('/tx/address/:address', co(function* (req, res) { var txs; enforce(req.options.address, 'Address is required.'); txs = yield this.node.getMetaByAddress(req.options.address); - send(200, txs.map(function(tx) { + res.send(200, txs.map(function(tx) { return tx.getJSON(this.network); }, this)); })); // Bulk read TXs - this.post('/tx/address', con(function* (req, res, send, next) { + this.post('/tx/address', co(function* (req, res) { var txs; enforce(req.options.address, 'Address is required.'); txs = yield this.node.getMetaByAddress(req.options.address); - send(200, txs.map(function(tx) { + res.send(200, txs.map(function(tx) { return tx.getJSON(this.network); }, this)); })); // Block by hash/height - this.get('/block/:block', con(function* (req, res, send, next) { + this.get('/block/:block', co(function* (req, res) { var hash = req.options.hash || req.options.height; var block, view, height; @@ -761,54 +756,62 @@ HTTPServer.prototype._init = function _init() { block = yield this.chain.db.getBlock(hash); - if (!block) - return send(404); + if (!block) { + res.send(404); + return; + } view = yield this.chain.db.getBlockView(block); - if (!view) - return send(404); + if (!view) { + res.send(404); + return; + } height = yield this.chain.db.getHeight(hash); - send(200, block.getJSON(this.network, view, height)); + res.send(200, block.getJSON(this.network, view, height)); })); // Mempool snapshot - this.get('/mempool', con(function* (req, res, send, next) { + this.get('/mempool', co(function* (req, res) { var txs; - if (!this.mempool) - return send(400, { error: 'No mempool available.' }); + if (!this.mempool) { + res.send(500, { error: 'No mempool available.' }); + return; + } txs = this.mempool.getHistory(); - send(200, txs.map(function(tx) { + res.send(200, txs.map(function(tx) { return tx.getJSON(this.network); }, this)); })); // Broadcast TX - this.post('/broadcast', con(function* (req, res, send, next) { + this.post('/broadcast', co(function* (req, res) { enforce(req.options.tx, 'TX is required.'); yield this.node.sendTX(req.options.tx); - send(200, { success: true }); + res.send(200, { success: true }); })); // Estimate fee - this.get('/fee', function(req, res, send, next) { + this.get('/fee', function(req, res) { var fee; - if (!this.fees) - return send(200, { rate: Amount.btc(this.network.feeRate) }); + if (!this.fees) { + res.send(200, { rate: Amount.btc(this.network.feeRate) }); + return; + } fee = this.fees.estimateFee(req.options.blocks); - send(200, { rate: Amount.btc(fee) }); + res.send(200, { rate: Amount.btc(fee) }); }); // Reset chain - this.post('/reset', con(function* (req, res, send, next) { + this.post('/reset', co(function* (req, res) { var options = req.options; var hash = options.hash || options.height; @@ -816,68 +819,72 @@ HTTPServer.prototype._init = function _init() { yield this.chain.reset(hash); - send(200, { success: true }); + res.send(200, { success: true }); })); // Rescan - this.post('/rescan', con(function* (req, res, send, next) { + this.post('/rescan', co(function* (req, res) { var options = req.options; var height = options.height; - send(200, { success: true }); + res.send(200, { success: true }); + yield this.walletdb.rescan(height); })); // Resend - this.post('/resend', con(function* (req, res, send, next) { + this.post('/resend', co(function* (req, res) { yield this.walletdb.resend(); - send(200, { success: true }); + res.send(200, { success: true }); })); // Backup WalletDB - this.post('/backup', con(function* (req, res, send, next) { + this.post('/backup', co(function* (req, res) { var options = req.options; var path = options.path; enforce(path, 'Path is required.'); yield this.walletdb.backup(path); - send(200, { success: true }); + + res.send(200, { success: true }); })); // List wallets - this.get('/wallets', con(function* (req, res, send, next) { + this.get('/wallets', co(function* (req, res) { var wallets = yield this.walletdb.getWallets(); - send(200, wallets); + res.send(200, wallets); })); // Get wallet - this.get('/wallet/:id', function(req, res, send, next) { - send(200, req.wallet.toJSON()); + this.get('/wallet/:id', function(req, res) { + res.send(200, req.wallet.toJSON()); }); // Get wallet master key - this.get('/wallet/:id/master', function(req, res, send, next) { - if (!req.admin) - return send(403, { error: 'Admin access required.' }); + this.get('/wallet/:id/master', function(req, res) { + if (!req.admin) { + res.send(403, { error: 'Admin access required.' }); + return; + } - send(200, req.wallet.master.toJSON(true)); + res.send(200, req.wallet.master.toJSON(true)); }); // Create wallet - this.post('/wallet/:id?', con(function* (req, res, send, next) { + this.post('/wallet/:id?', co(function* (req, res) { var wallet = yield this.walletdb.create(req.options); - send(200, wallet.toJSON()); + res.send(200, wallet.toJSON()); })); // List accounts - this.get('/wallet/:id/account', con(function* (req, res, send, next) { + this.get('/wallet/:id/account', co(function* (req, res) { var accounts = yield req.wallet.getAccounts(); - send(200, accounts); + res.send(200, accounts); })); // Get account - this.get('/wallet/:id/account/:account', con(function* (req, res, send, next) { + this.get('/wallet/:id/account/:account', co(function* (req, res) { var options = req.options; var acct = options.name || options.account; var account; @@ -886,14 +893,16 @@ HTTPServer.prototype._init = function _init() { account = yield req.wallet.getAccount(acct); - if (!account) - return send(404); + if (!account) { + res.send(404); + return; + } - send(200, account.toJSON()); + res.send(200, account.toJSON()); })); // Create account - this.post('/wallet/:id/account/:account?', con(function* (req, res, send, next) { + this.post('/wallet/:id/account/:account?', co(function* (req, res) { var options = req.options; var passphrase = options.passphrase; var account; @@ -905,54 +914,56 @@ HTTPServer.prototype._init = function _init() { account = yield req.wallet.createAccount(options, passphrase); - if (!account) - return send(404); + if (!account) { + res.send(404); + return; + } - send(200, account.toJSON()); + res.send(200, account.toJSON()); })); // Change passphrase - this.post('/wallet/:id/passphrase', con(function* (req, res, send, next) { + this.post('/wallet/:id/passphrase', co(function* (req, res) { var options = req.options; var old = options.old; var new_ = options.passphrase; enforce(old || new_, 'Passphrase is required.'); yield req.wallet.setPassphrase(old, new_); - send(200, { success: true }); + res.send(200, { success: true }); })); // Unlock wallet - this.post('/wallet/:id/unlock', con(function* (req, res, send, next) { + this.post('/wallet/:id/unlock', co(function* (req, res) { var options = req.options; var passphrase = options.passphrase; var timeout = options.timeout; enforce(passphrase, 'Passphrase is required.'); yield req.wallet.unlock(passphrase, timeout); - send(200, { success: true }); + res.send(200, { success: true }); })); // Lock wallet - this.post('/wallet/:id/lock', con(function* (req, res, send, next) { + this.post('/wallet/:id/lock', co(function* (req, res) { yield req.wallet.lock(); - send(200, { success: true }); + res.send(200, { success: true }); })); // Import key - this.post('/wallet/:id/import', con(function* (req, res, send, next) { + this.post('/wallet/:id/import', co(function* (req, res) { var options = req.options; var acct = req.options.name || req.options.account; var key = options.privateKey || options.publicKey; if (key) { yield req.wallet.importKey(acct, key); - send(200, { success: true }); + res.send(200, { success: true }); return; } if (options.address) { enforce(options.address instanceof Address, 'Address is required.'); yield req.wallet.importAddress(acct, options.address); - send(200, { success: true }); + res.send(200, { success: true }); return; } @@ -960,66 +971,66 @@ HTTPServer.prototype._init = function _init() { })); // Generate new token - this.post('/wallet/:id/retoken', con(function* (req, res, send, next) { + this.post('/wallet/:id/retoken', co(function* (req, res) { var options = req.options; var token = yield req.wallet.retoken(options.passphrase); - send(200, { token: token.toString('hex') }); + res.send(200, { token: token.toString('hex') }); })); // Send TX - this.post('/wallet/:id/send', con(function* (req, res, send, next) { + this.post('/wallet/:id/send', co(function* (req, res) { var options = req.options; var passphrase = options.passphrase; var tx = yield req.wallet.send(options, passphrase); var details = yield req.wallet.getDetails(tx.hash('hex')); - send(200, details.toJSON()); + res.send(200, details.toJSON()); })); // Create TX - this.post('/wallet/:id/create', con(function* (req, res, send, next) { + this.post('/wallet/:id/create', co(function* (req, res) { var options = req.options; var passphrase = options.passphrase; var tx = yield req.wallet.createTX(options); yield req.wallet.sign(tx, passphrase); - send(200, tx.getJSON(this.network)); + res.send(200, tx.getJSON(this.network)); })); // Sign TX - this.post('/wallet/:id/sign', con(function* (req, res, send, next) { + this.post('/wallet/:id/sign', co(function* (req, res) { var options = req.options; var passphrase = options.passphrase; var tx = req.options.tx; enforce(tx, 'TX is required.'); yield req.wallet.sign(tx, passphrase); - send(200, tx.getJSON(this.network)); + res.send(200, tx.getJSON(this.network)); })); // Zap Wallet TXs - this.post('/wallet/:id/zap', con(function* (req, res, send, next) { + this.post('/wallet/:id/zap', co(function* (req, res) { var options = req.options; var acct = options.name || options.account; var age = options.age; enforce(age, 'Age is required.'); yield req.wallet.zap(acct, age); - send(200, { success: true }); + res.send(200, { success: true }); })); // Abandon Wallet TX - this.del('/wallet/:id/tx/:hash', con(function* (req, res, send, next) { + this.del('/wallet/:id/tx/:hash', co(function* (req, res) { var hash = req.options.hash; enforce(hash, 'Hash is required.'); yield req.wallet.abandon(hash); - send(200, { success: true }); + res.send(200, { success: true }); })); // List blocks - this.get('/wallet/:id/block', con(function* (req, res, send, next) { + this.get('/wallet/:id/block', co(function* (req, res) { var heights = yield req.wallet.getBlocks(); - send(200, heights); + res.send(200, heights); })); // Get Block Record - this.get('/wallet/:id/block/:height', con(function* (req, res, send, next) { + this.get('/wallet/:id/block/:height', co(function* (req, res) { var height = req.options.height; var block; @@ -1027,34 +1038,36 @@ HTTPServer.prototype._init = function _init() { block = yield req.wallet.getBlock(height); - if (!block) - return send(404); + if (!block) { + res.send(404); + return; + } - send(200, block.toJSON()); + res.send(200, block.toJSON()); })); // Add key - this.put('/wallet/:id/shared-key', con(function* (req, res, send, next) { + this.put('/wallet/:id/shared-key', co(function* (req, res) { var options = req.options; var acct = options.name || options.account; var key = options.accountKey; enforce(key, 'Key is required.'); yield req.wallet.addSharedKey(acct, key); - send(200, { success: true }); + res.send(200, { success: true }); })); // Remove key - this.del('/wallet/:id/shared-key', con(function* (req, res, send, next) { + this.del('/wallet/:id/shared-key', co(function* (req, res) { var options = req.options; var acct = options.name || options.account; var key = options.accountKey; enforce(key, 'Key is required.'); yield req.wallet.removeSharedKey(acct, key); - send(200, { success: true }); + res.send(200, { success: true }); })); // Get key by address - this.get('/wallet/:id/key/:address', con(function* (req, res, send, next) { + this.get('/wallet/:id/key/:address', co(function* (req, res) { var options = req.options; var address = options.address; var key; @@ -1063,14 +1076,16 @@ HTTPServer.prototype._init = function _init() { key = yield req.wallet.getKey(address); - if (!key) - return send(404); + if (!key) { + res.send(404); + return; + } - send(200, key.toJSON()); + res.send(200, key.toJSON()); })); // Get private key - this.get('/wallet/:id/wif/:address', con(function* (req, res, send, next) { + this.get('/wallet/:id/wif/:address', co(function* (req, res) { var options = req.options; var address = options.address; var passphrase = options.passphrase; @@ -1080,71 +1095,75 @@ HTTPServer.prototype._init = function _init() { key = yield req.wallet.getPrivateKey(address, passphrase); - if (!key) - return send(404); + if (!key) { + res.send(404); + return; + } - send(200, { privateKey: key.toSecret() }); + res.send(200, { privateKey: key.toSecret() }); })); // Create address - this.post('/wallet/:id/address', con(function* (req, res, send, next) { + this.post('/wallet/:id/address', co(function* (req, res) { var options = req.options; var acct = options.name || options.account; var address = yield req.wallet.createReceive(acct); - send(200, address.toJSON()); + res.send(200, address.toJSON()); })); // Create change address - this.post('/wallet/:id/change', con(function* (req, res, send, next) { + this.post('/wallet/:id/change', co(function* (req, res) { var options = req.options; var acct = options.name || options.account; var address = yield req.wallet.createChange(acct); - send(200, address.toJSON()); + res.send(200, address.toJSON()); })); // Create nested address - this.post('/wallet/:id/nested', con(function* (req, res, send, next) { + this.post('/wallet/:id/nested', co(function* (req, res) { var options = req.options; var acct = options.name || options.account; var address = yield req.wallet.createNested(acct); - send(200, address.toJSON()); + res.send(200, address.toJSON()); })); // Wallet Balance - this.get('/wallet/:id/balance', con(function* (req, res, send, next) { + this.get('/wallet/:id/balance', co(function* (req, res) { var options = req.options; var acct = options.name || options.account; var balance = yield req.wallet.getBalance(acct); - if (!balance) - return send(404); + if (!balance) { + res.send(404); + return; + } - send(200, balance.toJSON()); + res.send(200, balance.toJSON()); })); // Wallet UTXOs - this.get('/wallet/:id/coin', con(function* (req, res, send, next) { + this.get('/wallet/:id/coin', co(function* (req, res) { var options = req.options; var acct = options.name || options.account; var coins = yield req.wallet.getCoins(acct); sortCoins(coins); - send(200, coins.map(function(coin) { + res.send(200, coins.map(function(coin) { return coin.getJSON(this.network); }, this)); })); // Locked coins - this.get('/wallet/:id/coin/locked', con(function* (req, res, send, next) { + this.get('/wallet/:id/coin/locked', co(function* (req, res) { var locked = this.wallet.getLocked(); - send(200, locked.map(function(outpoint) { + res.send(200, locked.map(function(outpoint) { return outpoint.toJSON(); })); })); // Lock coin - this.put('/wallet/:id/coin/locked', con(function* (req, res, send, next) { + this.put('/wallet/:id/coin/locked', co(function* (req, res) { var options = req.options.hash; var outpoint; @@ -1157,7 +1176,7 @@ HTTPServer.prototype._init = function _init() { })); // Unlock coin - this.del('/wallet/:id/coin/locked', con(function* (req, res, send, next) { + this.del('/wallet/:id/coin/locked', co(function* (req, res) { var options = req.options.hash; var outpoint; @@ -1170,7 +1189,7 @@ HTTPServer.prototype._init = function _init() { })); // Wallet Coin - this.get('/wallet/:id/coin/:hash/:index', con(function* (req, res, send, next) { + this.get('/wallet/:id/coin/:hash/:index', co(function* (req, res) { var hash = req.options.hash; var index = req.options.index; var coin; @@ -1180,14 +1199,16 @@ HTTPServer.prototype._init = function _init() { coin = yield req.wallet.getCoin(hash, index); - if (!coin) - return send(404); + if (!coin) { + res.send(404); + return; + } - send(200, coin.getJSON(this.network)); + res.send(200, coin.getJSON(this.network)); })); // Wallet TXs - this.get('/wallet/:id/tx/history', con(function* (req, res, send, next) { + this.get('/wallet/:id/tx/history', co(function* (req, res) { var options = req.options; var acct = options.name || options.account; var txs = yield req.wallet.getHistory(acct); @@ -1197,13 +1218,13 @@ HTTPServer.prototype._init = function _init() { details = yield req.wallet.toDetails(txs); - send(200, details.map(function(tx) { + res.send(200, details.map(function(tx) { return tx.toJSON(); })); })); // Wallet Pending TXs - this.get('/wallet/:id/tx/unconfirmed', con(function* (req, res, send, next) { + this.get('/wallet/:id/tx/unconfirmed', co(function* (req, res) { var options = req.options; var acct = options.name || options.account; var txs = yield req.wallet.getPending(acct); @@ -1213,36 +1234,36 @@ HTTPServer.prototype._init = function _init() { details = yield req.wallet.toDetails(txs); - send(200, details.map(function(tx) { + res.send(200, details.map(function(tx) { return tx.toJSON(); })); })); // Wallet TXs within time range - this.get('/wallet/:id/tx/range', con(function* (req, res, send, next) { + this.get('/wallet/:id/tx/range', co(function* (req, res) { var options = req.options; var acct = options.name || options.account; var txs = yield req.wallet.getRange(acct, options); var details = yield req.wallet.toDetails(txs); - send(200, details.map(function(tx) { + res.send(200, details.map(function(tx) { return tx.toJSON(); })); })); // Last Wallet TXs - this.get('/wallet/:id/tx/last', con(function* (req, res, send, next) { + this.get('/wallet/:id/tx/last', co(function* (req, res) { var options = req.options; var acct = options.name || options.account; var limit = options.limit; var txs = yield req.wallet.getLast(acct, limit); var details = yield req.wallet.toDetails(txs); - send(200, details.map(function(tx) { + res.send(200, details.map(function(tx) { return tx.toJSON(); })); })); // Wallet TX - this.get('/wallet/:id/tx/:hash', con(function* (req, res, send, next) { + this.get('/wallet/:id/tx/:hash', co(function* (req, res) { var hash = req.options.hash; var tx, details; @@ -1250,18 +1271,20 @@ HTTPServer.prototype._init = function _init() { tx = yield req.wallet.getTX(hash); - if (!tx) - return send(404); + if (!tx) { + res.send(404); + return; + } details = yield req.wallet.toDetails(tx); - send(200, details.toJSON()); + res.send(200, details.toJSON()); })); // Resend - this.post('/wallet/:id/resend', con(function* (req, res, send, next) { + this.post('/wallet/:id/resend', co(function* (req, res) { yield req.wallet.resend(); - send(200, { success: true }); + res.send(200, { success: true }); })); this.server.on('error', function(err) { @@ -1295,12 +1318,12 @@ HTTPServer.prototype._initIO = function _initIO() { socket.destroy(); }); - socket.on('auth', function(args, callback) { + socket.on('auth', cob(function* (args) { var key = args[0]; var hash, api, service; if (socket.auth) - return callback({ error: 'Already authed.' }); + throw { error: 'Already authed.' }; socket.stop(); @@ -1308,8 +1331,10 @@ HTTPServer.prototype._initIO = function _initIO() { hash = hash256(key); api = crypto.ccmp(hash, self.apiHash); service = crypto.ccmp(hash, self.serviceHash); + if (!api && !service) - return callback({ error: 'Bad key.' }); + throw { error: 'Bad key.' }; + socket.api = api; } @@ -1318,9 +1343,7 @@ HTTPServer.prototype._initIO = function _initIO() { self.logger.info('Successful auth from %s.', socket.host); self.emit('websocket', socket); - - callback(); - }); + })); socket.emit('version', { version: pkg.version, @@ -1330,127 +1353,117 @@ HTTPServer.prototype._initIO = function _initIO() { }); this.on('websocket', function(socket) { - socket.on('wallet join', function(args, callback) { + socket.on('wallet join', cob(function* (args) { var id = args[0]; var token = args[1]; + var wallet; if (typeof id !== 'string') - return callback({ error: 'Invalid parameter.' }); + throw { error: 'Invalid parameter.' }; if (!self.options.walletAuth) { socket.join(id); - return callback(); + return; } if (!util.isHex256(token)) - return callback({ error: 'Invalid parameter.' }); + throw { error: 'Invalid parameter.' }; token = new Buffer(token, 'hex'); if (socket.api && id === '!all') { socket.join(id); - return callback(); + return; } - self.walletdb.auth(id, token).then(function(wallet) { - if (!wallet) - return callback({ error: 'Wallet does not exist.' }); + try { + wallet = yield self.walletdb.auth(id, token); + } catch (e) { + self.logger.info('Wallet auth failure for %s: %s.', id, e.message); + throw { error: 'Bad token.' }; + } - self.logger.info('Successful wallet auth for %s.', id); + if (!wallet) + throw { error: 'Wallet does not exist.' }; - socket.join(id); + self.logger.info('Successful wallet auth for %s.', id); - callback(); - }, function(err) { - self.logger.info('Wallet auth failure for %s: %s.', id, err.message); - return callback({ error: 'Bad token.' }); - }); - }); + socket.join(id); + })); - socket.on('wallet leave', function(args, callback) { + socket.on('wallet leave', cob(function* (args) { var id = args[0]; if (typeof id !== 'string') - return callback({ error: 'Invalid parameter.' }); + throw { error: 'Invalid parameter.' }; socket.leave(id); + })); - callback(); - }); - - socket.on('options', function(args, callback) { + socket.on('options', cob(function* (args) { var options = args[0]; - try { socket.setOptions(options); } catch (e) { - return callback({ error: e.message }); + throw { error: e.message }; } + })); - callback(); - }); - - socket.on('watch chain', function(args, callback) { + socket.on('watch chain', cob(function* (args) { if (!socket.api) - return callback({ error: 'Not authorized.' }); + throw { error: 'Not authorized.' }; try { socket.watchChain(); } catch (e) { - return callback({ error: e.message }); + throw { error: e.message }; } + })); - callback(); - }); - - socket.on('unwatch chain', function(args, callback) { + socket.on('unwatch chain', cob(function* (args) { if (!socket.api) - return callback({ error: 'Not authorized.' }); + throw { error: 'Not authorized.' }; try { socket.unwatchChain(); } catch (e) { - return callback({ error: e.message }); + throw { error: e.message }; } + })); - callback(); - }); - - socket.on('set filter', function(args, callback) { + socket.on('set filter', cob(function* (args) { var data = args[0]; var filter; if (!util.isHex(data) && !Buffer.isBuffer(data)) - return callback({ error: 'Invalid parameter.' }); + throw { error: 'Invalid parameter.' }; if (!socket.api) - return callback({ error: 'Not authorized.' }); + throw { error: 'Not authorized.' }; try { filter = Bloom.fromRaw(data, 'hex'); socket.setFilter(filter); } catch (e) { - return callback({ error: e.message }); + throw { error: e.message }; } + })); - callback(); - }); + socket.on('get tip', cob(function* (args) { + return socket.frameEntry(self.chain.tip); + })); - socket.on('get tip', function(args, callback) { - callback(null, socket.frameEntry(self.chain.tip)); - }); - - socket.on('get entry', co(function* (args, callback) { + socket.on('get entry', cob(function* (args) { var block = args[0]; var entry; if (typeof block === 'string') { if (!util.isHex256(block)) - return callback({ error: 'Invalid parameter.' }); + throw { error: 'Invalid parameter.' }; block = util.revHex(block); } else { if (!util.isUInt32(block)) - return callback({ error: 'Invalid parameter.' }); + throw { error: 'Invalid parameter.' }; } try { @@ -1458,97 +1471,95 @@ HTTPServer.prototype._initIO = function _initIO() { if (!(yield entry.isMainChain())) entry = null; } catch (e) { - return callback({ error: e.message }); + throw { error: e.message }; } if (!entry) - return callback(null, null); + return null; - callback(null, socket.frameEntry(entry)); + return socket.frameEntry(entry); })); - socket.on('add filter', function(args, callback) { + socket.on('add filter', cob(function* (args) { var chunks = args[0]; if (!Array.isArray(chunks)) - return callback({ error: 'Invalid parameter.' }); + throw { error: 'Invalid parameter.' }; if (!socket.api) - return callback({ error: 'Not authorized.' }); + throw { error: 'Not authorized.' }; try { socket.addFilter(chunks); } catch (e) { - return callback({ error: e.message }); + throw { error: e.message }; } + })); - callback(); - }); - - socket.on('reset filter', function(args, callback) { + socket.on('reset filter', cob(function* (args) { if (!socket.api) - return callback({ error: 'Not authorized.' }); + throw { error: 'Not authorized.' }; try { socket.resetFilter(); } catch (e) { - return callback({ error: e.message }); + throw { error: e.message }; } + })); - callback(); - }); - - socket.on('estimate fee', function(args, callback) { + socket.on('estimate fee', cob(function* (args) { var blocks = args[0]; var rate; if (blocks != null && !util.isNumber(blocks)) - return callback({ error: 'Invalid parameter.' }); + throw { error: 'Invalid parameter.' }; if (!self.fees) { rate = self.network.feeRate; rate = Amount.btc(rate); - return callback(null, rate); + return rate; } rate = self.fees.estimateFee(blocks); rate = Amount.btc(rate); - return callback(null, rate); - }); + return rate; + })); - socket.on('send', function(args, callback) { + socket.on('send', cob(function* (args) { var data = args[0]; var tx; if (!util.isHex(data) && !Buffer.isBuffer(data)) - return callback({ error: 'Invalid parameter.' }); + throw { error: 'Invalid parameter.' }; try { tx = TX.fromRaw(data, 'hex'); } catch (e) { - return callback({ error: 'Invalid parameter.' }); + throw { error: 'Invalid parameter.' }; } self.node.send(tx); - }); + })); - socket.on('rescan', function(args, callback) { + socket.on('rescan', cob(function* (args) { var start = args[0]; if (!util.isHex256(start) && !util.isUInt32(start)) - return callback({ error: 'Invalid parameter.' }); + throw { error: 'Invalid parameter.' }; if (!socket.api) - return callback({ error: 'Not authorized.' }); + throw { error: 'Not authorized.' }; if (typeof start === 'string') start = util.revHex(start); - socket.scan(start).then(callback, function(err) { - callback({ error: err.message }); - }); - }); + try { + yield socket.scan(start); + } catch (e) { + throw { error: e.message }; + } + })); }); this.walletdb.on('tx', function(id, tx, details) { @@ -1622,47 +1633,64 @@ HTTPServer.prototype.close = function close() { }; /** - * @see HTTPBase#use + * Add a middleware to the stack. + * @param {String?} path + * @param {Function} handler */ -HTTPServer.prototype.use = function use(path, callback) { - return this.server.use(path, callback, this); +HTTPServer.prototype.use = function use(path, handler) { + if (!handler) { + handler = path; + path = null; + } + return this.server.use(path, handler, this); }; /** - * @see HTTPBase#get + * Add a GET route. + * @param {String} path + * @param {Function} handler */ -HTTPServer.prototype.get = function get(path, callback) { - return this.server.get(path, callback, this); +HTTPServer.prototype.get = function get(path, handler) { + return this.server.get(path, handler, this); }; /** - * @see HTTPBase#post + * Add a POST route. + * @param {String} path + * @param {Function} handler */ -HTTPServer.prototype.post = function post(path, callback) { - return this.server.post(path, callback, this); +HTTPServer.prototype.post = function post(path, handler) { + return this.server.post(path, handler, this); }; /** - * @see HTTPBase#put + * Add a PUT route. + * @param {String} path + * @param {Function} handler */ -HTTPServer.prototype.put = function put(path, callback) { - return this.server.put(path, callback, this); +HTTPServer.prototype.put = function put(path, handler) { + return this.server.put(path, handler, this); }; /** - * @see HTTPBase#del + * Add a DEL route. + * @param {String} path + * @param {Function} handler */ -HTTPServer.prototype.del = function del(path, callback) { - return this.server.del(path, callback, this); +HTTPServer.prototype.del = function del(path, handler) { + return this.server.del(path, handler, this); }; /** - * @see HTTPBase#listen + * Listen on port and host. + * @param {Number} port + * @param {String} host + * @returns {Promise} */ HTTPServer.prototype.listen = function listen(port, host) { diff --git a/lib/utils/co.js b/lib/utils/co.js index 2c3ab701..36b21f95 100644 --- a/lib/utils/co.js +++ b/lib/utils/co.js @@ -119,38 +119,11 @@ function cob(generator) { gen = generator.apply(this, args); - return cb(exec(gen), callback); - }; -} - -/** - * Wrap a generator function to be - * executed into a function that - * accepts a node.js style callback. - * Only executes callback on error. - * @param {GeneratorFunction} - * @returns {Function} - */ - -function con(generator) { - return function() { - var i, args, callback, gen; - - if (arguments.length === 0 - || typeof arguments[arguments.length - 1] !== 'function') { - throw new Error((generator.name || 'Function') + ' requires a callback.'); - } - - args = new Array(arguments.length); - callback = arguments[arguments.length - 1]; - - for (i = 0; i < args.length; i++) - args[i] = arguments[i]; - - gen = generator.apply(this, args); - - return exec(gen).catch(function(err) { - // Escape the promise's scope: + exec(gen).then(function(value) { + nextTick(function() { + callback(null, value); + }); + }, function(err) { nextTick(function() { callback(err); }); @@ -158,25 +131,6 @@ function con(generator) { }; } -/** - * Wait for promise to resolve and - * execute a node.js style callback. - * @param {Promise} promise - * @returns {Promise} - */ - -function cb(promise, callback) { - promise.then(function(value) { - nextTick(function() { - callback(null, value); - }); - }, function(err) { - nextTick(function() { - callback(err); - }); - }); -} - /** * Wait for a nextTick with a promise. * @returns {Promise} @@ -188,6 +142,7 @@ function wait() { /** * Wait for a nextTick. + * @private * @param {Function} resolve * @param {Function} reject */ @@ -316,8 +271,6 @@ exports.exec = exec; exports.spawn = spawn; exports.co = co; exports.cob = cob; -exports.con = con; -exports.cb = cb; exports.wait = wait; exports.timeout = timeout; exports.wrap = wrap; diff --git a/test/chain-test.js b/test/chain-test.js index bcf19576..13d6c201 100644 --- a/test/chain-test.js +++ b/test/chain-test.js @@ -9,7 +9,6 @@ var Script = require('../lib/script/script'); var FullNode = require('../lib/node/fullnode'); var MTX = require('../lib/primitives/mtx'); // var Client = require('../lib/wallet/client'); -var cob = co.cob; describe('Chain', function() { var node = new FullNode({ db: 'memory', apiKey: 'foo', network: 'regtest' }); @@ -48,25 +47,25 @@ describe('Chain', function() { return yield attempt.mineAsync(); }); - it('should open chain and miner', cob(function* () { + it('should open chain and miner', co(function* () { miner.mempool = null; consensus.COINBASE_MATURITY = 0; yield node.open(); })); - it('should open walletdb', cob(function* () { + it('should open walletdb', co(function* () { wallet = yield walletdb.create(); miner.addresses.length = 0; miner.addAddress(wallet.getReceive()); })); - it('should mine a block', cob(function* () { + it('should mine a block', co(function* () { var block = yield miner.mineBlock(); assert(block); yield chain.add(block); })); - it('should mine competing chains', cob(function* () { + it('should mine competing chains', co(function* () { var i, block1, block2; for (i = 0; i < 10; i++) { @@ -89,6 +88,8 @@ describe('Chain', function() { assert(tip2); assert(!(yield tip2.isMainChain())); + + yield co.wait(); } })); @@ -98,7 +99,7 @@ describe('Chain', function() { assert.equal(chain.db.state.tx, 21); }); - it('should have correct balance', cob(function* () { + it('should have correct balance', co(function* () { var balance; yield co.timeout(100); @@ -108,7 +109,7 @@ describe('Chain', function() { assert.equal(balance.confirmed, 550 * 1e8); })); - it('should handle a reorg', cob(function* () { + it('should handle a reorg', co(function* () { var entry, block, forked; assert.equal(walletdb.state.height, chain.height); @@ -139,7 +140,7 @@ describe('Chain', function() { assert.equal(chain.db.state.tx, 22); }); - it('should have correct balance', cob(function* () { + it('should have correct balance', co(function* () { var balance; yield co.timeout(100); @@ -149,12 +150,12 @@ describe('Chain', function() { assert.equal(balance.confirmed, 600 * 1e8); })); - it('should check main chain', cob(function* () { + it('should check main chain', co(function* () { var result = yield tip1.isMainChain(); assert(!result); })); - it('should mine a block after a reorg', cob(function* () { + it('should mine a block after a reorg', co(function* () { var block = yield mineBlock(null, cb2); var entry, result; @@ -168,7 +169,7 @@ describe('Chain', function() { assert(result); })); - it('should prevent double spend on new chain', cob(function* () { + it('should prevent double spend on new chain', co(function* () { var block = yield mineBlock(null, cb2); var tip = chain.tip; var err; @@ -184,7 +185,7 @@ describe('Chain', function() { assert(chain.tip === tip); })); - it('should fail to mine a block with coins on an alternate chain', cob(function* () { + it('should fail to mine a block with coins on an alternate chain', co(function* () { var block = yield mineBlock(null, cb1); var tip = chain.tip; var err; @@ -206,7 +207,7 @@ describe('Chain', function() { assert.equal(chain.db.state.tx, 24); }); - it('should get coin', cob(function* () { + it('should get coin', co(function* () { var block, tx, output, coin; block = yield mineBlock(); @@ -223,7 +224,7 @@ describe('Chain', function() { assert.deepEqual(coin.toRaw(), output.toRaw()); })); - it('should get balance', cob(function* () { + it('should get balance', co(function* () { var balance, txs; yield co.timeout(100); @@ -241,7 +242,7 @@ describe('Chain', function() { assert.equal(txs.length, 45); })); - it('should get tips and remove chains', cob(function* () { + it('should get tips and remove chains', co(function* () { var tips = yield chain.db.getTips(); assert.notEqual(tips.indexOf(chain.tip.hash), -1); @@ -255,7 +256,7 @@ describe('Chain', function() { assert.equal(tips.length, 1); })); - it('should rescan for transactions', cob(function* () { + it('should rescan for transactions', co(function* () { var total = 0; yield chain.db.scan(0, walletdb.filter, function(block, txs) { @@ -266,7 +267,7 @@ describe('Chain', function() { assert.equal(total, 26); })); - it('should activate csv', cob(function* () { + it('should activate csv', co(function* () { var deployments = chain.network.deployments; var i, block, prev, state, cache; @@ -330,7 +331,7 @@ describe('Chain', function() { return yield attempt.mineAsync(); }); - it('should test csv', cob(function* () { + it('should test csv', co(function* () { var tx = (yield chain.db.getBlock(chain.height)).txs[0]; var block = yield mineCSV(tx); var csv, attempt, redeemer; @@ -361,7 +362,7 @@ describe('Chain', function() { yield chain.add(block); })); - it('should fail csv with bad sequence', cob(function* () { + it('should fail csv with bad sequence', co(function* () { var csv = (yield chain.db.getBlock(chain.height)).txs[1]; var block, attempt, redeemer, err; @@ -394,13 +395,13 @@ describe('Chain', function() { assert(err.reason, 'mandatory-script-verify-flag-failed'); })); - it('should mine a block', cob(function* () { + it('should mine a block', co(function* () { var block = yield miner.mineBlock(); assert(block); yield chain.add(block); })); - it('should fail csv lock checks', cob(function* () { + it('should fail csv lock checks', co(function* () { var tx = (yield chain.db.getBlock(chain.height)).txs[0]; var block = yield mineCSV(tx); var csv, attempt, redeemer, err; @@ -438,12 +439,12 @@ describe('Chain', function() { assert.equal(err.reason, 'bad-txns-nonfinal'); })); - it('should rescan for transactions', cob(function* () { + it('should rescan for transactions', co(function* () { yield walletdb.rescan(0); assert.equal(wallet.txdb.state.confirmed, 1289250000000); })); - it('should cleanup', cob(function* () { + it('should cleanup', co(function* () { consensus.COINBASE_MATURITY = 100; yield node.close(); })); diff --git a/test/http-test.js b/test/http-test.js index b9664e12..028af59d 100644 --- a/test/http-test.js +++ b/test/http-test.js @@ -11,7 +11,6 @@ var MTX = require('../lib/primitives/mtx'); var HTTP = require('../lib/http'); var FullNode = require('../lib/node/fullnode'); var USER_VERSION = require('../package.json').version; -var cob = co.cob; describe('HTTP', function() { var node, wallet, addr, hash; @@ -32,17 +31,17 @@ describe('HTTP', function() { this.timeout(15000); - it('should open node', cob(function* () { + it('should open node', co(function* () { consensus.COINBASE_MATURITY = 0; yield node.open(); })); - it('should create wallet', cob(function* () { + it('should create wallet', co(function* () { var info = yield wallet.create({ id: 'test' }); assert.equal(info.id, 'test'); })); - it('should get info', cob(function* () { + it('should get info', co(function* () { var info = yield wallet.client.getInfo(); assert.equal(info.network, node.network.type); assert.equal(info.version, USER_VERSION); @@ -51,7 +50,7 @@ describe('HTTP', function() { assert.equal(info.chain.height, 0); })); - it('should get wallet info', cob(function* () { + it('should get wallet info', co(function* () { var info = yield wallet.getInfo(); assert.equal(info.id, 'test'); addr = info.account.receiveAddress; @@ -59,7 +58,7 @@ describe('HTTP', function() { addr = Address.fromBase58(addr); })); - it('should fill with funds', cob(function* () { + it('should fill with funds', co(function* () { var tx, balance, receive, details; // Coinbase @@ -97,13 +96,13 @@ describe('HTTP', function() { assert.equal(details.hash, tx.rhash()); })); - it('should get balance', cob(function* () { + it('should get balance', co(function* () { var balance = yield wallet.getBalance(); assert.equal(Amount.value(balance.confirmed), 0); assert.equal(Amount.value(balance.unconfirmed), 201840); })); - it('should send a tx', cob(function* () { + it('should send a tx', co(function* () { var value = 0; var options, tx; @@ -128,30 +127,30 @@ describe('HTTP', function() { hash = tx.hash; })); - it('should get a tx', cob(function* () { + it('should get a tx', co(function* () { var tx = yield wallet.getTX(hash); assert(tx); assert.equal(tx.hash, hash); })); - it('should generate new api key', cob(function* () { + it('should generate new api key', co(function* () { var t = wallet.token.toString('hex'); var token = yield wallet.retoken(null); assert(token.length === 64); assert.notEqual(token, t); })); - it('should get balance', cob(function* () { + it('should get balance', co(function* () { var balance = yield wallet.getBalance(); assert.equal(Amount.value(balance.unconfirmed), 199570); })); - it('should execute an rpc call', cob(function* () { + it('should execute an rpc call', co(function* () { var info = yield wallet.client.rpc.call('getblockchaininfo', []); assert.equal(info.blocks, 0); })); - it('should cleanup', cob(function* () { + it('should cleanup', co(function* () { consensus.COINBASE_MATURITY = 100; yield wallet.close(); yield node.close(); diff --git a/test/mempool-test.js b/test/mempool-test.js index 23c949e0..ec388854 100644 --- a/test/mempool-test.js +++ b/test/mempool-test.js @@ -17,7 +17,6 @@ var Script = require('../lib/script/script'); var Witness = require('../lib/script/witness'); var Block = require('../lib/primitives/block'); var opcodes = Script.opcodes; -var cob = co.cob; describe('Mempool', function() { var chain, mempool, walletdb, wallet, cached; @@ -65,20 +64,20 @@ describe('Mempool', function() { return Coin.fromTX(fund, 0, -1); } - it('should open mempool', cob(function* () { + it('should open mempool', co(function* () { yield mempool.open(); chain.state.flags |= Script.flags.VERIFY_WITNESS; })); - it('should open walletdb', cob(function* () { + it('should open walletdb', co(function* () { yield walletdb.open(); })); - it('should open wallet', cob(function* () { + it('should open wallet', co(function* () { wallet = yield walletdb.create(); })); - it('should handle incoming orphans and TXs', cob(function* () { + it('should handle incoming orphans and TXs', co(function* () { var kp = KeyRing.generate(); var w = wallet; var t1, t2, t3, t4, f1, fake, prev, sig, balance, txs; @@ -177,7 +176,7 @@ describe('Mempool', function() { })); })); - it('should handle locktime', cob(function* () { + it('should handle locktime', co(function* () { var w = wallet; var kp = KeyRing.generate(); var tx, prev, prevHash, sig; @@ -203,7 +202,7 @@ describe('Mempool', function() { chain.tip.height = 0; })); - it('should handle invalid locktime', cob(function* () { + it('should handle invalid locktime', co(function* () { var w = wallet; var kp = KeyRing.generate(); var tx, prev, prevHash, sig, err; @@ -234,7 +233,7 @@ describe('Mempool', function() { chain.tip.height = 0; })); - it('should not cache a malleated wtx with mutated sig', cob(function* () { + it('should not cache a malleated wtx with mutated sig', co(function* () { var w = wallet; var kp = KeyRing.generate(); var tx, prev, prevHash, prevs, sig, err; @@ -268,7 +267,7 @@ describe('Mempool', function() { assert(!mempool.hasReject(tx.hash())); })); - it('should not cache a malleated tx with unnecessary witness', cob(function* () { + it('should not cache a malleated tx with unnecessary witness', co(function* () { var w = wallet; var kp = KeyRing.generate(); var tx, prev, prevHash, sig, err; @@ -297,7 +296,7 @@ describe('Mempool', function() { assert(!mempool.hasReject(tx.hash())); })); - it('should not cache a malleated wtx with wit removed', cob(function* () { + it('should not cache a malleated wtx with wit removed', co(function* () { var w = wallet; var kp = KeyRing.generate(); var tx, prev, prevHash, err; @@ -326,7 +325,7 @@ describe('Mempool', function() { assert(!mempool.hasReject(tx.hash())); })); - it('should cache non-malleated tx without sig', cob(function* () { + it('should cache non-malleated tx without sig', co(function* () { var w = wallet; var kp = KeyRing.generate(); var tx, prev, prevHash, err; @@ -354,7 +353,7 @@ describe('Mempool', function() { cached = tx; })); - it('should clear reject cache', cob(function* () { + it('should clear reject cache', co(function* () { var w = wallet; var tx, input; @@ -368,7 +367,7 @@ describe('Mempool', function() { assert(!mempool.hasReject(cached.hash())); })); - it('should destroy mempool', cob(function* () { + it('should destroy mempool', co(function* () { yield mempool.close(); })); }); diff --git a/test/wallet-test.js b/test/wallet-test.js index 603563f1..7c25d388 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -17,7 +17,6 @@ var Outpoint = require('../lib/primitives/outpoint'); var Script = require('../lib/script/script'); var HD = require('../lib/hd'); var scriptTypes = Script.types; -var cob = co.cob; var KEY1 = 'xprv9s21ZrQH143K3Aj6xQBymM31Zb4BVc7wxqfUhMZrzewdDVCt' + 'qUP9iWfcHgJofs25xbaUpCps9GDXj83NiWvQCAkWQhVj5J4CorfnpKX94AZ'; @@ -68,12 +67,12 @@ describe('Wallet', function() { this.timeout(5000); - it('should open walletdb', cob(function* () { + it('should open walletdb', co(function* () { consensus.COINBASE_MATURITY = 0; yield walletdb.open(); })); - it('should generate new key and address', cob(function* () { + it('should generate new key and address', co(function* () { var w = yield walletdb.create(); var addr = w.getAddress('base58'); assert(addr); @@ -90,7 +89,7 @@ describe('Wallet', function() { }); }); - it('should create and get wallet', cob(function* () { + it('should create and get wallet', co(function* () { var w1, w2; w1 = yield walletdb.create(); @@ -134,19 +133,19 @@ describe('Wallet', function() { assert(tx.verify(flags)); }); - it('should sign/verify pubkeyhash tx', cob(function* () { + it('should sign/verify pubkeyhash tx', co(function* () { yield testP2PKH(false, false); })); - it('should sign/verify witnesspubkeyhash tx', cob(function* () { + it('should sign/verify witnesspubkeyhash tx', co(function* () { yield testP2PKH(true, false); })); - it('should sign/verify witnesspubkeyhash tx with bullshit nesting', cob(function* () { + it('should sign/verify witnesspubkeyhash tx with bullshit nesting', co(function* () { yield testP2PKH(true, true); })); - it('should multisign/verify TX', cob(function* () { + it('should multisign/verify TX', co(function* () { var w, k, script, src, tx, maxSize; w = yield walletdb.create({ @@ -183,7 +182,7 @@ describe('Wallet', function() { assert(tx.verify()); })); - it('should handle missed and invalid txs', cob(function* () { + it('should handle missed and invalid txs', co(function* () { var w = yield walletdb.create(); var f = yield walletdb.create(); var t1, t2, t3, t4, f1, fake, balance, txs; @@ -287,7 +286,7 @@ describe('Wallet', function() { })); })); - it('should cleanup spenders after double-spend', cob(function* () { + it('should cleanup spenders after double-spend', co(function* () { var w = doubleSpendWallet; var tx, txs, total, balance; @@ -323,7 +322,7 @@ describe('Wallet', function() { assert.equal(total, 56000); })); - it('should handle missed txs without resolution', cob(function* () { + it('should handle missed txs without resolution', co(function* () { var walletdb, w, f, t1, t2, t3, t4, f1, balance, txs; walletdb = new WalletDB({ @@ -448,7 +447,7 @@ describe('Wallet', function() { assert.equal(balance.unconfirmed, 10000); })); - it('should fill tx with inputs', cob(function* () { + it('should fill tx with inputs', co(function* () { var w1 = yield walletdb.create(); var w2 = yield walletdb.create(); var view, t1, t2, t3, err; @@ -493,7 +492,7 @@ describe('Wallet', function() { assert.equal(err.requiredFunds, 25000); })); - it('should fill tx with inputs with accurate fee', cob(function* () { + it('should fill tx with inputs with accurate fee', co(function* () { var w1 = yield walletdb.create({ master: KEY1 }); var w2 = yield walletdb.create({ master: KEY2 }); var view, t1, t2, t3, balance, err; @@ -552,7 +551,7 @@ describe('Wallet', function() { assert(balance.unconfirmed === 5460); })); - it('should sign multiple inputs using different keys', cob(function* () { + it('should sign multiple inputs using different keys', co(function* () { var w1 = yield walletdb.create(); var w2 = yield walletdb.create(); var to = yield walletdb.create(); @@ -746,19 +745,19 @@ describe('Wallet', function() { assert.equal(send.getFee(view), 10000); }); - it('should verify 2-of-3 scripthash tx', cob(function* () { + it('should verify 2-of-3 scripthash tx', co(function* () { yield testMultisig(false, false); })); - it('should verify 2-of-3 witnessscripthash tx', cob(function* () { + it('should verify 2-of-3 witnessscripthash tx', co(function* () { yield testMultisig(true, false); })); - it('should verify 2-of-3 witnessscripthash tx with bullshit nesting', cob(function* () { + it('should verify 2-of-3 witnessscripthash tx with bullshit nesting', co(function* () { yield testMultisig(true, true); })); - it('should fill tx with account 1', cob(function* () { + it('should fill tx with account 1', co(function* () { var w1 = yield walletdb.create(); var w2 = yield walletdb.create(); var account, accounts, rec, t1, t2, t3, err; @@ -813,7 +812,7 @@ describe('Wallet', function() { assert.deepEqual(accounts, ['default', 'foo']); })); - it('should fail to fill tx with account 1', cob(function* () { + it('should fail to fill tx with account 1', co(function* () { var w = yield walletdb.create(); var acc, account, t1, t2, err; @@ -879,7 +878,7 @@ describe('Wallet', function() { yield w.fund(t2, { rate: 10000, round: true, account: 'foo' }); })); - it('should create two accounts (multiple encryption)', cob(function* () { + it('should create two accounts (multiple encryption)', co(function* () { var w = yield walletdb.create({ id: 'foobar', passphrase: 'foo' }); var account; @@ -893,7 +892,7 @@ describe('Wallet', function() { yield w.lock(); })); - it('should fill tx with inputs when encrypted', cob(function* () { + it('should fill tx with inputs when encrypted', co(function* () { var w = yield walletdb.create({ passphrase: 'foo' }); var t1, t2, err; @@ -931,7 +930,7 @@ describe('Wallet', function() { assert(t2.verify()); })); - it('should fill tx with inputs with subtract fee', cob(function* () { + it('should fill tx with inputs with subtract fee', co(function* () { var w1 = yield walletdb.create(); var w2 = yield walletdb.create(); var t1, t2; @@ -960,7 +959,7 @@ describe('Wallet', function() { assert.equal(t2.getFee(), 10000); })); - it('should fill tx with inputs with subtract fee', cob(function* () { + it('should fill tx with inputs with subtract fee', co(function* () { var w1 = yield walletdb.create(); var w2 = yield walletdb.create(); var options, t1, t2; @@ -994,19 +993,19 @@ describe('Wallet', function() { assert.equal(t2.getFee(), 10000); })); - it('should get range of txs', cob(function* () { + it('should get range of txs', co(function* () { var w = wallet; var txs = yield w.getRange({ start: util.now() - 1000 }); assert.equal(txs.length, 2); })); - it('should get range of txs from account', cob(function* () { + it('should get range of txs from account', co(function* () { var w = wallet; var txs = yield w.getRange('foo', { start: util.now() - 1000 }); assert.equal(txs.length, 2); })); - it('should not get range of txs from non-existent account', cob(function* () { + it('should not get range of txs from non-existent account', co(function* () { var w = wallet; var txs, err; @@ -1020,13 +1019,13 @@ describe('Wallet', function() { assert.equal(err.message, 'Account not found.'); })); - it('should get account balance', cob(function* () { + it('should get account balance', co(function* () { var w = wallet; var balance = yield w.getBalance('foo'); assert.equal(balance.unconfirmed, 21840); })); - it('should import privkey', cob(function* () { + it('should import privkey', co(function* () { var key = KeyRing.generate(); var w = yield walletdb.create({ passphrase: 'test' }); var options, k, t1, t2, wtx; @@ -1069,7 +1068,7 @@ describe('Wallet', function() { ekey = key; })); - it('should import pubkey', cob(function* () { + it('should import pubkey', co(function* () { var priv = KeyRing.generate(); var key = new KeyRing(priv.publicKey); var w = yield walletdb.create({ watchOnly: true }); @@ -1085,7 +1084,7 @@ describe('Wallet', function() { assert(k); })); - it('should import address', cob(function* () { + it('should import address', co(function* () { var key = KeyRing.generate(); var w = yield walletdb.create({ watchOnly: true }); var k; @@ -1100,7 +1099,7 @@ describe('Wallet', function() { assert(!k); })); - it('should get details', cob(function* () { + it('should get details', co(function* () { var w = wallet; var txs = yield w.getRange('foo', { start: util.now() - 1000 }); var details = yield w.toDetails(txs); @@ -1109,7 +1108,7 @@ describe('Wallet', function() { })); })); - it('should rename wallet', cob(function* () { + it('should rename wallet', co(function* () { var w = wallet; yield wallet.rename('test'); var txs = yield w.getRange('foo', { start: util.now() - 1000 }); @@ -1117,7 +1116,7 @@ describe('Wallet', function() { assert.equal(details[0].toJSON().id, 'test'); })); - it('should change passphrase with encrypted imports', cob(function* () { + it('should change passphrase with encrypted imports', co(function* () { var w = ewallet; var addr = ekey.getAddress(); var path, d1, d2, k; @@ -1156,7 +1155,7 @@ describe('Wallet', function() { assert.equal(k.getHash('hex'), addr.getHash('hex')); })); - it('should recover from a missed tx', cob(function* () { + it('should recover from a missed tx', co(function* () { var walletdb, alice, addr, bob, t1, t2, t3; walletdb = new WalletDB({ @@ -1220,7 +1219,7 @@ describe('Wallet', function() { assert.equal((yield bob.getBalance()).unconfirmed, 30000); })); - it('should recover from a missed tx and double spend', cob(function* () { + it('should recover from a missed tx and double spend', co(function* () { var walletdb, alice, addr, bob, t1, t2, t3, t2a; walletdb = new WalletDB({