http: make rpc more hookable.
This commit is contained in:
parent
a47316c264
commit
59e9ef277a
@ -401,6 +401,36 @@ HTTPBase.prototype._readBody = function _readBody(req, enc, opt, resolve, reject
|
||||
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.
|
||||
* @private
|
||||
|
||||
1048
lib/http/rpc.js
1048
lib/http/rpc.js
File diff suppressed because it is too large
Load Diff
226
lib/http/rpcbase.js
Normal file
226
lib/http/rpcbase.js
Normal 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;
|
||||
@ -102,17 +102,7 @@ HTTPServer.prototype.initRouter = function initRouter() {
|
||||
contentType: 'json'
|
||||
}));
|
||||
|
||||
// JSON 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.use(this.jsonRPC(this.rpc));
|
||||
|
||||
this.get('/', co(function* (req, res) {
|
||||
var totalTX = this.mempool ? this.mempool.totalTX : 0;
|
||||
|
||||
@ -44,6 +44,9 @@ function FullNode(options) {
|
||||
|
||||
Node.call(this, options);
|
||||
|
||||
// SPV flag.
|
||||
this.spv = false;
|
||||
|
||||
// Instantiate blockchain.
|
||||
this.chain = new Chain({
|
||||
network: this.network,
|
||||
|
||||
@ -41,6 +41,7 @@ function Node(options) {
|
||||
this.plugins = {};
|
||||
this.stack = [];
|
||||
|
||||
this.spv = false;
|
||||
this.logger = new Logger();
|
||||
this.chain = null;
|
||||
this.fees = null;
|
||||
@ -48,7 +49,6 @@ function Node(options) {
|
||||
this.pool = null;
|
||||
this.miner = null;
|
||||
this.http = null;
|
||||
this.client = null;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
@ -41,6 +41,9 @@ function SPVNode(options) {
|
||||
|
||||
Node.call(this, options);
|
||||
|
||||
// SPV flag.
|
||||
this.spv = true;
|
||||
|
||||
this.chain = new Chain({
|
||||
network: this.network,
|
||||
logger: this.logger,
|
||||
|
||||
@ -19,12 +19,3 @@ exports.version = 'v1.0.0-beta.9';
|
||||
*/
|
||||
|
||||
exports.url = 'https://github.com/bcoin-org/bcoin';
|
||||
|
||||
/**
|
||||
* Default prefix.
|
||||
* @const {String}
|
||||
*/
|
||||
|
||||
exports.prefix = process.platform === 'win32'
|
||||
? 'C:/Temp/.bcoin'
|
||||
: '/tmp/.bcoin';
|
||||
|
||||
@ -187,6 +187,25 @@ Validator.prototype.amt = function amt(key, fallback) {
|
||||
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).
|
||||
* @param {String} key
|
||||
@ -313,10 +332,13 @@ Validator.prototype.bool = function bool(key, fallback) {
|
||||
* @returns {Buffer|null}
|
||||
*/
|
||||
|
||||
Validator.prototype.buf = function buf(key, fallback) {
|
||||
Validator.prototype.buf = function buf(key, fallback, enc) {
|
||||
var value = this.get(key);
|
||||
var data;
|
||||
|
||||
if (!enc)
|
||||
enc = 'hex';
|
||||
|
||||
if (fallback === undefined)
|
||||
fallback = null;
|
||||
|
||||
@ -329,10 +351,10 @@ Validator.prototype.buf = function buf(key, fallback) {
|
||||
return value;
|
||||
}
|
||||
|
||||
data = new Buffer(value, 'hex');
|
||||
data = new Buffer(value, enc);
|
||||
|
||||
if (data.length !== value.length / 2)
|
||||
throw new Error(key + ' must be a hex string.');
|
||||
if (data.length !== Buffer.byteLength(value, enc))
|
||||
throw new Error(key + ' must be a ' + enc + ' string.');
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
@ -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
|
||||
* @returns {TX[]}
|
||||
*/
|
||||
|
||||
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 count = {};
|
||||
var result = [];
|
||||
|
||||
@ -18,6 +18,7 @@ var Script = require('../script/script');
|
||||
var crypto = require('../crypto/crypto');
|
||||
var Network = require('../protocol/network');
|
||||
var Validator = require('../utils/validator');
|
||||
var common = require('./common');
|
||||
var RPC = require('./rpc');
|
||||
|
||||
/**
|
||||
@ -105,6 +106,8 @@ HTTPServer.prototype.initRouter = function initRouter() {
|
||||
contentType: 'json'
|
||||
}));
|
||||
|
||||
this.use(this.jsonRPC(this.rpc));
|
||||
|
||||
this.hook(co(function* (req, res) {
|
||||
var valid = req.valid();
|
||||
var id, token, wallet;
|
||||
@ -583,7 +586,7 @@ HTTPServer.prototype.initRouter = function initRouter() {
|
||||
var result = [];
|
||||
var i, coin;
|
||||
|
||||
sortCoins(coins);
|
||||
common.sortCoins(coins);
|
||||
|
||||
for (i = 0; i < coins.length; i++) {
|
||||
coin = coins[i];
|
||||
@ -665,7 +668,7 @@ HTTPServer.prototype.initRouter = function initRouter() {
|
||||
var result = [];
|
||||
var i, details, item;
|
||||
|
||||
sortTX(txs);
|
||||
common.sortTX(txs);
|
||||
|
||||
details = yield req.wallet.toDetails(txs);
|
||||
|
||||
@ -685,7 +688,7 @@ HTTPServer.prototype.initRouter = function initRouter() {
|
||||
var result = [];
|
||||
var i, details, item;
|
||||
|
||||
sortTX(txs);
|
||||
common.sortTX(txs);
|
||||
|
||||
details = yield req.wallet.toDetails(txs);
|
||||
|
||||
@ -787,35 +790,41 @@ HTTPServer.prototype.initSockets = function initSockets() {
|
||||
|
||||
this.walletdb.on('tx', function(id, tx, details) {
|
||||
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);
|
||||
});
|
||||
|
||||
this.walletdb.on('confirmed', function(id, tx, details) {
|
||||
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);
|
||||
});
|
||||
|
||||
this.walletdb.on('unconfirmed', function(id, tx, details) {
|
||||
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);
|
||||
});
|
||||
|
||||
this.walletdb.on('conflict', function(id, tx, details) {
|
||||
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);
|
||||
});
|
||||
|
||||
this.walletdb.on('balance', function(id, balance) {
|
||||
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);
|
||||
});
|
||||
|
||||
this.walletdb.on('address', function(id, receive) {
|
||||
var channel = 'w:' + id;
|
||||
var json = [];
|
||||
var i, address;
|
||||
|
||||
@ -824,7 +833,7 @@ HTTPServer.prototype.initSockets = function initSockets() {
|
||||
json.push(address.toJSON());
|
||||
}
|
||||
|
||||
self.to(id, 'wallet address', json);
|
||||
self.to(channel, 'wallet address', 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) {
|
||||
var valid = new Validator([args]);
|
||||
var id = valid.str(0);
|
||||
var id = valid.str(0, '');
|
||||
var token = valid.buf(1);
|
||||
var channel = 'w:' + id;
|
||||
var wallet;
|
||||
|
||||
if (!id)
|
||||
throw new Error('Invalid parameter.');
|
||||
|
||||
if (!self.options.walletAuth) {
|
||||
socket.join(id);
|
||||
socket.join(channel);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -900,19 +910,20 @@ HTTPServer.prototype.handleAuth = function handleAuth(socket) {
|
||||
|
||||
self.logger.info('Successful wallet auth for %s.', id);
|
||||
|
||||
socket.join(id);
|
||||
socket.join(channel);
|
||||
|
||||
return null;
|
||||
}));
|
||||
|
||||
socket.hook('wallet leave', function(args) {
|
||||
var valid = new Validator([args]);
|
||||
var id = valid.str(0);
|
||||
var id = valid.str(0, '');
|
||||
var channel = 'w:' + id;
|
||||
|
||||
if (!id)
|
||||
throw new Error('Invalid parameter.');
|
||||
|
||||
socket.leave(id);
|
||||
socket.leave(channel);
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
1011
lib/wallet/rpc.js
1011
lib/wallet/rpc.js
File diff suppressed because it is too large
Load Diff
@ -1774,7 +1774,7 @@ Wallet.prototype.resend = co(function* resend() {
|
||||
txs.push(wtx.tx);
|
||||
}
|
||||
|
||||
txs = common.sortTX(txs);
|
||||
txs = common.sortDeps(txs);
|
||||
|
||||
for (i = 0; i < txs.length; i++)
|
||||
yield this.db.send(txs[i]);
|
||||
|
||||
@ -130,12 +130,12 @@ WalletDB.init = function init(node) {
|
||||
wipeNoReally: config.bool('wallet-wipe-no-really'),
|
||||
apiKey: config.str(['wallet-api-key', 'api-key']),
|
||||
walletAuth: config.bool('wallet-auth'),
|
||||
noAuth: config.bool('no-auth'),
|
||||
noAuth: config.bool(['wallet-no-auth', 'no-auth']),
|
||||
ssl: config.str('wallet-ssl'),
|
||||
host: config.str('wallet-host'),
|
||||
port: config.num('wallet-port'),
|
||||
spv: node.chain.options.spv,
|
||||
verify: node.chain.options.spv
|
||||
spv: node.spv,
|
||||
verify: node.spv
|
||||
});
|
||||
|
||||
if (node.http) {
|
||||
@ -1571,7 +1571,7 @@ WalletDB.prototype.resendPending = co(function* resendPending(wid) {
|
||||
txs.push(wtx.tx);
|
||||
}
|
||||
|
||||
txs = common.sortTX(txs);
|
||||
txs = common.sortDeps(txs);
|
||||
|
||||
for (i = 0; i < txs.length; i++) {
|
||||
tx = txs[i];
|
||||
|
||||
Loading…
Reference in New Issue
Block a user