From dd4d093945f26e7f356bd9f2241c0e1764a06bfb Mon Sep 17 00:00:00 2001 From: Ivan FB Date: Thu, 16 Jul 2026 01:28:54 +0200 Subject: [PATCH] fix(examples): port toy-chat and tic-tac-toe-gui to the generated API examples/ is its own cargo workspace and only examples/basic is a member of the root one, so nothing built from the repo root ever compiled these two. They still used waku_new, set_event_callback, WakuEvent and the WakuNodeHandle typestate, none of which exist any more. Both collapse their App typestate: the node no longer carries one, so neither can the app wrapping it. Event handling shrinks to a single add_on_received_message_listener -- the filter on content topic stays, but the enum match, the JSON parse and the panics on unexpected variants go, since a listener only receives what it registered for. Payloads are base64-decoded by the caller now. The store DTOs (StoreQueryRequest, StoreResponse, StoreWakuMessageResponse) come back as general/store.rs: they are plain serde shapes, and deleting node/store.rs took them out along with the FFI call they sat next to. toy-chat needs them to build the store query JSON and read the response. CI gains a check over examples/Cargo.toml. Without it these rot silently on the next API change, which is exactly how they got here. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/main.yml | 8 ++ examples/Cargo.lock | 172 ++++++++++++++------------- examples/tic-tac-toe-gui/Cargo.toml | 1 + examples/tic-tac-toe-gui/src/main.rs | 121 ++++++++++--------- examples/toy-chat/Cargo.toml | 1 + examples/toy-chat/src/main.rs | 151 +++++++++++------------ waku-bindings/src/general/mod.rs | 1 + waku-bindings/src/general/store.rs | 136 +++++++++++++++++++++ waku-bindings/src/lib.rs | 1 + 9 files changed, 381 insertions(+), 211 deletions(-) create mode 100644 waku-bindings/src/general/store.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 79592f1..804c300 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -38,6 +38,14 @@ jobs: continue-on-error: false with: command: check + # examples/ is its own workspace and only examples/basic is a member of + # the root one, so a plain `cargo check` here never sees toy-chat or + # tic-tac-toe-gui. They break silently on any API change without this. + - uses: actions-rs/cargo@v1 + continue-on-error: false + with: + command: check + args: --manifest-path examples/Cargo.toml --workspace test: name: Test diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 27d14c5..3a086df 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "ab_glyph" @@ -163,7 +163,7 @@ dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -740,6 +740,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" name = "basic" version = "0.1.0" dependencies = [ + "base64 0.21.7", "futures", "serde_json", "tokio", @@ -747,28 +748,6 @@ dependencies = [ "waku-bindings", ] -[[package]] -name = "bindgen" -version = "0.64.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" -dependencies = [ - "bitflags 1.3.2", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "log", - "peeking_take_while", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 1.0.109", - "which", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -967,15 +946,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -1017,6 +987,33 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half 2.7.1", +] + [[package]] name = "cipher" version = "0.4.4" @@ -1027,17 +1024,6 @@ dependencies = [ "inout", ] -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading 0.8.6", -] - [[package]] name = "clap" version = "2.34.0" @@ -1941,6 +1927,17 @@ dependencies = [ "miniz_oxide 0.8.0", ] +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + [[package]] name = "fnv" version = "1.0.7" @@ -2182,12 +2179,6 @@ dependencies = [ "xml-rs", ] -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - [[package]] name = "glow" version = "0.12.3" @@ -2281,6 +2272,17 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy 0.8.27", +] + [[package]] name = "hashbrown" version = "0.11.2" @@ -2712,12 +2714,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "leb128" version = "0.2.5" @@ -2885,12 +2881,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.7.4" @@ -3078,16 +3068,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "num" version = "0.4.0" @@ -3473,12 +3453,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - [[package]] name = "percent-encoding" version = "2.3.1" @@ -3623,7 +3597,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -4156,7 +4130,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" dependencies = [ - "half", + "half 1.8.3", "serde", ] @@ -4392,6 +4366,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +dependencies = [ + "lock_api", +] + [[package]] name = "spki" version = "0.6.0" @@ -4572,6 +4555,7 @@ name = "tic-tac-toe-gui" version = "0.1.0" dependencies = [ "ark-std", + "base64 0.21.7", "ctrlc", "eframe", "egui", @@ -4731,6 +4715,7 @@ dependencies = [ name = "toy-chat" version = "0.1.0" dependencies = [ + "base64 0.21.7", "chrono", "crossterm", "prost", @@ -4974,8 +4959,11 @@ dependencies = [ name = "waku-sys" version = "1.0.0" dependencies = [ - "bindgen", "cc", + "ciborium", + "flume", + "serde", + "tokio", ] [[package]] @@ -6008,7 +5996,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive 0.8.27", ] [[package]] @@ -6022,6 +6019,17 @@ dependencies = [ "syn 2.0.89", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "zerofrom" version = "0.1.5" diff --git a/examples/tic-tac-toe-gui/Cargo.toml b/examples/tic-tac-toe-gui/Cargo.toml index 21e41d9..5527180 100644 --- a/examples/tic-tac-toe-gui/Cargo.toml +++ b/examples/tic-tac-toe-gui/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] waku = { path = "../../waku-bindings", package = "waku-bindings" } +base64 = "0.21" serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } ark-std = "0.4" diff --git a/examples/tic-tac-toe-gui/src/main.rs b/examples/tic-tac-toe-gui/src/main.rs index 53db0ad..75f9e17 100644 --- a/examples/tic-tac-toe-gui/src/main.rs +++ b/examples/tic-tac-toe-gui/src/main.rs @@ -6,12 +6,16 @@ use std::time::Duration; use tokio::task; use tokio::sync::mpsc; +use base64::Engine; use waku::{ - waku_new, Encoding, WakuEvent, LibwakuResponse, WakuContentTopic, - WakuMessage, WakuNodeConfig, WakuNodeHandle, Initialized, Running, + Encoding, LogosDeliveryCtx, WakuContentTopic, + WakuMessage, WakuNodeConfig, general::pubsubtopic::PubsubTopic, }; +const NODE_TIMEOUT: Duration = Duration::from_secs(30); +const PUBLISH_TIMEOUT_MS: u32 = 10_000; + #[derive(Serialize, Deserialize, PartialEq, Debug, Copy, Clone)] enum Player { X, @@ -25,17 +29,17 @@ struct GameState { moves_left: usize, } -struct TicTacToeApp { +struct TicTacToeApp { game_state: Arc>, - waku: WakuNodeHandle, + waku: LogosDeliveryCtx, game_topic: PubsubTopic, tx: mpsc::Sender, // Sender to send `msg` to main thread player_role: Option, // Store the player's role (X or O) } -impl TicTacToeApp { +impl TicTacToeApp { fn new( - waku: WakuNodeHandle, + waku: LogosDeliveryCtx, game_topic: PubsubTopic, game_state: Arc>, tx: mpsc::Sender, @@ -49,56 +53,48 @@ impl TicTacToeApp { } } - async fn start(self) -> TicTacToeApp { + async fn start(self) -> TicTacToeApp { let tx_clone = self.tx.clone(); - let game_content_topic = WakuContentTopic::new("waku", "2", "tictactoegame", Encoding::Proto); + let game_content_topic = + WakuContentTopic::new("waku", "2", "tictactoegame", Encoding::Proto).to_string(); - let my_closure = move |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) => { - let message = evt.waku_message; - // Filter: only process messages for our game content topic - if message.content_topic != game_content_topic { - return; // Skip messages from other apps - } - let payload = message.payload.to_vec(); - match from_utf8(&payload) { - Ok(msg) => { - // Lock succeeded, proceed to send the message - if tx_clone.blocking_send(msg.to_string()).is_err() { - eprintln!("Failed to send message to async task"); - } - } - Err(e) => { - eprintln!("Failed to decode payload as UTF-8: {}", e); - // Handle the error as needed, or just log and skip - } - } - }, - 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"), - }; + // Establish a listener that handles the incoming messages + self.waku.add_on_received_message_listener(move |event| { + // Filter: only process messages for our game content topic + if event.waku_message.content_topic != game_content_topic { + return; // Skip messages from other apps } - }; - - // Establish a closure that handles the incoming messages - self.waku.set_event_callback(my_closure).expect("set event call back working"); + let payload = match base64::engine::general_purpose::STANDARD + .decode(&event.waku_message.payload) + { + Ok(payload) => payload, + Err(e) => { + eprintln!("Failed to decode payload as base64: {}", e); + return; + } + }; + match from_utf8(&payload) { + Ok(msg) => { + // Lock succeeded, proceed to send the message + if tx_clone.blocking_send(msg.to_string()).is_err() { + eprintln!("Failed to send message to async task"); + } + } + Err(e) => { + eprintln!("Failed to decode payload as UTF-8: {}", e); + // Handle the error as needed, or just log and skip + } + } + }); // Start the waku node - let waku = self.waku.start().await.expect("waku should start"); + self.waku.start_node_async().await.expect("waku should start"); // Subscribe to desired topic using the relay protocol - waku.relay_subscribe(&self.game_topic).await.expect("waku should subscribe"); + self.waku + .waku_relay_subscribe_async(String::from(&self.game_topic)) + .await + .expect("waku should subscribe"); // Example filter subscription. This is needed in edge nodes (resource-restricted devices) // Nodes usually use either relay or lightpush/filter protocols @@ -125,7 +121,7 @@ impl TicTacToeApp { TicTacToeApp { game_state: self.game_state, - waku, + waku: self.waku, game_topic: self.game_topic, tx: self.tx, player_role: self.player_role, @@ -133,7 +129,7 @@ impl TicTacToeApp { } } -impl TicTacToeApp { +impl TicTacToeApp { async fn send_game_state(&self, game_state: &GameState) { let serialized_game_state = serde_json::to_string(game_state).unwrap(); let content_topic = WakuContentTopic::new("waku", "2", "tictactoegame", Encoding::Proto); @@ -146,7 +142,16 @@ impl TicTacToeApp { false, ); - if let Ok(msg_hash) = self.waku.relay_publish_message(&message, &self.game_topic, None).await { + let message = serde_json::to_string(&message).expect("message should serialise"); + if let Ok(msg_hash) = self + .waku + .waku_relay_publish_async( + String::from(&self.game_topic), + message, + PUBLISH_TIMEOUT_MS, + ) + .await + { dbg!(format!("message hash published: {}", msg_hash)); } @@ -239,7 +244,7 @@ impl TicTacToeApp { } } -impl eframe::App for TicTacToeApp { +impl eframe::App for TicTacToeApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { // Request a repaint every second @@ -344,7 +349,7 @@ async fn main() -> eframe::Result<()> { let game_topic = PubsubTopic::new("/waku/2/rs/16/32"); // Create a Waku instance - let waku = waku_new(Some(WakuNodeConfig { + let config = serde_json::to_string(&WakuNodeConfig { tcp_port: Some(60010), cluster_id: Some(16), shards: vec![1, 32, 64, 128, 256], @@ -363,8 +368,12 @@ async fn main() -> eframe::Result<()> { // discv5_enr_auto_update: Some(false), ..Default::default() - })).await - .expect("should instantiate"); + }) + .expect("config should serialise"); + + let waku = LogosDeliveryCtx::new_async(config, NODE_TIMEOUT) + .await + .expect("should instantiate"); let game_state = GameState { board: [[None; 3]; 3], diff --git a/examples/toy-chat/Cargo.toml b/examples/toy-chat/Cargo.toml index abc5442..60dc208 100644 --- a/examples/toy-chat/Cargo.toml +++ b/examples/toy-chat/Cargo.toml @@ -9,6 +9,7 @@ authors = [ [dependencies] waku = { path = "../../waku-bindings", package = "waku-bindings" } +base64 = "0.21" serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } tui = "0.19" diff --git a/examples/toy-chat/src/main.rs b/examples/toy-chat/src/main.rs index e5890fd..135b1a4 100644 --- a/examples/toy-chat/src/main.rs +++ b/examples/toy-chat/src/main.rs @@ -22,9 +22,10 @@ use tui::{ Frame, Terminal, }; use unicode_width::UnicodeWidthStr; +use base64::Engine; use waku::{ - general::pubsubtopic::PubsubTopic, general::Result, waku_new, Initialized, LibwakuResponse, Running, WakuEvent, - WakuMessage, WakuNodeConfig, WakuNodeHandle, + general::pubsubtopic::PubsubTopic, general::Result, LogosDeliveryCtx, StoreQueryRequest, + StoreResponse, WakuMessage, WakuNodeConfig, }; enum InputMode { @@ -36,8 +37,12 @@ const STORE_NODE: &str = "/dns4/store-01.do-ams3.status.staging.status.im/tcp/30 const DEFAULT_PUBSUB_TOPIC: &str = "/waku/2/rs/16/32"; +const NODE_TIMEOUT: Duration = Duration::from_secs(30); +const STORE_TIMEOUT_MS: i32 = 10_000; +const PUBLISH_TIMEOUT_MS: u32 = 10_000; + /// App holds the state of the application -struct App { +struct App { /// Current value of the input box input: String, nick: String, @@ -45,13 +50,13 @@ struct App { input_mode: InputMode, /// History of recorded messages messages: Arc>>, - waku: WakuNodeHandle, + waku: LogosDeliveryCtx, } -impl App { - async fn new(nick: String) -> Result> { +impl App { + async fn new(nick: String) -> Result { let pubsub_topic = PubsubTopic::new(DEFAULT_PUBSUB_TOPIC); - let waku = waku_new(Some(WakuNodeConfig { + let config = serde_json::to_string(&WakuNodeConfig { tcp_port: Some(60010), cluster_id: Some(16), shards: vec![1, 32, 64, 128, 256], @@ -70,8 +75,11 @@ impl App { // discv5_enr_auto_update: Some(false), ..Default::default() - })).await?; - + }) + .map_err(|e| e.to_string())?; + + let waku = LogosDeliveryCtx::new_async(config, NODE_TIMEOUT).await?; + Ok(App { input: String::new(), input_mode: InputMode::Normal, @@ -81,61 +89,46 @@ impl App { }) } - async fn start_waku_node(self) -> Result> { - + async fn start_waku_node(self) -> Result { let shared_messages = Arc::clone(&self.messages); + let toy_chat_topic = TOY_CHAT_CONTENT_TOPIC.to_string(); - self.waku.set_event_callback(move|response| { - if let LibwakuResponse::Success(v) = response { - let event: WakuEvent = - serde_json::from_str(v.unwrap().as_str()).expect("failed parsing event in set_event_callback"); - - match event { - WakuEvent::WakuMessage(evt) => { - - if evt.waku_message.content_topic != TOY_CHAT_CONTENT_TOPIC { - return; // skip the messages that don't belong to the toy chat - } - - match ::decode(evt.waku_message.payload()) { - Ok(chat_message) => { - // Add the new message to the front - { - let mut messages_lock = shared_messages.write().unwrap(); - messages_lock.insert(0, chat_message); // Insert at the front (index 0) - } - } - Err(e) => { - let mut out = std::io::stderr(); - write!(out, "{e:?}").unwrap(); - } - } - }, - WakuEvent::RelayTopicHealthChange(_evt) => { - // dbg!("Relay topic change evt", evt); - }, - WakuEvent::ConnectionChange(_evt) => { - // dbg!("Conn change evt", evt); - }, - WakuEvent::Unrecognized(err) => eprintln!("Unrecognized waku event: {:?}", err), - _ => eprintln!("event case not expected"), - }; + self.waku.add_on_received_message_listener(move |event| { + if event.waku_message.content_topic != toy_chat_topic { + return; // skip the messages that don't belong to the toy chat } - })?; - let waku = self.waku.start().await?; + let payload = match base64::engine::general_purpose::STANDARD + .decode(&event.waku_message.payload) + { + Ok(payload) => payload, + Err(e) => { + let mut out = std::io::stderr(); + write!(out, "{e:?}").unwrap(); + return; + } + }; - Ok(App { - input: self.input, - nick: self.nick, - input_mode: self.input_mode, - messages: self.messages, - waku, - }) + match ::decode(payload.as_slice()) { + Ok(chat_message) => { + // Add the new message to the front + { + let mut messages_lock = shared_messages.write().unwrap(); + messages_lock.insert(0, chat_message); // Insert at the front (index 0) + } + } + Err(e) => { + let mut out = std::io::stderr(); + write!(out, "{e:?}").unwrap(); + } + } + }); + + self.waku.start_node_async().await?; + + Ok(self) } -} -impl App { async fn retrieve_history(&mut self) { let one_day_in_secs = 60 * 60 * 24; @@ -143,22 +136,29 @@ impl App { - Duration::from_secs(one_day_in_secs)) .as_nanos() as u64; - let include_data = true; + let query = StoreQueryRequest::new() + .with_include_data(true) + .with_content_topics(vec![TOY_CHAT_CONTENT_TOPIC.clone()]) + .with_time_start(Some(time_start)); + let query = serde_json::to_string(&query).expect("store query should serialise"); - let messages = self.waku.store_query(None, - vec![TOY_CHAT_CONTENT_TOPIC.clone()], - STORE_NODE, - include_data, - Some(time_start), - None, - None).await.unwrap(); + let response = self + .waku + .waku_store_query_async(query, STORE_NODE.to_string(), STORE_TIMEOUT_MS) + .await + .unwrap(); + let response: StoreResponse = + serde_json::from_str(&response).expect("could not parse store resp"); - let messages: Vec<_> = messages + let messages: Vec<_> = response + .messages .into_iter() // we expect messages because the query was passed with include_data == true .filter(|item| item.message.is_some()) .map(|store_resp_msg| { - ::decode(store_resp_msg.message.unwrap().payload()) + ::decode( + store_resp_msg.message.unwrap().payload.as_slice(), + ) .expect("Toy chat messages should be decodeable") }) .collect(); @@ -211,10 +211,12 @@ impl App { handle.block_on(async { // Assuming `self` is available in the current context let pubsub_topic = PubsubTopic::new(DEFAULT_PUBSUB_TOPIC); - if let Err(e) = self.waku.relay_publish_message( - &waku_message, - &pubsub_topic, - None, + let waku_message = serde_json::to_string(&waku_message) + .expect("message should serialise"); + if let Err(e) = self.waku.waku_relay_publish_async( + String::from(&pubsub_topic), + waku_message, + PUBLISH_TIMEOUT_MS, ).await { let mut out = std::io::stderr(); write!(out, "{e:?}").unwrap(); @@ -240,7 +242,10 @@ impl App { } async fn stop_app(self) { - self.waku.stop().await.expect("the node should stop properly"); + self.waku + .stop_node_async() + .await + .expect("the node should stop properly"); } } @@ -277,7 +282,7 @@ async fn main() -> std::result::Result<(), Box> { Ok(()) } -fn ui(f: &mut Frame, app: &App) { +fn ui(f: &mut Frame, app: &App) { let chunks = Layout::default() .direction(Direction::Vertical) .margin(2) diff --git a/waku-bindings/src/general/mod.rs b/waku-bindings/src/general/mod.rs index d078eb4..fa1843c 100644 --- a/waku-bindings/src/general/mod.rs +++ b/waku-bindings/src/general/mod.rs @@ -3,6 +3,7 @@ pub mod contenttopic; pub mod messagehash; pub mod pubsubtopic; +pub mod store; pub mod time; pub mod waku_decode; diff --git a/waku-bindings/src/general/store.rs b/waku-bindings/src/general/store.rs new file mode 100644 index 0000000..56233f6 --- /dev/null +++ b/waku-bindings/src/general/store.rs @@ -0,0 +1,136 @@ +//! Waku store protocol types. +//! +//! The query itself goes through `LogosDeliveryCtx::waku_store_query`, which +//! takes and returns JSON; these are the typed shapes on either side of it. + +// crates +use serde::{Deserialize, Serialize}; +use uuid::Uuid; +// internal +use crate::general::time::get_now_in_nanosecs; +use crate::general::{ + contenttopic::WakuContentTopic, messagehash::MessageHash, pubsubtopic::PubsubTopic, + WakuStoreRespMessage, +}; + +// #[derive(Clone, Serialize, Deserialize, Debug)] +// #[serde(rename_all = "camelCase")] +// pub struct PagingOptions { +// pub page_size: usize, +// pub cursor: Option, +// pub forward: bool, +// } + +/// Criteria used to retrieve historical messages +#[derive(Clone, Serialize, Debug)] +pub struct StoreQueryRequest { + /// if true, the store-response will include the full message content. If false, + /// the store-response will only include a list of message hashes. + #[serde(rename = "request_id")] + request_id: String, + #[serde(rename = "include_data")] + include_data: bool, + #[serde(rename = "pubsub_topic", skip_serializing_if = "Option::is_none")] + pubsub_topic: Option, + #[serde(rename = "content_topics")] + content_topics: Vec, + #[serde(rename = "time_start", skip_serializing_if = "Option::is_none")] + time_start: Option, + #[serde(rename = "time_end", skip_serializing_if = "Option::is_none")] + time_end: Option, + #[serde(rename = "message_hashes", skip_serializing_if = "Option::is_none")] + message_hashes: Option>, + #[serde(rename = "pagination_cursor", skip_serializing_if = "Option::is_none")] + pagination_cursor: Option, // Message hash (key) from where to start query (exclusive) + #[serde(rename = "pagination_forward")] + pagination_forward: bool, + #[serde(rename = "pagination_limit", skip_serializing_if = "Option::is_none")] + pagination_limit: Option, +} + +impl StoreQueryRequest { + pub fn new() -> Self { + StoreQueryRequest { + request_id: Uuid::new_v4().to_string(), + include_data: true, + pubsub_topic: None, + content_topics: Vec::new(), + time_start: Some(get_now_in_nanosecs()), + time_end: Some(get_now_in_nanosecs()), + message_hashes: None, + pagination_cursor: None, + pagination_forward: true, + pagination_limit: Some(25), + } + } + + pub fn with_include_data(mut self, include_data: bool) -> Self { + self.include_data = include_data; + self + } + + pub fn with_pubsub_topic(mut self, pubsub_topic: Option) -> Self { + self.pubsub_topic = pubsub_topic; + self + } + + pub fn with_content_topics(mut self, content_topics: Vec) -> Self { + self.content_topics = content_topics; + self + } + + pub fn with_time_start(mut self, time_start: Option) -> Self { + self.time_start = time_start; + self + } + + pub fn with_time_end(mut self, time_end: Option) -> Self { + self.time_end = time_end; + self + } + + #[allow(dead_code)] + pub fn with_message_hashes(mut self, message_hashes: Vec) -> Self { + self.message_hashes = Some(message_hashes); + self + } + + pub fn with_pagination_cursor(mut self, pagination_cursor: Option) -> Self { + self.pagination_cursor = pagination_cursor; + self + } + + pub fn with_pagination_forward(mut self, pagination_forward: bool) -> Self { + self.pagination_forward = pagination_forward; + self + } +} + +#[derive(Clone, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct StoreWakuMessageResponse { + pub message_hash: MessageHash, + pub message: Option, // None if include_data == false + pub pubsub_topic: String, +} + +#[derive(Clone, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct StoreResponse { + #[allow(unused)] + pub request_id: String, + + #[serde(skip_serializing_if = "Option::is_none")] + #[allow(unused)] + pub status_code: u32, + + #[allow(unused)] + pub status_desc: String, + + /// Array of retrieved historical messages in [`WakuMessage`] format + // #[serde(default)] + pub messages: Vec, + /// Paging information in [`PagingOptions`] format from which to resume further historical queries + #[serde(skip_serializing_if = "Option::is_none")] + pub pagination_cursor: Option, +} diff --git a/waku-bindings/src/lib.rs b/waku-bindings/src/lib.rs index 2014f0f..2eb2f39 100644 --- a/waku-bindings/src/lib.rs +++ b/waku-bindings/src/lib.rs @@ -20,6 +20,7 @@ pub use waku_sys::*; use rln; pub use general::contenttopic::{Encoding, WakuContentTopic}; +pub use general::store::{StoreQueryRequest, StoreResponse, StoreWakuMessageResponse}; pub use general::{messagehash::MessageHash, Result, WakuMessage, WakuMessageVersion}; pub use node::{ Key, Multiaddr, PublicKey, PubsubTopic, RLNConfig, SecretKey, WakuNodeConfig,