generators: refactor http.

This commit is contained in:
Christopher Jeffrey 2016-09-22 01:29:48 -07:00
parent ae83aa6fba
commit 8c11a2aa3f
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
11 changed files with 452 additions and 463 deletions

13
bin/cli
View File

@ -8,6 +8,7 @@ var spawn = require('../lib/utils/spawn');
var Client = require('../lib/http/client'); var Client = require('../lib/http/client');
var Wallet = require('../lib/http/wallet'); var Wallet = require('../lib/http/wallet');
var assert = utils.assert; var assert = utils.assert;
var main;
function CLI() { function CLI() {
this.config = config({ this.config = config({
@ -444,13 +445,11 @@ CLI.prototype.destroy = function destroy() {
return Promise.resolve(null); return Promise.resolve(null);
}; };
function main() { main = co(function* main() {
return spawn(function *() { var cli = new CLI();
var cli = new CLI(); yield cli.open();
yield cli.open(); yield cli.destroy();
yield cli.destroy(); });
}, this);
}
main().then(process.exit).catch(function(err) { main().then(process.exit).catch(function(err) {
console.error(err.stack + ''); console.error(err.stack + '');

View File

@ -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() { node.open().then(function() {
if (process.argv.indexOf('--test') !== -1) { if (process.argv.indexOf('--test') !== -1) {
node.pool.watchAddress('1VayNert3x1KzbpzMGt2qdqrAThiRovi8'); node.pool.watchAddress('1VayNert3x1KzbpzMGt2qdqrAThiRovi8');

View File

@ -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(); entry = yield entry.getNext();
} }

View File

@ -126,6 +126,7 @@ function Environment() {
this.require('bloom', './utils/bloom'); this.require('bloom', './utils/bloom');
this.require('uri', './utils/uri'); this.require('uri', './utils/uri');
this.require('errors', './utils/errors'); this.require('errors', './utils/errors');
this.require('spawn', './utils/spawn');
// Crypto // Crypto
this.require('ec', './crypto/ec'); this.require('ec', './crypto/ec');
@ -207,6 +208,7 @@ function Environment() {
// HTTP // HTTP
this.require('http', './http'); this.require('http', './http');
this.require('rpc', './http/rpc');
// Workers // Workers
this.require('workers', './workers/workers'); this.require('workers', './workers/workers');
@ -343,6 +345,7 @@ Environment.prototype.cache = function cache() {
require('./utils/bloom'); require('./utils/bloom');
require('./utils/uri'); require('./utils/uri');
require('./utils/errors'); require('./utils/errors');
require('./utils/spawn');
require('./crypto/ec'); require('./crypto/ec');
require('./crypto/crypto'); require('./crypto/crypto');
require('./crypto/chachapoly'); require('./crypto/chachapoly');
@ -399,6 +402,7 @@ Environment.prototype.cache = function cache() {
require('./wallet/walletdb'); require('./wallet/walletdb');
require('./wallet/path'); require('./wallet/path');
require('./http'); require('./http');
require('./http/rpc');
require('./workers/workers'); require('./workers/workers');
require('./bip70/bip70'); require('./bip70/bip70');
}; };

View File

@ -162,7 +162,7 @@ HTTPBase.prototype._initRouter = function _initRouter() {
// Avoid stack overflows // Avoid stack overflows
utils.nextTick(function() { utils.nextTick(function() {
try { try {
callback(req, res, next, _send); callback.call(route.ctx, req, res, _send, next);
} catch (e) { } catch (e) {
done(e); done(e);
} }
@ -264,11 +264,11 @@ HTTPBase.prototype._handle = function _handle(req, res, _send, callback) {
handler = self.stack[i++]; handler = self.stack[i++];
utils.nextTick(function() { utils.nextTick(function() {
if (handler.path && req.pathname.indexOf(handler.path) === -1) if (handler.path && req.pathname.indexOf(handler.path) !== 0)
return next(); return next();
try { try {
handler.callback(req, res, next, _send); handler.callback.call(handler.ctx, req, res, _send, next);
} catch (e) { } catch (e) {
next(e); next(e);
} }
@ -291,7 +291,7 @@ HTTPBase.prototype._handle = function _handle(req, res, _send, callback) {
* @param {RouteCallback} callback * @param {RouteCallback} callback
*/ */
HTTPBase.prototype.use = function use(path, callback) { HTTPBase.prototype.use = function use(path, callback, ctx) {
if (!callback) { if (!callback) {
callback = path; callback = path;
path = null; path = null;
@ -299,12 +299,12 @@ HTTPBase.prototype.use = function use(path, callback) {
if (Array.isArray(path)) { if (Array.isArray(path)) {
path.forEach(function(path) { path.forEach(function(path) {
this.use(path, callback); this.use(path, callback, ctx);
}, this); }, this);
return; 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 * @param {RouteCallback} callback
*/ */
HTTPBase.prototype.get = function get(path, callback) { HTTPBase.prototype.get = function get(path, callback, ctx) {
if (Array.isArray(path)) { if (Array.isArray(path)) {
path.forEach(function(path) { path.forEach(function(path) {
this.get(path, callback); this.get(path, callback, ctx);
}, this); }, this);
return; 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 * @param {RouteCallback} callback
*/ */
HTTPBase.prototype.post = function post(path, callback) { HTTPBase.prototype.post = function post(path, callback, ctx) {
if (Array.isArray(path)) { if (Array.isArray(path)) {
path.forEach(function(path) { path.forEach(function(path) {
this.post(path, callback); this.post(path, callback, ctx);
}, this); }, this);
return; 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 * @param {RouteCallback} callback
*/ */
HTTPBase.prototype.put = function put(path, callback) { HTTPBase.prototype.put = function put(path, callback, ctx) {
if (Array.isArray(path)) { if (Array.isArray(path)) {
path.forEach(function(path) { path.forEach(function(path) {
this.put(path, callback); this.put(path, callback, ctx);
}, this); }, this);
return; 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 * @param {RouteCallback} callback
*/ */
HTTPBase.prototype.del = function del(path, callback) { HTTPBase.prototype.del = function del(path, callback, ctx) {
if (Array.isArray(path)) { if (Array.isArray(path)) {
path.forEach(function(path) { path.forEach(function(path) {
this.del(path, callback); this.del(path, callback, ctx);
}, this); }, this);
return; return;
} }
this.routes.del.push({ path: path, callback: callback }); this.routes.del.push({ ctx: ctx, path: path, callback: callback });
}; };
/** /**

View File

@ -1393,14 +1393,10 @@ RPC.prototype._getwork = co(function* _getwork() {
}; };
}); });
RPC.prototype.getworklp = function getworklp(args) { RPC.prototype.getworklp = co(function* getworklp(args) {
var self = this; yield this._onBlock();
return new Promise(function(resolve, reject) { return yield this._getwork();
self.once('clear block', function() { });
self._getwork().then(resolve).catch(reject);
});
});
};
RPC.prototype.getwork = co(function* getwork(args) { RPC.prototype.getwork = co(function* getwork(args) {
var unlock = yield this.locker.lock(); var unlock = yield this.locker.lock();
@ -1686,27 +1682,32 @@ RPC.prototype._tmpl = co(function* _tmpl(version, coinbase, rules) {
return template; return template;
}); });
RPC.prototype._poll = function _poll(lpid) { RPC.prototype._poll = co(function* _poll(lpid) {
var self = this; var self = this;
var watched, lastTX; var watched, lastTX;
if (typeof lpid !== 'string') if (typeof lpid !== 'string')
return Promise.resolve(null); return null;
if (lpid.length !== 74) if (lpid.length !== 74)
return Promise.reject(new RPCError('Invalid parameter.')); throw new RPCError('Invalid parameter.');
watched = lpid.slice(0, 64); watched = lpid.slice(0, 64);
lastTX = +lpid.slice(64, 74); lastTX = +lpid.slice(64, 74);
if (!utils.isHex(watched) || !utils.isNumber(lastTX)) if (!utils.isHex(watched) || !utils.isNumber(lastTX))
return Promise.reject(new RPCError('Invalid parameter.')); throw new RPCError('Invalid parameter.');
watched = utils.revHex(watched); watched = utils.revHex(watched);
if (this.chain.tip.hash !== 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) { return new Promise(function(resolve, reject) {
self.once('clear block', resolve); self.once('clear block', resolve);
}); });
@ -1855,42 +1856,39 @@ RPC.prototype.prioritisetransaction = function prioritisetransaction(args) {
}; };
RPC.prototype._hashps = co(function* _hashps(lookup, height) { RPC.prototype._hashps = co(function* _hashps(lookup, height) {
var i, minTime, maxTime, pb0, time; var i, minTime, maxTime, workDiff, timeDiff, ps, tip, entry;
var workDiff, timeDiff, ps, pb, entry;
pb = this.chain.tip; tip = this.chain.tip;
if (height >= 0 && height < this.chain.tip.height) 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; return 0;
if (lookup <= 0) if (lookup <= 0)
lookup = pb.height % this.network.pow.retargetInterval + 1; lookup = tip.height % this.network.pow.retargetInterval + 1;
if (lookup > pb.height) if (lookup > tip.height)
lookup = pb.height; lookup = tip.height;
minTime = pb.ts; minTime = tip.ts;
maxTime = minTime; maxTime = minTime;
pb0 = pb; entry = tip;
for (i = 0; i < lookup; i++) { for (i = 0; i < lookup; i++) {
entry = yield pb0.getPrevious(); entry = yield entry.getPrevious();
if (!entry) if (!entry)
throw new RPCError('Not found.'); throw new RPCError('Not found.');
pb0 = entry; minTime = Math.min(entry.ts, minTime);
time = pb0.ts; maxTime = Math.max(entry.ts, maxTime);
minTime = Math.min(time, minTime);
maxTime = Math.max(time, maxTime);
} }
if (minTime === maxTime) if (minTime === maxTime)
return 0; return 0;
workDiff = pb.chainwork.sub(pb0.chainwork); workDiff = tip.chainwork.sub(entry.chainwork);
timeDiff = maxTime - minTime; timeDiff = maxTime - minTime;
ps = +workDiff.toString(10) / timeDiff; ps = +workDiff.toString(10) / timeDiff;

View File

@ -17,6 +17,7 @@ var HTTPBase = http.base;
var utils = require('../utils/utils'); var utils = require('../utils/utils');
var spawn = require('../utils/spawn'); var spawn = require('../utils/spawn');
var co = spawn.co; var co = spawn.co;
var con = spawn.con;
var crypto = require('../crypto/crypto'); var crypto = require('../crypto/crypto');
var assert = utils.assert; var assert = utils.assert;
var RPC; /*= require('./rpc'); - load lazily */ var RPC; /*= require('./rpc'); - load lazily */
@ -101,7 +102,7 @@ HTTPServer.prototype._init = function _init() {
address.address, address.port); 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-Origin', '*');
res.setHeader('Access-Control-Allow-Credentials', 'true'); res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader( res.setHeader(
@ -115,14 +116,14 @@ HTTPServer.prototype._init = function _init() {
res.setHeader('X-Bcoin-Version', constants.USER_VERSION); res.setHeader('X-Bcoin-Version', constants.USER_VERSION);
res.setHeader('X-Bcoin-Agent', constants.USER_AGENT); res.setHeader('X-Bcoin-Agent', constants.USER_AGENT);
res.setHeader('X-Bcoin-Network', self.network.type); res.setHeader('X-Bcoin-Network', this.network.type);
res.setHeader('X-Bcoin-Height', self.chain.height + ''); res.setHeader('X-Bcoin-Height', this.chain.height + '');
res.setHeader('X-Bcoin-Tip', utils.revHex(self.chain.tip.hash)); res.setHeader('X-Bcoin-Tip', utils.revHex(this.chain.tip.hash));
next(); next();
}); });
this.use(function(req, res, next, send) { this.use(function(req, res, send, next) {
var auth = req.headers['authorization']; var auth = req.headers['authorization'];
var parts; var parts;
@ -145,11 +146,11 @@ HTTPServer.prototype._init = function _init() {
next(); next();
}); });
this.use(function(req, res, next, send) { this.use(function(req, res, send, next) {
if (!self.apiHash) if (!this.apiHash)
return next(); return next();
if (crypto.ccmp(hash256(req.password), self.apiHash)) if (crypto.ccmp(hash256(req.password), this.apiHash))
return next(); return next();
res.setHeader('WWW-Authenticate', 'Basic realm="node"'); res.setHeader('WWW-Authenticate', 'Basic realm="node"');
@ -170,7 +171,7 @@ HTTPServer.prototype._init = function _init() {
send(401, { error: 'Bad API key.' }); 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; var i, params, options, output, address;
if (req.method === 'POST' && req.pathname === '/') { if (req.method === 'POST' && req.pathname === '/') {
@ -187,8 +188,8 @@ HTTPServer.prototype._init = function _init() {
softMerge(params, req.query, true); softMerge(params, req.query, true);
softMerge(params, req.body); softMerge(params, req.body);
self.logger.debug('Params:'); this.logger.debug('Params:');
self.logger.debug(params); this.logger.debug(params);
if (params.id) { if (params.id) {
assert(typeof params.id === 'string', 'ID must be a string.'); assert(typeof params.id === 'string', 'ID must be a string.');
@ -374,37 +375,16 @@ HTTPServer.prototype._init = function _init() {
next(); next();
}); });
this.use(function(req, res, next, send) { this.use(con(function *(req, res, send, next) {
spawn(function *() { var wallet;
var wallet;
if (req.path.length < 2 || req.path[0] !== 'wallet') { if (req.path.length < 2 || req.path[0] !== 'wallet') {
next(); next();
return; return;
} }
if (!self.options.walletAuth) { if (!this.options.walletAuth) {
wallet = yield self.walletdb.get(req.options.id); wallet = yield this.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 (!wallet) { if (!wallet) {
send(404); send(404);
@ -412,458 +392,411 @@ HTTPServer.prototype._init = function _init() {
} }
req.wallet = wallet; req.wallet = wallet;
self.logger.info('Successful auth for %s.', req.options.id);
next(); 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 // JSON RPC
this.post('/', function(req, res, next, send) { this.post('/', con(function *(req, res, send, next) {
spawn(function *() { var json;
var json;
if (!self.rpc) { if (!this.rpc) {
RPC = require('./rpc'); RPC = require('./rpc');
self.rpc = new RPC(self.node); this.rpc = new RPC(this.node);
} }
if (req.body.method === 'getwork') { if (req.body.method === 'getwork') {
res.setHeader('X-Long-Polling', '/?longpoll=1'); res.setHeader('X-Long-Polling', '/?longpoll=1');
if (req.query.longpoll) if (req.query.longpoll)
req.body.method = 'getworklp'; req.body.method = 'getworklp';
} }
try { try {
json = yield self.rpc.execute(req.body); json = yield this.rpc.execute(req.body);
} catch (err) { } catch (err) {
self.logger.error(err); this.logger.error(err);
if (err.type === 'RPCError') { if (err.type === 'RPCError') {
return send(400, { return send(400, {
result: err.message, result: err.message,
error: null, error: null,
id: req.body.id
});
}
return send(500, {
result: null,
error: {
message: err.message,
code: 1
},
id: req.body.id id: req.body.id
}); });
} }
send(200, { return send(500, {
result: json != null ? json : null, result: null,
error: null, error: {
message: err.message,
code: 1
},
id: req.body.id 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, { send(200, {
version: constants.USER_VERSION, version: constants.USER_VERSION,
agent: constants.USER_AGENT, agent: constants.USER_AGENT,
services: self.pool.services, services: this.pool.services,
network: self.network.type, network: this.network.type,
height: self.chain.height, height: this.chain.height,
tip: self.chain.tip.rhash, tip: this.chain.tip.rhash,
peers: self.pool.peers.all.length, peers: this.pool.peers.all.length,
progress: self.chain.getProgress() progress: this.chain.getProgress()
}); });
}); });
// UTXO by address // UTXO by address
this.get('/coin/address/:address', function(req, res, next, send) { this.get('/coin/address/:address', con(function *(req, res, send, next) {
spawn(function *() { var coins = yield this.node.getCoinsByAddress(req.options.address);
var coins = yield self.node.getCoinsByAddress(req.options.address); send(200, coins.map(function(coin) {
send(200, coins.map(function(coin) { return coin.toJSON();
return coin.toJSON(); }));
})); }));
}).catch(next);
});
// UTXO by id // UTXO by id
this.get('/coin/:hash/:index', function(req, res, next, send) { this.get('/coin/:hash/:index', con(function *(req, res, send, next) {
spawn(function *() { var coin = yield this.node.getCoin(req.options.hash, req.options.index);
var coin = yield self.node.getCoin(req.options.hash, req.options.index);
if (!coin) if (!coin)
return send(404); return send(404);
send(200, coin.toJSON()); send(200, coin.toJSON());
}).catch(next); }));
});
// Bulk read UTXOs // Bulk read UTXOs
this.post('/coin/address', function(req, res, next, send) { this.post('/coin/address', con(function *(req, res, send, next) {
spawn(function *() { var coins = yield this.node.getCoinsByAddress(req.options.address);
var coins = yield self.node.getCoinsByAddress(req.options.address); send(200, coins.map(function(coin) {
send(200, coins.map(function(coin) { return coin.toJSON();
return coin.toJSON(); }));
})); }));
}).catch(next);
});
// TX by hash // TX by hash
this.get('/tx/:hash', function(req, res, next, send) { this.get('/tx/:hash', con(function *(req, res, send, next) {
spawn(function *() { var tx = yield this.node.getTX(req.options.hash);
var tx = yield self.node.getTX(req.options.hash);
if (!tx) if (!tx)
return send(404); return send(404);
yield self.node.fillHistory(tx); yield this.node.fillHistory(tx);
send(200, tx.toJSON()); send(200, tx.toJSON());
}).catch(next); }));
});
// TX by address // TX by address
this.get('/tx/address/:address', function(req, res, next, send) { this.get('/tx/address/:address', con(function *(req, res, send, next) {
spawn(function *() { var txs = yield this.node.getTXByAddress(req.options.address);
var txs = yield self.node.getTXByAddress(req.options.address); var i, tx;
var i, tx;
for (i = 0; i < txs.length; i++) { for (i = 0; i < txs.length; i++) {
tx = txs[i]; tx = txs[i];
yield self.node.fillHistory(tx); yield this.node.fillHistory(tx);
} }
send(200, txs.map(function(tx) { send(200, txs.map(function(tx) {
return tx.toJSON(); return tx.toJSON();
})); }));
}).catch(next); }));
});
// Bulk read TXs // Bulk read TXs
this.post('/tx/address', function(req, res, next, send) { this.post('/tx/address', con(function *(req, res, send, next) {
spawn(function *() { var txs = yield this.node.getTXByAddress(req.options.address);
var txs = yield self.node.getTXByAddress(req.options.address); var i, tx;
var i, tx;
for (i = 0; i < txs.length; i++) { for (i = 0; i < txs.length; i++) {
tx = txs[i]; tx = txs[i];
yield self.node.fillHistory(tx); yield this.node.fillHistory(tx);
} }
send(200, txs.map(function(tx) { send(200, txs.map(function(tx) {
return tx.toJSON(); return tx.toJSON();
})); }));
}).catch(next); }));
});
// Block by hash/height // Block by hash/height
this.get('/block/:hash', function(req, res, next, send) { this.get('/block/:hash', con(function *(req, res, send, next) {
spawn(function *() { var hash = req.options.hash || req.options.height;
var hash = req.options.hash || req.options.height; var block = yield this.node.getFullBlock(hash);
var block = yield self.node.getFullBlock(hash);
if (!block) if (!block)
return send(404); return send(404);
send(200, block.toJSON()); send(200, block.toJSON());
}).catch(next); }));
});
// Mempool snapshot // Mempool snapshot
this.get('/mempool', function(req, res, next, send) { this.get('/mempool', con(function *(req, res, send, next) {
spawn(function *() { var i, txs, tx;
var i, txs, tx;
if (!self.mempool) if (!this.mempool)
return send(400, { error: 'No mempool available.' }); return send(400, { error: 'No mempool available.' });
txs = self.mempool.getHistory(); txs = this.mempool.getHistory();
for (i = 0; i < txs.length; i++) { for (i = 0; i < txs.length; i++) {
tx = txs[i]; tx = txs[i];
yield self.node.fillHistory(tx); yield this.node.fillHistory(tx);
} }
send(200, txs.map(function(tx) { send(200, txs.map(function(tx) {
return tx.toJSON(); return tx.toJSON();
})); }));
}).catch(next); }));
});
// Broadcast TX // Broadcast TX
this.post('/broadcast', function(req, res, next, send) { this.post('/broadcast', con(function *(req, res, send, next) {
spawn(function *() { yield this.node.sendTX(req.options.tx);
yield self.node.sendTX(req.options.tx); send(200, { success: true });
send(200, { success: true }); }));
}).catch(next);
});
// Estimate fee // Estimate fee
this.get('/fee', function(req, res, next, send) { this.get('/fee', function(req, res, send, next) {
var fee; var fee;
if (!self.fees) if (!this.fees)
return send(400, { error: 'Fee estimation not available.' }); 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) }); send(200, { rate: utils.btc(fee) });
}); });
// Get wallet // 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()); send(200, req.wallet.toJSON());
}); });
// Create wallet // Create wallet
this.post('/wallet/:id?', function(req, res, next, send) { this.post('/wallet/:id?', con(function *(req, res, send, next) {
spawn(function *() { var wallet = yield this.walletdb.create(req.options);
var wallet = yield self.walletdb.create(req.options); send(200, wallet.toJSON());
send(200, wallet.toJSON()); }));
}).catch(next);
});
// List accounts // List accounts
this.get('/wallet/:id/account', function(req, res, next, send) { this.get('/wallet/:id/account', con(function *(req, res, send, next) {
spawn(function *() { var accounts = yield req.wallet.getAccounts();
var accounts = yield req.wallet.getAccounts(); send(200, accounts);
send(200, accounts); }));
}).catch(next);
});
// Get account // Get account
this.get('/wallet/:id/account/:account', function(req, res, next, send) { this.get('/wallet/:id/account/:account', con(function *(req, res, send, next) {
spawn(function *() { var account = yield req.wallet.getAccount(req.options.account);
var account = yield req.wallet.getAccount(req.options.account);
if (!account) if (!account)
return send(404); return send(404);
send(200, account.toJSON()); send(200, account.toJSON());
}).catch(next); }));
});
// Create/get account // Create/get account
this.post('/wallet/:id/account/:account?', function(req, res, next, send) { this.post('/wallet/:id/account/:account?', con(function *(req, res, send, next) {
spawn(function *() { var account = yield req.wallet.createAccount(req.options);
var account = yield req.wallet.createAccount(req.options);
if (!account) if (!account)
return send(404); return send(404);
send(200, account.toJSON()); send(200, account.toJSON());
}).catch(next); }));
});
// Change passphrase // Change passphrase
this.post('/wallet/:id/passphrase', function(req, res, next, send) { this.post('/wallet/:id/passphrase', con(function *(req, res, send, next) {
spawn(function *() { var options = req.options;
var options = req.options; var old = options.old;
var old = options.old; var new_ = options.passphrase;
var new_ = options.passphrase; yield req.wallet.setPassphrase(old, new_);
yield req.wallet.setPassphrase(old, new_); send(200, { success: true });
send(200, { success: true }); }));
}).catch(next);
});
// Generate new token // Generate new token
this.post('/wallet/:id/retoken', function(req, res, next, send) { this.post('/wallet/:id/retoken', con(function *(req, res, send, next) {
spawn(function *() { var options = req.options;
var options = req.options; var token = yield req.wallet.retoken(options.passphrase);
var token = yield req.wallet.retoken(options.passphrase); send(200, { token: token.toString('hex') });
send(200, { token: token.toString('hex') }); }));
}).catch(next);
});
// Send TX // Send TX
this.post('/wallet/:id/send', function(req, res, next, send) { this.post('/wallet/:id/send', con(function *(req, res, send, next) {
spawn(function *() { var options = req.options;
var options = req.options; var tx = yield req.wallet.send(options);
var tx = yield req.wallet.send(options); send(200, tx.toJSON());
send(200, tx.toJSON()); }));
}).catch(next);
});
// Create TX // Create TX
this.post('/wallet/:id/create', function(req, res, next, send) { this.post('/wallet/:id/create', con(function *(req, res, send, next) {
spawn(function *() { var options = req.options;
var options = req.options; var tx = yield req.wallet.createTX(options);
var tx = yield req.wallet.createTX(options); yield req.wallet.sign(tx, options);
yield req.wallet.sign(tx, options); send(200, tx.toJSON());
send(200, tx.toJSON()); }));
}).catch(next);
});
// Sign TX // Sign TX
this.post('/wallet/:id/sign', function(req, res, next, send) { this.post('/wallet/:id/sign', con(function *(req, res, send, next) {
spawn(function *() { var options = req.options;
var options = req.options; var tx = req.options.tx;
var tx = req.options.tx; yield req.wallet.sign(tx, options);
yield req.wallet.sign(tx, options); send(200, tx.toJSON());
send(200, tx.toJSON()); }));
}).catch(next);
});
// Fill TX // Fill TX
this.post('/wallet/:id/fill', function(req, res, next, send) { this.post('/wallet/:id/fill', con(function *(req, res, send, next) {
spawn(function *() { var tx = req.options.tx;
var tx = req.options.tx; yield req.wallet.fillHistory(tx);
yield req.wallet.fillHistory(tx); send(200, tx.toJSON());
send(200, tx.toJSON()); }));
}).catch(next);
});
// Zap Wallet TXs // Zap Wallet TXs
this.post('/wallet/:id/zap', function(req, res, next, send) { this.post('/wallet/:id/zap', con(function *(req, res, send, next) {
spawn(function *() { var account = req.options.account;
var account = req.options.account; var age = req.options.age;
var age = req.options.age; yield req.wallet.zap(account, age);
yield req.wallet.zap(account, age); send(200, { success: true });
send(200, { success: true }); }));
}).catch(next);
});
// Abandon Wallet TX // Abandon Wallet TX
this.del('/wallet/:id/tx/:hash', function(req, res, next, send) { this.del('/wallet/:id/tx/:hash', con(function *(req, res, send, next) {
spawn(function *() { var hash = req.options.hash;
var hash = req.options.hash; yield req.wallet.abandon(hash);
yield req.wallet.abandon(hash); send(200, { success: true });
send(200, { success: true }); }));
}).catch(next);
});
// Add key // Add key
this.put('/wallet/:id/key', function(req, res, next, send) { this.put('/wallet/:id/key', con(function *(req, res, send, next) {
spawn(function *() { var account = req.options.account;
var account = req.options.account; var key = req.options.key;
var key = req.options.key; yield req.wallet.addKey(account, key);
yield req.wallet.addKey(account, key); send(200, { success: true });
send(200, { success: true }); }));
}).catch(next);
});
// Remove key // Remove key
this.del('/wallet/:id/key', function(req, res, next, send) { this.del('/wallet/:id/key', con(function *(req, res, send, next) {
spawn(function *() { var account = req.options.account;
var account = req.options.account; var key = req.options.key;
var key = req.options.key; yield req.wallet.removeKey(account, key);
yield req.wallet.removeKey(account, key); send(200, { success: true });
send(200, { success: true }); }));
}).catch(next);
});
// Create address // Create address
this.post('/wallet/:id/address', function(req, res, next, send) { this.post('/wallet/:id/address', con(function *(req, res, send, next) {
spawn(function *() { var account = req.options.account;
var account = req.options.account; var address = yield req.wallet.createReceive(account);
var address = yield req.wallet.createReceive(account); send(200, address.toJSON());
send(200, address.toJSON()); }));
}).catch(next);
});
// Wallet Balance // Wallet Balance
this.get('/wallet/:id/balance', function(req, res, next, send) { this.get('/wallet/:id/balance', con(function *(req, res, send, next) {
spawn(function *() { var account = req.options.account;
var account = req.options.account; var balance = yield req.wallet.getBalance(account);
var balance = yield req.wallet.getBalance(account);
if (!balance) if (!balance)
return send(404); return send(404);
send(200, balance.toJSON()); send(200, balance.toJSON());
}).catch(next); }));
});
// Wallet UTXOs // Wallet UTXOs
this.get('/wallet/:id/coin', function(req, res, next, send) { this.get('/wallet/:id/coin', con(function *(req, res, send, next) {
spawn(function *() { var account = req.options.account;
var account = req.options.account; var coins = yield req.wallet.getCoins(account);
var coins = yield req.wallet.getCoins(account); send(200, coins.map(function(coin) {
send(200, coins.map(function(coin) { return coin.toJSON();
return coin.toJSON(); }));
})); }));
}).catch(next);
});
// Wallet Coin // Wallet Coin
this.get('/wallet/:id/coin/:hash/:index', function(req, res, next, send) { this.get('/wallet/:id/coin/:hash/:index', con(function *(req, res, send, next) {
spawn(function *() { var hash = req.options.hash;
var hash = req.options.hash; var index = req.options.index;
var index = req.options.index; var coin = yield req.wallet.getCoin(hash, index);
var coin = yield req.wallet.getCoin(hash, index);
if (!coin) if (!coin)
return send(404); return send(404);
send(200, coin.toJSON()); send(200, coin.toJSON());
}).catch(next); }));
});
// Wallet TXs // Wallet TXs
this.get('/wallet/:id/tx/history', function(req, res, next, send) { this.get('/wallet/:id/tx/history', con(function *(req, res, send, next) {
spawn(function *() { var account = req.options.account;
var account = req.options.account; var txs = yield req.wallet.getHistory(account);
var txs = yield req.wallet.getHistory(account); var details = yield req.wallet.toDetails(txs);
var details = yield req.wallet.toDetails(txs); send(200, details.map(function(tx) {
send(200, details.map(function(tx) { return tx.toJSON();
return tx.toJSON(); }));
})); }));
}).catch(next);
});
// Wallet Pending TXs // Wallet Pending TXs
this.get('/wallet/:id/tx/unconfirmed', function(req, res, next, send) { this.get('/wallet/:id/tx/unconfirmed', con(function *(req, res, send, next) {
spawn(function *() { var account = req.options.account;
var account = req.options.account; var txs = yield req.wallet.getUnconfirmed(account);
var txs = yield req.wallet.getUnconfirmed(account); var details = yield req.wallet.toDetails(txs);
var details = yield req.wallet.toDetails(txs); send(200, details.map(function(tx) {
send(200, details.map(function(tx) { return tx.toJSON();
return tx.toJSON(); }));
})); }));
}).catch(next);
});
// Wallet TXs within time range // Wallet TXs within time range
this.get('/wallet/:id/tx/range', function(req, res, next, send) { this.get('/wallet/:id/tx/range', con(function *(req, res, send, next) {
spawn(function *() { var account = req.options.account;
var account = req.options.account; var options = req.options;
var options = req.options; var txs = yield req.wallet.getRange(account, options);
var txs = yield req.wallet.getRange(account, options); var details = yield req.wallet.toDetails(txs);
var details = yield req.wallet.toDetails(txs); send(200, details.map(function(tx) {
send(200, details.map(function(tx) { return tx.toJSON();
return tx.toJSON(); }));
})); }));
}).catch(next);
});
// Last Wallet TXs // Last Wallet TXs
this.get('/wallet/:id/tx/last', function(req, res, next, send) { this.get('/wallet/:id/tx/last', con(function *(req, res, send, next) {
spawn(function *() { var account = req.options.account;
var account = req.options.account; var limit = req.options.limit;
var limit = req.options.limit; var txs = yield req.wallet.getLast(account, limit);
var txs = yield req.wallet.getLast(account, limit); var details = yield req.wallet.toDetails(txs);
var details = yield req.wallet.toDetails(txs); send(200, details.map(function(tx) {
send(200, details.map(function(tx) { return tx.toJSON();
return tx.toJSON(); }));
})); }));
}).catch(next);
});
// Wallet TX // Wallet TX
this.get('/wallet/:id/tx/:hash', function(req, res, next, send) { this.get('/wallet/:id/tx/:hash', con(function *(req, res, send, next) {
spawn(function *() { var hash = req.options.hash;
var hash = req.options.hash; var tx = yield req.wallet.getTX(hash);
var tx = yield req.wallet.getTX(hash); var details;
var details;
if (!tx) if (!tx)
return send(404); return send(404);
details = yield req.wallet.toDetails(tx); details = yield req.wallet.toDetails(tx);
send(200, details.toJSON()); send(200, details.toJSON());
}).catch(next); }));
});
this.server.on('error', function(err) { this.server.on('error', function(err) {
self.emit('error', err); self.emit('error', err);
@ -1090,7 +1023,7 @@ HTTPServer.prototype.close = function close() {
*/ */
HTTPServer.prototype.use = function use(path, callback) { 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) { 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) { 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) { 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) { 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) if (this.chain.db.options.prune)
return Promise.reject(new Error('Cannot scan in pruned mode.')); 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++) for (i = 0; i < txs.length; i++)
txs[i] = txs[i].toJSON(); txs[i] = txs[i].toJSON();
self.emit('block tx', entry.toJSON(), txs); self.emit('block tx', entry.toJSON(), txs);
yield utils.wait(); yield utils.wait();
}); }));
}; };
ClientSocket.prototype.join = function join(id) { ClientSocket.prototype.join = function join(id) {

View File

@ -253,36 +253,36 @@ Miner.prototype.createBlock = co(function* createBlock(tip) {
// Find target // Find target
target = yield this.chain.getTargetAsync(ts, tip); target = yield this.chain.getTargetAsync(ts, tip);
if (this.version != null) { if (this.version != null) {
version = this.version; version = this.version;
} else { } else {
// Calculate version with versionbits // Calculate version with versionbits
version = yield this.chain.computeBlockVersion(tip); version = yield this.chain.computeBlockVersion(tip);
} }
attempt = new MinerBlock({ attempt = new MinerBlock({
workerPool: this.workerPool, workerPool: this.workerPool,
tip: tip, tip: tip,
version: version, version: version,
target: target, target: target,
address: this.address, address: this.address,
coinbaseFlags: this.coinbaseFlags, coinbaseFlags: this.coinbaseFlags,
witness: this.chain.segwitActive, witness: this.chain.segwitActive,
parallel: this.options.parallel, parallel: this.options.parallel,
network: this.network network: this.network
}); });
if (!this.mempool) if (!this.mempool)
return attempt; return attempt;
txs = this.mempool.getHistory(); txs = this.mempool.getHistory();
for (i = 0; i < txs.length; i++) { for (i = 0; i < txs.length; i++) {
tx = txs[i]; tx = txs[i];
attempt.addTX(tx); 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) { Miner.prototype.mineBlock = co(function* mineBlock(tip) {
// Create a new block and start hashing // Create a new block and start hashing
var attempt = yield this.createBlock(tip); var attempt = yield this.createBlock(tip);
return yield attempt.mineAsync(); return yield attempt.mineAsync();
}); });

View File

@ -102,7 +102,47 @@ function cob(generator) {
gen = generator.apply(this, args); 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.spawn = spawn;
exports.co = co; exports.co = co;
exports.cob = cob; exports.cob = cob;
exports.con = con;
exports.cb = cb; exports.cb = cb;
exports.wait = wait; exports.wait = wait;
exports.timeout = timeout; exports.timeout = timeout;

View File

@ -975,9 +975,9 @@ WalletDB.prototype.rescan = co(function* rescan(chaindb, height) {
this.logger.info('Scanning for %d addresses.', hashes.length); this.logger.info('Scanning for %d addresses.', hashes.length);
try { try {
yield chaindb.scan(height, hashes, function *(block, txs) { yield chaindb.scan(height, hashes, co(function *(block, txs) {
yield self.addBlock(block, txs, true); yield self.addBlock(block, txs, true);
}); }));
} catch (e) { } catch (e) {
unlock(); unlock();
throw e; throw e;

View File

@ -8,6 +8,7 @@ var crypto = require('../lib/crypto/crypto');
var assert = require('assert'); var assert = require('assert');
var opcodes = constants.opcodes; var opcodes = constants.opcodes;
var spawn = require('../lib/utils/spawn'); var spawn = require('../lib/utils/spawn');
var co = require('../lib/utils/spawn').co;
var c = require('../lib/utils/spawn').cb; var c = require('../lib/utils/spawn').cb;
describe('Chain', function() { describe('Chain', function() {
@ -234,10 +235,10 @@ describe('Chain', function() {
var total = 0; var total = 0;
c(walletdb.getAddressHashes(), function(err, hashes) { c(walletdb.getAddressHashes(), function(err, hashes) {
assert.ifError(err); 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; total += txs.length;
yield spawn.wait(); yield spawn.wait();
}), function(err) { })), function(err) {
assert.ifError(err); assert.ifError(err);
assert.equal(total, 25); assert.equal(total, 25);
cb(); cb();