From 8c11a2aa3ff5aa7ab3bcc8117bb6928fb242b43f Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 22 Sep 2016 01:29:48 -0700 Subject: [PATCH] generators: refactor http. --- bin/cli | 13 +- bin/spvnode | 13 + lib/chain/chaindb.js | 2 +- lib/env.js | 4 + lib/http/base.js | 36 +-- lib/http/rpc.js | 56 ++-- lib/http/server.js | 689 +++++++++++++++++++---------------------- lib/miner/miner.js | 50 +-- lib/utils/spawn.js | 43 ++- lib/wallet/walletdb.js | 4 +- test/chain-test.js | 5 +- 11 files changed, 452 insertions(+), 463 deletions(-) diff --git a/bin/cli b/bin/cli index ec507552..e7ef18cd 100755 --- a/bin/cli +++ b/bin/cli @@ -8,6 +8,7 @@ var spawn = require('../lib/utils/spawn'); var Client = require('../lib/http/client'); var Wallet = require('../lib/http/wallet'); var assert = utils.assert; +var main; function CLI() { this.config = config({ @@ -444,13 +445,11 @@ CLI.prototype.destroy = function destroy() { return Promise.resolve(null); }; -function main() { - return spawn(function *() { - var cli = new CLI(); - yield cli.open(); - yield cli.destroy(); - }, this); -} +main = co(function* main() { + var cli = new CLI(); + yield cli.open(); + yield cli.destroy(); +}); main().then(process.exit).catch(function(err) { console.error(err.stack + ''); diff --git a/bin/spvnode b/bin/spvnode index 94aebabd..8f475aae 100755 --- a/bin/spvnode +++ b/bin/spvnode @@ -25,6 +25,19 @@ node.on('error', function(err) { ; }); +process.on('uncaughtException', function(err) { + node.logger.debug(err.stack); + node.logger.error(err); + process.exit(1); +}); + +process.on('unhandledRejection', function(err, promise) { + node.logger.debug('Unhandled Rejection'); + node.logger.debug(err.stack); + node.logger.error(err); + process.exit(1); +}); + node.open().then(function() { if (process.argv.indexOf('--test') !== -1) { node.pool.watchAddress('1VayNert3x1KzbpzMGt2qdqrAThiRovi8'); diff --git a/lib/chain/chaindb.js b/lib/chain/chaindb.js index 36aca9aa..ffa4684d 100644 --- a/lib/chain/chaindb.js +++ b/lib/chain/chaindb.js @@ -1185,7 +1185,7 @@ ChainDB.prototype.scan = co(function* scan(start, filter, iter) { } } - yield* iter(entry, txs); + yield iter(entry, txs); entry = yield entry.getNext(); } diff --git a/lib/env.js b/lib/env.js index 0b3ba86c..d2f2a019 100644 --- a/lib/env.js +++ b/lib/env.js @@ -126,6 +126,7 @@ function Environment() { this.require('bloom', './utils/bloom'); this.require('uri', './utils/uri'); this.require('errors', './utils/errors'); + this.require('spawn', './utils/spawn'); // Crypto this.require('ec', './crypto/ec'); @@ -207,6 +208,7 @@ function Environment() { // HTTP this.require('http', './http'); + this.require('rpc', './http/rpc'); // Workers this.require('workers', './workers/workers'); @@ -343,6 +345,7 @@ Environment.prototype.cache = function cache() { require('./utils/bloom'); require('./utils/uri'); require('./utils/errors'); + require('./utils/spawn'); require('./crypto/ec'); require('./crypto/crypto'); require('./crypto/chachapoly'); @@ -399,6 +402,7 @@ Environment.prototype.cache = function cache() { require('./wallet/walletdb'); require('./wallet/path'); require('./http'); + require('./http/rpc'); require('./workers/workers'); require('./bip70/bip70'); }; diff --git a/lib/http/base.js b/lib/http/base.js index deffdcc9..127e1512 100644 --- a/lib/http/base.js +++ b/lib/http/base.js @@ -162,7 +162,7 @@ HTTPBase.prototype._initRouter = function _initRouter() { // Avoid stack overflows utils.nextTick(function() { try { - callback(req, res, next, _send); + callback.call(route.ctx, req, res, _send, next); } catch (e) { done(e); } @@ -264,11 +264,11 @@ HTTPBase.prototype._handle = function _handle(req, res, _send, callback) { handler = self.stack[i++]; utils.nextTick(function() { - if (handler.path && req.pathname.indexOf(handler.path) === -1) + if (handler.path && req.pathname.indexOf(handler.path) !== 0) return next(); try { - handler.callback(req, res, next, _send); + handler.callback.call(handler.ctx, req, res, _send, next); } catch (e) { next(e); } @@ -291,7 +291,7 @@ HTTPBase.prototype._handle = function _handle(req, res, _send, callback) { * @param {RouteCallback} callback */ -HTTPBase.prototype.use = function use(path, callback) { +HTTPBase.prototype.use = function use(path, callback, ctx) { if (!callback) { callback = path; path = null; @@ -299,12 +299,12 @@ HTTPBase.prototype.use = function use(path, callback) { if (Array.isArray(path)) { path.forEach(function(path) { - this.use(path, callback); + this.use(path, callback, ctx); }, this); return; } - this.stack.push({ path: path, callback: callback }); + this.stack.push({ ctx: ctx, path: path, callback: callback }); }; /** @@ -313,14 +313,14 @@ HTTPBase.prototype.use = function use(path, callback) { * @param {RouteCallback} callback */ -HTTPBase.prototype.get = function get(path, callback) { +HTTPBase.prototype.get = function get(path, callback, ctx) { if (Array.isArray(path)) { path.forEach(function(path) { - this.get(path, callback); + this.get(path, callback, ctx); }, this); return; } - this.routes.get.push({ path: path, callback: callback }); + this.routes.get.push({ ctx: ctx, path: path, callback: callback }); }; /** @@ -329,14 +329,14 @@ HTTPBase.prototype.get = function get(path, callback) { * @param {RouteCallback} callback */ -HTTPBase.prototype.post = function post(path, callback) { +HTTPBase.prototype.post = function post(path, callback, ctx) { if (Array.isArray(path)) { path.forEach(function(path) { - this.post(path, callback); + this.post(path, callback, ctx); }, this); return; } - this.routes.post.push({ path: path, callback: callback }); + this.routes.post.push({ ctx: ctx, path: path, callback: callback }); }; /** @@ -345,14 +345,14 @@ HTTPBase.prototype.post = function post(path, callback) { * @param {RouteCallback} callback */ -HTTPBase.prototype.put = function put(path, callback) { +HTTPBase.prototype.put = function put(path, callback, ctx) { if (Array.isArray(path)) { path.forEach(function(path) { - this.put(path, callback); + this.put(path, callback, ctx); }, this); return; } - this.routes.put.push({ path: path, callback: callback }); + this.routes.put.push({ ctx: ctx, path: path, callback: callback }); }; /** @@ -361,14 +361,14 @@ HTTPBase.prototype.put = function put(path, callback) { * @param {RouteCallback} callback */ -HTTPBase.prototype.del = function del(path, callback) { +HTTPBase.prototype.del = function del(path, callback, ctx) { if (Array.isArray(path)) { path.forEach(function(path) { - this.del(path, callback); + this.del(path, callback, ctx); }, this); return; } - this.routes.del.push({ path: path, callback: callback }); + this.routes.del.push({ ctx: ctx, path: path, callback: callback }); }; /** diff --git a/lib/http/rpc.js b/lib/http/rpc.js index 6de1ae30..85096441 100644 --- a/lib/http/rpc.js +++ b/lib/http/rpc.js @@ -1393,14 +1393,10 @@ RPC.prototype._getwork = co(function* _getwork() { }; }); -RPC.prototype.getworklp = function getworklp(args) { - var self = this; - return new Promise(function(resolve, reject) { - self.once('clear block', function() { - self._getwork().then(resolve).catch(reject); - }); - }); -}; +RPC.prototype.getworklp = co(function* getworklp(args) { + yield this._onBlock(); + return yield this._getwork(); +}); RPC.prototype.getwork = co(function* getwork(args) { var unlock = yield this.locker.lock(); @@ -1686,27 +1682,32 @@ RPC.prototype._tmpl = co(function* _tmpl(version, coinbase, rules) { return template; }); -RPC.prototype._poll = function _poll(lpid) { +RPC.prototype._poll = co(function* _poll(lpid) { var self = this; var watched, lastTX; if (typeof lpid !== 'string') - return Promise.resolve(null); + return null; if (lpid.length !== 74) - return Promise.reject(new RPCError('Invalid parameter.')); + throw new RPCError('Invalid parameter.'); watched = lpid.slice(0, 64); lastTX = +lpid.slice(64, 74); if (!utils.isHex(watched) || !utils.isNumber(lastTX)) - return Promise.reject(new RPCError('Invalid parameter.')); + throw new RPCError('Invalid parameter.'); watched = utils.revHex(watched); if (this.chain.tip.hash !== watched) - return Promise.resolve(null); + return null; + yield this._onBlock(); +}); + +RPC.prototype._onBlock = function _onBlock() { + var self = this; return new Promise(function(resolve, reject) { self.once('clear block', resolve); }); @@ -1855,42 +1856,39 @@ RPC.prototype.prioritisetransaction = function prioritisetransaction(args) { }; RPC.prototype._hashps = co(function* _hashps(lookup, height) { - var i, minTime, maxTime, pb0, time; - var workDiff, timeDiff, ps, pb, entry; + var i, minTime, maxTime, workDiff, timeDiff, ps, tip, entry; - pb = this.chain.tip; + tip = this.chain.tip; if (height >= 0 && height < this.chain.tip.height) - pb = yield this.chain.db.get(height); + tip = yield this.chain.db.get(height); - if (!pb) + if (!tip) return 0; if (lookup <= 0) - lookup = pb.height % this.network.pow.retargetInterval + 1; + lookup = tip.height % this.network.pow.retargetInterval + 1; - if (lookup > pb.height) - lookup = pb.height; + if (lookup > tip.height) + lookup = tip.height; - minTime = pb.ts; + minTime = tip.ts; maxTime = minTime; - pb0 = pb; + entry = tip; for (i = 0; i < lookup; i++) { - entry = yield pb0.getPrevious(); + entry = yield entry.getPrevious(); if (!entry) throw new RPCError('Not found.'); - pb0 = entry; - time = pb0.ts; - minTime = Math.min(time, minTime); - maxTime = Math.max(time, maxTime); + minTime = Math.min(entry.ts, minTime); + maxTime = Math.max(entry.ts, maxTime); } if (minTime === maxTime) return 0; - workDiff = pb.chainwork.sub(pb0.chainwork); + workDiff = tip.chainwork.sub(entry.chainwork); timeDiff = maxTime - minTime; ps = +workDiff.toString(10) / timeDiff; diff --git a/lib/http/server.js b/lib/http/server.js index efcfa9b7..e7158adb 100644 --- a/lib/http/server.js +++ b/lib/http/server.js @@ -17,6 +17,7 @@ var HTTPBase = http.base; var utils = require('../utils/utils'); var spawn = require('../utils/spawn'); var co = spawn.co; +var con = spawn.con; var crypto = require('../crypto/crypto'); var assert = utils.assert; var RPC; /*= require('./rpc'); - load lazily */ @@ -101,7 +102,7 @@ HTTPServer.prototype._init = function _init() { address.address, address.port); }); - this.use(function(req, res, next, send) { + this.use(function(req, res, send, next) { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Credentials', 'true'); res.setHeader( @@ -115,14 +116,14 @@ HTTPServer.prototype._init = function _init() { res.setHeader('X-Bcoin-Version', constants.USER_VERSION); res.setHeader('X-Bcoin-Agent', constants.USER_AGENT); - res.setHeader('X-Bcoin-Network', self.network.type); - res.setHeader('X-Bcoin-Height', self.chain.height + ''); - res.setHeader('X-Bcoin-Tip', utils.revHex(self.chain.tip.hash)); + res.setHeader('X-Bcoin-Network', this.network.type); + res.setHeader('X-Bcoin-Height', this.chain.height + ''); + res.setHeader('X-Bcoin-Tip', utils.revHex(this.chain.tip.hash)); next(); }); - this.use(function(req, res, next, send) { + this.use(function(req, res, send, next) { var auth = req.headers['authorization']; var parts; @@ -145,11 +146,11 @@ HTTPServer.prototype._init = function _init() { next(); }); - this.use(function(req, res, next, send) { - if (!self.apiHash) + this.use(function(req, res, send, next) { + if (!this.apiHash) return next(); - if (crypto.ccmp(hash256(req.password), self.apiHash)) + if (crypto.ccmp(hash256(req.password), this.apiHash)) return next(); res.setHeader('WWW-Authenticate', 'Basic realm="node"'); @@ -170,7 +171,7 @@ HTTPServer.prototype._init = function _init() { send(401, { error: 'Bad API key.' }); }); - this.use(function(req, res, next, send) { + this.use(function(req, res, send, next) { var i, params, options, output, address; if (req.method === 'POST' && req.pathname === '/') { @@ -187,8 +188,8 @@ HTTPServer.prototype._init = function _init() { softMerge(params, req.query, true); softMerge(params, req.body); - self.logger.debug('Params:'); - self.logger.debug(params); + this.logger.debug('Params:'); + this.logger.debug(params); if (params.id) { assert(typeof params.id === 'string', 'ID must be a string.'); @@ -374,37 +375,16 @@ HTTPServer.prototype._init = function _init() { next(); }); - this.use(function(req, res, next, send) { - spawn(function *() { - var wallet; + this.use(con(function *(req, res, send, next) { + var wallet; - if (req.path.length < 2 || req.path[0] !== 'wallet') { - next(); - return; - } + if (req.path.length < 2 || req.path[0] !== 'wallet') { + next(); + return; + } - if (!self.options.walletAuth) { - wallet = yield self.walletdb.get(req.options.id); - - if (!wallet) { - send(404); - return; - } - - req.wallet = wallet; - - next(); - return; - } - - try { - wallet = yield self.walletdb.auth(req.options.id, req.options.token); - } catch (err) { - self.logger.info('Auth failure for %s: %s.', - req.options.id, err.message); - send(403, { error: err.message }); - return; - } + if (!this.options.walletAuth) { + wallet = yield this.walletdb.get(req.options.id); if (!wallet) { send(404); @@ -412,458 +392,411 @@ HTTPServer.prototype._init = function _init() { } req.wallet = wallet; - self.logger.info('Successful auth for %s.', req.options.id); + next(); - }).catch(next); - }); + return; + } + + try { + wallet = yield this.walletdb.auth(req.options.id, req.options.token); + } catch (err) { + this.logger.info('Auth failure for %s: %s.', + req.options.id, err.message); + send(403, { error: err.message }); + return; + } + + if (!wallet) { + send(404); + return; + } + + req.wallet = wallet; + this.logger.info('Successful auth for %s.', req.options.id); + next(); + })); // JSON RPC - this.post('/', function(req, res, next, send) { - spawn(function *() { - var json; + this.post('/', con(function *(req, res, send, next) { + var json; - if (!self.rpc) { - RPC = require('./rpc'); - self.rpc = new RPC(self.node); - } + if (!this.rpc) { + RPC = require('./rpc'); + this.rpc = new RPC(this.node); + } - if (req.body.method === 'getwork') { - res.setHeader('X-Long-Polling', '/?longpoll=1'); - if (req.query.longpoll) - req.body.method = 'getworklp'; - } + if (req.body.method === 'getwork') { + res.setHeader('X-Long-Polling', '/?longpoll=1'); + if (req.query.longpoll) + req.body.method = 'getworklp'; + } - try { - json = yield self.rpc.execute(req.body); - } catch (err) { - self.logger.error(err); + try { + json = yield this.rpc.execute(req.body); + } catch (err) { + this.logger.error(err); - if (err.type === 'RPCError') { - return send(400, { - result: err.message, - error: null, - id: req.body.id - }); - } - - return send(500, { - result: null, - error: { - message: err.message, - code: 1 - }, + if (err.type === 'RPCError') { + return send(400, { + result: err.message, + error: null, id: req.body.id }); } - send(200, { - result: json != null ? json : null, - error: null, + return send(500, { + result: null, + error: { + message: err.message, + code: 1 + }, id: req.body.id }); - }).catch(next); - }); + } - this.get('/', function(req, res, next, send) { + send(200, { + result: json != null ? json : null, + error: null, + id: req.body.id + }); + })); + + this.get('/', function(req, res, send, next) { send(200, { version: constants.USER_VERSION, agent: constants.USER_AGENT, - services: self.pool.services, - network: self.network.type, - height: self.chain.height, - tip: self.chain.tip.rhash, - peers: self.pool.peers.all.length, - progress: self.chain.getProgress() + services: this.pool.services, + network: this.network.type, + height: this.chain.height, + tip: this.chain.tip.rhash, + peers: this.pool.peers.all.length, + progress: this.chain.getProgress() }); }); // UTXO by address - this.get('/coin/address/:address', function(req, res, next, send) { - spawn(function *() { - var coins = yield self.node.getCoinsByAddress(req.options.address); - send(200, coins.map(function(coin) { - return coin.toJSON(); - })); - }).catch(next); - }); + this.get('/coin/address/:address', con(function *(req, res, send, next) { + var coins = yield this.node.getCoinsByAddress(req.options.address); + send(200, coins.map(function(coin) { + return coin.toJSON(); + })); + })); // UTXO by id - this.get('/coin/:hash/:index', function(req, res, next, send) { - spawn(function *() { - var coin = yield self.node.getCoin(req.options.hash, req.options.index); + this.get('/coin/:hash/:index', con(function *(req, res, send, next) { + var coin = yield this.node.getCoin(req.options.hash, req.options.index); - if (!coin) - return send(404); + if (!coin) + return send(404); - send(200, coin.toJSON()); - }).catch(next); - }); + send(200, coin.toJSON()); + })); // Bulk read UTXOs - this.post('/coin/address', function(req, res, next, send) { - spawn(function *() { - var coins = yield self.node.getCoinsByAddress(req.options.address); - send(200, coins.map(function(coin) { - return coin.toJSON(); - })); - }).catch(next); - }); + this.post('/coin/address', con(function *(req, res, send, next) { + var coins = yield this.node.getCoinsByAddress(req.options.address); + send(200, coins.map(function(coin) { + return coin.toJSON(); + })); + })); // TX by hash - this.get('/tx/:hash', function(req, res, next, send) { - spawn(function *() { - var tx = yield self.node.getTX(req.options.hash); + this.get('/tx/:hash', con(function *(req, res, send, next) { + var tx = yield this.node.getTX(req.options.hash); - if (!tx) - return send(404); + if (!tx) + return send(404); - yield self.node.fillHistory(tx); + yield this.node.fillHistory(tx); - send(200, tx.toJSON()); - }).catch(next); - }); + send(200, tx.toJSON()); + })); // TX by address - this.get('/tx/address/:address', function(req, res, next, send) { - spawn(function *() { - var txs = yield self.node.getTXByAddress(req.options.address); - var i, tx; + this.get('/tx/address/:address', con(function *(req, res, send, next) { + var txs = yield this.node.getTXByAddress(req.options.address); + var i, tx; - for (i = 0; i < txs.length; i++) { - tx = txs[i]; - yield self.node.fillHistory(tx); - } + for (i = 0; i < txs.length; i++) { + tx = txs[i]; + yield this.node.fillHistory(tx); + } - send(200, txs.map(function(tx) { - return tx.toJSON(); - })); - }).catch(next); - }); + send(200, txs.map(function(tx) { + return tx.toJSON(); + })); + })); // Bulk read TXs - this.post('/tx/address', function(req, res, next, send) { - spawn(function *() { - var txs = yield self.node.getTXByAddress(req.options.address); - var i, tx; + this.post('/tx/address', con(function *(req, res, send, next) { + var txs = yield this.node.getTXByAddress(req.options.address); + var i, tx; - for (i = 0; i < txs.length; i++) { - tx = txs[i]; - yield self.node.fillHistory(tx); - } + for (i = 0; i < txs.length; i++) { + tx = txs[i]; + yield this.node.fillHistory(tx); + } - send(200, txs.map(function(tx) { - return tx.toJSON(); - })); - }).catch(next); - }); + send(200, txs.map(function(tx) { + return tx.toJSON(); + })); + })); // Block by hash/height - this.get('/block/:hash', function(req, res, next, send) { - spawn(function *() { - var hash = req.options.hash || req.options.height; - var block = yield self.node.getFullBlock(hash); + this.get('/block/:hash', con(function *(req, res, send, next) { + var hash = req.options.hash || req.options.height; + var block = yield this.node.getFullBlock(hash); - if (!block) - return send(404); + if (!block) + return send(404); - send(200, block.toJSON()); - }).catch(next); - }); + send(200, block.toJSON()); + })); // Mempool snapshot - this.get('/mempool', function(req, res, next, send) { - spawn(function *() { - var i, txs, tx; + this.get('/mempool', con(function *(req, res, send, next) { + var i, txs, tx; - if (!self.mempool) - return send(400, { error: 'No mempool available.' }); + if (!this.mempool) + return send(400, { error: 'No mempool available.' }); - txs = self.mempool.getHistory(); + txs = this.mempool.getHistory(); - for (i = 0; i < txs.length; i++) { - tx = txs[i]; - yield self.node.fillHistory(tx); - } + for (i = 0; i < txs.length; i++) { + tx = txs[i]; + yield this.node.fillHistory(tx); + } - send(200, txs.map(function(tx) { - return tx.toJSON(); - })); - }).catch(next); - }); + send(200, txs.map(function(tx) { + return tx.toJSON(); + })); + })); // Broadcast TX - this.post('/broadcast', function(req, res, next, send) { - spawn(function *() { - yield self.node.sendTX(req.options.tx); - send(200, { success: true }); - }).catch(next); - }); + this.post('/broadcast', con(function *(req, res, send, next) { + yield this.node.sendTX(req.options.tx); + send(200, { success: true }); + })); // Estimate fee - this.get('/fee', function(req, res, next, send) { + this.get('/fee', function(req, res, send, next) { var fee; - if (!self.fees) + if (!this.fees) return send(400, { error: 'Fee estimation not available.' }); - fee = self.fees.estimateFee(req.options.blocks); + fee = this.fees.estimateFee(req.options.blocks); send(200, { rate: utils.btc(fee) }); }); // Get wallet - this.get('/wallet/:id', function(req, res, next, send) { + this.get('/wallet/:id', function(req, res, send, next) { send(200, req.wallet.toJSON()); }); // Create wallet - this.post('/wallet/:id?', function(req, res, next, send) { - spawn(function *() { - var wallet = yield self.walletdb.create(req.options); - send(200, wallet.toJSON()); - }).catch(next); - }); + this.post('/wallet/:id?', con(function *(req, res, send, next) { + var wallet = yield this.walletdb.create(req.options); + send(200, wallet.toJSON()); + })); // List accounts - this.get('/wallet/:id/account', function(req, res, next, send) { - spawn(function *() { - var accounts = yield req.wallet.getAccounts(); - send(200, accounts); - }).catch(next); - }); + this.get('/wallet/:id/account', con(function *(req, res, send, next) { + var accounts = yield req.wallet.getAccounts(); + send(200, accounts); + })); // Get account - this.get('/wallet/:id/account/:account', function(req, res, next, send) { - spawn(function *() { - var account = yield req.wallet.getAccount(req.options.account); + this.get('/wallet/:id/account/:account', con(function *(req, res, send, next) { + var account = yield req.wallet.getAccount(req.options.account); - if (!account) - return send(404); + if (!account) + return send(404); - send(200, account.toJSON()); - }).catch(next); - }); + send(200, account.toJSON()); + })); // Create/get account - this.post('/wallet/:id/account/:account?', function(req, res, next, send) { - spawn(function *() { - var account = yield req.wallet.createAccount(req.options); + this.post('/wallet/:id/account/:account?', con(function *(req, res, send, next) { + var account = yield req.wallet.createAccount(req.options); - if (!account) - return send(404); + if (!account) + return send(404); - send(200, account.toJSON()); - }).catch(next); - }); + send(200, account.toJSON()); + })); // Change passphrase - this.post('/wallet/:id/passphrase', function(req, res, next, send) { - spawn(function *() { - var options = req.options; - var old = options.old; - var new_ = options.passphrase; - yield req.wallet.setPassphrase(old, new_); - send(200, { success: true }); - }).catch(next); - }); + this.post('/wallet/:id/passphrase', con(function *(req, res, send, next) { + var options = req.options; + var old = options.old; + var new_ = options.passphrase; + yield req.wallet.setPassphrase(old, new_); + send(200, { success: true }); + })); // Generate new token - this.post('/wallet/:id/retoken', function(req, res, next, send) { - spawn(function *() { - var options = req.options; - var token = yield req.wallet.retoken(options.passphrase); - send(200, { token: token.toString('hex') }); - }).catch(next); - }); + this.post('/wallet/:id/retoken', con(function *(req, res, send, next) { + var options = req.options; + var token = yield req.wallet.retoken(options.passphrase); + send(200, { token: token.toString('hex') }); + })); // Send TX - this.post('/wallet/:id/send', function(req, res, next, send) { - spawn(function *() { - var options = req.options; - var tx = yield req.wallet.send(options); - send(200, tx.toJSON()); - }).catch(next); - }); + this.post('/wallet/:id/send', con(function *(req, res, send, next) { + var options = req.options; + var tx = yield req.wallet.send(options); + send(200, tx.toJSON()); + })); // Create TX - this.post('/wallet/:id/create', function(req, res, next, send) { - spawn(function *() { - var options = req.options; - var tx = yield req.wallet.createTX(options); - yield req.wallet.sign(tx, options); - send(200, tx.toJSON()); - }).catch(next); - }); + this.post('/wallet/:id/create', con(function *(req, res, send, next) { + var options = req.options; + var tx = yield req.wallet.createTX(options); + yield req.wallet.sign(tx, options); + send(200, tx.toJSON()); + })); // Sign TX - this.post('/wallet/:id/sign', function(req, res, next, send) { - spawn(function *() { - var options = req.options; - var tx = req.options.tx; - yield req.wallet.sign(tx, options); - send(200, tx.toJSON()); - }).catch(next); - }); + this.post('/wallet/:id/sign', con(function *(req, res, send, next) { + var options = req.options; + var tx = req.options.tx; + yield req.wallet.sign(tx, options); + send(200, tx.toJSON()); + })); // Fill TX - this.post('/wallet/:id/fill', function(req, res, next, send) { - spawn(function *() { - var tx = req.options.tx; - yield req.wallet.fillHistory(tx); - send(200, tx.toJSON()); - }).catch(next); - }); + this.post('/wallet/:id/fill', con(function *(req, res, send, next) { + var tx = req.options.tx; + yield req.wallet.fillHistory(tx); + send(200, tx.toJSON()); + })); // Zap Wallet TXs - this.post('/wallet/:id/zap', function(req, res, next, send) { - spawn(function *() { - var account = req.options.account; - var age = req.options.age; - yield req.wallet.zap(account, age); - send(200, { success: true }); - }).catch(next); - }); + this.post('/wallet/:id/zap', con(function *(req, res, send, next) { + var account = req.options.account; + var age = req.options.age; + yield req.wallet.zap(account, age); + send(200, { success: true }); + })); // Abandon Wallet TX - this.del('/wallet/:id/tx/:hash', function(req, res, next, send) { - spawn(function *() { - var hash = req.options.hash; - yield req.wallet.abandon(hash); - send(200, { success: true }); - }).catch(next); - }); + this.del('/wallet/:id/tx/:hash', con(function *(req, res, send, next) { + var hash = req.options.hash; + yield req.wallet.abandon(hash); + send(200, { success: true }); + })); // Add key - this.put('/wallet/:id/key', function(req, res, next, send) { - spawn(function *() { - var account = req.options.account; - var key = req.options.key; - yield req.wallet.addKey(account, key); - send(200, { success: true }); - }).catch(next); - }); + this.put('/wallet/:id/key', con(function *(req, res, send, next) { + var account = req.options.account; + var key = req.options.key; + yield req.wallet.addKey(account, key); + send(200, { success: true }); + })); // Remove key - this.del('/wallet/:id/key', function(req, res, next, send) { - spawn(function *() { - var account = req.options.account; - var key = req.options.key; - yield req.wallet.removeKey(account, key); - send(200, { success: true }); - }).catch(next); - }); + this.del('/wallet/:id/key', con(function *(req, res, send, next) { + var account = req.options.account; + var key = req.options.key; + yield req.wallet.removeKey(account, key); + send(200, { success: true }); + })); // Create address - this.post('/wallet/:id/address', function(req, res, next, send) { - spawn(function *() { - var account = req.options.account; - var address = yield req.wallet.createReceive(account); - send(200, address.toJSON()); - }).catch(next); - }); + this.post('/wallet/:id/address', con(function *(req, res, send, next) { + var account = req.options.account; + var address = yield req.wallet.createReceive(account); + send(200, address.toJSON()); + })); // Wallet Balance - this.get('/wallet/:id/balance', function(req, res, next, send) { - spawn(function *() { - var account = req.options.account; - var balance = yield req.wallet.getBalance(account); + this.get('/wallet/:id/balance', con(function *(req, res, send, next) { + var account = req.options.account; + var balance = yield req.wallet.getBalance(account); - if (!balance) - return send(404); + if (!balance) + return send(404); - send(200, balance.toJSON()); - }).catch(next); - }); + send(200, balance.toJSON()); + })); // Wallet UTXOs - this.get('/wallet/:id/coin', function(req, res, next, send) { - spawn(function *() { - var account = req.options.account; - var coins = yield req.wallet.getCoins(account); - send(200, coins.map(function(coin) { - return coin.toJSON(); - })); - }).catch(next); - }); + this.get('/wallet/:id/coin', con(function *(req, res, send, next) { + var account = req.options.account; + var coins = yield req.wallet.getCoins(account); + send(200, coins.map(function(coin) { + return coin.toJSON(); + })); + })); // Wallet Coin - this.get('/wallet/:id/coin/:hash/:index', function(req, res, next, send) { - spawn(function *() { - var hash = req.options.hash; - var index = req.options.index; - var coin = yield req.wallet.getCoin(hash, index); + this.get('/wallet/:id/coin/:hash/:index', con(function *(req, res, send, next) { + var hash = req.options.hash; + var index = req.options.index; + var coin = yield req.wallet.getCoin(hash, index); - if (!coin) - return send(404); + if (!coin) + return send(404); - send(200, coin.toJSON()); - }).catch(next); - }); + send(200, coin.toJSON()); + })); // Wallet TXs - this.get('/wallet/:id/tx/history', function(req, res, next, send) { - spawn(function *() { - var account = req.options.account; - var txs = yield req.wallet.getHistory(account); - var details = yield req.wallet.toDetails(txs); - send(200, details.map(function(tx) { - return tx.toJSON(); - })); - }).catch(next); - }); + this.get('/wallet/:id/tx/history', con(function *(req, res, send, next) { + var account = req.options.account; + var txs = yield req.wallet.getHistory(account); + var details = yield req.wallet.toDetails(txs); + send(200, details.map(function(tx) { + return tx.toJSON(); + })); + })); // Wallet Pending TXs - this.get('/wallet/:id/tx/unconfirmed', function(req, res, next, send) { - spawn(function *() { - var account = req.options.account; - var txs = yield req.wallet.getUnconfirmed(account); - var details = yield req.wallet.toDetails(txs); - send(200, details.map(function(tx) { - return tx.toJSON(); - })); - }).catch(next); - }); + this.get('/wallet/:id/tx/unconfirmed', con(function *(req, res, send, next) { + var account = req.options.account; + var txs = yield req.wallet.getUnconfirmed(account); + var details = yield req.wallet.toDetails(txs); + send(200, details.map(function(tx) { + return tx.toJSON(); + })); + })); // Wallet TXs within time range - this.get('/wallet/:id/tx/range', function(req, res, next, send) { - spawn(function *() { - var account = req.options.account; - var options = req.options; - var txs = yield req.wallet.getRange(account, options); - var details = yield req.wallet.toDetails(txs); - send(200, details.map(function(tx) { - return tx.toJSON(); - })); - }).catch(next); - }); + this.get('/wallet/:id/tx/range', con(function *(req, res, send, next) { + var account = req.options.account; + var options = req.options; + var txs = yield req.wallet.getRange(account, options); + var details = yield req.wallet.toDetails(txs); + send(200, details.map(function(tx) { + return tx.toJSON(); + })); + })); // Last Wallet TXs - this.get('/wallet/:id/tx/last', function(req, res, next, send) { - spawn(function *() { - var account = req.options.account; - var limit = req.options.limit; - var txs = yield req.wallet.getLast(account, limit); - var details = yield req.wallet.toDetails(txs); - send(200, details.map(function(tx) { - return tx.toJSON(); - })); - }).catch(next); - }); + this.get('/wallet/:id/tx/last', con(function *(req, res, send, next) { + var account = req.options.account; + var limit = req.options.limit; + var txs = yield req.wallet.getLast(account, limit); + var details = yield req.wallet.toDetails(txs); + send(200, details.map(function(tx) { + return tx.toJSON(); + })); + })); // Wallet TX - this.get('/wallet/:id/tx/:hash', function(req, res, next, send) { - spawn(function *() { - var hash = req.options.hash; - var tx = yield req.wallet.getTX(hash); - var details; + this.get('/wallet/:id/tx/:hash', con(function *(req, res, send, next) { + var hash = req.options.hash; + var tx = yield req.wallet.getTX(hash); + var details; - if (!tx) - return send(404); + if (!tx) + return send(404); - details = yield req.wallet.toDetails(tx); - send(200, details.toJSON()); - }).catch(next); - }); + details = yield req.wallet.toDetails(tx); + send(200, details.toJSON()); + })); this.server.on('error', function(err) { self.emit('error', err); @@ -1090,7 +1023,7 @@ HTTPServer.prototype.close = function close() { */ HTTPServer.prototype.use = function use(path, callback) { - return this.server.use(path, callback); + return this.server.use(path, callback, this); }; /** @@ -1098,7 +1031,7 @@ HTTPServer.prototype.use = function use(path, callback) { */ HTTPServer.prototype.get = function get(path, callback) { - return this.server.get(path, callback); + return this.server.get(path, callback, this); }; /** @@ -1106,7 +1039,7 @@ HTTPServer.prototype.get = function get(path, callback) { */ HTTPServer.prototype.post = function post(path, callback) { - return this.server.post(path, callback); + return this.server.post(path, callback, this); }; /** @@ -1114,7 +1047,7 @@ HTTPServer.prototype.post = function post(path, callback) { */ HTTPServer.prototype.put = function put(path, callback) { - return this.server.put(path, callback); + return this.server.put(path, callback, this); }; /** @@ -1122,7 +1055,7 @@ HTTPServer.prototype.put = function put(path, callback) { */ HTTPServer.prototype.del = function del(path, callback) { - return this.server.del(path, callback); + return this.server.del(path, callback, this); }; /** @@ -1337,14 +1270,14 @@ ClientSocket.prototype.scan = function scan(start) { if (this.chain.db.options.prune) return Promise.reject(new Error('Cannot scan in pruned mode.')); - return this.chain.db.scan(start, this.filter, function *(entry, txs) { + return this.chain.db.scan(start, this.filter, co(function *(entry, txs) { for (i = 0; i < txs.length; i++) txs[i] = txs[i].toJSON(); self.emit('block tx', entry.toJSON(), txs); yield utils.wait(); - }); + })); }; ClientSocket.prototype.join = function join(id) { diff --git a/lib/miner/miner.js b/lib/miner/miner.js index a2a90fca..e07ea457 100644 --- a/lib/miner/miner.js +++ b/lib/miner/miner.js @@ -253,36 +253,36 @@ Miner.prototype.createBlock = co(function* createBlock(tip) { // Find target target = yield this.chain.getTargetAsync(ts, tip); - if (this.version != null) { - version = this.version; - } else { + if (this.version != null) { + version = this.version; + } else { // Calculate version with versionbits - version = yield this.chain.computeBlockVersion(tip); + version = yield this.chain.computeBlockVersion(tip); } - attempt = new MinerBlock({ - workerPool: this.workerPool, - tip: tip, - version: version, - target: target, - address: this.address, - coinbaseFlags: this.coinbaseFlags, - witness: this.chain.segwitActive, - parallel: this.options.parallel, - network: this.network - }); + attempt = new MinerBlock({ + workerPool: this.workerPool, + tip: tip, + version: version, + target: target, + address: this.address, + coinbaseFlags: this.coinbaseFlags, + witness: this.chain.segwitActive, + parallel: this.options.parallel, + network: this.network + }); - if (!this.mempool) - return attempt; + if (!this.mempool) + return attempt; - txs = this.mempool.getHistory(); + txs = this.mempool.getHistory(); - for (i = 0; i < txs.length; i++) { - tx = txs[i]; - attempt.addTX(tx); - } + for (i = 0; i < txs.length; i++) { + tx = txs[i]; + attempt.addTX(tx); + } - return attempt; + return attempt; }); /** @@ -292,8 +292,8 @@ Miner.prototype.createBlock = co(function* createBlock(tip) { */ Miner.prototype.mineBlock = co(function* mineBlock(tip) { - // Create a new block and start hashing - var attempt = yield this.createBlock(tip); + // Create a new block and start hashing + var attempt = yield this.createBlock(tip); return yield attempt.mineAsync(); }); diff --git a/lib/utils/spawn.js b/lib/utils/spawn.js index 88ee9424..0a1d23ba 100644 --- a/lib/utils/spawn.js +++ b/lib/utils/spawn.js @@ -102,7 +102,47 @@ function cob(generator) { gen = generator.apply(this, args); - return cb(exec(gen), callback); + return cb(exec(gen), function(err, result) { + // Escape the promise's scope: + utils.nextTick(function() { + callback(err, result); + }); + }); + }; +} + +/** + * 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('Function must accept 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: + utils.nextTick(function() { + callback(err); + }); + }); }; } @@ -223,6 +263,7 @@ exports.exec = exec; exports.spawn = spawn; exports.co = co; exports.cob = cob; +exports.con = con; exports.cb = cb; exports.wait = wait; exports.timeout = timeout; diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js index 5e2feba7..6b48a496 100644 --- a/lib/wallet/walletdb.js +++ b/lib/wallet/walletdb.js @@ -975,9 +975,9 @@ WalletDB.prototype.rescan = co(function* rescan(chaindb, height) { this.logger.info('Scanning for %d addresses.', hashes.length); try { - yield chaindb.scan(height, hashes, function *(block, txs) { + yield chaindb.scan(height, hashes, co(function *(block, txs) { yield self.addBlock(block, txs, true); - }); + })); } catch (e) { unlock(); throw e; diff --git a/test/chain-test.js b/test/chain-test.js index 6f9cb1d6..3d8e72bd 100644 --- a/test/chain-test.js +++ b/test/chain-test.js @@ -8,6 +8,7 @@ var crypto = require('../lib/crypto/crypto'); var assert = require('assert'); var opcodes = constants.opcodes; var spawn = require('../lib/utils/spawn'); +var co = require('../lib/utils/spawn').co; var c = require('../lib/utils/spawn').cb; describe('Chain', function() { @@ -234,10 +235,10 @@ describe('Chain', function() { var total = 0; c(walletdb.getAddressHashes(), function(err, hashes) { assert.ifError(err); - c(chain.db.scan(null, hashes, function *(block, txs) { + c(chain.db.scan(null, hashes, co(function *(block, txs) { total += txs.length; yield spawn.wait(); - }), function(err) { + })), function(err) { assert.ifError(err); assert.equal(total, 25); cb();