more http things.

This commit is contained in:
Christopher Jeffrey 2016-03-10 02:15:29 -08:00
parent ed420e0769
commit c2aa7b3727
13 changed files with 1120 additions and 631 deletions

View File

@ -270,7 +270,7 @@ Chain.prototype._preload = function _preload(callback) {
var self = this;
var url = 'https://headers.electrum.org/blockchain_headers';
var buf, height, stream;
var request;
var request = require('./http/request');
if (!this.options.preload)
return callback();
@ -281,19 +281,13 @@ Chain.prototype._preload = function _preload(callback) {
if (network.type !== 'main')
return callback(new Error('Electrum.org only offers `main` headers.'));
try {
request = require('request');
} catch (e) {
return callback(e);
}
utils.debug('Loading %s', url);
this.db.getChainHeight(function(err, chainHeight) {
if (err)
return callback(err);
stream = request.get(url);
stream = request({ method: 'GET', uri: url });
height = 0;
buf = {
data: [],
@ -386,7 +380,7 @@ Chain.prototype._preload = function _preload(callback) {
// redundant blocks to disk!
if (height <= chainHeight) {
self.db.addCache(entry);
self.db.bloom(entry.hash, 'hex');
// self.db.bloom(entry.hash, 'hex');
} else {
self.db.save(entry);
}

370
lib/bcoin/http/client.js Normal file
View File

@ -0,0 +1,370 @@
/**
* client.js - http client for wallets
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* https://github.com/indutny/bcoin
*/
var EventEmitter = require('events').EventEmitter;
var bcoin = require('../../bcoin');
var constants = bcoin.protocol.constants;
var network = bcoin.protocol.network;
var utils = bcoin.utils;
var assert = utils.assert;
var request = require('./request');
/**
* Client
*/
function Client(uri) {
if (!(this instanceof Client))
return new Client(uri);
EventEmitter.call(this);
this.uri = uri;
this.id = null;
this._init();
}
utils.inherits(Client, EventEmitter);
Client.prototype._init = function _init() {
var self = this;
var io;
try {
io = require('socket.io');
} catch (e) {
;
}
if (!io)
return;
this.socket = new io.Socket(this.uri);
this.socket.on('open', function() {
self.socket.on('tx', function(tx, map) {
self.emit('tx', bcoin.tx.fromJSON(tx), map);
});
self.socket.on('confirmed', function(tx, map) {
self.emit('confirmed', bcoin.tx.fromJSON(tx), map);
});
self.socket.on('updated', function(tx, map) {
self.emit('updated', bcoin.tx.fromJSON(tx), map);
});
self.socket.on('balance', function(balance, id) {
self.emit('balance', utils.satoshi(balance), id);
});
self.socket.on('balances', function(balances) {
Object.keys(balances).forEach(function(id) {
balances[id] = utils.satoshi(balances[id]);
});
self.emit('balances', balances);
});
self.socket.on('error', function(err) {
self.emit('error', err);
});
});
};
Client.prototype.listenWallet = function listenWallet(id) {
this.socket.join(id);
};
Client.prototype.unlistenWallet = function unlistenWallet(id) {
this.socket.leave(id);
};
Client.prototype.listenAll = function listenAll(id) {
this.listenWallet('!all');
};
Client.prototype.unlistenAll = function unlistenAll(id) {
this.unlistenWallet('!all');
};
Client.prototype.destroy = function destroy() {
this.socket.destroy();
this.socket = null;
};
Client.prototype._request = function _request(method, endpoint, json, callback) {
var query;
if (!callback) {
callback = json;
json = null;
}
if (json && method === 'get') {
json = null;
query = json;
}
request({
method: method,
uri: this.uri + '/' + endpoint,
query: query,
json: json,
expect: 'json'
}, function(err, res, body) {
if (err)
return callback(err);
if (res.statusCode === 404)
return callback();
if (!body)
return callback(new Error('No body.'));
if (res.statusCode !== 200)
return callback(new Error('Status code: ' + res.statusCode));
try {
return callback(null, body);
} catch (e) {
return callback(e);
}
});
};
Client.prototype._get = function _get(endpoint, json, callback) {
return this._request('get', endpoint, json, callback);
};
Client.prototype._post = function _post(endpoint, json, callback) {
return this._request('post', endpoint, json, callback);
};
Client.prototype._put = function _put(endpoint, json, callback) {
return this._request('put', endpoint, json, callback);
};
Client.prototype._del = function _del(endpoint, json, callback) {
return this._request('delete', endpoint, json, callback);
};
Client.prototype.getWalletAll = function getWalletAll(id, callback) {
return this._get('/wallet/' + id + '/tx/all', function(err, body) {
if (err)
return callback(err);
if (!body)
return callback(null, []);
return callback(null, body.map(function(data) {
return bcoin.tx.fromJSON(data);
}));
});
};
Client.prototype.getWalletCoins = function getWalletCoins(id, callback) {
return this._get('/wallet/' + id + '/coin', function(err, body) {
if (err)
return callback(err);
if (!body)
return callback(null, []);
return callback(null, body.map(function(data) {
return bcoin.coin.fromJSON(data);
}));
});
};
Client.prototype.getWalletPending = function getPending(id, callback) {
return this._get('/wallet/' + id + '/tx/pending', function(err, body) {
if (err)
return callback(err);
if (!body)
return callback(null, []);
return callback(null, body.map(function(data) {
return bcoin.coin.fromJSON(data);
}));
});
};
Client.prototype.getWalletBalance = function getBalance(id, callback) {
return this._get('/wallet/' + id + '/balance', function(err, body) {
if (err)
return callback(err);
if (!body)
return callback(new Error('Not found.'));
return callback(null, utils.satoshi(body.balance));
});
};
Client.prototype.getWalletLast = function getLast(id, limit, callback) {
return this._get('/wallet/' + id + '/tx/last', options, function(err, body) {
if (err)
return callback(err);
if (!body)
return callback(null, []);
return callback(null, body.map(function(data) {
return bcoin.tx.fromJSON(data);
}));
});
};
Client.prototype.getWalletRange = function getWalletRange(id, options, callback) {
return this._get('/wallet/' + id + '/tx/range', options, function(err, body) {
if (err)
return callback(err);
if (!body)
return callback(null, []);
return callback(null, body.map(function(data) {
return bcoin.tx.fromJSON(data);
}));
});
};
Client.prototype.getWalletTX = function getTX(id, hash, callback) {
hash = utils.revHex(hash);
return this._get('/wallet/' + id + '/tx/' + hash, function(err, body) {
if (err)
return callback(err);
if (!body)
return callback(null, []);
return callback(null, bcoin.tx.fromJSON(body));
});
};
Client.prototype.getWalletCoin = function getCoin(id, hash, index, callback) {
hash = utils.revHex(hash);
return this._get('/wallet/' + id + '/coin/' + hash + '/' + index, function(err, body) {
if (err)
return callback(err);
if (!body)
return callback(null, []);
return callback(null, bcoin.coin.fromJSON(body));
});
};
Client.prototype.syncWallet = function syncWallet(id, options, callback) {
var self = this;
var body = {
receiveDepth: options.receiveDepth,
changeDepth: options.changeDepth
};
return this._put('/wallet/' + id, body, function(err) {
if (err)
return callback(err);
return callback();
});
};
Client.prototype.getCoinByAddress = function getCoinByAddress(address, callback) {
var body = { addresses: address };
return this._post('/coin/address', body, function(err, body) {
if (err)
return callback(err);
if (!body)
return callback(null, []);
return callback(null, body.map(function(data) {
return bcoin.coin.fromJSON(data);
}));
});
};
Client.prototype.getCoin = function getCoin(hash, index, callback) {
hash = utils.revHex(hash);
return this._get('/coin/' + hash + '/' + index, function(err, body) {
if (err)
return callback(err);
if (!body)
return callback(null, []);
return callback(null, bcoin.coin.fromJSON(body));
});
};
Client.prototype.getTXByAddress = function getTXByAddress(address, callback) {
var body = { addresses: address };
return this._post('/tx/address', body, function(err, body) {
if (err)
return callback(err);
if (!body)
return callback(null, []);
return callback(null, body.map(function(data) {
return bcoin.tx.fromJSON(data);
}));
});
};
Client.prototype.getTX = function getTX(hash, callback) {
hash = utils.revHex(hash);
return this._get('/tx/' + hash, function(err, body) {
if (err)
return callback(err);
if (!body)
return callback(null, []);
return callback(null, bcoin.tx.fromJSON(body));
});
};
Client.prototype.getBlock = function getBlock(hash, callback) {
if (typeof hash !== 'number')
hash = utils.revHex(hash);
return this._get('/block/' + hash, function(err, body) {
if (err)
return callback(err);
if (!body)
return callback(null, []);
return callback(null, bcoin.block.fromJSON(body));
});
};
Client.prototype.broadcast = function broadcast(tx, callback) {
var body = { tx: utils.toHex(tx.toRaw()) };
callback = utils.ensure(callback);
return this._post('/broadcast', body, function(err, body) {
if (err)
return callback(err);
return callback();
});
};
/**
* Expose
*/
module.exports = Client;

View File

@ -5,35 +5,24 @@
*/
var EventEmitter = require('events').EventEmitter;
var StringDecoder = require('string_decoder').StringDecoder;
var url = require('url');
var engine;
try {
engine = require('engine.io');
} catch (e) {
;
}
var bcoin = require('../bcoin');
var bn = require('bn.js');
var constants = bcoin.protocol.constants;
var network = bcoin.protocol.network;
var bcoin = require('../../bcoin');
var utils = bcoin.utils;
var assert = utils.assert;
/**
* HTTPServer
*/
function HTTPServer(node, options) {
function HTTPServer(options) {
var self = this;
if (!(this instanceof HTTPServer))
return new HTTPServer(options);
if (!options)
options = {};
this.options = options;
this.node = node;
this.io = null;
this.routes = {
get: [],
@ -42,6 +31,8 @@ function HTTPServer(node, options) {
del: []
};
this.stack = [];
this.server = options.key
? require('https').createServer(options)
: require('http').createServer();
@ -55,7 +46,7 @@ HTTPServer.prototype._init = function _init() {
var self = this;
this._initRouter();
this._initEngine();
this._initIO();
this.server.on('connection', function(socket) {
socket.on('error', function(err) {
@ -67,348 +58,30 @@ HTTPServer.prototype._init = function _init() {
});
});
this.get('/', function(req, res, next, send) {
send(200, {
version: require('../../package.json').version,
network: self.node.network.type
});
});
// UTXO by address
this.get('/coin/address/:address', function(req, res, next, send) {
var addresses = req.params.address.split(',');
self.node.getCoinByAddress(addresses, function(err, coins) {
if (err)
return next(err);
if (!coins.length)
return send(404);
send(200, coins.map(function(coin) { return coin.toJSON(); }));
});
});
// UTXO by id
this.get('/coin/:hash/:index', function(req, res, next, send) {
req.params.hash = utils.revHex(req.params.hash);
self.node.getCoin(req.params.hash, +req.params.index, function(err, coin) {
if (err)
return next(err);
if (!coin)
return send(404);
send(200, coin.toJSON());
});
});
// Bulk read UTXOs
this.post('/coin/address', function(req, res, next, send) {
self.node.getCoinByAddress(req.body.addresses, function(err, coins) {
if (err)
return next(err);
if (!coins.length)
return send(404);
send(200, coins.map(function(coin) { return coin.toJSON(); }));
});
});
// TX by hash
this.get('/tx/:hash', function(req, res, next, send) {
req.params.hash = utils.revHex(req.params.hash);
self.node.getTX(req.params.hash, function(err, tx) {
if (err)
return next(err);
if (!tx)
return send(404);
send(200, tx.toJSON());
});
});
// TX by address
this.get('/tx/address/:address', function(req, res, next, send) {
var addresses = req.params.address.split(',');
self.node.getTXByAddress(addresses, function(err, txs) {
if (err)
return next(err);
if (!txs.length)
return send(404);
send(200, txs.map(function(tx) { return tx.toJSON(); }));
});
});
// Bulk read TXs
this.post('/tx/address', function(req, res, next, send) {
self.node.getTXByAddress(req.params.addresses, function(err, txs) {
if (err)
return next(err);
if (!txs.length)
return send(404);
send(200, txs.map(function(tx) { return tx.toJSON(); }));
});
});
// Block by hash/height
this.get('/block/:hash', function(req, res, next, send) {
var hash = req.params.hash;
if (utils.isInt(hash))
hash = +hash;
else
hash = utils.revHex(hash);
self.node.getFullBlock(hash, function(err, block) {
if (err)
return next(err);
if (!block)
return send(404);
send(200, block.toJSON());
});
});
// Get wallet
this.get('/wallet/:id', function(req, res, next, send) {
self.node.walletdb.getJSON(req.params.id, function(err, json) {
if (err)
return next(err);
if (!json)
return send(404);
send(200, json);
});
});
// Create/get wallet
this.post('/wallet/:id', function(req, res, next, send) {
req.body.id = req.params.id;
self.node.walletdb.create(req.body, function(err, wallet) {
var wallet;
if (err)
return next(err);
if (!wallet)
return send(404);
json = wallet.toJSON();
wallet.destroy();
send(200, json);
});
});
// Update wallet / sync address depth
this.put('/wallet/:id', function(req, res, next, send) {
var id = req.params.id;
var receive = req.body.receiveDepth >>> 0;
var change = req.body.changeDepth >>> 0;
self.node.walletdb.setDepth(id, receive, change, function(err) {
if (err)
return next(err);
if (!json)
return send(404);
send(200, { success: true });
});
});
// Wallet Balance
this.get('/wallet/:id/balance', function(req, res, next, send) {
var id = req.params.id;
if (id === '_all')
id = null;
self.node.walletdb.getBalance(req.params.id, function(err, balance) {
if (err)
return next(err);
if (!coins.length)
return send(404);
send(200, { balance: utils.btc(balance) });
});
});
// Wallet UTXOs
this.get('/wallet/:id/coin', function(req, res, next, send) {
var id = req.params.id;
if (id === '_all')
id = null;
self.node.walletdb.getCoins(req.params.id, function(err, coins) {
if (err)
return next(err);
if (!coins.length)
return send(404);
send(200, coins.map(function(coin) { return coin.toJSON(); }));
});
});
// Wallet TX
this.get('/wallet/:id/coin/:hash/:index', function(req, res, next, send) {
var id = req.params.id;
if (id === '_all')
id = null;
self.node.walletdb.getCoin(req.params.hash, +req.params.index, function(err, coin) {
if (err)
return next(err);
if (!coin)
return send(404);
send(200, coin.toJSON());
});
});
// Wallet TXs
this.get('/wallet/:id/tx/all', function(req, res, next, send) {
var id = req.params.id;
if (id === '_all')
id = null;
self.node.walletdb.getAll(req.params.id, function(err, txs) {
if (err)
return next(err);
if (!txs.length)
return send(404);
send(200, txs.map(function(tx) { return tx.toJSON(); }));
});
});
// Wallet Pending TXs
this.get('/wallet/:id/tx/pending', function(req, res, next, send) {
var id = req.params.id;
if (id === '_all')
id = null;
self.node.walletdb.getPending(req.params.id, function(err, txs) {
if (err)
return next(err);
if (!txs.length)
return send(404);
send(200, txs.map(function(tx) { return tx.toJSON(); }));
});
});
// Wallet TXs within time range
this.get('/wallet/:id/tx/range', function(req, res, next, send) {
var id = req.params.id;
if (id === '_all')
id = null;
var options = {
start: +req.query.start,
end: +req.query.end,
limit: +req.query.limit
};
self.node.walletdb.getTimeRange(req.params.id, options, function(err, txs) {
if (err)
return next(err);
if (!txs.length)
return send(404);
send(200, txs.map(function(tx) { return tx.toJSON(); }));
});
});
// Wallet TXs within time range
this.get('/wallet/:id/tx/last', function(req, res, next, send) {
var id = req.params.id;
if (id === '_all')
id = null;
self.node.walletdb.getTimeRange(id, +req.query.limit, function(err, txs) {
if (err)
return next(err);
if (!txs.length)
return send(404);
send(200, txs.map(function(tx) { return tx.toJSON(); }));
});
});
// Wallet TX
this.get('/wallet/:id/tx/:hash', function(req, res, next, send) {
self.node.walletdb.getTX(req.params.hash, function(err, tx) {
if (err)
return next(err);
if (!tx)
return send(404);
send(200, tx.toJSON());
});
});
this.post('/broadcast', function(req, res, next, send) {
self.node.pool.broadcast(bcoin.tx.fromRaw(req.body.tx, 'hex'));
send(200, { success: true });
this.server.on('error', function(err) {
self.emit('error', err);
});
};
HTTPServer.prototype._initEngine = function _initEngine() {
HTTPServer.prototype._initIO = function _initIO() {
var self = this;
var io;
if (!engine)
try {
io = require('socket.io');
} catch (e) {
;
}
if (!io)
return;
this.clients = [];
this.io = new io.Server();
this.engine = new engine.Server();
this.io.attach(this.server);
this.engine.attach(this.server);
this.engine.on('connection', function(socket) {
var s = new Socket(self, socket);
socket.on('message', function(data) {
s.parse(data);
});
socket.on('close', function() {
s.destroy();
});
socket.on('error', function(err) {
self.emit('error', err);
});
s.on('error', function(err) {
self.emit('error', err);
});
});
};
HTTPServer.prototype.listen = function listen(port, host, callback) {
var self = this;
this.server.listen(port, host, function(err) {
var address;
if (err)
throw err;
address = self.server.address();
utils.debug('Listening - host=%s port=%d',
address.address, address.port);
if (callback)
callback();
this.io.on('connection', function(socket) {
self.emit('websocket', socket);
});
};
@ -485,19 +158,60 @@ HTTPServer.prototype._initRouter = function _initRouter() {
req.params[i] = item;
});
// Avoid stack overflows
utils.nextTick(function() {
try {
callback(req, res, next, _send);
} catch (e) {
done(e);
}
self._handle(req, res, _send, function(err) {
if (err)
return done(err);
// Avoid stack overflows
utils.nextTick(function() {
try {
callback(req, res, next, _send);
} catch (e) {
done(e);
}
});
});
})();
});
});
};
HTTPServer.prototype._handle = function _handle(req, res, _send, callback) {
var self = this;
var i = 0;
var handler;
(function next(err) {
if (err)
return callback(err);
if (i === self.stack.length)
return callback();
handler = self.stack[i++];
utils.nextTick(function() {
if (handler.path && req.pathname.indexOf(handler.path) === -1)
return next();
try {
handler.callback(req, res, next, _send);
} catch (e) {
next(e);
}
});
})();
};
HTTPServer.prototype.use = function use(path, callback) {
if (!callback) {
callback = path;
path = null;
}
this.stack.push({ path: path, callback: callback });
};
HTTPServer.prototype.get = function get(path, callback) {
this.routes.get.push({ path: path, callback: callback });
};
@ -514,37 +228,21 @@ HTTPServer.prototype.del = function del(path, callback) {
this.routes.del.push({ path: path, callback: callback });
};
HTTPServer.prototype.sendWebhook = function sendWebhook(msg, callback) {
var request, body, secret, hmac;
HTTPServer.prototype.listen = function listen(port, host, callback) {
var self = this;
this.server.listen(port, host, function(err) {
var address;
callback = utils.ensure(callback);
if (!this.options.webhook)
return callback();
try {
request = require('request');
} catch (e) {
return callback(e);
}
body = new Buffer(JSON.stringify(msg) + '\n', 'utf8');
secret = new Buffer(this.options.webhook.secret || '', 'utf8');
hmac = utils.sha512hmac(body, secret);
request({
method: 'POST',
uri: this.options.webhook.endpoint,
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Content-Length': body.length + '',
'X-Bcoin-Hmac': hmac.toString('hex')
},
body: body
}, function(err, res, body) {
if (err)
return callback(err);
return callback();
throw err;
address = self.server.address();
utils.debug('Listening - host=%s port=%d',
address.address, address.port);
if (callback)
callback();
});
};
@ -558,8 +256,10 @@ function send(res, code, msg) {
try {
res.statusCode = code;
msg = JSON.stringify(msg, null, 2) + '\n';
res.setHeader('Content-Type', 'application/json; charset=utf-8');
if (typeof msg === 'object') {
res.setHeader('Content-Type', 'application/json; charset=utf-8');
msg = JSON.stringify(msg, null, 2) + '\n';
}
res.setHeader('Content-Length', Buffer.byteLength(msg) + '');
res.write(msg);
res.end();
@ -594,6 +294,7 @@ function compilePath(path) {
}
function parseBody(req, callback) {
var StringDecoder = require('string_decoder').StringDecoder;
var decode = new StringDecoder('utf8');
var total = 0;
var body = '';
@ -661,6 +362,7 @@ function parsePairs(str, del, eq) {
}
function parsePath(req) {
var url = require('url');
var uri = url.parse(req.url);
var pathname = uri.pathname || '/';
@ -706,215 +408,6 @@ function unescape(str) {
}
}
function Socket(server, socket) {
this.server = server;
this.engine = server.engine;
this.node = server.node;
this.walletdb = server.node.walletdb;
this.socket = socket;
this.listeners = {};
this._init();
}
Socket.prototype._init = function _init() {
this.server.clients.push(this);
};
Socket.prototype.parse = function parse(msg) {
var size, off, header, payload;
size = utils.readIntv(msg, 0);
off = size.off;
size = size.r;
if (off + size > msg.length)
return this.send({ event: 'error', msg: 'Size larger than message.' });
try {
header = JSON.parse(msg.slice(off, off + size).toString('utf8'));
off += size;
} catch (e) {
return this.send({ event: 'error', msg: 'Header malformed.' });
}
payload = data.slice(off);
try {
return this._handle(header, payload);
} catch (e) {
return this.send({ event: 'error', msg: e.message + '' });
}
};
Socket.prototype._handle = function _handle(header, payload) {
if (header.cmd === 'handshake')
return this._handleHandshake(header, payload);
if (header.cmd === 'listen')
return this._handleListen(header, payload);
if (header.cmd === 'unlisten')
return this._handleUnlisten(header, payload);
if (header.event)
return this.emit(header.event, header, payload);
throw new Error('Not a valid command.');
};
Socket.prototype.destroy = function destroy() {
this.remove();
this.cleanup();
return this.clients.destroy();
};
Socket.prototype.remove = function remove() {
var i = this.server.clients.indexOf(this);
if (i !== -1)
this.server.clients.splice(i, 1);
};
Socket.prototype.cleanup = function cleanup() {
Object.keys(this.listeners).forEach(function(id) {
this.unlistenWallet(id);
}, this);
};
Socket.prototype.unlistenWallet = function unlistenWallet(id) {
this.listeners[id].forEach(function(listener) {
this.walletdb.removeListener(listener[0], listener[1]);
}, this);
delete this.listeners[id];
};
Socket.prototype._listenWallet = function _listenWallet(id, event, listener) {
if (!this.listeners[id])
this.listeners[id] = [];
this.listeners[id].push([event, listener]);
this.walletdb.on(event, listener);
};
Socket.prototype.listenWallet = function listenWallet(event, id, listener) {
if (id === '_all') {
this._listenWallet(id, 'tx', function(tx, map) {
self.send({ event: 'tx', map: map, id: id, _payload: tx.toExtended() });
});
this._listenWallet(id, 'updated', function(tx, map) {
self.send({ event: 'updated', map: map, id: id, _payload: tx.toExtended() });
});
this._listenWallet(id, 'confirmed', function(tx, map) {
self.send({ event: 'confirmed', map: map, id: id, _payload: tx.toExtended() });
});
this._listenWallet(id, 'unconfirmed', function(tx, map) {
self.send({ event: 'unconfirmed', map: map, id: id, _payload: tx.toExtended() });
});
this._listenWallet(id, 'balances', function(balances) {
Object.keys(balances).forEach(function(key) {
balances[key] = utils.btc(balances[key]);
});
self.send({ event: 'balances', id: id, balances: balances });
});
return;
}
this._listenWallet(id, id + ' tx', function(tx) {
self.send({ event: 'tx', id: id, _payload: tx.toExtended() });
});
this._listenWallet(id, id + ' updated', function(tx) {
self.send({ event: 'updated', id: id, _payload: tx.toExtended() });
});
this._listenWallet(id, id + ' confirmed', function(tx) {
self.send({ event: 'confirmed', id: id, _payload: tx.toExtended() });
});
this._listenWallet(id, id + ' unconfirmed', function(tx) {
self.send({ event: 'unconfirmed', id: id, _payload: tx.toExtended() });
});
this._listenWallet(id, id + ' balance', function(balance) {
self.send({ event: 'balance', id: id, balance: utils.btc(balance) });
});
};
Socket.prototype._onListen = function _onListen(header, payload) {
var self = this;
var id = header.id;
if (typeof id !== 'string')
throw new Error('Wallet ID is not a string.');
if (id.length > 1000)
throw new Error('Wallet ID too large.');
if (!/^[a-zA-Z0-9]+$/.test(id))
throw new Error('Wallet ID must be alphanumeric.');
if (this.listeners[id])
throw new Error('Already listening.');
this.listenWallet(id);
};
Socket.prototype._onUnlisten = function _onUnlisten(header, payload) {
var self = this;
var id = header.id;
if (typeof id !== 'string')
throw new Error('Wallet ID is not a string.');
if (id.length > 1000)
throw new Error('Wallet ID too large.');
if (!/^[a-zA-Z0-9]+$/.test(id))
throw new Error('Wallet ID must be alphanumeric.');
if (!this.listeners[id])
throw new Error('Not listening.');
this.unlistenWallet(id);
};
Socket.prototype._handleHandshake = function _handleHandshake(header, payload) {
if (this._activated)
throw new Error('Handshake already completed.');
if (payload.length > 0)
throw new Error('Unexpected payload.');
this._activated = true;
};
Socket.prototype.send = function send(data) {
var header, payload, size, msg;
if (data._payload) {
payload = data._payload;
delete data._payload;
} else {
payload = new Buffer([]);
}
assert(Buffer.isBuffer(payload));
header = new Buffer(JSON.stringify(data), 'utf8');
size = new Buffer(utils.sizeIntv(header.length));
utils.writeIntv(size, header.length, 0);
msg = Buffer.concat([size, header, payload]);
return this.socket.send(msg);
};
/**
* Expose
*/

4
lib/bcoin/http/index.js Normal file
View File

@ -0,0 +1,4 @@
exports.http = require('./http');
exports.server = require('./server');
exports.client = require('./client');
exports.request = require('./request');

125
lib/bcoin/http/provider.js Normal file
View File

@ -0,0 +1,125 @@
/**
* provider.js - http provider for wallets
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* https://github.com/indutny/bcoin
*/
var EventEmitter = require('events').EventEmitter;
var bcoin = require('../bcoin');
var bn = require('bn.js');
var constants = bcoin.protocol.constants;
var network = bcoin.protocol.network;
var utils = bcoin.utils;
var assert = utils.assert;
var Client = require('./client');
/**
* Provider
*/
function Provider(url) {
if (!(this instanceof Provider))
return new Provider(url);
EventEmitter.call(this);
this.client = new Client(url);
this.url = url;
this.id = null;
this._init();
}
utils.inherits(Provider, EventEmitter);
Provider.prototype._init = function _init() {
var self = this;
this.client.on('tx', function(tx) {
self.emit('tx', tx);
});
this.client.on('confirmed', function(tx) {
self.emit('confirmed', tx);
});
this.client.on('updated', function(tx) {
self.emit('updated', tx);
});
this.client.on('balance', function(balance) {
self.emit('balance', balance);
});
this.client.on('error', function(err) {
self.emit('error', err);
});
};
Provider.prototype.setID = function setID(id) {
assert(!this.id)
this.id = id;
this.client.listenWallet(id);
};
Provider.prototype.destroy = function destroy() {
this.client.destroy();
this.client = null;
};
Provider.prototype.getAll = function getAll(callback) {
return this.client.getWalletAll(this.id, callback);
};
Provider.prototype.getCoins = function getCoins(callback) {
return this.client.getWalletCoins(this.id, callback);
};
Provider.prototype.getPending = function getPending(callback) {
return this.client.getWalletPending(this.id, callback);
};
Provider.prototype.getBalance = function getBalance(callback) {
return this.client.getWalletBalance(this.id, callback);
};
Provider.prototype.getLastTime = function getLastTime(callback) {
assert(false);
};
Provider.prototype.getLast = function getLast(limit, callback) {
return this.client.getWalletLast(this.id, limit, callback);
};
Provider.prototype.getRange = function getRange(options, callback) {
return this.client.getWalletRange(this.id, options, callback);
};
Provider.prototype.getTX = function getTX(hash, callback) {
return this.client.getWalletTX(this.id, hash, callback);
};
Provider.prototype.getCoin = function getCoin(hash, index, callback) {
return this.client.getWalletCoin(this.id, hash, index, callback);
};
Provider.prototype.fillTX = function fillTX(tx, callback) {
assert(false);
};
Provider.prototype.fillCoin = function fillCoin(tx, callback) {
assert(false);
};
Provider.prototype.sync = function sync(wallet, address) {
return this.client.syncWallet(this.id, wallet, function(err) {
if (err)
self.emit('error', err);
});
};
/**
* Expose
*/
module.exports = WalletDB;

View File

@ -1,13 +1,24 @@
var StringDecoder = require('string_decoder').StringDecoder;
/**
* request.js - http request for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* https://github.com/indutny/bcoin
*/
var Stream = require('stream').Stream;
var qs = require('querystring');
var url = require('url');
// Spoof by default
var USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1)'
+ ' AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36';
/**
* Request
*/
function request(options, callback, stream) {
var StringDecoder = require('string_decoder').StringDecoder;
var qs = require('querystring');
var url = require('url');
var uri = options.uri;
var query = options.query;
var body = options.body;
@ -57,7 +68,8 @@ function request(options, callback, stream) {
path: uri.pathname + query,
headers: {
'User-Agent': options.agent || USER_AGENT
}
},
rejectUnauthorized: options.strictSSL !== false
};
if (body) {
@ -99,6 +111,14 @@ function request(options, callback, stream) {
opt.method = options.method || 'GET';
}
if (options.auth)
uri.auth = options.auth.username + ':' + options.auth.password;
if (uri.auth) {
opt.headers['Authorization'] =
'Basic ' + new Buffer(uri.auth, 'utf8').toString('base64');
}
opt.method = opt.method.toUpperCase();
req = http.request(opt);
@ -246,8 +266,16 @@ request._buffer = function(options, callback) {
return stream;
};
/**
* ReqStream
*/
function ReqStream(options) {
if (!(this instanceof ReqStream))
return new ReqStream(options);
Stream.call(this);
this.req = null;
this.res = null;
this.headers = null;
@ -277,3 +305,9 @@ ReqStream.prototype.destroy = function destroy() {
;
}
};
/**
* Expose
*/
module.exports = request;

459
lib/bcoin/http/server.js Normal file
View File

@ -0,0 +1,459 @@
/**
* server.js - http server for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* https://github.com/indutny/bcoin
*/
var EventEmitter = require('events').EventEmitter;
var bcoin = require('../../bcoin');
var HTTPServer = require('./http');
var constants = bcoin.protocol.constants;
var network = bcoin.protocol.network;
var utils = bcoin.utils;
var assert = utils.assert;
/**
* NodeServer
*/
function NodeServer(node, options) {
var self = this;
if (!options)
options = {};
this.options = options;
this.node = node;
this.walletdb = node.walletdb;
this.pool = node.pool;
this.server = new HTTPServer(options);
this.io = null;
this._init();
}
utils.inherits(NodeServer, EventEmitter);
NodeServer.prototype._init = function _init() {
var self = this;
this.use(function(req, res, next, send) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader(
'Access-Control-Allow-Methods',
'GET,HEAD,PUT,PATCH,POST,DELETE');
if (req.method === 'OPTIONS')
return send(200);
next();
});
this.use(function(req, res, next, send) {
var params = utils.merge({}, req.query, req.body, req.params);
var options = {};
if (params.id) {
assert(params.id !== '!all');
options.id = params.id;
}
if (params.hash) {
if (utils.isInt(params.hash))
options.height = params.hash >>> 0;
else
options.hash = utils.revHex(params.hash);
}
if (params.index != null)
options.index = params.index >>> 0;
if (params.height != null)
options.height = params.height >>> 0;
if (params.start != null)
options.start = params.start >>> 0;
if (params.end != null)
options.end = params.end >>> 0;
if (params.limit != null)
options.limit = params.limit >>> 0;
if (params.changeDepth)
options.changeDepth = params.changeDepth >>> 0;
if (params.receiveDepth)
options.receiveDepth = params.receiveDepth >>> 0;
if (params.address)
params.addresses = params.address;
if (params.addresses) {
if (typeof params.addresses === 'string')
options.addresses = params.addresses.split(',');
else
options.addresses = params.addresses;
}
if (params.tx) {
try {
options.tx = bcoin.tx.fromRaw(params.tx, 'hex');
} catch (e) {
return next(e);
}
}
if (params.bin)
options.bin = true;
req.options = options;
next();
});
this.get('/', function(req, res, next, send) {
send(200, {
version: require('../../../package.json').version,
network: self.node.network.type
});
});
// UTXO by address
this.get('/coin/address/:address', function(req, res, next, send) {
self.node.getCoinByAddress(req.options.addresses, function(err, coins) {
if (err)
return next(err);
if (!coins.length)
return send(404);
send(200, coins.map(function(coin) { return coin.toJSON(); }));
});
});
// UTXO by id
this.get('/coin/:hash/:index', function(req, res, next, send) {
self.node.getCoin(req.options.hash, req.options.index, function(err, coin) {
if (err)
return next(err);
if (!coin)
return send(404);
send(200, coin.toJSON());
});
});
// Bulk read UTXOs
this.post('/coin/address', function(req, res, next, send) {
self.node.getCoinByAddress(req.body.addresses, function(err, coins) {
if (err)
return next(err);
if (!coins.length)
return send(404);
send(200, coins.map(function(coin) { return coin.toJSON(); }));
});
});
// TX by hash
this.get('/tx/:hash', function(req, res, next, send) {
self.node.getTX(req.options.hash, function(err, tx) {
if (err)
return next(err);
if (!tx)
return send(404);
send(200, tx.toJSON());
});
});
// TX by address
this.get('/tx/address/:address', function(req, res, next, send) {
self.node.getTXByAddress(req.options.addresses, function(err, txs) {
if (err)
return next(err);
if (!txs.length)
return send(404);
send(200, txs.map(function(tx) { return tx.toJSON(); }));
});
});
// Bulk read TXs
this.post('/tx/address', function(req, res, next, send) {
self.node.getTXByAddress(req.body.addresses, function(err, txs) {
if (err)
return next(err);
if (!txs.length)
return send(404);
send(200, txs.map(function(tx) { return tx.toJSON(); }));
});
});
// Block by hash/height
this.get('/block/:hash', function(req, res, next, send) {
var hash = req.options.hash || req.options.height;
self.node.getFullBlock(hash, function(err, block) {
if (err)
return next(err);
if (!block)
return send(404);
send(200, block.toJSON());
});
});
// Get wallet
this.get('/wallet/:id', function(req, res, next, send) {
self.walletdb.getJSON(req.options.id, function(err, json) {
if (err)
return next(err);
if (!json)
return send(404);
send(200, json);
});
});
// Create/get wallet
this.post('/wallet/:id', function(req, res, next, send) {
self.walletdb.create(req.options, function(err, wallet) {
var json;
if (err)
return next(err);
if (!wallet)
return send(404);
json = wallet.toJSON();
wallet.destroy();
send(200, json);
});
});
// Update wallet / sync address depth
this.put('/wallet/:id', function(req, res, next, send) {
var id = req.options.id;
var receive = req.options.receiveDepth;
var change = req.options.changeDepth;
self.walletdb.setDepth(id, receive, change, function(err) {
if (err)
return next(err);
if (!json)
return send(404);
send(200, { success: true });
});
});
// Wallet Balance
this.get('/wallet/:id/balance', function(req, res, next, send) {
self.walletdb.getBalance(req.options.id, function(err, balance) {
if (err)
return next(err);
if (!coins.length)
return send(404);
send(200, { balance: utils.btc(balance) });
});
});
// Wallet UTXOs
this.get('/wallet/:id/coin', function(req, res, next, send) {
self.walletdb.getCoins(req.options.id, function(err, coins) {
if (err)
return next(err);
if (!coins.length)
return send(404);
send(200, coins.map(function(coin) { return coin.toJSON(); }));
});
});
// Wallet TX
this.get('/wallet/:id/coin/:hash/:index', function(req, res, next, send) {
self.walletdb.getCoin(req.options.hash, req.options.index, function(err, coin) {
if (err)
return next(err);
if (!coin)
return send(404);
send(200, coin.toJSON());
});
});
// Wallet TXs
this.get('/wallet/:id/tx/all', function(req, res, next, send) {
self.walletdb.getAll(req.options.id, function(err, txs) {
if (err)
return next(err);
if (!txs.length)
return send(404);
send(200, txs.map(function(tx) { return tx.toJSON(); }));
});
});
// Wallet Pending TXs
this.get('/wallet/:id/tx/pending', function(req, res, next, send) {
self.walletdb.getPending(req.options.id, function(err, txs) {
if (err)
return next(err);
if (!txs.length)
return send(404);
send(200, txs.map(function(tx) { return tx.toJSON(); }));
});
});
// Wallet TXs within time range
this.get('/wallet/:id/tx/range', function(req, res, next, send) {
self.walletdb.getRange(req.options.id, req.options, function(err, txs) {
if (err)
return next(err);
if (!txs.length)
return send(404);
send(200, txs.map(function(tx) { return tx.toJSON(); }));
});
});
// Wallet TXs within time range
this.get('/wallet/:id/tx/last', function(req, res, next, send) {
self.walletdb.getRange(req.options.id, req.options.limit, function(err, txs) {
if (err)
return next(err);
if (!txs.length)
return send(404);
send(200, txs.map(function(tx) { return tx.toJSON(); }));
});
});
// Wallet TX
this.get('/wallet/:id/tx/:hash', function(req, res, next, send) {
self.walletdb.getTX(req.options.hash, function(err, tx) {
if (err)
return next(err);
if (!tx)
return send(404);
send(200, tx.toJSON());
});
});
// Broadcast TX
this.post('/broadcast', function(req, res, next, send) {
var tx = req.options.tx;
self.pool.broadcast(tx);
send(200, { success: true });
});
this.server.on('error', function(err) {
self.emit('error', err);
});
this._initIO();
};
NodeServer.prototype._initIO = function _initIO() {
if (!this.server.io)
return;
this.server.on('websocket', function(socket) {
socket.on('error', function(err) {
self.emit('error', err);
});
self.emit('websocket', socket);
});
this.walletdb.on('tx', function(tx, map) {
tx = tx.toJSON();
map.all.forEach(function(id) {
self.server.io.to(id).emit('tx', tx);
});
self.server.io.to('!all').emit('tx', tx, map);
});
this.walletdb.on('confirmed', function(tx, map) {
tx = tx.toJSON();
map.all.forEach(function(id) {
self.server.io.to(id).emit('confirmed', tx);
});
self.server.io.to('!all').emit('confirmed', tx, map);
});
this.walletdb.on('updated', function(tx, map) {
tx = tx.toJSON();
map.all.forEach(function(id) {
self.server.io.to(id).emit('updated', tx);
});
self.server.io.to('!all').emit('updated', tx, map);
});
this.walletdb.on('balance', function(balance, id) {
balance = utils.btc(balance);
self.server.io.to(id).emit('balance', balance);
self.server.io.to('!all').emit('balance', balance, id);
});
this.walletdb.on('balances', function(balances) {
Object.keys(balances).forEach(function(id) {
balances[id] = utils.btc(balances[id]);
});
self.server.io.to('!all').emit('balances', balances);
});
};
NodeServer.prototype.use = function use(path, callback) {
return this.server.use(path, callback);
};
NodeServer.prototype.get = function get(path, callback) {
return this.server.get(path, callback);
};
NodeServer.prototype.post = function post(path, callback) {
return this.server.post(path, callback);
};
NodeServer.prototype.put = function put(path, callback) {
return this.server.put(path, callback);
};
NodeServer.prototype.del = function del(path, callback) {
return this.server.del(path, callback);
};
NodeServer.prototype.listen = function listen(port, host, callback) {
return this.server.listen(port, host, callback);
};
/**
* Expose
*/
module.exports = NodeServer;

View File

@ -75,16 +75,13 @@ Fullnode.prototype._init = function _init() {
});
// WalletDB needs access to the network type.
this.walletdb = new bcoin.walletdb(this, {
type: this.options.walletdb
});
this.walletdb = new bcoin.walletdb(this);
// HTTP needs access to the mempool
// and blockdb.
this.http = new bcoin.http(this, {
key: this.options.httpKey,
cert: this.options.httpCert,
webhook: this.options.webhook
this.http = new bcoin.http.server(this, {
key: this.options.sslKey,
cert: this.options.sslCert
});
// Bind to errors
@ -100,6 +97,10 @@ Fullnode.prototype._init = function _init() {
self.emit('error', err);
});
this.http.on('error', function(err) {
self.emit('error', err);
});
this.walletdb.on('error', function(err) {
self.emit('error', err);
});

View File

@ -1106,7 +1106,7 @@ TX.prototype.toExtended = function toExtended(coins) {
return;
}
coin = bcoin.protocol.framer.coin(input.output, true);
coin = bcoin.protocol.framer.coin(input.output);
off += utils.writeIntv(buf, coin.length, off);
off += utils.copy(coin, buf, off);
});
@ -1166,7 +1166,11 @@ TX._fromExtended = function _fromExtended(buf, coins) {
off += chunkSize;
if (coin.length === 0)
continue;
tx.inputs[i].output = bcoin.protocol.parser.parseCoin(coin, true);
coin = bcoin.protocol.parser.parseCoin(coin);
coin.hash = tx.inputs[i].prevout.hash;
coin.index = tx.inputs[i].prevout.index;
coin.spent = false;
tx.inputs[i].output = coin;
}
}

View File

@ -1048,7 +1048,7 @@ TXPool.prototype.getHeightHashes = function getHeightHashes(height, callback) {
return this.getHeightRangeHashes({ start: height, end: height }, callback);
};
TXPool.prototype.getTimeRangeHashes = function getTimeRangeHashes(address, options, callback) {
TXPool.prototype.getRangeHashes = function getRangeHashes(address, options, callback) {
var prefix = this.prefix + '/';
var self = this;
var txs = [];
@ -1097,11 +1097,11 @@ TXPool.prototype.getTimeRangeHashes = function getTimeRangeHashes(address, optio
})();
};
TXPool.prototype.getTimeRange = function getLast(address, options, callback) {
TXPool.prototype.getRange = function getLast(address, options, callback) {
var self = this;
var txs = [];
return this.getTimeRangeHashes(address, options, function(err, hashes) {
return this.getRangeHashes(address, options, function(err, hashes) {
if (err)
return callback(err);
@ -1127,7 +1127,7 @@ TXPool.prototype.getTimeRange = function getLast(address, options, callback) {
};
TXPool.prototype.getLast = function getLast(address, limit, callback) {
return this.getTimeRange(address, {
return this.getRange(address, {
start: 0,
end: 0xffffffff,
reverse: true,

View File

@ -134,6 +134,10 @@ Wallet.prototype._init = function _init() {
this.provider.setID(this.id);
this.provider.on('error', function(err) {
utils.debug('Wallet Error: %s', err.message);
});
this.provider.on('tx', function(tx) {
self.emit('tx', tx);
});

View File

@ -161,13 +161,14 @@ WalletDB.prototype._init = function _init() {
balances[id] = balance;
self.emit('balance', balance, id);
self.emit(id + ' balance', balance);
});
}, function(err) {
if (err)
self.emit('error', err);
self.emit('balances', balances);
self.emit('balances', balances, map);
});
// Only sync for confirmed txs.
@ -551,10 +552,10 @@ WalletDB.prototype.getLast = function getLast(id, limit, callback) {
return this.tx.getLast(id, limit, callback);
};
WalletDB.prototype.getTimeRange = function getTimeRange(id, options, callback) {
WalletDB.prototype.getRange = function getRange(id, options, callback) {
var self = this;
id = id.id || id;
return this.tx.getTimeRange(id, options, callback);
return this.tx.getRange(id, options, callback);
};
WalletDB.prototype.fillTX = function fillTX(tx, callback) {
@ -689,8 +690,8 @@ Provider.prototype.getLast = function getLast(limit, callback) {
return this.db.getLast(this.id, limit, callback);
};
Provider.prototype.getTimeRange = function getTimeRange(options, callback) {
return this.db.getTimeRange(this.id, options, callback);
Provider.prototype.getRange = function getRange(options, callback) {
return this.db.getRange(this.id, options, callback);
};
Provider.prototype.getTX = function getTX(hash, callback) {

View File

@ -8,7 +8,7 @@
"scripts": {
"test": "rm -rf ~/.bcoin-test && BCOIN_PREFIX=~/.bcoin-test mocha --reporter spec test/*-test.js"
},
"repository": "git://github.com/indutny/bcoin.git",
"repository": "git://github.com/bcoin-org/bcoin.git",
"keywords": [
"bitcoin",
"blockchain",
@ -35,7 +35,7 @@
},
"optionalDependencies": {
"secp256k1": "3.0.0",
"request": "2.67.0"
"socket.io": "1.45.0"
},
"devDependencies": {
"hash.js": "1.0.3",