diff options
Diffstat (limited to 'src/remake/snapshot.rs')
| -rw-r--r-- | src/remake/snapshot.rs | 58 |
1 files changed, 57 insertions, 1 deletions
diff --git a/src/remake/snapshot.rs b/src/remake/snapshot.rs index e50a2a2..20569e0 100644 --- a/src/remake/snapshot.rs +++ b/src/remake/snapshot.rs @@ -38,6 +38,8 @@ pub(crate) const REMAKES: &str = "remakes"; /// The votes register (`records`). pub(crate) const VOTES: &str = "votes"; pub(crate) const EPOCHS: &str = "epochs"; +/// The swarm ID, written once by the genesis node (`swarm::bootstrap`). +pub(crate) const SWARM: &str = "swarm"; /// Everything that determines a snapshot besides the base content. pub(crate) struct SnapshotParams<'a> { @@ -224,6 +226,38 @@ pub(crate) fn verify_document(doc: &mut AutoCommit, epoch: Epoch) -> Result<Epoc parse_metadata(&at_snapshot, epoch.generation) } +/// 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> { + let generation = store::keys_at(doc, &[META_ROOT.to_owned(), EPOCHS.to_owned()]) + .map_err(|error| error.to_string())? + .iter() + .filter_map(|key| key.parse::<u64>().ok()) + .max(); + let Some(generation) = generation.filter(|generation| *generation > 0) else { + return Ok(Epoch::GENESIS); + }; + let roots: Vec<ChangeHash> = doc + .get_changes(&[]) + .iter() + .filter(|change| change.deps().is_empty()) + .map(|change| change.hash()) + .collect(); + let [root] = roots.as_slice() else { + return Err(format!( + "generation {generation} needs exactly one change without dependencies (its snapshot), found {}", + roots.len() + )); + }; + let epoch = Epoch { + generation, + id: root.0, + }; + verify_document(doc, epoch)?; + Ok(epoch) +} + /// Metadata of an earlier generation, as the current document holds it. Snapshots /// 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. @@ -521,6 +555,27 @@ mod tests { } #[test] + fn epoch_of_a_backup() { + let mut a = doc(b"actor-a"); + store::write_path(&mut a, &["k".into()], &json!(1)).unwrap(); + a.commit(); + assert_eq!(epoch_of(&mut a), Ok(Epoch::GENESIS)); + + let heads = a.get_heads(); + let policy = json!({}); + let mut snap = build(&mut a, ¶ms(&heads, &policy)).unwrap(); + // Saved and loaded, with a write since, as a backup would be. + let mut later = snap + .doc + .fork() + .with_actor(ActorId::from(b"actor-b".as_slice())); + store::write_path(&mut later, &["k".into()], &json!(2)).unwrap(); + later.commit(); + let mut backup = AutoCommit::load(&later.save()).unwrap(); + assert_eq!(epoch_of(&mut backup), Ok(snap.epoch)); + } + + #[test] fn metadata_written_and_remake_records_dropped() { let mut a = doc(b"actor-a"); store::write_path( @@ -695,7 +750,8 @@ mod tests { assert!(verify_document(&mut fetched, wrong).is_err()); let own = late.get_actor().clone(); - let missing = delta::missing_from_snapshot(&mut late, &metadata.clock, &own); + let missing = + delta::missing_from_snapshot(&mut 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"[..])); |
