lisk-framework
Version:
Lisk blockchain application platform
231 lines • 10.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RPCServer = void 0;
const constants_1 = require("../../constants");
const http_server_1 = require("../../controller/http/http_server");
const ipc_server_1 = require("../../controller/ipc/ipc_server");
const ws_server_1 = require("../../controller/ws/ws_server");
const request_1 = require("../../controller/request");
const JSONRPC = require("../../controller/jsonrpc");
const jsonrpc_1 = require("../../controller/jsonrpc");
const system_dirs_1 = require("../../system_dirs");
class RPCServer {
constructor(dataPath, config) {
this._rpcHandler = new Map();
this._config = config;
if (this._config.modes.includes(constants_1.RPC_MODES.IPC)) {
const dirs = (0, system_dirs_1.systemDirs)(dataPath);
this._ipcServer = new ipc_server_1.IPCServer({
socketsDir: dirs.sockets,
name: 'engine',
externalSocket: true,
});
}
if (this._config.modes.includes(constants_1.RPC_MODES.WS)) {
this._wsServer = new ws_server_1.WSServer({
path: '/rpc-ws',
port: this._config.port,
host: this._config.host,
accessControlAllowOrigin: this._config.accessControlAllowOrigin,
});
}
if (this._config.modes.includes(constants_1.RPC_MODES.HTTP)) {
this._httpServer = new http_server_1.HTTPServer({
host: this._config.host,
port: this._config.port,
path: '/rpc',
ignorePaths: ['/rpc-ws'],
accessControlAllowOrigin: this._config.accessControlAllowOrigin,
});
}
}
init(options) {
this._logger = options.logger;
this._chainID = options.chainID;
}
async start() {
var _a, _b;
if (this._ipcServer) {
await this._ipcServer.start();
this._handleIPCRequest(this._ipcServer.rpcServer).catch(err => {
this._logger.info({ status: 'error', err: err }, 'Failed to handle IPC request');
});
this._logger.info(`RPC IPC Server starting at ${this._ipcServer.socketPaths.rpcServer}`);
}
if (this._httpServer) {
this._httpServer.start(this._logger, (req, res, message) => {
this._logger.debug({ remoteAddress: req.socket.remoteAddress }, 'Received HTTP request');
this._handleRequest(message)
.then(data => {
res.end(JSON.stringify(data));
})
.catch((error) => {
this._logger.info({ status: 'error', err: error }, 'Failed to handle HTTP request');
res.end(JSON.stringify(error.response));
});
});
}
if (this._wsServer) {
this._wsServer.start(this._logger, (socket, message) => {
this._logger.debug({ id: socket.id, url: socket.url }, 'Received WS request');
this._handleRequest(message)
.then(data => {
socket.send(JSON.stringify(data));
})
.catch((error) => {
this._logger.info({ status: 'error', err: error }, 'Failed to handle WS request');
socket.send(JSON.stringify(error.response));
});
}, (_a = this._httpServer) === null || _a === void 0 ? void 0 : _a.server);
}
(_b = this._httpServer) === null || _b === void 0 ? void 0 : _b.listen();
}
stop() {
if (this._ipcServer) {
this._ipcServer.stop();
}
if (this._wsServer) {
this._wsServer.stop();
}
if (this._httpServer) {
this._httpServer.stop();
}
}
async publish(eventName, data) {
await this._publishIPC(eventName, data);
this._publishWS(eventName, data);
}
registerEndpoint(namespace, method, handler) {
var _a;
if (!this._rpcHandler.has(namespace)) {
this._rpcHandler.set(namespace, new Map());
}
const existingNamespace = this._rpcHandler.get(namespace);
const existingMethod = (_a = this._rpcHandler.get(namespace)) === null || _a === void 0 ? void 0 : _a.get(method);
if (existingMethod) {
throw new Error(`Method ${method} in ${namespace} is already registered.`);
}
existingNamespace.set(method, handler);
this._logger.info({ method: `${namespace}_${method}` }, `Registered endpoint`);
}
registerNotFoundEndpoint(handler) {
if (this._notFoundHandler) {
throw new Error('NotFoundHandler is already registered.');
}
this._notFoundHandler = handler;
}
async _handleIPCRequest(rpcServer) {
for await (const [sender, request] of rpcServer) {
this._logger.debug({ sender: sender.toString('hex') }, 'Received IPC request');
this._handleRequest(request.toString())
.then(result => {
rpcServer.send([sender, JSON.stringify(result)]).catch(error => {
this._logger.debug({ err: error }, `Failed to send request response: ${result.id} to ipc client.`);
});
})
.catch((err) => {
this._logger.info({ status: 'error', err }, 'Failed to handle IPC request');
rpcServer.send([sender, JSON.stringify(err.response)]).catch(error => {
this._logger.debug({ err: error }, `Failed to send IPC error response.`);
});
});
}
}
async _handleRequest(rawRequest) {
var _a;
if (!rawRequest) {
this._logger.error('Empty invoke request.');
throw new JSONRPC.JSONRPCError('Invalid invoke request.', JSONRPC.errorResponse(null, JSONRPC.invalidRequest('Invalid invoke request.')));
}
let requestObj;
try {
requestObj = JSON.parse(rawRequest);
}
catch (error) {
throw new JSONRPC.JSONRPCError('Invalid RPC request. Failed to parse request params.', JSONRPC.errorResponse(null, JSONRPC.invalidRequest('Invalid RPC request. Failed to parse request params.')));
}
try {
JSONRPC.validateJSONRPCRequest(requestObj);
}
catch (error) {
this._logger.error({ err: error }, 'Invalid RPC request.');
throw new JSONRPC.JSONRPCError('Invalid RPC request. Invalid request format.', JSONRPC.errorResponse(null, JSONRPC.invalidRequest('Invalid RPC request. Invalid request format.')));
}
try {
const request = request_1.Request.fromJSONRPCRequest(requestObj);
if (!this._isAllowedMethod(request.namespace, request.name)) {
const disabledMethodErrorMessage = 'Requested method or namespace is disabled in node config.';
throw new JSONRPC.JSONRPCError(disabledMethodErrorMessage, JSONRPC.errorResponse(request.id, JSONRPC.invalidRequest(disabledMethodErrorMessage)));
}
const handler = this._getHandler(request.namespace, request.name);
const context = {
logger: this._logger,
chainID: this._chainID,
params: (_a = requestObj.params) !== null && _a !== void 0 ? _a : {},
};
if (!handler) {
if (this._notFoundHandler) {
const result = await this._notFoundHandler(request.namespace, request.name, context);
return request.buildJSONRPCResponse({ result });
}
throw new JSONRPC.JSONRPCError(`Method ${request.name} with in namespace ${request.namespace} does not exist`, JSONRPC.errorResponse(requestObj.id, JSONRPC.methodNotFound()));
}
const result = await handler(context);
this._logger.info({ status: 'success', method: `${request.namespace}_${request.name}`, id: request.id }, 'Handled RPC request');
return request.buildJSONRPCResponse({
result: result !== null && result !== void 0 ? result : {},
});
}
catch (error) {
if (error instanceof JSONRPC.JSONRPCError) {
throw error;
}
throw new JSONRPC.JSONRPCError(error.message, JSONRPC.errorResponse(requestObj.id, JSONRPC.invalidRequest(error.message)));
}
}
async _publishIPC(eventName, data) {
if (!this._ipcServer) {
return;
}
const notification = (0, jsonrpc_1.notificationRequest)(eventName, data);
try {
await this._ipcServer.pubSocket.send([eventName, JSON.stringify(notification)]);
this._logger.info({ status: 'success', event: eventName }, 'IPCServer published event');
}
catch (error) {
this._logger.info({ status: 'error', event: eventName }, 'IPCServer published event');
this._logger.debug({ err: error, event: eventName }, `Failed to publish event from ipc server.`);
}
}
_publishWS(eventName, data) {
if (!this._wsServer) {
return;
}
const notification = (0, jsonrpc_1.notificationRequest)(eventName, data);
this._wsServer.broadcast(notification);
this._logger.info({ status: 'success', event: eventName }, 'WSServer published event');
}
_getHandler(namespace, method) {
const existingNamespace = this._rpcHandler.get(namespace);
if (!existingNamespace) {
return undefined;
}
const existingMethod = existingNamespace.get(method);
if (!existingMethod) {
return undefined;
}
return existingMethod;
}
_isAllowedMethod(namespace, method) {
const { allowedMethods } = this._config;
if (!allowedMethods || allowedMethods.length === 0) {
return false;
}
if (allowedMethods.includes('*')) {
return true;
}
return allowedMethods.includes(namespace) || allowedMethods.includes(`${namespace}_${method}`);
}
}
exports.RPCServer = RPCServer;
//# sourceMappingURL=rpc_server.js.map