This commit is contained in:
2026-09-22 15:30:12 +07:00
parent 04fb70e657
commit 28f4c1596d
9 changed files with 1431 additions and 80 deletions
+108
View File
@@ -17,6 +17,27 @@ use crate::{ChannelId, CommunityId, Epoch, Extra, decode_hex_32};
/// The `concord/` namespace for locally-keyed documents.
pub const STATE_PREFIX: &str = "concord/";
/// A key epoch the client still holds, retained so history stays readable.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct HeldKey {
pub epoch: Epoch,
pub key: [u8; 32],
#[serde(default, skip_serializing_if = "Option::is_none")]
pub retired_at: Option<u64>,
}
/// A community root epoch the client still holds, retained for the same reason.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct HeldRoot {
pub epoch: Epoch,
pub key: [u8; 32],
/// The epoch's Control Plane signer, when the rotation delivered one.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub control_pk: Option<PublicKey>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub retired_at: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ChannelKeyRef {
pub id: ChannelId,
@@ -26,6 +47,16 @@ pub struct ChannelKeyRef {
/// The channel's read secret when the member was granted it.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub key: Option<[u8; 32]>,
/// Keys this one superseded, retained so a rotation never blanks history.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub priors: Vec<HeldKey>,
}
impl ChannelKeyRef {
/// The write coordinate: only the current epoch is ever published under.
pub fn current(&self) -> Option<(Epoch, [u8; 32])> {
self.key.map(|key| (self.epoch, key))
}
}
/// How far a channel's history sync has reached, in epoch milliseconds.
@@ -92,6 +123,19 @@ pub struct CommunityState {
/// Where each channel's history sync has reached.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub cursors: BTreeMap<ChannelId, ChannelCursor>,
/// Root epochs superseded by a rotation we adopted, newest first.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub held_roots: Vec<HeldRoot>,
/// The epoch a channel rotation removed us at.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub channel_cuts: BTreeMap<ChannelId, u64>,
/// The base epoch we were excluded at.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub removed_at: Option<Epoch>,
/// A complete rotation ahead of our epoch predates our join and carries no
/// blob for us: a stale invite landed us on a superseded epoch.
#[serde(default)]
pub stranded: bool,
#[serde(default)]
pub dissolved: bool,
pub added_at_ms: u64,
@@ -135,6 +179,7 @@ impl CommunityState {
private: metadata.private,
epoch: ROOT_EPOCH,
key: None,
priors: Vec::new(),
});
}
_ => {}
@@ -165,6 +210,10 @@ impl CommunityState {
heads,
banned: BTreeSet::new(),
cursors: BTreeMap::new(),
held_roots: Vec::new(),
channel_cuts: BTreeMap::new(),
removed_at: None,
stranded: false,
dissolved: false,
added_at_ms,
})
@@ -177,6 +226,7 @@ impl CommunityState {
};
let mut channels = Vec::with_capacity(material.channels.len());
for grant in &material.channels {
let key = match &grant.key {
Some(key) => Some(decode_hex_32(key)?),
@@ -189,6 +239,7 @@ impl CommunityState {
private: key.is_some(),
epoch: grant.epoch,
key,
priors: Vec::new(),
});
}
@@ -213,6 +264,10 @@ impl CommunityState {
heads: Vec::new(),
banned: BTreeSet::new(),
cursors: BTreeMap::new(),
held_roots: Vec::new(),
channel_cuts: BTreeMap::new(),
removed_at: None,
stranded: false,
dissolved: false,
added_at_ms,
})
@@ -222,6 +277,58 @@ impl CommunityState {
state_identifier(&self.id)
}
/// Every root epoch we hold, the current one first.
pub fn roots(&self) -> Vec<HeldRoot> {
let mut roots = Vec::with_capacity(self.held_roots.len() + 1);
roots.push(HeldRoot {
epoch: self.root_epoch,
key: self.community_root,
control_pk: self.control_pks.get(&self.root_epoch.0).copied(),
retired_at: None,
});
roots.extend(self.held_roots.iter().copied());
roots
}
/// Every secret held for a channel, newest epoch first.
pub fn held_keys(&self, channel: &ChannelId) -> Vec<HeldKey> {
let Some(held) = self.channels.iter().find(|held| held.id == *channel) else {
return Vec::new();
};
if held.private {
let mut keys: Vec<HeldKey> = held
.key
.map(|key| HeldKey {
epoch: held.epoch,
key,
retired_at: None,
})
.into_iter()
.collect();
keys.extend(held.priors.iter().copied());
return keys;
}
// A public channel derives from the community root, so its history
// spans every root epoch the rotation kept a floor for.
self.roots()
.into_iter()
.map(|root| HeldKey {
epoch: held.epoch,
key: root.key,
retired_at: root.retired_at,
})
.collect()
}
/// Whether a channel rotation removed us at or after `epoch`.
pub fn channel_cut(&self, channel: &ChannelId, epoch: Epoch) -> bool {
self.channel_cuts
.get(channel)
.is_some_and(|cut| epoch.0 <= *cut)
}
pub fn floors(&self) -> Floors {
self.heads
.iter()
@@ -265,6 +372,7 @@ impl CommunityState {
private: false,
epoch: self.root_epoch,
key: None,
priors: Vec::new(),
}),
None => {}
}