aboutsummaryrefslogtreecommitdiffstats
path: root/src/swarm/members.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/swarm/members.rs')
-rw-r--r--src/swarm/members.rs78
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) {