aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/state/mod.rs102
-rw-r--r--src/swarm/members.rs49
2 files changed, 114 insertions, 37 deletions
diff --git a/src/state/mod.rs b/src/state/mod.rs
index 294b446..7ee0acb 100644
--- a/src/state/mod.rs
+++ b/src/state/mod.rs
@@ -474,6 +474,21 @@ impl AppState {
.map(|(out, _)| out)
}
+ /// [`mutate_doc`](Self::mutate_doc) of this node's registration in the member list
+ /// (`swarm::members::ensure_registered`): the one write not preceded by it.
+ pub(crate) async fn mutate_doc_registering<T, E>(
+ &self,
+ f: impl FnOnce(&mut Transaction<'_>) -> Result<T, E> + Send + 'static,
+ ) -> Result<T, E>
+ where
+ T: Send + 'static,
+ E: From<StoreError> + Send + 'static,
+ {
+ self.mutate_doc_as(None, Sign::Protocol, false, f)
+ .await
+ .map(|(out, _)| out)
+ }
+
/// [`mutate_doc`](Self::mutate_doc) for `user`, who wrote through the API: the
/// change names them (`replica::Writer`), and is signed as `sign` says, with this
/// node's key. With the heads of the document written to after the
@@ -488,6 +503,22 @@ impl AppState {
T: Send + 'static,
E: From<StoreError> + Send + 'static,
{
+ self.mutate_doc_as(user, sign, true, f).await
+ }
+
+ /// [`mutate_doc_for`](Self::mutate_doc_for); with `registering_first`, after this
+ /// node's registration if the policy needs it first.
+ async fn mutate_doc_as<T, E>(
+ &self,
+ user: Option<String>,
+ sign: Sign,
+ registering_first: bool,
+ f: impl FnOnce(&mut Transaction<'_>) -> Result<T, E> + Send + 'static,
+ ) -> Result<(T, Vec<crate::document::ChangeHash>), E>
+ where
+ T: Send + 'static,
+ E: From<StoreError> + Send + 'static,
+ {
let state = self.clone();
tokio::task::spawn_blocking(move || {
@@ -525,6 +556,29 @@ impl AppState {
heads.sort_unstable();
return Ok((out, heads));
}
+ // Under a policy judging every change, a change by a node that isn't a
+ // member is refused everywhere, and so is every change built on it: a
+ // write before this node registered (`ensure_registered`), through the API
+ // or its own, comes after its registration.
+ if registering_first
+ && crate::access::policy::of(&replica.doc).enforced()
+ && !crate::swarm::members::registered(&replica.doc, state.local_id())
+ {
+ let path = crate::swarm::members::member_path(state.local_id());
+ let entry = crate::swarm::members::entry(&state);
+ let writer = crate::document::replica::Writer {
+ node: state.local_id(),
+ user: None,
+ };
+ let before = replica.doc.get_heads();
+ replica
+ .transact(state.clock.now_millis(), &writer, |doc| {
+ crate::document::store::write_path(doc, &path, &entry)
+ })
+ .map_err(E::from)?;
+ state.sign_made(&mut replica, &before, &Sign::Protocol);
+ tracing::info!("Registered in the swarm's member list, before writing");
+ }
// One API write = one change; on error, its partial writes (e.g. the old
// value deleted, the new one not written) are dropped.
let writer = crate::document::replica::Writer {
@@ -533,25 +587,8 @@ impl AppState {
};
let before = replica.doc.get_heads();
let result = replica.transact(state.clock.now_millis(), &writer, f);
- if result.is_ok()
- && let Some(key) = &state.node_key
- {
- // The change made, if any: the one head after it, as it builds on
- // every head before.
- let after = replica.doc.get_heads();
- if after != before
- && let [hash] = after[..]
- {
- if state.sign_every == SignEvery::Change
- || sign.wanted(&replica.doc)
- || crate::access::policy::of(&replica.doc).required()
- {
- let replica = &mut *replica;
- replica.signatures.sign(&replica.doc, hash, key);
- } else {
- replica.signatures.left_unsigned(hash);
- }
- }
+ if result.is_ok() {
+ state.sign_made(&mut replica, &before, &sign);
}
replica.refresh_heads();
result.map(|out| (out, replica.heads().to_vec()))
@@ -566,6 +603,33 @@ impl AppState {
.expect("document write task panicked")
}
+ /// Signs the change just made in `replica`, if any (the one head after `before`, as
+ /// it builds on every head before), as `sign` says, or as `--sign-every` or the
+ /// policy has it; or keeps it as left unsigned.
+ fn sign_made(
+ &self,
+ replica: &mut crate::document::replica::Replica,
+ before: &[crate::document::ChangeHash],
+ sign: &Sign,
+ ) {
+ let Some(key) = &self.node_key else {
+ return;
+ };
+ let after = replica.doc.get_heads();
+ if after != before
+ && let [hash] = after[..]
+ {
+ if self.sign_every == SignEvery::Change
+ || sign.wanted(&replica.doc)
+ || crate::access::policy::of(&replica.doc).required()
+ {
+ replica.signatures.sign(&replica.doc, hash, key);
+ } else {
+ replica.signatures.left_unsigned(hash);
+ }
+ }
+ }
+
/// This node's latest change signed, if no signature vouches for it yet, every
/// `every` (`--sign-every`): every change it makes is vouched for within that.
pub(crate) async fn sign_latest_every(self, every: Duration) {
diff --git a/src/swarm/members.rs b/src/swarm/members.rs
index cbd970d..0cadb00 100644
--- a/src/swarm/members.rs
+++ b/src/swarm/members.rs
@@ -293,6 +293,34 @@ const RENEWAL_MOST_OFTEN: Duration = Duration::from_secs(3600);
pub(crate) const MIN_ELECTORATE_WINDOW: Duration =
Duration::from_secs(7 * RENEWAL_MOST_OFTEN.as_secs());
+/// Whether `node` is registered in `doc`'s member list.
+pub(crate) fn registered(doc: &impl ReadDoc, node: &str) -> bool {
+ store::read_path(doc, &member_path(node))
+ .ok()
+ .flatten()
+ .is_some()
+}
+
+/// This node's entry in the member list, as it registers (`ensure_registered`).
+pub(crate) fn entry(state: &AppState) -> JsonValue {
+ let now = state.clock.now_millis().as_u64();
+ let mut addresses = state.transport.local_addresses();
+ addresses.truncate(MAX_ADDRESSES);
+ let mut entry = json!({
+ "since": now,
+ "alive": now,
+ "addresses": addresses,
+ "window": state.electorate_window.as_millis() as u64,
+ });
+ if let Some(alias) = &state.alias {
+ entry["alias"] = json!(alias);
+ }
+ if let Some(url) = &state.dashboard_url {
+ entry["dashboard"] = json!(url);
+ }
+ entry
+}
+
/// Registers this node if it is ready and its entry is missing, keeps its alias and its
/// dashboard's URL as configured (set, changed or removed after a restart with another
/// `--alias` or `--dashboard-url`) and its
@@ -315,12 +343,8 @@ pub(crate) async fn ensure_registered(state: &AppState) {
shortest,
) = {
let replica = state.doc.read().await;
- let registered = store::read_path(&replica.doc, &own)
- .ok()
- .flatten()
- .is_some();
(
- registered,
+ registered(&replica.doc, &local),
alias(&replica.doc, &local),
dashboard(&replica.doc, &local),
addresses(&replica.doc, &local),
@@ -335,20 +359,9 @@ pub(crate) async fn ensure_registered(state: &AppState) {
let now = state.clock.now_millis().as_u64();
if !registered {
- let mut entry = json!({
- "since": now,
- "alive": now,
- "addresses": own_addresses,
- "window": window,
- });
- if let Some(alias) = &wanted {
- entry["alias"] = json!(alias);
- }
- if let Some(url) = &state.dashboard_url {
- entry["dashboard"] = json!(url);
- }
+ let entry = entry(state);
match state
- .mutate_doc(move |doc| store::write_path(doc, &own, &entry))
+ .mutate_doc_registering(move |doc| store::write_path(doc, &own, &entry))
.await
{
Ok(()) => info!(