2026-06-25 15:15:54 -03:00
|
|
|
// Library-backed config tests (start a real node). Pure parsing: unit_config.cpp.
|
|
|
|
|
|
2026-05-07 08:01:35 -04:00
|
|
|
#include <logos_test.h>
|
2026-03-06 16:42:04 +00:00
|
|
|
#include <plugin.h>
|
|
|
|
|
|
2026-07-01 11:31:59 -03:00
|
|
|
#include <cstdio>
|
|
|
|
|
#include <cstdlib>
|
|
|
|
|
#include <fstream>
|
|
|
|
|
#include <unistd.h>
|
2026-06-25 15:15:54 -03:00
|
|
|
#include <string>
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-07-01 11:31:59 -03:00
|
|
|
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, LIBP2P_TRANSPORT_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_key_type_and_priv_key) {
|
|
|
|
|
auto j = nlohmann::json::parse(R"({
|
|
|
|
|
"keyType": "ed25519",
|
|
|
|
|
"privKey": "0a0bFF"
|
|
|
|
|
})");
|
|
|
|
|
|
|
|
|
|
Libp2pModuleOptions opts;
|
|
|
|
|
libp2p_module_config::apply(j, opts);
|
|
|
|
|
|
|
|
|
|
LOGOS_ASSERT_EQ(opts.keyType, LIBP2P_PK_ED25519);
|
|
|
|
|
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_key_type_defaults_to_secp256k1) {
|
|
|
|
|
Libp2pModuleOptions opts;
|
|
|
|
|
LOGOS_ASSERT_EQ(opts.keyType, LIBP2P_PK_SECP256K1);
|
|
|
|
|
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) {
|
|
|
|
|
Libp2pModuleImpl keyGen;
|
|
|
|
|
auto keyRes = keyGen.newPrivateKey();
|
|
|
|
|
LOGOS_ASSERT_TRUE(keyRes.success);
|
|
|
|
|
std::string hexKey = keyRes.value.get<std::string>();
|
|
|
|
|
auto priv = decodeHex(hexKey);
|
|
|
|
|
|
|
|
|
|
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<std::string>();
|
|
|
|
|
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<std::string>() == 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, LIBP2P_TRANSPORT_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, LIBP2P_TRANSPORT_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, LIBP2P_TRANSPORT_QUIC);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOGOS_TEST(config_load_inline_json_leading_whitespace) {
|
|
|
|
|
ScopedModuleConfig cfg(" \n\t{\"transport\": \"quic\"}");
|
|
|
|
|
|
|
|
|
|
Libp2pModuleOptions opts = Libp2pModuleOptions::load();
|
|
|
|
|
LOGOS_ASSERT_EQ(opts.transport, LIBP2P_TRANSPORT_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, LIBP2P_TRANSPORT_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, LIBP2P_TRANSPORT_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, LIBP2P_TRANSPORT_TCP);
|
|
|
|
|
LOGOS_ASSERT_FALSE(opts.autonat);
|
|
|
|
|
LOGOS_ASSERT_FALSE(opts.autonatV2);
|
|
|
|
|
LOGOS_ASSERT_FALSE(opts.autonatV2Server);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-07 08:01:35 -04:00
|
|
|
LOGOS_TEST(config_custom_listen_address) {
|
2026-05-07 14:34:51 -04:00
|
|
|
Libp2pModuleImpl plugin(Libp2pModuleOptions{ .addrs = {"/ip6/::1/tcp/0"} });
|
|
|
|
|
auto startRes = plugin.start();
|
|
|
|
|
if (!startRes.success) return; // IPv6 not available in sandbox
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-05-07 14:34:51 -04:00
|
|
|
auto res = plugin.peerInfo();
|
|
|
|
|
LOGOS_ASSERT_TRUE(res.success);
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-05-07 14:34:51 -04:00
|
|
|
auto addrs = res.value["addrs"];
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-05-07 14:34:51 -04:00
|
|
|
bool hasIp6 = false;
|
|
|
|
|
for (const auto& addr : addrs) {
|
|
|
|
|
if (addr.get<std::string>().find("/ip6/::1") != std::string::npos) {
|
|
|
|
|
hasIp6 = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-07 08:01:35 -04:00
|
|
|
LOGOS_ASSERT_TRUE(hasIp6);
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-05-07 14:34:51 -04:00
|
|
|
LOGOS_ASSERT_TRUE(plugin.stop().success);
|
2026-05-07 08:01:35 -04:00
|
|
|
}
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-05-07 08:01:35 -04:00
|
|
|
LOGOS_TEST(config_gossipsub_trigger_self_enabled) {
|
2026-05-07 14:34:51 -04:00
|
|
|
Libp2pModuleImpl plugin;
|
|
|
|
|
LOGOS_ASSERT_TRUE(plugin.start().success);
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-05-07 14:34:51 -04:00
|
|
|
std::string topic = "self-test";
|
|
|
|
|
LOGOS_ASSERT_TRUE(plugin.gossipsubSubscribe(topic).success);
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-05-07 14:34:51 -04:00
|
|
|
std::string payload = "hello";
|
|
|
|
|
LOGOS_ASSERT_TRUE(plugin.gossipsubPublish(topic, payload).success);
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-05-07 14:34:51 -04:00
|
|
|
auto res = plugin.gossipsubNextMessage(topic, 1000);
|
|
|
|
|
LOGOS_ASSERT_TRUE(res.success);
|
|
|
|
|
LOGOS_ASSERT_TRUE(res.value.get<std::string>() == payload);
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-05-07 14:34:51 -04:00
|
|
|
LOGOS_ASSERT_TRUE(plugin.stop().success);
|
2026-05-07 08:01:35 -04:00
|
|
|
}
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-05-07 08:01:35 -04:00
|
|
|
LOGOS_TEST(config_gossipsub_trigger_self_disabled) {
|
2026-05-07 14:34:51 -04:00
|
|
|
Libp2pModuleImpl plugin(Libp2pModuleOptions{ .gossipsubTriggerSelf = false });
|
|
|
|
|
LOGOS_ASSERT_TRUE(plugin.start().success);
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-05-07 14:34:51 -04:00
|
|
|
std::string topic = "self-test";
|
|
|
|
|
LOGOS_ASSERT_TRUE(plugin.gossipsubSubscribe(topic).success);
|
|
|
|
|
LOGOS_ASSERT_TRUE(plugin.gossipsubPublish(topic, "hello").success);
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-05-07 14:34:51 -04:00
|
|
|
auto res = plugin.gossipsubNextMessage(topic, 500);
|
|
|
|
|
LOGOS_ASSERT_FALSE(res.success);
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-05-07 14:34:51 -04:00
|
|
|
LOGOS_ASSERT_TRUE(plugin.stop().success);
|
2026-05-07 08:01:35 -04:00
|
|
|
}
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-05-07 08:01:35 -04:00
|
|
|
LOGOS_TEST(config_tcp_transport) {
|
2026-05-07 14:34:51 -04:00
|
|
|
Libp2pModuleImpl plugin;
|
|
|
|
|
LOGOS_ASSERT_TRUE(plugin.start().success);
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-05-07 14:34:51 -04:00
|
|
|
auto res = plugin.peerInfo();
|
|
|
|
|
LOGOS_ASSERT_TRUE(res.success);
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-05-07 14:34:51 -04:00
|
|
|
auto addrs = res.value["addrs"];
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-05-07 14:34:51 -04:00
|
|
|
bool hasTcp = false;
|
|
|
|
|
for (const auto& addr : addrs) {
|
|
|
|
|
if (addr.get<std::string>().find("/tcp/") != std::string::npos) {
|
|
|
|
|
hasTcp = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-07 08:01:35 -04:00
|
|
|
LOGOS_ASSERT_TRUE(hasTcp);
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-05-07 14:34:51 -04:00
|
|
|
LOGOS_ASSERT_TRUE(plugin.stop().success);
|
2026-05-07 08:01:35 -04:00
|
|
|
}
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-05-07 08:01:35 -04:00
|
|
|
LOGOS_TEST(config_quic_transport) {
|
2026-05-07 14:34:51 -04:00
|
|
|
Libp2pModuleImpl plugin(Libp2pModuleOptions{ .transport = LIBP2P_TRANSPORT_QUIC });
|
|
|
|
|
LOGOS_ASSERT_TRUE(plugin.start().success);
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-05-07 14:34:51 -04:00
|
|
|
auto res = plugin.peerInfo();
|
|
|
|
|
LOGOS_ASSERT_TRUE(res.success);
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-05-07 14:34:51 -04:00
|
|
|
auto addrs = res.value["addrs"];
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-05-07 14:34:51 -04:00
|
|
|
bool hasQuic = false;
|
|
|
|
|
for (const auto& addr : addrs) {
|
|
|
|
|
if (addr.get<std::string>().find("/quic-v1") != std::string::npos) {
|
|
|
|
|
hasQuic = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-07 08:01:35 -04:00
|
|
|
LOGOS_ASSERT_TRUE(hasQuic);
|
2026-03-06 16:42:04 +00:00
|
|
|
|
2026-05-07 14:34:51 -04:00
|
|
|
LOGOS_ASSERT_TRUE(plugin.stop().success);
|
2026-05-07 08:01:35 -04:00
|
|
|
}
|