mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-10-06 07:28:23 +00:00
fix(nostr): harden announcement cascade fallbacks
This commit is contained in:
@@ -23,6 +23,12 @@ enum RefKey {
|
||||
Address(String),
|
||||
}
|
||||
|
||||
struct ParsedAddressRef {
|
||||
kind: Kind,
|
||||
pubkey: PublicKey,
|
||||
identifier: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct CascadeGraph {
|
||||
nodes: HashMap<EventId, Event>,
|
||||
@@ -139,9 +145,10 @@ impl DeletionPolicy {
|
||||
Ok(graph) => graph,
|
||||
Err(e) => {
|
||||
tracing::warn!(error = %e, "Cascade deletion: graph expansion failed; falling back to simple coordinate deletion");
|
||||
self.delete_coordinate_from_main_db(
|
||||
self.delete_announcement_coordinate_and_maybe_state(
|
||||
author,
|
||||
announcement_addr,
|
||||
identifier,
|
||||
deletion_created_at,
|
||||
&mut moved_ids,
|
||||
source,
|
||||
@@ -182,9 +189,10 @@ impl DeletionPolicy {
|
||||
max = MAX_CASCADE_ORPHAN_DELETES,
|
||||
"Cascade deletion orphan set exceeds limit; deleting announcement coordinate only"
|
||||
);
|
||||
self.delete_coordinate_from_main_db(
|
||||
self.delete_announcement_coordinate_and_maybe_state(
|
||||
author,
|
||||
announcement_addr,
|
||||
identifier,
|
||||
deletion_created_at,
|
||||
&mut moved_ids,
|
||||
source,
|
||||
@@ -222,21 +230,11 @@ impl DeletionPolicy {
|
||||
}
|
||||
|
||||
// Step 5b: hard-delete the announcement coordinate itself (up to the
|
||||
// deletion's created_at, per NIP-09), exactly as the simple path does.
|
||||
self.delete_coordinate_from_main_db(
|
||||
// deletion's created_at, per NIP-09), exactly as the simple path does,
|
||||
// then clean state if this identifier is now unanchored.
|
||||
self.delete_announcement_coordinate_and_maybe_state(
|
||||
author,
|
||||
announcement_addr,
|
||||
deletion_created_at,
|
||||
&mut moved_ids,
|
||||
source,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Step 5c: repository state (30618) is keyed by identifier, not by
|
||||
// graph edges. After deleting this announcement coordinate, delete
|
||||
// state events for the identifier only when no announcement for that
|
||||
// identifier remains in the main DB.
|
||||
self.delete_repo_state_if_identifier_is_unanchored(
|
||||
identifier,
|
||||
deletion_created_at,
|
||||
&mut moved_ids,
|
||||
@@ -371,6 +369,37 @@ impl DeletionPolicy {
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn delete_announcement_coordinate_and_maybe_state(
|
||||
&self,
|
||||
author: &PublicKey,
|
||||
announcement_addr: &str,
|
||||
identifier: &str,
|
||||
deletion_created_at: Timestamp,
|
||||
moved_ids: &mut HashSet<EventId>,
|
||||
source: DeletionSource,
|
||||
) {
|
||||
self.delete_coordinate_from_main_db(
|
||||
author,
|
||||
announcement_addr,
|
||||
deletion_created_at,
|
||||
moved_ids,
|
||||
source,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Repository state (30618) is keyed by identifier, not by graph edges.
|
||||
// After deleting this announcement coordinate, delete state events for
|
||||
// the identifier only when no announcement for that identifier remains
|
||||
// in the main DB. This must also run on coordinate-only fallbacks.
|
||||
self.delete_repo_state_if_identifier_is_unanchored(
|
||||
identifier,
|
||||
deletion_created_at,
|
||||
moved_ids,
|
||||
source,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn expand_cascade_graph(
|
||||
&self,
|
||||
seed_event_ids: HashSet<EventId>,
|
||||
@@ -522,23 +551,13 @@ async fn query_address_events(
|
||||
database: &SharedDatabase,
|
||||
address: &str,
|
||||
) -> Result<Vec<Event>, String> {
|
||||
let parts: Vec<&str> = address.splitn(3, ':').collect();
|
||||
if parts.len() < 2 {
|
||||
let Some(parsed) = parse_accepted_address_ref(address) else {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let kind_num = parts[0]
|
||||
.parse::<u16>()
|
||||
.map_err(|e| format!("invalid address kind {address}: {e}"))?;
|
||||
let pubkey = PublicKey::from_hex(parts[1])
|
||||
.map_err(|e| format!("invalid address pubkey {address}: {e}"))?;
|
||||
};
|
||||
|
||||
let kind = Kind::from(kind_num);
|
||||
let mut filter = Filter::new().kind(kind).author(pubkey);
|
||||
if parts.len() == 3 && kind.is_addressable() {
|
||||
filter = filter.custom_tag(
|
||||
SingleLetterTag::lowercase(Alphabet::D),
|
||||
parts[2].to_string(),
|
||||
);
|
||||
let mut filter = Filter::new().kind(parsed.kind).author(parsed.pubkey);
|
||||
if let Some(identifier) = parsed.identifier {
|
||||
filter = filter.custom_tag(SingleLetterTag::lowercase(Alphabet::D), identifier);
|
||||
}
|
||||
|
||||
database
|
||||
@@ -548,6 +567,35 @@ async fn query_address_events(
|
||||
.map_err(|e| format!("query address {address} failed: {e}"))
|
||||
}
|
||||
|
||||
fn parse_accepted_address_ref(address: &str) -> Option<ParsedAddressRef> {
|
||||
let parts: Vec<&str> = address.splitn(3, ':').collect();
|
||||
if parts.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let kind = Kind::from(parts[0].parse::<u16>().ok()?);
|
||||
let pubkey = PublicKey::from_hex(parts[1]).ok()?;
|
||||
|
||||
if kind.is_addressable() {
|
||||
let identifier = parts.get(2)?;
|
||||
return Some(ParsedAddressRef {
|
||||
kind,
|
||||
pubkey,
|
||||
identifier: Some((*identifier).to_string()),
|
||||
});
|
||||
}
|
||||
|
||||
if kind.is_replaceable() && parts.len() == 2 {
|
||||
return Some(ParsedAddressRef {
|
||||
kind,
|
||||
pubkey,
|
||||
identifier: None,
|
||||
});
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
async fn query_incoming_event_refs(
|
||||
database: &SharedDatabase,
|
||||
event_id: EventId,
|
||||
@@ -772,7 +820,9 @@ fn extract_accepted_reference_keys(event: &Event) -> HashSet<RefKey> {
|
||||
}
|
||||
match tag_vec[0].as_str() {
|
||||
"a" | "A" => {
|
||||
refs.insert(RefKey::Address(tag_vec[1].to_string()));
|
||||
if parse_accepted_address_ref(&tag_vec[1]).is_some() {
|
||||
refs.insert(RefKey::Address(tag_vec[1].to_string()));
|
||||
}
|
||||
}
|
||||
"e" | "E" => {
|
||||
if let Ok(event_id) = EventId::from_hex(&tag_vec[1]) {
|
||||
@@ -780,7 +830,9 @@ fn extract_accepted_reference_keys(event: &Event) -> HashSet<RefKey> {
|
||||
}
|
||||
}
|
||||
"q" if tag_vec[1].contains(':') => {
|
||||
refs.insert(RefKey::Address(tag_vec[1].to_string()));
|
||||
if parse_accepted_address_ref(&tag_vec[1]).is_some() {
|
||||
refs.insert(RefKey::Address(tag_vec[1].to_string()));
|
||||
}
|
||||
}
|
||||
"q" => {
|
||||
if let Ok(event_id) = EventId::from_hex(&tag_vec[1]) {
|
||||
|
||||
Reference in New Issue
Block a user