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.rs54
1 files changed, 51 insertions, 3 deletions
diff --git a/src/state/mod.rs b/src/state/mod.rs
index 5a36faa..87b7fe7 100644
--- a/src/state/mod.rs
+++ b/src/state/mod.rs
@@ -236,6 +236,41 @@ pub(crate) struct CatchUpSource {
pub(crate) refusals: u32,
}
+/// The change just made in `replica` (the one head after `before`), judged by the
+/// swarm's rules as every node will judge it on receipt (`access::rules`), under a policy
+/// judging every change: a write the API let through and the rules refuse would be
+/// refused everywhere, with everything built on it.
+fn judged_here(
+ replica: &crate::document::replica::Replica,
+ before: &[crate::document::ChangeHash],
+) -> Result<(), String> {
+ if !crate::access::policy::of(&replica.doc).enforced() {
+ return Ok(());
+ }
+ let after = replica.doc.get_heads();
+ let [hash] = after[..] else {
+ return Ok(());
+ };
+ if after == before {
+ return Ok(());
+ }
+ match crate::access::rules::judge_held(&replica.doc, replica.epoch, &hash) {
+ Some(judged) => judged,
+ None => Ok(()),
+ }
+}
+
+/// The document back to `before`, the change just made dropped: it never left this node
+/// (the write lock is held since), so its actor goes on with the same sequence.
+fn roll_back(
+ replica: &mut crate::document::replica::Replica,
+ before: &[crate::document::ChangeHash],
+) -> Result<(), StoreError> {
+ let actor = replica.doc.get_actor().clone();
+ replica.doc = replica.doc.fork_at(before)?.with_actor(actor);
+ Ok(())
+}
+
impl AppState {
/// Asks the sync worker to look at `peer` (`SyncEvent::PeerNeedsSync`): whether to
/// send it a sync message now. Nothing if the worker is gone (the node stops).
@@ -589,9 +624,22 @@ impl AppState {
};
let before = replica.doc.get_heads();
let result = replica.transact(state.clock.now_millis(), &writer, f);
- if result.is_ok() {
- state.sign_made(&mut replica, &before, &sign);
- }
+ let result = match result {
+ // Judged as every node will judge it, before it leaves; refused: as
+ // if never written.
+ Ok(out) => match judged_here(&replica, &before) {
+ Ok(()) => {
+ state.sign_made(&mut replica, &before, &sign);
+ Ok(out)
+ }
+ Err(why) => {
+ roll_back(&mut replica, &before)?;
+ tracing::debug!(reason = %why, "Refused a write every node would refuse");
+ Err(E::from(StoreError::Refused(why)))
+ }
+ },
+ Err(error) => Err(error),
+ };
replica.refresh_heads();
result.map(|out| (out, replica.heads().to_vec()))
}; // lock released before notifying