mirror of
https://github.com/logos-co/logos-libp2p-module.git
synced 2026-08-31 09:51:12 +00:00
338 lines
11 KiB
C++
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);
|
|
}
|