diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c2145e5e..5253b9572 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,8 @@ option(WITH_BOOTSTRAP "build lokinet-bootstrap tool" ${DEFAULT_WITH_BOOTSTRAP}) option(WITH_PEERSTATS "build with experimental peerstats db support" OFF) option(STRIP_SYMBOLS "strip off all debug symbols into an external archive for all executables built" OFF) +set(BOOTSTRAP_FALLBACK_MAINNET "${PROJECT_SOURCE_DIR}/contrib/bootstrap/mainnet.signed" CACHE PATH "Fallback bootstrap path (mainnet)") +set(BOOTSTRAP_FALLBACK_TESTNET "${PROJECT_SOURCE_DIR}/contrib/bootstrap/testnet.signed" CACHE PATH "Fallback bootstrap path (testnet)") include(cmake/enable_lto.cmake) diff --git a/contrib/bootstrap/testnet.signed b/contrib/bootstrap/testnet.signed index 9ad107115..366c5c5a0 100644 --- a/contrib/bootstrap/testnet.signed +++ b/contrib/bootstrap/testnet.signed @@ -1 +1 @@ -d1:ald1:ci2e1:d3:iwp1:e32:9ãxÚsX«l%ìû€ê<,sØ›”•©÷ïå_1:i21:::ffff:144.76.164.2021:pi1666e1:vi0eee1:i5:gamma1:k32:ÞÊðòm=o›„ZÐ1ÿßmcŒ%»¸ÿû¾™SĹ1:p32:!EÏâéz˜ý:Š‹úý… /0¡Ú„ Ãݪ„µNçB1:rli0ei0ei8ei3ee1:ui1614788310454e1:vi0e1:xle1:z64:Œ¤u G¿”D“=Œxµ¢{ïÌ51þ`í߀ùEâw m)q2Øg¯±˜øš ï³À)˜TÑP•´ò³ö—Á1e \ No newline at end of file +ld1:ald1:ci2e1:d3:iwp1:e32:9ãxÚsX«l%ìû€ê<,sØ›”•©÷ïå_1:i21:::ffff:144.76.164.2021:pi1666e1:vi0eee1:i5:gamma1:k32:ÞÊðòm=o›„ZÐ1ÿßmcŒ%»¸ÿû¾™SĹ1:p32:!EÏâéz˜ý:Š‹úý… /0¡Ú„ Ãݪ„µNçB1:rli0ei0ei8ei3ee1:ui1614788310454e1:vi0e1:xle1:z64:Œ¤u G¿”D“=Œxµ¢{ïÌ51þ`í߀ùEâw m)q2Øg¯±˜øš ï³À)˜TÑP•´ò³ö—Á1ee diff --git a/external/oxen-mq b/external/oxen-mq index 526f51ba4..ac6ef82ff 160000 --- a/external/oxen-mq +++ b/external/oxen-mq @@ -1 +1 @@ -Subproject commit 526f51ba4ef273b58a46746cbaf72f2a84371f30 +Subproject commit ac6ef82ff6fd20437b7d073466dbef82a95a2173 diff --git a/llarp/CMakeLists.txt b/llarp/CMakeLists.txt index d1d0196ef..a42c76bb1 100644 --- a/llarp/CMakeLists.txt +++ b/llarp/CMakeLists.txt @@ -235,6 +235,24 @@ add_library(lokinet-amalgum service/tag.cpp ) +set(BOOTSTRAP_FALLBACKS) +foreach(bs IN ITEMS MAINNET TESTNET) + if(BOOTSTRAP_FALLBACK_${bs}) + message(STATUS "Building with ${bs} fallback boostrap path \"${BOOTSTRAP_FALLBACK_${bs}}\"") + file(READ "${BOOTSTRAP_FALLBACK_${bs}}" bs_data HEX) + if(bs STREQUAL TESTNET) + set(network "gamma") + elseif(bs STREQUAL MAINNET) + set(network "lokinet") + else() + string(TOLOWER "${bs}" network) + endif() + string(REGEX REPLACE "([0-9a-f][0-9a-f])" "\\\\x\\1" bs_data "${bs_data}") + set(BOOTSTRAP_FALLBACKS "${BOOTSTRAP_FALLBACKS}{\"${network}\"s, \"${bs_data}\"sv},\n") + endif() +endforeach() +configure_file("bootstrap-fallbacks.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/bootstrap-fallbacks.cpp" @ONLY) +target_sources(lokinet-amalgum PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/bootstrap-fallbacks.cpp") if(WITH_PEERSTATS_BACKEND) target_compile_definitions(lokinet-amalgum PRIVATE -DLOKINET_PEERSTATS_BACKEND) diff --git a/llarp/bootstrap-fallbacks.cpp.in b/llarp/bootstrap-fallbacks.cpp.in new file mode 100644 index 000000000..5c81ea716 --- /dev/null +++ b/llarp/bootstrap-fallbacks.cpp.in @@ -0,0 +1,25 @@ +#include +#include "llarp/bootstrap.hpp" + +namespace llarp +{ + using namespace std::literals; + + std::unordered_map + load_bootstrap_fallbacks() + { + std::unordered_map fallbacks; + using init_list = std::initializer_list>; + // clang-format off + for (const auto& [network, bootstrap] : init_list{ +@BOOTSTRAP_FALLBACKS@ + }) + // clang-format on + { + llarp_buffer_t buf{bootstrap.data(), bootstrap.size()}; + auto& bsl = fallbacks[network]; + bsl.BDecode(&buf); + } + return fallbacks; + } +} // namespace llarp diff --git a/llarp/bootstrap.cpp b/llarp/bootstrap.cpp index 56731257f..7d221ab66 100644 --- a/llarp/bootstrap.cpp +++ b/llarp/bootstrap.cpp @@ -36,4 +36,35 @@ namespace llarp { return BEncodeWriteList(begin(), end(), buf); } + + void + BootstrapList::AddFromFile(fs::path fpath) + { + bool isListFile = false; + { + std::ifstream inf(fpath.c_str(), std::ios::binary); + if (inf.is_open()) + { + const char ch = inf.get(); + isListFile = ch == 'l'; + } + } + if (isListFile) + { + if (not BDecodeReadFile(fpath, *this)) + { + throw std::runtime_error{fmt::format("failed to read bootstrap list file '{}'", fpath)}; + } + } + else + { + RouterContact rc; + if (not rc.Read(fpath)) + { + throw std::runtime_error{ + fmt::format("failed to decode bootstrap RC, file='{}', rc={}", fpath, rc)}; + } + this->insert(rc); + } + } } // namespace llarp diff --git a/llarp/bootstrap.hpp b/llarp/bootstrap.hpp index e9a62d7e9..c4a4b7d3f 100644 --- a/llarp/bootstrap.hpp +++ b/llarp/bootstrap.hpp @@ -2,6 +2,8 @@ #include "router_contact.hpp" #include +#include +#include "llarp/util/fs.hpp" namespace llarp { @@ -13,7 +15,14 @@ namespace llarp bool BEncode(llarp_buffer_t* buf) const; + void + AddFromFile(fs::path fpath); + void Clear(); }; + + std::unordered_map + load_bootstrap_fallbacks(); + } // namespace llarp diff --git a/llarp/router/abstractrouter.hpp b/llarp/router/abstractrouter.hpp index 4826947a6..022e6767a 100644 --- a/llarp/router/abstractrouter.hpp +++ b/llarp/router/abstractrouter.hpp @@ -199,6 +199,15 @@ namespace llarp virtual bool IsServiceNode() const = 0; + virtual bool + IsActiveServiceNode() const = 0; + + /// If we are running as a service node and appear active, i.e. registered and not + /// decommissioned, we should *not* ping core if we know of too few peers, to indicate to core + /// we are not in a good state. + virtual bool + ShouldPingOxen() const = 0; + virtual bool StartRpcServer() = 0; diff --git a/llarp/router/router.cpp b/llarp/router/router.cpp index 440c848f6..975c6074b 100644 --- a/llarp/router/router.cpp +++ b/llarp/router/router.cpp @@ -464,6 +464,25 @@ namespace llarp return m_isServiceNode; } + bool + Router::TooFewPeers() const + { + constexpr int KnownPeerWarningThreshold = 5; + return nodedb()->NumLoaded() < KnownPeerWarningThreshold; + } + + bool + Router::IsActiveServiceNode() const + { + return IsServiceNode() and not(LooksDeregistered() or LooksDecommissioned()); + } + + bool + Router::ShouldPingOxen() const + { + return IsActiveServiceNode() and not TooFewPeers(); + } + void Router::Close() { @@ -646,52 +665,20 @@ namespace llarp // if our conf had no bootstrap files specified, try the default location of // /bootstrap.signed. If this isn't present, leave a useful error message + // TODO: use constant + fs::path defaultBootstrapFile = conf.router.m_dataDir / "bootstrap.signed"; if (configRouters.empty() and conf.bootstrap.routers.empty()) { - // TODO: use constant - fs::path defaultBootstrapFile = conf.router.m_dataDir / "bootstrap.signed"; if (fs::exists(defaultBootstrapFile)) { configRouters.push_back(defaultBootstrapFile); } - else if (not conf.bootstrap.seednode) - { - LogError("No bootstrap files specified in config file, and the default"); - LogError("bootstrap file ", defaultBootstrapFile, " does not exist."); - LogError("Please provide a bootstrap file (e.g. run 'lokinet-bootstrap)'"); - throw std::runtime_error("No bootstrap files available."); - } } BootstrapList b_list; for (const auto& router : configRouters) { - bool isListFile = false; - { - std::ifstream inf(router.c_str(), std::ios::binary); - if (inf.is_open()) - { - const char ch = inf.get(); - isListFile = ch == 'l'; - } - } - if (isListFile) - { - if (not BDecodeReadFile(router, b_list)) - { - throw std::runtime_error{fmt::format("failed to read bootstrap list file '{}'", router)}; - } - } - else - { - RouterContact rc; - if (not rc.Read(router)) - { - throw std::runtime_error{ - fmt::format("failed to decode bootstrap RC, file='{}', rc={}", router, rc)}; - } - b_list.insert(rc); - } + b_list.AddFromFile(router); } for (const auto& rc : conf.bootstrap.routers) @@ -699,25 +686,58 @@ namespace llarp b_list.emplace(rc); } - for (auto& rc : b_list) + // in case someone has an old bootstrap file and is trying to use a bootstrap + // that no longer exists + for (auto rc_itr = b_list.begin(); rc_itr != b_list.end();) { - if (not rc.Verify(Now())) + if (rc_itr->IsObsoleteBootstrap()) + b_list.erase(rc_itr); + else + rc_itr++; + } + + auto verifyRCs = [&]() { + for (auto& rc : b_list) { - LogWarn("ignoring invalid RC: ", RouterID(rc.pubkey)); - continue; + if (rc.IsObsoleteBootstrap()) + { + log::warning(logcat, "ignoring obsolete boostrap RC: {}", RouterID(rc.pubkey)); + continue; + } + if (not rc.Verify(Now())) + { + log::warning(logcat, "ignoring invalid RC: {}", RouterID(rc.pubkey)); + continue; + } + bootstrapRCList.emplace(std::move(rc)); } - bootstrapRCList.emplace(std::move(rc)); - } + }; + + verifyRCs(); if (bootstrapRCList.empty() and not conf.bootstrap.seednode) { - throw std::runtime_error{"we have no bootstrap nodes"}; + auto fallbacks = llarp::load_bootstrap_fallbacks(); + if (auto itr = fallbacks.find(_rc.netID.ToString()); itr != fallbacks.end()) + { + b_list = itr->second; + + verifyRCs(); + } + if (bootstrapRCList.empty() + and not conf.bootstrap.seednode) // empty after trying fallback, if set + { + log::error( + logcat, + "No bootstrap routers were loaded. The default bootstrap file {} does not exist, and " + "loading fallback bootstrap RCs failed.", + defaultBootstrapFile); + throw std::runtime_error("No bootstrap nodes available."); + } } if (conf.bootstrap.seednode) - { LogInfo("we are a seed node"); - } else LogInfo("Loaded ", bootstrapRCList.size(), " bootstrap routers"); @@ -1038,14 +1058,25 @@ namespace llarp connectToNum = strictConnect; } - if (auto dereg = LooksDeregistered(); (dereg or decom) and now >= m_NextDecommissionWarn) + if (now >= m_NextDecommissionWarn) { - // complain about being deregistered - constexpr auto DecommissionWarnInterval = 30s; - LogError( - "We are running as a service node but we seem to be ", - dereg ? "deregistered" : "decommissioned"); - m_NextDecommissionWarn = now + DecommissionWarnInterval; + constexpr auto DecommissionWarnInterval = 5min; + if (auto dereg = LooksDeregistered(); dereg or decom) + { + // complain about being deregistered + LogError( + "We are running as a service node but we seem to be ", + dereg ? "deregistered" : "decommissioned"); + m_NextDecommissionWarn = now + DecommissionWarnInterval; + } + else if (isSvcNode and TooFewPeers()) + { + log::error( + logcat, + "We appear to be an active service node, but have only {} known peers.", + nodedb()->NumLoaded()); + m_NextDecommissionWarn = now + DecommissionWarnInterval; + } } // if we need more sessions to routers and we are not a service node kicked from the network diff --git a/llarp/router/router.hpp b/llarp/router/router.hpp index 8f56024ea..8eb751410 100644 --- a/llarp/router/router.hpp +++ b/llarp/router/router.hpp @@ -378,6 +378,13 @@ namespace llarp bool IsServiceNode() const override; + /// return true if service node *and* not deregistered or decommissioned + bool + IsActiveServiceNode() const override; + + bool + ShouldPingOxen() const override; + void Close(); @@ -573,6 +580,9 @@ namespace llarp void MessageSent(const RouterID& remote, SendStatus status); + bool + TooFewPeers() const; + protected: virtual void HandleRouterEvent(tooling::RouterEventPtr event) const override; diff --git a/llarp/router_contact.cpp b/llarp/router_contact.cpp index 598aa1753..9004c1c34 100644 --- a/llarp/router_contact.cpp +++ b/llarp/router_contact.cpp @@ -533,6 +533,20 @@ namespace llarp return false; } + static constexpr std::array obsolete_bootstraps = { + "7a16ac0b85290bcf69b2f3b52456d7e989ac8913b4afbb980614e249a3723218"sv}; + + bool + RouterContact::IsObsoleteBootstrap() const + { + for (const auto& k : obsolete_bootstraps) + { + if (pubkey.ToHex() == k) + return true; + } + return false; + } + bool RouterContact::Write(const fs::path& fname) const { diff --git a/llarp/router_contact.hpp b/llarp/router_contact.hpp index bafade89b..340bab597 100644 --- a/llarp/router_contact.hpp +++ b/llarp/router_contact.hpp @@ -205,6 +205,9 @@ namespace llarp bool FromOurNetwork() const; + bool + IsObsoleteBootstrap() const; + private: bool DecodeVersion_0(llarp_buffer_t* buf); diff --git a/llarp/rpc/lokid_rpc_client.cpp b/llarp/rpc/lokid_rpc_client.cpp index be448c4eb..7048188fd 100644 --- a/llarp/rpc/lokid_rpc_client.cpp +++ b/llarp/rpc/lokid_rpc_client.cpp @@ -152,18 +152,25 @@ namespace llarp auto makePingRequest = [self = shared_from_this()]() { // send a ping PubKey pk{}; + bool should_ping = false; if (auto r = self->m_Router.lock()) + { pk = r->pubkey(); - nlohmann::json payload = { - {"pubkey_ed25519", oxenc::to_hex(pk.begin(), pk.end())}, - {"version", {VERSION[0], VERSION[1], VERSION[2]}}}; - self->Request( - "admin.lokinet_ping", - [](bool success, std::vector data) { - (void)data; - LogDebug("Received response for ping. Successful: ", success); - }, - payload.dump()); + should_ping = r->ShouldPingOxen(); + } + if (should_ping) + { + nlohmann::json payload = { + {"pubkey_ed25519", oxenc::to_hex(pk.begin(), pk.end())}, + {"version", {VERSION[0], VERSION[1], VERSION[2]}}}; + self->Request( + "admin.lokinet_ping", + [](bool success, std::vector data) { + (void)data; + LogDebug("Received response for ping. Successful: ", success); + }, + payload.dump()); + } // subscribe to block updates self->Request("sub.block", [](bool success, std::vector data) { if (data.empty() or not success) @@ -183,54 +190,41 @@ namespace llarp LokidRpcClient::HandleGotServiceNodeList(std::string data) { auto j = nlohmann::json::parse(std::move(data)); + if (const auto itr = j.find("unchanged"); itr != j.end() and itr->get()) { - const auto itr = j.find("unchanged"); - if (itr != j.end()) - { - if (itr->get()) - { - LogDebug("service node list unchanged"); - return; - } - } + LogDebug("service node list unchanged"); + return; } std::unordered_map keymap; std::vector activeNodeList, nonActiveNodeList; + if (const auto itr = j.find("service_node_states"); itr != j.end() and itr->is_array()) { - const auto itr = j.find("service_node_states"); - if (itr != j.end() and itr->is_array()) + for (auto& snode : *itr) { - for (auto j_itr = itr->begin(); j_itr != itr->end(); j_itr++) - { - const auto ed_itr = j_itr->find("pubkey_ed25519"); - if (ed_itr == j_itr->end() or not ed_itr->is_string()) - continue; - const auto svc_itr = j_itr->find("service_node_pubkey"); - if (svc_itr == j_itr->end() or not svc_itr->is_string()) - continue; - const auto funded_itr = j_itr->find("funded"); - if (funded_itr == j_itr->end() or not funded_itr->is_boolean()) - continue; - const auto active_itr = j_itr->find("active"); - if (active_itr == j_itr->end() or not active_itr->is_boolean()) - continue; - const bool active = active_itr->get(); - const bool funded = funded_itr->get(); - - if (not funded) - continue; - - RouterID rid; - PubKey pk; - if (rid.FromHex(ed_itr->get()) and pk.FromHex(svc_itr->get())) - { - keymap[rid] = pk; - if (active) - activeNodeList.emplace_back(std::move(rid)); - else - nonActiveNodeList.emplace_back(std::move(rid)); - } - } + // Skip unstaked snodes: + if (const auto funded_itr = snode.find("funded"); funded_itr == snode.end() + or not funded_itr->is_boolean() or not funded_itr->get()) + continue; + + const auto ed_itr = snode.find("pubkey_ed25519"); + if (ed_itr == snode.end() or not ed_itr->is_string()) + continue; + const auto svc_itr = snode.find("service_node_pubkey"); + if (svc_itr == snode.end() or not svc_itr->is_string()) + continue; + const auto active_itr = snode.find("active"); + if (active_itr == snode.end() or not active_itr->is_boolean()) + continue; + const bool active = active_itr->get(); + + RouterID rid; + PubKey pk; + if (not rid.FromHex(ed_itr->get()) + or not pk.FromHex(svc_itr->get())) + continue; + + keymap[rid] = pk; + (active ? activeNodeList : nonActiveNodeList).push_back(std::move(rid)); } } diff --git a/llarp/util/types.hpp b/llarp/util/types.hpp index be6a0a286..1944bd905 100644 --- a/llarp/util/types.hpp +++ b/llarp/util/types.hpp @@ -3,6 +3,8 @@ #include #include +#include "oxen/log/format.hpp" + using byte_t = uint8_t; using llarp_proto_version_t = std::uint8_t; @@ -10,6 +12,7 @@ namespace llarp { using Duration_t = std::chrono::milliseconds; using namespace std::literals; + using namespace oxen::log::literals; /// convert to milliseconds uint64_t diff --git a/test/net/test_llarp_net.cpp b/test/net/test_llarp_net.cpp index b65f8ce73..0c237a90b 100644 --- a/test/net/test_llarp_net.cpp +++ b/test/net/test_llarp_net.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include @@ -125,7 +125,7 @@ TEST_CASE("uint128_t") SECTION("layout") { llarp::uint128_t i{0x0011223f44556677ULL, 0x8899aabbc3ddeeffULL}; - REQUIRE(oxenmq::to_hex(std::string_view{reinterpret_cast(&i), sizeof(i)}) == + REQUIRE(oxenc::to_hex(std::string_view{reinterpret_cast(&i), sizeof(i)}) == #ifdef __BIG_ENDIAN__ "0011223f445566778899aabbc3ddeeff" #else diff --git a/test/service/test_llarp_service_name.cpp b/test/service/test_llarp_service_name.cpp index a04628f9c..3f9daef61 100644 --- a/test/service/test_llarp_service_name.cpp +++ b/test/service/test_llarp_service_name.cpp @@ -1,7 +1,7 @@ #include "catch2/catch.hpp" #include #include -#include +#include using namespace std::literals; @@ -9,7 +9,7 @@ TEST_CASE("Test LNS name decrypt", "[lns]") { llarp::sodium::CryptoLibSodium crypto; constexpr auto recordhex = "0ba76cbfdb6dc8f950da57ae781912f31c8ad0c55dbf86b88cb0391f563261a9656571a817be4092969f8a78ee0fcee260424acb4a1f4bbdd27348b71de006b6152dd04ed11bf3c4"sv; - const auto recordbin = oxenmq::from_hex(recordhex); + const auto recordbin = oxenc::from_hex(recordhex); CHECK(not recordbin.empty()); llarp::SymmNonce n{}; std::vector ciphertext{};