mirror of
https://github.com/logos-messaging/libchat.git
synced 2026-08-25 07:01:08 +00:00
feat: name the group state each member holds
A GroupV2 group can divide into branches that never converge again, and no member can tell from its own view that it happened: both halves keep a roster and an epoch number, each simply stops being able to read what the other sends, and a fresh split leaves both carrying the same members. A member that has merely fallen behind produces the same endless run of undecryptable frames, from a different cause needing a different fix, and nothing separates the two. MLS derives a value for exactly this comparison, meant to be compared out of band: at one epoch number, different epoch authenticators are a fork and identical ones are real convergence. Write it once per epoch a conversation reaches, so a run leaves behind the one thing that partitions its members.
This commit is contained in:
@@ -226,6 +226,7 @@ impl GroupV2Convo {
|
||||
|
||||
convo.init(service_ctx)?;
|
||||
convo.after_op(service_ctx)?;
|
||||
convo.log_epoch();
|
||||
Ok(convo)
|
||||
}
|
||||
|
||||
@@ -262,6 +263,7 @@ impl GroupV2Convo {
|
||||
|
||||
convo.init(service_ctx)?; // subscribe
|
||||
convo.after_op(service_ctx)?; // flush join broadcast + schedule wakeup
|
||||
convo.log_epoch();
|
||||
|
||||
Ok(convo)
|
||||
}
|
||||
@@ -490,6 +492,12 @@ impl GroupV2Convo {
|
||||
};
|
||||
service_ctx.group_v2_status.record(&self.convo_id, kind);
|
||||
}
|
||||
if events
|
||||
.iter()
|
||||
.any(|evt| matches!(evt, ConversationEvent::CommitApplied(_)))
|
||||
{
|
||||
self.log_epoch();
|
||||
}
|
||||
|
||||
// 3. Publish
|
||||
for out in outbound {
|
||||
@@ -520,6 +528,26 @@ impl GroupV2Convo {
|
||||
Ok(events)
|
||||
}
|
||||
|
||||
/// Names this member's view of the group's state. Two members at the same
|
||||
/// epoch holding different authenticators have forked; a member at a lower
|
||||
/// epoch has only fallen behind. The pair carries that meaning only
|
||||
/// compared across members, never on one alone.
|
||||
fn log_epoch(&self) {
|
||||
let epoch = match self.conversation.epoch_and_retry() {
|
||||
Ok((epoch, _)) => epoch,
|
||||
Err(e) => {
|
||||
tracing::warn!(convo = %self.convo_id, error = %e, "epoch unavailable");
|
||||
return;
|
||||
}
|
||||
};
|
||||
info!(
|
||||
convo = %self.convo_id,
|
||||
epoch,
|
||||
authenticator = %hex::encode(self.conversation.epoch_authenticator()),
|
||||
"epoch reached"
|
||||
);
|
||||
}
|
||||
|
||||
/// Turn drained de-mls events into a [`ConvoOutcome`], unwrapping the
|
||||
/// message from its causal-history envelope.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user