summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/api/mod.rs7
-rw-r--r--src/config/mod.rs2
-rw-r--r--src/remake/snapshot.rs2
-rw-r--r--src/swarm/bootstrap.rs22
4 files changed, 27 insertions, 6 deletions
diff --git a/src/api/mod.rs b/src/api/mod.rs
index 1fba48b..affb9ef 100644
--- a/src/api/mod.rs
+++ b/src/api/mod.rs
@@ -120,8 +120,11 @@ fn parse_write_path(
if path.first().map(String::as_str) != Some(crate::remake::snapshot::META_ROOT) {
return Ok(path);
}
- let epochs = path.len() < 2 || path[1] == crate::remake::snapshot::EPOCHS;
- if epochs || !state.protocol_writers.contains(&user.0) {
+ // `_continuum` itself, epoch metadata and the swarm ID: nobody.
+ let fixed = path.len() < 2
+ || path[1] == crate::remake::snapshot::EPOCHS
+ || path[1] == crate::remake::snapshot::SWARM;
+ if fixed || !state.protocol_writers.contains(&user.0) {
warn!(user = %user.0, method, path = %key, "Refused a write to protocol data");
return Err(StatusCode::FORBIDDEN);
}
diff --git a/src/config/mod.rs b/src/config/mod.rs
index ad88494..73f4432 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -86,7 +86,7 @@ pub(crate) const OPTIONS: &[OptionSpec] = &[
Kind::List,
"USER,[USER...]",
None,
- "HTTP API users (from htpasswd) who may write protocol data under /store/_continuum (remake records, members, forget records), e.g. to repair a stuck remake. Such writes act on the whole swarm and are logged. Epoch metadata is never writable.",
+ "HTTP API users (from htpasswd) who may write protocol data under /store/_continuum (remake records, members, forget records), e.g. to repair a stuck remake. Such writes act on the whole swarm and are logged. Epoch metadata and the swarm ID are never writable.",
),
option(
"debug-api",
diff --git a/src/remake/snapshot.rs b/src/remake/snapshot.rs
index e50a2a2..ca50c86 100644
--- a/src/remake/snapshot.rs
+++ b/src/remake/snapshot.rs
@@ -38,6 +38,8 @@ pub(crate) const REMAKES: &str = "remakes";
/// The votes register (`records`).
pub(crate) const VOTES: &str = "votes";
pub(crate) const EPOCHS: &str = "epochs";
+/// The swarm ID, written once by the genesis node (`swarm::bootstrap`).
+pub(crate) const SWARM: &str = "swarm";
/// Everything that determines a snapshot besides the base content.
pub(crate) struct SnapshotParams<'a> {
diff --git a/src/swarm/bootstrap.rs b/src/swarm/bootstrap.rs
index 767a85c..1ae5173 100644
--- a/src/swarm/bootstrap.rs
+++ b/src/swarm/bootstrap.rs
@@ -4,15 +4,21 @@
//! seeds and keeps trying until it is ready: seeds that don't resolve are resolved
//! again with backoff, and HELLOs to resolved ones are retried by the peer worker. A
//! node without seeds waits (nothing to join yet) and says so now and then.
+//!
+//! The genesis node records the swarm ID in the document (`_continuum/swarm`), so
+//! snapshots carry it forward and every backup names its swarm.
use std::sync::atomic::Ordering;
use std::time::Duration;
-use anyhow::Result;
+use anyhow::{Context, Result};
+use serde_json::Value as JsonValue;
use tokio::time::Instant;
use tracing::{debug, info, warn};
use uuid::Uuid;
+use crate::document::store;
+use crate::remake::snapshot::{META_ROOT, SWARM};
use crate::state::AppState;
use crate::swarm::peer::{remember_peer, send_hello_once};
@@ -25,9 +31,15 @@ const RESOLVE_RETRY_MAX: Duration = Duration::from_secs(300);
pub(crate) async fn bootstrap(state: &AppState, seeds: &[String], genesis: bool) -> Result<()> {
if genesis {
- info!("Starting a new swarm as its genesis node (--genesis)");
+ let swarm_id = Uuid::new_v4().to_string();
+ info!(swarm_id = %swarm_id, "Starting a new swarm as its genesis node (--genesis)");
+ let recorded = JsonValue::String(swarm_id.clone());
+ state
+ .mutate_doc(move |doc| store::write_path(doc, &swarm_id_path(), &recorded))
+ .await
+ .context("Recording the swarm ID")?;
state.initial_sync_done.store(true, Ordering::Release);
- state.mark_ready(Uuid::new_v4().to_string()).await?;
+ state.mark_ready(swarm_id).await?;
return Ok(());
}
@@ -70,6 +82,10 @@ pub(crate) async fn bootstrap(state: &AppState, seeds: &[String], genesis: bool)
}
}
+fn swarm_id_path() -> Vec<String> {
+ vec![META_ROOT.to_owned(), SWARM.to_owned()]
+}
+
/// Resolves the seeds in `unresolved`, says HELLO to each that resolves, and keeps the
/// rest for later.
async fn resolve_seeds(state: &AppState, unresolved: &mut Vec<String>) {