aboutsummaryrefslogtreecommitdiffstats
path: root/src/remake/snapshot.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/remake/snapshot.rs')
-rw-r--r--src/remake/snapshot.rs94
1 files changed, 49 insertions, 45 deletions
diff --git a/src/remake/snapshot.rs b/src/remake/snapshot.rs
index c818af6..157a28d 100644
--- a/src/remake/snapshot.rs
+++ b/src/remake/snapshot.rs
@@ -20,7 +20,8 @@
use std::collections::BTreeMap;
-use automerge::{ActorId, AutoCommit, ChangeHash, transaction::CommitOptions};
+use crate::document::{ActorId, ChangeHash, Document, ReadDoc};
+use automerge::transaction::CommitOptions;
use serde_json::{Map as JsonMap, Value as JsonValue, json};
use sha2::{Digest, Sha256};
@@ -55,7 +56,7 @@ pub(crate) struct Snapshot {
/// The new epoch: next generation, ID = hash of the snapshot change.
pub(crate) epoch: Epoch,
/// A document consisting of exactly that one change.
- pub(crate) doc: AutoCommit,
+ pub(crate) doc: Document,
/// Highest sequence number per actor (hex) contained in the base. A change
/// (actor, seq) is part of the snapshot iff seq <= clock[actor]. Read by tests only:
/// nodes read the clock from the epoch metadata (`EpochMetadata::clock`).
@@ -116,11 +117,11 @@ impl SnapshotRequest {
/// No lock is held while building; the caller only needs a lock for the clone.
/// Returns the copy as well, for callers that continue working with it.
pub(crate) async fn build_detached(
- mut copy: AutoCommit,
+ copy: Document,
request: SnapshotRequest,
-) -> Result<(Snapshot, AutoCommit), SnapshotError> {
+) -> Result<(Snapshot, Document), SnapshotError> {
tokio::task::spawn_blocking(move || {
- let snapshot = build(&mut copy, &request.params())?;
+ let snapshot = build(&copy, &request.params())?;
Ok((snapshot, copy))
})
.await
@@ -173,7 +174,7 @@ struct MetadataRecord {
/// carry the metadata of all earlier epochs forward (only remake records stay
/// behind), so a node many epochs behind can still find the epoch that followed its own.
pub(crate) fn read_epoch_metadata(
- doc: &AutoCommit,
+ doc: &Document,
generation: u64,
) -> Result<EpochMetadata, String> {
let value = store::read_path(doc, &epoch_metadata_path(generation))
@@ -209,7 +210,7 @@ pub(crate) fn read_epoch_metadata(
///
/// This does not prove the snapshot's content matches the old epoch; the hash commits
/// to it, and the peer is a swarm member trusted as much as for ordinary sync.
-pub(crate) fn verify_document(doc: &mut AutoCommit, epoch: Epoch) -> Result<EpochMetadata, String> {
+pub(crate) fn verify_document(doc: &Document, epoch: Epoch) -> Result<EpochMetadata, String> {
let root = ChangeHash(epoch.id);
let Some(change) = doc.get_change_by_hash(&root) else {
return Err(format!(
@@ -227,7 +228,7 @@ pub(crate) fn verify_document(doc: &mut AutoCommit, epoch: Epoch) -> Result<Epoc
/// The epoch `doc` is on: genesis if it holds no epoch metadata; otherwise the newest
/// generation it has metadata for, whose snapshot change is the document's only change
/// without dependencies (verified). For a document loaded from a backup.
-pub(crate) fn epoch_of(doc: &mut AutoCommit) -> Result<Epoch, String> {
+pub(crate) fn epoch_of(doc: &Document) -> Result<Epoch, String> {
let generation = store::keys_at(doc, &meta::path(&[EPOCHS]))
.map_err(|error| error.to_string())?
.iter()
@@ -263,7 +264,7 @@ pub(crate) fn epoch_metadata_path(generation: u64) -> Vec<String> {
/// Builds the snapshot of `source` at `params.base_heads`.
pub(crate) fn build(
- source: &mut AutoCommit,
+ source: &Document,
params: &SnapshotParams<'_>,
) -> Result<Snapshot, SnapshotError> {
// fork_at with unknown heads would quietly build something else.
@@ -273,21 +274,24 @@ pub(crate) fn build(
}
}
- let mut base = source.fork_at(params.base_heads)?;
- let clock = clock_of(&mut base);
+ let base = source.fork_at(params.base_heads)?;
+ let clock = clock_of(&base);
let generation = params.previous.generation + 1;
let kind =
by_name(params.kind).ok_or_else(|| SnapshotError::UnknownKind(params.kind.to_owned()))?;
- let mut snapshot = AutoCommit::new().with_actor(snapshot_actor(params.remake_id, generation));
+ let mut snapshot = Document::new().with_actor(snapshot_actor(params.remake_id, generation));
+
+ // One transaction, so the snapshot is a single change.
+ let mut target = snapshot.transaction();
// The user content is the kind's.
- kind.build(&base, params.content, &mut snapshot)?;
+ kind.build(&base, params.content, &mut target)?;
// Protocol data carries forward, whatever the kind: forget records, earlier epochs'
// metadata (rejoining nodes need it). Remake records and the votes register describe
// the old epoch's protocol and stay behind.
- match store::delete_path(&mut snapshot, &meta::path(&[]), false) {
+ match store::delete_path(&mut target, &meta::path(&[]), false) {
Ok(()) | Err(StoreError::NotFound) => {}
Err(error) => return Err(error.into()),
}
@@ -297,7 +301,7 @@ pub(crate) fn build(
.into_iter()
.filter(|key| key != REMAKES && key != VOTES)
{
- store::copy_path(&base, &mut snapshot, &meta::path(&[key.as_str()]))?;
+ store::copy_path(&base, &mut target, &meta::path(&[key.as_str()]))?;
}
let mut base_heads: Vec<String> = params.base_heads.iter().map(|h| h.to_string()).collect();
@@ -317,9 +321,9 @@ pub(crate) fn build(
"kind": kind.name(),
"translation": params.translation,
}));
- store::write_path(&mut snapshot, &epoch_metadata_path(generation), &metadata)?;
+ store::write_path(&mut target, &epoch_metadata_path(generation), &metadata)?;
- let _ = snapshot.commit_with(CommitOptions::default().with_time(0));
+ target.commit_with(CommitOptions::default().with_time(0));
let heads = snapshot.get_heads();
let [id] = heads.as_slice() else {
@@ -337,7 +341,7 @@ pub(crate) fn build(
}
/// Highest sequence number per actor in `doc`'s history.
-fn clock_of(doc: &mut AutoCommit) -> BTreeMap<String, u64> {
+fn clock_of(doc: &Document) -> BTreeMap<String, u64> {
let mut clock = BTreeMap::new();
for change in doc.get_changes(&[]) {
let seq = clock.entry(change.actor_id().to_string()).or_insert(0);
@@ -379,8 +383,8 @@ mod tests {
use crate::document::testing::change;
use automerge::{ObjType, ROOT, ReadDoc, ScalarValue, Value, transaction::Transactable};
- fn doc(actor: &[u8]) -> AutoCommit {
- AutoCommit::new().with_actor(ActorId::from(actor))
+ fn doc(actor: &[u8]) -> Document {
+ Document::new().with_actor(ActorId::from(actor))
}
fn params<'a>(heads: &'a [ChangeHash], policy: &'a JsonValue) -> SnapshotParams<'a> {
@@ -398,14 +402,14 @@ mod tests {
static NULL: JsonValue = JsonValue::Null;
- fn snapshot_of(source: &mut AutoCommit) -> Snapshot {
+ fn snapshot_of(source: &Document) -> Snapshot {
let heads = source.get_heads();
let policy = json!({});
build(source, &params(&heads, &policy)).expect("snapshot")
}
/// The user-visible view, without our own metadata.
- fn user_view(doc: &AutoCommit) -> JsonValue {
+ fn user_view(doc: &Document) -> JsonValue {
let mut view = store::read_path(doc, &[]).unwrap().unwrap();
view.as_object_mut().unwrap().remove(meta::ROOT);
view
@@ -443,8 +447,8 @@ mod tests {
assert_eq!(user_view(&a), user_view(&b));
- let snap_a = snapshot_of(&mut a);
- let snap_b = snapshot_of(&mut b);
+ let snap_a = snapshot_of(&a);
+ let snap_b = snapshot_of(&b);
// Different histories mean different clocks, and the clock is part of the
// metadata. Equal views alone therefore give equal content but not equal IDs;
@@ -468,9 +472,9 @@ mod tests {
let mut b = doc(b"actor-b");
b.merge(&mut a.clone()).unwrap();
- let first = snapshot_of(&mut a);
- let again = snapshot_of(&mut a);
- let elsewhere = snapshot_of(&mut b);
+ let first = snapshot_of(&a);
+ let again = snapshot_of(&a);
+ let elsewhere = snapshot_of(&b);
assert_eq!(first.epoch, again.epoch);
assert_eq!(first.epoch, elsewhere.epoch);
@@ -498,7 +502,7 @@ mod tests {
"test setup should produce a conflict"
);
- let snap = snapshot_of(&mut a);
+ let snap = snapshot_of(&a);
assert_eq!(user_view(&snap.doc), json!({ "cfg": { "x": 1, "y": 2 } }));
let (_, conflicts) = store::read_path_with_conflicts(&snap.doc, &[])
@@ -529,7 +533,7 @@ mod tests {
change(&mut a, |a| a.put(ROOT, "late", true).unwrap());
let policy = json!({});
- let snap = build(&mut a, &params(&base, &policy)).unwrap();
+ let snap = build(&a, &params(&base, &policy)).unwrap();
let view = user_view(&snap.doc);
assert!(view.get("late").is_none());
@@ -548,11 +552,11 @@ mod tests {
change(&mut a, |a| {
store::write_path(a, &["k".into()], &json!(1)).unwrap()
});
- assert_eq!(epoch_of(&mut a), Ok(Epoch::GENESIS));
+ assert_eq!(epoch_of(&a), Ok(Epoch::GENESIS));
let heads = a.get_heads();
let policy = json!({});
- let mut snap = build(&mut a, &params(&heads, &policy)).unwrap();
+ let snap = build(&a, &params(&heads, &policy)).unwrap();
// Saved and loaded, with a write since, as a backup would be.
let mut later = snap
.doc
@@ -561,8 +565,8 @@ mod tests {
change(&mut later, |later| {
store::write_path(later, &["k".into()], &json!(2)).unwrap()
});
- let mut backup = AutoCommit::load(&later.save()).unwrap();
- assert_eq!(epoch_of(&mut backup), Ok(snap.epoch));
+ let backup = Document::load(&later.save()).unwrap();
+ assert_eq!(epoch_of(&backup), Ok(snap.epoch));
}
#[test]
@@ -591,7 +595,7 @@ mod tests {
let heads = a.get_heads();
let policy = json!({ "replayWindow": { "epochs": 1 } });
- let snap = build(&mut a, &params(&heads, &policy)).unwrap();
+ let snap = build(&a, &params(&heads, &policy)).unwrap();
let meta = store::read_path(&snap.doc, &meta::path(&[]))
.unwrap()
@@ -624,7 +628,7 @@ mod tests {
a.put(ROOT, "neg_zero", -0.0_f64).unwrap();
});
- let snap = snapshot_of(&mut a);
+ let snap = snapshot_of(&a);
let d = &snap.doc;
match d.get(ROOT, "c").unwrap() {
@@ -665,7 +669,7 @@ mod tests {
let mut a = doc(b"actor-a");
change(&mut a, |a| a.put(ROOT, "x", 1_i64).unwrap());
- let mut snap = snapshot_of(&mut a);
+ let snap = snapshot_of(&a);
let heads = snap.doc.get_heads();
assert_eq!(heads.len(), 1);
assert_eq!(heads[0].0, snap.epoch.id);
@@ -683,15 +687,15 @@ mod tests {
let policy = json!({});
assert!(matches!(
- build(&mut a, &params(&foreign, &policy)),
+ build(&a, &params(&foreign, &policy)),
Err(SnapshotError::MissingBase(_))
));
}
#[test]
fn empty_document() {
- let mut a = doc(b"actor-a");
- let snap = snapshot_of(&mut a);
+ let a = doc(b"actor-a");
+ let snap = snapshot_of(&a);
assert_eq!(snap.epoch.generation, 1);
assert!(snap.clock.is_empty());
assert_eq!(user_view(&snap.doc), json!({}));
@@ -706,7 +710,7 @@ mod tests {
change(&mut late, |late| late.put(ROOT, "shared", "base").unwrap());
let base = late.get_heads();
let policy = json!({});
- let mut snap = build(&mut late, &params(&base, &policy)).unwrap();
+ let snap = build(&late, &params(&base, &policy)).unwrap();
// Meanwhile the swarm moved on in the new epoch.
let mut swarm = snap
@@ -718,8 +722,8 @@ mod tests {
change(&mut late, |late| late.put(ROOT, "mine", "late").unwrap());
- let mut fetched = AutoCommit::load(&bytes).unwrap();
- let metadata = verify_document(&mut fetched, snap.epoch).unwrap();
+ let fetched = Document::load(&bytes).unwrap();
+ let metadata = verify_document(&fetched, snap.epoch).unwrap();
assert_eq!(metadata.previous, Epoch::GENESIS);
assert_eq!(metadata.remake_id, "c-1");
assert_eq!(
@@ -730,14 +734,14 @@ mod tests {
// Documents of other epochs are refused.
let mut wrong = snap.epoch;
wrong.generation = 2;
- assert!(verify_document(&mut fetched, wrong).is_err());
+ assert!(verify_document(&fetched, wrong).is_err());
let mut wrong = snap.epoch;
wrong.id = [0; 32];
- assert!(verify_document(&mut fetched, wrong).is_err());
+ assert!(verify_document(&fetched, wrong).is_err());
let own = late.get_actor().clone();
let missing =
- delta::missing_from_snapshot(&mut late, &metadata.clock, std::slice::from_ref(&own));
+ delta::missing_from_snapshot(&late, &metadata.clock, std::slice::from_ref(&own));
assert_eq!(missing.own, 1);
let heads = late.get_heads();
let mut adopted = fetched.with_actor(ActorId::from(&b"late-node-next"[..]));