Files

338 lines
11 KiB
C++

// Library-backed config tests (start a real node). Pure parsing: unit_config.cpp.
#include <logos_test.h>
#include <plugin.h>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <unistd.h>
#include <string>
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<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, 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<std::string>().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<std::string>() == 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<std::string>().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<std::string>().find("/quic-v1") != std::string::npos) {
hasQuic = true;
break;
}
}
LOGOS_ASSERT_TRUE(hasQuic);
LOGOS_ASSERT_TRUE(plugin.stop().success);
}