http: make rpc more hookable.

This commit is contained in:
Christopher Jeffrey 2017-03-09 18:15:35 -08:00
parent a47316c264
commit 59e9ef277a
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
14 changed files with 1133 additions and 1332 deletions

View File

@ -401,6 +401,36 @@ HTTPBase.prototype._readBody = function _readBody(req, enc, opt, resolve, reject
req.on('end', onEnd); req.on('end', onEnd);
}; };
/**
* JSON rpc middleware.
* @param {RPCBase} rpc
* @returns {Function}
*/
HTTPBase.prototype.jsonRPC = function jsonRPC(rpc) {
return co(function* (req, res) {
var json;
if (req.method !== 'POST')
return;
if (req.pathname !== '/')
return;
if (typeof req.body.method !== 'string')
return;
json = yield rpc.call(req.body, req.query);
json = JSON.stringify(json);
json += '\n';
res.setHeader('X-Long-Polling', '/?longpoll=1');
res.send(200, json, 'json');
});
};
/** /**
* Handle mount stack. * Handle mount stack.
* @private * @private

File diff suppressed because it is too large Load Diff

226
lib/http/rpcbase.js Normal file
View File

@ -0,0 +1,226 @@
/*!
* rpcbase.js - json rpc for bcoin.
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var assert = require('assert');
var EventEmitter = require('events').EventEmitter;
var util = require('../utils/util');
var co = require('../utils/co');
var Lock = require('../utils/lock');
var Logger = require('../node/logger');
/**
* JSON RPC
* @alias module:http.RPCBase
* @constructor
*/
function RPCBase() {
if (!(this instanceof RPCBase))
return new RPCBase();
EventEmitter.call(this);
this.logger = Logger.global;
this.calls = {};
this.mounts = [];
this.locker = new Lock();
}
util.inherits(RPCBase, EventEmitter);
/**
* Magic string for signing.
* @const {String}
* @default
*/
RPCBase.MAGIC_STRING = 'Bitcoin Signed Message:\n';
/**
* Execute batched RPC calls.
* @param {Object|Object[]} body
* @param {Object} query
* @returns {Promise}
*/
RPCBase.prototype.call = co(function* call(body, query) {
var cmds = body;
var out = [];
var array = true;
var i, cmd, result;
if (!Array.isArray(cmds)) {
cmds = [cmds];
array = false;
}
for (i = 0; i < cmds.length; i++) {
cmd = cmds[i];
assert(cmd && typeof cmd === 'object', 'Command must be an object.');
assert(typeof cmd.method === 'string', 'Method must be a string.');
if (!cmd.params)
cmd.params = [];
assert(Array.isArray(cmd.params), 'Params must be an array.');
if (!cmd.id)
cmd.id = 0;
assert(typeof cmd.id === 'number', 'ID must be a number.');
}
for (i = 0; i < cmds.length; i++) {
cmd = cmds[i];
if (cmd.method !== 'getwork'
&& cmd.method !== 'getblocktemplate'
&& cmd.method !== 'getbestblockhash') {
this.logger.debug('Handling RPC call: %s.', cmd.method);
if (cmd.method !== 'submitblock'
&& cmd.method !== 'getmemorypool') {
this.logger.debug(cmd.params);
}
}
if (cmd.method === 'getwork') {
if (query.longpoll)
cmd.method = 'getworklp';
}
try {
result = yield this.execute(cmd);
} catch (err) {
if (err.type === 'RPCError') {
out.push({
result: null,
error: {
message: err.message,
code: -1
},
id: cmd.id
});
continue;
}
this.logger.error(err);
out.push({
result: null,
error: {
message: err.message,
code: 1
},
id: cmd.id
});
continue;
}
if (result === undefined)
result = null;
out.push({
result: result,
error: null,
id: cmd.id
});
}
if (!array)
out = out[0];
return out;
});
/**
* Execute an RPC call.
* @private
* @param {Object} json
* @param {Boolean} help
* @returns {Promise}
*/
RPCBase.prototype.execute = co(function* execute(json, help) {
var func = this.calls[json.method];
var i, mount;
if (!func) {
for (i = 0; i < this.mounts.length; i++) {
mount = this.mounts[i];
if (mount.call[json.method])
return yield mount.execute(json, help);
}
throw new RPCError('Method not found: ' + json.method + '.');
}
return yield func.call(this, json.params, help);
});
/**
* Add a custom RPC call.
* @param {String} name
* @param {Function} func
*/
RPCBase.prototype.add = function add(name, func) {
assert(typeof func === 'function', 'Handler must be a function.');
assert(!this.calls[name], 'Duplicate RPC call.');
this.calls[name] = func;
};
/**
* Mount another RPC object.
* @param {Object} rpc
*/
RPCBase.prototype.mount = function mount(rpc) {
assert(rpc, 'RPC must be an object.');
assert(typeof rpc.execute === 'function', 'Execute must be a method.');
this.mounts.push(rpc);
};
/**
* Attach to another RPC object.
* @param {Object} rpc
*/
RPCBase.prototype.attach = function attach(rpc) {
assert(rpc, 'RPC must be an object.');
assert(typeof rpc.execute === 'function', 'Execute must be a method.');
rpc.mount(this);
};
/**
* RPC Error
* @constructor
* @ignore
*/
function RPCError(msg, code) {
Error.call(this);
if (Error.captureStackTrace)
Error.captureStackTrace(this, RPCError);
this.type = 'RPCError';
this.message = msg;
this.code = code != null ? code : -1;
}
util.inherits(RPCError, Error);
/*
* Expose
*/
exports = RPCBase;
exports.RPCError = RPCError;
module.exports = exports;

View File

@ -102,17 +102,7 @@ HTTPServer.prototype.initRouter = function initRouter() {
contentType: 'json' contentType: 'json'
})); }));
// JSON RPC this.use(this.jsonRPC(this.rpc));
this.post('/', co(function* (req, res) {
var json = yield this.rpc.call(req.body, req.query);
json = JSON.stringify(json);
json += '\n';
res.setHeader('X-Long-Polling', '/?longpoll=1');
res.send(200, json, 'json');
}));
this.get('/', co(function* (req, res) { this.get('/', co(function* (req, res) {
var totalTX = this.mempool ? this.mempool.totalTX : 0; var totalTX = this.mempool ? this.mempool.totalTX : 0;

View File

@ -44,6 +44,9 @@ function FullNode(options) {
Node.call(this, options); Node.call(this, options);
// SPV flag.
this.spv = false;
// Instantiate blockchain. // Instantiate blockchain.
this.chain = new Chain({ this.chain = new Chain({
network: this.network, network: this.network,

View File

@ -41,6 +41,7 @@ function Node(options) {
this.plugins = {}; this.plugins = {};
this.stack = []; this.stack = [];
this.spv = false;
this.logger = new Logger(); this.logger = new Logger();
this.chain = null; this.chain = null;
this.fees = null; this.fees = null;
@ -48,7 +49,6 @@ function Node(options) {
this.pool = null; this.pool = null;
this.miner = null; this.miner = null;
this.http = null; this.http = null;
this.client = null;
this.init(); this.init();
} }

View File

@ -41,6 +41,9 @@ function SPVNode(options) {
Node.call(this, options); Node.call(this, options);
// SPV flag.
this.spv = true;
this.chain = new Chain({ this.chain = new Chain({
network: this.network, network: this.network,
logger: this.logger, logger: this.logger,

View File

@ -19,12 +19,3 @@ exports.version = 'v1.0.0-beta.9';
*/ */
exports.url = 'https://github.com/bcoin-org/bcoin'; exports.url = 'https://github.com/bcoin-org/bcoin';
/**
* Default prefix.
* @const {String}
*/
exports.prefix = process.platform === 'win32'
? 'C:/Temp/.bcoin'
: '/tmp/.bcoin';

View File

@ -187,6 +187,25 @@ Validator.prototype.amt = function amt(key, fallback) {
return value * 1e8; return value * 1e8;
}; };
/**
* Get a config option (as a number).
* @param {String} key
* @param {Object?} fallback
* @returns {Number|null}
*/
Validator.prototype.btc = function btc(key, fallback) {
var value = this.num(key);
if (fallback === undefined)
fallback = null;
if (value === null)
return fallback;
return value * 1e8;
};
/** /**
* Get a config option (as a number). * Get a config option (as a number).
* @param {String} key * @param {String} key
@ -313,10 +332,13 @@ Validator.prototype.bool = function bool(key, fallback) {
* @returns {Buffer|null} * @returns {Buffer|null}
*/ */
Validator.prototype.buf = function buf(key, fallback) { Validator.prototype.buf = function buf(key, fallback, enc) {
var value = this.get(key); var value = this.get(key);
var data; var data;
if (!enc)
enc = 'hex';
if (fallback === undefined) if (fallback === undefined)
fallback = null; fallback = null;
@ -329,10 +351,10 @@ Validator.prototype.buf = function buf(key, fallback) {
return value; return value;
} }
data = new Buffer(value, 'hex'); data = new Buffer(value, enc);
if (data.length !== value.length / 2) if (data.length !== Buffer.byteLength(value, enc))
throw new Error(key + ' must be a hex string.'); throw new Error(key + ' must be a ' + enc + ' string.');
return data; return data;
}; };

View File

@ -36,12 +36,38 @@ common.isName = function isName(key) {
}; };
/** /**
* Sort an array of transactions in dependency order. * Sort an array of transactions by time.
* @param {TX[]} txs * @param {TX[]} txs
* @returns {TX[]} * @returns {TX[]}
*/ */
common.sortTX = function sortTX(txs) { common.sortTX = function sortTX(txs) {
return txs.sort(function(a, b) {
return a.ps - b.ps;
});
};
/**
* Sort an array of coins by height.
* @param {Coin[]} txs
* @returns {Coin[]}
*/
common.sortCoins = function sortCoins(coins) {
return coins.sort(function(a, b) {
a = a.height === -1 ? 0x7fffffff : a.height;
b = b.height === -1 ? 0x7fffffff : b.height;
return a - b;
});
};
/**
* Sort an array of transactions in dependency order.
* @param {TX[]} txs
* @returns {TX[]}
*/
common.sortDeps = function sortDeps(txs) {
var depMap = {}; var depMap = {};
var count = {}; var count = {};
var result = []; var result = [];

View File

@ -18,6 +18,7 @@ var Script = require('../script/script');
var crypto = require('../crypto/crypto'); var crypto = require('../crypto/crypto');
var Network = require('../protocol/network'); var Network = require('../protocol/network');
var Validator = require('../utils/validator'); var Validator = require('../utils/validator');
var common = require('./common');
var RPC = require('./rpc'); var RPC = require('./rpc');
/** /**
@ -105,6 +106,8 @@ HTTPServer.prototype.initRouter = function initRouter() {
contentType: 'json' contentType: 'json'
})); }));
this.use(this.jsonRPC(this.rpc));
this.hook(co(function* (req, res) { this.hook(co(function* (req, res) {
var valid = req.valid(); var valid = req.valid();
var id, token, wallet; var id, token, wallet;
@ -583,7 +586,7 @@ HTTPServer.prototype.initRouter = function initRouter() {
var result = []; var result = [];
var i, coin; var i, coin;
sortCoins(coins); common.sortCoins(coins);
for (i = 0; i < coins.length; i++) { for (i = 0; i < coins.length; i++) {
coin = coins[i]; coin = coins[i];
@ -665,7 +668,7 @@ HTTPServer.prototype.initRouter = function initRouter() {
var result = []; var result = [];
var i, details, item; var i, details, item;
sortTX(txs); common.sortTX(txs);
details = yield req.wallet.toDetails(txs); details = yield req.wallet.toDetails(txs);
@ -685,7 +688,7 @@ HTTPServer.prototype.initRouter = function initRouter() {
var result = []; var result = [];
var i, details, item; var i, details, item;
sortTX(txs); common.sortTX(txs);
details = yield req.wallet.toDetails(txs); details = yield req.wallet.toDetails(txs);
@ -787,35 +790,41 @@ HTTPServer.prototype.initSockets = function initSockets() {
this.walletdb.on('tx', function(id, tx, details) { this.walletdb.on('tx', function(id, tx, details) {
var json = details.toJSON(); var json = details.toJSON();
self.to(id, 'wallet tx', json); var channel = 'w:' + id;
self.to(channel, 'wallet tx', json);
self.to('!all', 'wallet tx', id, json); self.to('!all', 'wallet tx', id, json);
}); });
this.walletdb.on('confirmed', function(id, tx, details) { this.walletdb.on('confirmed', function(id, tx, details) {
var json = details.toJSON(); var json = details.toJSON();
self.to(id, 'wallet confirmed', json); var channel = 'w:' + id;
self.to(channel, 'wallet confirmed', json);
self.to('!all', 'wallet confirmed', id, json); self.to('!all', 'wallet confirmed', id, json);
}); });
this.walletdb.on('unconfirmed', function(id, tx, details) { this.walletdb.on('unconfirmed', function(id, tx, details) {
var json = details.toJSON(); var json = details.toJSON();
self.to(id, 'wallet unconfirmed', json); var channel = 'w:' + id;
self.to(channel, 'wallet unconfirmed', json);
self.to('!all', 'wallet unconfirmed', id, json); self.to('!all', 'wallet unconfirmed', id, json);
}); });
this.walletdb.on('conflict', function(id, tx, details) { this.walletdb.on('conflict', function(id, tx, details) {
var json = details.toJSON(); var json = details.toJSON();
self.to(id, 'wallet conflict', json); var channel = 'w:' + id;
self.to(channel, 'wallet conflict', json);
self.to('!all', 'wallet conflict', id, json); self.to('!all', 'wallet conflict', id, json);
}); });
this.walletdb.on('balance', function(id, balance) { this.walletdb.on('balance', function(id, balance) {
var json = balance.toJSON(); var json = balance.toJSON();
self.to(id, 'wallet balance', json); var channel = 'w:' + id;
self.to(channel, 'wallet balance', json);
self.to('!all', 'wallet balance', id, json); self.to('!all', 'wallet balance', id, json);
}); });
this.walletdb.on('address', function(id, receive) { this.walletdb.on('address', function(id, receive) {
var channel = 'w:' + id;
var json = []; var json = [];
var i, address; var i, address;
@ -824,7 +833,7 @@ HTTPServer.prototype.initSockets = function initSockets() {
json.push(address.toJSON()); json.push(address.toJSON());
} }
self.to(id, 'wallet address', json); self.to(channel, 'wallet address', json);
self.to('!all', 'wallet address', id, json); self.to('!all', 'wallet address', id, json);
}); });
}; };
@ -873,15 +882,16 @@ HTTPServer.prototype.handleAuth = function handleAuth(socket) {
socket.hook('wallet join', co(function* (args) { socket.hook('wallet join', co(function* (args) {
var valid = new Validator([args]); var valid = new Validator([args]);
var id = valid.str(0); var id = valid.str(0, '');
var token = valid.buf(1); var token = valid.buf(1);
var channel = 'w:' + id;
var wallet; var wallet;
if (!id) if (!id)
throw new Error('Invalid parameter.'); throw new Error('Invalid parameter.');
if (!self.options.walletAuth) { if (!self.options.walletAuth) {
socket.join(id); socket.join(channel);
return; return;
} }
@ -900,19 +910,20 @@ HTTPServer.prototype.handleAuth = function handleAuth(socket) {
self.logger.info('Successful wallet auth for %s.', id); self.logger.info('Successful wallet auth for %s.', id);
socket.join(id); socket.join(channel);
return null; return null;
})); }));
socket.hook('wallet leave', function(args) { socket.hook('wallet leave', function(args) {
var valid = new Validator([args]); var valid = new Validator([args]);
var id = valid.str(0); var id = valid.str(0, '');
var channel = 'w:' + id;
if (!id) if (!id)
throw new Error('Invalid parameter.'); throw new Error('Invalid parameter.');
socket.leave(id); socket.leave(channel);
return null; return null;
}); });
@ -1066,20 +1077,6 @@ function enforce(value, msg) {
} }
} }
function sortTX(txs) {
return txs.sort(function(a, b) {
return a.ps - b.ps;
});
}
function sortCoins(coins) {
return coins.sort(function(a, b) {
a = a.height === -1 ? 0x7fffffff : a.height;
b = b.height === -1 ? 0x7fffffff : b.height;
return a - b;
});
}
/* /*
* Expose * Expose
*/ */

File diff suppressed because it is too large Load Diff

View File

@ -1774,7 +1774,7 @@ Wallet.prototype.resend = co(function* resend() {
txs.push(wtx.tx); txs.push(wtx.tx);
} }
txs = common.sortTX(txs); txs = common.sortDeps(txs);
for (i = 0; i < txs.length; i++) for (i = 0; i < txs.length; i++)
yield this.db.send(txs[i]); yield this.db.send(txs[i]);

View File

@ -130,12 +130,12 @@ WalletDB.init = function init(node) {
wipeNoReally: config.bool('wallet-wipe-no-really'), wipeNoReally: config.bool('wallet-wipe-no-really'),
apiKey: config.str(['wallet-api-key', 'api-key']), apiKey: config.str(['wallet-api-key', 'api-key']),
walletAuth: config.bool('wallet-auth'), walletAuth: config.bool('wallet-auth'),
noAuth: config.bool('no-auth'), noAuth: config.bool(['wallet-no-auth', 'no-auth']),
ssl: config.str('wallet-ssl'), ssl: config.str('wallet-ssl'),
host: config.str('wallet-host'), host: config.str('wallet-host'),
port: config.num('wallet-port'), port: config.num('wallet-port'),
spv: node.chain.options.spv, spv: node.spv,
verify: node.chain.options.spv verify: node.spv
}); });
if (node.http) { if (node.http) {
@ -1571,7 +1571,7 @@ WalletDB.prototype.resendPending = co(function* resendPending(wid) {
txs.push(wtx.tx); txs.push(wtx.tx);
} }
txs = common.sortTX(txs); txs = common.sortDeps(txs);
for (i = 0; i < txs.length; i++) { for (i = 0; i < txs.length; i++) {
tx = txs[i]; tx = txs[i];