summaryrefslogtreecommitdiffstats
path: root/src/remake/snapshot.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/remake/snapshot.rs')
-rw-r--r--src/remake/snapshot.rs53
1 files changed, 53 insertions, 0 deletions
diff --git a/src/remake/snapshot.rs b/src/remake/snapshot.rs
index ca50c86..3c180ba 100644
--- a/src/remake/snapshot.rs
+++ b/src/remake/snapshot.rs
@@ -226,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.
@@ -523,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, &params(&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(