diff options
Diffstat (limited to 'src/state/mod.rs')
| -rw-r--r-- | src/state/mod.rs | 54 |
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 |
