trust model

- greedy evaluation of returned rid's, simplifying post-processing logic to simple frequency comparison per rid against a constant threshold
- tidied up link_manager request/response handling
- TODO:
  - review and decide thresholds
  - evaluate necessity and potential implementation of rc comparison
This commit is contained in:
dr7ana 2023-12-01 09:19:07 -08:00
parent 483b79aca7
commit e02ddd61d0
5 changed files with 300 additions and 196 deletions

View File

@ -2,6 +2,7 @@
#include "router_contact.hpp" #include "router_contact.hpp"
#include <llarp/crypto/crypto.hpp>
#include <llarp/util/fs.hpp> #include <llarp/util/fs.hpp>
#include <set> #include <set>
@ -12,6 +13,7 @@ namespace llarp
struct BootstrapList final : public std::set<RemoteRC> struct BootstrapList final : public std::set<RemoteRC>
{ {
size_t index; size_t index;
std::set<RemoteRC>::iterator current;
bool bool
bt_decode(std::string_view buf); bt_decode(std::string_view buf);
@ -27,16 +29,26 @@ namespace llarp
// returns a reference to the next index and a boolean that equals true if // returns a reference to the next index and a boolean that equals true if
// this is the front of the set // this is the front of the set
std::pair<const RemoteRC&, bool> const RemoteRC&
next() next()
{ {
++index %= this->size(); ++current;
return std::make_pair(*std::next(this->begin(), index), index == 0);
if (current == this->end())
current = this->begin();
return *current;
} }
bool bool
contains(const RemoteRC& rc); contains(const RemoteRC& rc);
void
randomize()
{
current = std::next(begin(), std::uniform_int_distribution<size_t>{0, size() - 1}(csrng));
}
void void
clear_list() clear_list()
{ {

View File

@ -587,7 +587,7 @@ namespace llarp
while (i < quantity) while (i < quantity)
{ {
auto& next_rc = std::next(rcs.begin(), csrng() % rc_size)->second; auto& next_rc = *std::next(rcs.begin(), csrng() % rc_size);
if (next_rc.is_expired(now)) if (next_rc.is_expired(now))
continue; continue;
@ -664,7 +664,7 @@ namespace llarp
if (since_time != decltype(since_time)::min()) if (since_time != decltype(since_time)::min())
since_time -= 5s; since_time -= 5s;
for (const auto& [_, rc] : rcs) for (const auto& rc : rcs)
{ {
if (last_time.at(rc.router_id()) > since_time or explicit_relays.count(rc.router_id())) if (last_time.at(rc.router_id()) > since_time or explicit_relays.count(rc.router_id()))
sublist.append_encoded(rc.view()); sublist.append_encoded(rc.view());

View File

@ -53,6 +53,7 @@ namespace llarp
, _next_flush_time{time_now_ms() + FLUSH_INTERVAL} , _next_flush_time{time_now_ms() + FLUSH_INTERVAL}
{ {
EnsureSkiplist(_root); EnsureSkiplist(_root);
fetch_counters.clear();
} }
void void
@ -68,7 +69,7 @@ namespace llarp
// make copy of all rcs // make copy of all rcs
std::vector<RemoteRC> copy; std::vector<RemoteRC> copy;
for (const auto& item : known_rcs) for (const auto& item : rc_lookup)
copy.push_back(item.second); copy.push_back(item.second);
// flush them to disk in one big job // flush them to disk in one big job
@ -111,75 +112,95 @@ namespace llarp
assert(_bootstraps->empty()); assert(_bootstraps->empty());
_bootstraps = std::move(from_router); _bootstraps = std::move(from_router);
_bootstraps->randomize();
} }
bool bool
NodeDB::process_fetched_rcs(RouterID source, std::vector<RemoteRC> rcs, rc_time timestamp) NodeDB::process_fetched_rcs(RouterID, std::vector<RemoteRC> rcs, rc_time timestamp)
{ {
fetch_source = source; std::unordered_set<RemoteRC> union_set;
/* // if we are not bootstrapping, we should check the rc's against the ones we currently hold
TODO: trust model analyzing returned list of RCs // if (not using_bootstrap_fallback)
*/ // {
// const auto local_count = static_cast<double>(known_rcs.size());
// const auto& recv_count = rcs.size();
// std::set_intersection(
// known_rcs.begin(),
// known_rcs.end(),
// rcs.begin(),
// rcs.end(),
// std::inserter(union_set, union_set.begin()));
// const auto union_size = static_cast<double>(union_set.size());
// }
for (auto& rc : rcs) for (auto& rc : rcs)
put_rc_if_newer(std::move(rc), timestamp); put_rc_if_newer(std::move(rc), timestamp);
last_rc_update_relay_timestamp = timestamp;
return true; return true;
} }
void void
NodeDB::ingest_rid_fetch_responses(const RouterID& source, std::unordered_set<RouterID> ids) NodeDB::ingest_rid_fetch_responses(const RouterID& source, std::unordered_set<RouterID> rids)
{ {
if (ids.empty()) if (rids.empty())
{
fail_sources.insert(source); fail_sources.insert(source);
return;
}
fetch_rid_responses[source] = std::move(ids); for (const auto& rid : rids)
fetch_counters[rid] += 1;
} }
/** We only call into this function after ensuring two conditions: /** We only call into this function after ensuring two conditions:
1) We have received at least 8 of 12 responses from the queried RouterID sources 1) We have received all 12 responses from the queried RouterID sources, whether that
2) Of those reponses, less than 4 were errors of any sorts response was a timeout or not
2) Of those responses, less than 4 were errors of any sorts
Logically, this function performs the following basic analysis of the returned RIDs. Upon receiving each response from the rid fetch sources, the returned rid's are incremented
in fetch_counters. This greatly simplifies the analysis required by this function to the
determine success or failure:
- If the frequency of each rid is above a threshold, it is accepted
- If the number of accepted rids is below a certain amount, the set is rejected
Logically, this function performs the following basic analysis of the returned RIDs:
1) All responses are coalesced into a union set with no repetitions
2) If we are bootstrapping:
- The routerID's returned
*/ */
bool bool
NodeDB::process_fetched_rids() NodeDB::process_fetched_rids()
{ {
std::unordered_set<RouterID> union_set; std::unordered_set<RouterID> union_set;
for (const auto& [rid, responses] : fetch_rid_responses) for (const auto& [rid, count] : fetch_counters)
{ {
std::merge( if (count > MIN_RID_FETCH_FREQ)
union_set.begin(), union_set.insert(rid);
union_set.end(),
responses.begin(),
responses.end(),
std::inserter(union_set, union_set.begin()));
} }
for (const auto& [rid, responses] : fetch_rid_responses) const auto num_rids = static_cast<double>(known_rids.size());
{ const auto union_size = static_cast<double>(union_set.size());
// TODO: empty == failure, handle that case
for (const auto& response : responses)
{
active_client_routers.insert(std::move(response));
}
}
return true; return (union_size / num_rids) > GOOD_RID_FETCH_THRESHOLD and union_size > MIN_RID_FETCH_TOTAL;
} }
void void
NodeDB::fetch_initial() NodeDB::fetch_initial()
{ {
// Set fetch source as random selection of known active client routers if (known_rids.empty())
fetch_source = {
*std::next(active_client_routers.begin(), csrng() % active_client_routers.size()); fallback_to_bootstrap();
}
fetch_rcs(true); else
{
// Set fetch source as random selection of known active client routers
fetch_source = *std::next(known_rids.begin(), csrng() % known_rids.size());
fetch_rcs(true);
}
} }
void void
@ -194,12 +215,12 @@ namespace llarp
return; return;
} }
is_fetching_rcs = true; // TOTHINK: do these booleans do anything? is_fetching_rcs = true; // DISCUSS: do these booleans do anything?
std::vector<RouterID> needed; std::vector<RouterID> needed;
const auto now = time_point_now(); const auto now = time_point_now();
for (const auto& [rid, rc] : known_rcs) for (const auto& [rid, rc] : rc_lookup)
{ {
if (now - rc.timestamp() > RouterContact::OUTDATED_AGE) if (now - rc.timestamp() > RouterContact::OUTDATED_AGE)
needed.push_back(rid); needed.push_back(rid);
@ -209,7 +230,7 @@ namespace llarp
_router.link_manager().fetch_rcs( _router.link_manager().fetch_rcs(
src, src,
RCFetchMessage::serialize(last_rc_update_relay_timestamp, needed), RCFetchMessage::serialize(_router.last_rc_fetch, needed),
[this, src, initial](oxen::quic::message m) mutable { [this, src, initial](oxen::quic::message m) mutable {
if (m.timed_out) if (m.timed_out)
{ {
@ -260,7 +281,9 @@ namespace llarp
} }
if (rid_sources.empty()) if (rid_sources.empty())
select_router_id_sources(); {
reselect_router_id_sources(rid_sources);
}
if (not initial and rid_sources.empty()) if (not initial and rid_sources.empty())
{ {
@ -269,7 +292,8 @@ namespace llarp
} }
is_fetching_rids = true; is_fetching_rids = true;
fetch_rid_responses.clear(); fetch_counters.clear();
// fetch_rid_responses.clear();
RouterID& src = fetch_source; RouterID& src = fetch_source;
@ -337,24 +361,31 @@ namespace llarp
{ {
if (error) if (error)
{ {
++fetch_failures; auto& fail_count = (using_bootstrap_fallback) ? bootstrap_failures : fetch_failures;
auto& THRESHOLD =
(using_bootstrap_fallback) ? MAX_BOOTSTRAP_FETCH_ATTEMPTS : MAX_FETCH_ATTEMPTS;
if (fetch_failures > MAX_FETCH_ATTEMPTS) // This catches three different failure cases;
// 1) bootstrap fetching and over failure threshold
// 2) bootstrap fetching and more failures to go
// 3) standard fetching and over threshold
if (++fail_count >= THRESHOLD || using_bootstrap_fallback)
{ {
log::info( log::info(
logcat, logcat,
"Failed {} attempts to fetch RC's from {}; reverting to bootstrap...", "RC fetching from {} reached failure threshold ({}); falling back to bootstrap...",
MAX_FETCH_ATTEMPTS, fetch_source,
fetch_source); THRESHOLD);
fallback_to_bootstrap(); fallback_to_bootstrap();
return; return;
} }
// If we have passed the last last conditional, then it means we are not bootstrapping
// and the current fetch_source has more attempts before being rotated. As a result, we
// find new non-bootstrap RC fetch source and try again buddy // find new non-bootstrap RC fetch source and try again buddy
fetch_source = (initial) fetch_source = (initial) ? *std::next(known_rids.begin(), csrng() % known_rids.size())
? *std::next(active_client_routers.begin(), csrng() % active_client_routers.size()) : std::next(rc_lookup.begin(), csrng() % rc_lookup.size())->first;
: std::next(known_rcs.begin(), csrng() % known_rcs.size())->first;
fetch_rcs(initial); fetch_rcs(initial);
} }
@ -380,11 +411,11 @@ namespace llarp
return; return;
} }
auto n_responses = fetch_rid_responses.size(); auto n_responses = RID_SOURCE_COUNT - fail_sources.size();
if (n_responses < ROUTER_ID_SOURCE_COUNT) if (n_responses < RID_SOURCE_COUNT)
{ {
log::debug(logcat, "Received {}/{} fetch RID requests", n_responses, ROUTER_ID_SOURCE_COUNT); log::debug(logcat, "Received {}/{} fetch RID requests", n_responses, RID_SOURCE_COUNT);
return; return;
} }
@ -405,7 +436,7 @@ namespace llarp
log::debug( log::debug(
logcat, "Accumulated RID's rejected by trust model, reselecting all RID sources..."); logcat, "Accumulated RID's rejected by trust model, reselecting all RID sources...");
select_router_id_sources(rid_sources); reselect_router_id_sources(rid_sources);
++fetch_failures; ++fetch_failures;
} }
else else
@ -414,7 +445,7 @@ namespace llarp
log::debug( log::debug(
logcat, "RID fetching found {} failures; reselecting failed RID sources...", n_fails); logcat, "RID fetching found {} failures; reselecting failed RID sources...", n_fails);
++fetch_failures; ++fetch_failures;
select_router_id_sources(fail_sources); reselect_router_id_sources(fail_sources);
} }
fetch_rids(true); fetch_rids(true);
@ -434,10 +465,11 @@ namespace llarp
NodeDB::post_fetch_rids(bool initial) NodeDB::post_fetch_rids(bool initial)
{ {
is_fetching_rids = false; is_fetching_rids = false;
fetch_rid_responses.clear(); // fetch_rid_responses.clear();
fail_sources.clear(); fail_sources.clear();
fetch_failures = 0; fetch_failures = 0;
_router.last_rid_fetch = llarp::time_point_now(); _router.last_rid_fetch = llarp::time_point_now();
fetch_counters.clear();
if (initial) if (initial)
_router.initial_fetch_completed(); _router.initial_fetch_completed();
@ -446,26 +478,40 @@ namespace llarp
void void
NodeDB::fallback_to_bootstrap() NodeDB::fallback_to_bootstrap()
{ {
if (bootstrap_failures >= MAX_BOOTSTRAP_FETCH_ATTEMPTS) auto at_max_failures = bootstrap_failures >= MAX_BOOTSTRAP_FETCH_ATTEMPTS;
// base case: we have failed to query a bootstrap relay 3 times, or we received a
// sample of the network, but the sample was unusable or unreachable (immediately
// counts as 3 strikes)
// We will also enter this if we are on our first fallback to bootstrap so we can
// set the fetch_source (by checking not using_bootstrap_fallback)
if (at_max_failures || not using_bootstrap_fallback)
{ {
log::info(logcat, "Current bootstrap failed... cycling to next bootstrap..."); if (at_max_failures)
log::error(logcat, "All bootstraps failed... reattempting in {}...", REBOOTSTRAP_INTERVAL);
bootstrap_failures = 0; bootstrap_failures = 0;
auto [rc, is_front] = _bootstraps->next(); auto rc = _bootstraps->next();
// Base case: if we have returned to the front of the bootstrap list, we're in a // Fail case: if we have returned to the front of the bootstrap list, we're in a
// bad spot // bad spot; we are unable to do anything
if (using_bootstrap_fallback && is_front) if (using_bootstrap_fallback)
{ {
auto err = fmt::format("ERROR: ALL BOOTSTRAPS ARE BAD"); auto err = fmt::format("ERROR: ALL BOOTSTRAPS ARE BAD");
log::error(logcat, err); log::error(logcat, err);
throw std::runtime_error{err};
/*
TODO:
trigger bootstrap cooldown timer in router
*/
} }
using_bootstrap_fallback = true;
fetch_source = rc.router_id(); fetch_source = rc.router_id();
} }
// By passing the last conditional, we ensure this is set to true
using_bootstrap_fallback = true;
_router.link_manager().fetch_bootstrap_rcs( _router.link_manager().fetch_bootstrap_rcs(
fetch_source, fetch_source,
BootstrapFetchMessage::serialize(BOOTSTRAP_SOURCE_COUNT), BootstrapFetchMessage::serialize(BOOTSTRAP_SOURCE_COUNT),
@ -473,6 +519,12 @@ namespace llarp
if (not m) if (not m)
{ {
++bootstrap_failures; ++bootstrap_failures;
log::warning(
logcat,
"BootstrapRC fetch request to {} failed (error {}/{})",
fetch_source,
bootstrap_failures,
MAX_BOOTSTRAP_FETCH_ATTEMPTS);
fallback_to_bootstrap(); fallback_to_bootstrap();
return; return;
} }
@ -495,65 +547,47 @@ namespace llarp
} }
catch (const std::exception& e) catch (const std::exception& e)
{ {
log::info(
logcat,
"Failed to parse BootstrapRC fetch response from {}: {}",
fetch_source,
e.what());
++bootstrap_failures; ++bootstrap_failures;
log::warning(
logcat,
"Failed to parse BootstrapRC fetch response from {} (error {}/{}): {}",
fetch_source,
bootstrap_failures,
MAX_BOOTSTRAP_FETCH_ATTEMPTS,
e.what());
fallback_to_bootstrap(); fallback_to_bootstrap();
return; return;
} }
rid_sources.swap(rids); // We set this to the max allowable value because if this result is bad, we won't
// if this result is bad, we won't try this bootstrap again // try this bootstrap again. If this result is undersized, we roll right into the
// next call to fallback_to_bootstrap() and hit the base case, rotating sources
bootstrap_failures = MAX_BOOTSTRAP_FETCH_ATTEMPTS; bootstrap_failures = MAX_BOOTSTRAP_FETCH_ATTEMPTS;
using_bootstrap_fallback = false;
fetch_initial(); if (rids.size() == BOOTSTRAP_SOURCE_COUNT)
{
known_rids.swap(rids);
fetch_initial();
}
else
{
++bootstrap_failures;
log::warning(
logcat,
"BootstrapRC fetch response from {} returned insufficient number of RC's (error "
"{}/{})",
fetch_source,
bootstrap_failures,
MAX_BOOTSTRAP_FETCH_ATTEMPTS);
fallback_to_bootstrap();
}
}); });
} }
void void
NodeDB::select_router_id_sources(std::unordered_set<RouterID> excluded) NodeDB::reselect_router_id_sources(std::unordered_set<RouterID> specific)
{ {
// bootstrapping should be finished before this is called, so this replace_subset(rid_sources, specific, known_rids, RID_SOURCE_COUNT, csrng);
// shouldn't happen; need to make sure that's the case.
if (active_client_routers.empty())
return;
// in case we pass the entire list
std::unordered_set<RouterID> temp = rid_sources;
// keep using any we've been using, but remove `excluded` ones
if (excluded == rid_sources)
temp.clear();
else
{
for (const auto& r : excluded)
temp.erase(r);
}
// only know so many routers, so no need to randomize
if (active_client_routers.size() <= (ROUTER_ID_SOURCE_COUNT + excluded.size()))
{
for (const auto& r : active_client_routers)
{
if (excluded.count(r))
continue;
temp.insert(r);
}
}
// select at random until we have chosen enough
while (temp.size() < ROUTER_ID_SOURCE_COUNT)
{
RouterID r;
std::sample(active_client_routers.begin(), active_client_routers.end(), &r, 1, csrng);
if (excluded.count(r) == 0)
temp.insert(r);
}
rid_sources.swap(temp);
} }
void void
@ -598,9 +632,7 @@ namespace llarp
{ {
if (_pinned_edges.size() && _pinned_edges.count(remote) == 0 if (_pinned_edges.size() && _pinned_edges.count(remote) == 0
&& not _bootstraps->contains(remote)) && not _bootstraps->contains(remote))
{
return false; return false;
}
if (not _router.is_service_node()) if (not _router.is_service_node())
return true; return true;
@ -613,6 +645,7 @@ namespace llarp
{ {
if (_pinned_edges.size() && _pinned_edges.count(remote) == 0) if (_pinned_edges.size() && _pinned_edges.count(remote) == 0)
return false; return false;
return true; return true;
} }
@ -630,6 +663,7 @@ namespace llarp
{ {
if (!ch) if (!ch)
continue; continue;
std::string p; std::string p;
p += ch; p += ch;
fs::path sub = _root / p; fs::path sub = _root / p;
@ -657,11 +691,9 @@ namespace llarp
const auto& rid = rc.router_id(); const auto& rid = rc.router_id();
known_rcs.emplace(rid, rc); auto [itr, b] = known_rcs.emplace(std::move(rc));
// TODO: the list of relays should be maintained and stored separately from rc_lookup.emplace(rid, *itr);
// the RCs, as we keep older RCs around in case we go offline and need to known_rids.insert(rid);
// bootstrap, but they shouldn't be in the "good relays" list.
active_client_routers.insert(rid);
return true; return true;
}); });
@ -683,33 +715,33 @@ namespace llarp
return; return;
_router.loop()->call([this]() { _router.loop()->call([this]() {
for (const auto& item : known_rcs) for (const auto& rc : rc_lookup)
item.second.write(get_path_by_pubkey(item.first)); {
rc.second.write(get_path_by_pubkey(rc.first));
}
}); });
} }
bool bool
NodeDB::has_rc(RouterID pk) const NodeDB::has_rc(RouterID pk) const
{ {
return known_rcs.count(pk); return rc_lookup.count(pk);
} }
std::optional<RemoteRC> std::optional<RemoteRC>
NodeDB::get_rc(RouterID pk) const NodeDB::get_rc(RouterID pk) const
{ {
const auto itr = known_rcs.find(pk); if (auto itr = rc_lookup.find(pk); itr != rc_lookup.end())
return itr->second;
if (itr == known_rcs.end()) return std::nullopt;
return std::nullopt;
return itr->second;
} }
void void
NodeDB::remove_router(RouterID pk) NodeDB::remove_router(RouterID pk)
{ {
_router.loop()->call([this, pk]() { _router.loop()->call([this, pk]() {
known_rcs.erase(pk); rc_lookup.erase(pk);
remove_many_from_disk_async({pk}); remove_many_from_disk_async({pk});
}); });
} }
@ -721,12 +753,13 @@ namespace llarp
cutoff_time -= cutoff_time -=
_router.is_service_node() ? RouterContact::OUTDATED_AGE : RouterContact::LIFETIME; _router.is_service_node() ? RouterContact::OUTDATED_AGE : RouterContact::LIFETIME;
for (auto itr = known_rcs.begin(); itr != known_rcs.end();)
for (auto itr = rc_lookup.begin(); itr != rc_lookup.end();)
{ {
if (cutoff_time > itr->second.timestamp()) if (cutoff_time > itr->second.timestamp())
{ {
log::info(logcat, "Pruning RC for {}, as it is too old to keep.", itr->first); log::info(logcat, "Pruning RC for {}, as it is too old to keep.", itr->first);
known_rcs.erase(itr); rc_lookup.erase(itr);
continue; continue;
} }
itr++; itr++;
@ -737,10 +770,16 @@ namespace llarp
NodeDB::put_rc(RemoteRC rc, rc_time now) NodeDB::put_rc(RemoteRC rc, rc_time now)
{ {
const auto& rid = rc.router_id(); const auto& rid = rc.router_id();
if (not want_rc(rid)) if (not want_rc(rid))
return false; return false;
known_rcs.erase(rid);
known_rcs.emplace(rid, std::move(rc)); rc_lookup.erase(rid);
auto [itr, b] = known_rcs.emplace(std::move(rc));
rc_lookup.emplace(rid, *itr);
known_rids.insert(rid);
last_rc_update_times[rid] = now; last_rc_update_times[rid] = now;
return true; return true;
} }
@ -754,11 +793,10 @@ namespace llarp
bool bool
NodeDB::put_rc_if_newer(RemoteRC rc, rc_time now) NodeDB::put_rc_if_newer(RemoteRC rc, rc_time now)
{ {
auto itr = known_rcs.find(rc.router_id()); if (auto itr = rc_lookup.find(rc.router_id());
if (itr == known_rcs.end() or itr->second.other_is_newer(rc)) itr == rc_lookup.end() or itr->second.other_is_newer(rc))
{
return put_rc(std::move(rc), now); return put_rc(std::move(rc), now);
}
return false; return false;
} }
@ -767,12 +805,13 @@ namespace llarp
{ {
if (_root.empty()) if (_root.empty())
return; return;
// build file list // build file list
std::set<fs::path> files; std::set<fs::path> files;
for (auto id : remove) for (auto id : remove)
{
files.emplace(get_path_by_pubkey(std::move(id))); files.emplace(get_path_by_pubkey(std::move(id)));
}
// remove them from the disk via the diskio thread // remove them from the disk via the diskio thread
_disk([files]() { _disk([files]() {
for (auto fpath : files) for (auto fpath : files)
@ -807,12 +846,14 @@ namespace llarp
std::vector<const RemoteRC*> all; std::vector<const RemoteRC*> all;
all.reserve(known_rcs.size()); all.reserve(known_rcs.size());
for (auto& entry : known_rcs)
for (auto& entry : rc_lookup)
{ {
all.push_back(&entry.second); all.push_back(&entry.second);
} }
auto it_mid = numRouters < all.size() ? all.begin() + numRouters : all.end(); auto it_mid = numRouters < all.size() ? all.begin() + numRouters : all.end();
std::partial_sort( std::partial_sort(
all.begin(), it_mid, all.end(), [compare = dht::XorMetric{location}](auto* a, auto* b) { all.begin(), it_mid, all.end(), [compare = dht::XorMetric{location}](auto* a, auto* b) {
return compare(*a, *b); return compare(*a, *b);

View File

@ -12,9 +12,9 @@
#include <algorithm> #include <algorithm>
#include <atomic> #include <atomic>
#include <map>
#include <optional> #include <optional>
#include <set> #include <set>
#include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <utility> #include <utility>
@ -22,14 +22,25 @@ namespace llarp
{ {
struct Router; struct Router;
inline constexpr size_t ROUTER_ID_SOURCE_COUNT{12}; inline constexpr size_t RID_SOURCE_COUNT{12};
inline constexpr size_t MIN_RID_FETCHES{8};
inline constexpr size_t MIN_ACTIVE_RIDS{24};
inline constexpr size_t MAX_RID_ERRORS{ROUTER_ID_SOURCE_COUNT - MIN_RID_FETCHES};
inline constexpr int MAX_FETCH_ATTEMPTS{10};
inline constexpr int MAX_BOOTSTRAP_FETCH_ATTEMPTS{3};
inline constexpr size_t BOOTSTRAP_SOURCE_COUNT{50}; inline constexpr size_t BOOTSTRAP_SOURCE_COUNT{50};
inline constexpr size_t MIN_ACTIVE_RIDS{24};
inline constexpr size_t MAX_RID_ERRORS{4};
// when fetching rids, each returned rid must appear this number of times across
inline constexpr int MIN_RID_FETCH_FREQ{6};
// when fetching rids, the total number of accepted returned rids should be above this number
inline constexpr int MIN_RID_FETCH_TOTAL{};
// when fetching rids, the ratio of accepted:rejected rids must be above this ratio
inline constexpr double GOOD_RID_FETCH_THRESHOLD{};
inline constexpr int MAX_FETCH_ATTEMPTS{10};
inline constexpr int MAX_BOOTSTRAP_FETCH_ATTEMPTS{5};
inline constexpr auto REBOOTSTRAP_INTERVAL{1min};
inline constexpr auto FLUSH_INTERVAL{5min}; inline constexpr auto FLUSH_INTERVAL{5min};
class NodeDB class NodeDB
@ -44,14 +55,18 @@ namespace llarp
/** RouterID mappings /** RouterID mappings
Both the following are populated in NodeDB startup with RouterID's stored on disk. Both the following are populated in NodeDB startup with RouterID's stored on disk.
- active_client_routers: meant to persist between lokinet sessions, and is only - known_rids: meant to persist between lokinet sessions, and is only
populated during startup and RouterID fetching. This is meant to represent the populated during startup and RouterID fetching. This is meant to represent the
client instance's perspective of the network and which RouterID's are "active" client instance's most recent perspective of the network, and record which RouterID's
were recently "active" and connected to
- known_rcs: populated during startup and when RC's are updated both during gossip - known_rcs: populated during startup and when RC's are updated both during gossip
and periodic RC fetching and periodic RC fetching
- rc_lookup: holds all the same rc's as known_rcs, but can be used to look them up by
their rid. Deleting an rid key deletes the corresponding rc in known_rcs
*/ */
std::unordered_set<RouterID> active_client_routers; std::unordered_set<RouterID> known_rids;
std::unordered_map<RouterID, RemoteRC> known_rcs; std::unordered_set<RemoteRC> known_rcs;
std::unordered_map<RouterID, const RemoteRC&> rc_lookup;
/** RouterID lists /** RouterID lists
- white: active routers - white: active routers
@ -64,10 +79,9 @@ namespace llarp
// All registered relays (service nodes) // All registered relays (service nodes)
std::unordered_set<RouterID> registered_routers; std::unordered_set<RouterID> registered_routers;
// timing // timing (note: Router holds the variables for last rc and rid request times)
std::unordered_map<RouterID, rc_time> last_rc_update_times; std::unordered_map<RouterID, rc_time> last_rc_update_times;
rc_time last_rc_update_relay_timestamp; // if populated from a config file, lists specific exclusively used as path first-hops
// only ever use to specific edges as path first-hops
std::unordered_set<RouterID> _pinned_edges; std::unordered_set<RouterID> _pinned_edges;
// source of "truth" for RC updating. This relay will also mediate requests to the // source of "truth" for RC updating. This relay will also mediate requests to the
// 12 selected active RID's for RID fetching // 12 selected active RID's for RID fetching
@ -76,8 +90,11 @@ namespace llarp
std::unordered_set<RouterID> rid_sources{}; std::unordered_set<RouterID> rid_sources{};
// logs the RID's that resulted in an error during RID fetching // logs the RID's that resulted in an error during RID fetching
std::unordered_set<RouterID> fail_sources{}; std::unordered_set<RouterID> fail_sources{};
// tracks the number of times each rid appears in the above responses
std::unordered_map<RouterID, int> fetch_counters{};
// stores all RID fetch responses for greedy comprehensive processing // stores all RID fetch responses for greedy comprehensive processing
std::unordered_map<RouterID, std::unordered_set<RouterID>> fetch_rid_responses; // std::unordered_map<RouterID, std::unordered_set<RouterID>> fetch_rid_responses;
/** Failure counters: /** Failure counters:
- fetch_failures: tracks errors fetching RC's from the RC node and requesting RID's - fetch_failures: tracks errors fetching RC's from the RC node and requesting RID's
from the 12 RID sources. Errors in the individual RID sets are NOT counted towards from the 12 RID sources. Errors in the individual RID sets are NOT counted towards
@ -142,30 +159,11 @@ namespace llarp
void void
fallback_to_bootstrap(); fallback_to_bootstrap();
// Populate rid_sources with random sample from known_rids. A set of rids is passed
// if only specific RID's need to be re-selected; to re-select all, pass the member
// variable ::known_rids
void void
select_router_id_sources(std::unordered_set<RouterID> excluded = {}); reselect_router_id_sources(std::unordered_set<RouterID> specific);
// /// If we receive a bad set of RCs from our current RC source relay, we consider
// /// that relay to be a bad source of RCs and we randomly choose a new one.
// ///
// /// When using a new RC fetch relay, we first re-fetch the full RC list and, if
// /// that aligns with our RouterID list, we go back to periodic updates from that relay.
// ///
// /// This will respect edge-pinning and attempt to use a relay we already have
// /// a connection with.
// void
// rotate_rc_source();
// /// This function is called during startup and initial fetching. When a lokinet client
// /// instance performs its initial RC/RID fetching, it may need to randomly select a
// /// node from its list of stale RC's to relay its requests. If there is a failure in
// /// mediating these request, the client will randomly select another RC source
// ///
// /// Returns:
// /// true - a new startup RC source was selected
// /// false - a new startup RC source was NOT selected
// bool
// rotate_startup_rc_source();
void void
set_router_whitelist( set_router_whitelist(
@ -236,12 +234,18 @@ namespace llarp
return registered_routers; return registered_routers;
} }
const std::unordered_map<RouterID, RemoteRC>& const std::unordered_set<RemoteRC>&
get_rcs() const get_rcs() const
{ {
return known_rcs; return known_rcs;
} }
// const std::unordered_map<RouterID, RemoteRC>&
// get_rcs() const
// {
// return known_rcs;
// }
const std::unordered_map<RouterID, rc_time>& const std::unordered_map<RouterID, rc_time>&
get_last_rc_update_times() const get_last_rc_update_times() const
{ {
@ -284,23 +288,49 @@ namespace llarp
std::optional<RemoteRC> std::optional<RemoteRC>
GetRandom(Filter visit) const GetRandom(Filter visit) const
{ {
return _router.loop()->call_get([visit]() -> std::optional<RemoteRC> { return _router.loop()->call_get([visit, this]() mutable -> std::optional<RemoteRC> {
std::vector<const decltype(known_rcs)::value_type*> known_rcs; std::vector<RemoteRC> rcs{known_rcs.begin(), known_rcs.end()};
std::shuffle(rcs.begin(), rcs.end(), llarp::csrng);
for (const auto& entry : known_rcs) for (const auto& entry : known_rcs)
known_rcs.push_back(entry);
std::shuffle(known_rcs.begin(), known_rcs.end(), llarp::csrng);
for (const auto entry : known_rcs)
{ {
if (visit(entry->second)) if (visit(entry))
return entry->second; return entry;
} }
return std::nullopt; return std::nullopt;
}); });
} }
// Updates `current` to not contain any of the elements of `replace` and resamples (up to
// `target_size`) from population to refill it.
template <typename T, typename RNG>
void
replace_subset(
std::unordered_set<T>& current,
const std::unordered_set<T>& replace,
std::unordered_set<T> population,
size_t target_size,
RNG&& rng)
{
// Remove the ones we are replacing from current:
current.erase(replace.begin(), replace.end());
// Remove ones we are replacing, and ones we already have, from the population so that we
// won't reselect them:
population.erase(replace.begin(), replace.end());
population.erase(current.begin(), current.end());
if (current.size() < target_size)
std::sample(
population.begin(),
population.end(),
std::inserter(current, current.end()),
target_size - current.size(),
rng);
}
/// visit all known_rcs /// visit all known_rcs
template <typename Visit> template <typename Visit>
void void
@ -308,7 +338,7 @@ namespace llarp
{ {
_router.loop()->call([this, visit]() { _router.loop()->call([this, visit]() {
for (const auto& item : known_rcs) for (const auto& item : known_rcs)
visit(item.second); visit(item);
}); });
} }
@ -323,17 +353,18 @@ namespace llarp
{ {
_router.loop()->call([this, visit]() { _router.loop()->call([this, visit]() {
std::unordered_set<RouterID> removed; std::unordered_set<RouterID> removed;
auto itr = known_rcs.begin();
while (itr != known_rcs.end()) for (auto itr = rc_lookup.begin(); itr != rc_lookup.end();)
{ {
if (visit(itr->second)) if (visit(itr->second))
{ {
removed.insert(itr->second.router_id()); removed.insert(itr->first);
itr = known_rcs.erase(itr); itr = rc_lookup.erase(itr);
} }
else else
++itr; ++itr;
} }
if (not removed.empty()) if (not removed.empty())
remove_many_from_disk_async(std::move(removed)); remove_many_from_disk_async(std::move(removed));
}); });

View File

@ -358,10 +358,30 @@ namespace std
template <> template <>
struct hash<llarp::RouterContact> struct hash<llarp::RouterContact>
{ {
size_t virtual size_t
operator()(const llarp::RouterContact& r) const operator()(const llarp::RouterContact& r) const
{ {
return std::hash<llarp::PubKey>{}(r.router_id()); return std::hash<llarp::PubKey>{}(r.router_id());
} }
}; };
template <>
struct hash<llarp::RemoteRC> final : public hash<llarp::RouterContact>
{
size_t
operator()(const llarp::RouterContact& r) const override
{
return std::hash<llarp::PubKey>{}(r.router_id());
}
};
template <>
struct hash<llarp::LocalRC> final : public hash<llarp::RouterContact>
{
size_t
operator()(const llarp::RouterContact& r) const override
{
return std::hash<llarp::PubKey>{}(r.router_id());
}
};
} // namespace std } // namespace std