fcoin/lib/http/rpcbase.js
2019-02-15 20:47:06 -07:00

325 lines
6.6 KiB
JavaScript

/*!
* rpcbase.js - json rpc for bcoin.
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
const assert = require('assert');
const EventEmitter = require('events');
const Lock = require('../utils/lock');
const 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 = Object.create(null);
this.mounts = [];
this.locker = new Lock();
}
Object.setPrototypeOf(RPCBase.prototype, EventEmitter.prototype);
/**
* RPC errors.
* @enum {Number}
* @default
*/
RPCBase.errors = {
// Standard JSON-RPC 2.0 errors
INVALID_REQUEST: -32600,
METHOD_NOT_FOUND: -32601,
INVALID_PARAMS: -32602,
INTERNAL_ERROR: -32603,
PARSE_ERROR: -32700,
// General application defined errors
MISC_ERROR: -1,
FORBIDDEN_BY_SAFE_MODE: -2,
TYPE_ERROR: -3,
INVALID_ADDRESS_OR_KEY: -5,
OUT_OF_MEMORY: -7,
INVALID_PARAMETER: -8,
DATABASE_ERROR: -20,
DESERIALIZATION_ERROR: -22,
VERIFY_ERROR: -25,
VERIFY_REJECTED: -26,
VERIFY_ALREADY_IN_CHAIN: -27,
IN_WARMUP: -28,
// Aliases for backward compatibility
TRANSACTION_ERROR: -25,
TRANSACTION_REJECTED: -26,
TRANSACTION_ALREADY_IN_CHAIN: -27,
// P2P client errors
CLIENT_NOT_CONNECTED: -9,
CLIENT_IN_INITIAL_DOWNLOAD: -10,
CLIENT_NODE_ALREADY_ADDED: -23,
CLIENT_NODE_NOT_ADDED: -24,
CLIENT_NODE_NOT_CONNECTED: -29,
CLIENT_INVALID_IP_OR_SUBNET: -30,
CLIENT_P2P_DISABLED: -31,
// Wallet errors
WALLET_ERROR: -4,
WALLET_INSUFFICIENT_FUNDS: -6,
WALLET_INVALID_ACCOUNT_NAME: -11,
WALLET_KEYPOOL_RAN_OUT: -12,
WALLET_UNLOCK_NEEDED: -13,
WALLET_PASSPHRASE_INCORRECT: -14,
WALLET_WRONG_ENC_STATE: -15,
WALLET_ENCRYPTION_FAILED: -16,
WALLET_ALREADY_UNLOCKED: -17
};
/**
* Magic string for signing.
* @const {String}
* @default
*/
RPCBase.MAGIC_STRING = '\u001bFlorincoin Signed Message:\n';
/**
* Execute batched RPC calls.
* @param {Object|Object[]} body
* @param {Object} query
* @returns {Promise}
*/
RPCBase.prototype.call = async function call(body, query) {
let cmds = body;
let out = [];
let array = true;
if (!Array.isArray(cmds)) {
cmds = [cmds];
array = false;
}
for (const cmd of cmds) {
if (!cmd || typeof cmd !== 'object') {
out.push({
result: null,
error: {
message: 'Invalid request.',
code: RPCBase.errors.INVALID_REQUEST
},
id: null
});
continue;
}
if (cmd.id && typeof cmd.id === 'object') {
out.push({
result: null,
error: {
message: 'Invalid ID.',
code: RPCBase.errors.INVALID_REQUEST
},
id: null
});
continue;
}
if (cmd.id == null)
cmd.id = null;
if (!cmd.params)
cmd.params = [];
if (typeof cmd.method !== 'string') {
out.push({
result: null,
error: {
message: 'Method not found.',
code: RPCBase.errors.METHOD_NOT_FOUND
},
id: cmd.id
});
continue;
}
if (!Array.isArray(cmd.params)) {
out.push({
result: null,
error: {
message: 'Invalid params.',
code: RPCBase.errors.INVALID_PARAMS
},
id: cmd.id
});
continue;
}
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';
}
let result;
try {
result = await this.execute(cmd);
} catch (err) {
let code;
switch (err.type) {
case 'RPCError':
code = err.code;
break;
case 'ValidationError':
code = RPCBase.errors.TYPE_ERROR;
break;
case 'EncodingError':
code = RPCBase.errors.DESERIALIZATION_ERROR;
break;
case 'FundingError':
code = RPCBase.errors.WALLET_INSUFFICIENT_FUNDS;
break;
default:
code = RPCBase.errors.INTERNAL_ERROR;
this.logger.error('RPC internal error.');
this.logger.error(err);
this.logger.error(err.stack);
break;
}
out.push({
result: null,
error: {
message: err.message,
code: code
},
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 = async function execute(json, help) {
const func = this.calls[json.method];
if (!func) {
for (const mount of this.mounts) {
if (mount.calls[json.method])
return await mount.execute(json, help);
}
throw new RPCError(RPCBase.errors.METHOD_NOT_FOUND,
`Method not found: ${json.method}.`);
}
return await 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(code, msg) {
Error.call(this);
assert(typeof code === 'number');
assert(typeof msg === 'string');
this.type = 'RPCError';
this.message = msg;
this.code = code;
if (Error.captureStackTrace)
Error.captureStackTrace(this, RPCError);
}
Object.setPrototypeOf(RPCError.prototype, Error.prototype);
/*
* Expose
*/
exports = RPCBase;
exports.RPCError = RPCError;
module.exports = exports;