mirror of
https://github.com/logos-messaging/logos-delivery-rust-bindings.git
synced 2026-07-29 22:43:34 +00:00
feat: reliable channels bindings and vendor bump
Exposes the reliable-channel surface: channel_create / channel_send / channel_close on WakuNodeHandle, backed by the logosdelivery_channel_* FFI calls. Channel state is persisted, so re-creating a closed channel resumes it rather than starting fresh, and send payloads travel base64-encoded. Adds the three channel lifecycle events (received / sent / error) to WakuEvent, plus the messaging events (sent, error, propagated, received) and connection status change, so callers can observe delivery rather than only firing and hoping. build.rs resolves the nimble package dirs and librln by scanning rather than hardcoding, since their names carry versions and hashes that move whenever nimble.lock does. It also checks make's exit status: a failed build previously passed silently and the crate linked a stale library from an earlier run. Bumps the vendor submodule to master. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
95040402fe
commit
96499cc735
4
.gitignore
vendored
4
.gitignore
vendored
@ -3,3 +3,7 @@
|
||||
/.idea
|
||||
/.fleet
|
||||
nimcache/
|
||||
|
||||
# SDS state written by nodes at runtime (e.g. the channels tests)
|
||||
data/
|
||||
.submodules-initialized
|
||||
|
||||
@ -15,8 +15,10 @@ pub use general::libwaku_response::LibwakuResponse;
|
||||
use rln;
|
||||
|
||||
pub use node::{
|
||||
waku_create_content_topic, waku_new, Initialized, Key, Multiaddr, PublicKey, RLNConfig,
|
||||
Running, SecretKey, WakuEvent, WakuMessageEvent, WakuNodeConfig, WakuNodeHandle,
|
||||
waku_create_content_topic, waku_new, ChannelMessageErrorEvent, ChannelMessageReceivedEvent,
|
||||
ChannelMessageSentEvent, ConnectionStatusChangeEvent, Initialized, Key, MessageErrorEvent,
|
||||
MessagePropagatedEvent, MessageReceivedEvent, MessageSentEvent, Multiaddr, PublicKey,
|
||||
RLNConfig, Running, SecretKey, WakuEvent, WakuMessageEvent, WakuNodeConfig, WakuNodeHandle,
|
||||
};
|
||||
|
||||
pub use general::contenttopic::{Encoding, WakuContentTopic};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use crate::general::libwaku_response::LibwakuResponse;
|
||||
|
||||
use std::{slice, str};
|
||||
use waku_sys::WakuCallBack;
|
||||
use waku_sys::FFICallBack;
|
||||
|
||||
unsafe extern "C" fn trampoline<F>(
|
||||
ret_code: ::std::os::raw::c_int,
|
||||
@ -26,7 +26,7 @@ unsafe extern "C" fn trampoline<F>(
|
||||
closure(result);
|
||||
}
|
||||
|
||||
pub fn get_trampoline<F>(_closure: &F) -> WakuCallBack
|
||||
pub fn get_trampoline<F>(_closure: &F) -> FFICallBack
|
||||
where
|
||||
F: FnMut(LibwakuResponse),
|
||||
{
|
||||
@ -58,9 +58,9 @@ macro_rules! handle_ffi_call {
|
||||
let cb = get_trampoline(&closure);
|
||||
$waku_fn(
|
||||
$ctx, // Pass the context
|
||||
$($($arg),*,)? // Expand the variadic arguments if provided
|
||||
cb, // Pass the callback trampoline
|
||||
&mut closure as *mut _ as *mut c_void
|
||||
&mut closure as *mut _ as *mut c_void,
|
||||
$($($arg),*)? // Expand the variadic arguments if provided
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
93
waku-bindings/src/node/channels.rs
Normal file
93
waku-bindings/src/node/channels.rs
Normal file
@ -0,0 +1,93 @@
|
||||
//! Reliable channels: an ordered, gap-detecting message stream over a content topic.
|
||||
|
||||
// std
|
||||
use std::ffi::CString;
|
||||
// crates
|
||||
use base64::Engine;
|
||||
use serde::Serialize;
|
||||
// internal
|
||||
use crate::general::libwaku_response::{handle_no_response, handle_response, LibwakuResponse};
|
||||
use crate::general::Result;
|
||||
use crate::handle_ffi_call;
|
||||
use crate::node::context::WakuNodeContext;
|
||||
|
||||
/// Body of `logosdelivery_channel_send`, whose payload travels base64-encoded.
|
||||
#[derive(Serialize)]
|
||||
struct ChannelMessage {
|
||||
payload: String,
|
||||
ephemeral: bool,
|
||||
}
|
||||
|
||||
/// Create (or restore) a reliable channel, returning its id.
|
||||
///
|
||||
/// Channel state is persisted, so re-creating a previously closed channel
|
||||
/// resumes it rather than starting a new one.
|
||||
pub async fn logosdelivery_channel_create(
|
||||
ctx: &WakuNodeContext,
|
||||
channel_id: &str,
|
||||
content_topic: &str,
|
||||
sender_id: &str,
|
||||
) -> Result<String> {
|
||||
let channel_id =
|
||||
CString::new(channel_id).expect("CString should build properly from the channel id");
|
||||
let content_topic =
|
||||
CString::new(content_topic).expect("CString should build properly from the content topic");
|
||||
let sender_id =
|
||||
CString::new(sender_id).expect("CString should build properly from the sender id");
|
||||
|
||||
handle_ffi_call!(
|
||||
waku_sys::logosdelivery_channel_create,
|
||||
handle_response,
|
||||
ctx.get_ptr(),
|
||||
channel_id.as_ptr(),
|
||||
content_topic.as_ptr(),
|
||||
sender_id.as_ptr()
|
||||
)
|
||||
}
|
||||
|
||||
/// Send a message over a reliable channel, returning the id of the send request.
|
||||
///
|
||||
/// That id is what correlates this call with the `onChannelMessageSent` /
|
||||
/// `onChannelMessageError` events reporting the message's fate.
|
||||
pub async fn logosdelivery_channel_send(
|
||||
ctx: &WakuNodeContext,
|
||||
channel_id: &str,
|
||||
payload: &[u8],
|
||||
ephemeral: bool,
|
||||
) -> Result<String> {
|
||||
let channel_id =
|
||||
CString::new(channel_id).expect("CString should build properly from the channel id");
|
||||
|
||||
let message = ChannelMessage {
|
||||
payload: base64::engine::general_purpose::STANDARD.encode(payload),
|
||||
ephemeral,
|
||||
};
|
||||
let message = CString::new(
|
||||
serde_json::to_string(&message)
|
||||
.expect("ChannelMessage should always be able to success serializing"),
|
||||
)
|
||||
.expect("CString should build properly from the serialized channel message");
|
||||
|
||||
handle_ffi_call!(
|
||||
waku_sys::logosdelivery_channel_send,
|
||||
handle_response,
|
||||
ctx.get_ptr(),
|
||||
channel_id.as_ptr(),
|
||||
message.as_ptr()
|
||||
)
|
||||
}
|
||||
|
||||
/// Close a reliable channel, stopping its loops.
|
||||
///
|
||||
/// Persisted state survives, so the channel can be restored by re-creating it.
|
||||
pub async fn logosdelivery_channel_close(ctx: &WakuNodeContext, channel_id: &str) -> Result<()> {
|
||||
let channel_id =
|
||||
CString::new(channel_id).expect("CString should build properly from the channel id");
|
||||
|
||||
handle_ffi_call!(
|
||||
waku_sys::logosdelivery_channel_close,
|
||||
handle_no_response,
|
||||
ctx.get_ptr(),
|
||||
channel_id.as_ptr()
|
||||
)
|
||||
}
|
||||
@ -12,13 +12,18 @@ use smart_default::SmartDefault;
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WakuNodeConfig {
|
||||
/// Listening IP address. Default `0.0.0.0`
|
||||
#[serde(skip_serializing_if = "Option::is_none", rename = "listen-address")]
|
||||
#[default(Some(std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0))))]
|
||||
pub host: Option<std::net::IpAddr>,
|
||||
/// Libp2p TCP listening port. Default `60000`. Use `0` for **random**
|
||||
#[default(Some(60000))]
|
||||
pub tcp_port: Option<usize>,
|
||||
/// Secp256k1 private key in Hex format (`0x123...abc`). Default random
|
||||
#[serde(with = "secret_key_serde", rename = "key")]
|
||||
#[serde(
|
||||
with = "secret_key_serde",
|
||||
skip_serializing_if = "Option::is_none",
|
||||
rename = "nodekey"
|
||||
)]
|
||||
pub node_key: Option<SecretKey>,
|
||||
/// Cluster id that the node is running in
|
||||
#[default(Some(0))]
|
||||
@ -27,6 +32,7 @@ pub struct WakuNodeConfig {
|
||||
/// Relay protocol
|
||||
#[default(Some(true))]
|
||||
pub relay: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub relay_topics: Vec<String>,
|
||||
#[default(vec![1])]
|
||||
pub shards: Vec<usize>,
|
||||
|
||||
@ -49,7 +49,7 @@ impl WakuNodeContext {
|
||||
*boxed_closure = Box::new(closure);
|
||||
unsafe {
|
||||
let cb = get_trampoline(&(*boxed_closure));
|
||||
waku_sys::waku_set_event_callback(
|
||||
waku_sys::logosdelivery_set_event_callback(
|
||||
self.obj_ptr,
|
||||
cb,
|
||||
&mut (*boxed_closure) as *mut _ as *mut c_void,
|
||||
|
||||
@ -27,6 +27,30 @@ pub enum WakuEvent {
|
||||
#[serde(rename = "connection_change")]
|
||||
ConnectionChange(ConnectionChangeEvent),
|
||||
|
||||
#[serde(rename = "connection_status_change")]
|
||||
ConnectionStatusChange(ConnectionStatusChangeEvent),
|
||||
|
||||
#[serde(rename = "message_sent")]
|
||||
MessageSent(MessageSentEvent),
|
||||
|
||||
#[serde(rename = "message_error")]
|
||||
MessageError(MessageErrorEvent),
|
||||
|
||||
#[serde(rename = "message_propagated")]
|
||||
MessagePropagated(MessagePropagatedEvent),
|
||||
|
||||
#[serde(rename = "message_received")]
|
||||
MessageReceived(MessageReceivedEvent),
|
||||
|
||||
#[serde(rename = "channel_message_received")]
|
||||
ChannelMessageReceived(ChannelMessageReceivedEvent),
|
||||
|
||||
#[serde(rename = "channel_message_sent")]
|
||||
ChannelMessageSent(ChannelMessageSentEvent),
|
||||
|
||||
#[serde(rename = "channel_message_error")]
|
||||
ChannelMessageError(ChannelMessageErrorEvent),
|
||||
|
||||
Unrecognized(serde_json::Value),
|
||||
}
|
||||
|
||||
@ -62,6 +86,116 @@ pub struct ConnectionChangeEvent {
|
||||
pub peer_event: String,
|
||||
}
|
||||
|
||||
/// Type of `event` field for a `message sent` event
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MessageSentEvent {
|
||||
/// The id returned by the originating send
|
||||
pub request_id: String,
|
||||
/// The message hash
|
||||
pub message_hash: String,
|
||||
}
|
||||
|
||||
/// Type of `event` field for a `message error` event
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MessageErrorEvent {
|
||||
/// The id returned by the originating send
|
||||
pub request_id: String,
|
||||
/// The message hash
|
||||
pub message_hash: String,
|
||||
/// Why the send failed
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
/// Type of `event` field for a `message propagated` event
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MessagePropagatedEvent {
|
||||
/// The id returned by the originating send
|
||||
pub request_id: String,
|
||||
/// The message hash
|
||||
pub message_hash: String,
|
||||
}
|
||||
|
||||
/// Type of `event` field for a `message received` event
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MessageReceivedEvent {
|
||||
/// The message hash
|
||||
pub message_hash: String,
|
||||
/// The message in [`WakuMessage`] format
|
||||
pub message: WakuMessage,
|
||||
}
|
||||
|
||||
/// Type of `event` field for a `connection status change` event
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ConnectionStatusChangeEvent {
|
||||
/// Whether the node is online, and how it is connected
|
||||
pub connection_status: String,
|
||||
}
|
||||
|
||||
/// Type of `event` field for a `channel message received` event
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ChannelMessageReceivedEvent {
|
||||
/// The channel the message arrived on
|
||||
pub channel_id: String,
|
||||
/// The participant that sent the message
|
||||
pub sender_id: String,
|
||||
/// The message contents
|
||||
#[serde(with = "base64_payload")]
|
||||
pub payload: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Type of `event` field for a `channel message sent` event
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ChannelMessageSentEvent {
|
||||
/// The channel the message was sent on
|
||||
pub channel_id: String,
|
||||
/// The id returned by the originating send
|
||||
pub request_id: String,
|
||||
}
|
||||
|
||||
/// Type of `event` field for a `channel message error` event
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ChannelMessageErrorEvent {
|
||||
/// The channel the message was sent on
|
||||
pub channel_id: String,
|
||||
/// The id returned by the originating send
|
||||
pub request_id: String,
|
||||
/// Why the send failed
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
mod base64_payload {
|
||||
use base64::Engine;
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
pub fn serialize<S>(payload: &[u8], serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
base64::engine::general_purpose::STANDARD
|
||||
.encode(payload)
|
||||
.serialize(serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let as_string = String::deserialize(deserializer)?;
|
||||
base64::engine::general_purpose::STANDARD
|
||||
.decode(as_string)
|
||||
.map_err(|e| D::Error::custom(format!("{e}")))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::WakuEvent;
|
||||
@ -87,6 +221,46 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_channel_message_received_event() {
|
||||
let s = "{\"eventType\":\"channel_message_received\",\"channelId\":\"my-channel\",\"senderId\":\"alice\",\"payload\":\"SGkgZnJvbSDwn6aAIQ==\"}";
|
||||
let evt: WakuEvent = serde_json::from_str(s).unwrap();
|
||||
match evt {
|
||||
WakuEvent::ChannelMessageReceived(event) => {
|
||||
assert_eq!(event.channel_id, "my-channel");
|
||||
assert_eq!(event.sender_id, "alice");
|
||||
assert_eq!(event.payload, "Hi from 🦀!".as_bytes());
|
||||
}
|
||||
_ => panic!("Expected ChannelMessageReceived event, but got {:?}", evt),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_channel_message_error_event() {
|
||||
let s = "{\"eventType\":\"channel_message_error\",\"channelId\":\"my-channel\",\"requestId\":\"req-1\",\"error\":\"boom\"}";
|
||||
let evt: WakuEvent = serde_json::from_str(s).unwrap();
|
||||
match evt {
|
||||
WakuEvent::ChannelMessageError(event) => {
|
||||
assert_eq!(event.channel_id, "my-channel");
|
||||
assert_eq!(event.request_id, "req-1");
|
||||
assert_eq!(event.error, "boom");
|
||||
}
|
||||
_ => panic!("Expected ChannelMessageError event, but got {:?}", evt),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_connection_status_change_event() {
|
||||
let s = "{\"eventType\":\"connection_status_change\",\"connectionStatus\":\"Online\"}";
|
||||
let evt: WakuEvent = serde_json::from_str(s).unwrap();
|
||||
match evt {
|
||||
WakuEvent::ConnectionStatusChange(event) => {
|
||||
assert_eq!(event.connection_status, "Online");
|
||||
}
|
||||
_ => panic!("Expected ConnectionStatusChange event, but got {:?}", evt),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_connection_change_event() {
|
||||
let s = "{\"eventType\":\"connection_change\", \"peerId\":\"16Uiu2HAmAR24Mbb6VuzoyUiGx42UenDkshENVDj4qnmmbabLvo31\",\"peerEvent\":\"Joined\"}";
|
||||
|
||||
@ -36,7 +36,7 @@ pub async fn waku_new(config: Option<WakuNodeConfig>) -> Result<WakuNodeContext>
|
||||
let mut closure = result_cb;
|
||||
let obj_ptr = unsafe {
|
||||
let cb = get_trampoline(&closure);
|
||||
waku_sys::waku_new(config_ptr, cb, &mut closure as *mut _ as *mut c_void)
|
||||
waku_sys::logosdelivery_create_node(config_ptr, cb, &mut closure as *mut _ as *mut c_void)
|
||||
};
|
||||
|
||||
notify.notified().await; // Wait until a result is received
|
||||
@ -49,19 +49,31 @@ pub async fn waku_new(config: Option<WakuNodeConfig>) -> Result<WakuNodeContext>
|
||||
}
|
||||
|
||||
pub async fn waku_destroy(ctx: &WakuNodeContext) -> Result<()> {
|
||||
handle_ffi_call!(waku_sys::waku_destroy, handle_no_response, ctx.get_ptr())
|
||||
handle_ffi_call!(
|
||||
waku_sys::logosdelivery_destroy,
|
||||
handle_no_response,
|
||||
ctx.get_ptr()
|
||||
)
|
||||
}
|
||||
|
||||
/// Start a Waku node mounting all the protocols that were enabled during the Waku node instantiation.
|
||||
/// as per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_start)
|
||||
pub async fn waku_start(ctx: &WakuNodeContext) -> Result<()> {
|
||||
handle_ffi_call!(waku_sys::waku_start, handle_no_response, ctx.get_ptr())
|
||||
handle_ffi_call!(
|
||||
waku_sys::logosdelivery_start_node,
|
||||
handle_no_response,
|
||||
ctx.get_ptr()
|
||||
)
|
||||
}
|
||||
|
||||
/// Stops a Waku node
|
||||
/// as per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_stop)
|
||||
pub async fn waku_stop(ctx: &WakuNodeContext) -> Result<()> {
|
||||
handle_ffi_call!(waku_sys::waku_stop, handle_no_response, ctx.get_ptr())
|
||||
handle_ffi_call!(
|
||||
waku_sys::logosdelivery_stop_node,
|
||||
handle_no_response,
|
||||
ctx.get_ptr()
|
||||
)
|
||||
}
|
||||
|
||||
/// nwaku version
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
//! Waku node implementation
|
||||
|
||||
mod channels;
|
||||
mod config;
|
||||
mod context;
|
||||
mod events;
|
||||
@ -26,7 +27,11 @@ use crate::general::{messagehash::MessageHash, Result, WakuMessage};
|
||||
use crate::node::context::WakuNodeContext;
|
||||
pub use config::RLNConfig;
|
||||
pub use config::WakuNodeConfig;
|
||||
pub use events::{WakuEvent, WakuMessageEvent};
|
||||
pub use events::{
|
||||
ChannelMessageErrorEvent, ChannelMessageReceivedEvent, ChannelMessageSentEvent,
|
||||
ConnectionStatusChangeEvent, MessageErrorEvent, MessagePropagatedEvent, MessageReceivedEvent,
|
||||
MessageSentEvent, WakuEvent, WakuMessageEvent,
|
||||
};
|
||||
pub use relay::waku_create_content_topic;
|
||||
|
||||
// Define state marker types
|
||||
@ -113,6 +118,41 @@ impl WakuNodeHandle<Running> {
|
||||
peers::waku_connect(&self.ctx, address, timeout).await
|
||||
}
|
||||
|
||||
/// Create (or restore) a reliable channel on `content_topic`, returning its id.
|
||||
///
|
||||
/// `sender_id` identifies this participant within the channel. Messages
|
||||
/// arrive through the `onChannelMessageReceived` event, so register an event
|
||||
/// callback before creating the channel to avoid missing any.
|
||||
pub async fn channel_create(
|
||||
&self,
|
||||
channel_id: &str,
|
||||
content_topic: &str,
|
||||
sender_id: &str,
|
||||
) -> Result<String> {
|
||||
channels::logosdelivery_channel_create(&self.ctx, channel_id, content_topic, sender_id)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Send `payload` over a reliable channel, returning the send request's id.
|
||||
///
|
||||
/// The id correlates this call with the `onChannelMessageSent` /
|
||||
/// `onChannelMessageError` events that report the message's fate. An
|
||||
/// `ephemeral` message is not persisted and is not retransmitted.
|
||||
pub async fn channel_send(
|
||||
&self,
|
||||
channel_id: &str,
|
||||
payload: &[u8],
|
||||
ephemeral: bool,
|
||||
) -> Result<String> {
|
||||
channels::logosdelivery_channel_send(&self.ctx, channel_id, payload, ephemeral).await
|
||||
}
|
||||
|
||||
/// Close a reliable channel. Persisted state survives, so re-creating the
|
||||
/// channel restores it.
|
||||
pub async fn channel_close(&self, channel_id: &str) -> Result<()> {
|
||||
channels::logosdelivery_channel_close(&self.ctx, channel_id).await
|
||||
}
|
||||
|
||||
pub async fn relay_publish_txt(
|
||||
&self,
|
||||
pubsub_topic: &PubsubTopic,
|
||||
|
||||
68
waku-bindings/tests/channels.rs
Normal file
68
waku-bindings/tests/channels.rs
Normal file
@ -0,0 +1,68 @@
|
||||
use serial_test::serial;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use waku_bindings::{waku_new, LibwakuResponse, WakuEvent, WakuNodeConfig};
|
||||
|
||||
const TEST_CHANNEL_ID: &str = "test-channel";
|
||||
const TEST_CONTENT_TOPIC: &str = "/test/1/channels/proto";
|
||||
const TEST_SENDER_ID: &str = "test-sender";
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn channel_create_send_close() {
|
||||
let node = waku_new(Some(WakuNodeConfig {
|
||||
tcp_port: Some(60070),
|
||||
..Default::default()
|
||||
}))
|
||||
.await
|
||||
.expect("node should instantiate");
|
||||
|
||||
// Channel traffic is reported through events, so capture them to prove the
|
||||
// channel event payloads decode into the WakuEvent variants.
|
||||
let events: Arc<Mutex<Vec<WakuEvent>>> = Arc::new(Mutex::new(Vec::new()));
|
||||
let events_cloned = events.clone();
|
||||
node.set_event_callback(move |response| {
|
||||
if let LibwakuResponse::Success(Some(v)) = response {
|
||||
let event: WakuEvent = serde_json::from_str(&v).expect("event should parse");
|
||||
events_cloned.lock().unwrap().push(event);
|
||||
}
|
||||
})
|
||||
.expect("event callback should be set");
|
||||
|
||||
let node = node.start().await.expect("node should start");
|
||||
|
||||
let channel_id = node
|
||||
.channel_create(TEST_CHANNEL_ID, TEST_CONTENT_TOPIC, TEST_SENDER_ID)
|
||||
.await
|
||||
.expect("channel should be created");
|
||||
assert_eq!(channel_id, TEST_CHANNEL_ID);
|
||||
|
||||
let request_id = node
|
||||
.channel_send(TEST_CHANNEL_ID, b"Hi from a reliable channel!", false)
|
||||
.await
|
||||
.expect("channel send should succeed");
|
||||
assert!(!request_id.is_empty(), "send should return a request id");
|
||||
|
||||
// Give the send a moment to finalise and emit its outcome event.
|
||||
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
|
||||
|
||||
node.channel_close(TEST_CHANNEL_ID)
|
||||
.await
|
||||
.expect("channel should close");
|
||||
|
||||
// Every event this node emitted parsed into a known variant rather than
|
||||
// falling through to Unrecognized. Scoped so the guard is dropped before the
|
||||
// awaits below.
|
||||
{
|
||||
let events = events.lock().unwrap();
|
||||
println!("events observed: {:?}", events);
|
||||
assert!(
|
||||
!events
|
||||
.iter()
|
||||
.any(|event| matches!(event, WakuEvent::Unrecognized(_))),
|
||||
"no event should be Unrecognized, got: {events:?}"
|
||||
);
|
||||
}
|
||||
|
||||
let node = node.stop().await.expect("node should stop");
|
||||
node.waku_destroy().await.expect("node should be destroyed");
|
||||
}
|
||||
@ -53,60 +53,114 @@ fn build_nwaku_lib(project_dir: &Path) {
|
||||
set_current_dir(nwaku_path).expect("Moving to vendor dir");
|
||||
|
||||
let mut cmd = Command::new("make");
|
||||
cmd.arg("libwaku").arg("STATIC=1");
|
||||
cmd.status()
|
||||
.map_err(|e| println!("cargo:warning=make build failed due to: {e}"))
|
||||
.unwrap();
|
||||
cmd.arg("liblogosdelivery").arg("STATIC=1");
|
||||
let status = cmd
|
||||
.status()
|
||||
.unwrap_or_else(|e| panic!("Failed to run 'make liblogosdelivery': {e}"));
|
||||
|
||||
// The exit status has to be checked, not just the spawn: a failed make would
|
||||
// otherwise pass silently and the crate would link a stale library from a
|
||||
// previous build.
|
||||
if !status.success() {
|
||||
panic!("'make liblogosdelivery STATIC=1' failed with {status}");
|
||||
}
|
||||
|
||||
set_current_dir(project_dir).expect("Going back to project dir");
|
||||
}
|
||||
|
||||
/// Resolves a package directory under the vendor's `nimbledeps/pkgs2`, whose
|
||||
/// names carry a version and hash (e.g. `nat_traversal-0.0.1-1a376d3e...`) that
|
||||
/// change whenever `nimble.lock` moves.
|
||||
fn nimble_pkg_dir(nwaku_path: &Path, pkg_name: &str) -> PathBuf {
|
||||
let pkgs_dir = nwaku_path.join("nimbledeps/pkgs2");
|
||||
let prefix = format!("{pkg_name}-");
|
||||
std::fs::read_dir(&pkgs_dir)
|
||||
.unwrap_or_else(|e| panic!("Cannot read {}: {e}", pkgs_dir.display()))
|
||||
.filter_map(|entry| entry.ok())
|
||||
.map(|entry| entry.path())
|
||||
.find(|path| {
|
||||
path.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.is_some_and(|name| name.starts_with(&prefix))
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"No '{pkg_name}' package under {}. Was 'make liblogosdelivery' run?",
|
||||
pkgs_dir.display()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Finds the vendor's `librln_<version>.a` and returns the name to link it by
|
||||
/// (the file stem without the `lib` prefix).
|
||||
fn find_librln(nwaku_path: &Path) -> String {
|
||||
std::fs::read_dir(nwaku_path)
|
||||
.unwrap_or_else(|e| panic!("Cannot read {}: {e}", nwaku_path.display()))
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter_map(|entry| entry.file_name().into_string().ok())
|
||||
.find_map(|name| {
|
||||
name.strip_prefix("lib")
|
||||
.and_then(|name| name.strip_suffix(".a"))
|
||||
.filter(|name| name.starts_with("rln"))
|
||||
.map(str::to_owned)
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"No 'librln_*.a' in {}. Was 'make librln' run?",
|
||||
nwaku_path.display()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_bindgen_code(project_dir: &Path) {
|
||||
let nwaku_path = project_dir.join("vendor");
|
||||
let header_path = nwaku_path.join("library/libwaku.h");
|
||||
|
||||
cc::Build::new()
|
||||
.object(
|
||||
nwaku_path
|
||||
.join("vendor/nim-libbacktrace/libbacktrace_wrapper.o")
|
||||
.display()
|
||||
.to_string(),
|
||||
)
|
||||
.compile("libbacktrace_wrapper");
|
||||
// The kernel header includes the stable one, so this single entry point
|
||||
// yields both the low-level waku_* tier and the logosdelivery_* messaging
|
||||
// and reliable-channel surface.
|
||||
let header_path = nwaku_path.join("library/liblogosdelivery_kernel.h");
|
||||
|
||||
println!("cargo:rerun-if-changed={}", header_path.display());
|
||||
println!(
|
||||
"cargo:rustc-link-search={}",
|
||||
nwaku_path.join("build").display()
|
||||
);
|
||||
println!("cargo:rustc-link-lib=static=waku");
|
||||
println!("cargo:rustc-link-lib=static=logosdelivery");
|
||||
|
||||
// nat_traversal moved from a git submodule under vendor/ to a nimble
|
||||
// dependency, and builds its NAT archives inside its own package dir.
|
||||
let nat_traversal = nimble_pkg_dir(&nwaku_path, "nat_traversal");
|
||||
|
||||
println!(
|
||||
"cargo:rustc-link-search={}",
|
||||
nwaku_path
|
||||
.join("vendor/nim-nat-traversal/vendor/miniupnp/miniupnpc/build")
|
||||
nat_traversal
|
||||
.join("vendor/miniupnp/miniupnpc/build")
|
||||
.display()
|
||||
);
|
||||
println!("cargo:rustc-link-lib=static=miniupnpc");
|
||||
|
||||
println!(
|
||||
"cargo:rustc-link-search={}",
|
||||
nwaku_path
|
||||
.join("vendor/nim-nat-traversal/vendor/libnatpmp-upstream")
|
||||
.display()
|
||||
nat_traversal.join("vendor/libnatpmp-upstream").display()
|
||||
);
|
||||
println!("cargo:rustc-link-lib=static=natpmp");
|
||||
|
||||
println!("cargo:rustc-link-lib=dl");
|
||||
println!("cargo:rustc-link-lib=m");
|
||||
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}",
|
||||
nwaku_path
|
||||
.join("vendor/nim-libbacktrace/install/usr/lib")
|
||||
.display()
|
||||
);
|
||||
println!("cargo:rustc-link-lib=static=backtrace");
|
||||
// boringssl (pulled in by libp2p) is C++, so its runtime has to be linked.
|
||||
if cfg!(target_os = "macos") {
|
||||
println!("cargo:rustc-link-lib=c++");
|
||||
} else {
|
||||
println!("cargo:rustc-link-lib=stdc++");
|
||||
}
|
||||
|
||||
// The vendor's Makefile fetches librln into its root, naming it after the
|
||||
// RLN version it pins, so the archive is discovered rather than hardcoded.
|
||||
let librln = find_librln(&nwaku_path);
|
||||
println!("cargo:rustc-link-search={}", nwaku_path.display());
|
||||
println!("cargo:rustc-link-lib=static={librln}");
|
||||
|
||||
// libbacktrace is not linked: the vendor builds with -d:disable_libbacktrace.
|
||||
|
||||
cc::Build::new()
|
||||
.file("src/cmd.c") // Compile the C file
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit 4117449b9af6c0304a6115dd4bc0d1d745159685
|
||||
Subproject commit 77cb8a6c7a199b7823e5149bb72f8ce384883b8f
|
||||
Loading…
x
Reference in New Issue
Block a user