update
This commit is contained in:
+137
-21
@@ -10,7 +10,7 @@ use concord::cord04::roles::{Permissions, citation_ok};
|
||||
use concord::derive::{
|
||||
channel_group_key, control_group_key, control_signer_group_key, guestbook_group_key,
|
||||
};
|
||||
use concord::state::{CommunityState, list_entry};
|
||||
use concord::state::{CommunityState, HeldKey, HeldRoot, list_entry};
|
||||
use concord::{ChannelId, CommunityId, Epoch, GroupKey};
|
||||
use gpui::AsyncApp;
|
||||
use nostr_sdk::prelude::*;
|
||||
@@ -35,18 +35,42 @@ pub struct Plane {
|
||||
/// The wrap's author: the control signer for Control, the group's own key otherwise.
|
||||
pub address: PublicKey,
|
||||
pub group: GroupKey,
|
||||
/// Epoch seconds the key behind this plane was retired.
|
||||
pub retired_at: Option<u64>,
|
||||
}
|
||||
|
||||
impl Plane {
|
||||
/// Whether a wrap sealed under this plane is still inside its key's life.
|
||||
pub fn accepts(&self, wrap: &Event) -> bool {
|
||||
self.retired_at
|
||||
.is_none_or(|retired| wrap.created_at.as_secs() <= retired)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn planes(state: &CommunityState) -> Result<Vec<Plane>> {
|
||||
let mut planes = Vec::new();
|
||||
let roots = state.roots();
|
||||
|
||||
for (epoch, address) in &state.control_pks {
|
||||
let epoch = Epoch(*epoch);
|
||||
let group = control_group_key(&state.community_root, &state.id, epoch)?;
|
||||
// An epoch's Control Plane reads under the root that was current then,
|
||||
// so a rotation that kept a floor for the prior root republishes here.
|
||||
let root = roots
|
||||
.iter()
|
||||
.find(|root| root.epoch == epoch)
|
||||
.copied()
|
||||
.unwrap_or(HeldRoot {
|
||||
epoch,
|
||||
key: state.community_root,
|
||||
control_pk: None,
|
||||
retired_at: None,
|
||||
});
|
||||
let group = control_group_key(&root.key, &state.id, epoch)?;
|
||||
planes.push(Plane {
|
||||
kind: PlaneKind::Control(epoch),
|
||||
address: *address,
|
||||
group,
|
||||
retired_at: root.retired_at,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -56,31 +80,38 @@ pub fn planes(state: &CommunityState) -> Result<Vec<Plane>> {
|
||||
kind: PlaneKind::Control(state.root_epoch),
|
||||
address: group.pk(),
|
||||
group,
|
||||
retired_at: None,
|
||||
});
|
||||
}
|
||||
|
||||
let group = guestbook_group_key(&state.community_root, &state.id, state.root_epoch)?;
|
||||
planes.push(Plane {
|
||||
kind: PlaneKind::Guestbook,
|
||||
address: group.pk(),
|
||||
group,
|
||||
});
|
||||
|
||||
for channel in &state.channels {
|
||||
let secret = match (channel.private, channel.key) {
|
||||
(true, None) => continue,
|
||||
(true, Some(key)) => key,
|
||||
(false, _) => state.community_root,
|
||||
};
|
||||
|
||||
let group = channel_group_key(&secret, &channel.id, channel.epoch)?;
|
||||
for root in &roots {
|
||||
let group = guestbook_group_key(&root.key, &state.id, root.epoch)?;
|
||||
planes.push(Plane {
|
||||
kind: PlaneKind::Channel(channel.id, channel.epoch),
|
||||
kind: PlaneKind::Guestbook,
|
||||
address: group.pk(),
|
||||
group,
|
||||
retired_at: root.retired_at,
|
||||
});
|
||||
}
|
||||
|
||||
for channel in &state.channels {
|
||||
for held in state.held_keys(&channel.id) {
|
||||
let group = channel_group_key(&held.key, &channel.id, held.epoch)?;
|
||||
planes.push(Plane {
|
||||
kind: PlaneKind::Channel(channel.id, held.epoch),
|
||||
address: group.pk(),
|
||||
group,
|
||||
retired_at: held.retired_at,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// A rotation can re-derive an address the current root already produced.
|
||||
//
|
||||
// A duplicate author would only repeat a filter, so keep the set unique.
|
||||
let mut seen = BTreeSet::new();
|
||||
planes.retain(|plane| seen.insert(plane.address));
|
||||
|
||||
Ok(planes)
|
||||
}
|
||||
|
||||
@@ -389,17 +420,44 @@ fn refresh(mut held: CommunityState, fresh: CommunityState) -> CommunityState {
|
||||
held.relays = fresh.relays;
|
||||
|
||||
for channel in fresh.channels {
|
||||
let cut = held.channel_cuts.get(&channel.id).copied();
|
||||
|
||||
match held.channels.iter_mut().find(|held| held.id == channel.id) {
|
||||
Some(held) => {
|
||||
held.name = channel.name;
|
||||
held.epoch = channel.epoch;
|
||||
|
||||
if cut.is_some_and(|cut| channel.epoch.0 <= cut) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if channel.private {
|
||||
held.private = true;
|
||||
held.key = channel.key;
|
||||
|
||||
if let Some(key) = channel.key
|
||||
&& (held.key != Some(key) || held.epoch != channel.epoch)
|
||||
{
|
||||
// The key being superseded still reads everything
|
||||
// written under it, so it is retained, never overwritten.
|
||||
if let Some((epoch, previous)) = held.current()
|
||||
&& !held.priors.iter().any(|prior| prior.epoch == epoch)
|
||||
{
|
||||
held.priors.push(HeldKey {
|
||||
epoch,
|
||||
key: previous,
|
||||
retired_at: None,
|
||||
});
|
||||
}
|
||||
|
||||
held.key = Some(key);
|
||||
held.epoch = channel.epoch;
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if cut.is_none() {
|
||||
held.channels.push(channel);
|
||||
}
|
||||
}
|
||||
None => held.channels.push(channel),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,6 +544,11 @@ pub async fn fold(client: &Client, state: &CommunityState) -> Result<Option<Snap
|
||||
continue;
|
||||
};
|
||||
|
||||
// A retired key reads only what was sealed before its rotation published.
|
||||
if !plane.accepts(wrap) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match plane.kind {
|
||||
PlaneKind::Control(_) => {
|
||||
if let Ok(edition) = cord02::open_edition(wrap, &plane.group, &plane.address, true)
|
||||
@@ -609,6 +672,7 @@ mod tests {
|
||||
private: false,
|
||||
epoch: Epoch(0),
|
||||
key: None,
|
||||
priors: Vec::new(),
|
||||
},
|
||||
concord::state::ChannelKeyRef {
|
||||
id: staff,
|
||||
@@ -616,6 +680,7 @@ mod tests {
|
||||
private: true,
|
||||
epoch: Epoch(0),
|
||||
key: Some([0x04; 32]),
|
||||
priors: Vec::new(),
|
||||
},
|
||||
concord::state::ChannelKeyRef {
|
||||
id: ChannelId::from_bytes([0x9e; 32]),
|
||||
@@ -623,12 +688,17 @@ mod tests {
|
||||
private: true,
|
||||
epoch: Epoch(0),
|
||||
key: None,
|
||||
priors: Vec::new(),
|
||||
},
|
||||
],
|
||||
relays: vec![RelayUrl::parse("wss://relay.example").expect("a url")],
|
||||
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: 0,
|
||||
};
|
||||
@@ -719,11 +789,16 @@ mod tests {
|
||||
private: false,
|
||||
epoch: Epoch(0),
|
||||
key: None,
|
||||
priors: Vec::new(),
|
||||
}],
|
||||
relays: Vec::new(),
|
||||
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: 1_700_000_000_000,
|
||||
}
|
||||
@@ -1021,4 +1096,45 @@ mod tests {
|
||||
|
||||
assert!(subscription_id(&id).as_str().len() <= 64);
|
||||
}
|
||||
|
||||
fn event_at(at_ms: u64) -> Event {
|
||||
EventBuilder::new(Kind::TextNote, "wrap")
|
||||
.custom_created_at(Timestamp::from_secs(at_ms / 1000))
|
||||
.finalize(&Keys::generate())
|
||||
.expect("signs")
|
||||
}
|
||||
|
||||
/// A key a rotation stepped off still reads its own history, but nothing
|
||||
/// sealed after the rotation published it.
|
||||
#[test]
|
||||
fn a_superseded_key_reads_only_up_to_the_rotation_that_retired_it() {
|
||||
let channel = ChannelId::from_bytes([0x9c; 32]);
|
||||
let mut state = held(
|
||||
CommunityId::from_bytes([0x42; 32]),
|
||||
Keys::generate().public_key(),
|
||||
);
|
||||
|
||||
state.channels = vec![concord::state::ChannelKeyRef {
|
||||
id: channel,
|
||||
name: "staff".to_owned(),
|
||||
private: true,
|
||||
epoch: Epoch(1),
|
||||
key: Some([0x08; 32]),
|
||||
priors: vec![HeldKey {
|
||||
epoch: Epoch(0),
|
||||
key: [0x07; 32],
|
||||
retired_at: Some(1_000),
|
||||
}],
|
||||
}];
|
||||
|
||||
let planes = planes(&state).expect("planes");
|
||||
let retired = planes
|
||||
.iter()
|
||||
.find(|plane| plane.kind == PlaneKind::Channel(channel, Epoch(0)))
|
||||
.expect("the retired epoch keeps a plane");
|
||||
|
||||
assert_eq!(retired.retired_at, Some(1_000));
|
||||
assert!(retired.accepts(&event_at(1_000_000)));
|
||||
assert!(!retired.accepts(&event_at(1_001_000)));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user