diff options
| author | tv <tv@krebsco.de> | 2026-11-06 05:29:17 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-06 05:29:17 +0100 |
| commit | 7f2d5be7a5cd4204fe0ec933fe4d99134607e1b3 (patch) | |
| tree | 49bc403457c64113890e4ca2c16e307d7d8adbc1 /src/swarm/members.rs | |
| parent | 6f51f8ee9624ecb15d9c9ad6656e69056e245e7b (diff) | |
working links recorded
Diffstat (limited to 'src/swarm/members.rs')
| -rw-r--r-- | src/swarm/members.rs | 78 |
1 files changed, 75 insertions, 3 deletions
diff --git a/src/swarm/members.rs b/src/swarm/members.rs index a534c04..1cb4062 100644 --- a/src/swarm/members.rs +++ b/src/swarm/members.rs @@ -12,11 +12,12 @@ //! for people reading /peers and metrics (not unique and never an identity: the public //! key stays that), where people reach its dashboard (`--dashboard-url`, `dashboard`), //! where it can be reached (`addresses`, as its transport gives them: -//! hints for the others' transports), and an `alive` stamp the node renews now and +//! hints for the others' transports), an `alive` stamp the node renews now and //! then: the members renewed within `--electorate-window` are the electorate of a -//! remake. +//! remake, and where its links differ from the member list's graph (`links`, +//! `swarm::working_links`). -use std::collections::HashMap; +use std::collections::{BTreeSet, HashMap}; use std::time::Duration; use serde_json::{Value as JsonValue, json}; @@ -426,6 +427,77 @@ pub(crate) async fn ensure_registered(state: &AppState) { } } +fn links_path(pk: &str) -> Vec<String> { + let mut path = member_path(pk); + path.push("links".to_owned()); + path +} + +/// The most members a member may record in each of `RecordedLinks`' sets: more than it +/// could hold sessions with. +pub(crate) const MAX_RECORDED_LINKS: usize = 256; + +/// Where a member's links differ from the member list's graph, as it recorded them +/// (`links`, `swarm::working_links`): its neighbours by the graph it doesn't reach +/// (`unreached`), and the members it reaches the graph doesn't link it to (`reached`). +/// Both empty while every link the graph gives works, and no other does. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) struct RecordedLinks { + pub(crate) unreached: BTreeSet<String>, + pub(crate) reached: BTreeSet<String>, +} + +impl RecordedLinks { + fn is_empty(&self) -> bool { + self.unreached.is_empty() && self.reached.is_empty() + } +} + +/// `pk`'s recorded links; none if its entry doesn't say (at most `MAX_RECORDED_LINKS` +/// in each set). +pub(crate) fn links(doc: &impl ReadDoc, pk: &str) -> RecordedLinks { + let Ok(Some(JsonValue::Object(links))) = store::read_path(doc, &links_path(pk)) else { + return RecordedLinks::default(); + }; + let set = |name: &str| -> BTreeSet<String> { + match links.get(name) { + Some(JsonValue::Array(items)) => items + .iter() + .filter_map(JsonValue::as_str) + .take(MAX_RECORDED_LINKS) + .map(str::to_owned) + .collect(), + _ => BTreeSet::new(), + } + }; + RecordedLinks { + unreached: set("unreached"), + reached: set("reached"), + } +} + +/// Records `pk`'s `links` (at most `MAX_RECORDED_LINKS` in each set); none removes them. +pub(crate) fn set_links( + doc: &mut impl Transactable, + pk: &str, + links: &RecordedLinks, +) -> Result<(), StoreError> { + if links.is_empty() { + return match store::delete_path(doc, &links_path(pk), false) { + Err(StoreError::NotFound) => Ok(()), + result => result, + }; + } + let list = |set: &BTreeSet<String>| -> Vec<String> { + set.iter().take(MAX_RECORDED_LINKS).cloned().collect() + }; + let value = json!({ + "unreached": list(&links.unreached), + "reached": list(&links.reached), + }); + store::write_path(doc, &links_path(pk), &value) +} + /// Removes `pk`'s entry (an operator forgot it). Absent is fine. pub(crate) fn unregister(doc: &mut impl Transactable, pk: &str) -> Result<(), StoreError> { match store::delete_path(doc, &member_path(pk), false) { |
