Skip to content

Commit

Permalink
Enable https (#64)
Browse files Browse the repository at this point in the history
* refactor: add clearTimeout in get request

* style: remove one line code

* feat: add https checker

* fix: split nodos before init promise.

* fix: add protocol support when request node info status

* feat: add node health

* feat: add finalization

* style: fix lint, and disable unuse build script

* fix: change params to required

Co-authored-by: OlegMakarenko <33131259+OlegMakarenko@users.noreply.github.com>
  • Loading branch information
2 people authored and fboucquez committed Oct 31, 2021
1 parent 40c8b75 commit 6f7a8e4
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 35 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ before_script:
- VERSION="$(node_load_version)"
- log_env_variables
script:
- npm run build:clients
# - npm run build:clients
- npm run build
jobs:
include:
Expand Down
3 changes: 2 additions & 1 deletion src/config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@
"PEER_NODE_PORT": 7900,
"REQUEST_TIMEOUT": 5000,
"CONTROLLER_ENDPOINT": "http://34.252.126.146:7890",
"NETWORK_IDENTIFIER": 152
"NETWORK_IDENTIFIER": 152,
"NUMBER_OF_NODE_REQUEST_CHUNK": 10
}
5 changes: 5 additions & 0 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface Symbol {

interface Monitor {
NODE_MONITOR_SCHEDULE_INTERVAL: number;
NUMBER_OF_NODE_REQUEST_CHUNK: number;
CHAIN_HEIGHT_MONITOR_SCHEDULE_INTERVAL: number;
GEOLOCATION_MONITOR_SCHEDULE_INTERVAL: number;
API_NODE_PORT: number;
Expand Down Expand Up @@ -50,6 +51,7 @@ export const symbol: Symbol = {

export const monitor: Monitor = {
NODE_MONITOR_SCHEDULE_INTERVAL: Number(process.env.NODE_MONITOR_SCHEDULE_INTERVAL) || config.NODE_MONITOR_SCHEDULE_INTERVAL,
NUMBER_OF_NODE_REQUEST_CHUNK: Number(process.env.NUMBER_OF_NODE_REQUEST_CHUNK) || config.NUMBER_OF_NODE_REQUEST_CHUNK,
CHAIN_HEIGHT_MONITOR_SCHEDULE_INTERVAL:
Number(process.env.CHAIN_HEIGHT_MONITOR_SCHEDULE_INTERVAL) || config.CHAIN_HEIGHT_MONITOR_SCHEDULE_INTERVAL,
GEOLOCATION_MONITOR_SCHEDULE_INTERVAL:
Expand Down Expand Up @@ -86,6 +88,9 @@ export const verifyConfig = (cfg: Config): boolean => {
if (isNaN(cfg.monitor.NODE_MONITOR_SCHEDULE_INTERVAL) || cfg.monitor.NODE_MONITOR_SCHEDULE_INTERVAL < 0)
error = 'Invalid "NODE_MONITOR_SCHEDULE_INTERVAL"';

if (isNaN(cfg.monitor.NUMBER_OF_NODE_REQUEST_CHUNK) || cfg.monitor.NUMBER_OF_NODE_REQUEST_CHUNK < 0)
error = 'Invalid "NUMBER_OF_NODE_REQUEST_CHUNK"';

if (isNaN(cfg.monitor.API_NODE_PORT) || cfg.monitor.API_NODE_PORT <= 0 || cfg.monitor.API_NODE_PORT >= 10000)
error = 'Invalid "API_NODE_PORT"';

Expand Down
38 changes: 36 additions & 2 deletions src/models/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,46 @@ const NodeSchema: Schema = new Schema({
type: Boolean,
required: false,
},
isHttpsEnabled: {
type: Boolean,
required: false,
},
nodeStatus: {
type: {
apiNode: {
type: String,
required: false,
},
db: {
type: String,
required: false,
},
},
required: false,
},
chainHeight: {
type: Number,
required: false,
},
finalizationHeight: {
type: Number,
finalization: {
type: {
height: {
type: Number,
required: false,
},
epoch: {
type: Number,
required: false,
},
point: {
type: Number,
required: false,
},
hash: {
type: String,
required: false,
},
},
required: false,
},
nodePublicKey: {
Expand Down
126 changes: 110 additions & 16 deletions src/services/ApiNodeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,24 @@ import { Logger } from '@src/infrastructure';

const logger: winston.Logger = Logger.getLogger(basename(__filename));

interface NodeStatus {
apiNode: string;
db: string;
}

interface FinalizedBlock {
height: number;
epoch: number;
point: number;
hash: string;
}

export interface ApiStatus {
isAvailable: boolean;
isHttpsEnabled?: boolean;
nodeStatus?: NodeStatus;
chainHeight?: number;
finalizationHeight?: number;
finalization?: FinalizedBlock;
nodePublicKey?: string;
restVersion?: string;
lastStatusCheck: number;
Expand Down Expand Up @@ -38,44 +52,124 @@ export interface ChainInfo {
};
}

export class ApiNodeService {
static getStatus = async (host: string, port: number): Promise<ApiStatus> => {
// logger.info(`Getting api status for: ${host}`);
export interface ServerInfo {
restVersion: string;
sdkVersion: string;
deployment: {
deploymentTool: string;
deploymentToolVersion: string;
lastUpdatedDate: string;
};
}

export class ApiNodeService {
static getStatus = async (host: string): Promise<ApiStatus> => {
try {
const nodeInfo = (await HTTP.get(`http://${host}:${port}/node/info`)).data;
const chainInfo = (await HTTP.get(`http://${host}:${port}/chain/info`)).data;
const nodeServer = (await HTTP.get(`http://${host}:${port}/node/server`)).data;
const isHttps = await ApiNodeService.isHttpsEnabled(host);
const protocol = isHttps ? 'https' : 'http';
const port = isHttps ? 3001 : 3000;

return {
logger.info(`Getting node status for: ${protocol}://${host}:${port}`);

const [nodeInfo, chainInfo, nodeServer, nodeHealth] = await Promise.all([
ApiNodeService.getNodeInfo(host, port, protocol),
ApiNodeService.getNodeChainInfo(host, port, protocol),
ApiNodeService.getNodeServer(host, port, protocol),
ApiNodeService.getNodeHealth(host, port, protocol),
]);

let apiStatus = {
isAvailable: true,
chainHeight: chainInfo.height,
finalizationHeight: chainInfo.latestFinalizedBlock.height,
nodePublicKey: nodeInfo.nodePublicKey,
restVersion: nodeServer.serverInfo.restVersion,
lastStatusCheck: Date.now(),
};

if (nodeHealth) {
Object.assign(apiStatus, {
nodeStatus: nodeHealth,
});
}

if (nodeInfo) {
Object.assign(apiStatus, {
isHttpsEnabled: isHttps,
nodePublicKey: nodeInfo.nodePublicKey,
});
}

if (chainInfo) {
Object.assign(apiStatus, {
chainHeight: chainInfo.height,
finalization: {
height: chainInfo.latestFinalizedBlock.height,
epoch: chainInfo.latestFinalizedBlock.finalizationEpoch,
point: chainInfo.latestFinalizedBlock.finalizationPoint,
hash: chainInfo.latestFinalizedBlock.hash,
},
});
}

if (nodeServer) {
Object.assign(apiStatus, {
restVersion: nodeServer.restVersion,
});
}

return apiStatus;
} catch (e) {
logger.error(`Fail to request host node status: ${host}`, e);
return {
isAvailable: false,
lastStatusCheck: Date.now(),
};
}
};

static getNodeInfo = async (host: string, port: number): Promise<NodeInfo | null> => {
static getNodeInfo = async (host: string, port: number, protocol: string): Promise<NodeInfo | null> => {
try {
return (await HTTP.get(`http://${host}:${port}/node/info`)).data;
return (await HTTP.get(`${protocol}://${host}:${port}/node/info`)).data;
} catch (e) {
logger.error(`Fail to request /node/info: ${host}`, e);
return null;
}
};

static getNodeChainInfo = async (host: string, port: number): Promise<ChainInfo | null> => {
static getNodeChainInfo = async (host: string, port: number, protocol: string): Promise<ChainInfo | null> => {
try {
return (await HTTP.get(`http://${host}:${port}/chain/info`)).data;
return (await HTTP.get(`${protocol}://${host}:${port}/chain/info`)).data;
} catch (e) {
logger.error(`Fail to request /chain/info: ${host}`, e);
return null;
}
};

static getNodeServer = async (host: string, port: number, protocol: string): Promise<ServerInfo | null> => {
try {
const nodeServerInfo = (await HTTP.get(`${protocol}://${host}:${port}/node/server`)).data;

return nodeServerInfo.serverInfo;
} catch (e) {
logger.error(`Fail to request /node/server: ${host}`, e);
return null;
}
};

static getNodeHealth = async (host: string, port: number, protocol: string): Promise<NodeStatus | null> => {
try {
const health = (await HTTP.get(`${protocol}://${host}:${port}/node/health`)).data;

return health.status;
} catch (e) {
logger.error(`Fail to request /node/health: ${host}`, e);
return null;
}
};

static isHttpsEnabled = async (host: string, port = 3001): Promise<boolean> => {
try {
await HTTP.get(`https://${host}:${port}/chain/info`);
return true;
} catch (e) {
return false;
}
};
}
19 changes: 13 additions & 6 deletions src/services/ChainHeightMonitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,14 @@ export class ChainHeightMonitor {
this.nodeList = (await DataBase.getNodeList()).filter((node) => isAPIRole(node.roles));
} catch (e) {
logger.error('Failed to get node list. Use nodes from config');
for (const nodeUrl of symbol.NODES) {
const node = await ApiNodeService.getNodeInfo(new URL(nodeUrl).host, Number(monitor.API_NODE_PORT));
for (const node of symbol.NODES) {
const url = new URL(node);
const nodeInfo = await ApiNodeService.getNodeInfo(url.host, Number(url.port), url.protocol);

if (node) {
const status = await ApiNodeService.getStatus(node.host, monitor.API_NODE_PORT);
if (nodeInfo) {
const status = await ApiNodeService.getStatus(nodeInfo.host);

if (status.isAvailable) this.nodeList.push({ ...node, rewardPrograms: [] });
if (status.isAvailable) this.nodeList.push({ ...nodeInfo, rewardPrograms: [] });
}
}
}
Expand All @@ -79,7 +80,13 @@ export class ChainHeightMonitor {
private getNodeChainHeight = async () => {
logger.info(`Getting height stats for ${this.nodeList.length} nodes`);
const nodes: INode[] = this.nodeList;
const nodeChainInfoPromises = nodes.map((node) => ApiNodeService.getNodeChainInfo(node.host, monitor.API_NODE_PORT));
const nodeChainInfoPromises = nodes.map((node) => {
const isHttps = node.apiStatus?.isHttpsEnabled;
const protocol = isHttps ? 'https' : 'http';
const port = isHttps ? 3001 : 3000;

return ApiNodeService.getNodeChainInfo(node.host, port, protocol);
});
const nodeChainInfoList = await Promise.all(nodeChainInfoPromises);

for (let chainInfo of nodeChainInfoList) {
Expand Down
11 changes: 6 additions & 5 deletions src/services/GeolocationMonitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,14 @@ export class GeolocationMonitor {
try {
this.nodeList = await DataBase.getNodeList();
} catch (e) {
for (const nodeUrl of symbol.NODES) {
const node = await ApiNodeService.getNodeInfo(new URL(nodeUrl).host, Number(monitor.API_NODE_PORT));
for (const node of symbol.NODES) {
const url = new URL(node);
const nodeInfo = await ApiNodeService.getNodeInfo(url.host, Number(url.port), url.protocol);

if (node) {
const status = await ApiNodeService.getStatus(node.host, monitor.API_NODE_PORT);
if (nodeInfo) {
const status = await ApiNodeService.getStatus(nodeInfo.host);

if (status.isAvailable) this.nodeList.push({ ...node, rewardPrograms: [] });
if (status.isAvailable) this.nodeList.push({ ...nodeInfo, rewardPrograms: [] });
}
}
}
Expand Down
16 changes: 12 additions & 4 deletions src/services/Http.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import axios, { AxiosResponse, AxiosRequestConfig } from 'axios';
const REQEST_TIMEOUT = 10000;
import { monitor } from '@src/config';

const REQEST_TIMEOUT = monitor.REQUEST_TIMEOUT;

export class HTTP {
static get(url: string, config?: AxiosRequestConfig | undefined): Promise<AxiosResponse<any>> {
return new Promise<AxiosResponse<any>>((resolve, reject) => {
setTimeout(() => {
const timeout = setTimeout(() => {
reject(Error(`HTTP get request failed. Timeout error`));
}, REQEST_TIMEOUT + REQEST_TIMEOUT * 0.1);

axios
.get(url, { timeout: REQEST_TIMEOUT, ...config })
.then(resolve)
.catch(reject);
.then((response) => {
clearTimeout(timeout);
resolve(response);
})
.catch((e) => {
clearTimeout(timeout);
reject(e);
});
});
}
}

0 comments on commit 6f7a8e4

Please sign in to comment.