aboutsummaryrefslogtreecommitdiffstats
path: root/src/state/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/mod.rs')
-rw-r--r--src/state/mod.rs102
1 files changed, 83 insertions, 19 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) {