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

179 lines
5.9 KiB
C++

/// # Tutorial 5: Kademlia DHT Basics
///
/// Kademlia is a Distributed Hash Table (DHT) that lets peers store and
/// retrieve values without a central server. The `logos-libp2p-module`
/// exposes this through the `kadPutValue()` and `kadGetValue()` functions.
///
/// In this tutorial we'll:
/// - Start two nodes that bootstrap to each other
/// - Store a value from one node
/// - Retrieve it from the other node
///
/// ## How Kademlia works in libp2p
///
/// Each node in the DHT maintains a routing table of peers closest to
/// certain "keys" (hashes). When you call `kadPutValue(key, value)`:
/// 1. The key is hashed and the node finds the closest peers in its
/// routing table
/// 2. The value is sent to those peers for storage
///
/// When you call `kadGetValue(key, quorum)`:
/// 1. The key is hashed and closest peers are looked up
/// 2. The value is requested from them
/// 3. The quorum parameter controls how many peers must return the same
/// value for the key before the lookup succeeds
///
/// ## Bootstrap nodes
///
/// New nodes need at least one bootstrap peer to join the DHT. In this
/// tutorial, Node A acts as the bootstrap for Node B.
///
/// -----------
#include <cstdio>
#include <string>
#include <vector>
#include "plugin.h"
int main()
{
printf("=== Tutorial 5: Kademlia DHT Basics ===\n\n");
setLogLevel("fatal");
/// ## Step 1: Create two nodes
///
/// We need at least two nodes to demonstrate DHT operations. In a
/// real network, one node would be a well-known bootstrap peer.
///
/// > **Important**: Kademlia is mounted by default (`mountKad: true`).
/// > We explicitly enable it in our options.
Libp2pModuleOptions optsA;
optsA.addrs = {"/ip4/127.0.0.1/tcp/9390"};
optsA.mountKad = true;
// Node A is the bootstrap — no special config needed, just its address.
Libp2pModuleImpl nodeA(optsA);
printf("Starting Node A (bootstrap)...\n");
StdLogosResult startARes = nodeA.start();
if (!startARes.success) {
fprintf(stderr, "Node A failed to start: %s\n",
startARes.error.c_str());
return 1;
}
// Get Node A's peer info for bootstrapping Node B
StdLogosResult 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("Node A peer ID: %s\n", peerIdA.c_str());
// Now create Node B with Node A as its bootstrap
Libp2pModuleOptions optsB;
optsB.addrs = {"/ip4/127.0.0.1/tcp/9391"};
optsB.mountKad = true;
optsB.bootstrapNodes = {{peerIdA, addrsA}};
Libp2pModuleImpl nodeB(optsB);
printf("Starting Node B (with Node A as bootstrap)...\n");
StdLogosResult startBRes = nodeB.start();
if (!startBRes.success) {
fprintf(stderr, "Node B failed to start: %s\n",
startBRes.error.c_str());
return 1;
}
// Connect Node B to Node A so they can participate in the DHT
printf("Connecting 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("Nodes connected\n");
/// ## Step 2: Store a value in the DHT
///
/// Node A puts a key-value pair into the DHT. The key is a string,
/// and the value is also a string.
std::string key = "greeting";
std::string value = "Hello from the DHT!";
printf("\nNode A putting value into DHT...\n");
printf(" Key: \"%s\"\n", key.c_str());
printf(" Value: \"%s\"\n", value.c_str());
StdLogosResult putRes = nodeA.kadPutValue(key, value);
if (!putRes.success) {
fprintf(stderr, "PutValue failed: %s\n", putRes.error.c_str());
return 1;
}
printf("Value stored!\n");
/// ## Step 3: Retrieve the value from Node B
///
/// Node B fetches the value from the DHT. The `quorum` parameter
/// controls the consistency level:
/// - `0` = default (usually 1 matching response is enough)
/// - `1` = wait for at least 1 peer to return the value for the key
/// - Higher values = require at least that many peers to return the same
/// value for the key, which improves consistency but can be slower
printf("\nNode B fetching value from DHT...\n");
StdLogosResult getRes = nodeB.kadGetValue(key, 1);
if (!getRes.success) {
fprintf(stderr, "GetValue failed: %s\n",
getRes.error.c_str());
return 1;
}
std::string received = base64Decode(getRes.value.get<std::string>());
if (received.empty()) {
fprintf(stderr, "Node B did not find the value\n");
return 1;
}
printf("Node B received: \"%s\"\n", received.c_str());
if (received != value) {
fprintf(stderr, "Value mismatch (expected: \"%s\", got: \"%s\")\n",
value.c_str(), received.c_str());
return 1;
}
printf("Value matches!\n");
/// > **Note**: This example uses only two local nodes to keep the tutorial
/// > easy to follow. In a real Kademlia network, the same value could be
/// > retrieved from a network of thousands of peers, including peers many
/// > hops away from the node that originally stored it.
///
/// ## Step 4: Clean up
nodeA.stop();
nodeB.stop();
printf("\n=== Tutorial 5 Complete ===\n");
return 0;
}
/// ## Key Takeaways
///
/// - Kademlia DHT is enabled by default (`mountKad: true`)
/// - Bootstrap nodes help new peers join the DHT
/// - `kadPutValue(key, value)` stores a value
/// - `kadGetValue(key, quorum)` retrieves it
/// - Values may take a moment to propagate after storing
/// ## Run tutorial
///
/// ```bash
/// ./build/tutorial/tutorial_5_kademlia_basics
/// ```