From bc1069a0f65cfbf3218b40a64d179856738e41c9 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Wed, 17 Jun 2026 09:31:52 +0000 Subject: [PATCH] test(nip09): verify out-of-order delete-then-create is honoured Add deletion_before_target_rejects_later_submission: a kind-5 deleting an own event arrives before the relay has ever seen that event. The pre-emptive delete is accepted and records a tombstone, so the later-arriving target is rejected by the resubmission gate ("this event is deleted") and never served. --- tests/nip09_validation.rs | 77 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/tests/nip09_validation.rs b/tests/nip09_validation.rs index 02ae24c..9836592 100644 --- a/tests/nip09_validation.rs +++ b/tests/nip09_validation.rs @@ -16,6 +16,8 @@ //! - `a`-tag coordinates are deleted when the coordinate pubkey matches the //! deleter; //! - deletion of a non-existent target is accepted (NIP-09 pre-emptive delete); +//! - a pre-emptive delete that arrives *before* its target lays down a +//! tombstone, so the later-arriving target is rejected and never served; //! - malformed `a` coordinates are silently treated as no-ops (accepted). //! //! `normal_mode_honours_deletion` in `nip09_disrespector.rs` already covers the @@ -294,6 +296,81 @@ async fn deletion_of_nonexistent_event_is_accepted() { ); } +/// 2b. Out-of-order deletion: a kind-5 deleting an own event arrives *before* +/// the relay has ever seen that event. The deletion is accepted (NIP-09 +/// pre-emptive delete) and records a tombstone; a subsequent submission of +/// the now-deleted event is rejected by the resubmission gate ("this event +/// is deleted") and the event is never served. +/// +/// This exercises the delete-then-create ordering: the server receives the +/// deletion request first and must still honour it when the target finally +/// shows up. +#[tokio::test] +async fn deletion_before_target_rejects_later_submission() { + let relay = TestRelay::start().await; + let client = AuditClient::new(relay.url(), AuditConfig::isolated()) + .await + .expect("create audit client"); + + // Promote a repo so we can build a well-formed issue against it. The issue + // is built (so we know its id) but deliberately NOT sent yet. + let (announcement, _repo_id) = publish_served_repo(&client, "delete-first").await; + + let issue = client + .create_issue(&announcement, "Issue deleted before it arrives", "delete me", vec![]) + .expect("build issue"); + let issue_id = issue.id; + + // Sanity: the relay has never seen the issue. + assert!( + !client + .is_event_on_relay(issue_id) + .await + .expect("query issue before anything is sent"), + "issue should not be served before it is ever submitted" + ); + + // Send the deletion FIRST — the relay receives the deletion request before + // the event it deletes. This must be accepted (pre-emptive delete) and lay + // down a tombstone for the issue id. + let deletion = build_deletion(&client, &[issue_id], &[]); + client + .send_event(deletion) + .await + .expect("relay should accept pre-emptive deletion of own (not-yet-seen) event"); + + tokio::time::sleep(Duration::from_millis(200)).await; + + // Now send the issue the deletion already targeted. The resubmission gate + // must reject it because a tombstone for this id already exists. + let result = client.send_event(issue.clone()).await; + + tokio::time::sleep(Duration::from_millis(200)).await; + + let served = client + .is_event_on_relay(issue_id) + .await + .expect("query issue after late submission"); + + relay.stop().await; + + assert!( + result.is_err(), + "submission of an already-deleted event must be rejected, but it was accepted" + ); + let msg = result.err().unwrap().to_string().to_lowercase(); + assert!( + msg.contains("delete"), + "rejection message should mention the event was deleted; got: {}", + msg + ); + assert!( + !served, + "issue {} must never be served: it was deleted before it arrived", + issue_id + ); +} + /// 3. Author B cannot delete an event owned by author A: the relay rejects the /// kind-5 with a message mentioning the authorship problem. #[tokio::test]