mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
## Summary - return complete channel rosters instead of truncating at 1,000 members - chunk `event_mentions` inserts inside one transaction so large kind `39002` snapshots remain discoverable by every `p` tag - add a targeted `buzz-admin reconcile-channels --channel <uuid>` force-republish path for stale discovery snapshots - cover a 1,501-member roster, 11,000-tag mention index, and kind `39002` tag construction past member 1,000 ## Why The relay builds NIP-29 discovery and several authorization decisions from `get_members()`, but that helper silently returned only the first 1,000 active members. Desktop then counted the truncated kind `39002` event, while late members could be rejected by roster-scanning member actions. Removing the roster cap exposes PostgreSQL's 65,535 bind-parameter ceiling in mention indexing, so the insert is chunked transactionally to preserve all-or-nothing indexing. The existing reconcilers only fill missing discovery events. The targeted admin option bypasses the separately known 1,000-channel reconciliation-list ceiling and replaces an existing channel snapshot using the configured production relay key. ## Attribution This supersedes and builds on #3166 by @LordMelkor. Thank you for identifying the roster boundary and contributing the original complete-roster and mention-index patch. The production roster/query changes and the two PostgreSQL regressions retain that work's shape; this PR rebases it onto current `main`, adds relay coverage, and adds the targeted repair operation requested for rollout. ## Validation Exact pushed head: `24d02e4f3824150ed84913c9d230e675502e5b12` - `cargo check -p buzz-db -p buzz-admin` - `cargo test -p buzz-db channel::tests::get_members_returns_full_roster_beyond_1000 -- --ignored --exact --nocapture` - `cargo test -p buzz-db feed::tests::insert_mentions_indexes_rosters_past_bind_parameter_cap -- --ignored --exact --nocapture` - `cargo test -p buzz-relay --lib handlers::side_effects::tests::group_members_snapshot_keeps_members_past_one_thousand -- --exact` - `cargo run -q -p buzz-admin -- reconcile-channels --help` - mandatory pre-push hook: branch-skew, desktop checks/typecheck/tests, mobile tests, Rust tests, and desktop Tauri checks all passed on the pushed head ## Rollout 1. Deploy the relay/backend build. 2. Run `buzz-admin reconcile-channels --channel <general-channel-uuid>` with `BUZZ_RELAY_PRIVATE_KEY` configured. 3. Verify the replacement kind `39002` roster count matches the active database membership count. No schema migration or desktop release is required. Fixes #3156 Supersedes #3166 --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
128 lines
5.0 KiB
Bash
Executable File
128 lines
5.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Prove a channel member past the historical 1,000-row boundary is present in
|
|
# relay-served discovery, can write, and survives an authoritative republish.
|
|
set -euo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$REPO_ROOT"
|
|
|
|
: "${DATABASE_URL:?set DATABASE_URL to the isolated relay database}"
|
|
: "${BUZZ_RELAY_URL:=http://localhost:3030}"
|
|
: "${RELAY_URL:=ws://localhost:3030}"
|
|
: "${BUZZ_RELAY_PRIVATE_KEY:=0000000000000000000000000000000000000000000000000000000000000001}"
|
|
export BUZZ_RELAY_URL RELAY_URL BUZZ_RELAY_PRIVATE_KEY
|
|
unset BUZZ_AUTH_TAG
|
|
|
|
for binary in buzz buzz-admin; do
|
|
resolved="$(command -v "$binary" || true)"
|
|
[[ "$resolved" == "$REPO_ROOT/target/release/$binary" ]] || {
|
|
echo "error: $binary must resolve to $REPO_ROOT/target/release/$binary (got ${resolved:-not found})" >&2
|
|
exit 1
|
|
}
|
|
done
|
|
command -v jq >/dev/null || { echo "error: jq is required" >&2; exit 1; }
|
|
command -v psql >/dev/null || { echo "error: psql is required" >&2; exit 1; }
|
|
|
|
key_field() {
|
|
awk -v label="$1" '$1 == label && $2 == "key:" { print $3 }'
|
|
}
|
|
|
|
OWNER_GEN="$(buzz-admin generate-key)"
|
|
OWNER_SK="$(printf '%s\n' "$OWNER_GEN" | key_field Secret)"
|
|
export BUZZ_PRIVATE_KEY="$OWNER_SK"
|
|
|
|
CHANNEL="$(buzz channels create \
|
|
--name "roster-boundary-$$" --type stream --visibility open | jq -er '.channel_id')"
|
|
|
|
LATE_GEN="$(buzz-admin generate-key)"
|
|
LATE_SK="$(printf '%s\n' "$LATE_GEN" | key_field Secret)"
|
|
LATE_PUBKEY="$(printf '%s\n' "$LATE_GEN" | key_field Public)"
|
|
|
|
# The creator is roster position 1. Add 1,499 fixtures followed by the real
|
|
# late identity at position 1,501. A final API-added member forces the relay to
|
|
# emit a fresh discovery snapshot through the normal membership-change path.
|
|
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 \
|
|
--set=channel="$CHANNEL" --set=late_pubkey="$LATE_PUBKEY" <<'SQL'
|
|
WITH target AS (
|
|
SELECT community_id, id
|
|
FROM channels
|
|
WHERE id = :'channel'::uuid
|
|
), fixtures AS (
|
|
SELECT n, decode(lpad(to_hex(n + 65536), 64, '0'), 'hex') AS pubkey
|
|
FROM generate_series(1, 1499) AS n
|
|
)
|
|
INSERT INTO channel_members (community_id, channel_id, pubkey, role, joined_at)
|
|
SELECT target.community_id, target.id, fixtures.pubkey, 'member',
|
|
NOW() + fixtures.n * interval '1 millisecond'
|
|
FROM target CROSS JOIN fixtures;
|
|
|
|
INSERT INTO channel_members (community_id, channel_id, pubkey, role, joined_at)
|
|
SELECT community_id, id, decode(:'late_pubkey', 'hex'), 'member',
|
|
NOW() + interval '2 seconds'
|
|
FROM channels
|
|
WHERE id = :'channel'::uuid;
|
|
SQL
|
|
|
|
TRIGGER_GEN="$(buzz-admin generate-key)"
|
|
TRIGGER_PUBKEY="$(printf '%s\n' "$TRIGGER_GEN" | key_field Public)"
|
|
buzz channels add-member --channel "$CHANNEL" --pubkey "$TRIGGER_PUBKEY" --role member >/dev/null
|
|
|
|
BEFORE="$(buzz channels members --channel "$CHANNEL")"
|
|
BEFORE_COUNT="$(jq 'length' <<<"$BEFORE")"
|
|
jq -e --arg pk "$LATE_PUBKEY" 'any(.[]; .pubkey == $pk and .role == "member")' \
|
|
<<<"$BEFORE" >/dev/null
|
|
(( BEFORE_COUNT > 1000 ))
|
|
printf 'PASS discovery-before-republish channel=%s members=%s late_pubkey=%s\n' \
|
|
"$CHANNEL" "$BEFORE_COUNT" "$LATE_PUBKEY"
|
|
|
|
export BUZZ_PRIVATE_KEY="$LATE_SK"
|
|
ACTION="$(buzz messages send --channel "$CHANNEL" --content "member-1501-action")"
|
|
jq -e '.accepted == true' <<<"$ACTION" >/dev/null
|
|
ACTION_ID="$(jq -er '.event_id' <<<"$ACTION")"
|
|
buzz messages get --channel "$CHANNEL" --limit 10 \
|
|
| jq -e --arg id "$ACTION_ID" 'any(.[]; .id == $id and .content == "member-1501-action")' >/dev/null
|
|
printf 'PASS late-member-action event_id=%s\n' "$ACTION_ID"
|
|
|
|
# Targeted roster repair must not replace canonical metadata or admin events.
|
|
# Preserve both the event ID and complete tags, including fields this backfill
|
|
# command does not know how to rebuild (DM participants, topic, TTL, etc.).
|
|
DISCOVERY_BEFORE="$(psql "$DATABASE_URL" -AtX -v ON_ERROR_STOP=1 \
|
|
--set=channel="$CHANNEL" <<'SQL'
|
|
SELECT jsonb_object_agg(kind::text, jsonb_build_object(
|
|
'id', encode(id, 'hex'),
|
|
'tags', tags
|
|
) ORDER BY kind)::text
|
|
FROM events
|
|
WHERE channel_id = :'channel'::uuid
|
|
AND kind IN (39000, 39001)
|
|
AND deleted_at IS NULL;
|
|
SQL
|
|
)"
|
|
jq -e 'has("39000") and has("39001")' <<<"$DISCOVERY_BEFORE" >/dev/null
|
|
|
|
DATABASE_URL="$DATABASE_URL" RELAY_URL="$RELAY_URL" \
|
|
buzz-admin reconcile-channels --channel "$CHANNEL" >/dev/null
|
|
|
|
DISCOVERY_AFTER="$(psql "$DATABASE_URL" -AtX -v ON_ERROR_STOP=1 \
|
|
--set=channel="$CHANNEL" <<'SQL'
|
|
SELECT jsonb_object_agg(kind::text, jsonb_build_object(
|
|
'id', encode(id, 'hex'),
|
|
'tags', tags
|
|
) ORDER BY kind)::text
|
|
FROM events
|
|
WHERE channel_id = :'channel'::uuid
|
|
AND kind IN (39000, 39001)
|
|
AND deleted_at IS NULL;
|
|
SQL
|
|
)"
|
|
[[ "$DISCOVERY_AFTER" == "$DISCOVERY_BEFORE" ]]
|
|
printf 'PASS targeted-repair-preserves-metadata-and-admin-events channel=%s\n' "$CHANNEL"
|
|
|
|
AFTER="$(buzz channels members --channel "$CHANNEL")"
|
|
AFTER_COUNT="$(jq 'length' <<<"$AFTER")"
|
|
jq -e --arg pk "$LATE_PUBKEY" 'any(.[]; .pubkey == $pk and .role == "member")' \
|
|
<<<"$AFTER" >/dev/null
|
|
(( AFTER_COUNT == BEFORE_COUNT ))
|
|
printf 'PASS discovery-after-republish channel=%s members=%s late_pubkey=%s\n' \
|
|
"$CHANNEL" "$AFTER_COUNT" "$LATE_PUBKEY"
|