mirror of https://github.com/waku-org/nwaku.git
* Add Waku v1 usage example, fix #54 * Add readme for example and fix typos
This commit is contained in:
parent
893134b536
commit
4314dcf6e9
8
Makefile
8
Makefile
|
@ -25,6 +25,8 @@ DOCKER_IMAGE_NIM_PARAMS ?= -d:chronicles_colors:none -d:insecure
|
||||||
deps \
|
deps \
|
||||||
update \
|
update \
|
||||||
wakunode \
|
wakunode \
|
||||||
|
wakusim \
|
||||||
|
wakuexample \
|
||||||
test \
|
test \
|
||||||
clean \
|
clean \
|
||||||
libbacktrace
|
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.
|
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 '.'
|
# 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
|
# must be included after the default target
|
||||||
-include $(BUILD_SYSTEM_DIR)/makefiles/targets.mk
|
-include $(BUILD_SYSTEM_DIR)/makefiles/targets.mk
|
||||||
|
@ -76,6 +78,10 @@ wakusim: | build deps wakunode
|
||||||
echo -e $(BUILD_MSG) "build/$@" && \
|
echo -e $(BUILD_MSG) "build/$@" && \
|
||||||
$(ENV_SCRIPT) nim wakusim $(NIM_PARAMS) waku.nims
|
$(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
|
wakunode2: | build deps
|
||||||
echo -e $(BUILD_MSG) "build/$@" && \
|
echo -e $(BUILD_MSG) "build/$@" && \
|
||||||
$(ENV_SCRIPT) nim wakunode2 $(NIM_PARAMS) waku.nims
|
$(ENV_SCRIPT) nim wakunode2 $(NIM_PARAMS) waku.nims
|
||||||
|
|
|
@ -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
|
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
|
### Waku Quick Simulation
|
||||||
One can set up several nodes, get them connected and then instruct them via the
|
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
|
JSON-RPC interface. This can be done via e.g. web3.js, nim-web3 (needs to be
|
||||||
|
|
|
@ -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.
|
|
@ -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 @[]
|
Binary file not shown.
|
@ -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()
|
|
@ -43,13 +43,16 @@ task test, "Run waku v1 tests":
|
||||||
task test2, "Run waku v2 tests":
|
task test2, "Run waku v2 tests":
|
||||||
test "all_tests_v2"
|
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"
|
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 "quicksim", "waku/node/v1/", "-d:chronicles_log_level=INFO"
|
||||||
buildBinary "start_network", "waku/node/v1/", "-d:chronicles_log_level=DEBUG"
|
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
|
# TODO Also build Waku store and filter protocols here
|
||||||
task protocol2, "Build the experimental Waku protocol":
|
task protocol2, "Build the experimental Waku protocol":
|
||||||
buildBinary "waku_relay", "waku/protocol/v2/", "-d:chronicles_log_level=TRACE"
|
buildBinary "waku_relay", "waku/protocol/v2/", "-d:chronicles_log_level=TRACE"
|
||||||
|
|
|
@ -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()
|
|
@ -1,74 +1,23 @@
|
||||||
import
|
import
|
||||||
confutils, config, strutils, chronos, json_rpc/rpcserver, metrics,
|
std/strutils,
|
||||||
metrics/chronicles_support,
|
confutils, chronos, json_rpc/rpcserver, metrics, metrics/chronicles_support,
|
||||||
eth/[keys, p2p, async_utils], eth/common/utils, eth/net/nat,
|
eth/[keys, p2p, async_utils], eth/common/utils,
|
||||||
eth/p2p/[discovery, enode, peer_pool, bootnodes, whispernodes],
|
eth/p2p/[discovery, enode, peer_pool, bootnodes, whispernodes],
|
||||||
eth/p2p/rlpx_protocols/whisper_protocol,
|
eth/p2p/rlpx_protocols/whisper_protocol,
|
||||||
../../protocol/v1/[waku_protocol, waku_bridge],
|
../../protocol/v1/[waku_protocol, waku_bridge],
|
||||||
./rpc/[waku, wakusim, key_storage]
|
./rpc/[waku, wakusim, key_storage], ./waku_helpers, ./config
|
||||||
|
|
||||||
const clientId = "Nimbus waku node"
|
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) =
|
proc run(config: WakuNodeConf, rng: ref BrHmacDrbgContext) =
|
||||||
let
|
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)
|
address = Address(ip: ip, tcpPort: tcpPort, udpPort: udpPort)
|
||||||
|
|
||||||
# Set-up node
|
# Set-up node
|
||||||
var node = newEthereumNode(config.nodekey, address, 1, nil, clientId,
|
var node = newEthereumNode(config.nodekey, address, 1, nil, clientId,
|
||||||
addAllCapabilities = false)
|
addAllCapabilities = false, rng = rng)
|
||||||
if not config.bootnodeOnly:
|
if not config.bootnodeOnly:
|
||||||
node.addCapability Waku # Always enable Waku protocol
|
node.addCapability Waku # Always enable Waku protocol
|
||||||
var topicInterest: Option[seq[waku_protocol.Topic]]
|
var topicInterest: Option[seq[waku_protocol.Topic]]
|
||||||
|
|
Loading…
Reference in New Issue