Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable https #64

Merged
merged 13 commits into from
Oct 16, 2021
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
fboucquez marked this conversation as resolved.
Show resolved Hide resolved
- 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);
});
});
}
}
Loading