test(relay): racing creates for one label resolve to the same channel

One creator sends two kind:9007s for the same label at once. The relay
keeps both channels, the #P + #t lookup finds both, and oldest_channel
picks the same one in any input order, even after the older channel's
kind:39000 is re-published by an edit.

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
This commit is contained in:
Logan Johnson
2026-10-08 16:09:17 -04:00
parent 4641fdfb5f
commit 148a7a5a80

View File

@@ -454,6 +454,103 @@ async fn members_cannot_change_labels() {
);
}
/// Ensure race: one creator sends two kind:9007s for the same label at
/// once. The relay keeps both channels, a `#P` + `#t` lookup finds both,
/// and every reader picks the same one: the lowest stored creation time,
/// then the lowest ID.
/// Mutation: rank by the 39000's own created_at after an edit, or let the
/// lookup miss one channel → RED.
#[tokio::test]
#[ignore = "requires Postgres"]
async fn racing_creates_resolve_to_the_same_oldest_channel() {
let state = state().await;
let tenant = community(&state, "group-state-ensure-race").await;
let creator = Keys::generate();
let extra: &[&[&str]] = &[&["t", "ensure-race"]];
let (first, second) = tokio::join!(
create_channel(&state, &tenant, &creator, extra),
create_channel(&state, &tenant, &creator, extra),
);
assert_ne!(
first, second,
"both creates must succeed as separate channels"
);
// Edit whichever channel is older so its 39000 becomes the newest event.
// Readers must still pick it, because it was created first.
let created = |id: Uuid| {
let state = state.clone();
let tenant = tenant.clone();
async move {
state
.db
.get_channel_for_event_write(tenant.community(), id)
.await
.expect("channel")
.created_at
.timestamp()
}
};
let mut ranked = [
(created(first).await, first.to_string()),
(created(second).await, second.to_string()),
];
ranked.sort();
let expected = ranked[0].1.clone();
tokio::time::sleep(std::time::Duration::from_millis(1_100)).await;
edit_channel(
&state,
&tenant,
&creator,
expected.parse().unwrap(),
&[&["about", "edited after the race"]],
)
.await
.expect("edit the older channel");
// Step 1 of Ensure: the creator's label lookup finds both channels.
let found = req_event_ids(
&state,
&tenant,
&creator,
serde_json::json!({
"kinds": [39000],
"#P": [creator.public_key().to_hex()],
"#t": ["ensure-race"],
}),
)
.await;
assert_eq!(found.len(), 2, "lookup must find both channels: {found:?}");
let mut events = Vec::new();
for id in [first, second] {
let stored = state
.db
.query_events(&EventQuery {
kinds: Some(vec![39000]),
d_tag: Some(id.to_string()),
limit: Some(1),
..EventQuery::for_community(tenant.community())
})
.await
.expect("query 39000");
let event = stored.into_iter().next().expect("39000").event;
assert!(
found.contains(&event.id.to_hex()),
"lookup returned the current 39000"
);
events.push(event);
}
let pick = |events: Vec<&nostr::Event>| {
buzz_core::channel::oldest_channel(events)
.and_then(|event| event.tags.identifier())
.map(str::to_owned)
};
assert_eq!(pick(vec![&events[0], &events[1]]), Some(expected.clone()));
assert_eq!(pick(vec![&events[1], &events[0]]), Some(expected));
}
/// Run one historical REQ as `reader` and return the IDs of the events sent
/// before EOSE.
async fn req_event_ids(