mirror of
https://github.com/pezkuwichain/pezkuwi-telemetry.git
synced 2026-05-30 17:01:07 +00:00
Remove JS backend (#219)
* chore: Remove JS backend * chore: Update travis to run Rust backend tests * chore(travis): Install dependencies before building
This commit is contained in:
+30
-4
@@ -1,11 +1,37 @@
|
|||||||
sudo: false
|
language: rust
|
||||||
|
|
||||||
language: node_js
|
cache:
|
||||||
|
cargo: true
|
||||||
|
|
||||||
|
rust:
|
||||||
|
- stable
|
||||||
|
- beta
|
||||||
|
- nightly
|
||||||
|
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- CLIPPY_TOOLCHAIN=nightly
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
allow_failures:
|
||||||
- node_js: "10"
|
- rust: nightly
|
||||||
|
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- source $HOME/.nvm/nvm.sh
|
||||||
|
- nvm install --lts
|
||||||
|
- nvm use --lts
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- if [ $TRAVIS_RUST_VERSION = $CLIPPY_TOOLCHAIN ]; then rustup component add clippy-preview --toolchain=$CLIPPY_TOOLCHAIN; fi
|
||||||
|
- curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f
|
||||||
|
|
||||||
|
script: ./test.sh
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
- yarn
|
||||||
- yarn build:all
|
- yarn build:all
|
||||||
- yarn test
|
- yarn test
|
||||||
|
- cd backend && cargo test
|
||||||
|
|||||||
-24
@@ -1,24 +0,0 @@
|
|||||||
declare module 'iplocation' {
|
|
||||||
namespace iplocation {
|
|
||||||
export interface LocationData {
|
|
||||||
as?: string;
|
|
||||||
city: string;
|
|
||||||
country?: string;
|
|
||||||
countryCode?: string;
|
|
||||||
isp?: string;
|
|
||||||
latitude: number;
|
|
||||||
longitude: number;
|
|
||||||
org?: string;
|
|
||||||
query?: string;
|
|
||||||
region?: string;
|
|
||||||
regionName?: string;
|
|
||||||
status: string;
|
|
||||||
timezone?: string;
|
|
||||||
zip?: string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function iplocation(ip: string, providers: any[], callback: (err: Error, result: iplocation.LocationData) => void): void;
|
|
||||||
|
|
||||||
export = iplocation;
|
|
||||||
}
|
|
||||||
Generated
-466
@@ -1,466 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "dotstats-server",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"lockfileVersion": 1,
|
|
||||||
"requires": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@types/body-parser": {
|
|
||||||
"version": "1.17.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.0.tgz",
|
|
||||||
"integrity": "sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w==",
|
|
||||||
"requires": {
|
|
||||||
"@types/connect": "3.4.32",
|
|
||||||
"@types/node": "10.3.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@types/connect": {
|
|
||||||
"version": "3.4.32",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz",
|
|
||||||
"integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==",
|
|
||||||
"requires": {
|
|
||||||
"@types/node": "10.3.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@types/events": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz",
|
|
||||||
"integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA=="
|
|
||||||
},
|
|
||||||
"@types/express": {
|
|
||||||
"version": "4.16.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.16.0.tgz",
|
|
||||||
"integrity": "sha512-TtPEYumsmSTtTetAPXlJVf3kEqb6wZK0bZojpJQrnD/djV4q1oB6QQ8aKvKqwNPACoe02GNiy5zDzcYivR5Z2w==",
|
|
||||||
"requires": {
|
|
||||||
"@types/body-parser": "1.17.0",
|
|
||||||
"@types/express-serve-static-core": "4.16.0",
|
|
||||||
"@types/serve-static": "1.13.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@types/express-serve-static-core": {
|
|
||||||
"version": "4.16.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.0.tgz",
|
|
||||||
"integrity": "sha512-lTeoCu5NxJU4OD9moCgm0ESZzweAx0YqsAcab6OB0EB3+As1OaHtKnaGJvcngQxYsi9UNv0abn4/DRavrRxt4w==",
|
|
||||||
"requires": {
|
|
||||||
"@types/events": "1.2.0",
|
|
||||||
"@types/node": "10.3.3",
|
|
||||||
"@types/range-parser": "1.2.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@types/mime": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-A2TAGbTFdBw9azHbpVd+/FkdW2T6msN1uct1O9bH3vTerEHKZhTXJUQXy+hNq1B0RagfU8U+KBdqiZpxjhOUQA=="
|
|
||||||
},
|
|
||||||
"@types/node": {
|
|
||||||
"version": "10.3.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.3.3.tgz",
|
|
||||||
"integrity": "sha512-/gwCgiI2e9RzzZTKbl+am3vgNqOt7a9fJ/uxv4SqYKxenoEDNVU3KZEadlpusWhQI0A0dOrZ0T68JYKVjzmgdQ=="
|
|
||||||
},
|
|
||||||
"@types/range-parser": {
|
|
||||||
"version": "1.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.2.tgz",
|
|
||||||
"integrity": "sha512-HtKGu+qG1NPvYe1z7ezLsyIaXYyi8SoAVqWDZgDQ8dLrsZvSzUNCwZyfX33uhWxL/SU0ZDQZ3nwZ0nimt507Kw=="
|
|
||||||
},
|
|
||||||
"@types/serve-static": {
|
|
||||||
"version": "1.13.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz",
|
|
||||||
"integrity": "sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q==",
|
|
||||||
"requires": {
|
|
||||||
"@types/express-serve-static-core": "4.16.0",
|
|
||||||
"@types/mime": "2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@types/ws": {
|
|
||||||
"version": "5.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-5.1.2.tgz",
|
|
||||||
"integrity": "sha512-NkTXUKTYdXdnPE2aUUbGOXE1XfMK527SCvU/9bj86kyFF6kZ9ZnOQ3mK5jADn98Y2vEUD/7wKDgZa7Qst2wYOg==",
|
|
||||||
"requires": {
|
|
||||||
"@types/events": "1.2.0",
|
|
||||||
"@types/node": "10.3.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"accepts": {
|
|
||||||
"version": "1.3.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
|
|
||||||
"integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
|
|
||||||
"requires": {
|
|
||||||
"mime-types": "2.1.18",
|
|
||||||
"negotiator": "0.6.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"array-flatten": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
|
||||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
|
||||||
},
|
|
||||||
"async-limiter": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
|
|
||||||
},
|
|
||||||
"body-parser": {
|
|
||||||
"version": "1.18.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz",
|
|
||||||
"integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=",
|
|
||||||
"requires": {
|
|
||||||
"bytes": "3.0.0",
|
|
||||||
"content-type": "1.0.4",
|
|
||||||
"debug": "2.6.9",
|
|
||||||
"depd": "1.1.2",
|
|
||||||
"http-errors": "1.6.3",
|
|
||||||
"iconv-lite": "0.4.19",
|
|
||||||
"on-finished": "2.3.0",
|
|
||||||
"qs": "6.5.1",
|
|
||||||
"raw-body": "2.3.2",
|
|
||||||
"type-is": "1.6.16"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"bytes": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
|
||||||
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
|
|
||||||
},
|
|
||||||
"content-disposition": {
|
|
||||||
"version": "0.5.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
|
|
||||||
"integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ="
|
|
||||||
},
|
|
||||||
"content-type": {
|
|
||||||
"version": "1.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
|
|
||||||
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
|
|
||||||
},
|
|
||||||
"cookie": {
|
|
||||||
"version": "0.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
|
|
||||||
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
|
|
||||||
},
|
|
||||||
"cookie-signature": {
|
|
||||||
"version": "1.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
|
||||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
|
||||||
},
|
|
||||||
"debug": {
|
|
||||||
"version": "2.6.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
|
||||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
|
||||||
"requires": {
|
|
||||||
"ms": "2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"depd": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
|
||||||
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
|
|
||||||
},
|
|
||||||
"destroy": {
|
|
||||||
"version": "1.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
|
||||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
|
||||||
},
|
|
||||||
"ee-first": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
|
||||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
|
||||||
},
|
|
||||||
"encodeurl": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
|
||||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
|
||||||
},
|
|
||||||
"escape-html": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
|
||||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
|
||||||
},
|
|
||||||
"etag": {
|
|
||||||
"version": "1.8.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
|
||||||
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
|
|
||||||
},
|
|
||||||
"express": {
|
|
||||||
"version": "4.16.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz",
|
|
||||||
"integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=",
|
|
||||||
"requires": {
|
|
||||||
"accepts": "1.3.5",
|
|
||||||
"array-flatten": "1.1.1",
|
|
||||||
"body-parser": "1.18.2",
|
|
||||||
"content-disposition": "0.5.2",
|
|
||||||
"content-type": "1.0.4",
|
|
||||||
"cookie": "0.3.1",
|
|
||||||
"cookie-signature": "1.0.6",
|
|
||||||
"debug": "2.6.9",
|
|
||||||
"depd": "1.1.2",
|
|
||||||
"encodeurl": "1.0.2",
|
|
||||||
"escape-html": "1.0.3",
|
|
||||||
"etag": "1.8.1",
|
|
||||||
"finalhandler": "1.1.1",
|
|
||||||
"fresh": "0.5.2",
|
|
||||||
"merge-descriptors": "1.0.1",
|
|
||||||
"methods": "1.1.2",
|
|
||||||
"on-finished": "2.3.0",
|
|
||||||
"parseurl": "1.3.2",
|
|
||||||
"path-to-regexp": "0.1.7",
|
|
||||||
"proxy-addr": "2.0.3",
|
|
||||||
"qs": "6.5.1",
|
|
||||||
"range-parser": "1.2.0",
|
|
||||||
"safe-buffer": "5.1.1",
|
|
||||||
"send": "0.16.2",
|
|
||||||
"serve-static": "1.13.2",
|
|
||||||
"setprototypeof": "1.1.0",
|
|
||||||
"statuses": "1.4.0",
|
|
||||||
"type-is": "1.6.16",
|
|
||||||
"utils-merge": "1.0.1",
|
|
||||||
"vary": "1.1.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"finalhandler": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
|
|
||||||
"requires": {
|
|
||||||
"debug": "2.6.9",
|
|
||||||
"encodeurl": "1.0.2",
|
|
||||||
"escape-html": "1.0.3",
|
|
||||||
"on-finished": "2.3.0",
|
|
||||||
"parseurl": "1.3.2",
|
|
||||||
"statuses": "1.4.0",
|
|
||||||
"unpipe": "1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"forwarded": {
|
|
||||||
"version": "0.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
|
|
||||||
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
|
|
||||||
},
|
|
||||||
"fresh": {
|
|
||||||
"version": "0.5.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
|
||||||
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
|
|
||||||
},
|
|
||||||
"http-errors": {
|
|
||||||
"version": "1.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
|
|
||||||
"integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
|
|
||||||
"requires": {
|
|
||||||
"depd": "1.1.2",
|
|
||||||
"inherits": "2.0.3",
|
|
||||||
"setprototypeof": "1.1.0",
|
|
||||||
"statuses": "1.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"iconv-lite": {
|
|
||||||
"version": "0.4.19",
|
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
|
|
||||||
"integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ=="
|
|
||||||
},
|
|
||||||
"inherits": {
|
|
||||||
"version": "2.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
|
||||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
|
||||||
},
|
|
||||||
"ipaddr.js": {
|
|
||||||
"version": "1.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz",
|
|
||||||
"integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs="
|
|
||||||
},
|
|
||||||
"media-typer": {
|
|
||||||
"version": "0.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
|
||||||
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
|
||||||
},
|
|
||||||
"merge-descriptors": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
|
||||||
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
|
|
||||||
},
|
|
||||||
"methods": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
|
||||||
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
|
|
||||||
},
|
|
||||||
"mime": {
|
|
||||||
"version": "1.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
|
|
||||||
"integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ=="
|
|
||||||
},
|
|
||||||
"mime-db": {
|
|
||||||
"version": "1.33.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
|
|
||||||
"integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ=="
|
|
||||||
},
|
|
||||||
"mime-types": {
|
|
||||||
"version": "2.1.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
|
|
||||||
"integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
|
|
||||||
"requires": {
|
|
||||||
"mime-db": "1.33.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ms": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
|
||||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
|
||||||
},
|
|
||||||
"negotiator": {
|
|
||||||
"version": "0.6.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
|
|
||||||
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
|
|
||||||
},
|
|
||||||
"on-finished": {
|
|
||||||
"version": "2.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
|
||||||
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
|
|
||||||
"requires": {
|
|
||||||
"ee-first": "1.1.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"parseurl": {
|
|
||||||
"version": "1.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
|
|
||||||
"integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M="
|
|
||||||
},
|
|
||||||
"path-to-regexp": {
|
|
||||||
"version": "0.1.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
|
||||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
|
||||||
},
|
|
||||||
"proxy-addr": {
|
|
||||||
"version": "2.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz",
|
|
||||||
"integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==",
|
|
||||||
"requires": {
|
|
||||||
"forwarded": "0.1.2",
|
|
||||||
"ipaddr.js": "1.6.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"qs": {
|
|
||||||
"version": "6.5.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
|
|
||||||
"integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A=="
|
|
||||||
},
|
|
||||||
"range-parser": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
|
|
||||||
"integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4="
|
|
||||||
},
|
|
||||||
"raw-body": {
|
|
||||||
"version": "2.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz",
|
|
||||||
"integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=",
|
|
||||||
"requires": {
|
|
||||||
"bytes": "3.0.0",
|
|
||||||
"http-errors": "1.6.2",
|
|
||||||
"iconv-lite": "0.4.19",
|
|
||||||
"unpipe": "1.0.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"depd": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz",
|
|
||||||
"integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k="
|
|
||||||
},
|
|
||||||
"http-errors": {
|
|
||||||
"version": "1.6.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz",
|
|
||||||
"integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=",
|
|
||||||
"requires": {
|
|
||||||
"depd": "1.1.1",
|
|
||||||
"inherits": "2.0.3",
|
|
||||||
"setprototypeof": "1.0.3",
|
|
||||||
"statuses": "1.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"setprototypeof": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz",
|
|
||||||
"integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"safe-buffer": {
|
|
||||||
"version": "5.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
|
|
||||||
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
|
|
||||||
},
|
|
||||||
"send": {
|
|
||||||
"version": "0.16.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
|
|
||||||
"integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
|
|
||||||
"requires": {
|
|
||||||
"debug": "2.6.9",
|
|
||||||
"depd": "1.1.2",
|
|
||||||
"destroy": "1.0.4",
|
|
||||||
"encodeurl": "1.0.2",
|
|
||||||
"escape-html": "1.0.3",
|
|
||||||
"etag": "1.8.1",
|
|
||||||
"fresh": "0.5.2",
|
|
||||||
"http-errors": "1.6.3",
|
|
||||||
"mime": "1.4.1",
|
|
||||||
"ms": "2.0.0",
|
|
||||||
"on-finished": "2.3.0",
|
|
||||||
"range-parser": "1.2.0",
|
|
||||||
"statuses": "1.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"serve-static": {
|
|
||||||
"version": "1.13.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
|
|
||||||
"integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
|
|
||||||
"requires": {
|
|
||||||
"encodeurl": "1.0.2",
|
|
||||||
"escape-html": "1.0.3",
|
|
||||||
"parseurl": "1.3.2",
|
|
||||||
"send": "0.16.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"setprototypeof": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
|
|
||||||
},
|
|
||||||
"statuses": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
|
|
||||||
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
|
|
||||||
},
|
|
||||||
"type-is": {
|
|
||||||
"version": "1.6.16",
|
|
||||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
|
|
||||||
"integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
|
|
||||||
"requires": {
|
|
||||||
"media-typer": "0.3.0",
|
|
||||||
"mime-types": "2.1.18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"typescript": {
|
|
||||||
"version": "2.9.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz",
|
|
||||||
"integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w=="
|
|
||||||
},
|
|
||||||
"unpipe": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
|
||||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
|
||||||
},
|
|
||||||
"utils-merge": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
|
||||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
|
||||||
},
|
|
||||||
"vary": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
|
||||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
|
||||||
},
|
|
||||||
"ws": {
|
|
||||||
"version": "5.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-5.2.0.tgz",
|
|
||||||
"integrity": "sha512-c18dMeW+PEQdDFzkhDsnBAlS4Z8KGStBQQUcQ5mf7Nf689jyGk0594L+i9RaQuf4gog6SvWLJorz2NfSaqxZ7w==",
|
|
||||||
"requires": {
|
|
||||||
"async-limiter": "1.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@dotstats/backend",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"author": "Parity Technologies Ltd. <admin@parity.io>",
|
|
||||||
"license": "GPL-3.0",
|
|
||||||
"description": "Polkadot Telemetry frontend",
|
|
||||||
"main": "build/index.js",
|
|
||||||
"scripts": {
|
|
||||||
"start": "tsc && node build/index.js",
|
|
||||||
"build": "tsc",
|
|
||||||
"check": "tsc --noEmit",
|
|
||||||
"test": "node ./test | tap-spec"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/express": "^4.16.0",
|
|
||||||
"@types/node": "^10.3.3",
|
|
||||||
"@types/ws": "^5.1.2",
|
|
||||||
"express": "^4.16.3",
|
|
||||||
"iplocation": "^6.1.0",
|
|
||||||
"typescript": "^2.9.2",
|
|
||||||
"ws": "6.1.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"tap-spec": "^5.0.0",
|
|
||||||
"tape": "^4.9.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
import Chain from './Chain';
|
|
||||||
import Node from './Node';
|
|
||||||
import Feed from './Feed';
|
|
||||||
import FeedSet from './FeedSet';
|
|
||||||
import { Types, FeedMessage, Maybe, timestamp } from '@dotstats/common';
|
|
||||||
|
|
||||||
export default class Aggregator {
|
|
||||||
private readonly chains = new Map<Types.ChainLabel, Chain>();
|
|
||||||
private readonly feeds = new FeedSet();
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
setInterval(() => this.timeoutCheck(), 10000);
|
|
||||||
}
|
|
||||||
|
|
||||||
public addNode(node: Node) {
|
|
||||||
let chain = this.getChain(node.chain);
|
|
||||||
|
|
||||||
chain.addNode(node);
|
|
||||||
|
|
||||||
this.feeds.broadcast(Feed.addedChain(chain));
|
|
||||||
}
|
|
||||||
|
|
||||||
public addFeed(feed: Feed) {
|
|
||||||
this.feeds.add(feed);
|
|
||||||
|
|
||||||
feed.sendMessage(Feed.feedVersion());
|
|
||||||
|
|
||||||
for (const chain of this.chains.values()) {
|
|
||||||
feed.sendMessage(Feed.addedChain(chain));
|
|
||||||
}
|
|
||||||
|
|
||||||
feed.events.on('subscribe', (label: Types.ChainLabel) => {
|
|
||||||
const chain = this.chains.get(label);
|
|
||||||
|
|
||||||
if (chain) {
|
|
||||||
feed.sendMessage(Feed.subscribedTo(label));
|
|
||||||
chain.addFeed(feed);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
feed.events.on('unsubscribe', (label: Types.ChainLabel) => {
|
|
||||||
const chain = this.chains.get(label);
|
|
||||||
|
|
||||||
if (chain) {
|
|
||||||
chain.removeFeed(feed);
|
|
||||||
feed.sendMessage(Feed.unsubscribedFrom(label));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public getExistingChain(label: Types.ChainLabel) : Maybe<Chain> {
|
|
||||||
return this.chains.get(label);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getChain(label: Types.ChainLabel): Chain {
|
|
||||||
const chain = this.chains.get(label);
|
|
||||||
|
|
||||||
if (chain) {
|
|
||||||
return chain;
|
|
||||||
} else {
|
|
||||||
const chain = new Chain(label);
|
|
||||||
|
|
||||||
chain.events.on('disconnect', (count: number) => {
|
|
||||||
if (count !== 0) {
|
|
||||||
this.feeds.broadcast(Feed.addedChain(chain));
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
chain.events.removeAllListeners();
|
|
||||||
|
|
||||||
this.chains.delete(chain.label);
|
|
||||||
|
|
||||||
console.log(`Chain: ${label} lost all nodes`);
|
|
||||||
this.feeds.broadcast(Feed.removedChain(label));
|
|
||||||
});
|
|
||||||
|
|
||||||
this.chains.set(label, chain);
|
|
||||||
|
|
||||||
console.log(`New chain: ${label}`);
|
|
||||||
this.feeds.broadcast(Feed.addedChain(chain));
|
|
||||||
|
|
||||||
return chain;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private timeoutCheck() {
|
|
||||||
const empty: Types.ChainLabel[] = [];
|
|
||||||
|
|
||||||
const now = timestamp();
|
|
||||||
|
|
||||||
for (const chain of this.chains.values()) {
|
|
||||||
chain.timeoutCheck(now);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const feed of this.feeds.values()) {
|
|
||||||
feed.ping();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import { Types } from '@dotstats/common';
|
|
||||||
|
|
||||||
export default class Block {
|
|
||||||
public static readonly ZERO = new Block(0 as Types.BlockNumber, '' as Types.BlockHash);
|
|
||||||
|
|
||||||
public readonly number: Types.BlockNumber;
|
|
||||||
public readonly hash: Types.BlockHash;
|
|
||||||
|
|
||||||
constructor(number: Types.BlockNumber, hash: Types.BlockHash) {
|
|
||||||
this.number = number;
|
|
||||||
this.hash = hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
gt(other: Block): boolean {
|
|
||||||
return this.number > other.number;
|
|
||||||
}
|
|
||||||
|
|
||||||
eq(other: Block): boolean {
|
|
||||||
return this.number === other.number && this.hash === other.hash;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,223 +0,0 @@
|
|||||||
import * as EventEmitter from 'events';
|
|
||||||
import Node from './Node';
|
|
||||||
import Feed from './Feed';
|
|
||||||
import FeedSet from './FeedSet';
|
|
||||||
import Block from './Block';
|
|
||||||
import { Maybe, Types, NumStats } from '@dotstats/common';
|
|
||||||
|
|
||||||
const BLOCK_TIME_HISTORY = 10;
|
|
||||||
|
|
||||||
export default class Chain {
|
|
||||||
private nodes = new Set<Node>();
|
|
||||||
private feeds = new FeedSet();
|
|
||||||
private count = 0;
|
|
||||||
|
|
||||||
public readonly events = new EventEmitter();
|
|
||||||
public readonly label: Types.ChainLabel;
|
|
||||||
|
|
||||||
public height = 0 as Types.BlockNumber;
|
|
||||||
public finalized = Block.ZERO;
|
|
||||||
public blockTimestamp = 0 as Types.Timestamp;
|
|
||||||
|
|
||||||
private blockTimes = new NumStats<Types.Milliseconds>(BLOCK_TIME_HISTORY);
|
|
||||||
private averageBlockTime: Maybe<Types.Milliseconds> = null;
|
|
||||||
|
|
||||||
public lastBroadcastedAuthoritySetInfo: Maybe<Types.AuthoritySetInfo> = null;
|
|
||||||
|
|
||||||
constructor(label: Types.ChainLabel) {
|
|
||||||
this.label = label;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get nodeCount(): Types.NodeCount {
|
|
||||||
return this.nodes.size as Types.NodeCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public addNode(node: Node) {
|
|
||||||
console.log(`[${this.label}] new node: ${node.name}`);
|
|
||||||
|
|
||||||
this.nodes.add(node);
|
|
||||||
this.feeds.broadcast(Feed.addedNode(node));
|
|
||||||
|
|
||||||
node.events.once('disconnect', () => this.removeNode(node));
|
|
||||||
node.events.once('stale', () => this.staleNode(node));
|
|
||||||
|
|
||||||
node.events.on('block', () => this.updateBlock(node));
|
|
||||||
node.events.on('finalized', () => this.updateFinalized(node));
|
|
||||||
|
|
||||||
node.events.on('afg-finalized', (finalizedNumber, finalizedHash) => this.feeds.each(
|
|
||||||
f => f.sendConsensusMessage(Feed.afgFinalized(node, finalizedNumber, finalizedHash))
|
|
||||||
));
|
|
||||||
node.events.on('afg-received-prevote', (finalizedNumber, finalizedHash, voter) => this.feeds.each(
|
|
||||||
f => f.sendConsensusMessage(Feed.afgReceivedPrevote(node, finalizedNumber, finalizedHash, voter))
|
|
||||||
));
|
|
||||||
node.events.on('afg-received-precommit', (finalizedNumber, finalizedHash, voter) => this.feeds.each(
|
|
||||||
f => f.sendConsensusMessage(Feed.afgReceivedPrecommit(node, finalizedNumber, finalizedHash, voter))
|
|
||||||
));
|
|
||||||
node.events.on('authority-set-changed', (authorities, authoritySetId, blockNumber, blockHash) => {
|
|
||||||
let newSet;
|
|
||||||
if (this.lastBroadcastedAuthoritySetInfo == null) {
|
|
||||||
newSet = true;
|
|
||||||
} else {
|
|
||||||
const [lastBroadcastedAuthoritySetId] = this.lastBroadcastedAuthoritySetInfo;
|
|
||||||
newSet = authoritySetId !== lastBroadcastedAuthoritySetId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.isAuthority() && newSet) {
|
|
||||||
const addr = node.address != null ? node.address : "" as Types.Address;
|
|
||||||
const set = [authoritySetId, authorities, addr, blockNumber, blockHash] as Types.AuthoritySetInfo;
|
|
||||||
this.feeds.broadcast(Feed.afgAuthoritySet(set));
|
|
||||||
this.lastBroadcastedAuthoritySetInfo = set;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
node.events.on('stats', () => this.feeds.broadcast(Feed.stats(node)));
|
|
||||||
node.events.on('hardware', () => this.feeds.broadcast(Feed.hardware(node)));
|
|
||||||
node.events.on('location', (location) => this.feeds.broadcast(Feed.locatedNode(node, location)));
|
|
||||||
|
|
||||||
this.updateBlock(node);
|
|
||||||
this.updateFinalized(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
public removeNode(node: Node) {
|
|
||||||
node.events.removeAllListeners();
|
|
||||||
|
|
||||||
this.nodes.delete(node);
|
|
||||||
this.feeds.broadcast(Feed.removedNode(node));
|
|
||||||
this.events.emit('disconnect', this.nodeCount);
|
|
||||||
|
|
||||||
if (this.height === node.best.number) {
|
|
||||||
this.downgradeBlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public staleNode(node: Node) {
|
|
||||||
node.isStale = true;
|
|
||||||
|
|
||||||
this.feeds.broadcast(Feed.staleNode(node));
|
|
||||||
|
|
||||||
if (this.height === node.best.number) {
|
|
||||||
this.downgradeBlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public addFeed(feed: Feed) {
|
|
||||||
this.feeds.add(feed);
|
|
||||||
|
|
||||||
// TODO: this is a bit unclean, find a better way
|
|
||||||
feed.chain = this.label;
|
|
||||||
|
|
||||||
feed.sendMessage(Feed.timeSync());
|
|
||||||
feed.sendMessage(Feed.bestBlock(this.height, this.blockTimestamp, this.averageBlockTime));
|
|
||||||
feed.sendMessage(Feed.bestFinalizedBlock(this.finalized));
|
|
||||||
|
|
||||||
if (this.lastBroadcastedAuthoritySetInfo != null) {
|
|
||||||
feed.sendMessage(Feed.afgAuthoritySet(this.lastBroadcastedAuthoritySetInfo));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const node of this.nodes.values()) {
|
|
||||||
feed.sendMessage(Feed.addedNode(node));
|
|
||||||
feed.sendMessage(Feed.finalized(node));
|
|
||||||
|
|
||||||
if (node.isStale) {
|
|
||||||
feed.sendMessage(Feed.staleNode(node));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public removeFeed(feed: Feed) {
|
|
||||||
this.feeds.remove(feed);
|
|
||||||
}
|
|
||||||
|
|
||||||
public nodeList(): IterableIterator<Node> {
|
|
||||||
return this.nodes.values();
|
|
||||||
}
|
|
||||||
|
|
||||||
public timeoutCheck(now: Types.Timestamp) {
|
|
||||||
for (const node of this.nodes.values()) {
|
|
||||||
node.timeoutCheck(now);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.feeds.broadcast(Feed.timeSync());
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateBlock(node: Node) {
|
|
||||||
const height = node.best.number;
|
|
||||||
|
|
||||||
if (height > this.height) {
|
|
||||||
// New best block
|
|
||||||
const { blockTimestamp } = node;
|
|
||||||
|
|
||||||
if (this.blockTimestamp) {
|
|
||||||
this.updateAverageBlockTime(height, blockTimestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const otherNode of this.nodes) {
|
|
||||||
otherNode.propagationTime = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.height = height;
|
|
||||||
this.blockTimestamp = blockTimestamp;
|
|
||||||
node.propagationTime = 0 as Types.PropagationTime;
|
|
||||||
|
|
||||||
this.feeds.broadcast(Feed.bestBlock(this.height, this.blockTimestamp, this.averageBlockTime));
|
|
||||||
|
|
||||||
console.log(`[${this.label}] New block ${this.height}`);
|
|
||||||
} else if (height === this.height) {
|
|
||||||
// Caught up to best block
|
|
||||||
node.propagationTime = (node.blockTimestamp - this.blockTimestamp) as Types.PropagationTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.isStale) {
|
|
||||||
node.isStale = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.feeds.broadcast(Feed.imported(node));
|
|
||||||
|
|
||||||
console.log(`[${this.label}] ${node.name} imported ${height}, block time: ${node.blockTime / 1000}s, average: ${node.average / 1000}s | latency ${node.latency}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private downgradeBlock() {
|
|
||||||
let height = 0 as Types.BlockNumber;
|
|
||||||
let finalized = Block.ZERO;
|
|
||||||
|
|
||||||
for (const node of this.nodes) {
|
|
||||||
if (node.isStale) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.height === node.best.number) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.best.number > height) {
|
|
||||||
height = node.best.number;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.finalized.number > finalized.number) {
|
|
||||||
finalized = node.finalized;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.height = height;
|
|
||||||
this.finalized = finalized;
|
|
||||||
this.feeds.broadcast(Feed.bestBlock(this.height, this.blockTimestamp, this.averageBlockTime));
|
|
||||||
this.feeds.broadcast(Feed.bestFinalizedBlock(this.finalized));
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateFinalized(node: Node) {
|
|
||||||
if (node.finalized.gt(this.finalized)) {
|
|
||||||
this.finalized = node.finalized;
|
|
||||||
|
|
||||||
this.feeds.broadcast(Feed.bestFinalizedBlock(this.finalized));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.feeds.broadcast(Feed.finalized(node));
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateAverageBlockTime(height: Types.BlockNumber, now: Types.Timestamp) {
|
|
||||||
this.blockTimes.push((now - this.blockTimestamp) as Types.Milliseconds);
|
|
||||||
|
|
||||||
// We are guaranteed that count > 0
|
|
||||||
this.averageBlockTime = this.blockTimes.average();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,288 +0,0 @@
|
|||||||
import * as WebSocket from 'ws';
|
|
||||||
import * as EventEmitter from 'events';
|
|
||||||
import Node from './Node';
|
|
||||||
import Chain from './Chain';
|
|
||||||
import Block from './Block';
|
|
||||||
import { VERSION, timestamp, Maybe, FeedMessage, Types, idGenerator } from '@dotstats/common';
|
|
||||||
import { Location } from './location';
|
|
||||||
|
|
||||||
const nextId = idGenerator<Types.FeedId>();
|
|
||||||
const { Actions } = FeedMessage;
|
|
||||||
|
|
||||||
export default class Feed {
|
|
||||||
public id: Types.FeedId;
|
|
||||||
|
|
||||||
public chain: Maybe<Types.ChainLabel> = null;
|
|
||||||
public readonly events = new EventEmitter();
|
|
||||||
|
|
||||||
private socket: WebSocket;
|
|
||||||
private messages: Array<FeedMessage.Message> = [];
|
|
||||||
private waitingForPong = false;
|
|
||||||
private sendFinality = false;
|
|
||||||
|
|
||||||
constructor(socket: WebSocket) {
|
|
||||||
this.id = nextId();
|
|
||||||
this.socket = socket;
|
|
||||||
|
|
||||||
socket.on('message', this.handleCommand);
|
|
||||||
socket.on('error', this.disconnect);
|
|
||||||
socket.on('close', this.disconnect);
|
|
||||||
socket.on('pong', this.onPong);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static feedVersion(): FeedMessage.Message {
|
|
||||||
return {
|
|
||||||
action: Actions.FeedVersion,
|
|
||||||
payload: VERSION
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bestBlock(height: Types.BlockNumber, ts: Types.Timestamp, avg: Maybe<Types.Milliseconds>): FeedMessage.Message {
|
|
||||||
return {
|
|
||||||
action: Actions.BestBlock,
|
|
||||||
payload: [height, ts, avg]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bestFinalizedBlock(block: Block): FeedMessage.Message {
|
|
||||||
return {
|
|
||||||
action: Actions.BestFinalized,
|
|
||||||
payload: [block.number, block.hash]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static addedNode(node: Node): FeedMessage.Message {
|
|
||||||
return {
|
|
||||||
action: Actions.AddedNode,
|
|
||||||
payload: [node.id, node.nodeDetails(), node.nodeStats(), node.nodeHardware(), node.blockDetails(), node.nodeLocation()]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static removedNode(node: Node): FeedMessage.Message {
|
|
||||||
return {
|
|
||||||
action: Actions.RemovedNode,
|
|
||||||
payload: node.id
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static staleNode(node: Node): FeedMessage.Message {
|
|
||||||
return {
|
|
||||||
action: Actions.StaleNode,
|
|
||||||
payload: node.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static locatedNode(node: Node, location: Location): FeedMessage.Message {
|
|
||||||
return {
|
|
||||||
action: Actions.LocatedNode,
|
|
||||||
payload: [node.id, location.lat, location.lon, location.city]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static imported(node: Node): FeedMessage.Message {
|
|
||||||
return {
|
|
||||||
action: Actions.ImportedBlock,
|
|
||||||
payload: [node.id, node.blockDetails()]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static finalized(node: Node): FeedMessage.Message {
|
|
||||||
return {
|
|
||||||
action: Actions.FinalizedBlock,
|
|
||||||
payload: [node.id, node.finalized.number, node.finalized.hash]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static stats(node: Node): FeedMessage.Message {
|
|
||||||
return {
|
|
||||||
action: Actions.NodeStats,
|
|
||||||
payload: [node.id, node.nodeStats()]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static afgFinalized(node: Node, finalizedNumber: Types.BlockNumber, finalizedHash: Types.BlockHash): FeedMessage.Message {
|
|
||||||
const addr = node.address != null ? node.address : "" as Types.Address;
|
|
||||||
return {
|
|
||||||
action: Actions.AfgFinalized,
|
|
||||||
payload: [addr, finalizedNumber, finalizedHash]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static afgReceivedPrevote(
|
|
||||||
node: Node,
|
|
||||||
targetNumber: Types.BlockNumber,
|
|
||||||
targetHash: Types.BlockHash,
|
|
||||||
voter: Types.Address
|
|
||||||
): FeedMessage.Message {
|
|
||||||
const addr = node.address != null ? node.address : "" as Types.Address;
|
|
||||||
return {
|
|
||||||
action: Actions.AfgReceivedPrevote,
|
|
||||||
payload: [addr, targetNumber, targetHash, voter]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static afgReceivedPrecommit(
|
|
||||||
node: Node,
|
|
||||||
targetNumber: Types.BlockNumber,
|
|
||||||
targetHash: Types.BlockHash,
|
|
||||||
voter: Types.Address
|
|
||||||
): FeedMessage.Message {
|
|
||||||
const addr = node.address != null ? node.address : "" as Types.Address;
|
|
||||||
return {
|
|
||||||
action: Actions.AfgReceivedPrecommit,
|
|
||||||
payload: [addr, targetNumber, targetHash, voter]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static afgAuthoritySet(
|
|
||||||
authoritySetInfo: Types.AuthoritySetInfo,
|
|
||||||
): FeedMessage.Message {
|
|
||||||
return {
|
|
||||||
action: Actions.AfgAuthoritySet,
|
|
||||||
payload: authoritySetInfo,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static hardware(node: Node): FeedMessage.Message {
|
|
||||||
return {
|
|
||||||
action: Actions.NodeHardware,
|
|
||||||
payload: [node.id, node.nodeHardware()]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static timeSync(): FeedMessage.Message {
|
|
||||||
return {
|
|
||||||
action: Actions.TimeSync,
|
|
||||||
payload: timestamp()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static addedChain(chain: Chain): FeedMessage.Message {
|
|
||||||
return {
|
|
||||||
action: Actions.AddedChain,
|
|
||||||
payload: [chain.label, chain.nodeCount]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static removedChain(label: Types.ChainLabel): FeedMessage.Message {
|
|
||||||
return {
|
|
||||||
action: Actions.RemovedChain,
|
|
||||||
payload: label
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static subscribedTo(label: Types.ChainLabel): FeedMessage.Message {
|
|
||||||
return {
|
|
||||||
action: Actions.SubscribedTo,
|
|
||||||
payload: label
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static unsubscribedFrom(label: Types.ChainLabel): FeedMessage.Message {
|
|
||||||
return {
|
|
||||||
action: Actions.UnsubscribedFrom,
|
|
||||||
payload: label
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static pong(payload: string): FeedMessage.Message {
|
|
||||||
return {
|
|
||||||
action: Actions.Pong,
|
|
||||||
payload
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendData(data: FeedMessage.Data) {
|
|
||||||
this.socket.send(data, this.handleError);
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendMessage(message: FeedMessage.Message) {
|
|
||||||
const queue = this.messages.length === 0;
|
|
||||||
|
|
||||||
this.messages.push(message);
|
|
||||||
|
|
||||||
if (queue) {
|
|
||||||
process.nextTick(this.sendMessages);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendConsensusMessage(message: FeedMessage.Message) {
|
|
||||||
if (!this.sendFinality) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.sendMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ping() {
|
|
||||||
if (this.waitingForPong) {
|
|
||||||
this.disconnect();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.waitingForPong = true;
|
|
||||||
|
|
||||||
this.socket.ping(this.handleError);
|
|
||||||
}
|
|
||||||
|
|
||||||
private sendMessages = () => {
|
|
||||||
const data = FeedMessage.serialize(this.messages);
|
|
||||||
this.messages = [];
|
|
||||||
this.socket.send(data, this.handleError);
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleCommand = (data: WebSocket.Data) => {
|
|
||||||
const [tag, payload] = data.toString().split(':', 2) as [string, Maybe<string>];
|
|
||||||
|
|
||||||
if (!payload) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (tag) {
|
|
||||||
case 'subscribe':
|
|
||||||
if (this.chain) {
|
|
||||||
this.events.emit('unsubscribe', this.chain);
|
|
||||||
this.chain = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.events.emit('subscribe', payload as Types.ChainLabel);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'send-finality':
|
|
||||||
this.sendFinality = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'no-more-finality':
|
|
||||||
this.sendFinality = false;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'ping':
|
|
||||||
this.sendMessage(Feed.pong(payload));
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
console.error('Unknown command tag:', tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleError = (err: Maybe<Error>) => {
|
|
||||||
if (err) {
|
|
||||||
console.error('Error when sending data to the socket', err);
|
|
||||||
|
|
||||||
this.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private disconnect = () => {
|
|
||||||
this.socket.removeListener('message', this.handleCommand);
|
|
||||||
this.socket.removeListener('error', this.disconnect);
|
|
||||||
this.socket.removeListener('close', this.disconnect);
|
|
||||||
this.socket.removeListener('pong', this.onPong);
|
|
||||||
this.socket.terminate();
|
|
||||||
|
|
||||||
this.events.emit('disconnect');
|
|
||||||
}
|
|
||||||
|
|
||||||
private onPong = () => {
|
|
||||||
this.waitingForPong = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import Feed from './Feed';
|
|
||||||
import { FeedMessage } from '@dotstats/common';
|
|
||||||
|
|
||||||
type DisconnectListener = () => void;
|
|
||||||
|
|
||||||
export default class FeedSet {
|
|
||||||
private feeds = new Map<Feed, DisconnectListener>();
|
|
||||||
private messages: Array<FeedMessage.Message> = [];
|
|
||||||
|
|
||||||
public values(): IterableIterator<Feed> {
|
|
||||||
return this.feeds.keys();
|
|
||||||
}
|
|
||||||
|
|
||||||
public each(fn: (feed: Feed) => void) {
|
|
||||||
for (const feed of this.values()) {
|
|
||||||
fn(feed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public add(feed: Feed) {
|
|
||||||
const listener = () => this.remove(feed);
|
|
||||||
|
|
||||||
this.feeds.set(feed, listener);
|
|
||||||
|
|
||||||
feed.events.once('disconnect', listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public remove(feed: Feed) {
|
|
||||||
const listener = this.feeds.get(feed);
|
|
||||||
|
|
||||||
if (!listener) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
feed.events.removeListener('disconnect', listener);
|
|
||||||
|
|
||||||
this.feeds.delete(feed);
|
|
||||||
}
|
|
||||||
|
|
||||||
public broadcast(message: FeedMessage.Message) {
|
|
||||||
const queue = this.messages.length === 0;
|
|
||||||
|
|
||||||
this.messages.push(message);
|
|
||||||
|
|
||||||
if (queue) {
|
|
||||||
process.nextTick(this.sendMessages);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sendMessages = () => {
|
|
||||||
const data = FeedMessage.serialize(this.messages);
|
|
||||||
this.messages = [];
|
|
||||||
|
|
||||||
this.each(feed => {
|
|
||||||
try {
|
|
||||||
feed.sendData(data);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Failed to broadcast to feed", err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
import { Maybe, Types, timestamp } from '@dotstats/common';
|
|
||||||
|
|
||||||
export default class MeanList<T extends number> {
|
|
||||||
private periodCount = 0;
|
|
||||||
private periodSum = 0;
|
|
||||||
private meanIndex = 0;
|
|
||||||
private means = Array<T>(20).fill(0 as T);
|
|
||||||
private ticksPerMean = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Push a new value, returns true if a new mean value was produced
|
|
||||||
*
|
|
||||||
* @param {T} value
|
|
||||||
*
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
public push(val: Maybe<T>): boolean {
|
|
||||||
if (val == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.meanIndex === 20 && this.ticksPerMean < 32) {
|
|
||||||
this.squashMeans();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.periodSum += val as number;
|
|
||||||
this.periodCount += 1;
|
|
||||||
|
|
||||||
if (this.periodCount === this.ticksPerMean) {
|
|
||||||
this.pushMean();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get(): Array<T> {
|
|
||||||
if (this.meanIndex === 20) {
|
|
||||||
return this.means;
|
|
||||||
} else {
|
|
||||||
return this.means.slice(0, this.meanIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private pushMean() {
|
|
||||||
const mean = (this.periodSum / this.periodCount) as T;
|
|
||||||
|
|
||||||
if (this.meanIndex === 20 && this.ticksPerMean === 32) {
|
|
||||||
this.means.copyWithin(0, 1);
|
|
||||||
this.means[19] = mean;
|
|
||||||
} else {
|
|
||||||
this.means[this.meanIndex++] = mean;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.periodSum = 0;
|
|
||||||
this.periodCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private squashMeans() {
|
|
||||||
this.ticksPerMean *= 2;
|
|
||||||
|
|
||||||
const means = this.means;
|
|
||||||
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
let i2 = i * 2;
|
|
||||||
means[i] = (((means[i2] as number) + (means[i2 + 1] as number)) / 2) as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.meanIndex = 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,433 +0,0 @@
|
|||||||
import * as WebSocket from 'ws';
|
|
||||||
import * as EventEmitter from 'events';
|
|
||||||
|
|
||||||
import { noop, timestamp, idGenerator, Maybe, Types, NumStats } from '@dotstats/common';
|
|
||||||
import { BlockHash, BlockNumber, ConsensusView } from "@dotstats/common/build/types";
|
|
||||||
import {
|
|
||||||
parseMessage,
|
|
||||||
getBestBlock,
|
|
||||||
Message,
|
|
||||||
BestBlock,
|
|
||||||
SystemInterval,
|
|
||||||
SystemNetworkState,
|
|
||||||
AfgFinalized,
|
|
||||||
AfgReceivedPrecommit,
|
|
||||||
AfgReceivedPrevote,
|
|
||||||
AfgAuthoritySet,
|
|
||||||
} from './message';
|
|
||||||
import { locate, Location } from './location';
|
|
||||||
import MeanList from './MeanList';
|
|
||||||
import Block from './Block';
|
|
||||||
|
|
||||||
const BLOCK_TIME_HISTORY = 10;
|
|
||||||
const MEMORY_RECORDS = 20;
|
|
||||||
const CPU_RECORDS = 20;
|
|
||||||
const TIMEOUT = (1000 * 60 * 1) as Types.Milliseconds; // 1 minute
|
|
||||||
const NO_BLOCK_TIMEOUT = (1000 * 60 * 1) as Types.Milliseconds; // 1 minute
|
|
||||||
|
|
||||||
const nextId = idGenerator<Types.NodeId>();
|
|
||||||
|
|
||||||
export interface NodeEvents {
|
|
||||||
on(event: 'location', fn: (location: Location) => void): void;
|
|
||||||
emit(event: 'location', location: Location): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Node {
|
|
||||||
public readonly id: Types.NodeId;
|
|
||||||
public readonly name: Types.NodeName;
|
|
||||||
public readonly chain: Types.ChainLabel;
|
|
||||||
public readonly implementation: Types.NodeImplementation;
|
|
||||||
public readonly version: Types.NodeVersion;
|
|
||||||
public readonly networkId: Maybe<Types.NetworkId>;
|
|
||||||
public readonly authority: boolean;
|
|
||||||
|
|
||||||
public readonly events = new EventEmitter() as EventEmitter & NodeEvents;
|
|
||||||
|
|
||||||
public address: Maybe<Types.Address> = null;
|
|
||||||
public networkState: Maybe<Types.NetworkState> = null;
|
|
||||||
public location: Maybe<Location> = null;
|
|
||||||
public lastMessage: Types.Timestamp;
|
|
||||||
public config: string;
|
|
||||||
public best = Block.ZERO;
|
|
||||||
public finalized = Block.ZERO;
|
|
||||||
public latency = 0 as Types.Milliseconds;
|
|
||||||
public blockTime = 0 as Types.Milliseconds;
|
|
||||||
public blockTimestamp = 0 as Types.Timestamp;
|
|
||||||
public propagationTime: Maybe<Types.PropagationTime> = null;
|
|
||||||
public isStale = false;
|
|
||||||
|
|
||||||
private peers = 0 as Types.PeerCount;
|
|
||||||
private txcount = 0 as Types.TransactionCount;
|
|
||||||
private memory = new MeanList<Types.MemoryUse>();
|
|
||||||
private cpu = new MeanList<Types.CPUUse>();
|
|
||||||
private upload = new MeanList<Types.BytesPerSecond>();
|
|
||||||
private download = new MeanList<Types.BytesPerSecond>();
|
|
||||||
private chartstamps = new MeanList<Types.Timestamp>();
|
|
||||||
|
|
||||||
private readonly ip: string;
|
|
||||||
private readonly socket: WebSocket;
|
|
||||||
private blockTimes = new NumStats<Types.Milliseconds>(BLOCK_TIME_HISTORY);
|
|
||||||
private lastBlockAt: Maybe<Date> = null;
|
|
||||||
private pingStart = 0 as Types.Timestamp;
|
|
||||||
private throttle = false;
|
|
||||||
|
|
||||||
private authorities: Types.Authorities = [] as Types.Authorities;
|
|
||||||
private authoritySetId: Types.AuthoritySetId = 0 as Types.AuthoritySetId;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
ip: string,
|
|
||||||
socket: WebSocket,
|
|
||||||
name: Types.NodeName,
|
|
||||||
chain: Types.ChainLabel,
|
|
||||||
config: string,
|
|
||||||
implentation: Types.NodeImplementation,
|
|
||||||
version: Types.NodeVersion,
|
|
||||||
networkId: Maybe<Types.NetworkId>,
|
|
||||||
authority: boolean,
|
|
||||||
messages: Array<Message>,
|
|
||||||
) {
|
|
||||||
this.ip = ip;
|
|
||||||
this.id = nextId();
|
|
||||||
this.name = name;
|
|
||||||
this.chain = chain;
|
|
||||||
this.config = config;
|
|
||||||
this.implementation = implentation;
|
|
||||||
this.version = version;
|
|
||||||
this.authority = authority;
|
|
||||||
this.networkId = networkId;
|
|
||||||
this.lastMessage = timestamp();
|
|
||||||
this.socket = socket;
|
|
||||||
|
|
||||||
socket.on('message', this.onMessageData);
|
|
||||||
socket.on('close', this.disconnect);
|
|
||||||
socket.on('error', this.disconnect);
|
|
||||||
socket.on('pong', this.onPong);
|
|
||||||
|
|
||||||
process.nextTick(() => {
|
|
||||||
// Handle cached messages
|
|
||||||
for (const message of messages) {
|
|
||||||
this.onMessage(message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
locate(ip).then((location) => {
|
|
||||||
if (!location) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.location = location;
|
|
||||||
|
|
||||||
this.events.emit('location', location);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static fromSocket(socket: WebSocket, ip: string): Promise<Node> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
function cleanup() {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
socket.removeAllListeners('message');
|
|
||||||
}
|
|
||||||
|
|
||||||
const messages: Array<Message> = [];
|
|
||||||
|
|
||||||
function handler(data: WebSocket.Data) {
|
|
||||||
const message = parseMessage(data);
|
|
||||||
|
|
||||||
if (!message || !message.msg) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.msg === "system.connected") {
|
|
||||||
cleanup();
|
|
||||||
|
|
||||||
const { name, chain, config, implementation, version, authority, network_id: networkId } = message;
|
|
||||||
|
|
||||||
resolve(new Node(ip, socket, name, chain, config, implementation, version, networkId, authority === true, messages));
|
|
||||||
} else {
|
|
||||||
if (messages.length === 10) {
|
|
||||||
messages.shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
messages.push(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.on('message', handler);
|
|
||||||
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
cleanup();
|
|
||||||
|
|
||||||
socket.close();
|
|
||||||
socket.terminate();
|
|
||||||
|
|
||||||
return reject(new Error('Timeout on waiting for system.connected message'));
|
|
||||||
}, 5000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public timeoutCheck(now: Types.Timestamp) {
|
|
||||||
if (this.lastMessage + TIMEOUT < now) {
|
|
||||||
this.disconnect();
|
|
||||||
} else {
|
|
||||||
if (!this.isStale && this.blockTimestamp + NO_BLOCK_TIMEOUT < now) {
|
|
||||||
this.events.emit('stale');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateLatency(now);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public nodeDetails(): Types.NodeDetails {
|
|
||||||
const authority = this.authority ? this.address : null;
|
|
||||||
const addr = this.address ? this.address : '' as Types.Address;
|
|
||||||
|
|
||||||
return [this.name, this.implementation, this.version, authority, this.networkId, addr];
|
|
||||||
}
|
|
||||||
|
|
||||||
public nodeStats(): Types.NodeStats {
|
|
||||||
return [this.peers, this.txcount];
|
|
||||||
}
|
|
||||||
|
|
||||||
public nodeHardware(): Types.NodeHardware {
|
|
||||||
return [this.memory.get(), this.cpu.get(), this.upload.get(), this.download.get(), this.chartstamps.get()];
|
|
||||||
}
|
|
||||||
|
|
||||||
public blockDetails(): Types.BlockDetails {
|
|
||||||
return [this.best.number, this.best.hash, this.blockTime, this.blockTimestamp, this.propagationTime];
|
|
||||||
}
|
|
||||||
|
|
||||||
public nodeLocation(): Maybe<Types.NodeLocation> {
|
|
||||||
const { location } = this;
|
|
||||||
|
|
||||||
return location ? [location.lat, location.lon, location.city] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get average(): Types.Milliseconds {
|
|
||||||
return this.blockTimes.average();
|
|
||||||
}
|
|
||||||
|
|
||||||
public get localBlockAt(): Types.Milliseconds {
|
|
||||||
if (!this.lastBlockAt) {
|
|
||||||
return 0 as Types.Milliseconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
return +(this.lastBlockAt || 0) as Types.Milliseconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
private disconnect = () => {
|
|
||||||
console.log(`${this.name} has disconnected`);
|
|
||||||
|
|
||||||
this.socket.removeListener('message', this.onMessageData);
|
|
||||||
this.socket.removeListener('close', this.disconnect);
|
|
||||||
this.socket.removeListener('error', this.disconnect);
|
|
||||||
this.socket.removeListener('pong', this.onPong);
|
|
||||||
this.socket.close();
|
|
||||||
this.socket.terminate();
|
|
||||||
|
|
||||||
this.events.emit('disconnect');
|
|
||||||
}
|
|
||||||
|
|
||||||
private onMessageData = (data: WebSocket.Data) => {
|
|
||||||
const message = parseMessage(data);
|
|
||||||
|
|
||||||
if (!message) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.onMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onMessage(message: Message) {
|
|
||||||
this.lastMessage = timestamp();
|
|
||||||
|
|
||||||
const update = getBestBlock(message);
|
|
||||||
|
|
||||||
if (update) {
|
|
||||||
this.updateBestBlock(update);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.msg === 'system.interval') {
|
|
||||||
this.onSystemInterval(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.msg === 'system.network_state') {
|
|
||||||
this.onSystemNetworkState(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.msg === 'afg.finalized') {
|
|
||||||
this.onAfgFinalized(message);
|
|
||||||
}
|
|
||||||
if (message.msg === 'afg.received_precommit') {
|
|
||||||
this.onAfgReceivedPrecommit(message);
|
|
||||||
}
|
|
||||||
if (message.msg === 'afg.received_prevote') {
|
|
||||||
this.onAfgReceivedPrevote(message);
|
|
||||||
}
|
|
||||||
if (message.msg === 'afg.authority_set') {
|
|
||||||
this.onAfgAuthoritySet(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private onSystemInterval(message: SystemInterval) {
|
|
||||||
const {
|
|
||||||
network_state,
|
|
||||||
peers,
|
|
||||||
txcount,
|
|
||||||
cpu,
|
|
||||||
memory,
|
|
||||||
bandwidth_download: download,
|
|
||||||
bandwidth_upload: upload,
|
|
||||||
finalized_height: finalized,
|
|
||||||
finalized_hash: finalizedHash
|
|
||||||
} = message;
|
|
||||||
|
|
||||||
if (this.networkState !== network_state && network_state) {
|
|
||||||
this.networkState = network_state;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.peers !== peers || this.txcount !== txcount) {
|
|
||||||
this.peers = peers;
|
|
||||||
this.txcount = txcount;
|
|
||||||
|
|
||||||
this.events.emit('stats');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (finalized != null && finalizedHash != null && finalized > this.finalized.number) {
|
|
||||||
this.finalized = new Block(finalized, finalizedHash);
|
|
||||||
|
|
||||||
this.events.emit('finalized');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cpu != null && memory != null) {
|
|
||||||
const cpuChange = this.cpu.push(cpu);
|
|
||||||
const memChange = this.memory.push(memory);
|
|
||||||
|
|
||||||
const uploadChange = this.upload.push(upload);
|
|
||||||
const downloadChange = this.download.push(download);
|
|
||||||
|
|
||||||
const stampChange = this.chartstamps.push(timestamp());
|
|
||||||
|
|
||||||
if (cpuChange || memChange || uploadChange || downloadChange || stampChange) {
|
|
||||||
this.events.emit('hardware');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private onSystemNetworkState(message: SystemNetworkState) {
|
|
||||||
this.networkState = message.state;
|
|
||||||
}
|
|
||||||
|
|
||||||
public isAuthority(): boolean {
|
|
||||||
return this.authority;
|
|
||||||
}
|
|
||||||
|
|
||||||
private onAfgReceivedPrecommit(message: AfgReceivedPrecommit) {
|
|
||||||
const {
|
|
||||||
target_number: targetNumber,
|
|
||||||
target_hash: targetHash,
|
|
||||||
} = message;
|
|
||||||
const voter = this.extractVoter(message.voter);
|
|
||||||
const number = parseInt(String(targetNumber), 10) as Types.BlockNumber;
|
|
||||||
this.events.emit('afg-received-precommit', number, targetHash, voter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onAfgReceivedPrevote(message: AfgReceivedPrevote) {
|
|
||||||
const {
|
|
||||||
target_number: targetNumber,
|
|
||||||
target_hash: targetHash,
|
|
||||||
} = message;
|
|
||||||
const voter = this.extractVoter(message.voter);
|
|
||||||
const number = parseInt(String(targetNumber), 10) as Types.BlockNumber;
|
|
||||||
this.events.emit('afg-received-prevote', number, targetHash, voter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onAfgAuthoritySet(message: AfgAuthoritySet) {
|
|
||||||
const {
|
|
||||||
authority_id: authorityId,
|
|
||||||
authority_set_id: authoritySetId,
|
|
||||||
hash,
|
|
||||||
number,
|
|
||||||
} = message;
|
|
||||||
|
|
||||||
// we manually parse the authorities message, because the array was formatted as a
|
|
||||||
// string by substrate before sending it.
|
|
||||||
const authorities = JSON.parse(String(message.authorities)) as Types.Authorities;
|
|
||||||
|
|
||||||
this.address = authorityId;
|
|
||||||
|
|
||||||
if (JSON.stringify(this.authorities) !== String(message.authorities) ||
|
|
||||||
this.authoritySetId !== authoritySetId) {
|
|
||||||
const no = parseInt(String(number), 10) as Types.BlockNumber;
|
|
||||||
this.events.emit('authority-set-changed', authorities, authoritySetId, no, hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private onAfgFinalized(message: AfgFinalized) {
|
|
||||||
const {
|
|
||||||
finalized_number: finalizedNumber,
|
|
||||||
finalized_hash: finalizedHash,
|
|
||||||
} = message;
|
|
||||||
const number = parseInt(String(finalizedNumber), 10) as Types.BlockNumber;
|
|
||||||
this.events.emit('afg-finalized', number, finalizedHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
private extractVoter(message_voter: String): Types.Address {
|
|
||||||
return String(message_voter.replace(/"/g, '')) as Types.Address;
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateLatency(now: Types.Timestamp) {
|
|
||||||
// if (this.pingStart) {
|
|
||||||
// console.error(`${this.name} timed out on ping message.`);
|
|
||||||
// this.disconnect();
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
this.pingStart = now;
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.socket.ping(noop);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to send ping to Node', err);
|
|
||||||
|
|
||||||
this.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateBestBlock(update: BestBlock) {
|
|
||||||
const { height, ts: time, best } = update;
|
|
||||||
|
|
||||||
if (this.best.hash !== best && this.best.number <= height) {
|
|
||||||
const blockTime = this.getBlockTime(time);
|
|
||||||
|
|
||||||
this.best = new Block(height, best);
|
|
||||||
this.blockTimestamp = timestamp();
|
|
||||||
this.lastBlockAt = time;
|
|
||||||
this.blockTimes.push(blockTime);
|
|
||||||
this.blockTime = blockTime;
|
|
||||||
|
|
||||||
if (blockTime > 100) {
|
|
||||||
this.events.emit('block');
|
|
||||||
} else if (!this.throttle) {
|
|
||||||
this.throttle = true;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.events.emit('block');
|
|
||||||
this.throttle = false;
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getBlockTime(time: Date): Types.Milliseconds {
|
|
||||||
if (!this.lastBlockAt) {
|
|
||||||
return 0 as Types.Milliseconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (+time - +this.lastBlockAt) as Types.Milliseconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
private onPong = () => {
|
|
||||||
this.latency = (timestamp() - this.pingStart) as Types.Milliseconds;
|
|
||||||
this.pingStart = 0 as Types.Timestamp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
import * as http from 'http';
|
|
||||||
import * as url from 'url';
|
|
||||||
import * as WebSocket from 'ws';
|
|
||||||
import Node from './Node';
|
|
||||||
import Feed from './Feed';
|
|
||||||
import Aggregator from './Aggregator';
|
|
||||||
import {Types} from '@dotstats/common';
|
|
||||||
|
|
||||||
const WS_PORT_TELEMETRY_SERVER = Number(process.env.TELEMETRY_SERVER || 1024);
|
|
||||||
const WS_PORT_FEED_SERVER = Number(process.env.FEED_SERVER || 8080);
|
|
||||||
|
|
||||||
const aggregator = new Aggregator();
|
|
||||||
|
|
||||||
// WebSocket for Nodes feeding telemetry data to the server
|
|
||||||
const incomingTelemetry = new WebSocket.Server({ port: WS_PORT_TELEMETRY_SERVER });
|
|
||||||
|
|
||||||
// WebSocket for web clients listening to the telemetry data aggregate
|
|
||||||
const telemetryFeed = new WebSocket.Server({ port: WS_PORT_FEED_SERVER });
|
|
||||||
|
|
||||||
console.log(`Telemetry server listening on port ${WS_PORT_TELEMETRY_SERVER}`);
|
|
||||||
console.log(`Feed server listening on port ${WS_PORT_FEED_SERVER}`);
|
|
||||||
|
|
||||||
const ipv4 = /[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/;
|
|
||||||
|
|
||||||
incomingTelemetry.on('connection', async (socket, req) => {
|
|
||||||
try {
|
|
||||||
const [ ip ] = (req.headers['x-forwarded-for'] || req.connection.remoteAddress || '0.0.0.0')
|
|
||||||
.toString()
|
|
||||||
.match(ipv4) || ['0.0.0.0'];
|
|
||||||
|
|
||||||
const node = await Node.fromSocket(socket, ip);
|
|
||||||
|
|
||||||
aggregator.addNode(node);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function logClients() {
|
|
||||||
const feed = telemetryFeed.clients.size;
|
|
||||||
const node = incomingTelemetry.clients.size;
|
|
||||||
|
|
||||||
console.log(`[System] ${node} open telemetry connections; ${feed} open feed connections`);
|
|
||||||
|
|
||||||
setTimeout(logClients, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
logClients();
|
|
||||||
|
|
||||||
telemetryFeed.on('connection', (socket: WebSocket) => {
|
|
||||||
aggregator.addFeed(new Feed(socket));
|
|
||||||
});
|
|
||||||
|
|
||||||
http.createServer((request, response) => {
|
|
||||||
const incoming_url = request.url || "";
|
|
||||||
const parsed_url = url.parse(incoming_url, true);
|
|
||||||
const path = decodeURI(parsed_url.path || "");
|
|
||||||
if (path.startsWith("/network_state/")) {
|
|
||||||
const [chainLabel, strNodeId] = path.split('/').slice(2);
|
|
||||||
const chain = aggregator.getExistingChain(chainLabel as Types.ChainLabel);
|
|
||||||
if (chain) {
|
|
||||||
const nodeList = Array.from(chain.nodeList());
|
|
||||||
const nodeId = Number(strNodeId);
|
|
||||||
const node = nodeList.filter((node) => node.id == nodeId)[0];
|
|
||||||
if (node && node.networkState) {
|
|
||||||
const { networkState } = node;
|
|
||||||
|
|
||||||
response.writeHead(200, {"Content-Type": "application/json"});
|
|
||||||
response.write(typeof networkState === 'string' ? networkState : JSON.stringify(networkState));
|
|
||||||
} else {
|
|
||||||
response.writeHead(404, {"Content-Type": "text/plain"});
|
|
||||||
response.write("Node has disconnected or has not submitted its network state yet");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
response.end();
|
|
||||||
}).listen(8081);
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import iplocation from 'iplocation';
|
|
||||||
import { Maybe, Types } from '@dotstats/common';
|
|
||||||
|
|
||||||
export interface Location {
|
|
||||||
lat: Types.Latitude;
|
|
||||||
lon: Types.Longitude;
|
|
||||||
city: Types.City;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cache = new Map<string, Maybe<Location>>();
|
|
||||||
|
|
||||||
export async function locate(ip: string): Promise<Maybe<Location>> {
|
|
||||||
if (ip === '127.0.0.1') {
|
|
||||||
return Promise.resolve({
|
|
||||||
lat: 52.5166667 as Types.Latitude,
|
|
||||||
lon: 13.4 as Types.Longitude,
|
|
||||||
city: 'Berlin' as Types.City,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cache.has(ip)) {
|
|
||||||
return Promise.resolve(cache.get(ip));
|
|
||||||
}
|
|
||||||
|
|
||||||
const cached = cache.get(ip);
|
|
||||||
|
|
||||||
return new Promise<Maybe<Location>>((resolve, _) => {
|
|
||||||
iplocation(ip, [], (err, result) => {
|
|
||||||
if (err) {
|
|
||||||
console.error(`Couldn't locate ${ip}`);
|
|
||||||
|
|
||||||
cache.set(ip, null);
|
|
||||||
|
|
||||||
return resolve(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { latitude: lat, longitude: lon, city } = result;
|
|
||||||
const location = { lat, lon, city } as Location;
|
|
||||||
|
|
||||||
cache.set(ip, location);
|
|
||||||
|
|
||||||
resolve(location);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
import { Data } from 'ws';
|
|
||||||
import { Maybe, Types } from '@dotstats/common';
|
|
||||||
|
|
||||||
export function parseMessage(data: Data): Maybe<Message> {
|
|
||||||
try {
|
|
||||||
const message = JSON.parse(data.toString());
|
|
||||||
|
|
||||||
if (message && typeof message.msg === 'string' && typeof message.ts === 'string') {
|
|
||||||
message.ts = new Date(message.ts);
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
} catch (_) {
|
|
||||||
console.warn('Error parsing message JSON');
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getBestBlock(message: Message): Maybe<BestBlock> {
|
|
||||||
switch (message.msg) {
|
|
||||||
case 'node.start':
|
|
||||||
case 'system.interval':
|
|
||||||
case 'block.import':
|
|
||||||
return message;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MessageBase {
|
|
||||||
ts: Date,
|
|
||||||
level: 'INFO' | 'WARN',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BestBlock {
|
|
||||||
best: Types.BlockHash;
|
|
||||||
height: Types.BlockNumber;
|
|
||||||
ts: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AfgFinalized {
|
|
||||||
ts: Date;
|
|
||||||
finalized_number: Types.BlockNumber;
|
|
||||||
finalized_hash: Types.BlockHash;
|
|
||||||
msg: 'afg.finalized';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AfgReceived {
|
|
||||||
ts: Date;
|
|
||||||
target_number: Maybe<Types.BlockNumber>;
|
|
||||||
target_hash: Maybe<Types.BlockHash>;
|
|
||||||
voter: Types.Address;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AfgReceivedPrecommit extends AfgReceived {
|
|
||||||
msg: 'afg.received_precommit';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AfgReceivedPrevote extends AfgReceived {
|
|
||||||
msg: 'afg.received_prevote';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AfgReceivedCommit extends AfgReceived {
|
|
||||||
msg: 'afg.received_commit';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AfgAuthoritySet {
|
|
||||||
msg: 'afg.authority_set';
|
|
||||||
ts: Date;
|
|
||||||
authority_id: Types.Address,
|
|
||||||
authorities: Types.Authorities;
|
|
||||||
authority_set_id: Types.AuthoritySetId;
|
|
||||||
number: Types.BlockNumber;
|
|
||||||
hash: Types.BlockHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SystemConnected {
|
|
||||||
msg: 'system.connected';
|
|
||||||
name: Types.NodeName;
|
|
||||||
chain: Types.ChainLabel;
|
|
||||||
config: string;
|
|
||||||
implementation: Types.NodeImplementation;
|
|
||||||
version: Types.NodeVersion;
|
|
||||||
authority: Maybe<boolean>;
|
|
||||||
network_id: Maybe<Types.NetworkId>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SystemInterval extends BestBlock {
|
|
||||||
msg: 'system.interval';
|
|
||||||
network_state: Maybe<Types.NetworkState>;
|
|
||||||
txcount: Types.TransactionCount;
|
|
||||||
peers: Types.PeerCount;
|
|
||||||
memory: Maybe<Types.MemoryUse>;
|
|
||||||
cpu: Maybe<Types.CPUUse>;
|
|
||||||
status: 'Idle' | string; // TODO: 'Idle' | ...?
|
|
||||||
bandwidth_upload: Maybe<Types.BytesPerSecond>;
|
|
||||||
bandwidth_download: Maybe<Types.BytesPerSecond>;
|
|
||||||
finalized_height: Maybe<Types.BlockNumber>;
|
|
||||||
finalized_hash: Maybe<Types.BlockHash>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SystemNetworkState extends MessageBase {
|
|
||||||
msg: 'system.network_state';
|
|
||||||
state: Types.NetworkState;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NodeStart extends BestBlock {
|
|
||||||
msg: 'node.start';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BlockImport extends BestBlock {
|
|
||||||
msg: 'block.import';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Union type
|
|
||||||
export type Message = MessageBase & (
|
|
||||||
| SystemConnected
|
|
||||||
| SystemInterval
|
|
||||||
| SystemNetworkState
|
|
||||||
| NodeStart
|
|
||||||
| BlockImport
|
|
||||||
| AfgFinalized
|
|
||||||
| AfgReceivedPrecommit
|
|
||||||
| AfgReceivedPrevote
|
|
||||||
| AfgReceivedCommit
|
|
||||||
| AfgAuthoritySet
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
// received: {"msg":"block.import","level":"INFO","ts":"2018-06-18T17:30:35.285406538+02:00","best":"3d4fdc7960078ddc9be87dddc48324a6d64afdf1f65fffe89529ce9965cd5f29","height":526}
|
|
||||||
// received: {"msg":"node.start","level":"INFO","ts":"2018-06-18T17:30:40.038731057+02:00","best":"3d4fdc7960078ddc9be87dddc48324a6d64afdf1f65fffe89529ce9965cd5f29","height":526}
|
|
||||||
// received: {"msg":"system.connected","level":"INFO","ts":"2018-06-18T17:30:40.038975471+02:00","chain":"dev","config":"","version":"0.2.0","implementation":"parity-polkadot","name":"Majestic Widget"}
|
|
||||||
// received: {"msg":"system.interval","level":"INFO","ts":"2018-06-19T14:00:05.091355364+02:00","txcount":0,"best":"360c9563857308703398f637932b7ffe884e5c7b09692600ff09a4d753c9d948","height":7559,"peers":0,"status":"Idle"}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
const test = require('tape');
|
|
||||||
const MeanList = require('../build/MeanList').default;
|
|
||||||
|
|
||||||
test('MeanList', (assert) => {
|
|
||||||
let list = new MeanList();
|
|
||||||
|
|
||||||
assert.same(list.get(), [], 'Inits empty');
|
|
||||||
|
|
||||||
list.push(0);
|
|
||||||
|
|
||||||
assert.same(list.get(), [0], 'Stores the first value');
|
|
||||||
|
|
||||||
list.push(1);
|
|
||||||
list.push(2);
|
|
||||||
list.push(3);
|
|
||||||
list.push(4);
|
|
||||||
list.push(5);
|
|
||||||
list.push(6);
|
|
||||||
list.push(7);
|
|
||||||
list.push(8);
|
|
||||||
list.push(9);
|
|
||||||
|
|
||||||
assert.same(list.get(), [0,1,2,3,4,5,6,7,8,9], 'Stores the first 10 values');
|
|
||||||
|
|
||||||
list.push(10);
|
|
||||||
list.push(11);
|
|
||||||
list.push(12);
|
|
||||||
list.push(13);
|
|
||||||
list.push(14);
|
|
||||||
list.push(15);
|
|
||||||
list.push(16);
|
|
||||||
list.push(17);
|
|
||||||
list.push(18);
|
|
||||||
list.push(19);
|
|
||||||
|
|
||||||
assert.same(list.get(), [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19], 'Stores the first 20 values');
|
|
||||||
|
|
||||||
list.push(20);
|
|
||||||
|
|
||||||
assert.same(list.get(), [0.5,2.5,4.5,6.5,8.5,10.5,12.5,14.5,16.5,18.5], 'Squashes values on 21st entry');
|
|
||||||
|
|
||||||
list.push(21);
|
|
||||||
|
|
||||||
assert.same(list.get(), [0.5,2.5,4.5,6.5,8.5,10.5,12.5,14.5,16.5,18.5,20.5], 'Adds a mean on 22nd entry');
|
|
||||||
|
|
||||||
list.push(22);
|
|
||||||
|
|
||||||
assert.same(list.get(), [0.5,2.5,4.5,6.5,8.5,10.5,12.5,14.5,16.5,18.5,20.5], 'Keeps track of 23rd entry internally');
|
|
||||||
|
|
||||||
list.push(23);
|
|
||||||
|
|
||||||
assert.same(list.get(), [0.5,2.5,4.5,6.5,8.5,10.5,12.5,14.5,16.5,18.5,20.5,22.5], 'Adds a mean on 24th entry');
|
|
||||||
|
|
||||||
list.push(24);
|
|
||||||
list.push(25);
|
|
||||||
list.push(26);
|
|
||||||
list.push(27);
|
|
||||||
list.push(28);
|
|
||||||
list.push(29);
|
|
||||||
list.push(30);
|
|
||||||
list.push(31);
|
|
||||||
list.push(32);
|
|
||||||
list.push(33);
|
|
||||||
list.push(34);
|
|
||||||
list.push(35);
|
|
||||||
list.push(36);
|
|
||||||
list.push(37);
|
|
||||||
list.push(38);
|
|
||||||
list.push(39);
|
|
||||||
|
|
||||||
assert.same(list.get(), [
|
|
||||||
0.5, 2.5, 4.5, 6.5, 8.5, 10.5, 12.5, 14.5, 16.5, 18.5,
|
|
||||||
20.5, 22.5, 24.5, 26.5, 28.5, 30.5, 32.5, 34.5, 36.5, 38.5
|
|
||||||
], 'Adds means up to 40th entry');
|
|
||||||
|
|
||||||
list.push(40);
|
|
||||||
|
|
||||||
assert.same(list.get(), [
|
|
||||||
1.5, 5.5, 9.5, 13.5, 17.5, 21.5, 25.5, 29.5, 33.5, 37.5,
|
|
||||||
], 'Squashes values on 41st entry');
|
|
||||||
|
|
||||||
list = new MeanList();
|
|
||||||
|
|
||||||
for (var i = 0; i < 640; i++) {
|
|
||||||
list.push(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.same(list.get(), [
|
|
||||||
15.5, 47.5, 79.5, 111.5, 143.5, 175.5, 207.5, 239.5, 271.5, 303.5,
|
|
||||||
335.5, 367.5, 399.5, 431.5, 463.5, 495.5, 527.5, 559.5, 591.5, 623.5
|
|
||||||
], 'Squashes values up to 32 degrees');
|
|
||||||
|
|
||||||
for (var i = 0; i < 31; i++) {
|
|
||||||
list.push(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.same(list.get(), [
|
|
||||||
15.5, 47.5, 79.5, 111.5, 143.5, 175.5, 207.5, 239.5, 271.5, 303.5,
|
|
||||||
335.5, 367.5, 399.5, 431.5, 463.5, 495.5, 527.5, 559.5, 591.5, 623.5
|
|
||||||
], 'Keeps track of 31 entries internally');
|
|
||||||
|
|
||||||
list.push(31);
|
|
||||||
|
|
||||||
assert.same(list.get(), [
|
|
||||||
47.5, 79.5, 111.5, 143.5, 175.5, 207.5, 239.5, 271.5, 303.5, 335.5,
|
|
||||||
367.5, 399.5, 431.5, 463.5, 495.5, 527.5, 559.5, 591.5, 623.5, 15.5
|
|
||||||
], 'Pushes a new mean on 32nd value');
|
|
||||||
|
|
||||||
assert.end();
|
|
||||||
});
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../../tsconfig",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "build"
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"./src/**/*.ts",
|
|
||||||
"./declarations/**/*.d.ts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user