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.rs58
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, &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(
@@ -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"[..]));