block/buzz · error

member p tag

Error message

member p tag

What it means

Identical family to the owner p tag: the test parses the member's role p tag with Tag::parse(["p", hex::encode(member), "", role]). The expect panics when the nostr crate rejects the p tag because the member bytes are not a valid 32-byte pubkey or the tag shape fails that crate version's validation.

Source

Thrown at crates/buzz-db/src/store/channel_members.rs:3175

            "INSERT INTO channel_members (community_id, channel_id, pubkey, role, invited_by) \
             VALUES ($1, $2, $3, 'admin', $4)",
        )
        .bind(community_uuid)
        .bind(channel)
        .bind(member.as_slice())
        .bind(owner.as_slice())
        .execute(&pool)
        .await
        .expect("seed canonical admin");

        let roster = |role: &str, timestamp| {
            EventBuilder::new(Kind::Custom(39002), "")
                .tags(vec![
                    Tag::parse(["d", channel.to_string().as_str()]).expect("d tag"),
                    Tag::parse(["p", hex::encode(owner).as_str(), "", "owner"])
                        .expect("owner p tag"),
                    Tag::parse(["p", hex::encode(member).as_str(), "", role])
                        .expect("member p tag"),
                ])
                .custom_created_at(Timestamp::from(timestamp))
                .sign_with_keys(&relay_keys)
                .expect("sign roster")
        };
        let base = Timestamp::now().as_secs();
        let fresh = roster("admin", base);
        assert!(
            db.replace_addressable_event(community, &fresh, Some(channel))
                .await
                .expect("publish canonical role")
                .1
        );
        let stale = roster("member", base + 1);
        let error = db
            .replace_addressable_event(community, &stale, Some(channel))
            .await
            .expect_err("desired-state fence must reject stale role");

View on GitHub (pinned to dad5a33865)

Solutions

  1. Assert member.len() == 32 and role is non-empty before Tag::parse
  2. Log hex::encode(member) and role in the panic message to diagnose
  3. Upgrade or pin the nostr crate to a version whose Tag::parse accepts the tag shape
  4. Use typed constructors (Tag::public_key / Tag::custom) instead of generic parse

Example fix

// before
Tag::parse(["p", hex::encode(member).as_str(), "", role]).expect("member p tag"),
// after
assert_eq!(member.len(), 32, "member pubkey must be 32 bytes");
assert!(!role.is_empty(), "role must be non-empty");
Tag::parse(["p", hex::encode(member).as_str(), "", role]).expect("member p tag"),
Defensive patterns

Strategy: validation

Validate before calling

assert_eq!(member.len(), 32);
assert!(!role.is_empty(), "role string must be non-empty");

Type guard

fn valid_member_p_tag(pubkey: &[u8], role: &str) -> bool {
    pubkey.len() == 32 && !role.is_empty() && Tag::parse(["p", &hex::encode(pubkey), "", role]).is_ok()
}

Try / catch

let tag = Tag::parse(["p", hex::encode(member).as_str(), "", role])
    .unwrap_or_else(|e| panic!("member p tag invalid (member={}, role={role}): {e}", hex::encode(member)));

Prevention

When it happens

Trigger: Tag::parse errors for ["p", member_hex, "", role] when member is not 32 bytes, is all zeros/empty, or `role` is empty at call time (the closure receives role strings like "owner"/"member"; an empty string may be rejected by stricter versions).

Common situations: member buffer built from wrong-length data; crate upgrade tightened validation of p-tag third/fourth elements; calling the closure with an empty role string in a test case.

Related errors


AI-assisted analysis of block/buzz@dad5a33865 (2026-08-30). Data as JSON: /api/errors/c54bb5ee853f564c. Report an issue: GitHub.