Skip to content
This repository has been archived by the owner on Apr 27, 2021. It is now read-only.

Commit

Permalink
Merge pull request #9 from Alethio/ws-api-docs
Browse files Browse the repository at this point in the history
Ws api docs
  • Loading branch information
baxy authored May 21, 2019
2 parents abaa85e + 62d8f32 commit 0846faa
Show file tree
Hide file tree
Showing 24 changed files with 1,741 additions and 611 deletions.
8 changes: 6 additions & 2 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ ENVIRONMENT=sample

TZ=UTC

NETWORK=mainnet
NETWORK_ID=1
NETWORK_NAME=mainnet

APP_HOST=localhost
APP_PORT=3000
APP_NAME=server

SERVER_PING_INTERVAL=30
SERVER_WS_TIMEOUT=180

LITE=1
LITE_API_PORT=3030
LITE_DB_LIMIT=3000
Expand All @@ -16,7 +20,7 @@ LITE_DB_PERSIST=0
WS_REQUEST_RATE_LIMIT=10000
WS_REQUEST_RATE_INTERVAL=300000

CLIENT_MIN_VERSION=2.4.6
CLIENT_MIN_VERSION=2.5.0
CLIENT_LAST_V1_VERSION=0.1.1

LOG_SHOW_DATETIME=1
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Changelog
All notable changes to this project will be documented in this file.

## [1.5.0] - 2019-05-21
- Add WebSocket API improvements
- Add WebSocket API documentation
- Add support for "stats.pendingTxs"
- Validate networkId on login
- Remove ".net" subdomain

## [1.4.1] - 2019-04-01
- Remove "sprintf" npm package due to memory leaks
- Update Infura library to use latest v3 API
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ethstats-network-server

> EthStats - Network Monitor - Server
> EthStats - Server
>
> The server collects/stores/aggregates data sent through `ethstats-cli`.
Expand Down
3 changes: 2 additions & 1 deletion app/controllers/api/NodesController.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export default class NodesController extends AbstractController {
});

if (nodeExists) {
responseObject.statusCode = 400;
responseObject.body.data.push(resultData);
responseObject.body.success = false;
responseObject.body.errors.push('Node already registered');
Expand All @@ -55,7 +56,7 @@ export default class NodesController extends AbstractController {
this.models.Nodes.add(nodeParams);

let mailTemplate = new NodeRegisterView({
network: this.appConfig.NETWORK,
networkName: this.appConfig.NETWORK_NAME,
nodeName: nodeParams.nodeName,
secretKey: secretKey,
showGethHelp: true
Expand Down
1 change: 1 addition & 0 deletions app/controllers/api/ToolsController.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default class ToolsController extends AbstractController {
this.cache.flushDb();

if (errors.length) {
responseObject.statusCode = 400;
responseObject.body.success = false;
responseObject.body.errors.push('There seem to be some problems executing this request');
}
Expand Down
65 changes: 11 additions & 54 deletions app/controllers/server/AbstractController.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,19 @@ export default class AbstractController {
this.log.info(`[${spark.id}] - Client disconnected`);
}

clientWrite(spark, topic, message) {
clientWrite(spark, topic, payload) {
let session = this.session.getAll(spark.id);
let logType = 'info';
let nodeName = '';

if (message.success !== undefined && message.data !== undefined) {
nodeName = (message.data.length && message.data[0].nodeName) ? `for node '${message.data[0].nodeName}' ` : '';
}

if (message.errors && message.errors.length) {
if (payload.errors && payload.errors.length) {
logType = 'error';
} else if (message.warnings && message.warnings.length) {
} else if (payload.warnings && payload.warnings.length) {
logType = 'warning';
}

this.log[logType](`[${spark.id}] - Data sent ${nodeName}on topic: ${topic} => message: ${JSON.stringify(message)}`);
this.log[logType](`[${spark.id}] - Message sent on topic: ${topic} => payload: ${JSON.stringify(payload)}`);

let objectToSend = this._formatWriteObject(topic, message, session.isV1Client);
let objectToSend = this._formatWriteObject(topic, payload, session.isV1Client);

if (objectToSend === false) {
return false;
Expand All @@ -63,19 +58,19 @@ export default class AbstractController {
return spark.write(objectToSend);
}

_formatWriteObject(topic, message, isV1Client) {
let result = {topic, message};
_formatWriteObject(topic, payload, isV1Client) {
let result = {topic, payload};

if (isV1Client === true) {
switch (topic) {
case 'loginResponse':
result = {emit: ['ready']};
break;
case 'node-pong':
result = {emit: ['node-pong', message]};
result = {emit: ['node-pong', payload]};
break;
case 'history':
result = {emit: ['history', {list: message}]};
case 'getBlocks':
result = {emit: ['history', {list: payload}]};
break;
default:
result = false;
Expand All @@ -86,13 +81,6 @@ export default class AbstractController {
return result;
}

sendLatencyToDeepstream(spark) {
let session = this.session.getAll(spark.id);
if (session.isLoggedIn === true) {
this.dsDataLoader.setRecord(`${this.appConfig.DEEPSTREAM_NAMESPACE}/node/${session.nodeName}/nodeData`, 'nodeData.wsLatency', session.latency);
}
}

async logout(spark) {
let responseObject = this.lodash.cloneDeep(this.responseObject);
let session = this.session.getAll(spark.id);
Expand Down Expand Up @@ -146,37 +134,6 @@ export default class AbstractController {
});
}

setLastActivityTimestamp(spark) {
let session = this.session.getAll(spark.id);

if (session.isLoggedIn === true) {
let nodeName = session.nodeName;
let totalOnlineTime = session.totalOnlineTime;
let firstLoginTimestamp = parseInt(session.firstLoginTimestamp, 10);
let lastActivityTimestamp = session.lastActivityTimestamp || 0;
let currentTimestamp = Date.now();

// throttle last activity once every 1 minute
if (currentTimestamp - lastActivityTimestamp >= 60000) {
totalOnlineTime = totalOnlineTime.plus((lastActivityTimestamp === 0) ? 0 : currentTimestamp - lastActivityTimestamp);
this.session.setVar(spark.id, 'totalOnlineTime', totalOnlineTime);

let onlineTimePercent = totalOnlineTime.dividedBy(currentTimestamp - firstLoginTimestamp).multipliedBy(100).toFixed(2);
onlineTimePercent = Math.max(0, Math.min(100, onlineTimePercent));
this.dsDataLoader.setRecord(`${this.appConfig.DEEPSTREAM_NAMESPACE}/node/${session.nodeName}/nodeData`, 'nodeData.onlineTimePercent', onlineTimePercent);

this.session.setVar(spark.id, 'lastActivityTimestamp', currentTimestamp);
this.models.Nodes.update({
nodeShard: nodeName.charAt(0).toLowerCase(),
nodeName: nodeName
}, {
lastActivityTimestamp: new Date(currentTimestamp).toISOString(),
totalOnlineTime: totalOnlineTime.toString(10)
});
}
}
}

requestCheckChain(spark, params) {
let session = this.session.getAll(spark.id);

Expand All @@ -189,7 +146,7 @@ export default class AbstractController {
if ((checkChainLastRequestedBlockNumber === null || receivedBlockNumber > checkChainLastRequestedBlockNumber) && receivedBlockNumber - checkChainLastRequestedBlockNumber >= chainDetectionRate) {
let blockNumberToCheck = receivedBlockNumber - chainDetectionRate;
this.log.debug(`[${spark.id}] - Check chain request: ${blockNumberToCheck}`);
this.clientWrite(spark, 'checkChainRequest', blockNumberToCheck);
this.clientWrite(spark, 'checkChain', {blockNumber: blockNumberToCheck});
this.session.setVar(spark.id, 'checkChainLastRequestedBlockNumber', receivedBlockNumber);
this.session.incVar(spark.id, 'checkChainRequestCount', 1);
checkChainRequestCount += 1;
Expand Down
11 changes: 9 additions & 2 deletions app/controllers/server/AuthController.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default class AuthController extends AbstractController {
secretKey: {type: 'string'},
coinbase: {type: ['string', 'null']},
node: {type: ['string', 'null']},
net: {type: ['string', 'null']},
net: {type: ['integer', 'string']},
protocol: {type: ['number', 'string', 'null']},
api: {type: ['string', 'null']},
os: {type: 'string'},
Expand All @@ -25,7 +25,7 @@ export default class AuthController extends AbstractController {
memory: {type: ['string', 'null']},
disk: {type: ['string', 'null']}
},
required: ['nodeName', 'secretKey', 'os', 'osVersion', 'client']
required: ['nodeName', 'secretKey', 'os', 'osVersion', 'client', 'net']
}
};

Expand Down Expand Up @@ -72,6 +72,13 @@ export default class AuthController extends AbstractController {
return responseObject;
}

if (this.appConfig.NETWORK_ID !== parseInt(params.net, 10)) {
responseObject.success = false;
responseObject.errors.push(`The node is NOT on the '${this.appConfig.NETWORK_NAME}' network`);

return responseObject;
}

let lastIp = spark.address.ip;

params.ip = lastIp;
Expand Down
30 changes: 15 additions & 15 deletions app/controllers/server/BlocksController.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export default class BlocksController extends AbstractController {
params.transactions = params.transactionHashes;
}

let validParams = this.validator.validate(requestValidation[this.appConfig.NETWORK].request, params);
let validParams = this.validator.validate((requestValidation[this.appConfig.NETWORK_NAME] || requestValidation.mainnet).request, params);
if (!validParams) {
responseObject.success = false;
responseObject.errors = this.validatorError.getReadableErrorMessages(this.validator.errors);
Expand Down Expand Up @@ -161,8 +161,8 @@ export default class BlocksController extends AbstractController {
let blocksToGet = this.lodash.range(Math.max(0, receivedBlockNumber - historyMaxBlocks), receivedBlockNumber, 1);

if (blocksToGet.length) {
this.log.debug(`[${spark.id}] - Get history: ${this.lodash.min(blocksToGet)}..${this.lodash.max(blocksToGet)}`);
this.clientWrite(spark, 'history', blocksToGet);
this.log.debug(`[${spark.id}] - Get block history: ${this.lodash.min(blocksToGet)}..${this.lodash.max(blocksToGet)}`);
this.clientWrite(spark, 'getBlocks', blocksToGet);
this.historyFulfilled = true;
}
}
Expand Down Expand Up @@ -219,23 +219,23 @@ export default class BlocksController extends AbstractController {
}
}

async checkChain(spark, block) {
async checkChain(spark, data) {
let responseObject = this.lodash.cloneDeep(this.responseObject);
let blockNumberToCheck = parseInt(block.number, 10);
let blockNumberToCheck = parseInt(data.blockNumber, 10);
let requestValidation = {
request: {
type: 'object',
additionalProperties: false,
properties: {
number: {type: 'integer'},
hash: {type: 'string'},
parentHash: {type: 'string'}
blockNumber: {type: 'integer'},
blockHash: {type: 'string'},
blockParentHash: {type: 'string'}
},
required: ['number', 'hash', 'parentHash']
required: ['blockNumber', 'blockHash', 'blockParentHash']
}
};

let validParams = this.validator.validate(requestValidation.request, block);
let validParams = this.validator.validate(requestValidation.request, data);
if (!validParams) {
responseObject.success = false;
responseObject.errors = this.validatorError.getReadableErrorMessages(this.validator.errors);
Expand All @@ -244,7 +244,7 @@ export default class BlocksController extends AbstractController {
}

this.session.incVar(spark.id, 'checkChainRequestCount', -1);
let notOnNetworkErrorMsg = `The node is NOT on the '${this.appConfig.NETWORK}' network`;
let notOnNetworkErrorMsg = `The node is NOT on the '${this.appConfig.NETWORK_NAME}' network`;

return this.infura.getLastBlockNumber().then(blockNumber => {
let lastBlockNumber = parseInt(this.bigNumberUtils.getInt(blockNumber), 10);
Expand All @@ -258,8 +258,8 @@ export default class BlocksController extends AbstractController {

return this.infura.getBlockByNumber(blockNumberToCheck).then(infuraBlock => {
if (infuraBlock) {
if (infuraBlock.hash === block.hash && infuraBlock.parentHash === block.parentHash) {
this.log.debug(`[${spark.id}] - The node is on '${this.appConfig.NETWORK}' network`);
if (infuraBlock.hash === data.blockHash && infuraBlock.parentHash === data.blockParentHash) {
this.log.debug(`[${spark.id}] - The node is on '${this.appConfig.NETWORK_NAME}' network`);
} else {
responseObject.success = false;
responseObject.errors.push(notOnNetworkErrorMsg);
Expand Down Expand Up @@ -287,9 +287,9 @@ export default class BlocksController extends AbstractController {
return Promise.all(allPromises).then(() => {
return responseObject;
}).catch(error => {
this.log.error(`Error processing history: ${error}`);
this.log.error(`Error processing block history: ${error}`);
responseObject.success = false;
responseObject.errors.push(`Error processing history: ${error}`);
responseObject.errors.push(`Error processing block history: ${error}`);

return responseObject;
});
Expand Down
79 changes: 78 additions & 1 deletion app/controllers/server/NodesController.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export default class NodesController extends AbstractController {
this.models.Nodes.add(nodeParams);

let mailTemplate = new NodeRegisterView({
network: this.appConfig.NETWORK,
networkName: this.appConfig.NETWORK_NAME,
nodeName: nodeParams.nodeName,
secretKey: undefined,
showGethHelp: false
Expand Down Expand Up @@ -392,4 +392,81 @@ export default class NodesController extends AbstractController {

return responseObject;
}

async getLatency(spark, params) {
let responseObject = this.lodash.cloneDeep(this.responseObject);
let requestValidation = {
request: {
type: 'object',
additionalProperties: false,
properties: {
timestamp: {type: 'number'}
},
required: ['timestamp']
}
};

let validParams = this.validator.validate(requestValidation.request, params);
if (!validParams) {
responseObject.success = false;
responseObject.errors = this.validatorError.getReadableErrorMessages(this.validator.errors);

return responseObject;
}

let session = this.session.getAll(spark.id);
if (session.lastPingTimestamp === params.timestamp) {
let latency = Math.ceil((Date.now() - session.lastPingTimestamp) / 2);

this.session.setVar(spark.id, 'latency', latency);
this.sendLatencyToDeepstream(spark);

responseObject.data.push({latency: latency});
responseObject.dataLength = responseObject.data.length;
} else {
responseObject.success = false;
responseObject.errors.push('Wrong timestamp');
}

return responseObject;
}

sendLatencyToDeepstream(spark) {
let session = this.session.getAll(spark.id);
if (session.isLoggedIn === true) {
this.dsDataLoader.setRecord(`${this.appConfig.DEEPSTREAM_NAMESPACE}/node/${session.nodeName}/nodeData`, 'nodeData.wsLatency', session.latency);
}
}

saveLastActivityTimestamp(spark) {
let session = this.session.getAll(spark.id);

if (session.isLoggedIn === true) {
let nodeName = session.nodeName;
let totalOnlineTime = session.totalOnlineTime;
let firstLoginTimestamp = parseInt(session.firstLoginTimestamp, 10);
let lastActivityTimestamp = session.lastActivityTimestamp;
let lastSavedLastActivityTimestamp = session.lastSavedLastActivityTimestamp;
let currentTimestamp = Date.now();

// save last activity in the DB once every min 1 minute
if (currentTimestamp - lastSavedLastActivityTimestamp >= 60000) {
totalOnlineTime = totalOnlineTime.plus(lastActivityTimestamp - lastSavedLastActivityTimestamp);
this.session.setVar(spark.id, 'totalOnlineTime', totalOnlineTime);

let onlineTimePercent = totalOnlineTime.dividedBy(currentTimestamp - firstLoginTimestamp).multipliedBy(100).toFixed(2);
onlineTimePercent = Math.max(0, Math.min(100, onlineTimePercent));
this.dsDataLoader.setRecord(`${this.appConfig.DEEPSTREAM_NAMESPACE}/node/${session.nodeName}/nodeData`, 'nodeData.onlineTimePercent', onlineTimePercent);

this.session.setVar(spark.id, 'lastSavedLastActivityTimestamp', currentTimestamp);
this.models.Nodes.update({
nodeShard: nodeName.charAt(0).toLowerCase(),
nodeName: nodeName
}, {
lastActivityTimestamp: new Date(lastActivityTimestamp).toISOString(),
totalOnlineTime: totalOnlineTime.toString(10)
});
}
}
}
}
Loading

0 comments on commit 0846faa

Please sign in to comment.