parsec-node
Version:
https://parseclabs.readthedocs.io/en/latest/
254 lines (232 loc) • 7.68 kB
JavaScript
/* eslint-disable no-await-in-loop */
const getPort = require('get-port');
const fs = require('fs-extra');
const level = require('level');
const axios = require('axios');
const { join } = require('path');
const ABCIServer = require('./lib/abci-app.js');
const TxServer = require('./lib/tx-server.js');
const Tendermint = require('./lib/tendermint.js');
const rimraf = require('rimraf');
const generateNetworkId = require('./lib/network-id.js');
const getNodeInfo = require('./lib/node-info.js');
const getRoot = require('./lib/get-root.js');
const serveGenesisGCI = require('./lib/gci-serve-genesis.js');
const announceSelfAsFullNode = require('./lib/gci-announce-self.js');
const os = require('os');
const merk = require('merk');
const { EventEmitter } = require('events');
const LOTION_HOME = process.env.LOTION_HOME || join(os.homedir(), '.lotion');
async function getPorts(peeringPort, rpcPort, abciAppPort) {
const p2pPort =
process.env.P2P_PORT || peeringPort || (await getPort(peeringPort));
const tendermintPort =
process.env.TENDERMINT_PORT || rpcPort || (await getPort(rpcPort));
const abciPort = process.env.ABCI_PORT || abciAppPort || (await getPort());
return { tendermintPort, abciPort, p2pPort };
}
function getGenesis(genesisPath) {
return fs.readFileSync(genesisPath, { encoding: 'utf8' });
}
function Lotion(opts = {}) {
const initialState = opts.initialState || {};
const peers = opts.peers || [];
const logTendermint = opts.logTendermint || false;
const createEmptyBlocks =
typeof opts.createEmptyBlocks === 'undefined'
? true
: opts.createEmptyBlocks;
const devMode = opts.devMode || false;
const { unsafeRpc } = opts;
const txMiddleware = [];
const queryMiddleware = [];
const initializerMiddleware = [];
const blockMiddleware = [];
const periodMiddleware = [];
const initChainMiddleware = [];
const postListenMiddleware = [];
const txEndpoints = [];
const keys =
typeof opts.keys === 'string'
? JSON.parse(fs.readFileSync(opts.keys, { encoding: 'utf8' }))
: opts.keys;
const genesis =
typeof opts.genesis === 'string'
? JSON.parse(getGenesis(opts.genesis))
: opts.genesis;
const appState = Object.assign({}, initialState);
const bus = new EventEmitter();
let appInfo;
let abciServer;
let tendermint;
let txHTTPServer;
let lotionPath;
const networkId =
opts.networkId ||
generateNetworkId(
txMiddleware,
blockMiddleware,
queryMiddleware,
initializerMiddleware,
initialState,
devMode,
genesis
);
lotionPath = join(LOTION_HOME, 'networks', networkId);
if (devMode) {
lotionPath += Math.floor(Math.random() * 1e9);
rimraf.sync(lotionPath);
process.on('SIGINT', () => {
rimraf.sync(lotionPath);
process.exit();
});
}
fs.mkdirpSync(lotionPath);
bus.on('listen', () => {
postListenMiddleware.forEach(f => {
f(appInfo);
});
});
const appMethods = {
use: middleware => {
if (middleware instanceof Array) {
middleware.forEach(appMethods.use);
} else if (typeof middleware === 'function') {
appMethods.useTx(middleware);
} else if (middleware.type === 'tx') {
appMethods.useTx(middleware.middleware);
} else if (middleware.type === 'query') {
appMethods.useQuery(middleware.middleware);
} else if (middleware.type === 'block') {
appMethods.useBlock(middleware.middleware);
} else if (middleware.type === 'period') {
appMethods.usePeriod(middleware.middleware);
} else if (middleware.type === 'initChain') {
appMethods.useInitChain(middleware.middleware);
} else if (middleware.type === 'initializer') {
appMethods.useInitializer(middleware.middleware);
} else if (middleware.type === 'post-listen') {
appMethods.usePostListen(middleware.middleware);
}
return appMethods;
},
useTx: txHandler => {
txMiddleware.push(txHandler);
},
useBlock: blockHandler => {
blockMiddleware.push(blockHandler);
},
usePeriod: periodHandler => {
periodMiddleware.push(periodHandler);
},
useInitChain: initChainHandler => {
initChainMiddleware.push(initChainHandler);
},
useQuery: queryHandler => {
queryMiddleware.push(queryHandler);
},
useInitializer: initializer => {
initializerMiddleware.push(initializer);
},
usePostListen: postListener => {
// TODO: rename "post listen", there's probably a more descriptive name.
postListenMiddleware.push(postListener);
},
listen: txServerPort => {
return new Promise(async resolve => {
// set up abci server, then tendermint node, then tx server
const { tendermintPort, abciPort, p2pPort } = await getPorts(
opts.p2pPort,
opts.tendermintPort,
opts.abciPort
);
// initialize merk store
const merkDb = level(join(lotionPath, 'merk'));
const store = await merk(merkDb);
const tendermintRpcUrl = `http://localhost:${tendermintPort}`;
for (const initializer of initializerMiddleware) {
await initializer(appState);
}
Object.assign(store, appState);
const initialAppHash = (await getRoot(store)).toString('hex');
abciServer = ABCIServer({
txMiddleware,
blockMiddleware,
queryMiddleware,
initializerMiddleware,
store,
initialAppHash,
periodMiddleware,
initChainMiddleware,
});
abciServer.listen(abciPort, 'localhost');
try {
tendermint = await Tendermint({
lotionPath,
tendermintPort,
abciPort,
p2pPort,
logTendermint,
createEmptyBlocks,
networkId,
peers,
genesis,
keys,
initialAppHash,
unsafeRpc,
});
} catch (e) {
console.log('error starting tendermint node:');
console.log(e);
throw e;
}
await tendermint.synced;
// serve genesis.json and get GCI
const genesisJson = fs.readFileSync(
join(lotionPath, 'config', 'genesis.json'),
'utf8'
);
const { GCI } = serveGenesisGCI(genesisJson);
announceSelfAsFullNode({ GCI, tendermintPort });
const nodeInfo = await getNodeInfo(lotionPath);
nodeInfo.GCI = GCI;
const txServer = TxServer({
tendermintRpcUrl,
store,
nodeInfo,
txEndpoints,
port: txServerPort,
});
txHTTPServer = txServer.listen(txServerPort, 'localhost', () => {
// add some references to useful variables to app object.
appInfo = {
tendermintPort,
abciPort,
txServerPort,
GCI,
p2pPort,
lotionPath,
genesisPath: join(lotionPath, 'config', 'genesis.json'),
};
bus.emit('listen');
resolve(appInfo);
});
});
},
close: () => {
abciServer.close();
tendermint.close();
txHTTPServer.close();
},
lotionPath: () => lotionPath,
status: async () => {
const { tendermintPort } = await getPorts(undefined, opts.tendermintPort);
const tendermintRpcUrl = `http://localhost:${tendermintPort}`;
return axios
.get(`${tendermintRpcUrl}/status`)
.then(rsp => rsp.data.result);
},
};
return appMethods;
}
module.exports = Lotion;