# Tutorial 8: GossipSub - Event Callback Messages In the [previous tutorial](tutorial_7_gossipsub.md), Node B waited for messages by calling `gossipsubNextMessage()`. That polling style is simple and direct, but event-driven applications often prefer callbacks. In this tutorial we'll: - Register an `emitEvent` callback before subscribing - Subscribe two nodes to the same topic - Publish a message from one node - Receive it on the other node through the callback ## How Callback Reception Works The module emits a `"gossipsubMessage"` event when a subscribed node receives a GossipSub message. The event payload is JSON with the topic and message data. Register the callback before subscribing. The subscription path snapshots the callback used by worker threads, so registering it late can miss messages. ----------- ```cpp #include #include #include #include #include #include #include #include "plugin.h" int main() { printf("=== Tutorial 8: GossipSub - Event Callback Messages ===\n\n"); ``` ## Step 1: Create two nodes with GossipSub enabled ```cpp Libp2pModuleOptions optsA, optsB; optsA.addrs = {"/ip4/127.0.0.1/tcp/9600"}; optsA.mountGossipsub = true; optsB.addrs = {"/ip4/127.0.0.1/tcp/9601"}; optsB.mountGossipsub = true; Libp2pModuleImpl nodeA(optsA); Libp2pModuleImpl nodeB(optsB); if (!nodeA.start().success) { fprintf(stderr, "Node A failed\n"); return 1; } if (!nodeB.start().success) { fprintf(stderr, "Node B failed\n"); return 1; } printf("Both nodes started\n"); ``` ## Step 2: Connect the nodes ```cpp auto infoARes = nodeA.peerInfo(); if (!infoARes.success) { fprintf(stderr, "Failed to get Node A info: %s\n", infoARes.error.c_str()); return 1; } auto infoA = infoARes.value; std::string peerIdA = infoA["peerId"].get(); std::vector addrsA; for (const auto& a : infoA["addrs"]) addrsA.push_back(a.get()); printf("Connecting Node B to Node A...\n"); if (!nodeB.connectPeer(peerIdA, addrsA, 5000).success) { fprintf(stderr, "Failed to connect\n"); return 1; } printf("Connected\n"); ``` ## Step 3: Register Node A's event callback The callback listens for `"gossipsubMessage"` events, parses the JSON payload, stores the received message, and wakes the waiting main thread. ```cpp std::mutex eventMtx; std::condition_variable eventCv; bool messageReceived = false; std::string eventMessage; nodeA.emitEvent = [&](const std::string& name, const std::string& data) { if (name != "gossipsubMessage") return; auto j = nlohmann::json::parse(data); std::string eventTopic = j["topic"].get(); std::string msg = j["data"].get(); printf("Node A callback received on topic \"%s\": \"%s\"\n", eventTopic.c_str(), msg.c_str()); { std::lock_guard lock(eventMtx); messageReceived = true; eventMessage = msg; } eventCv.notify_one(); }; ``` ## Step 4: Both nodes subscribe to a topic ```cpp std::string topic = "chat-room-1"; printf("Node A subscribing to topic: \"%s\"\n", topic.c_str()); if (!nodeA.gossipsubSubscribe(topic).success) { fprintf(stderr, "Node A subscribe failed\n"); return 1; } printf("Node B subscribing to topic: \"%s\"\n", topic.c_str()); if (!nodeB.gossipsubSubscribe(topic).success) { fprintf(stderr, "Node B subscribe failed\n"); return 1; } ``` ## Step 5: Wait for the GossipSub mesh to form ```cpp printf("Waiting for GossipSub mesh to form...\n"); std::this_thread::sleep_for(std::chrono::milliseconds(2000)); ``` ## Step 6: Publish a message Node B publishes so that Node A receives the message through its registered callback. ```cpp std::string payload = "Hello from Node B via event callback!"; printf("\nNode B publishing: \"%s\"\n", payload.c_str()); printf(" Topic: \"%s\"\n", topic.c_str()); if (!nodeB.gossipsubPublish(topic, payload).success) { fprintf(stderr, "Publish failed\n"); return 1; } printf("Message published!\n"); ``` ## Step 7: Wait for the callback The main thread waits on a condition variable while the callback handles asynchronous delivery. ```cpp bool eventReceived = false; std::string msgCopy; { std::unique_lock lock(eventMtx); eventCv.wait_for(lock, std::chrono::seconds(3), [&] { return messageReceived; }); eventReceived = messageReceived; msgCopy = eventMessage; } if (!eventReceived) { fprintf(stderr, "Node A did not receive the event-driven message\n"); return 1; } if (msgCopy == payload) { printf("Event-driven message verified!\n"); } else { fprintf(stderr, "Event-driven message differs (expected: \"%s\", got: \"%s\")\n", payload.c_str(), msgCopy.c_str()); return 1; } ``` ## Step 8: Unsubscribe and clean up ```cpp printf("\nUnsubscribing...\n"); if (!nodeA.gossipsubUnsubscribe(topic).success) { fprintf(stderr, "Node A unsubscribe failed\n"); return 1; } if (!nodeB.gossipsubUnsubscribe(topic).success) { fprintf(stderr, "Node B unsubscribe failed\n"); return 1; } nodeA.stop(); nodeB.stop(); printf("\n=== Tutorial 8 Complete ===\n"); return 0; } ``` ## Key Takeaways - Use `emitEvent` for event-driven GossipSub message reception - Register the callback before subscribing - The `"gossipsubMessage"` event payload contains the topic and data - A condition variable is one way to bridge callback delivery back to synchronous example code - Always unsubscribe and stop cleanly ## Run tutorial ```bash ./build/tutorial/tutorial_8_gossipsub_event_callback ``` ---

← GossipSub - Polling for Messages