Files
logos-libp2p-module/tutorial/tutorial_3_connecting_peers.cpp

211 lines
6.6 KiB
C++

/// # Tutorial 3: Connecting Peers and Exchanging Data
///
/// Now that we can create and configure nodes, let's make them talk to
/// each other! In this tutorial we'll:
///
/// - Create two nodes
/// - Connect one to the other
/// - Dial a protocol and open a stream
/// - Exchange data over the stream
///
/// ## How libp2p Connections Work
///
/// A libp2p connection is established in two steps:
///
/// 1. **Connect** — Establish a transport-level connection to a remote peer,
/// identified by its Peer ID and a multiaddress.
///
/// 2. **Dial** — Negotiate a protocol on top of the connection and open a
/// bidirectional stream. The stream is what you actually read from and
/// write to.
///
/// Streams in `logos-libp2p-module` are identified by a numeric `streamId`
/// that you get back from `dial()` and pass to read/write functions.
///
/// ## Stream Lifecycle
///
/// A stream must follow this lifecycle:
/// 1. `dial()` — Open a stream (returns a `streamId`)
/// 2. `streamWrite()` / `streamWriteLp()` — Send data
/// 3. `streamReadLp()` / `streamReadExactly()` — Receive data
/// 4. `streamClose()` or `streamCloseWithEOF()` — Close gracefully
/// 5. `streamRelease()` — Free server-side resources
///
/// -----------
#include <cstdio>
#include <cstdint>
#include <string>
#include <vector>
#include "plugin.h"
int main()
{
printf("=== Tutorial 3: Connecting Peers ===\n\n");
setLogLevel("fatal");
/// ## Step 1: Create and start two nodes
///
/// We create two nodes. Node A listens on port 9190, Node B on port 9191.
/// For simplicity, both mount the built-in `/ipfs/ping/1.0.0` protocol
/// (enabled by default — no extra config needed).
Libp2pModuleOptions optsA;
optsA.addrs = {"/ip4/127.0.0.1/tcp/9190"};
Libp2pModuleOptions optsB;
optsB.addrs = {"/ip4/127.0.0.1/tcp/9191"};
// Node B needs to know about node A to connect, but we'll pass
// that info after starting both nodes.
Libp2pModuleImpl nodeA(optsA);
Libp2pModuleImpl nodeB(optsB);
StdLogosResult startARes = nodeA.start();
if (!startARes.success) {
fprintf(stderr, "Node A failed to start: %s\n",
startARes.error.c_str());
return 1;
}
printf("Node A started\n");
StdLogosResult startBRes = nodeB.start();
if (!startBRes.success) {
fprintf(stderr, "Node B failed to start: %s\n",
startBRes.error.c_str());
return 1;
}
printf("Node B started\n");
/// ## Step 2: Get Node A's address info
///
/// Node B needs to know where to find Node A. We get Node A's
/// peer ID and listening addresses from `peerInfo()`.
StdLogosResult infoA = nodeA.peerInfo();
if (!infoA.success) {
fprintf(stderr, "Failed to get node A info: %s\n",
infoA.error.c_str());
return 1;
}
std::string peerIdA = infoA.value["peerId"].get<std::string>();
std::vector<std::string> addrsA;
for (const auto& a : infoA.value["addrs"]) {
addrsA.push_back(a.get<std::string>());
}
printf("Node A peer ID: %s\n", peerIdA.c_str());
printf("Node A addresses:\n");
for (const auto& a : addrsA) {
printf(" %s\n", a.c_str());
}
/// ## Step 3: Connect Node B to Node A
///
/// `connectPeer()` establishes the transport connection. The timeout
/// parameter is in milliseconds.
printf("\nConnecting Node B to Node A...\n");
StdLogosResult connectRes = nodeB.connectPeer(peerIdA, addrsA, 5000);
if (!connectRes.success) {
fprintf(stderr, "Failed to connect: %s\n",
connectRes.error.c_str());
return 1;
}
printf("Connected!\n");
/// ## Step 4: List connected peers
///
/// We can verify the connection by listing connected peers on each node.
///
/// The direction parameter:
/// - `PEER_DIRECTION_INBOUND` means "incoming connections" (other peers connected to this node).
/// - `PEER_DIRECTION_OUTBOUND` means "outgoing connections" (this node connected to other peers).
StdLogosResult peersA = nodeA.connectedPeers(PEER_DIRECTION_INBOUND);
if (!peersA.success) {
fprintf(stderr, "Failed to list Node A peers: %s\n",
peersA.error.c_str());
return 1;
}
printf("\nNode A's incoming connected peers:\n");
for (const auto& p : peersA.value) {
printf(" %s\n", p.get<std::string>().c_str());
}
StdLogosResult peersB = nodeB.connectedPeers(PEER_DIRECTION_OUTBOUND);
if (!peersB.success) {
fprintf(stderr, "Failed to list Node B peers: %s\n",
peersB.error.c_str());
return 1;
}
printf("Node B's outgoing connected peers:\n");
for (const auto& p : peersB.value) {
printf(" %s\n", p.get<std::string>().c_str());
}
/// ## Step 5: Dial the ping protocol and exchange data
///
/// Now let's use the Ping protocol — a simple built-in protocol where
/// the client sends a payload and the server echoes it back.
///
/// `dial()` opens a stream on the remote peer for a specific protocol.
/// It returns the `streamId` we use for subsequent operations.
printf("\nDialing /ipfs/ping/1.0.0 on Node A...\n");
StdLogosResult dialRes = nodeB.dial(peerIdA, "/ipfs/ping/1.0.0");
if (!dialRes.success) {
fprintf(stderr, "Dial failed: %s\n", dialRes.error.c_str());
return 1;
}
uint64_t streamId = dialRes.value.get<uint64_t>();
printf("Stream opened, id: %llu\n", (unsigned long long)streamId);
/// Write a 32-byte ping payload:
std::string payload(32, '\0');
for (int i = 0; i < 32; ++i) {
payload[i] = static_cast<char>(i);
}
printf("Sending %zu bytes...\n", payload.size());
StdLogosResult writeRes = nodeB.streamWrite(streamId, payload);
if (!writeRes.success) {
fprintf(stderr, "Write failed: %s\n", writeRes.error.c_str());
return 1;
}
/// Read the echo (32 bytes back):
StdLogosResult readRes = nodeB.streamReadExactly(streamId, 32);
if (!readRes.success) {
fprintf(stderr, "Read failed: %s\n", readRes.error.c_str());
return 1;
}
std::string reply = base64Decode(readRes.value.get<std::string>());
/// Verify the echo matches:
if (reply == payload) {
printf("Ping successful — received matching echo back!\n");
} else {
fprintf(stderr, "Ping payload mismatch\n");
return 1;
}
/// ## Step 6: Clean up the stream and nodes
///
/// Always close and release streams when done:
nodeB.streamClose(streamId);
nodeB.streamRelease(streamId);
nodeA.stop();
nodeB.stop();
printf("\n=== Tutorial 3 Complete ===\n");
return 0;
}
/// ## Run tutorial
///
/// ```bash
/// ./build/tutorial/tutorial_3_connecting_peers
/// ```