Skip to content

Commit

Permalink
Merge pull request #12 from Alethio/ws-protocol-improvements
Browse files Browse the repository at this point in the history
Ws protocol improvements
  • Loading branch information
baxy authored May 21, 2019
2 parents 601279a + e3806fc commit beebca3
Show file tree
Hide file tree
Showing 10 changed files with 592 additions and 608 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ version: 2
jobs:
build:
docker:
- image: circleci/node:latest
- image: circleci/node:10
working_directory: ~/repo
steps:
- checkout
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Changelog
All notable changes to this project will be documented in this file.

## [2.5.0] - 2019-05-21
- Add WebSocket API improvements
- Add support for "stats.pendingTxs"
- Remove ".net" subdomain

## [2.4.21] - 2019-04-02
- Remove "sprintf" npm package due to memory leaks
- Update npm dependent packages to latest versions
Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
# ethstats-cli [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url] [![Coverage percentage][coveralls-image]][coveralls-url]

> EthStats - Network Monitor - CLI Client
> EthStats - CLI Client
>
>
> The application connects to your Ethereum node through RPC and extract data that will be sent to the `EthStats - Network Monitor - Server` for analytics purposes.
> The application connects to your Ethereum node through RPC and extract data that will be sent to the `EthStats - Server` for analytics purposes.
# Live deployments
See active nodes or add your own on the following running deployments of the EthStats Network Monitor

- Mainnet - [net.ethstats.io](https://net.ethstats.io/)
- Görli Testnet - [net.goerli.ethstats.io](https://net.goerli.ethstats.io/)
- Mainnet - [ethstats.io](https://ethstats.io/)
- Rinkeby Testnet - [rinkeby.ethstats.io](https://rinkeby.ethstats.io/)
- Görli Testnet - [goerli.ethstats.io](https://goerli.ethstats.io/)

# Supported Ethereum nodes
Geth, Parity, Pantheon, basically any Ethereum node that has RPC enabled.
Expand Down Expand Up @@ -151,7 +152,7 @@ Keep in mind that the list of recovery hashes sent in the email expires in 30 mi

--server-url Server URL (Must include protocol and port if any)
--net, -n Specify Ethereum network your node is running on (Default: mainnet)
Available networks: mainnet|goerli
Available networks: mainnet|rinkeby|goerli
If --server-url is specified, this option is ignored

--client-url Client URL (Must include protocol and port if any; Default: http://localhost:8545)
Expand Down
2 changes: 1 addition & 1 deletion lib/Cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default class CLI {
--server-url Server URL (Must include protocol and port if any)
--net, -n Specify Ethereum network your node is running on (Default: mainnet)
Available networks: mainnet|goerli
Available networks: mainnet|rinkeby|goerli
If --server-url is specified, this option is ignored
--client-url Client URL (Must include protocol and port if any; Default: http://localhost:8545)
Expand Down
2 changes: 1 addition & 1 deletion lib/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Configstore from 'configstore';
const config = {
configStore: new Configstore('ethstats-cli'),
configurator: {
url: 'https://config.net.ethstats.io:443'
url: 'https://config.ethstats.io:443'
},
server: {
net: 'mainnet'
Expand Down
103 changes: 45 additions & 58 deletions lib/Server.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ export default class Server {
this.isLoggedIn = false;
this.socketIsOpen = false;

this.pingIntervalId = null;
this.pingInterval = 30000;
this.pingTimestamp = null;

this.url = null;
this.socket = null;
this.configToSave = null;
Expand Down Expand Up @@ -164,61 +160,60 @@ export default class Server {
this.log.error('Reconnect to ethstats server failed! Maximum attempts reached. Please try again later or contact ethstats support.', false, true);
});

this.socket.on('data', data => {
this.log.debug(`Data received for topic: "${data.topic}"`);
this.socket.on('data', message => {
this.log.debug(`Data received for topic: "${message.topic}"`);

switch (data.topic) {
switch (message.topic) {
case 'invalidMessage':
this.resolveResponse(message.topic, message.payload);
break;
case 'requestRateLimitReached':
this.resolveResponse(message.topic, message.payload);
break;
case 'registerNodeResponse':
this.resolveRegisterNodeResponse(data.message);
this.resolveRegisterNodeResponse(message.payload);
break;
case 'loginResponse':
this.resolveLoginResponse(data.message);
this.resolveLoginResponse(message.payload);
break;
case 'logoutResponse':
this.resolveResponse(data.topic, data.message);
this.resolveResponse(message.topic, message.payload);
break;
case 'connectionResponse':
this.resolveResponse(data.topic, data.message);
this.resolveResponse(message.topic, message.payload);
break;
case 'syncResponse':
this.resolveResponse(data.topic, data.message);
this.resolveResponse(message.topic, message.payload);
break;
case 'statsResponse':
this.resolveResponse(data.topic, data.message);
this.resolveResponse(message.topic, message.payload);
break;
case 'usageResponse':
this.resolveResponse(data.topic, data.message);
this.resolveResponse(message.topic, message.payload);
break;
case 'blockResponse':
this.resolveResponse(data.topic, data.message);
this.resolveResponse(message.topic, message.payload);
break;
case 'checkChainRequest':
this.client.getBlockHashes(data.message);
case 'ping':
this.send('pong', message.payload);
break;
case 'checkChainResponse':
this.client.resolveCheckChainResponse(data.message);
case 'pongResponse':
this.resolveResponse(message.topic, message.payload);
break;
case 'requestRateLimitReached':
this.resolveResponse(data.topic, data.message);
case 'checkChain':
this.client.getBlockHashes(message.payload.blockNumber);
break;
case 'history':
this.client.getHistory(data.message);
case 'checkChainResponse':
this.client.resolveCheckChainResponse(message.payload);
break;
case 'historyResponse':
this.resolveResponse(data.topic, data.message);
case 'getBlocks':
this.client.getBlocks(message.payload);
break;
case 'node-pong': {
let receivedPingTimestamp = data.message.clientTime;
if (receivedPingTimestamp === this.pingTimestamp) {
let latency = Math.ceil((Date.now() - receivedPingTimestamp) / 2);
this.send('latency', {latency});
}

case 'getBlocksResponse':
this.resolveResponse(message.topic, message.payload);
break;
}

default:
this.log.info(`Undefined topic: ${data.topic}`);
this.log.info(`Undefined topic: ${message.topic}`);
break;
}
});
Expand All @@ -229,11 +224,10 @@ export default class Server {
this.socket.destroy();
}

clearInterval(this.pingIntervalId);
clearInterval(this.checkLastBlockInterval);
}

send(topic, msg) {
send(topic, payload) {
let result = false;
let allowedTopicsWhenNotLoggedIn = [
{topic: 'login'},
Expand All @@ -245,35 +239,35 @@ export default class Server {
if (this.socket && this.socketIsOpen && (this.isLoggedIn || isAllowedTopicWhenNotLoggedIn)) {
result = this.socket.write({
topic: topic,
msg: msg
payload: payload
});

this.log.info(`Sending message on topic: "${topic}"`);

if (topic === 'block') {
msg = {
number: msg.number,
hash: msg.hash,
parentHash: msg.parentHash
payload = {
number: payload.number,
hash: payload.hash,
parentHash: payload.parentHash
};
}

if (topic === 'history') {
let result = [];
for (let i = 0; i < msg.length; i++) {
result.push({number: msg[i].number});
if (topic === 'getBlocksData') {
let tmpPayload = [];
for (let i = 0; i < payload.length; i++) {
tmpPayload.push({number: payload[i].number});
}

msg = result;
payload = tmpPayload;
}

this.log.debug(`Sent "${topic}" message: ${JSON.stringify(msg)}`);
this.log.debug(`Sent message on "${topic}" with payload: ${JSON.stringify(payload)}`);
}

return result;
}

sendAndWait(topic, msg) {
sendAndWait(topic, payload) {
let allowedTopicsWhenNotLoggedIn = [
{topic: 'checkIfNodeExists'},
{topic: 'checkIfEmailExists'},
Expand All @@ -292,12 +286,12 @@ export default class Server {
return new Promise((resolve, reject) => {
try {
if (this.socket && this.socketIsOpen && (this.isLoggedIn || isAllowedTopicWhenNotLoggedIn)) {
this.socket.writeAndWait({topic: topic, msg: msg}, response => {
this.socket.writeAndWait({topic: topic, payload: payload}, response => {
resolve(response);
});

this.log.info(`Sending message on topic: "${topic}"`, beginWithNewLine);
this.log.debug(`Sent "${topic}" message: ${JSON.stringify(msg)}`);
this.log.debug(`Sent message on "${topic}" with payload: ${JSON.stringify(payload)}`);
} else {
reject(new Error('Not connected to the server or not logged in'));
}
Expand Down Expand Up @@ -352,13 +346,6 @@ export default class Server {
}
});
}

if (!this.pingIntervalId) {
this.pingIntervalId = setInterval(() => {
this.pingTimestamp = Date.now();
this.send('node-ping', {clientTime: this.pingTimestamp});
}, this.pingInterval);
}
} else {
let errorMessage = `Authentication error: ${JSON.stringify(response.errors)}.`;
let possibleFlagErrorType = '';
Expand Down
28 changes: 19 additions & 9 deletions lib/client/protocol/Http.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,16 @@ export default class Http extends Abstract {
this.log.error(this.errorHandler.resolve(error));
}

data = (data === undefined) ? 0 : data;
return callback(null, data);
});
},
pendingTXs: callback => {
this.web3.eth.getBlockTransactionCount('pending', (error, data) => {
if (error) {
this.log.error(this.errorHandler.resolve(error));
}

data = (data === undefined) ? 0 : data;
return callback(null, data);
});
Expand Down Expand Up @@ -261,9 +271,9 @@ export default class Http extends Abstract {

getBlockHashes(blockNumber) {
let result = {
number: null,
hash: null,
parentHash: null
blockNumber: null,
blockHash: null,
blockParentHash: null
};

this.web3.eth.getBlock(blockNumber, false, (error, block) => {
Expand All @@ -272,26 +282,26 @@ export default class Http extends Abstract {
} else if (block === null) {
this.log.error(`Could not get block "${blockNumber}". Your node might be not fully synced.`, false, true);
} else {
result.number = parseInt(block.number, 10);
result.hash = block.hash.toString();
result.parentHash = block.parentHash.toString();
result.blockNumber = parseInt(block.number, 10);
result.blockHash = block.hash.toString();
result.blockParentHash = block.parentHash.toString();
}

this.server.send('checkChainData', result);
});
}

getHistory(range) {
getBlocks(range) {
mapSeries(range, (blockNumber, callback) => {
this.log.debug(`History get block: "${blockNumber}"`);
this.web3.eth.getBlock(blockNumber, false, callback);
}, (error, results) => {
if (error) {
this.log.error(`Error getting history: ${error}`);
this.log.error(`Error getting block history: ${error}`);
results = [];
}

this.server.send('history', results);
this.server.send('getBlocksData', results);
});
}
}
33 changes: 23 additions & 10 deletions lib/client/protocol/WebSocket.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,11 +277,24 @@ export default class WebSocket extends Abstract {
});
allPromises.push(hashrate);

let pendingTXs = this.web3.eth.getBlockTransactionCount('pending').then(data => {
if (data) {
return data;
}

return 0;
}).catch(error => {
this.log.error(this.errorHandler.resolve(error));
return 0;
});
allPromises.push(pendingTXs);

return Promise.all(allPromises).then(promiseResults => {
result.peers = promiseResults[0];
result.gasPrice = promiseResults[1];
result.mining = promiseResults[2];
result.hashrate = promiseResults[3];
result.pendingTXs = promiseResults[4];

this.server.send('stats', result);

Expand Down Expand Up @@ -309,18 +322,18 @@ export default class WebSocket extends Abstract {

getBlockHashes(blockNumber) {
let result = {
number: null,
hash: null,
parentHash: null
blockNumber: null,
blockHash: null,
blockParentHash: null
};

this.web3.eth.getBlock(blockNumber, false).then(block => {
if (block === null) {
this.log.error(`Could not get block "${blockNumber}". Your node might be not fully synced.`, false, true);
} else {
result.number = parseInt(block.number, 10);
result.hash = block.hash.toString();
result.parentHash = block.parentHash.toString();
result.blockNumber = parseInt(block.number, 10);
result.blockHash = block.hash.toString();
result.blockParentHash = block.parentHash.toString();
}

this.server.send('checkChainData', result);
Expand All @@ -329,17 +342,17 @@ export default class WebSocket extends Abstract {
});
}

getHistory(range) {
getBlocks(range) {
let allPromises = range.map(blockNumber => {
this.log.debug(`History get block: "${blockNumber}"`);
return this.web3.eth.getBlock(blockNumber, false);
});

Promise.all(allPromises).then(results => {
this.server.send('history', results);
this.server.send('getBlocksData', results);
}).catch(error => {
this.log.error(`Error getting history: ${error}`);
this.server.send('history', []);
this.log.error(`Error getting block history: ${error}`);
this.server.send('getBlocksData', []);
});
}
}
Loading

0 comments on commit beebca3

Please sign in to comment.