From 36103818be1228e092037e3d0aee6aaecac1843d Mon Sep 17 00:00:00 2001 From: Luke Tornquist Date: Wed, 28 Oct 2026 15:35:41 -0400 Subject: [PATCH] fix(invites): run the unknown-code check inside the admitted transaction 989412f3 ran the pre-lock `SELECT EXISTS` on the bare pool before `begin_community_write_transaction`. That answered claims on fenced (quiescing/deleting) tenants with `invite_invalid` instead of the fence rejection, and acquired a writer connection outside a typed `WriterOperation`. Open the admitted transaction first, which enforces the tenant fence and attribution without taking the invite admission lock, then check the code on `tx.conn()` before `lock_admission`. Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 Signed-off-by: Luke Tornquist --- crates/buzz-db/src/store/relay_invite.rs | 32 +++++++++++++----------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/crates/buzz-db/src/store/relay_invite.rs b/crates/buzz-db/src/store/relay_invite.rs index 614606a18f..d1b6763571 100644 --- a/crates/buzz-db/src/store/relay_invite.rs +++ b/crates/buzz-db/src/store/relay_invite.rs @@ -423,21 +423,6 @@ pub async fn claim_relay_invite_with_owner( verified_owner: Option<&str>, relay_keypair: &nostr::Keys, ) -> Result> { - // Unknown codes are free to forge, so reject them before taking the - // community-wide admission lock; the locked read below stays authoritative. - let known: bool = sqlx::query_scalar( - "SELECT EXISTS (SELECT 1 FROM relay_invites \ - WHERE community_id = $1 AND token_hash = $2)", - ) - .bind(community.as_uuid()) - .bind(token_hash) - .fetch_one(pool) - .await?; - if !known { - log_claim_outcome(community, None, "invalid", None, None); - return Ok(MembershipWrite::unchanged(ClaimOutcome::Invalid.into())); - } - let mut tx = crate::begin_community_write_transaction( pool, community, @@ -445,6 +430,23 @@ pub async fn claim_relay_invite_with_owner( ) .await?; + // Unknown codes are free to forge, so reject them after the tenant fence + // but before the community-wide admission lock; the locked read below + // stays authoritative. + let known: bool = sqlx::query_scalar( + "SELECT EXISTS (SELECT 1 FROM relay_invites \ + WHERE community_id = $1 AND token_hash = $2)", + ) + .bind(community.as_uuid()) + .bind(token_hash) + .fetch_one(tx.conn()) + .await?; + if !known { + tx.rollback().await?; + log_claim_outcome(community, None, "invalid", None, None); + return Ok(MembershipWrite::unchanged(ClaimOutcome::Invalid.into())); + } + lock_admission(tx.conn(), community).await?; if claim_owner::claimant_restricted(tx.conn(), community, claimer_pubkey, verified_owner) .await?