-
Notifications
You must be signed in to change notification settings - Fork 54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(cbindings): first commit - waku relay (#1632) #1714
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very cool, i like this PR.
I just left some comments with minor observations and items for discussion
apps/wakunode2/app.nim
Outdated
app.node.wakuRelay.unsubscribeAll(PubsubTopic($pubSubTopic)) | ||
|
||
proc publishMessage*(app: App, pubSubTopic: cstring, message: WakuMessage): Future[int] {.gcsafe, async.} = | ||
# Returns the number of peers connected to the given pubSubTopic. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# Returns the number of peers connected to the given pubSubTopic. |
library/cwakuv2.nim
Outdated
else: | ||
jsonResp = errResp("Timeout expired") | ||
|
||
proc init(config_file: cstring) {.dynlib, exportc.} = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could these be renamed to waku_new
and waku_start
? that way we follow the waku_*
convention. The same goes for show_version
apps/wakunode2/wakunode2.nim
Outdated
|
||
const versionString = "version / git commit hash: " & app.git_version | ||
{.pop.} # @TODO confutils.nim(775, 17) Error: can raise an unlisted exception: ref IOError | ||
proc init*(configFilePath = "") = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see init
here uses a path vs the rfc which suggests a json string. We should decide which approach to use to unify behaviors between implementations. cc: @danielSanchezQ for his thoughts as he has worked with the rust+go bindings before
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should init
an app here, but operate on the WakuNode
object as defined in waku_node.nim
(see comments elsewhere). wakunode2
is simply an application written around the Nim API. As stated elsewhere, we want to provide C bindings wrapping the Nim API, not the wakunode2
application
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see
init
here uses a path vs the rfc which suggests a json string. We should decide which approach to use to unify behaviors between implementations. cc: @danielSanchezQ for his thoughts as he has worked with the rust+go bindings before
IMO using a configuration is better. A config file should be used for a binary, a configuration object for a library.
@jm-clius Where can I find the nim api documentation? Don't know if Im understanding correctly, but, does it mean that the wakunode2
is an external application running and the bindings just communicate with it. Or do this works in a similar way to go-waku in where we do embedded the node within the application?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The application simply provides a way to configure and instantiate the node, so I guess more similar to "embedded within the application". The Nim API documentation is sparse and outdated (since it's only used internally), but in theory lives here: https://github.com/waku-org/nwaku/blob/master/docs/api/v2/node.md
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, it's very outdated, in fact. 😟 @Ivansete-status perhaps a good idea that we clean the Nim API doc up at some point during the development of the C bindings, since we're likely to expand/change it in any case.
exit(-1); | ||
} | ||
|
||
NimMain(); // initialize the Nim runtime |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably specify in the RFC or in the .h docs that NimMain() must be executed when using the nim c-bindings
library/cwakuv2.nim
Outdated
proc show_version(): cstring {.dynlib, exportc.} = | ||
return wakuNode2VersionString |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can be waku_version()
. waku_node
already provides git_version
, so I don't think this needs to be exported elsewhere. If we need to construct something specific, that should be done on the Nim API in waku_node.nim
.
library/cwakuv2.nim
Outdated
for i in 0 .. timeoutMs: | ||
if pubMsgFut.finished(): | ||
break | ||
sleep(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
chronos
has a withTimeout
function that i think could be used here instead of sleep
:
https://github.com/status-im/nim-chronos/blob/master/chronos/asyncloop.nim#L1116
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Super interesting @richard-ramos. Thanks for that!
However, we cannot invoke await
, and therefore, return a Future[..] in this proc because it is exported to C and this call needs to be synchronous.
library/cwakuv2.nim
Outdated
wakuMessage = WakuMessage( | ||
# Visit https://rfc.vac.dev/spec/14/ for further details | ||
payload: jsonContent["payload"].getStr().toSeq().mapIt(byte (it)), | ||
contentTopic: $jsonContent["content_topic"].getStr(), | ||
version: version, | ||
timestamp: getTime().toUnix(), | ||
ephemeral: false | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be possible to specify these values instead of having them hardcoded. The meta
field is also missing https://github.com/waku-org/nwaku/blob/master/waku/v2/waku_core/message/message.nim#L28 (this field should probably be added to the bindings rfc)
payload: payload, | ||
contentTopic: msg.contentTopic, | ||
version: msg.version, | ||
timestamp: int64(msg.timestamp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some fields from WakuMessage are missing. Maybe it also makes sense to include the proof
if RLN is enabled?
What do you think if instead of creating a JsonMessage with the WakuMessage content, to instead serialize it with https://github.com/status-im/nim-json-serialization? I imagine it should be possible to deserialize it too in waku_relay_publish
. The advantage of doing that is that if in the future new fields are added to WakuMessage
protobuffer, they'll be serialized/unserialized automatically in the c-bindings API functions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I absolutely agree. Nevertheless, I added the fields indicated in the next:
https://rfc.vac.dev/spec/36/#jsonmessage-type
# https://rfc.vac.dev/spec/36/#jsonmessageevent-type | ||
|
||
var payload = newString(len(msg.payload)) | ||
copyMem(addr payload[0], unsafeAddr msg.payload[0], len(msg.payload)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious about this line. Why is it necessary to copy the payload like this instead of doing it directly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point! I will try to directly use seq[byte]
instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I will leave it as is because if I change it to use seq[byte]
we don't get a readable base64 string. In this case, it appears sth like "payload":[97,71,57,115,89,81,61,61]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great to see progress here and thanks for including an example! Most important directional change I suggest is to keep the interface between the WakuNode
and the wakunode2
application very clean. Our library is the WakuNode
(defined in waku_node.nim
) and it already exposes a Nim API (as well as JSON, REST, etc.). We want to provide C bindings for this library (more or less just wrapping the Nim API) not the wakunode2
application.
Makefile
Outdated
################ | ||
.PHONY: cbindings cwakuv2example | ||
|
||
libcwakuv2.a: | build deps |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should move away from always having to specify v2
for Waku. Waku v1 is deprecated, in a sense, and not used anywhere. We even want to remove Waku v1 code from this repo, so it becomes simpler if we start referring to Waku v2 simply as the Waku. In other words, I think this can be libwaku
(just replacing the name occupied by previous unused target).
apps/wakunode2/wakunode2.nim
Outdated
@@ -92,20 +94,19 @@ when isMainModule: | |||
error "4/7 Mounting protocols failed", error=res5.error | |||
quit(QuitFailure) | |||
|
|||
proc startNode*() = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quite confused about this. Does this split the application's setup into an init
and start
phase?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exaclty, that is a simple split. However that might not be needed, and I will likely revert it back, when I wrap the waku_node.nim
library instead of the wakunode2 app.
apps/wakunode2/wakunode2.nim
Outdated
proc subscribeCallbackToTopic*(pubSubTopic: cstring, | ||
callback: proc(pubsubTopic: string, data: seq[byte]): Future[void] {.gcsafe, raises: [Defect].}) = | ||
wakunode2.subscribeCallbackToTopic(pubSubTopic, callback) | ||
|
||
proc unsubscribeCallbackFromTopic*(pubSubTopic: cstring, | ||
callback: proc(pubsubTopic: string, data: seq[byte]): Future[void] {.gcsafe, raises: [Defect].}) = | ||
wakunode2.unsubscribeCallbackFromTopic(pubSubTopic, callback) | ||
|
||
proc unsubscribeAllCallbacksFromTopic*(pubSubTopic: cstring) = | ||
wakunode2.unsubscribeAllCallbackFromTopic(pubSubTopic) | ||
|
||
proc publishMessage*(pubSubTopic: cstring, message: WakuMessage): Future[int] {.gcsafe, async.} = | ||
return await wakunode2.publishMessage(pubSubTopic, message) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way I see it, these do not belong here. The C bindings should be a wrapper around the Nim API defined in waku_node.nim
. As such the app modules should remain unchanged and preferably unimported elsewhere. Any additional API functionality should go to waku_node.nim
. Of course, since we need a configured WakuNode
object for those bindings, we may have to provide App
-like functionality when initiating the node. For now, though I think the configuration will be much, much simpler than is needed for a wakunode2
application and providing the basic configuration in a JsonConfig
via waku_new()
will be enough.
library/cwakuv2.nim
Outdated
proc show_version(): cstring {.dynlib, exportc.} = | ||
return wakuNode2VersionString |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can be waku_version()
. waku_node
already provides git_version
, so I don't think this needs to be exported elsewhere. If we need to construct something specific, that should be done on the Nim API in waku_node.nim
.
apps/wakunode2/wakunode2.nim
Outdated
|
||
const versionString = "version / git commit hash: " & app.git_version | ||
{.pop.} # @TODO confutils.nim(775, 17) Error: can raise an unlisted exception: ref IOError | ||
proc init*(configFilePath = "") = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should init
an app here, but operate on the WakuNode
object as defined in waku_node.nim
(see comments elsewhere). wakunode2
is simply an application written around the Nim API. As stated elsewhere, we want to provide C bindings wrapping the Nim API, not the wakunode2
application
Thanks for the comments ! It is now ready to be reviewed again :) Btw, very happy for the hint that you shared with me @LNSD. Thanks for that! The approach that is applied in this PR is inspired by https://github.com/KomodoPlatform/nimbus/blob/master/wrappers/wrapper_example.c |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really great work with this. Well done! I've added a couple of non-blocking comments. Feel free to merge once those are addressed.
I would suggest thinking of ways in which we can split the logic into separate modules in future (the main library module is going to grow very large!) I'd suggest using the REST API as inspiration and separate relay, store and other protocol bindings as well as types
, serdes
, etc. For now this is OK as a starting point.
library/waku.nim
Outdated
chronos, | ||
libp2p/crypto/secp, | ||
stew/shims/net, | ||
eth/net/nat as todo_delete_this_module |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leftover? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will clean the setupNat
, once this is merged, in a separate PR :)
library/waku.nim
Outdated
../../waku/v2/waku_core/message/message, | ||
../../waku/v2/waku_core/topics/pubsub_topic, | ||
../../waku/v2/node/peer_manager/peer_manager, | ||
../../waku/v2/node/waku_node as waku_node_module, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: is this alias necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, is not needed. Thanks!
library/waku.nim
Outdated
@@ -0,0 +1,399 @@ | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would consider calling this module something like libwaku
.
library/waku.nim
Outdated
proc waku_pubsub_topic(topicName: cstring, encoding: cstring, outPubsubTopic: var string) {.dynlib, exportc.} = | ||
# https://rfc.vac.dev/spec/36/#extern-char-waku_pubsub_topicchar-name-char-encoding | ||
outPubsubTopic = fmt"/waku/2/{topicName}/{encoding}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that for pubsub topics we've deprecated the {encoding}
suffix (https://rfc.vac.dev/spec/23/#pubsub-topic-format).
library/waku.nim
Outdated
let connectFut = node.connectToNodes(peers, source="static") | ||
while not connectFut.finished(): | ||
poll() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait, where does timeoutMs
come in here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, this is something we need to fix from lower levels. I'll add a TODO
statement here.
library/waku.nim
Outdated
# } | ||
return $(%* { "error": message }) | ||
|
||
proc setupNat(natConf, clientId: string, tcpPort, udpPort: Port): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I must be missing some details here, but why wouldn't setupNat
in the common
utility work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The setupNat
in the common
is deprecated with the next comment: deprecated: "Unsafe: this proc quits the app if something is not ok".
Once this PR is fixed I want to raise a separate PR to fix the setupNat
proc from common
and only use it. Also, clean the eth/net/nat as todo_delete_this_module
:)
waku/v2/node/waku_node.nim
Outdated
@@ -71,6 +70,7 @@ const clientId* = "Nimbus Waku v2 node" | |||
# Default Waku Filter Timeout | |||
const WakuFilterTimeout: Duration = 1.days | |||
|
|||
const wakuNode2VersionString* = "version / git commit hash: " & git_version |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not followed everywhere, but we want constants to be in PascalCase
(the exception are for those overridden by compiler definitions, such as git_version
above).
This would just be WakuNodeVersionString
(not WakuNode2)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really great work with this. Well done! I've added a couple of non-blocking comments. Feel free to merge once those are addressed.
I would suggest thinking of ways in which we can split the logic into separate modules in future (the main library module is going to grow very large!) I'd suggest using the REST API as inspiration and separate relay, store and other protocol bindings as well as types
, serdes
, etc. For now this is OK as a starting point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
library/libwaku.nim
Outdated
proc waku_poll() {.dynlib, exportc.} = | ||
poll() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this function used for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The poll()
function is part of the asynchronous chronos library. It is needed to make the Nim runtime to take steps further.
For each call to poll()
checks whether there are OS signals, or network events. This is therefore needed to make the Nwaku active so that it can attend and reply requests.
For example, in the Nwaku app, there is a runForever()
call that is an infinite loop around poll()
.
https://github.com/waku-org/nwaku/blob/2ec9809cf31b202ee705d337cf3384fe0f7374f4/apps/wakunode2/wakunode2.nim#LL151C4-L151C4
library/libwaku.nim
Outdated
if node.wakuRelay == nil: | ||
jsonResp = errResp("""Cannot unsubscribe without a callback. | ||
Kindly set it with the 'waku_set_event_callback' function""") | ||
return false | ||
|
||
if node.wakuRelay.isNil(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for learning purposes, what's the difference between wakuRelay == nil
and wakuRelay.isNil()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point my friend! Actually you discovered a bug hehe.
It seems isNil()
is more efficient, so I'd rather use it from now on.
proc isNil*[T](x: ptr T): bool {.noSideEffect, magic: "IsNil".}
proc isNil*(x: pointer): bool {.noSideEffect, magic: "IsNil".}
proc isNil*(x: cstring): bool {.noSideEffect, magic: "IsNil".}
proc isNil*[T: proc](x: T): bool {.noSideEffect, magic: "IsNil".}
Fast check whetherx
is nil. This is sometimes more efficient than ##== nil
.
Description
This allows the creation of a
libcwakuv2.a
library which exposes the next Waku functions:Changes
- subscribe/unsubscribe from a PubSub topic.
- send a message to a PubSub topic.
How to test
Build the
cwaku_example
with:make cwaku_example -j$(cat /proc/cpuinfo | grep processor | wc -l)
note: on completion, the
cwaku_example
binary and thelibwaku.a
library will be in thebuild
folder.Open a terminal and run:
./build/cwaku_example --peers=/ip4/127.0.0.1/tcp/60001/p2p/16Uiu2HAmVFXtAfSj4EiR7mL2KvL4EE2wztuQgUSBoj2Jx2KeXFLN --port=60000 --key=364d111d729a6eb6d2e6113e163f017b5ef03a6f94c9b5b7bb1bb36fa5cb07a9
Open a terminal and run:
./build/cwaku_example --peers=/ip4/127.0.0.1/tcp/60000/p2p/16Uiu2HAm2eqzqp6xn32fzgGi8K4BuF88W4Xy6yxsmDcW8h1gj6ie --port=60001 --key=0d714a1fada214dead6dc9c7274585eca0ff292451866e7d6d677dc818e8ccd2
Subscribe both nodes to the same pubsubtopic and from one node, publish a message. For that, follow the instructions that are shown in the naïve menu.
i.e.
Issue
#1632
Pending tasks
waku_relay_enough_peers(char* pubsubTopic)
. This feature is not currently implemented inlibp2p-1.0.0/libp2p/protocols/pubsub/gossipsub.nim
.show_version
function in the rfc. That would be helpful for users to know the library's version. This function is given in this PR.