aboutsummaryrefslogtreecommitdiffstats
path: root/src/access
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-08 09:33:41 +0000
committertv <tv@krebsco.de>2026-11-08 10:35:31 +0100
commitb758c93a1887ea36b0f52ccd4a6f9824de77d819 (patch)
tree96f7f4ca3007e393b185739a66b0474323b50872 /src/access
parentaf3869df9ff3a0266857bd7c7534b75b36d04aa5 (diff)
access: an epoch's root is never dropped
A REFUSALS naming the swarm's first change made the node receiving it judge that change again in a document that isn't empty, refuse it as "a change without dependencies that isn't the epoch's root", and drop it with everything built on it: its whole document. The first epoch's root is now the first change a document holds, as it comes to an empty one or as it is in one; a change judged again on a peer's word is never refused for having no dependencies; and drop_taken refuses to drop an epoch's root, or anything that would leave nothing kept, logging an error instead. Review finding 1 (critical).
Diffstat (limited to 'src/access')
-rw-r--r--src/access/rules.rs10
1 files changed, 8 insertions, 2 deletions
diff --git a/src/access/rules.rs b/src/access/rules.rs
index f285328..2aed4b8 100644
--- a/src/access/rules.rs
+++ b/src/access/rules.rs
@@ -33,7 +33,7 @@ use crate::document::at::At;
use crate::document::inspect::{Action, Op, Path};
use crate::document::meta;
use crate::document::replica::Epoch;
-use crate::document::{ChangeHash, Document, store};
+use crate::document::{ChangeHash, Document, ReadDoc, store};
/// A change to judge, as its bytes say.
#[derive(Debug, Clone)]
@@ -75,8 +75,10 @@ impl std::fmt::Display for Refusal {
/// Judges `change`, about to be applied to `doc` (of `epoch`).
pub(crate) fn judge(doc: &Document, epoch: Epoch, change: &Change<'_>) -> Result<(), Refusal> {
if change.deps.is_empty() {
+ // The first epoch's: the first change a document holds, as it comes to an empty
+ // one, or as it is in one (judged again, `swarm::refusals`).
let root = if epoch.generation == 0 {
- doc.get_heads().is_empty()
+ doc.get_heads().is_empty() || doc.get_change_by_hash(&change.hash).is_some()
} else {
change.hash.0 == epoch.id
};
@@ -720,6 +722,10 @@ mod tests {
};
assert!(root(&Document::new(), Epoch::GENESIS, [1; 32]).is_ok());
assert!(refused(root(&swarm(false), Epoch::GENESIS, [1; 32])));
+ // The first epoch's, judged again in a document that holds it: its root.
+ let held = swarm(false);
+ let first = held.get_changes(&[])[0].hash();
+ assert!(root(&held, Epoch::GENESIS, first.0).is_ok());
let epoch = Epoch {
generation: 3,
id: [5; 32],