// Library-backed config tests (start a real node). Pure parsing: unit_config.cpp. #include #include #include #include #include #include #include namespace { // Sets LIBP2P_MODULE_CONFIG for the test body and restores it on scope exit so // later default-constructed plugins don't pick up a stale config. struct ScopedModuleConfig { explicit ScopedModuleConfig(const std::string& value) { setenv("LIBP2P_MODULE_CONFIG", value.c_str(), 1); } ~ScopedModuleConfig() { unsetenv("LIBP2P_MODULE_CONFIG"); } }; } LOGOS_TEST(config_from_json_overlay) { auto j = nlohmann::json::parse(R"({ "addrs": ["/ip4/0.0.0.0/tcp/9000"], "bootstrapNodes": [ {"peerId": "16Uiu2bootstrap", "addrs": ["/ip4/1.2.3.4/tcp/9000", "/ip4/1.2.3.4/udp/9000/quic-v1"]} ], "transport": "quic", "maxConnections": 200, "mountServiceDiscovery": false })"); Libp2pModuleOptions opts; libp2p_module_config::apply(j, opts); LOGOS_ASSERT_EQ(opts.addrs.size(), 1u); LOGOS_ASSERT_TRUE(opts.addrs[0] == "/ip4/0.0.0.0/tcp/9000"); LOGOS_ASSERT_EQ(opts.bootstrapNodes.size(), 1u); LOGOS_ASSERT_TRUE(opts.bootstrapNodes[0].first == "16Uiu2bootstrap"); LOGOS_ASSERT_EQ(opts.bootstrapNodes[0].second.size(), 2u); LOGOS_ASSERT_EQ(opts.transport, TRANSPORT_TYPE_QUIC); LOGOS_ASSERT_EQ(opts.maxConnections, 200); LOGOS_ASSERT_FALSE(opts.mountServiceDiscovery); // Untouched keys keep their defaults. LOGOS_ASSERT_TRUE(opts.mountKad); LOGOS_ASSERT_EQ(opts.maxInConnections, 25); } LOGOS_TEST(config_from_json_priv_key) { auto j = nlohmann::json::parse(R"({ "privKey": "0a0bFF" })"); Libp2pModuleOptions opts; libp2p_module_config::apply(j, opts); LOGOS_ASSERT_EQ(opts.privKey.size(), 3u); LOGOS_ASSERT_EQ(opts.privKey[0], 0x0au); LOGOS_ASSERT_EQ(opts.privKey[1], 0x0bu); LOGOS_ASSERT_EQ(opts.privKey[2], 0xffu); } LOGOS_TEST(config_priv_key_defaults_to_empty) { Libp2pModuleOptions opts; LOGOS_ASSERT_TRUE(opts.privKey.empty()); } LOGOS_TEST(config_load_invalid_priv_key_hex_returns_defaults) { ScopedModuleConfig cfg(R"({"privKey": "zz", "maxConnections": 200})"); Libp2pModuleOptions opts = Libp2pModuleOptions::load(); LOGOS_ASSERT_TRUE(opts.privKey.empty()); LOGOS_ASSERT_EQ(opts.maxConnections, 50); } LOGOS_TEST(config_load_odd_priv_key_hex_returns_defaults) { ScopedModuleConfig cfg(R"({"privKey": "abc"})"); Libp2pModuleOptions opts = Libp2pModuleOptions::load(); LOGOS_ASSERT_TRUE(opts.privKey.empty()); } LOGOS_TEST(config_priv_key_stable_peer_identity) { // A fixed, valid secp256k1 key in libp2p's protobuf form (field 1 = scheme // 2, field 2 = 32 raw key bytes). A supplied key must yield the same peer id // across restarts. auto priv = decodeHex( "08021220" "0101010101010101010101010101010101010101010101010101010101010101"); std::string firstPeerId; { Libp2pModuleImpl plugin(Libp2pModuleOptions{ .privKey = priv }); LOGOS_ASSERT_TRUE(plugin.start().success); auto info = plugin.peerInfo(); LOGOS_ASSERT_TRUE(info.success); firstPeerId = info.value["peerId"].get(); LOGOS_ASSERT_TRUE(plugin.stop().success); } Libp2pModuleImpl plugin(Libp2pModuleOptions{ .privKey = priv }); LOGOS_ASSERT_TRUE(plugin.start().success); auto info = plugin.peerInfo(); LOGOS_ASSERT_TRUE(info.success); LOGOS_ASSERT_TRUE(info.value["peerId"].get() == firstPeerId); LOGOS_ASSERT_TRUE(plugin.stop().success); } LOGOS_TEST(config_from_json_partial_keeps_defaults) { auto j = nlohmann::json::parse(R"({"maxConnsPerPeer": 4})"); Libp2pModuleOptions opts; libp2p_module_config::apply(j, opts); LOGOS_ASSERT_EQ(opts.maxConnsPerPeer, 4); LOGOS_ASSERT_TRUE(opts.addrs.empty()); LOGOS_ASSERT_TRUE(opts.bootstrapNodes.empty()); LOGOS_ASSERT_EQ(opts.transport, TRANSPORT_TYPE_TCP); } LOGOS_TEST(config_load_unset_returns_defaults) { unsetenv("LIBP2P_MODULE_CONFIG"); Libp2pModuleOptions opts = Libp2pModuleOptions::load(); LOGOS_ASSERT_TRUE(opts.addrs.empty()); LOGOS_ASSERT_TRUE(opts.bootstrapNodes.empty()); LOGOS_ASSERT_EQ(opts.transport, TRANSPORT_TYPE_TCP); } LOGOS_TEST(config_load_inline_json) { ScopedModuleConfig cfg(R"({"addrs": ["/ip4/0.0.0.0/tcp/7000"], "transport": "quic"})"); Libp2pModuleOptions opts = Libp2pModuleOptions::load(); LOGOS_ASSERT_EQ(opts.addrs.size(), 1u); LOGOS_ASSERT_TRUE(opts.addrs[0] == "/ip4/0.0.0.0/tcp/7000"); LOGOS_ASSERT_EQ(opts.transport, TRANSPORT_TYPE_QUIC); } LOGOS_TEST(config_load_inline_json_leading_whitespace) { ScopedModuleConfig cfg(" \n\t{\"transport\": \"quic\"}"); Libp2pModuleOptions opts = Libp2pModuleOptions::load(); LOGOS_ASSERT_EQ(opts.transport, TRANSPORT_TYPE_QUIC); } LOGOS_TEST(config_load_from_file) { char path[] = "/tmp/libp2p_module_test_config.XXXXXX"; int fd = mkstemp(path); LOGOS_ASSERT_TRUE(fd != -1); close(fd); { std::ofstream f(path); f << R"({"bootstrapNodes": [{"peerId": "16Uiu2file", "addrs": ["/ip4/9.9.9.9/tcp/9000"]}]})"; } ScopedModuleConfig cfg(path); Libp2pModuleOptions opts = Libp2pModuleOptions::load(); LOGOS_ASSERT_EQ(opts.bootstrapNodes.size(), 1u); LOGOS_ASSERT_TRUE(opts.bootstrapNodes[0].first == "16Uiu2file"); LOGOS_ASSERT_TRUE(opts.bootstrapNodes[0].second[0] == "/ip4/9.9.9.9/tcp/9000"); std::remove(path); } LOGOS_TEST(config_load_invalid_json_returns_defaults) { ScopedModuleConfig cfg("{not valid json"); Libp2pModuleOptions opts = Libp2pModuleOptions::load(); LOGOS_ASSERT_TRUE(opts.addrs.empty()); LOGOS_ASSERT_TRUE(opts.bootstrapNodes.empty()); LOGOS_ASSERT_EQ(opts.transport, TRANSPORT_TYPE_TCP); } // Valid JSON but a wrong-typed field must not throw out of construction. LOGOS_TEST(config_load_wrong_field_type_returns_defaults) { ScopedModuleConfig cfg(R"({"maxConnections": "not-a-number"})"); Libp2pModuleOptions opts = Libp2pModuleOptions::load(); LOGOS_ASSERT_EQ(opts.maxConnections, 50); LOGOS_ASSERT_TRUE(opts.addrs.empty()); } LOGOS_TEST(config_options_defaults) { Libp2pModuleOptions opts; LOGOS_ASSERT_TRUE(opts.addrs.empty()); LOGOS_ASSERT_TRUE(opts.bootstrapNodes.empty()); LOGOS_ASSERT_EQ(opts.transport, TRANSPORT_TYPE_TCP); LOGOS_ASSERT_FALSE(opts.autonat); LOGOS_ASSERT_FALSE(opts.autonatV2); LOGOS_ASSERT_FALSE(opts.autonatV2Server); LOGOS_ASSERT_FALSE(opts.circuitRelay); LOGOS_ASSERT_EQ(opts.maxConnections, 50); LOGOS_ASSERT_EQ(opts.maxInConnections, 25); LOGOS_ASSERT_EQ(opts.maxOutConnections, 25); LOGOS_ASSERT_EQ(opts.maxConnsPerPeer, 1); LOGOS_ASSERT_TRUE(opts.gossipsubTriggerSelf); } LOGOS_TEST(config_options_designated_init) { Libp2pModuleOptions opts{ .circuitRelay = true }; LOGOS_ASSERT_TRUE(opts.circuitRelay); LOGOS_ASSERT_TRUE(opts.addrs.empty()); LOGOS_ASSERT_TRUE(opts.bootstrapNodes.empty()); LOGOS_ASSERT_EQ(opts.transport, TRANSPORT_TYPE_TCP); LOGOS_ASSERT_FALSE(opts.autonat); LOGOS_ASSERT_FALSE(opts.autonatV2); LOGOS_ASSERT_FALSE(opts.autonatV2Server); } LOGOS_TEST(config_custom_listen_address) { Libp2pModuleImpl plugin(Libp2pModuleOptions{ .addrs = {"/ip6/::1/tcp/0"} }); auto startRes = plugin.start(); if (!startRes.success) return; // IPv6 not available in sandbox auto res = plugin.peerInfo(); LOGOS_ASSERT_TRUE(res.success); auto addrs = res.value["addrs"]; bool hasIp6 = false; for (const auto& addr : addrs) { if (addr.get().find("/ip6/::1") != std::string::npos) { hasIp6 = true; break; } } LOGOS_ASSERT_TRUE(hasIp6); LOGOS_ASSERT_TRUE(plugin.stop().success); } LOGOS_TEST(config_gossipsub_trigger_self_enabled) { Libp2pModuleImpl plugin; LOGOS_ASSERT_TRUE(plugin.start().success); std::string topic = "self-test"; LOGOS_ASSERT_TRUE(plugin.gossipsubSubscribe(topic).success); std::string payload = "hello"; LOGOS_ASSERT_TRUE(plugin.gossipsubPublish(topic, payload).success); auto res = plugin.gossipsubNextMessage(topic, 1000); LOGOS_ASSERT_TRUE(res.success); LOGOS_ASSERT_TRUE(res.value.get() == payload); LOGOS_ASSERT_TRUE(plugin.stop().success); } LOGOS_TEST(config_gossipsub_trigger_self_disabled) { Libp2pModuleImpl plugin(Libp2pModuleOptions{ .gossipsubTriggerSelf = false }); LOGOS_ASSERT_TRUE(plugin.start().success); std::string topic = "self-test"; LOGOS_ASSERT_TRUE(plugin.gossipsubSubscribe(topic).success); LOGOS_ASSERT_TRUE(plugin.gossipsubPublish(topic, "hello").success); auto res = plugin.gossipsubNextMessage(topic, 500); LOGOS_ASSERT_FALSE(res.success); LOGOS_ASSERT_TRUE(plugin.stop().success); } LOGOS_TEST(config_gossipsub_ingress_limits_accepted) { Libp2pModuleImpl plugin(Libp2pModuleOptions{ .gossipsubMaxMessageSize = 1024 * 1024, .gossipsubOverheadRateLimitBytes = 64 * 1024, .gossipsubOverheadRateLimitIntervalMs = 1000 }); LOGOS_ASSERT_TRUE(plugin.start().success); std::string topic = "limits-test"; LOGOS_ASSERT_TRUE(plugin.gossipsubSubscribe(topic).success); LOGOS_ASSERT_TRUE(plugin.gossipsubPublish(topic, "hello").success); auto res = plugin.gossipsubNextMessage(topic, 1000); LOGOS_ASSERT_TRUE(res.success); LOGOS_ASSERT_TRUE(plugin.stop().success); } // nim-libp2p fails closed: a limit that nothing applies stops node creation. LOGOS_TEST(config_gossipsub_limit_without_mount_fails) { Libp2pModuleImpl plugin(Libp2pModuleOptions{ .mountGossipsub = false, .gossipsubMaxMessageSize = 1024 * 1024 }); auto res = plugin.start(); LOGOS_ASSERT_FALSE(res.success); LOGOS_ASSERT_FALSE(plugin.ok()); } // A budget without its interval never refills, so the pair goes together. LOGOS_TEST(config_gossipsub_rate_limit_without_interval_fails) { Libp2pModuleImpl plugin( Libp2pModuleOptions{ .gossipsubOverheadRateLimitBytes = 64 * 1024 }); LOGOS_ASSERT_FALSE(plugin.start().success); LOGOS_ASSERT_FALSE(plugin.ok()); } LOGOS_TEST(config_tcp_transport) { Libp2pModuleImpl plugin; LOGOS_ASSERT_TRUE(plugin.start().success); auto res = plugin.peerInfo(); LOGOS_ASSERT_TRUE(res.success); auto addrs = res.value["addrs"]; bool hasTcp = false; for (const auto& addr : addrs) { if (addr.get().find("/tcp/") != std::string::npos) { hasTcp = true; break; } } LOGOS_ASSERT_TRUE(hasTcp); LOGOS_ASSERT_TRUE(plugin.stop().success); } LOGOS_TEST(config_quic_transport) { Libp2pModuleImpl plugin(Libp2pModuleOptions{ .transport = TRANSPORT_TYPE_QUIC }); LOGOS_ASSERT_TRUE(plugin.start().success); auto res = plugin.peerInfo(); LOGOS_ASSERT_TRUE(res.success); auto addrs = res.value["addrs"]; bool hasQuic = false; for (const auto& addr : addrs) { if (addr.get().find("/quic-v1") != std::string::npos) { hasQuic = true; break; } } LOGOS_ASSERT_TRUE(hasQuic); LOGOS_ASSERT_TRUE(plugin.stop().success); }