# 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. ----------- ```cpp #include #include #include #include "plugin.h" int main() { printf("=== Tutorial 5: Kademlia DHT Basics ===\n\n"); ``` ## 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. ```cpp 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::vector addrsA; for (const auto& a : infoA["addrs"]) addrsA.push_back(a.get()); 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. ```cpp 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 ```cpp 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()); 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 ```cpp 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 ``` ---

← Custom Protocol Handlers  |  Kademlia Provider Records →