tangle


git clone https://radroots.dev/git/tangle.git
Log | Files | Refs | README | LICENSE

commit d5c68ab256ecddefc836164de764cff7bc005d38
parent 936e8bae39e0e824ecafcb40ff7162e43f3ed45a
Author: triesap <tyson@radroots.org>
Date:   Tue, 16 Jun 2026 14:14:19 -0700

runtime: extract HLL group targets

Diffstat:
Mcrates/tangle_runtime/src/relay/core.rs | 439+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mcrates/tangle_runtime/src/runtime.rs | 260++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Mcrates/tangle_runtime/tests/base_relay_v2.rs | 217+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mcrates/tangle_runtime/tests/phase2_acceptance_targets.rs | 220+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
4 files changed, 968 insertions(+), 168 deletions(-)

diff --git a/crates/tangle_runtime/src/relay/core.rs b/crates/tangle_runtime/src/relay/core.rs @@ -22,8 +22,9 @@ use std::{ collections::BTreeSet, }; use tangle_groups::{ - GroupAuthContext, GroupEventClass, GroupEventView, GroupRuntimeConfig, StoreOffset, - classify_group_event, validate_client_group_event_structure, + GroupAuthContext, GroupEventClass, GroupEventView, GroupId, GroupRuntimeConfig, + NIP29_RELAY_GENERATED_KIND_VALUES, StoreOffset, classify_group_event, + validate_client_group_event_structure, }; #[cfg(test)] use tangle_protocol::{ClientMessage, Event, Filter}; @@ -297,6 +298,26 @@ struct BaseRelayCountHll { suppressed: bool, } +#[derive(Debug, Clone, PartialEq, Eq)] +enum BaseRelayCountHllGroupTargets { + None, + Suppress, + Targets(Vec<GroupId>), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum BaseRelayCountHllTargetPolicy { + Eligible, + Suppress, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum BaseRelayCountHllDTagMode { + Ignore, + Target, + Suppress, +} + impl BaseRelayCountHll { fn new(filters: &[PocketOwnedFilter]) -> Result<Self, BaseRelayError> { let offset = BaseRelay::count_hll_offset(filters)?; @@ -1655,6 +1676,119 @@ impl BaseRelay { || group.metadata().hidden()) } + fn count_hll_filter_target_policy( + groups: Option<&GroupServiceHandle>, + filter: &PocketFilter, + ) -> BaseRelayCountHllTargetPolicy { + let Some(groups) = groups else { + return if Self::count_hll_filter_has_group_target(filter) { + BaseRelayCountHllTargetPolicy::Suppress + } else { + BaseRelayCountHllTargetPolicy::Eligible + }; + }; + match Self::count_hll_group_targets( + filter, + usize::from(groups.limits().max_group_id_bytes()), + ) { + BaseRelayCountHllGroupTargets::None => BaseRelayCountHllTargetPolicy::Eligible, + BaseRelayCountHllGroupTargets::Suppress => BaseRelayCountHllTargetPolicy::Suppress, + BaseRelayCountHllGroupTargets::Targets(group_ids) => { + let projection = groups.projection(); + if group_ids.iter().all(|group_id| { + projection.group(group_id).is_some_and(|group| { + projection.tombstone(group_id).is_none() + && !group.metadata().private() + && !group.metadata().hidden() + }) + }) { + BaseRelayCountHllTargetPolicy::Eligible + } else { + BaseRelayCountHllTargetPolicy::Suppress + } + } + } + } + + fn count_hll_group_targets( + filter: &PocketFilter, + max_group_id_bytes: usize, + ) -> BaseRelayCountHllGroupTargets { + let Ok(tags) = filter.tags() else { + return BaseRelayCountHllGroupTargets::Suppress; + }; + let d_tag_mode = Self::count_hll_filter_d_tag_mode(filter); + let mut group_ids = Vec::new(); + for tag in tags.iter() { + let mut values = tag.into_iter(); + let Some(name) = values.next() else { + continue; + }; + if name == b"d" { + match d_tag_mode { + BaseRelayCountHllDTagMode::Ignore => continue, + BaseRelayCountHllDTagMode::Suppress => { + return BaseRelayCountHllGroupTargets::Suppress; + } + BaseRelayCountHllDTagMode::Target => {} + } + } else if name != b"h" { + continue; + } + let mut found_value = false; + for value in values { + found_value = true; + let Ok(value) = std::str::from_utf8(value) else { + return BaseRelayCountHllGroupTargets::Suppress; + }; + let Ok(group_id) = GroupId::new_with_max_bytes(value, max_group_id_bytes) else { + return BaseRelayCountHllGroupTargets::Suppress; + }; + group_ids.push(group_id); + } + if !found_value { + return BaseRelayCountHllGroupTargets::Suppress; + } + } + if group_ids.is_empty() { + BaseRelayCountHllGroupTargets::None + } else { + group_ids.sort(); + group_ids.dedup(); + BaseRelayCountHllGroupTargets::Targets(group_ids) + } + } + + fn count_hll_filter_has_group_target(filter: &PocketFilter) -> bool { + let Ok(tags) = filter.tags() else { + return true; + }; + let d_tag_mode = Self::count_hll_filter_d_tag_mode(filter); + tags.iter().any(|tag| { + let mut values = tag.into_iter(); + let name = values.next(); + matches!(name, Some(b"h")) + || (matches!( + d_tag_mode, + BaseRelayCountHllDTagMode::Target | BaseRelayCountHllDTagMode::Suppress + ) && matches!(name, Some(b"d"))) + }) + } + + fn count_hll_filter_d_tag_mode(filter: &PocketFilter) -> BaseRelayCountHllDTagMode { + if filter.num_kinds() == 0 { + return BaseRelayCountHllDTagMode::Suppress; + } + if filter + .kinds() + .any(|kind| NIP29_RELAY_GENERATED_KIND_VALUES.contains(&u32::from(kind.as_u16()))) + { + BaseRelayCountHllDTagMode::Target + } else { + BaseRelayCountHllDTagMode::Ignore + } + } + fn query_filter_events_report_with_services( store: &PocketStoreHandle, groups: Option<&GroupServiceHandle>, @@ -1778,7 +1912,10 @@ fn pocket_filters_are_complete(filters: &[PocketOwnedFilter]) -> bool { #[cfg(test)] mod tests { - use super::{BaseRelay, BaseRelayLimitSettings, BaseRelayLimits, NEGENTROPY_DISABLED_MESSAGE}; + use super::{ + BaseRelay, BaseRelayCountHllTargetPolicy, BaseRelayLimitSettings, BaseRelayLimits, + NEGENTROPY_DISABLED_MESSAGE, + }; use crate::pocket_conversion::{tangle_event_to_pocket, tangle_filter_to_pocket}; use crate::relay::auth::BaseAuthState; use crate::relay::live::CloseResult; @@ -1792,8 +1929,8 @@ mod tests { parse_group_runtime_config_json, }; use tangle_protocol::{ - ClientMessage, Event, EventId, Filter, Kind, PublicKeyHex, RelayMessage, SubscriptionId, - Tag, UnixTimestamp, UnsignedEvent, filter_from_value, + ClientMessage, Event, EventId, Filter, Kind, PublicKeyHex, RelayMessage, SignatureHex, + SubscriptionId, Tag, UnixTimestamp, UnsignedEvent, filter_from_value, }; use tangle_store_pocket::{ PocketEvent, PocketKind, PocketOwnedEvent, PocketOwnedFilter, PocketOwnedTags, @@ -2663,11 +2800,11 @@ mod tests { let relay = test_relay("base-relay-count-hll-public", 8); let target = "a".repeat(EventId::HEX_LENGTH); let target_tag = Tag::from_parts("e", &[&target]).expect("tag"); - let first = signed_public_event(7, 7, vec![target_tag.clone()], "first reaction"); - let second = signed_public_event(8, 7, vec![target_tag], "second reaction"); + let first = signed_pocket_public_event(7, 7, vec![target_tag.clone()], "first reaction"); + let second = signed_pocket_public_event(8, 7, vec![target_tag], "second reaction"); for event in [&first, &second] { - assert_accepted(relay.handle_event(event.clone()).expect("event"), event); + assert_pocket_accepted(relay.handle_pocket_event(event).expect("event"), event); } let RelayMessage::Count { count, hll, .. } = relay @@ -2701,61 +2838,62 @@ mod tests { ); let target = "b".repeat(EventId::HEX_LENGTH); let target_tag = Tag::from_parts("e", &[&target]).expect("tag"); - let public = signed_public_event(8, 7, vec![target_tag.clone()], "public reaction"); + let public = signed_pocket_public_event(8, 7, vec![target_tag.clone()], "public reaction"); - assert_accepted(relay.handle_event(public.clone()).expect("public"), &public); - let private_create = signed_private_group_create_event(7, "PrivateHll"); - assert_accepted( + assert_pocket_accepted(relay.handle_pocket_event(&public).expect("public"), &public); + let private_create = signed_pocket_private_group_create_event(7, "PrivateHll"); + assert_pocket_accepted( relay - .handle_event_with_auth(private_create.clone(), &owner_auth) + .handle_pocket_event_with_auth(&private_create, &owner_auth) .expect("private create"), &private_create, ); - let private = signed_event_at( + let private = signed_pocket_event_at_tags( 7, 7, vec![h("PrivateHll"), target_tag.clone()], "private reaction", 1_714_124_434, ); - assert_accepted( + assert_pocket_accepted( relay - .handle_event_with_auth(private.clone(), &owner_auth) + .handle_pocket_event_with_auth(&private, &owner_auth) .expect("private reaction"), &private, ); - let hidden_create = - signed_group_create_event_with_tags(7, "HiddenHll", vec![hidden()], 1_714_124_435); - assert_accepted( + let hidden_create = signed_pocket_group_create_event_with_tags( + 7, + "HiddenHll", + vec![hidden()], + 1_714_124_435, + ); + assert_pocket_accepted( relay - .handle_event_with_auth(hidden_create.clone(), &owner_auth) + .handle_pocket_event_with_auth(&hidden_create, &owner_auth) .expect("hidden create"), &hidden_create, ); - let hidden = signed_event_at( + let hidden = signed_pocket_event_at_tags( 7, 7, vec![h("HiddenHll"), target_tag.clone()], "hidden reaction", 1_714_124_436, ); - assert_accepted( + assert_pocket_accepted( relay - .handle_event_with_auth(hidden.clone(), &owner_auth) + .handle_pocket_event_with_auth(&hidden, &owner_auth) .expect("hidden reaction"), &hidden, ); - let unknown = signed_event_at( + let unknown = signed_pocket_event_at_tags( 7, 7, vec![h("UnknownHll"), target_tag.clone()], "unknown reaction", 1_714_124_437, ); - relay - .store - .store_event(&tangle_event_to_pocket(&unknown).expect("unknown pocket")) - .expect("store unknown"); + relay.store.store_event(&unknown).expect("store unknown"); let authorized_private = relay .handle_count_with_auth_protocol( @@ -2839,6 +2977,130 @@ mod tests { } #[test] + fn base_relay_count_hll_group_target_policy_classifies_h_and_d_targets() { + let owner = signer(7).public_key().clone(); + let owner_auth = authenticated_state(7); + let relay = test_relay_with_groups( + "base-relay-count-hll-target-policy", + 8, + &enabled_groups_for_owner(&owner), + ); + for event in [ + signed_pocket_group_create_event(7, "PublicHll"), + signed_pocket_group_create_event(7, "SecondHll"), + signed_pocket_private_group_create_event(7, "PrivateHll"), + signed_pocket_group_create_event_with_tags( + 7, + "HiddenHll", + vec![hidden()], + 1_714_124_435, + ), + signed_pocket_group_create_event(7, "DeletedHll"), + ] { + assert_pocket_accepted( + relay + .handle_pocket_event_with_auth(&event, &owner_auth) + .expect("group create"), + &event, + ); + } + let delete_group = signed_pocket_event_at_tags( + 7, + KIND_GROUP_DELETE_GROUP.into(), + vec![h("DeletedHll")], + "", + 1_714_124_436, + ); + assert_pocket_accepted( + relay + .handle_pocket_event_with_auth(&delete_group, &owner_auth) + .expect("delete group"), + &delete_group, + ); + + assert_eq!( + hll_target_policy(&relay, serde_json::json!({"kinds":[7],"#h":["PublicHll"]})), + BaseRelayCountHllTargetPolicy::Eligible + ); + assert_eq!( + hll_target_policy( + &relay, + serde_json::json!({"kinds":[7],"#h":["PublicHll","SecondHll"]}) + ), + BaseRelayCountHllTargetPolicy::Eligible + ); + assert_eq!( + hll_target_policy( + &relay, + serde_json::json!({"kinds":[7],"#h":["PublicHll","PrivateHll"]}) + ), + BaseRelayCountHllTargetPolicy::Suppress + ); + assert_eq!( + hll_target_policy(&relay, serde_json::json!({"kinds":[7],"#h":["HiddenHll"]})), + BaseRelayCountHllTargetPolicy::Suppress + ); + assert_eq!( + hll_target_policy(&relay, serde_json::json!({"kinds":[7],"#h":["DeletedHll"]})), + BaseRelayCountHllTargetPolicy::Suppress + ); + assert_eq!( + hll_target_policy(&relay, serde_json::json!({"kinds":[7],"#h":["UnknownHll"]})), + BaseRelayCountHllTargetPolicy::Suppress + ); + assert_eq!( + hll_target_policy(&relay, serde_json::json!({"kinds":[7],"#h":[""]})), + BaseRelayCountHllTargetPolicy::Suppress + ); + assert_eq!( + hll_target_policy( + &relay, + serde_json::json!({"kinds":[KIND_GROUP_METADATA],"#d":["PublicHll"]}) + ), + BaseRelayCountHllTargetPolicy::Eligible + ); + assert_eq!( + hll_target_policy( + &relay, + serde_json::json!({"kinds":[KIND_GROUP_METADATA],"#d":["PrivateHll"]}) + ), + BaseRelayCountHllTargetPolicy::Suppress + ); + assert_eq!( + hll_target_policy(&relay, serde_json::json!({"#d":["PublicHll"]})), + BaseRelayCountHllTargetPolicy::Suppress + ); + assert_eq!( + hll_target_policy( + &relay, + serde_json::json!({"kinds":[30023],"#d":["PrivateHll"]}) + ), + BaseRelayCountHllTargetPolicy::Eligible + ); + } + + #[test] + fn base_relay_count_hll_group_target_policy_suppresses_unresolved_group_targets() { + let relay = test_relay("base-relay-count-hll-target-policy-no-groups", 8); + + assert_eq!( + hll_target_policy(&relay, serde_json::json!({"kinds":[7],"#h":["PublicHll"]})), + BaseRelayCountHllTargetPolicy::Suppress + ); + assert_eq!( + hll_target_policy(&relay, serde_json::json!({"#d":["PublicHll"]})), + BaseRelayCountHllTargetPolicy::Suppress + ); + assert_eq!( + hll_target_policy( + &relay, + serde_json::json!({"kinds":[30023],"#d":["PublicHll"]}) + ), + BaseRelayCountHllTargetPolicy::Eligible + ); + } + + #[test] fn base_relay_count_does_not_apply_default_or_client_limits() { let config = test_store_config("base-relay-count-no-default-limit"); let relay = BaseRelay::open( @@ -4257,7 +4519,7 @@ mod tests { } fn signed_auth_event(secret_byte: u8, challenge: &str, created_at: u64) -> Event { - signed_event_at( + signed_tangle_event_at( secret_byte, 22_242, vec![ @@ -4301,6 +4563,68 @@ mod tests { .expect("pocket event") } + fn signed_pocket_public_event( + secret_byte: u8, + kind: u32, + tags: Vec<Tag>, + content: &str, + ) -> PocketOwnedEvent { + signed_pocket_event_at_tags(secret_byte, kind, tags, content, 1_714_124_433) + } + + fn signed_pocket_group_create_event(secret_byte: u8, group_id: &str) -> PocketOwnedEvent { + signed_pocket_group_create_event_with_tags(secret_byte, group_id, Vec::new(), 1_714_124_433) + } + + fn signed_pocket_group_create_event_with_tags( + secret_byte: u8, + group_id: &str, + mut extra_tags: Vec<Tag>, + created_at: u64, + ) -> PocketOwnedEvent { + let mut tags = vec![h(group_id), name(group_id)]; + tags.append(&mut extra_tags); + signed_pocket_event_at_tags(secret_byte, KIND_GROUP_CREATE_GROUP, tags, "", created_at) + } + + fn signed_pocket_private_group_create_event( + secret_byte: u8, + group_id: &str, + ) -> PocketOwnedEvent { + signed_pocket_event_at_tags( + secret_byte, + KIND_GROUP_CREATE_GROUP, + vec![h(group_id), name(group_id), private()], + "", + 1_714_124_433, + ) + } + + fn signed_pocket_event_at_tags( + secret_byte: u8, + kind: u32, + tags: Vec<Tag>, + content: &str, + created_at: u64, + ) -> PocketOwnedEvent { + let tags = pocket_tags_from_protocol(&tags); + signed_pocket_event_at( + secret_byte, + u16::try_from(kind).expect("pocket kind"), + &tags, + content.as_bytes(), + created_at, + ) + } + + fn pocket_tags_from_protocol(tags: &[Tag]) -> PocketOwnedTags { + let parts = tags + .iter() + .map(|tag| tag.values().iter().map(String::as_str).collect::<Vec<_>>()) + .collect::<Vec<_>>(); + PocketOwnedTags::new(&parts).expect("pocket tags") + } + fn signed_group_create_event(secret_byte: u8, group_id: &str) -> Event { signed_group_create_event_with_tags(secret_byte, group_id, Vec::new(), 1_714_124_433) } @@ -4339,7 +4663,24 @@ mod tests { content: &str, created_at: u64, ) -> Event { - let secret = format!("{:02x}", secret_byte).repeat(32); + let pocket = signed_pocket_event_at_tags( + secret_byte, + u32::try_from(kind).expect("kind"), + tags, + content, + created_at, + ); + pocket_event_to_protocol(&pocket) + } + + fn signed_tangle_event_at( + secret_byte: u8, + kind: u64, + tags: Vec<Tag>, + content: &str, + created_at: u64, + ) -> Event { + let secret = format!("{secret_byte:02x}").repeat(32); let signer = RelaySigner::from_secret_hex(&secret).expect("signer"); let unsigned = UnsignedEvent::new( signer.public_key().clone(), @@ -4355,6 +4696,32 @@ mod tests { EventId::new(&event.id().as_hex_string()).expect("event id") } + fn pocket_event_to_protocol(event: &PocketEvent) -> Event { + let tags = event + .tags() + .expect("tags") + .iter() + .map(|tag| { + Tag::new( + tag.map(|value| std::str::from_utf8(value).expect("utf8").to_owned()) + .collect::<Vec<_>>(), + ) + .expect("tag") + }) + .collect::<Vec<_>>(); + Event::new( + pocket_event_id(event), + tangle_protocol::UnsignedEvent::new( + PublicKeyHex::new(&event.pubkey().as_hex_string()).expect("pubkey"), + UnixTimestamp::new(event.created_at().as_u64()), + tangle_protocol::Kind::new(u64::from(event.kind().as_u16())).expect("kind"), + tags, + std::str::from_utf8(event.content()).expect("content"), + ), + SignatureHex::new(&event.sig().to_string()).expect("sig"), + ) + } + fn authenticated_state(secret_byte: u8) -> BaseAuthState { let mut auth = BaseAuthState::new("wss://relay.radroots.test", 60, 600).expect("auth state"); @@ -4449,6 +4816,18 @@ mod tests { filter_from_value(&value).expect("filter") } + fn pocket_filter_from_value(value: serde_json::Value) -> PocketOwnedFilter { + tangle_filter_to_pocket(&filter_from_value(&value).expect("filter")).expect("pocket") + } + + fn hll_target_policy( + relay: &BaseRelay, + value: serde_json::Value, + ) -> BaseRelayCountHllTargetPolicy { + let filter = pocket_filter_from_value(value); + BaseRelay::count_hll_filter_target_policy(relay.groups.as_ref(), &filter) + } + fn assert_accepted(message: RelayMessage, event: &Event) { assert_eq!( message, diff --git a/crates/tangle_runtime/src/runtime.rs b/crates/tangle_runtime/src/runtime.rs @@ -2084,9 +2084,9 @@ impl Default for TangleShutdownSignal { #[cfg(test)] mod tests { use super::{ - BROAD_QUERY_TIME_WINDOW_SECONDS, TangleBroadQueryReason, TangleClientRateLimitContext, - TangleQueryClassification, TangleQueryClassifier, TangleRuntime, TangleRuntimeHandle, - TangleRuntimeLimits, + BROAD_QUERY_TIME_WINDOW_SECONDS, RuntimeClientMessage, TangleBroadQueryReason, + TangleClientRateLimitContext, TangleQueryClassification, TangleQueryClassifier, + TangleRuntime, TangleRuntimeHandle, TangleRuntimeLimits, }; use crate::config::{BaseRelayRuntimeConfig, parse_base_relay_runtime_config_json}; use crate::event_bus::{TangleEventBus, TangleEventReceiveError, TangleEventReceiver}; @@ -2102,15 +2102,20 @@ mod tests { path::{Path, PathBuf}, time::Duration, }; + use tangle_crypto::RelaySigner; use tangle_groups::{ CanonicalGroupEvent, GroupEventClass, GroupId, GroupProjection, KIND_GROUP_ADMINS, - KIND_GROUP_DELETE_GROUP, KIND_GROUP_JOIN_REQUEST, KIND_GROUP_MEMBERS, KIND_GROUP_METADATA, - MemberStatus, StoreOffset, rebuild_group_projection, + KIND_GROUP_CREATE_GROUP, KIND_GROUP_DELETE_GROUP, KIND_GROUP_JOIN_REQUEST, + KIND_GROUP_MEMBERS, KIND_GROUP_METADATA, KIND_GROUP_PUT_USER, MemberStatus, StoreOffset, + rebuild_group_projection, }; use tangle_protocol::{ ClientMessage, Event, EventId, Filter, Kind, PublicKeyHex, RelayMessage, SubscriptionId, Tag, UnixTimestamp, filter_from_value, }; + use tangle_store_pocket::{ + PocketEvent, PocketKind, PocketOwnedEvent, PocketOwnedTags, PocketTime, + }; use tangle_test_support::{ FixtureKey, tangle_v2_auth_event, tangle_v2_delete_group_event, tangle_v2_event, tangle_v2_group_create_event, tangle_v2_group_event, tangle_v2_join_event, @@ -3256,29 +3261,16 @@ mod tests { ); let mut auth = handle.auth_state().await.expect("auth"); let target = "c".repeat(64); - let first = tangle_v2_event( - FixtureKey::Member, - 1_714_124_433, - 7, - vec![Tag::from_parts("e", &[&target]).expect("tag")], - "first reaction", - ) - .expect("first"); - let second = tangle_v2_event( - FixtureKey::Admin, - 1_714_124_434, - 7, - vec![Tag::from_parts("e", &[&target]).expect("tag")], - "second reaction", - ) - .expect("second"); + let tags = PocketOwnedTags::new(&[["e", target.as_str()]]).expect("tags"); + let first = signed_pocket_event(12, 1_714_124_433, 7, &tags, b"first reaction"); + let second = signed_pocket_event(11, 1_714_124_434, 7, &tags, b"second reaction"); - assert_accepted_reply( - runtime_event_reply(&handle, first.clone(), &mut auth, 1_714_124_435).await, + assert_accepted_pocket_reply( + runtime_pocket_event_reply(&handle, &first, &mut auth), &first, ); - assert_accepted_reply( - runtime_event_reply(&handle, second.clone(), &mut auth, 1_714_124_436).await, + assert_accepted_pocket_reply( + runtime_pocket_event_reply(&handle, &second, &mut auth), &second, ); @@ -4104,63 +4096,61 @@ mod tests { owner_auth .issue_challenge("owner-stress", UnixTimestamp::new(base_time)) .expect("owner challenge"); - let owner_auth_event = tangle_v2_auth_event(FixtureKey::Owner, "owner-stress", base_time) - .expect("owner auth event"); + let owner_auth_event = + runtime_pocket_auth_event(FixtureKey::Owner, "owner-stress", base_time); assert_eq!( handle - .handle_protocol_client_message_for_test( - ClientMessage::Auth(owner_auth_event.clone()), + .handle_client_message( + RuntimeClientMessage::Auth(owner_auth_event.clone()), &mut owner_auth, UnixTimestamp::new(base_time) ) .await .expect("owner auth"), vec![RelayMessage::Ok { - event_id: owner_auth_event.id().clone(), + event_id: runtime_pocket_event_id(&owner_auth_event), accepted: true, message: String::new() }] ); - let create = tangle_v2_group_create_event( + let create = runtime_pocket_group_create_event( FixtureKey::Owner, "StressPrivate", base_time + 1, &["private"], - ) - .expect("create"); + ); assert_eq!( handle - .handle_protocol_client_message_for_test( - ClientMessage::Event(create.clone()), + .handle_client_message( + RuntimeClientMessage::Event(create.clone()), &mut owner_auth, UnixTimestamp::new(base_time + 1) ) .await .expect("create"), vec![RelayMessage::Ok { - event_id: create.id().clone(), + event_id: runtime_pocket_event_id(&create), accepted: true, message: String::new() }] ); - let put_member = tangle_v2_put_user_event( + let put_member = runtime_pocket_put_user_event( FixtureKey::Owner, "StressPrivate", FixtureKey::Member, base_time + 2, - ) - .expect("put member"); + ); assert_eq!( handle - .handle_protocol_client_message_for_test( - ClientMessage::Event(put_member.clone()), + .handle_client_message( + RuntimeClientMessage::Event(put_member.clone()), &mut owner_auth, UnixTimestamp::new(base_time + 2) ) .await .expect("put member"), vec![RelayMessage::Ok { - event_id: put_member.id().clone(), + event_id: runtime_pocket_event_id(&put_member), accepted: true, message: String::new() }] @@ -4170,19 +4160,18 @@ mod tests { .issue_challenge("member-stress", UnixTimestamp::new(base_time + 3)) .expect("member challenge"); let member_auth_event = - tangle_v2_auth_event(FixtureKey::Member, "member-stress", base_time + 3) - .expect("member auth event"); + runtime_pocket_auth_event(FixtureKey::Member, "member-stress", base_time + 3); assert_eq!( handle - .handle_protocol_client_message_for_test( - ClientMessage::Auth(member_auth_event.clone()), + .handle_client_message( + RuntimeClientMessage::Auth(member_auth_event.clone()), &mut member_auth, UnixTimestamp::new(base_time + 3) ) .await .expect("member auth"), vec![RelayMessage::Ok { - event_id: member_auth_event.id().clone(), + event_id: runtime_pocket_event_id(&member_auth_event), accepted: true, message: String::new() }] @@ -4196,18 +4185,17 @@ mod tests { let handle = handle.clone(); let mut auth = member_auth.clone(); write_tasks.push(tokio::spawn(async move { - let event = tangle_v2_group_event( + let event = runtime_pocket_group_event( FixtureKey::Member, "StressPrivate", base_time + 10 + u64::try_from(index).expect("index"), 1, &format!("private stress {index}"), - ) - .expect("group event"); + ); assert_eq!( handle - .handle_protocol_client_message_for_test( - ClientMessage::Event(event.clone()), + .handle_client_message( + RuntimeClientMessage::Event(event.clone()), &mut auth, UnixTimestamp::new( base_time + 10 + u64::try_from(index).expect("index") @@ -4216,30 +4204,29 @@ mod tests { .await .expect("group write"), vec![RelayMessage::Ok { - event_id: event.id().clone(), + event_id: runtime_pocket_event_id(&event), accepted: true, message: String::new() }] ); - (true, event) + (true, runtime_pocket_event_id(&event)) })); } for index in 0..public_write_count { let handle = handle.clone(); let mut auth = public_auth.clone(); write_tasks.push(tokio::spawn(async move { - let event = tangle_v2_event( + let event = runtime_pocket_event( FixtureKey::Admin, base_time + 40 + u64::try_from(index).expect("index"), 1, Vec::new(), &format!("public stress {index}"), - ) - .expect("public event"); + ); assert_eq!( handle - .handle_protocol_client_message_for_test( - ClientMessage::Event(event.clone()), + .handle_client_message( + RuntimeClientMessage::Event(event.clone()), &mut auth, UnixTimestamp::new( base_time + 40 + u64::try_from(index).expect("index") @@ -4248,12 +4235,12 @@ mod tests { .await .expect("public write"), vec![RelayMessage::Ok { - event_id: event.id().clone(), + event_id: runtime_pocket_event_id(&event), accepted: true, message: String::new() }] ); - (false, event) + (false, runtime_pocket_event_id(&event)) })); } let stored_events = tokio::time::timeout(Duration::from_secs(3), async { @@ -4282,7 +4269,7 @@ mod tests { let group_event_ids = stored_events .iter() .filter(|(is_group, _)| *is_group) - .map(|(_, event)| event.id().clone()) + .map(|(_, event_id)| event_id.clone()) .collect::<BTreeSet<_>>(); let mut published_offsets = Vec::new(); for _ in 0..stored_events.len() { @@ -4681,6 +4668,18 @@ mod tests { replies.into_iter().next().expect("reply") } + fn runtime_pocket_event_reply( + handle: &TangleRuntimeHandle, + event: &PocketEvent, + auth: &mut BaseAuthState, + ) -> RelayMessage { + handle + .inner + .handle_pocket_event_with_auth_report(event, auth) + .expect("event message") + .into_message() + } + async fn runtime_group_count( handle: &TangleRuntimeHandle, subscription_id: &str, @@ -4773,6 +4772,21 @@ mod tests { ); } + fn assert_accepted_pocket_reply(reply: RelayMessage, event: &PocketEvent) { + assert_eq!( + reply, + RelayMessage::Ok { + event_id: runtime_pocket_event_id(event), + accepted: true, + message: String::new() + } + ); + } + + fn runtime_pocket_event_id(event: &PocketEvent) -> EventId { + EventId::new(&event.id().as_hex_string()).expect("event id") + } + fn assert_runtime_member_status( handle: &TangleRuntimeHandle, group_id: &str, @@ -4876,6 +4890,128 @@ mod tests { crate::pocket_conversion::tangle_filter_to_pocket(&filter).expect("pocket filter") } + fn runtime_pocket_group_create_event( + key: FixtureKey, + group_id: &str, + created_at: u64, + flags: &[&str], + ) -> PocketOwnedEvent { + let mut tags = vec![ + Tag::from_parts("h", &[group_id]).expect("h"), + Tag::from_parts("name", &[group_id]).expect("name"), + ]; + for flag in flags { + tags.push(Tag::from_parts(flag, &[]).expect("flag")); + } + runtime_pocket_event(key, created_at, KIND_GROUP_CREATE_GROUP.into(), tags, "") + } + + fn runtime_pocket_auth_event( + key: FixtureKey, + challenge: &str, + created_at: u64, + ) -> PocketOwnedEvent { + runtime_pocket_event( + key, + created_at, + 22_242, + vec![ + Tag::from_parts("relay", &["wss://relay.radroots.test"]).expect("relay"), + Tag::from_parts("challenge", &[challenge]).expect("challenge"), + ], + "", + ) + } + + fn runtime_pocket_put_user_event( + key: FixtureKey, + group_id: &str, + target: FixtureKey, + created_at: u64, + ) -> PocketOwnedEvent { + let target_pubkey = target.public_key(); + runtime_pocket_event( + key, + created_at, + KIND_GROUP_PUT_USER.into(), + vec![ + Tag::from_parts("h", &[group_id]).expect("h"), + Tag::from_parts("p", &[target_pubkey.as_str()]).expect("p"), + ], + "", + ) + } + + fn runtime_pocket_group_event( + key: FixtureKey, + group_id: &str, + created_at: u64, + kind: u64, + content: &str, + ) -> PocketOwnedEvent { + runtime_pocket_event( + key, + created_at, + kind, + vec![Tag::from_parts("h", &[group_id]).expect("h")], + content, + ) + } + + fn runtime_pocket_event( + key: FixtureKey, + created_at: u64, + kind: u64, + tags: Vec<Tag>, + content: &str, + ) -> PocketOwnedEvent { + let tags = pocket_tags_from_protocol(&tags); + signed_pocket_event( + fixture_secret_byte(key), + created_at, + u16::try_from(kind).expect("pocket kind"), + &tags, + content.as_bytes(), + ) + } + + fn pocket_tags_from_protocol(tags: &[Tag]) -> PocketOwnedTags { + let parts = tags + .iter() + .map(|tag| tag.values().iter().map(String::as_str).collect::<Vec<_>>()) + .collect::<Vec<_>>(); + PocketOwnedTags::new(&parts).expect("pocket tags") + } + + fn fixture_secret_byte(key: FixtureKey) -> u8 { + match key { + FixtureKey::Relay => 9, + FixtureKey::Admin => 11, + FixtureKey::Member => 12, + FixtureKey::Outsider => 13, + FixtureKey::Owner => 10, + } + } + + fn signed_pocket_event( + secret_byte: u8, + created_at: u64, + kind: u16, + tags: &PocketOwnedTags, + content: &[u8], + ) -> PocketOwnedEvent { + let secret = format!("{secret_byte:02x}").repeat(32); + RelaySigner::from_secret_hex(&secret) + .expect("signer") + .sign_pocket_event( + PocketKind::from_u16(kind), + tags, + PocketTime::from_u64(created_at), + content, + ) + .expect("pocket event") + } + fn temp_root(name: &str) -> PathBuf { std::env::temp_dir().join(format!("tangle-runtime-{name}-{}", std::process::id())) } diff --git a/crates/tangle_runtime/tests/base_relay_v2.rs b/crates/tangle_runtime/tests/base_relay_v2.rs @@ -5,16 +5,16 @@ use tangle_crypto::{RelaySigner, event_id_matches, verify_event_signature}; use tangle_groups::{ GroupAuthContext, GroupAuthority, GroupGeneratedEventBuilder, GroupId, GroupLimitsConfig, GroupOutboxEffect, GroupOutboxKey, GroupOutboxRecord, GroupOutboxStatus, GroupProjection, - GroupRuntimeConfig, KIND_GROUP_ADMINS, KIND_GROUP_DELETE_GROUP, KIND_GROUP_JOIN_REQUEST, - KIND_GROUP_LEAVE_REQUEST, KIND_GROUP_MEMBERS, KIND_GROUP_METADATA, KIND_GROUP_PUT_USER, - MemberStatus, NIP29_RELAY_GENERATED_KIND_VALUES, PERMANENT_RELAY_OVERRIDE_ROLE, - ProjectionCheckpoint, StoreOffset, member_current_key, parse_group_runtime_config_json, - projection_checkpoint_key, + GroupRuntimeConfig, KIND_GROUP_ADMINS, KIND_GROUP_CREATE_GROUP, KIND_GROUP_DELETE_GROUP, + KIND_GROUP_JOIN_REQUEST, KIND_GROUP_LEAVE_REQUEST, KIND_GROUP_MEMBERS, KIND_GROUP_METADATA, + KIND_GROUP_PUT_USER, MemberStatus, NIP29_RELAY_GENERATED_KIND_VALUES, + PERMANENT_RELAY_OVERRIDE_ROLE, ProjectionCheckpoint, StoreOffset, member_current_key, + parse_group_runtime_config_json, projection_checkpoint_key, }; use tangle_protocol::{ - Event, Filter, PublicKeyHex, RawEventJson, RelayMessage, SubscriptionId, Tag, UnixTimestamp, - event_from_value, event_to_value, filter_from_value, filter_to_value, parse_client_message, - parse_event_json, + Event, EventId, Filter, Kind, PublicKeyHex, RawEventJson, RelayMessage, SignatureHex, + SubscriptionId, Tag, UnixTimestamp, UnsignedEvent, event_from_value, event_to_value, + filter_from_value, filter_to_value, parse_client_message, parse_event_json, }; use tangle_runtime::{ config::{BaseRelayRuntimeConfig, parse_base_relay_runtime_config_json}, @@ -29,9 +29,10 @@ use tangle_runtime::{ }, }; use tangle_store_pocket::{ - PocketOwnedEvent, PocketQueryConfig, PocketStoreConfig, PocketStoreHandle, PocketSyncPolicy, - TANGLE_GROUP_CHECKPOINT_TABLE, TANGLE_GROUP_OUTBOX_TABLE, TANGLE_GROUP_PROJECTION_TABLE, - parse_pocket_event_json, parse_pocket_filter_json, + PocketKind, PocketOwnedEvent, PocketOwnedTags, PocketQueryConfig, PocketStoreConfig, + PocketStoreHandle, PocketSyncPolicy, PocketTime, TANGLE_GROUP_CHECKPOINT_TABLE, + TANGLE_GROUP_OUTBOX_TABLE, TANGLE_GROUP_PROJECTION_TABLE, parse_pocket_event_json, + parse_pocket_filter_json, }; use tangle_test_support::{ FixtureKey, TANGLE_V2_RELAY_SECRET_HEX, TANGLE_V2_RELAY_URL, tangle_v2_auth_event, @@ -235,16 +236,151 @@ fn pocket_event_for_test(event: &Event) -> PocketOwnedEvent { parse_pocket_event_json(&raw).expect("pocket event") } +fn pocket_protocol_event( + key: FixtureKey, + created_at: u64, + kind: u64, + tags: Vec<Tag>, + content: &str, +) -> Event { + let tags = pocket_tags_from_protocol(&tags); + let pocket = signed_pocket_event( + fixture_secret_byte(key), + created_at, + u16::try_from(kind).expect("pocket kind"), + &tags, + content.as_bytes(), + ); + pocket_event_to_protocol(&pocket) +} + +fn pocket_protocol_auth_event(key: FixtureKey, challenge: &str, created_at: u64) -> Event { + pocket_protocol_event( + key, + created_at, + 22_242, + vec![ + Tag::from_parts("relay", &[TANGLE_V2_RELAY_URL]).expect("relay"), + Tag::from_parts("challenge", &[challenge]).expect("challenge"), + ], + "", + ) +} + +fn pocket_protocol_group_create_event( + key: FixtureKey, + group_id: &str, + created_at: u64, + flags: &[&str], +) -> Event { + let mut tags = vec![ + tangle_v2_group_tag(group_id).expect("group"), + tangle_v2_tag("name", &[group_id]).expect("name"), + ]; + for flag in flags { + tags.push(tangle_v2_tag(flag, &[]).expect("flag")); + } + pocket_protocol_event(key, created_at, KIND_GROUP_CREATE_GROUP.into(), tags, "") +} + +fn pocket_protocol_group_event( + key: FixtureKey, + group_id: &str, + created_at: u64, + kind: u64, + content: &str, +) -> Event { + pocket_protocol_event( + key, + created_at, + kind, + vec![tangle_v2_group_tag(group_id).expect("group")], + content, + ) +} + +fn pocket_protocol_join_event(key: FixtureKey, group_id: &str, created_at: u64) -> Event { + pocket_protocol_group_event( + key, + group_id, + created_at, + KIND_GROUP_JOIN_REQUEST.into(), + "", + ) +} + +fn signed_pocket_event( + secret_byte: u8, + created_at: u64, + kind: u16, + tags: &PocketOwnedTags, + content: &[u8], +) -> PocketOwnedEvent { + let secret = format!("{secret_byte:02x}").repeat(32); + RelaySigner::from_secret_hex(&secret) + .expect("signer") + .sign_pocket_event( + PocketKind::from_u16(kind), + tags, + PocketTime::from_u64(created_at), + content, + ) + .expect("pocket event") +} + +fn pocket_tags_from_protocol(tags: &[Tag]) -> PocketOwnedTags { + let parts = tags + .iter() + .map(|tag| tag.values().iter().map(String::as_str).collect::<Vec<_>>()) + .collect::<Vec<_>>(); + PocketOwnedTags::new(&parts).expect("pocket tags") +} + +fn pocket_event_to_protocol(event: &PocketOwnedEvent) -> Event { + let tags = event + .tags() + .expect("tags") + .iter() + .map(|tag| { + Tag::new( + tag.map(|value| std::str::from_utf8(value).expect("utf8").to_owned()) + .collect::<Vec<_>>(), + ) + .expect("tag") + }) + .collect::<Vec<_>>(); + Event::new( + EventId::new(&event.id().as_hex_string()).expect("event id"), + UnsignedEvent::new( + PublicKeyHex::new(&event.pubkey().as_hex_string()).expect("pubkey"), + UnixTimestamp::new(event.created_at().as_u64()), + Kind::new(u64::from(event.kind().as_u16())).expect("kind"), + tags, + std::str::from_utf8(event.content()).expect("content"), + ), + SignatureHex::new(&event.sig().to_string()).expect("sig"), + ) +} + +fn fixture_secret_byte(key: FixtureKey) -> u8 { + match key { + FixtureKey::Relay => 9, + FixtureKey::Owner => 10, + FixtureKey::Admin => 11, + FixtureKey::Member => 12, + FixtureKey::Outsider => 13, + } +} + #[test] fn public_relay_smoke_stores_queries_counts_and_fans_out() { let config = test_store_config("public-smoke"); let mut relay = BaseRelay::open(&config, relay_limits(4), PocketQueryConfig::default()).expect("relay"); - let first = - tangle_v2_event(FixtureKey::Member, 1_714_124_433, 1, Vec::new(), "hello").expect("first"); + let first = pocket_protocol_event(FixtureKey::Member, 1_714_124_433, 1, Vec::new(), "hello"); let query_id = subscription("public-query"); - assert_accepted(relay.handle_event(first.clone()).expect("event"), &first); + assert_accepted_pocket(relay.handle_event(first.clone()).expect("event"), &first); assert_event_query( relay .handle_req(query_id.clone(), vec![filter_kind(1)]) @@ -262,9 +398,8 @@ fn public_relay_smoke_stores_queries_counts_and_fans_out() { relay .handle_req(live_id.clone(), vec![filter_kind(1)]) .expect("live"); - let second = - tangle_v2_event(FixtureKey::Member, 1_714_124_434, 1, Vec::new(), "again").expect("second"); - assert_accepted(relay.handle_event(second.clone()).expect("event"), &second); + let second = pocket_protocol_event(FixtureKey::Member, 1_714_124_434, 1, Vec::new(), "again"); + assert_accepted_pocket(relay.handle_event(second.clone()).expect("event"), &second); assert!(matches!( relay.fanout(&second).as_slice(), @@ -639,9 +774,8 @@ fn metadata_flags_and_read_privacy_cover_req_count_and_fanout() { accept_group_create(&mut relay, "PrivateFarm", &["private"], 1, &owner_auth); let private_event = - tangle_v2_group_event(FixtureKey::Owner, "PrivateFarm", 2, 1, "private harvest") - .expect("private"); - assert_accepted( + pocket_protocol_group_event(FixtureKey::Owner, "PrivateFarm", 2, 1, "private harvest"); + assert_accepted_pocket( relay .handle_event_with_auth(private_event.clone(), &owner_auth) .expect("private"), @@ -734,8 +868,8 @@ fn metadata_flags_and_read_privacy_cover_req_count_and_fanout() { ) .expect("live owner"); let second_private = - tangle_v2_group_event(FixtureKey::Owner, "PrivateFarm", 3, 1, "second").expect("second"); - assert_accepted( + pocket_protocol_group_event(FixtureKey::Owner, "PrivateFarm", 3, 1, "second"); + assert_accepted_pocket( relay .handle_event_with_auth(second_private.clone(), &owner_auth) .expect("second"), @@ -782,8 +916,13 @@ fn metadata_flags_and_read_privacy_cover_req_count_and_fanout() { rejected_message( relay .handle_event_with_auth( - tangle_v2_group_event(FixtureKey::Outsider, "RestrictedFarm", 21, 1, "no") - .expect("restricted"), + pocket_protocol_group_event( + FixtureKey::Outsider, + "RestrictedFarm", + 21, + 1, + "no", + ), &outsider_auth, ) .expect("restricted") @@ -796,17 +935,16 @@ fn metadata_flags_and_read_privacy_cover_req_count_and_fanout() { rejected_message( relay .handle_event_with_auth( - tangle_v2_join_event(FixtureKey::Outsider, "ClosedFarm", 31) - .expect("closed join"), + pocket_protocol_join_event(FixtureKey::Outsider, "ClosedFarm", 31), &outsider_auth, ) .expect("closed join") ), "restricted: group is unavailable" ); - let closed_normal = tangle_v2_group_event(FixtureKey::Outsider, "ClosedFarm", 32, 1, "visible") - .expect("closed normal"); - assert_accepted( + let closed_normal = + pocket_protocol_group_event(FixtureKey::Outsider, "ClosedFarm", 32, 1, "visible"); + assert_accepted_pocket( relay .handle_event_with_auth(closed_normal.clone(), &outsider_auth) .expect("closed normal"), @@ -2207,9 +2345,8 @@ fn accept_group_create( created_at: u64, auth: &BaseAuthState, ) { - let event = tangle_v2_group_create_event(FixtureKey::Owner, group_id, created_at, flags) - .expect("event"); - assert_accepted( + let event = pocket_protocol_group_create_event(FixtureKey::Owner, group_id, created_at, flags); + assert_accepted_pocket( relay .handle_event_with_auth(event.clone(), auth) .expect("create"), @@ -2920,7 +3057,7 @@ fn group_config_with_outbox_batch(batch: u32) -> GroupRuntimeConfig { fn authenticated(key: FixtureKey) -> BaseAuthState { let auth = BaseAuthState::new(TANGLE_V2_RELAY_URL, 60, 600).expect("auth"); let mut auth = issue_challenge(auth, "challenge-a", 100); - let event = tangle_v2_auth_event(key, "challenge-a", 120).expect("auth event"); + let event = pocket_protocol_auth_event(key, "challenge-a", 120); authenticate_pocket_event_for_test(&mut auth, &event, UnixTimestamp::new(120)) .expect("authenticate"); auth @@ -2945,6 +3082,20 @@ fn assert_accepted(message: RelayMessage, event: &Event) { assert_eq!(verify_event_signature(event), Ok(())); } +fn assert_accepted_pocket(message: RelayMessage, event: &Event) { + assert_eq!( + message, + RelayMessage::Ok { + event_id: event.id().clone(), + accepted: true, + message: String::new() + } + ); + pocket_event_for_test(event) + .verify() + .expect("pocket verify"); +} + fn rejected_message(message: RelayMessage) -> String { match message { RelayMessage::Ok { diff --git a/crates/tangle_runtime/tests/phase2_acceptance_targets.rs b/crates/tangle_runtime/tests/phase2_acceptance_targets.rs @@ -9,13 +9,14 @@ use std::{ path::{Path, PathBuf}, time::{Duration, Instant, SystemTime, UNIX_EPOCH}, }; +use tangle_crypto::RelaySigner; use tangle_groups::{ GroupAuthContext, GroupAuthority, GroupErrorKind, GroupEventClass, GroupId, GroupMetadata, GroupMetadataFlags, GroupMetadataText, GroupPolicyConfig, GroupProjection, GroupReadDecision, GroupReadGate, GroupState, GroupWriteDecision, GroupWritePolicy, KIND_GROUP_ADMINS, - KIND_GROUP_JOIN_REQUEST, KIND_GROUP_LEAVE_REQUEST, KIND_GROUP_MEMBERS, KIND_GROUP_METADATA, - MemberState, MemberStatus, ProjectionOrderTuple, StoreOffset, SupportedKinds, - parse_group_runtime_config_json, + KIND_GROUP_CREATE_GROUP, KIND_GROUP_JOIN_REQUEST, KIND_GROUP_LEAVE_REQUEST, KIND_GROUP_MEMBERS, + KIND_GROUP_METADATA, KIND_GROUP_PUT_USER, MemberState, MemberStatus, ProjectionOrderTuple, + StoreOffset, SupportedKinds, parse_group_runtime_config_json, }; use tangle_protocol::{ Event, EventId, Filter, Kind, PublicKeyHex, RelayMessage, SignatureHex, SubscriptionId, Tag, @@ -30,9 +31,9 @@ use tangle_runtime::{ server::serve_listener_until_shutdown, }; use tangle_store_pocket::{ - PocketOwnedEvent, PocketStoreConfig, PocketStoreHandle, TANGLE_GROUP_CHECKPOINT_TABLE, - TANGLE_GROUP_OUTBOX_TABLE, TANGLE_GROUP_PROJECTION_TABLE, parse_pocket_event_json, - parse_pocket_filter_json, + PocketKind, PocketOwnedEvent, PocketOwnedTags, PocketStoreConfig, PocketStoreHandle, + PocketTime, TANGLE_GROUP_CHECKPOINT_TABLE, TANGLE_GROUP_OUTBOX_TABLE, + TANGLE_GROUP_PROJECTION_TABLE, parse_pocket_event_json, parse_pocket_filter_json, }; use tangle_test_support::{ FixtureKey, TANGLE_V2_RELAY_SECRET_HEX, TANGLE_V2_RELAY_URL, tangle_v2_auth_event, @@ -113,6 +114,151 @@ fn pocket_event_for_test(event: &Event) -> PocketOwnedEvent { parse_pocket_event_json(&raw).expect("pocket event") } +fn pocket_protocol_event( + key: FixtureKey, + created_at: u64, + kind: u64, + tags: Vec<Tag>, + content: &str, +) -> Event { + let tags = pocket_tags_from_protocol(&tags); + let pocket = signed_pocket_event( + fixture_secret_byte(key), + created_at, + u16::try_from(kind).expect("pocket kind"), + &tags, + content.as_bytes(), + ); + pocket_event_to_protocol(&pocket) +} + +fn pocket_protocol_auth_event(key: FixtureKey, challenge: &str, created_at: u64) -> Event { + pocket_protocol_event( + key, + created_at, + 22_242, + vec![ + Tag::from_parts("relay", &[TANGLE_V2_RELAY_URL]).expect("relay"), + Tag::from_parts("challenge", &[challenge]).expect("challenge"), + ], + "", + ) +} + +fn pocket_protocol_group_create_event( + key: FixtureKey, + group_id: &str, + created_at: u64, + flags: &[&str], +) -> Event { + let mut tags = vec![ + Tag::from_parts("h", &[group_id]).expect("h"), + Tag::from_parts("name", &[group_id]).expect("name"), + ]; + for flag in flags { + tags.push(Tag::from_parts(flag, &[]).expect("flag")); + } + pocket_protocol_event(key, created_at, KIND_GROUP_CREATE_GROUP.into(), tags, "") +} + +fn pocket_protocol_put_user_event( + key: FixtureKey, + group_id: &str, + target: FixtureKey, + created_at: u64, +) -> Event { + let target_pubkey = target.public_key(); + pocket_protocol_event( + key, + created_at, + KIND_GROUP_PUT_USER.into(), + vec![ + Tag::from_parts("h", &[group_id]).expect("h"), + Tag::from_parts("p", &[target_pubkey.as_str()]).expect("p"), + ], + "", + ) +} + +fn pocket_protocol_group_event( + key: FixtureKey, + group_id: &str, + created_at: u64, + kind: u64, + content: &str, +) -> Event { + pocket_protocol_event( + key, + created_at, + kind, + vec![Tag::from_parts("h", &[group_id]).expect("h")], + content, + ) +} + +fn signed_pocket_event( + secret_byte: u8, + created_at: u64, + kind: u16, + tags: &PocketOwnedTags, + content: &[u8], +) -> PocketOwnedEvent { + let secret = format!("{secret_byte:02x}").repeat(32); + RelaySigner::from_secret_hex(&secret) + .expect("signer") + .sign_pocket_event( + PocketKind::from_u16(kind), + tags, + PocketTime::from_u64(created_at), + content, + ) + .expect("pocket event") +} + +fn pocket_tags_from_protocol(tags: &[Tag]) -> PocketOwnedTags { + let parts = tags + .iter() + .map(|tag| tag.values().iter().map(String::as_str).collect::<Vec<_>>()) + .collect::<Vec<_>>(); + PocketOwnedTags::new(&parts).expect("pocket tags") +} + +fn pocket_event_to_protocol(event: &PocketOwnedEvent) -> Event { + let tags = event + .tags() + .expect("tags") + .iter() + .map(|tag| { + Tag::new( + tag.map(|value| std::str::from_utf8(value).expect("utf8").to_owned()) + .collect::<Vec<_>>(), + ) + .expect("tag") + }) + .collect::<Vec<_>>(); + Event::new( + EventId::new(&event.id().as_hex_string()).expect("event id"), + UnsignedEvent::new( + PublicKeyHex::new(&event.pubkey().as_hex_string()).expect("pubkey"), + UnixTimestamp::new(event.created_at().as_u64()), + Kind::new(u64::from(event.kind().as_u16())).expect("kind"), + tags, + std::str::from_utf8(event.content()).expect("content"), + ), + SignatureHex::new(&event.sig().to_string()).expect("sig"), + ) +} + +fn fixture_secret_byte(key: FixtureKey) -> u8 { + match key { + FixtureKey::Relay => 9, + FixtureKey::Owner => 10, + FixtureKey::Admin => 11, + FixtureKey::Member => 12, + FixtureKey::Outsider => 13, + } +} + #[tokio::test] async fn tangle_run_serves_until_shutdown() { let root = temp_root("acceptance-server"); @@ -337,46 +483,41 @@ async fn websocket_public_relay_covers_query_count_ephemeral_and_rejection_flows let mut subscriber = connect_nostr_socket(address).await; let _ = read_auth_challenge(&mut publisher).await; let _ = read_auth_challenge(&mut subscriber).await; - let first = tangle_v2_event( + let first = pocket_protocol_event( FixtureKey::Member, 1_714_124_433, 1, Vec::new(), "public one", - ) - .expect("first event"); - let second = tangle_v2_event( + ); + let second = pocket_protocol_event( FixtureKey::Admin, 1_714_124_435, 1, Vec::new(), "public two", - ) - .expect("second event"); - let other_kind = tangle_v2_event( + ); + let other_kind = pocket_protocol_event( FixtureKey::Owner, 1_714_124_436, 2, Vec::new(), "public other", - ) - .expect("other event"); - let ephemeral = tangle_v2_event( + ); + let ephemeral = pocket_protocol_event( FixtureKey::Member, 1_714_124_437, 20_001, Vec::new(), "public transient", - ) - .expect("ephemeral event"); - let signature_source = tangle_v2_event( + ); + let signature_source = pocket_protocol_event( FixtureKey::Owner, 1_714_124_438, 1, Vec::new(), "signature source", - ) - .expect("signature source"); + ); let invalid = Event::new( first.id().clone(), first.unsigned().clone(), @@ -490,14 +631,13 @@ async fn websocket_public_relay_covers_query_count_ephemeral_and_rejection_flows send_client_value(&mut publisher, json!(["CLOSE", "query-public"])).await; expect_no_relay_message(&mut publisher).await; - let after_close = tangle_v2_event( + let after_close = pocket_protocol_event( FixtureKey::Admin, 1_714_124_439, 1, Vec::new(), "after close", - ) - .expect("after close event"); + ); send_client_value( &mut publisher, json!(["EVENT", event_to_value(&after_close)]), @@ -825,13 +965,12 @@ async fn websocket_private_and_hidden_groups_do_not_leak_through_query_count_or_ ) .await; - let private_create = tangle_v2_group_create_event( + let private_create = pocket_protocol_group_create_event( FixtureKey::Owner, "PrivateSocket", 1_714_124_450, &["private"], - ) - .expect("private create"); + ); send_client_value( &mut owner_writer, json!(["EVENT", event_to_value(&private_create)]), @@ -844,13 +983,12 @@ async fn websocket_private_and_hidden_groups_do_not_leak_through_query_count_or_ "", ); - let private_put = tangle_v2_put_user_event( + let private_put = pocket_protocol_put_user_event( FixtureKey::Owner, "PrivateSocket", FixtureKey::Member, 1_714_124_451, - ) - .expect("private put"); + ); send_client_value( &mut owner_writer, json!(["EVENT", event_to_value(&private_put)]), @@ -944,14 +1082,13 @@ async fn websocket_private_and_hidden_groups_do_not_leak_through_query_count_or_ json!(["EOSE", "private-member-live"]) ); - let private_note = tangle_v2_group_event( + let private_note = pocket_protocol_group_event( FixtureKey::Member, "PrivateSocket", 1_714_124_452, 1, "private harvest", - ) - .expect("private note"); + ); send_client_value( &mut member_writer, json!(["EVENT", event_to_value(&private_note)]), @@ -1016,13 +1153,12 @@ async fn websocket_private_and_hidden_groups_do_not_leak_through_query_count_or_ ) .await; - let hidden_create = tangle_v2_group_create_event( + let hidden_create = pocket_protocol_group_create_event( FixtureKey::Owner, "HiddenSocket", 1_714_124_453, &["hidden"], - ) - .expect("hidden create"); + ); send_client_value( &mut owner_writer, json!(["EVENT", event_to_value(&hidden_create)]), @@ -1035,13 +1171,12 @@ async fn websocket_private_and_hidden_groups_do_not_leak_through_query_count_or_ "", ); - let hidden_put = tangle_v2_put_user_event( + let hidden_put = pocket_protocol_put_user_event( FixtureKey::Owner, "HiddenSocket", FixtureKey::Member, 1_714_124_454, - ) - .expect("hidden put"); + ); send_client_value( &mut owner_writer, json!(["EVENT", event_to_value(&hidden_put)]), @@ -1136,14 +1271,13 @@ async fn websocket_private_and_hidden_groups_do_not_leak_through_query_count_or_ json!(["EOSE", "hidden-member-live"]) ); - let hidden_note = tangle_v2_group_event( + let hidden_note = pocket_protocol_group_event( FixtureKey::Owner, "HiddenSocket", 1_714_124_455, 1, "hidden harvest", - ) - .expect("hidden note"); + ); send_client_value( &mut owner_writer, json!(["EVENT", event_to_value(&hidden_note)]), @@ -2387,7 +2521,7 @@ async fn authenticate_client( challenge: &str, created_at: u64, ) { - let auth = tangle_v2_auth_event(fixture_key, challenge, created_at).expect("auth"); + let auth = pocket_protocol_auth_event(fixture_key, challenge, created_at); send_client_value(socket, json!(["AUTH", event_to_value(&auth)])).await; assert_ok(read_relay_value(socket).await, &auth, true, ""); }