mirror of
https://github.com/block/buzz.git
synced 2026-10-31 22:41:12 +00:00
fix(relay): advance identity archive snapshots
Signed-off-by: Larry <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -3106,6 +3106,12 @@ pub async fn reconcile_channel_events(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn next_snapshot_timestamp(now: u64, current: Option<u64>) -> u64 {
|
||||
current
|
||||
.map(|created_at| created_at.saturating_add(1).max(now))
|
||||
.unwrap_or(now)
|
||||
}
|
||||
|
||||
/// Publish a kind:13535 archived identities list event (NIP-IA).
|
||||
///
|
||||
/// Queries all current archived identities and emits a relay-signed,
|
||||
@@ -3115,7 +3121,8 @@ pub async fn publish_nipia_archival_list(
|
||||
state: &Arc<AppState>,
|
||||
) -> anyhow::Result<()> {
|
||||
let archived = state.db.list_archived(tenant.community()).await?;
|
||||
let relay_pubkey_hex = state.relay_keypair.public_key().to_hex();
|
||||
let relay_pubkey = state.relay_keypair.public_key();
|
||||
let relay_pubkey_hex = relay_pubkey.to_hex();
|
||||
|
||||
let mut tags: Vec<Tag> = Vec::with_capacity(archived.len() + 1);
|
||||
tags.push(Tag::parse(["-"]).map_err(|e| anyhow::anyhow!("failed to build '-' tag: {e}"))?);
|
||||
@@ -3127,8 +3134,33 @@ pub async fn publish_nipia_archival_list(
|
||||
);
|
||||
}
|
||||
|
||||
// Nostr timestamps have one-second resolution, while archive and unarchive
|
||||
// can complete within the same second. A same-second snapshot would enter
|
||||
// NIP-16's event-id tie-break and could lose to the prior snapshot, leaving
|
||||
// clients with stale archive state. Advance strictly past the stored head so
|
||||
// every accepted identity-state transition produces the authoritative head.
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs();
|
||||
let existing = state
|
||||
.db
|
||||
.get_latest_global_replaceable(
|
||||
tenant.community(),
|
||||
KIND_IA_ARCHIVED_LIST as i32,
|
||||
relay_pubkey.to_bytes().as_slice(),
|
||||
)
|
||||
.await?;
|
||||
let created_at = next_snapshot_timestamp(
|
||||
now,
|
||||
existing
|
||||
.as_ref()
|
||||
.map(|stored| stored.event.created_at.as_secs()),
|
||||
);
|
||||
|
||||
let event = EventBuilder::new(Kind::Custom(KIND_IA_ARCHIVED_LIST as u16), "")
|
||||
.tags(tags)
|
||||
.custom_created_at(nostr::Timestamp::from(created_at))
|
||||
.sign_with_keys(&state.relay_keypair)
|
||||
.map_err(|e| anyhow::anyhow!("failed to sign kind:{KIND_IA_ARCHIVED_LIST}: {e}"))?;
|
||||
|
||||
@@ -3372,6 +3404,23 @@ fn topic_for_subscription(channel_id: Option<Uuid>) -> EventTopic {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn identity_archive_snapshot_advances_past_same_second_head() {
|
||||
assert_eq!(next_snapshot_timestamp(1_000, Some(1_000)), 1_001);
|
||||
assert_eq!(next_snapshot_timestamp(1_000, Some(1_001)), 1_002);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn identity_archive_snapshot_uses_wall_clock_when_it_is_newer() {
|
||||
assert_eq!(next_snapshot_timestamp(1_001, Some(1_000)), 1_001);
|
||||
assert_eq!(next_snapshot_timestamp(1_001, None), 1_001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn identity_archive_snapshot_timestamp_saturates() {
|
||||
assert_eq!(next_snapshot_timestamp(u64::MAX, Some(u64::MAX)), u64::MAX);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete_tombstone_omits_absent_moderation_metadata() {
|
||||
let content =
|
||||
|
||||
Reference in New Issue
Block a user