Add Waku v1 usage example, fix #54 (#106)

* Add Waku v1 usage example, fix #54

* Add readme for example and fix typos
This commit is contained in:
Kim De Mey 2020-08-26 14:20:04 +02:00 committed by GitHub
parent 893134b536
commit 4314dcf6e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 279 additions and 61 deletions

View File

@ -25,6 +25,8 @@ DOCKER_IMAGE_NIM_PARAMS ?= -d:chronicles_colors:none -d:insecure
deps \
update \
wakunode \
wakusim \
wakuexample \
test \
clean \
libbacktrace
@ -45,7 +47,7 @@ GIT_SUBMODULE_UPDATE := git submodule update --init --recursive
else # "variables.mk" was included. Business as usual until the end of this file.
# default target, because it's the first one that doesn't start with '.'
all: | wakunode wakusim wakunode2 wakusim2
all: | wakunode wakusim wakuexample wakunode2 wakusim2
# must be included after the default target
-include $(BUILD_SYSTEM_DIR)/makefiles/targets.mk
@ -76,6 +78,10 @@ wakusim: | build deps wakunode
echo -e $(BUILD_MSG) "build/$@" && \
$(ENV_SCRIPT) nim wakusim $(NIM_PARAMS) waku.nims
wakuexample: | build deps
echo -e $(BUILD_MSG) "build/$@" && \
$(ENV_SCRIPT) nim wakuexample $(NIM_PARAMS) waku.nims
wakunode2: | build deps
echo -e $(BUILD_MSG) "build/$@" && \
$(ENV_SCRIPT) nim wakunode2 $(NIM_PARAMS) waku.nims

View File

@ -58,6 +58,13 @@ You can also run a specific test (and alter compile options as you want):
nim c -r ./tests/v1/test_waku_connect.nim
```
### Waku Protocol Example
There is a more basic example, more limited in features and configuration than
the `wakunode`, located in `examples/v1/example.nim`.
More information on how to run this example can be found it its
[readme](examples/v1/README.md).
### Waku Quick Simulation
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

42
examples/v1/README.md Normal file
View File

@ -0,0 +1,42 @@
# Waku v1 example
## Introduction
This is a basic Waku v1 example to show the Waku v1 API usage.
It can be run as a single node, in which case it will just post and receive its
own messages.
Or multiple nodes can be started and can connect to each other, so that
messages can be passed around.
## How to build
```sh
make wakuexample
```
## How to run
### Single node
```sh
# Lauch example node
./build/example
```
Messages will be posted and received.
### Multiple nodes
```sh
# Launch first example node
./build/example
```
Now look for an `INFO` log containing the enode address, e.g.:
`enode://26..5b@0.0.0.0:30303` (but with full address)
Copy the full enode string of the first node and start the second
node with that enode string as staticnode config option:
```sh
# Launch second example node, providing the enode address of the first node
./build/example --staticnode:enode://26..5b@0.0.0.0:30303 --ports-shift:1
```
Now both nodes will receive also messages from each other.

View File

@ -0,0 +1,65 @@
import
confutils/defs, chronicles, chronos, eth/keys
type
WakuNodeCmd* = enum
noCommand
WakuNodeConf* = object
logLevel* {.
desc: "Sets the log level."
defaultValue: LogLevel.INFO
name: "log-level" .}: LogLevel
case cmd* {.
command
defaultValue: noCommand .}: WakuNodeCmd
of noCommand:
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
staticnodes* {.
desc: "Enode URL to directly connect with. Argument may be repeated."
name: "staticnode" .}: seq[string]
nodekey* {.
desc: "P2P node private key as hex.",
defaultValue: KeyPair.random(keys.newRng()[])
name: "nodekey" .}: KeyPair
proc parseCmdArg*(T: type KeyPair, p: TaintedString): T =
try:
let privkey = PrivateKey.fromHex(string(p)).tryGet()
result = privkey.toKeyPair()
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 @[]

BIN
examples/v1/example Executable file

Binary file not shown.

91
examples/v1/example.nim Normal file
View File

@ -0,0 +1,91 @@
import
confutils, chronicles, chronos, stew/byteutils,
eth/[keys, p2p, async_utils],
../../waku/protocol/v1/waku_protocol,
../../waku/node/v1/waku_helpers,
./config_example
## This is a simple Waku v1 example to show the Waku v1 API usage.
const clientId = "Waku example v1"
let
# Load the cli configuration from `config_example.nim`.
config = WakuNodeConf.load()
# Seed the rng.
rng = keys.newRng()
# Set up the address according to NAT information.
(ip, tcpPort, udpPort) = setupNat(config.nat, clientId, config.tcpPort,
config.udpPort, config.portsShift)
address = Address(ip: ip, tcpPort: tcpPort, udpPort: udpPort)
# Create Ethereum Node
var node = newEthereumNode(config.nodekey, # Node identifier
address, # Address reachable for incoming requests
1, # Network Id, only applicable for ETH protocol
nil, # Database, not required for Waku
clientId, # Client id string
addAllCapabilities = false, # Disable default all RLPx capabilities
rng = rng)
node.addCapability Waku # Enable only the Waku protocol.
# Set up the Waku configuration.
let wakuConfig = WakuConfig(powRequirement: 0.002,
bloom: some(fullBloom()), # Full bloom filter
isLightNode: false, # Full node
maxMsgSize: waku_protocol.defaultMaxMsgSize,
topics: none(seq[waku_protocol.Topic]) # empty topic interest
)
node.configureWaku(wakuConfig)
# Optionally direct connect to a set of nodes.
if config.staticnodes.len > 0:
connectToNodes(node, config.staticnodes)
# Connect to the network, which will make the node start listening and/or
# connect to bootnodes, and/or start discovery.
# This will block until first connection is made, which in this case can only
# happen if we directly connect to nodes (step above) or if an incoming
# connection occurs, which is why we use `traceAsyncErrors` instead of `await`.
# TODO: This looks a bit awkward and the API should perhaps be altered here.
traceAsyncErrors node.connectToNetwork(@[],
true, # Enable listening
false # Disable discovery (only discovery v4 is currently supported)
)
# Using a hardcoded symmetric key for encryption of the payload for the sake of
# simplicity.
var symKey: SymKey
symKey[31] = 1
# Asymmetric keypair to sign the payload.
let signKeyPair = KeyPair.random(rng[])
# Code to be executed on receival of a message on filter.
proc handler(msg: ReceivedMessage) =
if msg.decoded.src.isSome():
echo "Received message from ", $msg.decoded.src.get(), ": ",
string.fromBytes(msg.decoded.payload)
# Create and subscribe filter with above handler.
let
topic = [byte 0, 0, 0, 0]
filter = initFilter(symKey = some(symKey), topics = @[topic])
discard node.subscribeFilter(filter, handler)
# Repeat the posting of a message every 5 seconds.
proc repeatMessage(udata: pointer) {.gcsafe.} =
{.gcsafe.}:
# Post a waku message on the network, encrypted with provided symmetric key,
# signed with asymmetric key, on topic and with ttl of 30 seconds.
let posted = node.postMessage(
symKey = some(symKey), src = some(signKeyPair.seckey),
ttl = 30, topic = topic, payload = @[byte 0x48, 0x65, 0x6C, 0x6C, 0x6F])
if posted: echo "Posted message as ", $signKeyPair.pubkey
else: echo "Posting message failed."
discard setTimer(Moment.fromNow(5.seconds), repeatMessage)
discard setTimer(Moment.fromNow(5.seconds), repeatMessage)
runForever()

View File

@ -43,13 +43,16 @@ task test, "Run waku v1 tests":
task test2, "Run waku v2 tests":
test "all_tests_v2"
task wakunode, "Build Waku cli":
task wakunode, "Build Waku v1 cli node":
buildBinary "wakunode", "waku/node/v1/", "-d:chronicles_log_level=TRACE"
task wakusim, "Build Waku simulation tools":
task wakusim, "Build Waku v1 simulation tools":
buildBinary "quicksim", "waku/node/v1/", "-d:chronicles_log_level=INFO"
buildBinary "start_network", "waku/node/v1/", "-d:chronicles_log_level=DEBUG"
task wakuexample, "Build Waku v1 example":
buildBinary "example", "examples/v1/", "-d:chronicles_log_level=DEBUG"
# TODO Also build Waku store and filter protocols here
task protocol2, "Build the experimental Waku protocol":
buildBinary "waku_relay", "waku/protocol/v2/", "-d:chronicles_log_level=TRACE"

View File

@ -0,0 +1,55 @@
import
std/strutils,
chronos,
eth/net/nat, eth/[p2p, async_utils], eth/p2p/peer_pool
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*(natConf, clientId: string, tcpPort, udpPort, portsShift: uint16):
tuple[ip: IpAddress, tcpPort: Port, udpPort: Port] =
# defaults
result.ip = globalListeningAddr
result.tcpPort = Port(tcpPort + portsShift)
result.udpPort = Port(udpPort + portsShift)
var nat: NatStrategy
case natConf.toLowerAscii():
of "any":
nat = NatAny
of "none":
nat = NatNone
of "upnp":
nat = NatUpnp
of "pmp":
nat = NatPmp
else:
if natConf.startsWith("extip:") and isIpAddress(natConf[6..^1]):
# any required port redirection is assumed to be done by hand
result.ip = parseIpAddress(natConf[6..^1])
nat = NatNone
else:
error "not a valid NAT mechanism, nor a valid IP address", value = natConf
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()

View File

@ -1,74 +1,23 @@
import
confutils, config, strutils, chronos, json_rpc/rpcserver, metrics,
metrics/chronicles_support,
eth/[keys, p2p, async_utils], eth/common/utils, eth/net/nat,
std/strutils,
confutils, chronos, json_rpc/rpcserver, metrics, metrics/chronicles_support,
eth/[keys, p2p, async_utils], eth/common/utils,
eth/p2p/[discovery, enode, peer_pool, bootnodes, whispernodes],
eth/p2p/rlpx_protocols/whisper_protocol,
../../protocol/v1/[waku_protocol, waku_bridge],
./rpc/[waku, wakusim, key_storage]
./rpc/[waku, wakusim, key_storage], ./waku_helpers, ./config
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, rng: ref BrHmacDrbgContext) =
let
(ip, tcpPort, udpPort) = setupNat(config)
(ip, tcpPort, udpPort) = setupNat(config.nat, clientId, config.tcpPort,
config.udpPort, config.portsShift)
address = Address(ip: ip, tcpPort: tcpPort, udpPort: udpPort)
# Set-up node
var node = newEthereumNode(config.nodekey, address, 1, nil, clientId,
addAllCapabilities = false)
addAllCapabilities = false, rng = rng)
if not config.bootnodeOnly:
node.addCapability Waku # Always enable Waku protocol
var topicInterest: Option[seq[waku_protocol.Topic]]