feat: add examples

This commit is contained in:
Hanno Cornelius 2023-06-09 13:06:56 +02:00
parent efc3ce5c17
commit a2375d2eb4
No known key found for this signature in database
GPG Key ID: 081F615B3EDEF8DE
4 changed files with 228 additions and 0 deletions

42
examples/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 example1
```
## 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: string): 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: string): seq[string] =
return @[]
proc parseCmdArg*(T: type IpAddress, p: string): T =
try:
result = parseIpAddress(p)
except CatchableError as e:
raise newException(ConfigurationError, "Invalid IP address")
proc completeCmdArg*(T: type IpAddress, val: string): seq[string] =
return @[]

119
examples/example.nim Normal file
View File

@ -0,0 +1,119 @@
import
confutils, chronicles, chronos, stew/byteutils, stew/shims/net as stewNet,
eth/[keys, p2p],
../waku/protocol/waku_protocol,
../waku/node/waku_helpers,
../waku/common/utils/nat,
./config_example
## This is a simple Waku v1 example to show the Waku v1 API usage.
const clientId = "Waku example v1"
proc run(config: WakuNodeConf, rng: ref HmacDrbgContext) =
let natRes = setupNat(config.nat, clientId,
Port(config.tcpPort + config.portsShift),
Port(config.udpPort + config.portsShift))
if natRes.isErr():
fatal "setupNat failed", error = natRes.error
quit(1)
# Set up the address according to NAT information.
let (ipExt, tcpPortExt, udpPortExt) = natRes.get()
# TODO: EthereumNode should have a better split of binding address and
# external address. Also, can't have different ports as it stands now.
let address = if ipExt.isNone():
Address(ip: parseIpAddress("0.0.0.0"),
tcpPort: Port(config.tcpPort + config.portsShift),
udpPort: Port(config.udpPort + config.portsShift))
else:
Address(ip: ipExt.get(),
tcpPort: Port(config.tcpPort + config.portsShift),
udpPort: Port(config.udpPort + config.portsShift))
# Create Ethereum Node
var node = newEthereumNode(config.nodekey, # Node identifier
address, # Address reachable for incoming requests
NetworkId(1), # Network Id, only applicable for ETH protocol
clientId, # Client id string
addAllCapabilities = false, # Disable default all RLPx capabilities
bindUdpPort = address.udpPort, # Assume same as external
bindTcpPort = address.tcpPort, # Assume same as external
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 a callback to exit on errors instead of
# using `await`.
# TODO: This looks a bit awkward and the API should perhaps be altered here.
let connectedFut = node.connectToNetwork(
true, # Enable listening
false # Disable discovery (only discovery v4 is currently supported)
)
connectedFut.callback = proc(data: pointer) {.gcsafe.} =
{.gcsafe.}:
if connectedFut.failed:
fatal "connectToNetwork failed", msg = connectedFut.readError.msg
quit(1)
# 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.
# https://github.com/nim-lang/Nim/issues/17369
var repeatMessage: proc(udata: pointer) {.gcsafe, raises: [Defect].}
repeatMessage = proc(udata: pointer) =
{.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()
when isMainModule:
let
rng = keys.newRng()
conf = WakuNodeConf.load()
run(conf, rng)

2
examples/nim.cfg Normal file
View File

@ -0,0 +1,2 @@
-d:chronicles_line_numbers
-d:chronicles_runtime_filtering:on