/// # Tutorial 9: Service Discovery /// /// In decentralized networks, peers join and leave dynamically. How do you /// find a peer that offers a specific service? That's what **Service /// Discovery** (Disco) is for. /// /// Service Discovery lets peers: /// - **Advertise** the services they offer /// - **Discover** peers offering services they're interested in /// - **Register interest** to be notified when new providers appear /// - **Query randomly** to discover the network /// /// ## Architecture /// /// Service Discovery uses a **bootstrap node** as a rendezvous point. /// All peers connect to the bootstrap and register their services there. /// Looking up a service queries the bootstrap, which returns matching peers. /// /// ``` /// Bootstrap /// / \ /// Advertiser Discoverer /// ``` /// /// ----------- #include #include #include #include #include #include "plugin.h" int main() { printf("=== Tutorial 9: Service Discovery ===\n\n"); /// ## Step 1: Create a bootstrap node /// /// The bootstrap is a well-known node that all peers connect to. /// It relays service advertisements and lookup queries. Libp2pModuleOptions optsBootstrap; optsBootstrap.addrs = {"/ip4/127.0.0.1/tcp/9690"}; optsBootstrap.mountServiceDiscovery = true; Libp2pModuleImpl bootstrap(optsBootstrap); if (!bootstrap.start().success) { fprintf(stderr, "Bootstrap failed\n"); return 1; } if (!bootstrap.discoStart().success) { fprintf(stderr, "Bootstrap discoStart failed\n"); return 1; } auto bootstrapInfoRes = bootstrap.peerInfo(); if (!bootstrapInfoRes.success) { fprintf(stderr, "Failed to get bootstrap info: %s\n", bootstrapInfoRes.error.c_str()); return 1; } std::string bootstrapId = bootstrapInfoRes.value["peerId"].get(); std::vector bootstrapAddrs; for (const auto& a : bootstrapInfoRes.value["addrs"]) { bootstrapAddrs.push_back(a.get()); } printf("Bootstrap node started: %s\n", bootstrapId.c_str()); /// ## Step 2: Create an advertiser node /// /// This node will advertise a service that others can discover. Libp2pModuleOptions optsAdvertiser; optsAdvertiser.addrs = {"/ip4/127.0.0.1/tcp/9691"}; optsAdvertiser.bootstrapNodes = {{bootstrapId, bootstrapAddrs}}; optsAdvertiser.mountServiceDiscovery = true; Libp2pModuleImpl advertiser(optsAdvertiser); if (!advertiser.start().success) { fprintf(stderr, "Advertiser failed\n"); return 1; } if (!advertiser.discoStart().success) { fprintf(stderr, "Advertiser discoStart failed\n"); return 1; } // Connect to bootstrap if (!advertiser.connectPeer(bootstrapId, bootstrapAddrs, 5000).success) { fprintf(stderr, "Advertiser failed to connect to bootstrap\n"); return 1; } printf("Advertiser connected to bootstrap\n"); /// ## Step 3: Create a discoverer node Libp2pModuleOptions optsDiscoverer; optsDiscoverer.addrs = {"/ip4/127.0.0.1/tcp/9692"}; optsDiscoverer.bootstrapNodes = {{bootstrapId, bootstrapAddrs}}; optsDiscoverer.mountServiceDiscovery = true; Libp2pModuleImpl discoverer(optsDiscoverer); if (!discoverer.start().success) { fprintf(stderr, "Discoverer failed\n"); return 1; } if (!discoverer.discoStart().success) { fprintf(stderr, "Discoverer discoStart failed\n"); return 1; } if (!discoverer.connectPeer(bootstrapId, bootstrapAddrs, 5000).success) { fprintf(stderr, "Discoverer failed to connect to bootstrap\n"); return 1; } printf("Discoverer connected to bootstrap\n"); /// ## Step 4: The advertiser starts advertising a service /// /// Services are identified by a service ID (string) and can carry /// arbitrary data (also a string). std::string serviceId = "demo-chat-service"; std::string serviceData = "version=1.0;capacity=100"; printf("\nAdvertiser advertising: \"%s\"\n", serviceId.c_str()); if (!advertiser.discoStartAdvertising(serviceId, serviceData).success) { fprintf(stderr, "discoStartAdvertising failed\n"); return 1; } printf("Advertising started\n"); /// ## Step 5: The discoverer registers interest /// /// Registering interest tells the service discovery system that /// this peer wants to know about providers of this service. printf("Discoverer registering interest in \"%s\"\n", serviceId.c_str()); if (!discoverer.discoRegisterInterest(serviceId).success) { fprintf(stderr, "discoRegisterInterest failed\n"); return 1; } /// ## Step 6: Discoverer looks up the service /// /// The lookup queries the bootstrap for peers advertising this /// service. It may take a moment for the advertisement to propagate. printf("Discoverer looking up \"%s\"...\n", serviceId.c_str()); constexpr int kLookupAttempts = 10; constexpr int kLookupDelayMs = 500; StdLogosResult lookupRes; for (int i = 0; i < kLookupAttempts; ++i) { lookupRes = discoverer.discoLookup(serviceId, serviceData); if (!lookupRes.success) { fprintf(stderr, "Service lookup failed: %s\n", lookupRes.error.c_str()); return 1; } if (!lookupRes.value.empty()) { break; } std::this_thread::sleep_for(std::chrono::milliseconds(kLookupDelayMs)); } if (lookupRes.value.empty()) { fprintf(stderr, "Service lookup did not find any providers\n"); return 1; } auto records = lookupRes.value; printf("Discoverer found %zu provider(s):\n", records.size()); for (const auto& rec : records) { printf(" Peer: %s\n", rec["peerId"].get().c_str()); for (const auto& s : rec["services"]) { std::string decodedData = base64Decode(s["data"].get()); printf(" Service: %s (data: %s)\n", s["id"].get().c_str(), decodedData.c_str()); } } /// ## Step 7: Random lookup /// /// You can also discover random peers via `discoRandomLookup()`, /// which is useful for network exploration. printf("\nRandom lookup by advertiser...\n"); auto randomRes = advertiser.discoRandomLookup(); if (!randomRes.success) { fprintf(stderr, "Random lookup failed: %s\n", randomRes.error.c_str()); return 1; } if (!randomRes.value.is_array()) { fprintf(stderr, "Random lookup returned a non-array value\n"); return 1; } printf("Found %zu random peer(s):\n", randomRes.value.size()); for (const auto& rec : randomRes.value) { printf(" Peer: %s\n", rec["peerId"].get().c_str()); } /// ## Step 8: Creating and decoding Extended Peer Records (XPR) /// /// Extended Peer Records are signed records that bundle a peer's /// addresses and services. They can be shared offline or via /// side channels. printf("\n--- Extended Peer Records ---\n"); std::vector> xprServices = { {serviceId, serviceData}, {"file-sharing", "v2"}, }; auto xpr = advertiser.createXpr({}, xprServices, 0); if (!xpr.success) { fprintf(stderr, "Failed to create XPR: %s\n", xpr.error.c_str()); return 1; } if (!xpr.value.is_string()) { fprintf(stderr, "createXpr returned a non-string value\n"); return 1; } std::string xprStr = xpr.value.get(); printf("Created signed XPR\n"); // Decode it to verify auto decoded = advertiser.decodeXpr(xprStr); if (!decoded.success) { fprintf(stderr, "Failed to decode XPR: %s\n", decoded.error.c_str()); return 1; } if (!decoded.value.is_object()) { fprintf(stderr, "decodeXpr returned a non-object value\n"); return 1; } printf("Decoded XPR:\n"); printf(" Peer ID: %s\n", decoded.value["peerId"].get().c_str()); printf(" Sequence: %llu\n", (unsigned long long)decoded.value["seqNo"] .get()); printf(" Services: %zu\n", decoded.value["services"].size()); /// ## Step 9: Clean up — unregister, stop advertising, stop disco printf("\nCleaning up...\n"); discoverer.discoUnregisterInterest(serviceId); advertiser.discoStopAdvertising(serviceId); discoverer.discoStop(); advertiser.discoStop(); bootstrap.discoStop(); discoverer.stop(); advertiser.stop(); bootstrap.stop(); printf("\n=== Tutorial 9 Complete ===\n"); return 0; } /// ## Key Takeaways /// /// - Service Discovery requires a bootstrap node as rendezvous point /// - `discoStart()` / `discoStop()` control the discovery service /// - `discoStartAdvertising()` / `discoStopAdvertising()` manage ads /// - `discoRegisterInterest()` / `discoUnregisterInterest()` manage subscriptions /// - `discoLookup()` finds peers offering a service /// - `discoRandomLookup()` discovers random peers /// - `createXpr()` / `decodeXpr()` work with signed peer records /// ## Run tutorial /// /// ```bash /// ./build/tutorial/tutorial_9_service_discovery /// ```