Import waku

This commit is contained in:
Oskar Thoren 2020-04-21 13:29:56 +08:00
parent 09a1a48fe9
commit 2c7ab2072c
No known key found for this signature in database
GPG Key ID: B2ECCFD3BC2EF77E
13 changed files with 7748 additions and 0 deletions

View File

@ -25,3 +25,5 @@ make start_network quicksim
Use Waku stuff from Nimbus here,
Get quicksim working
Then try to do similar with libp2p gossipsub
waku folder is Waku imported from Nimbus, not using right now (need to fix Nimbus links etc).

View File

@ -0,0 +1,103 @@
# Introduction
`wakunode` is a cli application that allows you to run a
[Waku](https://github.com/vacp2p/specs/blob/master/waku.md) enabled node.
The application and Waku specification are still experimental and fully in flux.
Additionally the original Whisper (EIP-627) protocol can also be enabled as can
an experimental Whisper - Waku bridging option.
# How to Build & Run
## Prerequisites
* GNU Make, Bash and the usual POSIX utilities. Git 2.9.4 or newer.
* PCRE
More information on the installation of these can be found [here](https://github.com/status-im/nimbus#prerequisites).
## Build & Run
```bash
make # The first `make` invocation will update all Git submodules and prompt you to run `make` again.
# It's only required once per Git clone. You'll run `make update` after each `git pull`, in the future,
# to keep those submodules up to date.
make wakunode
./build/wakunode --help
```
# Using Metrics
Metrics are available for valid envelopes and dropped envelopes.
To compile in an HTTP endpoint for accessing the metrics we need to provide the
`insecure` flag:
```bash
make NIMFLAGS="-d:insecure" wakunode
./build/wakunode --metrics-server
```
Ensure your Prometheus config `prometheus.yml` contains the targets you care about, e.g.:
```
scrape_configs:
- job_name: "waku"
static_configs:
- targets: ['localhost:8008', 'localhost:8009', 'localhost:8010']
```
For visualisation, similar steps can be used as is written down for Nimbus
[here](https://github.com/status-im/nimbus#metric-visualisation).
There is a similar example dashboard that includes visualisation of the
envelopes available at `waku/examples/waku-grafana-dashboard.json`.
# Testing Waku Protocol
One can set up several nodes, get them connected and then instruct them via the
JSON-RPC interface. This can be done via e.g. web3.js, nim-web3 (needs to be
updated) or simply curl your way out.
The JSON-RPC interface is currently the same as the one of Whisper. The only
difference is the addition of broadcasting the topics interest when a filter
with a certain set of topics is subcribed.
Example of a quick simulation using this approach:
```bash
# Build wakunode + quicksim
make NIMFLAGS="-d:insecure" wakusim
# Start the simulation nodes, this currently requires multitail to be installed
./build/start_network --topology:FullMesh --amount:6 --test-node-peers:2
# In another shell run
./build/quicksim
```
The `start_network` tool will also provide a `prometheus.yml` with targets
set to all simulation nodes that are started. This way you can easily start
prometheus with this config, e.g.:
```bash
cd waku/metrics/prometheus
prometheus
```
A Grafana dashboard containing the example dashboard for each simulation node
is also generated and can be imported in case you have Grafana running.
This dashboard can be found at `./waku/metrics/waku-sim-all-nodes-grafana-dashboard.json`
# Spec support
*This section last updated April 7, 2020*
This client of Waku is spec compliant with [Waku spec v0.4](https://specs.vac.dev/waku/waku.html).
It doesn't yet implement the following recommended features:
- No support for rate limiting
- No support for DNS discovery to find Waku nodes
- It doesn't disconnect a peer if it receives a message before a Status message
- No support for negotiation with peer supporting multiple versions via Devp2p capabilities in `Hello` packet
Additionally it makes the following choices:
- It doesn't send message confirmations
- It has partial support for accounting:
- Accounting of total resource usage and total circulated envelopes is done through metrics But no accounting is done for individual peers.

View File

@ -0,0 +1,154 @@
import
confutils/defs, chronicles, chronos,
eth/keys, eth/p2p/rlpx_protocols/waku_protocol
type
Fleet* = enum
none
prod
staging
test
WakuNodeConf* = object
logLevel* {.
desc: "Sets the log level."
defaultValue: LogLevel.INFO
name: "log-level" }: LogLevel
tcpPort* {.
desc: "TCP listening port."
defaultValue: 30303
name: "tcp-port" }: uint16
udpPort* {.
desc: "UDP listening port."
defaultValue: 30303
name: "udp-port" }: uint16
portsShift* {.
desc: "Add a shift to all port numbers."
defaultValue: 0
name: "ports-shift" }: uint16
nat* {.
desc: "Specify method to use for determining public address. " &
"Must be one of: any, none, upnp, pmp, extip:<IP>."
defaultValue: "any" }: string
discovery* {.
desc: "Enable/disable discovery v4."
defaultValue: true
name: "discovery" }: bool
noListen* {.
desc: "Disable listening for incoming peers."
defaultValue: false
name: "no-listen" }: bool
fleet* {.
desc: "Select the fleet to connect to."
defaultValue: Fleet.none
name: "fleet" }: Fleet
bootnodes* {.
desc: "Enode URL to bootstrap P2P discovery with. Argument may be repeated."
name: "bootnode" }: seq[string]
staticnodes* {.
desc: "Enode URL to directly connect with. Argument may be repeated."
name: "staticnode" }: seq[string]
whisper* {.
desc: "Enable the Whisper protocol."
defaultValue: false
name: "whisper" }: bool
whisperBridge* {.
desc: "Enable the Whisper protocol and bridge with Waku protocol."
defaultValue: false
name: "whisper-bridge" }: bool
lightNode* {.
desc: "Run as light node (no message relay).",
defaultValue: false
name: "light-node" }: bool
wakuTopicInterest* {.
desc: "Run as node with a topic-interest",
defaultValue: false
name: "waku-topic-interest" }: bool
wakuPow* {.
desc: "PoW requirement of Waku node.",
defaultValue: 0.002
name: "waku-pow" }: float64
nodekey* {.
desc: "P2P node private key as hex.",
defaultValue: KeyPair.random().tryGet()
name: "nodekey" }: KeyPair
# TODO: Add nodekey file option
bootnodeOnly* {.
desc: "Run only as discovery bootnode."
defaultValue: false
name: "bootnode-only" }: bool
rpc* {.
desc: "Enable Waku RPC server.",
defaultValue: false
name: "rpc" }: bool
rpcAddress* {.
desc: "Listening address of the RPC server.",
defaultValue: parseIpAddress("127.0.0.1")
name: "rpc-address" }: IpAddress
rpcPort* {.
desc: "Listening port of the RPC server.",
defaultValue: 8545
name: "rpc-port" }: uint16
metricsServer* {.
desc: "Enable the metrics server."
defaultValue: false
name: "metrics-server" }: bool
metricsServerAddress* {.
desc: "Listening address of the metrics server."
defaultValue: parseIpAddress("127.0.0.1")
name: "metrics-server-address" }: IpAddress
metricsServerPort* {.
desc: "Listening HTTP port of the metrics server."
defaultValue: 8008
name: "metrics-server-port" }: uint16
logMetrics* {.
desc: "Enable metrics logging."
defaultValue: false
name: "log-metrics" }: bool
# TODO:
# - discv5 + topic register
# - mailserver functionality
proc parseCmdArg*(T: type KeyPair, p: TaintedString): T =
try:
# TODO: add isValidPrivateKey check from Nimbus?
result.seckey = PrivateKey.fromHex(string(p)).tryGet()
result.pubkey = result.seckey.toPublicKey()[]
except CatchableError as e:
raise newException(ConfigurationError, "Invalid private key")
proc completeCmdArg*(T: type KeyPair, val: TaintedString): seq[string] =
return @[]
proc parseCmdArg*(T: type IpAddress, p: TaintedString): T =
try:
result = parseIpAddress(p)
except CatchableError as e:
raise newException(ConfigurationError, "Invalid IP address")
proc completeCmdArg*(T: type IpAddress, val: TaintedString): seq[string] =
return @[]

View File

@ -0,0 +1,36 @@
FROM debian:bullseye-slim AS build
SHELL ["/bin/bash", "-c"]
RUN apt-get -qq update \
&& apt-get -qq -y install build-essential make wget libpcre3-dev git curl &>/dev/null \
&& apt-get -qq clean
ARG GIT_REVISION
RUN cd /root \
&& git clone https://github.com/status-im/nimbus.git \
&& cd nimbus \
&& git reset --hard ${GIT_REVISION} \
&& { make &>/dev/null || true; } \
&& make -j$(nproc) update \
&& make NIMFLAGS="-d:debug -d:insecure" wakunode
# --------------------------------- #
# Starting new image to reduce size #
# --------------------------------- #
FROM debian:bullseye-slim
SHELL ["/bin/bash", "-c"]
RUN apt-get -qq update \
&& apt-get -qq -y install libpcre3 &>/dev/null \
&& apt-get -qq clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY --from=build /root/nimbus/build/wakunode /usr/bin/wakunode
MAINTAINER Kim De Mey <kimdemey@status.im>
LABEL description="Wakunode: Waku and Whisper client"
ENTRYPOINT ["/usr/bin/wakunode"]

View File

@ -0,0 +1,16 @@
SHELL := bash # the shell used internally by "make"
# These default settings can be overriden by exporting env variables
GIT_REVISION ?= $(shell git rev-parse HEAD)
IMAGE_TAG ?= latest
IMAGE_NAME ?= statusteam/nimbus_wakunode:$(IMAGE_TAG)
build:
docker build \
--build-arg="GIT_REVISION=$(GIT_REVISION)" \
-t $(IMAGE_NAME) .
push: build
docker push $(IMAGE_NAME)

View File

@ -0,0 +1,812 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"id": 8,
"links": [],
"panels": [
{
"datasource": null,
"gridPos": {
"h": 4,
"w": 6,
"x": 0,
"y": 0
},
"id": 16,
"options": {
"fieldOptions": {
"calcs": [
"last"
],
"defaults": {
"mappings": [],
"max": 100,
"min": 0,
"thresholds": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"override": {},
"values": false
},
"orientation": "auto",
"showThresholdLabels": false,
"showThresholdMarkers": true
},
"pluginVersion": "6.4.5",
"targets": [
{
"expr": "connected_peers{node=\"0\"}",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Connected Peers #0",
"type": "gauge"
},
{
"cacheTimeout": null,
"colorBackground": false,
"colorValue": false,
"colors": [
"#299c46",
"rgba(237, 129, 40, 0.89)",
"#d44a3a"
],
"datasource": null,
"format": "none",
"gauge": {
"maxValue": 100,
"minValue": 0,
"show": false,
"thresholdLabels": false,
"thresholdMarkers": true
},
"gridPos": {
"h": 4,
"w": 4,
"x": 6,
"y": 0
},
"id": 22,
"interval": null,
"links": [],
"mappingType": 1,
"mappingTypes": [
{
"name": "value to text",
"value": 1
},
{
"name": "range to text",
"value": 2
}
],
"maxDataPoints": 100,
"nullPointMode": "connected",
"nullText": null,
"options": {},
"postfix": "",
"postfixFontSize": "50%",
"prefix": "",
"prefixFontSize": "50%",
"rangeMaps": [
{
"from": "null",
"text": "N/A",
"to": "null"
}
],
"sparkline": {
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": false,
"lineColor": "rgb(31, 120, 193)",
"show": false,
"ymax": null,
"ymin": null
},
"tableColumn": "",
"targets": [
{
"expr": "valid_envelopes_total{node=\"0\"}",
"refId": "A"
}
],
"thresholds": "",
"timeFrom": null,
"timeShift": null,
"title": "Valid Envelopes #0",
"type": "singlestat",
"valueFontSize": "80%",
"valueMaps": [
{
"op": "=",
"text": "N/A",
"value": "null"
}
],
"valueName": "current"
},
{
"cacheTimeout": null,
"colorBackground": false,
"colorValue": false,
"colors": [
"#299c46",
"rgba(237, 129, 40, 0.89)",
"#d44a3a"
],
"datasource": null,
"format": "none",
"gauge": {
"maxValue": 100,
"minValue": 0,
"show": false,
"thresholdLabels": false,
"thresholdMarkers": true
},
"gridPos": {
"h": 4,
"w": 4,
"x": 10,
"y": 0
},
"id": 20,
"interval": null,
"links": [],
"mappingType": 1,
"mappingTypes": [
{
"name": "value to text",
"value": 1
},
{
"name": "range to text",
"value": 2
}
],
"maxDataPoints": 100,
"nullPointMode": "connected",
"nullText": null,
"options": {},
"pluginVersion": "6.4.5",
"postfix": "",
"postfixFontSize": "50%",
"prefix": "",
"prefixFontSize": "50%",
"rangeMaps": [
{
"from": "null",
"text": "N/A",
"to": "null"
}
],
"sparkline": {
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": false,
"lineColor": "rgb(31, 120, 193)",
"show": false,
"ymax": null,
"ymin": null
},
"tableColumn": "",
"targets": [
{
"expr": "dropped_expired_envelopes_total{node=\"0\"} + dropped_from_future_envelopes_total{node=\"0\"} + dropped_low_pow_envelopes_total{node=\"0\"} + dropped_too_large_envelopes_total{node=\"0\"} + dropped_bloom_filter_mismatch_envelopes_total{node=\"0\"} + dropped_topic_mismatch_envelopes_total{node=\"0\"} +dropped_benign_duplicate_envelopes_total{node=\"0\"} + dropped_duplicate_envelopes_total{node=\"0\"}",
"legendFormat": "Invalid envelopes",
"refId": "A"
}
],
"thresholds": "",
"timeFrom": null,
"timeShift": null,
"title": "Invalid Envelopes #0",
"type": "singlestat",
"valueFontSize": "80%",
"valueMaps": [
{
"op": "=",
"text": "N/A",
"value": "null"
}
],
"valueName": "current"
},
{
"datasource": null,
"gridPos": {
"h": 4,
"w": 5,
"x": 14,
"y": 0
},
"id": 14,
"options": {
"fieldOptions": {
"calcs": [
"lastNotNull"
],
"defaults": {
"mappings": [],
"max": 200,
"min": 0,
"thresholds": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 200
}
],
"unit": "percent"
},
"override": {},
"values": false
},
"orientation": "auto",
"showThresholdLabels": false,
"showThresholdMarkers": true
},
"pluginVersion": "6.4.5",
"targets": [
{
"expr": "rate(process_cpu_seconds_total{node=\"0\"}[5s]) * 100",
"legendFormat": "CPU Usage",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "CPU Usage #0",
"type": "gauge"
},
{
"datasource": null,
"gridPos": {
"h": 4,
"w": 5,
"x": 19,
"y": 0
},
"id": 18,
"options": {
"fieldOptions": {
"calcs": [
"lastNotNull"
],
"defaults": {
"mappings": [],
"max": 2147483648,
"min": 0,
"thresholds": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 2147483648
}
],
"unit": "bytes"
},
"override": {},
"values": false
},
"orientation": "auto",
"showThresholdLabels": false,
"showThresholdMarkers": true
},
"pluginVersion": "6.4.5",
"targets": [
{
"expr": "process_resident_memory_bytes{node=\"0\"}",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "RSS Memory #0",
"type": "gauge"
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": null,
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 9,
"w": 12,
"x": 0,
"y": 4
},
"id": 6,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"dataLinks": []
},
"percentage": false,
"pluginVersion": "6.4.5",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "valid_envelopes_total{node=\"0\"}",
"hide": false,
"instant": false,
"legendFormat": "Valid",
"refId": "A"
},
{
"expr": "dropped_benign_duplicate_envelopes_total{node=\"0\"}",
"hide": false,
"instant": false,
"legendFormat": "Benign duplicate",
"refId": "B"
},
{
"expr": "dropped_duplicate_envelopes_total{node=\"0\"}",
"hide": false,
"legendFormat": "Duplicate",
"refId": "C"
},
{
"expr": "dropped_expired_envelopes_total{node=\"0\"}",
"hide": false,
"legendFormat": "Expired",
"refId": "D"
},
{
"expr": "dropped_from_future_envelopes_total{node=\"0\"}",
"hide": false,
"legendFormat": "Future timestamped",
"refId": "E"
},
{
"expr": "dropped_low_pow_envelopes_total{node=\"0\"}",
"hide": false,
"legendFormat": "Too low PoW",
"refId": "F"
},
{
"expr": "dropped_bloom_filter_mismatch_envelopes_total{node=\"0\"}",
"hide": false,
"legendFormat": "Bloom filter mismatch",
"refId": "G"
},
{
"expr": "dropped_topic_mismatch_envelopes_total{node=\"0\"}",
"hide": false,
"legendFormat": "Topic mismatch",
"refId": "H"
},
{
"expr": "dropped_too_large_envelopes_total{node=\"0\"}",
"hide": false,
"legendFormat": "Too Large",
"refId": "I"
},
{
"expr": "dropped_full_queue_new_envelopes_total{node=\"0\"}",
"hide": false,
"legendFormat": "Full queue new",
"refId": "J"
},
{
"expr": "dropped_full_queue_old_envelopes_total{node=\"0\"}",
"hide": false,
"legendFormat": "Full queue old",
"refId": "K"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Waku Envelopes #0",
"tooltip": {
"shared": true,
"sort": 1,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": null,
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 9,
"w": 12,
"x": 12,
"y": 4
},
"id": 2,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"dataLinks": []
},
"percentage": false,
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [
{
"alias": "RSS Memory",
"yaxis": 2
}
],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "connected_peers{node=\"0\"}",
"intervalFactor": 1,
"legendFormat": "Connected Peers",
"refId": "A"
},
{
"expr": "process_resident_memory_bytes{node=\"0\"}",
"interval": "",
"intervalFactor": 1,
"legendFormat": "RSS Memory",
"refId": "B"
},
{
"expr": "rate(process_cpu_seconds_total{node=\"0\"}[15s]) * 100",
"legendFormat": "CPU usage %",
"refId": "C"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Waku Node #0",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": null,
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 13
},
"id": 8,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"dataLinks": []
},
"percentage": false,
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "process_max_fds{node=\"0\"}",
"legendFormat": "Maximum file descriptors",
"refId": "A"
},
{
"expr": "process_open_fds{node=\"0\"}",
"legendFormat": "Open file descriptors",
"refId": "B"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "File Descriptors #0",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": null,
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 13
},
"id": 4,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"dataLinks": []
},
"percentage": false,
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "nim_gc_mem_bytes{node=\"0\"}",
"legendFormat": "Nim GC total memory",
"refId": "A"
},
{
"expr": "nim_gc_mem_occupied_bytes{node=\"0\"}",
"legendFormat": "Nim GC used memory",
"refId": "B"
},
{
"expr": "process_resident_memory_bytes{node=\"0\"}",
"legendFormat": "RSS memory",
"refId": "C"
},
{
"expr": "process_virtual_memory_bytes{node=\"0\"}",
"legendFormat": "Virtual memory",
"refId": "D"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Memory Usage #0",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
}
],
"refresh": "5s",
"schemaVersion": 20,
"style": "dark",
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-30m",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
]
},
"timezone": "",
"title": "Waku Node",
"uid": "K7Z6IoBZk",
"version": 2
}

View File

@ -0,0 +1,30 @@
global:
scrape_interval: 1s
scrape_configs:
- job_name: "wakusim"
static_configs:
- targets: ['127.0.0.1:8010']
labels:
node: '0'
- targets: ['127.0.0.1:8011']
labels:
node: '1'
- targets: ['127.0.0.1:8012']
labels:
node: '2'
- targets: ['127.0.0.1:8013']
labels:
node: '3'
- targets: ['127.0.0.1:8014']
labels:
node: '4'
- targets: ['127.0.0.1:8015']
labels:
node: '5'
- targets: ['127.0.0.1:8008']
labels:
node: '6'
- targets: ['127.0.0.1:8009']
labels:
node: '7'

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,4 @@
-d:chronicles_line_numbers
-d:"chronicles_runtime_filtering=on"
-d:nimDebugDlOpen

View File

@ -0,0 +1,76 @@
import
os, strformat, chronicles, json_rpc/[rpcclient, rpcserver], nimcrypto/sysrand,
eth/common as eth_common, eth/keys, eth/p2p/rlpx_protocols/waku_protocol,
../nimbus/rpc/[hexstrings, rpc_types, waku],
options as what # TODO: Huh? Redefinition?
from os import DirSep
from strutils import rsplit
template sourceDir: string = currentSourcePath.rsplit(DirSep, 1)[0]
const sigWakuPath = &"{sourceDir}{DirSep}rpc{DirSep}wakucallsigs.nim"
createRpcSigs(RpcHttpClient, sigWakuPath)
const topicAmount = 100
let
trafficNode = newRpcHttpClient()
lightNode = newRpcHttpClient()
lightNode2 = newRpcHttpClient()
waitFor lightNode.connect("localhost", Port(8545))
waitFor lightNode2.connect("localhost", Port(8546))
waitFor trafficNode.connect("localhost", Port(8548))
proc generateTopics(amount = topicAmount): seq[waku_protocol.Topic] =
var topic: waku_protocol.Topic
for i in 0..<amount:
if randomBytes(topic) != 4:
raise newException(ValueError, "Generation of random topic failed.")
result.add(topic)
let
symKey = "0x0000000000000000000000000000000000000000000000000000000000000001"
topics = generateTopics()
symKeyID = waitFor lightNode.waku_addSymKey(symKey)
options = WhisperFilterOptions(symKeyID: some(symKeyID),
topics: some(topics))
filterID = waitFor lightNode.waku_newMessageFilter(options)
symKeyID2 = waitFor lightNode2.waku_addSymKey(symKey)
options2 = WhisperFilterOptions(symKeyID: some(symKeyID2),
topics: some(topics))
filterID2 = waitFor lightNode2.waku_newMessageFilter(options2)
symkeyID3 = waitFor trafficNode.waku_addSymKey(symKey)
var message = WhisperPostMessage(symKeyID: some(symkeyID3),
ttl: 30,
topic: some(topics[0]),
payload: "0x45879632".HexDataStr,
powTime: 1.0,
powTarget: 0.002)
info "Posting envelopes on all subscribed topics"
for i in 0..<topicAmount:
message.topic = some(topics[i])
discard waitFor trafficNode.waku_post(message)
# Check if the subscription for the topics works
waitFor sleepAsync(1000.milliseconds) # This is a bit brittle
let
messages = waitFor lightNode.waku_getFilterMessages(filterID)
messages2 = waitFor lightNode2.waku_getFilterMessages(filterID2)
if messages.len != topicAmount or messages2.len != topicAmount:
error "Light node did not receive envelopes on all subscribed topics",
lightnode1=messages.len, lightnode2=messages2.len
quit 1
info "Received envelopes on all subscribed topics"
# Generate test traffic on node
discard waitFor trafficNode.wakusim_generateRandomTraffic(10_000)
info "Started random traffic generation"

View File

@ -0,0 +1,27 @@
proc waku_version(): string
proc waku_info(): WhisperInfo
proc waku_setMaxMessageSize(size: uint64): bool
proc waku_setMinPoW(pow: float): bool
proc waku_markTrustedPeer(enode: string): bool
proc waku_newKeyPair(): Identifier
proc waku_addPrivateKey(key: string): Identifier
proc waku_deleteKeyPair(id: Identifier): bool
proc waku_hasKeyPair(id: Identifier): bool
proc waku_getPublicKey(id: Identifier): PublicKey
proc waku_getPrivateKey(id: Identifier): PrivateKey
proc waku_newSymKey(): Identifier
proc waku_addSymKey(key: string): Identifier
proc waku_generateSymKeyFromPassword(password: string): Identifier
proc waku_hasSymKey(id: Identifier): bool
proc waku_getSymKey(id: Identifier): SymKey
proc waku_deleteSymKey(id: Identifier): bool
proc waku_newMessageFilter(options: WhisperFilterOptions): Identifier
proc waku_deleteMessageFilter(id: Identifier): bool
proc waku_getFilterMessages(id: Identifier): seq[WhisperFilterMessage]
proc waku_post(message: WhisperPostMessage): bool
proc wakusim_generateTraffic(amount: int): bool
proc wakusim_generateRandomTraffic(amount: int): bool

View File

@ -0,0 +1,197 @@
import
strformat, os, osproc, net, confutils, strformat, chronicles, json, strutils,
eth/keys, eth/p2p/enode
const
defaults ="--log-level:DEBUG --log-metrics --metrics-server --rpc"
wakuNodeBin = "build" / "wakunode"
metricsDir = "waku" / "metrics"
portOffset = 2
type
NodeType = enum
FullNode = "",
LightNode = "--light-node:on",
Topology = enum
Star,
FullMesh,
DiscoveryBased # Whatever topology the discovery brings
WakuNetworkConf* = object
topology* {.
desc: "Set the network topology."
defaultValue: Star
name: "topology" }: Topology
amount* {.
desc: "Amount of full nodes to be started."
defaultValue: 4
name: "amount" }: int
testNodePeers* {.
desc: "Amount of peers a test node should connect to."
defaultValue: 1
name: "test-node-peers" }: int
NodeInfo* = object
cmd: string
master: bool
enode: string
shift: int
label: string
proc initNodeCmd(nodeType: NodeType, shift: int, staticNodes: seq[string] = @[],
discovery = false, bootNodes: seq[string] = @[], topicInterest = false,
master = false, label: string): NodeInfo =
let
keypair = KeyPair.random().tryGet()
address = Address(ip: parseIpAddress("127.0.0.1"),
udpPort: (30303 + shift).Port, tcpPort: (30303 + shift).Port)
enode = ENode(pubkey: keypair.pubkey, address: address)
result.cmd = wakuNodeBin & " " & defaults & " "
result.cmd &= $nodeType & " "
result.cmd &= "--waku-topic-interest:" & $topicInterest & " "
result.cmd &= "--nodekey:" & $keypair.seckey & " "
result.cmd &= "--ports-shift:" & $shift & " "
if discovery:
result.cmd &= "--discovery:on" & " "
if bootNodes.len > 0:
for bootNode in bootNodes:
result.cmd &= "--bootnode:" & bootNode & " "
else:
result.cmd &= "--discovery:off" & " "
if staticNodes.len > 0:
for staticNode in staticNodes:
result.cmd &= "--staticnode:" & staticNode & " "
result.master = master
result.enode = $enode
result.shift = shift
result.label = label
debug "Node command created.", cmd=result.cmd
proc starNetwork(amount: int): seq[NodeInfo] =
let masterNode = initNodeCmd(FullNode, portOffset, master = true,
label = "master node")
result.add(masterNode)
for i in 1..<amount:
result.add(initNodeCmd(FullNode, portOffset + i, @[masterNode.enode],
label = "full node"))
proc fullMeshNetwork(amount: int): seq[NodeInfo] =
debug "amount", amount
for i in 0..<amount:
var staticnodes: seq[string]
for item in result:
staticnodes.add(item.enode)
result.add(initNodeCmd(FullNode, portOffset + i, staticnodes,
label = "full node"))
proc discoveryNetwork(amount: int): seq[NodeInfo] =
let bootNode = initNodeCmd(FullNode, portOffset, discovery = true,
master = true, label = "boot node")
result.add(bootNode)
for i in 1..<amount:
result.add(initNodeCmd(FullNode, portOffset + i, label = "full node",
discovery = true, bootNodes = @[bootNode.enode]))
proc generatePrometheusConfig(nodes: seq[NodeInfo], outputFile: string) =
var config = """
global:
scrape_interval: 1s
scrape_configs:
- job_name: "wakusim"
static_configs:"""
var count = 0
for node in nodes:
let port = 8008 + node.shift
config &= &"""
- targets: ['127.0.0.1:{port}']
labels:
node: '{count}'"""
count += 1
var (path, file) = splitPath(outputFile)
createDir(path)
writeFile(outputFile, config)
proc proccessGrafanaDashboard(nodes: int, inputFile: string,
outputFile: string) =
# from https://github.com/status-im/nim-beacon-chain/blob/master/tests/simulation/process_dashboard.nim
var
inputData = parseFile(inputFile)
panels = inputData["panels"].copy()
numPanels = len(panels)
gridHeight = 0
outputData = inputData
for panel in panels:
if panel["gridPos"]["x"].getInt() == 0:
gridHeight += panel["gridPos"]["h"].getInt()
outputData["panels"] = %* []
for nodeNum in 0 .. (nodes - 1):
var
nodePanels = panels.copy()
panelIndex = 0
for panel in nodePanels.mitems:
panel["title"] = %* replace(panel["title"].getStr(), "#0", "#" & $nodeNum)
panel["id"] = %* (panelIndex + (nodeNum * numPanels))
panel["gridPos"]["y"] = %* (panel["gridPos"]["y"].getInt() + (nodeNum * gridHeight))
var targets = panel["targets"]
for target in targets.mitems:
target["expr"] = %* replace(target["expr"].getStr(), "{node=\"0\"}", "{node=\"" & $nodeNum & "\"}")
outputData["panels"].add(panel)
panelIndex.inc()
outputData["uid"] = %* (outputData["uid"].getStr() & "a")
outputData["title"] = %* (outputData["title"].getStr() & " (all nodes)")
writeFile(outputFile, pretty(outputData))
when isMainModule:
let conf = WakuNetworkConf.load()
var nodes: seq[NodeInfo]
case conf.topology:
of Star:
nodes = starNetwork(conf.amount)
of FullMesh:
nodes = fullMeshNetwork(conf.amount)
of DiscoveryBased:
nodes = discoveryNetwork(conf.amount)
var staticnodes: seq[string]
for i in 0..<conf.testNodePeers:
# TODO: could also select nodes randomly
staticnodes.add(nodes[i].enode)
# light node with topic interest
nodes.add(initNodeCmd(LightNode, 0, staticnodes, topicInterest = true,
label = "light node topic interest"))
# Regular light node
nodes.add(initNodeCmd(LightNode, 1, staticnodes, label = "light node"))
var commandStr = "multitail -s 2 -M 0 -x \"Waku Simulation\""
var count = 0
var sleepDuration = 0
for node in nodes:
if conf.topology in {Star, DiscoveryBased}:
sleepDuration = if node.master: 0
else: 1
commandStr &= &" -cT ansi -t 'node #{count} {node.label}' -l 'sleep {sleepDuration}; {node.cmd}; echo [node execution completed]; while true; do sleep 100; done'"
if conf.topology == FullMesh:
sleepDuration += 1
count += 1
generatePrometheusConfig(nodes, metricsDir / "prometheus" / "prometheus.yml")
proccessGrafanaDashboard(nodes.len,
"waku" / "examples" / "waku-grafana-dashboard.json",
metricsDir / "waku-sim-all-nodes-grafana-dashboard.json")
let errorCode = execCmd(commandStr)
if errorCode != 0:
error "launch command failed", command=commandStr

View File

@ -0,0 +1,152 @@
import
confutils, config, strutils, chronos, json_rpc/rpcserver, metrics,
chronicles/topics_registry, # TODO: What? Need this for setLoglevel, weird.
eth/[keys, p2p, async_utils], eth/common/utils, eth/net/nat,
eth/p2p/[discovery, enode, peer_pool, bootnodes, whispernodes],
eth/p2p/rlpx_protocols/[whisper_protocol, waku_protocol, waku_bridge],
../nimbus/rpc/[waku, wakusim, key_storage]
const clientId = "Nimbus waku node"
let globalListeningAddr = parseIpAddress("0.0.0.0")
proc setBootNodes(nodes: openArray[string]): seq[ENode] =
result = newSeqOfCap[ENode](nodes.len)
for nodeId in nodes:
# TODO: something more user friendly than an expect
result.add(ENode.fromString(nodeId).expect("correct node"))
proc connectToNodes(node: EthereumNode, nodes: openArray[string]) =
for nodeId in nodes:
# TODO: something more user friendly than an assert
let whisperENode = ENode.fromString(nodeId).expect("correct node")
traceAsyncErrors node.peerPool.connectToNode(newNode(whisperENode))
proc setupNat(conf: WakuNodeConf): tuple[ip: IpAddress,
tcpPort: Port,
udpPort: Port] =
# defaults
result.ip = globalListeningAddr
result.tcpPort = Port(conf.tcpPort + conf.portsShift)
result.udpPort = Port(conf.udpPort + conf.portsShift)
var nat: NatStrategy
case conf.nat.toLowerAscii():
of "any":
nat = NatAny
of "none":
nat = NatNone
of "upnp":
nat = NatUpnp
of "pmp":
nat = NatPmp
else:
if conf.nat.startsWith("extip:") and isIpAddress(conf.nat[6..^1]):
# any required port redirection is assumed to be done by hand
result.ip = parseIpAddress(conf.nat[6..^1])
nat = NatNone
else:
error "not a valid NAT mechanism, nor a valid IP address", value = conf.nat
quit(QuitFailure)
if nat != NatNone:
let extIP = getExternalIP(nat)
if extIP.isSome:
result.ip = extIP.get()
let extPorts = redirectPorts(tcpPort = result.tcpPort,
udpPort = result.udpPort,
description = clientId)
if extPorts.isSome:
(result.tcpPort, result.udpPort) = extPorts.get()
proc run(config: WakuNodeConf) =
if config.logLevel != LogLevel.NONE:
setLogLevel(config.logLevel)
let
(ip, tcpPort, udpPort) = setupNat(config)
address = Address(ip: ip, tcpPort: tcpPort, udpPort: udpPort)
# Set-up node
var node = newEthereumNode(config.nodekey, address, 1, nil, clientId,
addAllCapabilities = false)
if not config.bootnodeOnly:
node.addCapability Waku # Always enable Waku protocol
var topicInterest: Option[seq[waku_protocol.Topic]]
var bloom: Option[Bloom]
if config.wakuTopicInterest:
var topics: seq[waku_protocol.Topic]
topicInterest = some(topics)
else:
bloom = some(fullBloom())
let wakuConfig = WakuConfig(powRequirement: config.wakuPow,
bloom: bloom,
isLightNode: config.lightNode,
maxMsgSize: waku_protocol.defaultMaxMsgSize,
topics: topicInterest)
node.configureWaku(wakuConfig)
if config.whisper or config.whisperBridge:
node.addCapability Whisper
node.protocolState(Whisper).config.powRequirement = 0.002
if config.whisperBridge:
node.shareMessageQueue()
# TODO: Status fleet bootnodes are discv5? That will not work.
let bootnodes = if config.bootnodes.len > 0: setBootNodes(config.bootnodes)
elif config.fleet == prod: setBootNodes(StatusBootNodes)
elif config.fleet == staging: setBootNodes(StatusBootNodesStaging)
elif config.fleet == test : setBootNodes(StatusBootNodesTest)
else: @[]
traceAsyncErrors node.connectToNetwork(bootnodes, not config.noListen,
config.discovery)
if not config.bootnodeOnly:
# Optionally direct connect with a set of nodes
if config.staticnodes.len > 0: connectToNodes(node, config.staticnodes)
elif config.fleet == prod: connectToNodes(node, WhisperNodes)
elif config.fleet == staging: connectToNodes(node, WhisperNodesStaging)
elif config.fleet == test: connectToNodes(node, WhisperNodesTest)
if config.rpc:
let ta = initTAddress(config.rpcAddress,
Port(config.rpcPort + config.portsShift))
var rpcServer = newRpcHttpServer([ta])
let keys = newKeyStorage()
setupWakuRPC(node, keys, rpcServer)
setupWakuSimRPC(node, rpcServer)
rpcServer.start()
when defined(insecure):
if config.metricsServer:
let
address = config.metricsServerAddress
port = config.metricsServerPort + config.portsShift
info "Starting metrics HTTP server", address, port
metrics.startHttpServer($address, Port(port))
if config.logMetrics:
proc logMetrics(udata: pointer) {.closure, gcsafe.} =
{.gcsafe.}:
let
connectedPeers = connected_peers.value
validEnvelopes = waku_protocol.valid_envelopes.value
invalidEnvelopes = waku_protocol.dropped_expired_envelopes.value +
waku_protocol.dropped_from_future_envelopes.value +
waku_protocol.dropped_low_pow_envelopes.value +
waku_protocol.dropped_too_large_envelopes.value +
waku_protocol.dropped_bloom_filter_mismatch_envelopes.value +
waku_protocol.dropped_topic_mismatch_envelopes.value +
waku_protocol.dropped_benign_duplicate_envelopes.value +
waku_protocol.dropped_duplicate_envelopes.value
info "Node metrics", connectedPeers, validEnvelopes, invalidEnvelopes
addTimer(Moment.fromNow(2.seconds), logMetrics)
addTimer(Moment.fromNow(2.seconds), logMetrics)
runForever()
when isMainModule:
let conf = WakuNodeConf.load()
run(conf)