update
This commit is contained in:
@@ -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 => {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user