feat!: expose the generated bindings, drop the hand-written FFI tier

The generated crate already provides what this tier existed to build:
CBOR marshalling, the callback dance, and a typed method per proc. So
macros.rs (trampoline, handle_ffi_call) and libwaku_response.rs
(RET_OK/RET_ERR, LibwakuResponse) are deleted rather than ported, and
node/events.rs goes with them -- its WakuEvent enum is now one generated
payload struct per event, which is what the typed listeners hand back.

What survives is what the generated crate does not give us: the domain
layer (WakuMessage, WakuContentTopic, MessageHash, pubsubtopic) and
WakuNodeConfig, which still serialises to the JSON create_node takes --
that JSON is unchanged, only its transport moved to CBOR.

BREAKING CHANGE: WakuNodeHandle and its Initialized/Running typestate,
waku_new, and waku_destroy are gone; callers use LogosDeliveryCtx::create
and let Drop tear the node down. set_event_callback is gone too: events
are per-name now, so a whole-stream callback has no equivalent -- use the
typed add_on_*_listener methods, which deliver a payload struct instead
of a JSON string to match on.

Tests and the example move with the API. The event handling gets shorter
rather than longer: no from_str, no matching five variants, no panicking
on Unrecognized -- a listener only receives the event it registered for.
Callers now base64-decode payloads themselves, since that was WakuMessage's
serde doing it before.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ivan FB
2026-07-16 01:08:23 +02:00
co-authored by Claude Opus 4.8
parent 98d005c9e1
commit 430ebc0bda
19 changed files with 260 additions and 1665 deletions
+1
View File
@@ -11,3 +11,4 @@ tokio = { version = "1.36.0", features = ["full"] }
tokio-util = { version = "0.7.10", features = ["rt"] }
waku = { path = "../../waku-bindings", package = "waku-bindings" }
serde_json = "1.0"
base64 = "0.21"
+48 -87
View File
@@ -1,101 +1,60 @@
use base64::Engine;
use std::io::Error;
use std::str::from_utf8;
use tokio::time::{sleep, Duration};
use waku::{
general::pubsubtopic::PubsubTopic, waku_new, Encoding, LibwakuResponse, WakuContentTopic,
WakuEvent, WakuMessage, WakuNodeConfig,
Encoding, LogosDeliveryCtx, ReceivedMessagePayload, WakuContentTopic, WakuMessage,
WakuNodeConfig,
};
const TOPIC: &str = "test";
const TIMEOUT: Duration = Duration::from_secs(30);
fn new_node(tcp_port: usize) -> LogosDeliveryCtx {
let config = serde_json::to_string(&WakuNodeConfig {
tcp_port: Some(tcp_port),
..Default::default()
})
.expect("config should serialise");
LogosDeliveryCtx::create(config, TIMEOUT).expect("should instantiate")
}
fn print_received(node_name: &'static str) -> impl Fn(&ReceivedMessagePayload) {
move |event| {
let payload = base64::engine::general_purpose::STANDARD
.decode(&event.waku_message.payload)
.expect("payload should be base64");
let msg = from_utf8(&payload).expect("should be valid message");
println!("::::::::::::::::::::::::::::::::::::::::::::::::::::");
println!("Message Received in {node_name}: {msg}");
println!("::::::::::::::::::::::::::::::::::::::::::::::::::::");
}
}
#[tokio::main]
async fn main() -> Result<(), Error> {
let node1 = waku_new(Some(WakuNodeConfig {
tcp_port: Some(60010), // TODO: use any available port.
..Default::default()
}))
.await
.expect("should instantiate");
let node2 = waku_new(Some(WakuNodeConfig {
tcp_port: Some(60020), // TODO: use any available port.
..Default::default()
}))
.await
.expect("should instantiate");
let node1 = new_node(60010); // TODO: use any available port.
let node2 = new_node(60020); // TODO: use any available port.
// ========================================================================
// Setting an event callback to be executed each time a message is received
node2
.set_event_callback(|response| {
if let LibwakuResponse::Success(v) = response {
let event: WakuEvent =
serde_json::from_str(v.unwrap().as_str()).expect("Parsing event to succeed");
// Registering a listener to be executed each time a message is received
node2.add_on_received_message_listener(print_received("NODE 2"));
node1.add_on_received_message_listener(print_received("NODE 1"));
match event {
WakuEvent::WakuMessage(evt) => {
// println!("WakuMessage event received: {:?}", evt.waku_message);
let message = evt.waku_message;
let payload = message.payload.to_vec();
let msg = from_utf8(&payload).expect("should be valid message");
println!("::::::::::::::::::::::::::::::::::::::::::::::::::::");
println!("Message Received in NODE 2: {}", msg);
println!("::::::::::::::::::::::::::::::::::::::::::::::::::::");
}
WakuEvent::RelayTopicHealthChange(_evt) => {
// dbg!("Relay topic change evt", evt);
}
WakuEvent::ConnectionChange(_evt) => {
// dbg!("Conn change evt", evt);
}
WakuEvent::Unrecognized(err) => panic!("Unrecognized waku event: {:?}", err),
_ => panic!("event case not expected"),
};
}
})
.expect("set event call back working");
node1
.set_event_callback(|response| {
if let LibwakuResponse::Success(v) = response {
let event: WakuEvent =
serde_json::from_str(v.unwrap().as_str()).expect("Parsing event to succeed");
match event {
WakuEvent::WakuMessage(evt) => {
// println!("WakuMessage event received: {:?}", evt.waku_message);
let message = evt.waku_message;
let payload = message.payload.to_vec();
let msg = from_utf8(&payload).expect("should be valid message");
println!("::::::::::::::::::::::::::::::::::::::::::::::::::::");
println!("Message Received in NODE 1: {}", msg);
println!("::::::::::::::::::::::::::::::::::::::::::::::::::::");
}
WakuEvent::RelayTopicHealthChange(_evt) => {
// dbg!("Relay topic change evt", evt);
}
WakuEvent::ConnectionChange(_evt) => {
// dbg!("Conn change evt", evt);
}
WakuEvent::Unrecognized(err) => panic!("Unrecognized waku event: {:?}", err),
_ => panic!("event case not expected"),
};
}
})
.expect("set event call back working");
let node1 = node1.start().await.expect("node1 should start");
let node2 = node2.start().await.expect("node2 should start");
node1.start_node_async().await.expect("node1 should start");
node2.start_node_async().await.expect("node2 should start");
// ========================================================================
// Subscribe to pubsub topic
let topic = PubsubTopic::new("test");
node1
.relay_subscribe(&topic)
.waku_relay_subscribe_async(TOPIC.to_string())
.await
.expect("node1 should subscribe");
node2
.relay_subscribe(&topic)
.waku_relay_subscribe_async(TOPIC.to_string())
.await
.expect("node2 should subscribe");
@@ -103,12 +62,16 @@ async fn main() -> Result<(), Error> {
// Connect nodes with each other
let addresses2 = node2
.listen_addresses()
.waku_listen_addresses_async()
.await
.expect("should obtain the addresses");
let address2 = addresses2
.split(',')
.next()
.expect("node2 should report a listen address");
node1
.connect(&addresses2[0], None)
.waku_connect_async(address2.to_string(), 10_000)
.await
.expect("node1 should connect to node2");
@@ -122,8 +85,9 @@ async fn main() -> Result<(), Error> {
let content_topic = WakuContentTopic::new("waku", "2", "test", Encoding::Proto);
let message = WakuMessage::new("Hello world", content_topic, 0, Vec::new(), false);
let message = serde_json::to_string(&message).expect("message should serialise");
node1
.relay_publish_message(&message, &topic, None)
.waku_relay_publish_async(TOPIC.to_string(), message, 10_000)
.await
.expect("should have sent the message");
@@ -135,13 +99,10 @@ async fn main() -> Result<(), Error> {
// ========================================================================
// Stop both instances
let node1 = node1.stop().await.expect("should stop");
let node2 = node2.stop().await.expect("should stop");
node1.stop_node_async().await.expect("should stop");
node2.stop_node_async().await.expect("should stop");
// ========================================================================
// Free resources
node1.waku_destroy().await.expect("should deallocate");
node2.waku_destroy().await.expect("should deallocate");
// Resources are freed by LogosDeliveryCtx's Drop impl.
Ok(())
}