Files
logos-libp2p-module/tutorial/docs/tutorial_8_gossipsub_event_callback.md
T

230 lines
6.2 KiB
Markdown
Raw Normal View History

# 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 <chrono>
#include <condition_variable>
#include <cstdio>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#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::string>();
std::vector<std::string> addrsA;
for (const auto& a : infoA["addrs"])
addrsA.push_back(a.get<std::string>());
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>();
std::string msg = j["data"].get<std::string>();
printf("Node A callback received on topic \"%s\": \"%s\"\n",
eventTopic.c_str(), msg.c_str());
{
std::lock_guard<std::mutex> 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<std::mutex> 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
```
---
<p align="center"><a href="tutorial_7_gossipsub_polling.md">&larr; GossipSub - Polling for Messages</a> &nbsp;|&nbsp; <a href="tutorial_9_service_discovery.md">Service Discovery &rarr;</a></p>