mirror of
https://github.com/block/buzz.git
synced 2026-11-12 20:31:12 +00:00
fix(relay): join new members to every default channel, not the first 32
`list_auto_join_channels` stopped at 32 rows while kind:9002 kept accepting `auto_join=true` on any number of channels, so with 33 flagged channels the newest one carried the kind:39000 `auto_join` tag but no claim or kind:9030 add ever joined anyone to it, and nothing retried because the person was already a relay member (Codex review on #8594). Drop the cap rather than enforce it at flag time: the kind:9002 transaction holds only the shared community admission lock and the one channel's row lock, so a count-based refusal would need a new community-wide lock to be race-free, and a private or archive flip on a flagged channel would still let the stored count drift past it. The loader now returns every effective channel, oldest first; the set is bounded by the community's channels, each flagged by a relay owner or admin, and NOSTR.md says so. Test: `every_default_channel_is_listed` (Postgres lane) flags 33 channels and asserts the loader lists all of them in creation order; putting the `LIMIT` back fails it (verified by mutation). Verified: fmt; clippy -D warnings on buzz-db and buzz-relay (all targets); the four auto_join Postgres tests. `auto_join_is_relay_admin_only_...` failed once at its first query with the known `PoolTimedOut` lane flake and passed on a solo rerun. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
This commit is contained in:
5
NOSTR.md
5
NOSTR.md
@@ -219,7 +219,10 @@ message in the channel.
|
||||
When someone becomes a community member (an invite claim or a kind:9030 add)
|
||||
the relay joins them to every effective default channel as a self-join: the
|
||||
body of kind:9021, so the channel's `join_role` applies and the channel gets
|
||||
its `member_joined` kind:40099, kind:39002 and kind:44100. It does this once,
|
||||
its `member_joined` kind:40099, kind:39002 and kind:44100. There is no cap on
|
||||
how many channels that is: whatever carries the kind:39000 tag is joined, and
|
||||
the work a new member costs grows with the number of channels an owner or
|
||||
admin flags. It does this once,
|
||||
at that moment, and never again on login or reconnect, so a later kind:9022
|
||||
leave stands. A channel that fails to join is logged and counted
|
||||
(`buzz_auto_join_failures_total`), not retried: the kind:39000 tag is the
|
||||
|
||||
@@ -616,13 +616,15 @@ async fn list_channels_with_operation(
|
||||
rows.into_iter().map(row_to_channel_record).collect()
|
||||
}
|
||||
|
||||
/// The most channels the relay joins a new community member into.
|
||||
pub const AUTO_JOIN_CHANNEL_LIMIT: i64 = 32;
|
||||
|
||||
/// Lists the channels whose `auto_join` is effective: set on a stream channel
|
||||
/// that is open, unarchived and not deleted. Oldest first, so the first
|
||||
/// default channel a member sees is the one that was flagged first. Bounded by
|
||||
/// [`AUTO_JOIN_CHANNEL_LIMIT`].
|
||||
/// default channel a member sees is the one that was flagged first.
|
||||
///
|
||||
/// Every effective channel, not a page of them: kind:9002 accepts the flag on
|
||||
/// any number of channels, and the kind:39000 `auto_join` tag promises the
|
||||
/// relay joins new members there, so a cap here would silently break that
|
||||
/// promise for the channels past it. The set is bounded by the community's
|
||||
/// channels, each flagged by a relay owner or admin.
|
||||
pub async fn list_auto_join_channels(
|
||||
pool: &PgPool,
|
||||
community_id: CommunityId,
|
||||
@@ -645,20 +647,11 @@ pub async fn list_auto_join_channels(
|
||||
WHERE community_id = $1 AND auto_join AND channel_type = 'stream'
|
||||
AND visibility = 'open' AND archived_at IS NULL AND deleted_at IS NULL
|
||||
ORDER BY created_at ASC
|
||||
LIMIT $2
|
||||
"#,
|
||||
)
|
||||
.bind(community_id.as_uuid())
|
||||
.bind(AUTO_JOIN_CHANNEL_LIMIT)
|
||||
.fetch_all(&mut *connection)
|
||||
.await?;
|
||||
if rows.len() as i64 == AUTO_JOIN_CHANNEL_LIMIT {
|
||||
tracing::warn!(
|
||||
community = %community_id,
|
||||
limit = AUTO_JOIN_CHANNEL_LIMIT,
|
||||
"auto_join channel list reached its cap; any channel beyond it is not applied"
|
||||
);
|
||||
}
|
||||
rows.into_iter().map(row_to_channel_record).collect()
|
||||
}
|
||||
|
||||
|
||||
@@ -618,6 +618,55 @@ fn archive_event(owner: &Keys, channel_id: Uuid, value: &str) -> nostr::Event {
|
||||
.expect("sign 9002")
|
||||
}
|
||||
|
||||
/// The loader returns every effective default channel. It used to stop at 32
|
||||
/// while kind:9002 kept accepting the flag past that, so the kind:39000 tag
|
||||
/// promised a join the relay never made for the newest channels. Mutation:
|
||||
/// put a `LIMIT` back on `list_auto_join_channels` → RED.
|
||||
#[tokio::test]
|
||||
#[ignore = "requires Postgres"]
|
||||
async fn every_default_channel_is_listed() {
|
||||
let state = state().await;
|
||||
let tenant = community(&state, "auto-join-all").await;
|
||||
let admin = Keys::generate();
|
||||
state
|
||||
.db
|
||||
.add_relay_member(
|
||||
tenant.community(),
|
||||
&admin.public_key().to_hex(),
|
||||
"admin",
|
||||
None,
|
||||
&Keys::generate(),
|
||||
)
|
||||
.await
|
||||
.map(|write| write.outcome)
|
||||
.expect("seed relay admin");
|
||||
|
||||
let mut flagged = Vec::new();
|
||||
for _ in 0..33 {
|
||||
let channel_id = create_channel(&state, &tenant, &admin, &[]).await;
|
||||
edit_channel(
|
||||
&state,
|
||||
&tenant,
|
||||
&admin,
|
||||
channel_id,
|
||||
&[&["auto_join", "true"]],
|
||||
)
|
||||
.await
|
||||
.expect("relay admin sets auto_join");
|
||||
flagged.push(channel_id);
|
||||
}
|
||||
|
||||
let listed: Vec<Uuid> = state
|
||||
.db
|
||||
.list_auto_join_channels(tenant.community())
|
||||
.await
|
||||
.expect("list auto_join channels")
|
||||
.iter()
|
||||
.map(|channel| channel.id)
|
||||
.collect();
|
||||
assert_eq!(listed, flagged, "every flagged channel, oldest first");
|
||||
}
|
||||
|
||||
/// A resend checked after its first copy committed is a duplicate.
|
||||
/// Validation runs on the new archive state and must not refuse the resend;
|
||||
/// the write must then report it as a duplicate, with no change and no
|
||||
|
||||
Reference in New Issue
Block a user