tangle


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

commit 91c6ddfdf3aba9466a2c92242de299d6a0ca540f
parent d5c68ab256ecddefc836164de764cff7bc005d38
Author: triesap <tyson@radroots.org>
Date:   Tue, 16 Jun 2026 14:44:36 -0700

runtime: suppress HLL for group targets

Diffstat:
MCargo.lock | 1+
Mcrates/tangle_bench/Cargo.toml | 1+
Mcrates/tangle_bench/src/lib.rs | 236+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mcrates/tangle_runtime/src/relay/core.rs | 167++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mcrates/tangle_runtime/src/runtime.rs | 185+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mcrates/tangle_runtime/src/server.rs | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mcrates/tangle_runtime/src/session.rs | 142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mcrates/tangle_runtime/tests/base_relay_v2.rs | 185++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mcrates/tangle_runtime/tests/ops_truthfulness.rs | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mcrates/tangle_runtime/tests/phase2_acceptance_targets.rs | 100++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
10 files changed, 1150 insertions(+), 84 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -1276,6 +1276,7 @@ dependencies = [ name = "tangle_bench" version = "0.1.0" dependencies = [ + "pocket-types", "serde_json", "sha2", "tangle_groups", diff --git a/crates/tangle_bench/Cargo.toml b/crates/tangle_bench/Cargo.toml @@ -12,6 +12,7 @@ name = "tangle-benchmark-report" path = "src/bin/tangle_benchmark_report.rs" [dependencies] +pocket-types = { git = "https://github.com/triesap/pocket", rev = "329334f20948c796c6016b673b92551ac4855ad7" } serde_json = "1" sha2 = "0.10" tangle_groups = { path = "../tangle_groups" } diff --git a/crates/tangle_bench/src/lib.rs b/crates/tangle_bench/src/lib.rs @@ -1,5 +1,7 @@ #![forbid(unsafe_code)] +use pocket_types::json::json_escape; +use pocket_types::secp256k1::{Keypair, Secp256k1, SecretKey}; use serde_json::json; use sha2::{Digest, Sha256}; use std::collections::BTreeMap; @@ -7,10 +9,13 @@ use std::fs; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicU64, Ordering}; use std::time::Instant; -use tangle_groups::{KIND_GROUP_ADMINS, KIND_GROUP_MEMBERS, KIND_GROUP_METADATA, MemberStatus}; +use tangle_groups::{ + KIND_GROUP_ADMINS, KIND_GROUP_CREATE_GROUP, KIND_GROUP_MEMBERS, KIND_GROUP_METADATA, + KIND_GROUP_PUT_USER, MemberStatus, +}; use tangle_protocol::{ - Event, Filter, RelayMessage, SubscriptionId, UnixTimestamp, event_to_value, filter_from_value, - filter_to_value, + Event, EventId, Filter, Kind, PublicKeyHex, RelayMessage, SignatureHex, SubscriptionId, Tag, + UnixTimestamp, UnsignedEvent, event_to_value, filter_from_value, filter_to_value, }; use tangle_runtime::{ config::{BaseRelayRuntimeConfig, parse_base_relay_runtime_config_json}, @@ -22,13 +27,11 @@ use tangle_runtime::{ runtime::{TangleRuntime, TangleRuntimeHandle}, }; use tangle_store_pocket::{ - PocketOwnedEvent, PocketOwnedFilter, PocketQueryConfig, PocketStoreConfig, PocketSyncPolicy, + PocketEventId, PocketKind, PocketOwnedEvent, PocketOwnedFilter, PocketOwnedTags, PocketPubkey, + PocketQueryConfig, PocketSig, PocketStoreConfig, PocketSyncPolicy, PocketTime, parse_pocket_event_json, parse_pocket_filter_json, }; -use tangle_test_support::{ - FixtureKey, TANGLE_V2_RELAY_URL, tangle_v2_auth_event, tangle_v2_event, tangle_v2_group_config, - tangle_v2_group_create_event, tangle_v2_group_event, tangle_v2_put_user_event, tangle_v2_tag, -}; +use tangle_test_support::{FixtureKey, TANGLE_V2_RELAY_URL, tangle_v2_group_config}; static TEMP_ID: AtomicU64 = AtomicU64::new(0); @@ -435,7 +438,7 @@ impl BenchDataset { for (group_index, group) in groups.iter().enumerate() { group_create_events.push(BenchSourceEvent { - event: tangle_v2_group_create_event( + event: pocket_protocol_group_create_event( FixtureKey::Owner, &group.id, 1_714_200_000 + u64::try_from(group_index).expect("group index fits in u64"), @@ -460,7 +463,7 @@ impl BenchDataset { + u64::try_from(group_index * 10_000 + event_index) .expect("event index fits in u64"); group_timeline_events.push(BenchSourceEvent { - event: tangle_v2_group_event( + event: pocket_protocol_group_event( FixtureKey::Owner, &group.id, created_at, @@ -477,11 +480,14 @@ impl BenchDataset { for index in 0..config.public_note_count { public_note_events.push(BenchSourceEvent { - event: tangle_v2_event( + event: pocket_protocol_event( FixtureKey::Outsider, 1_714_500_000 + u64::try_from(index).expect("note index fits in u64"), 1, - vec![tangle_v2_tag("t", &["tangle-bench"])?], + vec![ + Tag::from_parts("t", &["tangle-bench"]) + .map_err(|error| error.to_string())?, + ], &format!("bench public note {index:04}"), )?, auth: BenchEventAuth::None, @@ -1319,14 +1325,14 @@ fn run_broadcast_lag_benchmark(dataset: &BenchDataset) -> Result<ScenarioReport, ) .map_err(|error| error.to_string())?; } - let first = tangle_v2_group_event( + let first = pocket_protocol_group_event( FixtureKey::Owner, public_group.id(), 1_714_600_000, 1, "broadcast lag first", )?; - let second = tangle_v2_group_event( + let second = pocket_protocol_group_event( FixtureKey::Owner, public_group.id(), 1_714_600_001, @@ -1661,6 +1667,194 @@ fn pocket_event(event: &Event) -> Result<PocketOwnedEvent, String> { parse_pocket_event_json(&raw).map_err(|error| error.to_string()) } +fn pocket_protocol_event( + key: FixtureKey, + created_at: u64, + kind: u64, + tags: Vec<Tag>, + content: &str, +) -> Result<Event, String> { + let tags = pocket_tags_from_protocol(&tags)?; + let pocket = signed_pocket_event( + fixture_secret_byte(key), + created_at, + u16::try_from(kind).map_err(|error| error.to_string())?, + &tags, + content.as_bytes(), + )?; + pocket_event_to_protocol(&pocket) +} + +fn pocket_protocol_auth_event( + key: FixtureKey, + challenge: &str, + created_at: u64, +) -> Result<Event, String> { + pocket_protocol_event( + key, + created_at, + 22_242, + vec![ + Tag::from_parts("relay", &[TANGLE_V2_RELAY_URL]).map_err(|error| error.to_string())?, + Tag::from_parts("challenge", &[challenge]).map_err(|error| error.to_string())?, + ], + "", + ) +} + +fn pocket_protocol_group_create_event( + key: FixtureKey, + group_id: &str, + created_at: u64, + flags: &[&str], +) -> Result<Event, String> { + let mut tags = vec![ + Tag::from_parts("h", &[group_id]).map_err(|error| error.to_string())?, + Tag::from_parts("name", &[group_id]).map_err(|error| error.to_string())?, + ]; + for flag in flags { + tags.push(Tag::from_parts(flag, &[]).map_err(|error| error.to_string())?); + } + 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, +) -> Result<Event, String> { + let target_pubkey = target.public_key(); + pocket_protocol_put_pubkey_event(key, group_id, target_pubkey.as_str(), created_at) +} + +fn pocket_protocol_put_pubkey_event( + key: FixtureKey, + group_id: &str, + target_pubkey: &str, + created_at: u64, +) -> Result<Event, String> { + pocket_protocol_event( + key, + created_at, + KIND_GROUP_PUT_USER.into(), + vec![ + Tag::from_parts("h", &[group_id]).map_err(|error| error.to_string())?, + Tag::from_parts("p", &[target_pubkey]).map_err(|error| error.to_string())?, + ], + "", + ) +} + +fn pocket_protocol_group_event( + key: FixtureKey, + group_id: &str, + created_at: u64, + kind: u64, + content: &str, +) -> Result<Event, String> { + pocket_protocol_event( + key, + created_at, + kind, + vec![Tag::from_parts("h", &[group_id]).map_err(|error| error.to_string())?], + content, + ) +} + +fn signed_pocket_event( + secret_byte: u8, + created_at: u64, + kind: u16, + tags: &PocketOwnedTags, + content: &[u8], +) -> Result<PocketOwnedEvent, String> { + let secp = Secp256k1::new(); + let secret_key = + SecretKey::from_byte_array([secret_byte; 32]).map_err(|error| error.to_string())?; + let keypair = Keypair::from_secret_key(&secp, &secret_key); + let (xonlypubkey, _) = keypair.x_only_public_key(); + let pubkey_bytes = xonlypubkey.serialize(); + let pubkey = PocketPubkey::from_bytes(pubkey_bytes); + let pocket_kind = PocketKind::from_u16(kind); + let pocket_time = PocketTime::from_u64(created_at); + let escaped_content = json_escape(content, Vec::with_capacity(content.len() * 7 / 6)) + .map_err(|error| error.to_string())?; + let escaped_content = + std::str::from_utf8(&escaped_content).map_err(|error| error.to_string())?; + let tags_json = tags.as_json(); + let tags_json = std::str::from_utf8(&tags_json).map_err(|error| error.to_string())?; + let signable = format!( + r#"[0,"{}",{},{},{},"{}"]"#, + pubkey, pocket_time, pocket_kind, tags_json, escaped_content + ); + let digest = Sha256::digest(signable.as_bytes()); + let digest_slice: &[u8] = digest.as_ref(); + let event_id: [u8; 32] = digest_slice + .try_into() + .expect("sha256 digest length is 32 bytes"); + let signature = secp.sign_schnorr_no_aux_rand(&event_id, &keypair); + PocketOwnedEvent::new( + PocketEventId::from_bytes(event_id), + pocket_kind, + pubkey, + PocketSig::from_bytes(signature.to_byte_array()), + tags, + pocket_time, + content, + ) + .map_err(|error| error.to_string()) +} + +fn pocket_tags_from_protocol(tags: &[Tag]) -> Result<PocketOwnedTags, String> { + let parts = tags + .iter() + .map(|tag| tag.values().iter().map(String::as_str).collect::<Vec<_>>()) + .collect::<Vec<_>>(); + PocketOwnedTags::new(&parts).map_err(|error| error.to_string()) +} + +fn pocket_event_to_protocol(event: &PocketOwnedEvent) -> Result<Event, String> { + let tags = event + .tags() + .map_err(|error| error.to_string())? + .iter() + .map(|tag| { + Tag::new( + tag.map(|value| { + std::str::from_utf8(value) + .map(str::to_owned) + .map_err(|error| error.to_string()) + }) + .collect::<Result<Vec<_>, _>>()?, + ) + .map_err(|error| error.to_string()) + }) + .collect::<Result<Vec<_>, _>>()?; + Ok(Event::new( + EventId::new(&event.id().as_hex_string()).map_err(|error| error.to_string())?, + UnsignedEvent::new( + PublicKeyHex::new(&event.pubkey().as_hex_string()) + .map_err(|error| error.to_string())?, + UnixTimestamp::new(event.created_at().as_u64()), + Kind::new(u64::from(event.kind().as_u16())).map_err(|error| error.to_string())?, + tags, + std::str::from_utf8(event.content()).map_err(|error| error.to_string())?, + ), + SignatureHex::new(&event.sig().to_string()).map_err(|error| error.to_string())?, + )) +} + +fn fixture_secret_byte(key: FixtureKey) -> u8 { + match key { + FixtureKey::Relay => 9, + FixtureKey::Owner => 10, + FixtureKey::Admin => 11, + FixtureKey::Member => 12, + FixtureKey::Outsider => 13, + } +} + fn pocket_filter_from_value(value: &serde_json::Value) -> Result<PocketOwnedFilter, String> { let filter = filter_from_value(value)?; pocket_filter(&filter) @@ -1766,7 +1960,7 @@ fn bench_member_event( base_created_at: u64, ) -> Result<Event, String> { if member_index == 0 { - return tangle_v2_put_user_event( + return pocket_protocol_put_user_event( FixtureKey::Admin, group_id, FixtureKey::Member, @@ -1774,16 +1968,12 @@ fn bench_member_event( ); } let pubkey = synthetic_member_pubkey(group_index, member_index); - tangle_v2_event( + pocket_protocol_put_pubkey_event( FixtureKey::Admin, + group_id, + &pubkey, base_created_at + u64::try_from(group_index * 10_000 + member_index).expect("member index fits in u64"), - 9_000, - vec![ - tangle_v2_tag("h", &[group_id])?, - tangle_v2_tag("p", &[pubkey.as_str()])?, - ], - "", ) } @@ -1818,7 +2008,7 @@ fn authenticated(key: FixtureKey) -> Result<BaseAuthState, String> { BaseAuthState::new(TANGLE_V2_RELAY_URL, 60, 600).map_err(|error| error.to_string())?; auth.issue_challenge("challenge-a", tangle_protocol::UnixTimestamp::new(100)) .map_err(|error| error.to_string())?; - let event = tangle_v2_auth_event(key, "challenge-a", 120)?; + let event = pocket_protocol_auth_event(key, "challenge-a", 120)?; let pocket = pocket_event(&event)?; auth.authenticate_pocket(&pocket, tangle_protocol::UnixTimestamp::new(120)) .map_err(|error| error.to_string())?; diff --git a/crates/tangle_runtime/src/relay/core.rs b/crates/tangle_runtime/src/relay/core.rs @@ -334,6 +334,24 @@ impl BaseRelayCountHll { } } + fn suppress_for_filter_targets( + &mut self, + groups: Option<&GroupServiceHandle>, + filters: &[PocketOwnedFilter], + ) { + if self.offset.is_none() { + return; + } + let [filter] = filters else { + return; + }; + if BaseRelay::count_hll_filter_target_policy(groups, filter) + == BaseRelayCountHllTargetPolicy::Suppress + { + self.suppress(); + } + } + fn observe( &mut self, groups: Option<&GroupServiceHandle>, @@ -1615,6 +1633,7 @@ impl BaseRelay { let mut query_metrics = BaseRelayQueryMetrics::default(); let count_query = query.exact_count(); let mut hll = BaseRelayCountHll::new(filters)?; + hll.suppress_for_filter_targets(groups, filters); for filter in filters { let report = Self::query_filter_events_report_with_services( store, @@ -1913,8 +1932,8 @@ fn pocket_filters_are_complete(filters: &[PocketOwnedFilter]) -> bool { #[cfg(test)] mod tests { use super::{ - BaseRelay, BaseRelayCountHllTargetPolicy, BaseRelayLimitSettings, BaseRelayLimits, - NEGENTROPY_DISABLED_MESSAGE, + BaseRelay, BaseRelayCountHll, BaseRelayCountHllTargetPolicy, BaseRelayLimitSettings, + BaseRelayLimits, NEGENTROPY_DISABLED_MESSAGE, }; use crate::pocket_conversion::{tangle_event_to_pocket, tangle_filter_to_pocket}; use crate::relay::auth::BaseAuthState; @@ -1933,7 +1952,7 @@ mod tests { SubscriptionId, Tag, UnixTimestamp, UnsignedEvent, filter_from_value, }; use tangle_store_pocket::{ - PocketEvent, PocketKind, PocketOwnedEvent, PocketOwnedFilter, PocketOwnedTags, + PocketEvent, PocketHll8, PocketKind, PocketOwnedEvent, PocketOwnedFilter, PocketOwnedTags, PocketQueryConfig, PocketStoreConfig, PocketSyncPolicy, PocketTime, }; @@ -2886,12 +2905,45 @@ mod tests { .expect("hidden reaction"), &hidden, ); + let deleted_create = signed_pocket_group_create_event(7, "DeletedHll"); + assert_pocket_accepted( + relay + .handle_pocket_event_with_auth(&deleted_create, &owner_auth) + .expect("deleted create"), + &deleted_create, + ); + let deleted = signed_pocket_event_at_tags( + 7, + 7, + vec![h("DeletedHll"), target_tag.clone()], + "deleted reaction", + 1_714_124_438, + ); + assert_pocket_accepted( + relay + .handle_pocket_event_with_auth(&deleted, &owner_auth) + .expect("deleted reaction"), + &deleted, + ); + let delete_group = signed_pocket_event_at_tags( + 7, + KIND_GROUP_DELETE_GROUP, + vec![h("DeletedHll")], + "", + 1_714_124_439, + ); + assert_pocket_accepted( + relay + .handle_pocket_event_with_auth(&delete_group, &owner_auth) + .expect("delete group"), + &delete_group, + ); let unknown = signed_pocket_event_at_tags( 7, 7, vec![h("UnknownHll"), target_tag.clone()], "unknown reaction", - 1_714_124_437, + 1_714_124_440, ); relay.store.store_event(&unknown).expect("store unknown"); @@ -2974,6 +3026,63 @@ mod tests { .. } )); + + assert_count_without_hll( + &relay, + "count-hll-private-h-target", + serde_json::json!({"kinds":[7],"#h":["PrivateHll"]}), + None, + 0, + ); + assert_count_without_hll( + &relay, + "count-hll-hidden-h-target", + serde_json::json!({"kinds":[7],"#h":["HiddenHll"]}), + None, + 0, + ); + assert_count_without_hll( + &relay, + "count-hll-unknown-h-target", + serde_json::json!({"kinds":[7],"#h":["UnknownHll"]}), + None, + 0, + ); + assert_count_without_hll( + &relay, + "count-hll-deleted-h-target", + serde_json::json!({"kinds":[7],"#h":["DeletedHll"]}), + None, + 0, + ); + assert_count_without_hll( + &relay, + "count-hll-private-d-target", + serde_json::json!({"kinds":[KIND_GROUP_METADATA],"#d":["PrivateHll"]}), + None, + 1, + ); + assert_count_without_hll( + &relay, + "count-hll-hidden-d-target", + serde_json::json!({"kinds":[KIND_GROUP_METADATA],"#d":["HiddenHll"]}), + None, + 0, + ); + assert_count_without_hll( + &relay, + "count-hll-unknown-d-target", + serde_json::json!({"kinds":[KIND_GROUP_METADATA],"#d":["UnknownHll"]}), + None, + 0, + ); + assert_count_without_hll( + &relay, + "count-hll-deleted-d-target", + serde_json::json!({"kinds":[KIND_GROUP_METADATA],"#d":["DeletedHll"]}), + None, + 0, + ); } #[test] @@ -3006,7 +3115,7 @@ mod tests { } let delete_group = signed_pocket_event_at_tags( 7, - KIND_GROUP_DELETE_GROUP.into(), + KIND_GROUP_DELETE_GROUP, vec![h("DeletedHll")], "", 1_714_124_436, @@ -3077,6 +3186,20 @@ mod tests { ), BaseRelayCountHllTargetPolicy::Eligible ); + + let mut private_hll = count_hll_for_target_policy_test(); + let private_filter = [pocket_filter_from_value( + serde_json::json!({"kinds":[7],"#h":["PrivateHll"]}), + )]; + private_hll.suppress_for_filter_targets(relay.groups.as_ref(), &private_filter); + assert!(private_hll.into_hex().is_none()); + + let mut non_group_hll = count_hll_for_target_policy_test(); + let non_group_filter = [pocket_filter_from_value( + serde_json::json!({"kinds":[30023],"#d":["PrivateHll"]}), + )]; + non_group_hll.suppress_for_filter_targets(relay.groups.as_ref(), &non_group_filter); + assert!(non_group_hll.into_hex().is_some()); } #[test] @@ -4788,6 +4911,32 @@ mod tests { } } + fn assert_count_without_hll( + relay: &BaseRelay, + subscription_id: &str, + value: serde_json::Value, + auth: Option<&BaseAuthState>, + expected_count: u64, + ) { + let subscription_id = SubscriptionId::new(subscription_id).expect("sub"); + let filter = filter_from_value(&value).expect("filter"); + let message = match auth { + Some(auth) => { + relay.handle_count_with_auth_protocol(subscription_id.clone(), vec![filter], auth) + } + None => relay.handle_count_protocol(subscription_id.clone(), vec![filter]), + } + .expect("count"); + assert_eq!( + message, + RelayMessage::Count { + subscription_id, + count: expected_count, + hll: None + } + ); + } + fn query_filter(relay: &mut BaseRelay, subscription_id: &str, filter: Filter) -> Vec<Event> { relay .handle_protocol_req_for_test( @@ -4828,6 +4977,14 @@ mod tests { BaseRelay::count_hll_filter_target_policy(relay.groups.as_ref(), &filter) } + fn count_hll_for_target_policy_test() -> BaseRelayCountHll { + BaseRelayCountHll { + offset: Some(0), + hll: Some(PocketHll8::new()), + suppressed: false, + } + } + 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 @@ -2106,21 +2106,17 @@ mod tests { use tangle_groups::{ CanonicalGroupEvent, GroupEventClass, GroupId, GroupProjection, KIND_GROUP_ADMINS, 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, + KIND_GROUP_LEAVE_REQUEST, KIND_GROUP_MEMBERS, KIND_GROUP_METADATA, KIND_GROUP_PUT_USER, + KIND_GROUP_REMOVE_USER, MemberStatus, StoreOffset, rebuild_group_projection, }; use tangle_protocol::{ - ClientMessage, Event, EventId, Filter, Kind, PublicKeyHex, RelayMessage, SubscriptionId, - Tag, UnixTimestamp, 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, 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, - tangle_v2_leave_event, tangle_v2_put_user_event, tangle_v2_remove_user_event, - }; + use tangle_test_support::FixtureKey; #[test] fn tangle_runtime_opens_owned_process_shell_from_config() { @@ -4890,6 +4886,146 @@ mod tests { crate::pocket_conversion::tangle_filter_to_pocket(&filter).expect("pocket filter") } + fn tangle_v2_event( + key: FixtureKey, + created_at: u64, + kind: u64, + tags: Vec<Tag>, + content: &str, + ) -> Result<Event, String> { + let event = runtime_pocket_event(key, created_at, kind, tags, content); + runtime_pocket_event_to_protocol(&event) + } + + fn tangle_v2_auth_event( + key: FixtureKey, + challenge: &str, + created_at: u64, + ) -> Result<Event, String> { + tangle_v2_event( + key, + created_at, + 22_242, + vec![ + Tag::from_parts("relay", &["wss://relay.radroots.test"])?, + Tag::from_parts("challenge", &[challenge])?, + ], + "", + ) + } + + fn tangle_v2_group_create_event( + key: FixtureKey, + group_id: &str, + created_at: u64, + flags: &[&str], + ) -> Result<Event, String> { + let mut tags = vec![ + Tag::from_parts("h", &[group_id])?, + Tag::from_parts("name", &[group_id])?, + ]; + for flag in flags { + tags.push(Tag::from_parts(flag, &[])?); + } + tangle_v2_event(key, created_at, KIND_GROUP_CREATE_GROUP.into(), tags, "") + } + + fn tangle_v2_put_user_event( + key: FixtureKey, + group_id: &str, + target: FixtureKey, + created_at: u64, + ) -> Result<Event, String> { + let target_pubkey = target.public_key(); + tangle_v2_event( + key, + created_at, + KIND_GROUP_PUT_USER.into(), + vec![ + Tag::from_parts("h", &[group_id])?, + Tag::from_parts("p", &[target_pubkey.as_str()])?, + ], + "", + ) + } + + fn tangle_v2_remove_user_event( + key: FixtureKey, + group_id: &str, + target: FixtureKey, + created_at: u64, + ) -> Result<Event, String> { + let target_pubkey = target.public_key(); + tangle_v2_event( + key, + created_at, + KIND_GROUP_REMOVE_USER.into(), + vec![ + Tag::from_parts("h", &[group_id])?, + Tag::from_parts("p", &[target_pubkey.as_str()])?, + ], + "", + ) + } + + fn tangle_v2_join_event( + key: FixtureKey, + group_id: &str, + created_at: u64, + ) -> Result<Event, String> { + tangle_v2_group_event( + key, + group_id, + created_at, + KIND_GROUP_JOIN_REQUEST.into(), + "", + ) + } + + fn tangle_v2_leave_event( + key: FixtureKey, + group_id: &str, + created_at: u64, + ) -> Result<Event, String> { + tangle_v2_group_event( + key, + group_id, + created_at, + KIND_GROUP_LEAVE_REQUEST.into(), + "", + ) + } + + fn tangle_v2_delete_group_event( + key: FixtureKey, + group_id: &str, + created_at: u64, + ) -> Result<Event, String> { + tangle_v2_group_event( + key, + group_id, + created_at, + KIND_GROUP_DELETE_GROUP.into(), + "", + ) + } + + fn tangle_v2_group_event( + key: FixtureKey, + group_id: &str, + created_at: u64, + kind: u64, + content: &str, + ) -> Result<Event, String> { + tangle_v2_event( + key, + created_at, + kind, + vec![Tag::from_parts("h", &[group_id])?], + content, + ) + } + fn runtime_pocket_group_create_event( key: FixtureKey, group_id: &str, @@ -4975,6 +5111,37 @@ mod tests { ) } + fn runtime_pocket_event_to_protocol(event: &PocketEvent) -> Result<Event, String> { + let tags = event + .tags() + .map_err(|error| error.to_string())? + .iter() + .map(|tag| { + Tag::new( + tag.map(|value| { + std::str::from_utf8(value) + .map(str::to_owned) + .map_err(|error| error.to_string()) + }) + .collect::<Result<Vec<_>, _>>()?, + ) + .map_err(|error| error.to_string()) + }) + .collect::<Result<Vec<_>, _>>()?; + Ok(Event::new( + EventId::new(&event.id().as_hex_string()).map_err(|error| error.to_string())?, + UnsignedEvent::new( + PublicKeyHex::new(&event.pubkey().as_hex_string()) + .map_err(|error| error.to_string())?, + UnixTimestamp::new(event.created_at().as_u64()), + Kind::new(u64::from(event.kind().as_u16())).map_err(|error| error.to_string())?, + tags, + std::str::from_utf8(event.content()).map_err(|error| error.to_string())?, + ), + SignatureHex::new(&event.sig().to_string()).map_err(|error| error.to_string())?, + )) + } + fn pocket_tags_from_protocol(tags: &[Tag]) -> PocketOwnedTags { let parts = tags .iter() diff --git a/crates/tangle_runtime/src/server.rs b/crates/tangle_runtime/src/server.rs @@ -185,8 +185,15 @@ mod tests { path::{Path, PathBuf}, time::{SystemTime, UNIX_EPOCH}, }; - use tangle_protocol::event_to_value; - use tangle_test_support::{FixtureKey, tangle_v2_auth_event, tangle_v2_event}; + use tangle_crypto::RelaySigner; + use tangle_protocol::{ + Event, EventId, Kind, PublicKeyHex, SignatureHex, Tag, UnixTimestamp, UnsignedEvent, + event_to_value, + }; + use tangle_store_pocket::{ + PocketEvent, PocketKind, PocketOwnedEvent, PocketOwnedTags, PocketTime, + }; + use tangle_test_support::FixtureKey; use tokio::net::TcpListener; use tokio::time::{Duration, timeout}; use tokio_tungstenite::tungstenite::{ @@ -534,6 +541,103 @@ mod tests { let _ = std::fs::remove_dir_all(root); } + fn tangle_v2_event( + key: FixtureKey, + created_at: u64, + kind: u64, + tags: Vec<Tag>, + content: &str, + ) -> Result<Event, String> { + let event = server_pocket_event(key, created_at, kind, tags, content); + server_pocket_event_to_protocol(&event) + } + + fn tangle_v2_auth_event( + key: FixtureKey, + challenge: &str, + created_at: u64, + ) -> Result<Event, String> { + tangle_v2_event( + key, + created_at, + 22_242, + vec![ + Tag::from_parts("relay", &["wss://relay.radroots.test"])?, + Tag::from_parts("challenge", &[challenge])?, + ], + "", + ) + } + + fn server_pocket_event( + key: FixtureKey, + created_at: u64, + kind: u64, + tags: Vec<Tag>, + content: &str, + ) -> PocketOwnedEvent { + let tags = server_pocket_tags_from_protocol(&tags); + let secret = format!("{:02x}", fixture_secret_byte(key)).repeat(32); + RelaySigner::from_secret_hex(&secret) + .expect("signer") + .sign_pocket_event( + PocketKind::from_u16(u16::try_from(kind).expect("pocket kind")), + &tags, + PocketTime::from_u64(created_at), + content.as_bytes(), + ) + .expect("pocket event") + } + + fn server_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 server_pocket_event_to_protocol(event: &PocketEvent) -> Result<Event, String> { + let tags = event + .tags() + .map_err(|error| error.to_string())? + .iter() + .map(|tag| { + Tag::new( + tag.map(|value| { + std::str::from_utf8(value) + .map(str::to_owned) + .map_err(|error| error.to_string()) + }) + .collect::<Result<Vec<_>, _>>()?, + ) + .map_err(|error| error.to_string()) + }) + .collect::<Result<Vec<_>, _>>()?; + Ok(Event::new( + EventId::new(&event.id().as_hex_string()).map_err(|error| error.to_string())?, + UnsignedEvent::new( + PublicKeyHex::new(&event.pubkey().as_hex_string()) + .map_err(|error| error.to_string())?, + UnixTimestamp::new(event.created_at().as_u64()), + Kind::new(u64::from(event.kind().as_u16())).map_err(|error| error.to_string())?, + tags, + std::str::from_utf8(event.content()).map_err(|error| error.to_string())?, + ), + SignatureHex::new(&event.sig().to_string()).map_err(|error| error.to_string())?, + )) + } + + fn fixture_secret_byte(key: FixtureKey) -> u8 { + match key { + FixtureKey::Relay => 9, + FixtureKey::Owner => 10, + FixtureKey::Admin => 11, + FixtureKey::Member => 12, + FixtureKey::Outsider => 13, + } + } + fn runtime_config(root: &Path) -> BaseRelayRuntimeConfig { let raw = json!({ "server": { diff --git a/crates/tangle_runtime/src/session.rs b/crates/tangle_runtime/src/session.rs @@ -578,15 +578,16 @@ mod tests { use axum::extract::ws::Message; use serde_json::json; use std::path::{Path, PathBuf}; - use tangle_groups::StoreOffset; + use tangle_crypto::RelaySigner; + use tangle_groups::{KIND_GROUP_CREATE_GROUP, StoreOffset}; use tangle_protocol::{ - ClientMessage, Filter, RelayMessage, SubscriptionId, UnixTimestamp, event_to_value, - filter_from_value, + ClientMessage, Event, EventId, Filter, Kind, PublicKeyHex, RelayMessage, SignatureHex, + SubscriptionId, Tag, UnixTimestamp, UnsignedEvent, event_to_value, filter_from_value, }; - use tangle_test_support::{ - FixtureKey, tangle_v2_auth_event, tangle_v2_event, tangle_v2_group_create_event, - tangle_v2_group_event, + use tangle_store_pocket::{ + PocketEvent, PocketKind, PocketOwnedEvent, PocketOwnedTags, PocketTime, }; + use tangle_test_support::FixtureKey; #[test] fn websocket_session_records_connection_time() { @@ -1650,6 +1651,135 @@ mod tests { assert_eq!(metrics.outbound_queue_full_closes(), 1); } + fn tangle_v2_event( + key: FixtureKey, + created_at: u64, + kind: u64, + tags: Vec<Tag>, + content: &str, + ) -> Result<Event, String> { + let event = session_pocket_event(key, created_at, kind, tags, content); + session_pocket_event_to_protocol(&event) + } + + fn tangle_v2_auth_event( + key: FixtureKey, + challenge: &str, + created_at: u64, + ) -> Result<Event, String> { + tangle_v2_event( + key, + created_at, + 22_242, + vec![ + Tag::from_parts("relay", &["wss://relay.radroots.test"])?, + Tag::from_parts("challenge", &[challenge])?, + ], + "", + ) + } + + fn tangle_v2_group_create_event( + key: FixtureKey, + group_id: &str, + created_at: u64, + flags: &[&str], + ) -> Result<Event, String> { + let mut tags = vec![ + Tag::from_parts("h", &[group_id])?, + Tag::from_parts("name", &[group_id])?, + ]; + for flag in flags { + tags.push(Tag::from_parts(flag, &[])?); + } + tangle_v2_event(key, created_at, KIND_GROUP_CREATE_GROUP.into(), tags, "") + } + + fn tangle_v2_group_event( + key: FixtureKey, + group_id: &str, + created_at: u64, + kind: u64, + content: &str, + ) -> Result<Event, String> { + tangle_v2_event( + key, + created_at, + kind, + vec![Tag::from_parts("h", &[group_id])?], + content, + ) + } + + fn session_pocket_event( + key: FixtureKey, + created_at: u64, + kind: u64, + tags: Vec<Tag>, + content: &str, + ) -> PocketOwnedEvent { + let tags = session_pocket_tags_from_protocol(&tags); + let secret = format!("{:02x}", fixture_secret_byte(key)).repeat(32); + RelaySigner::from_secret_hex(&secret) + .expect("signer") + .sign_pocket_event( + PocketKind::from_u16(u16::try_from(kind).expect("pocket kind")), + &tags, + PocketTime::from_u64(created_at), + content.as_bytes(), + ) + .expect("pocket event") + } + + fn session_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 session_pocket_event_to_protocol(event: &PocketEvent) -> Result<Event, String> { + let tags = event + .tags() + .map_err(|error| error.to_string())? + .iter() + .map(|tag| { + Tag::new( + tag.map(|value| { + std::str::from_utf8(value) + .map(str::to_owned) + .map_err(|error| error.to_string()) + }) + .collect::<Result<Vec<_>, _>>()?, + ) + .map_err(|error| error.to_string()) + }) + .collect::<Result<Vec<_>, _>>()?; + Ok(Event::new( + EventId::new(&event.id().as_hex_string()).map_err(|error| error.to_string())?, + UnsignedEvent::new( + PublicKeyHex::new(&event.pubkey().as_hex_string()) + .map_err(|error| error.to_string())?, + UnixTimestamp::new(event.created_at().as_u64()), + Kind::new(u64::from(event.kind().as_u16())).map_err(|error| error.to_string())?, + tags, + std::str::from_utf8(event.content()).map_err(|error| error.to_string())?, + ), + SignatureHex::new(&event.sig().to_string()).map_err(|error| error.to_string())?, + )) + } + + fn fixture_secret_byte(key: FixtureKey) -> u8 { + match key { + FixtureKey::Relay => 9, + FixtureKey::Owner => 10, + FixtureKey::Admin => 11, + FixtureKey::Member => 12, + FixtureKey::Outsider => 13, + } + } + fn session_runtime( name: &str, ) -> ( diff --git a/crates/tangle_runtime/tests/base_relay_v2.rs b/crates/tangle_runtime/tests/base_relay_v2.rs @@ -1,13 +1,14 @@ #![forbid(unsafe_code)] use std::{fs, panic, path::PathBuf}; -use tangle_crypto::{RelaySigner, event_id_matches, verify_event_signature}; +use tangle_crypto::RelaySigner; use tangle_groups::{ GroupAuthContext, GroupAuthority, GroupGeneratedEventBuilder, GroupId, GroupLimitsConfig, GroupOutboxEffect, GroupOutboxKey, GroupOutboxRecord, GroupOutboxStatus, GroupProjection, - 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, + GroupRuntimeConfig, KIND_GROUP_ADMINS, KIND_GROUP_CREATE_GROUP, KIND_GROUP_DELETE_EVENT, + KIND_GROUP_DELETE_GROUP, KIND_GROUP_EDIT_METADATA, KIND_GROUP_JOIN_REQUEST, + KIND_GROUP_LEAVE_REQUEST, KIND_GROUP_MEMBERS, KIND_GROUP_METADATA, KIND_GROUP_PUT_USER, + KIND_GROUP_REMOVE_USER, MemberStatus, NIP29_RELAY_GENERATED_KIND_VALUES, PERMANENT_RELAY_OVERRIDE_ROLE, ProjectionCheckpoint, StoreOffset, member_current_key, parse_group_runtime_config_json, projection_checkpoint_key, }; @@ -35,12 +36,8 @@ use tangle_store_pocket::{ parse_pocket_filter_json, }; use tangle_test_support::{ - FixtureKey, TANGLE_V2_RELAY_SECRET_HEX, TANGLE_V2_RELAY_URL, tangle_v2_auth_event, - tangle_v2_delete_event_event, tangle_v2_delete_group_event, tangle_v2_event, - tangle_v2_group_config, tangle_v2_group_create_event, tangle_v2_group_event, - tangle_v2_group_metadata_event, tangle_v2_group_tag, tangle_v2_join_event, - tangle_v2_leave_event, tangle_v2_pubkey_tag, tangle_v2_put_user_event, - tangle_v2_remove_user_event, tangle_v2_tag, + FixtureKey, TANGLE_V2_RELAY_SECRET_HEX, TANGLE_V2_RELAY_URL, tangle_v2_group_config, + tangle_v2_group_tag, tangle_v2_pubkey_tag, tangle_v2_tag, }; trait BaseRelayEventTestExt { @@ -309,6 +306,150 @@ fn pocket_protocol_join_event(key: FixtureKey, group_id: &str, created_at: u64) ) } +fn tangle_v2_event( + key: FixtureKey, + created_at: u64, + kind: u64, + tags: Vec<Tag>, + content: &str, +) -> Result<Event, String> { + Ok(pocket_protocol_event(key, created_at, kind, tags, content)) +} + +fn tangle_v2_auth_event( + key: FixtureKey, + challenge: &str, + created_at: u64, +) -> Result<Event, String> { + Ok(pocket_protocol_auth_event(key, challenge, created_at)) +} + +fn tangle_v2_group_create_event( + key: FixtureKey, + group_id: &str, + created_at: u64, + flags: &[&str], +) -> Result<Event, String> { + Ok(pocket_protocol_group_create_event( + key, group_id, created_at, flags, + )) +} + +fn tangle_v2_group_metadata_event( + key: FixtureKey, + group_id: &str, + name: &str, + created_at: u64, + flags: &[&str], +) -> Result<Event, String> { + let mut tags = vec![ + tangle_v2_group_tag(group_id)?, + tangle_v2_tag("name", &[name])?, + ]; + for flag in flags { + tags.push(tangle_v2_tag(flag, &[])?); + } + tangle_v2_event(key, created_at, KIND_GROUP_EDIT_METADATA.into(), tags, "") +} + +fn tangle_v2_put_user_event( + key: FixtureKey, + group_id: &str, + target: FixtureKey, + created_at: u64, +) -> Result<Event, String> { + tangle_v2_event( + key, + created_at, + KIND_GROUP_PUT_USER.into(), + vec![ + tangle_v2_group_tag(group_id)?, + tangle_v2_pubkey_tag(target)?, + ], + "", + ) +} + +fn tangle_v2_remove_user_event( + key: FixtureKey, + group_id: &str, + target: FixtureKey, + created_at: u64, +) -> Result<Event, String> { + tangle_v2_event( + key, + created_at, + KIND_GROUP_REMOVE_USER.into(), + vec![ + tangle_v2_group_tag(group_id)?, + tangle_v2_pubkey_tag(target)?, + ], + "", + ) +} + +fn tangle_v2_join_event(key: FixtureKey, group_id: &str, created_at: u64) -> Result<Event, String> { + Ok(pocket_protocol_join_event(key, group_id, created_at)) +} + +fn tangle_v2_leave_event( + key: FixtureKey, + group_id: &str, + created_at: u64, +) -> Result<Event, String> { + tangle_v2_group_event( + key, + group_id, + created_at, + KIND_GROUP_LEAVE_REQUEST.into(), + "", + ) +} + +fn tangle_v2_delete_event_event( + key: FixtureKey, + group_id: &str, + target: &Event, + created_at: u64, +) -> Result<Event, String> { + tangle_v2_event( + key, + created_at, + KIND_GROUP_DELETE_EVENT.into(), + vec![ + tangle_v2_group_tag(group_id)?, + tangle_v2_tag("e", &[target.id().as_str()])?, + ], + "", + ) +} + +fn tangle_v2_delete_group_event( + key: FixtureKey, + group_id: &str, + created_at: u64, +) -> Result<Event, String> { + tangle_v2_group_event( + key, + group_id, + created_at, + KIND_GROUP_DELETE_GROUP.into(), + "", + ) +} + +fn tangle_v2_group_event( + key: FixtureKey, + group_id: &str, + created_at: u64, + kind: u64, + content: &str, +) -> Result<Event, String> { + Ok(pocket_protocol_group_event( + key, group_id, created_at, kind, content, + )) +} + fn signed_pocket_event( secret_byte: u8, created_at: u64, @@ -1266,13 +1407,9 @@ fn nip29_privacy_leak_suite_covers_relay_exposure_and_rejection_paths() { .expect("deleted target"), &deleted_target, ); - let delete_target = tangle_test_support::tangle_v2_delete_event_event( - FixtureKey::Owner, - "LeakDeleted", - &deleted_target, - 72, - ) - .expect("delete target"); + let delete_target = + tangle_v2_delete_event_event(FixtureKey::Owner, "LeakDeleted", &deleted_target, 72) + .expect("delete target"); assert_accepted( relay .handle_event_with_auth(delete_target.clone(), &owner_auth) @@ -1376,13 +1513,8 @@ fn delete_and_secondary_privacy_surfaces_are_read_gated_or_absent() { .expect("target"), &target, ); - let delete = tangle_test_support::tangle_v2_delete_event_event( - FixtureKey::Owner, - "DeleteFarm", - &target, - 3, - ) - .expect("delete"); + let delete = + tangle_v2_delete_event_event(FixtureKey::Owner, "DeleteFarm", &target, 3).expect("delete"); assert_accepted( relay .handle_event_with_auth(delete.clone(), &owner_auth) @@ -3078,8 +3210,9 @@ fn assert_accepted(message: RelayMessage, event: &Event) { message: String::new() } ); - assert!(event_id_matches(event)); - assert_eq!(verify_event_signature(event), Ok(())); + pocket_event_for_test(event) + .verify() + .expect("pocket verify"); } fn assert_accepted_pocket(message: RelayMessage, event: &Event) { diff --git a/crates/tangle_runtime/tests/ops_truthfulness.rs b/crates/tangle_runtime/tests/ops_truthfulness.rs @@ -2,7 +2,11 @@ use serde_json::json; use std::path::{Path, PathBuf}; -use tangle_protocol::{Event, RelayMessage, Tag, UnixTimestamp, event_to_value}; +use tangle_crypto::RelaySigner; +use tangle_protocol::{ + Event, EventId, Kind, PublicKeyHex, RelayMessage, SignatureHex, Tag, UnixTimestamp, + UnsignedEvent, event_to_value, +}; use tangle_runtime::{ config::{BaseRelayRuntimeConfig, parse_base_relay_runtime_config_json}, errors::BaseRelayError, @@ -14,10 +18,8 @@ use tangle_runtime::{ runtime::TangleRuntime, }; use tangle_store_pocket::parse_pocket_event_json; -use tangle_test_support::{ - FixtureKey, TANGLE_V2_RELAY_SECRET_HEX, TANGLE_V2_RELAY_URL, tangle_v2_auth_event, - tangle_v2_event, -}; +use tangle_store_pocket::{PocketEvent, PocketKind, PocketOwnedEvent, PocketOwnedTags, PocketTime}; +use tangle_test_support::{FixtureKey, TANGLE_V2_RELAY_SECRET_HEX, TANGLE_V2_RELAY_URL}; trait BaseRelayEventTestExt { fn handle_event(&self, event: Event) -> Result<RelayMessage, BaseRelayError>; @@ -57,6 +59,103 @@ fn authenticate_pocket_event_for_test( auth.authenticate_pocket(&pocket, now).map(|_| ()) } +fn tangle_v2_event( + key: FixtureKey, + created_at: u64, + kind: u64, + tags: Vec<Tag>, + content: &str, +) -> Result<Event, String> { + let event = ops_pocket_event(key, created_at, kind, tags, content); + ops_pocket_event_to_protocol(&event) +} + +fn tangle_v2_auth_event( + key: FixtureKey, + challenge: &str, + created_at: u64, +) -> Result<Event, String> { + tangle_v2_event( + key, + created_at, + 22_242, + vec![ + Tag::from_parts("relay", &[TANGLE_V2_RELAY_URL])?, + Tag::from_parts("challenge", &[challenge])?, + ], + "", + ) +} + +fn ops_pocket_event( + key: FixtureKey, + created_at: u64, + kind: u64, + tags: Vec<Tag>, + content: &str, +) -> PocketOwnedEvent { + let tags = ops_pocket_tags_from_protocol(&tags); + let secret = format!("{:02x}", fixture_secret_byte(key)).repeat(32); + RelaySigner::from_secret_hex(&secret) + .expect("signer") + .sign_pocket_event( + PocketKind::from_u16(u16::try_from(kind).expect("pocket kind")), + &tags, + PocketTime::from_u64(created_at), + content.as_bytes(), + ) + .expect("pocket event") +} + +fn ops_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 ops_pocket_event_to_protocol(event: &PocketEvent) -> Result<Event, String> { + let tags = event + .tags() + .map_err(|error| error.to_string())? + .iter() + .map(|tag| { + Tag::new( + tag.map(|value| { + std::str::from_utf8(value) + .map(str::to_owned) + .map_err(|error| error.to_string()) + }) + .collect::<Result<Vec<_>, _>>()?, + ) + .map_err(|error| error.to_string()) + }) + .collect::<Result<Vec<_>, _>>()?; + Ok(Event::new( + EventId::new(&event.id().as_hex_string()).map_err(|error| error.to_string())?, + UnsignedEvent::new( + PublicKeyHex::new(&event.pubkey().as_hex_string()) + .map_err(|error| error.to_string())?, + UnixTimestamp::new(event.created_at().as_u64()), + Kind::new(u64::from(event.kind().as_u16())).map_err(|error| error.to_string())?, + tags, + std::str::from_utf8(event.content()).map_err(|error| error.to_string())?, + ), + SignatureHex::new(&event.sig().to_string()).map_err(|error| error.to_string())?, + )) +} + +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 operations_surfaces_match_enforced_runtime_contracts() { let root = temp_root("ops-truthfulness"); diff --git a/crates/tangle_runtime/tests/phase2_acceptance_targets.rs b/crates/tangle_runtime/tests/phase2_acceptance_targets.rs @@ -14,9 +14,10 @@ use tangle_groups::{ GroupAuthContext, GroupAuthority, GroupErrorKind, GroupEventClass, GroupId, GroupMetadata, GroupMetadataFlags, GroupMetadataText, GroupPolicyConfig, GroupProjection, GroupReadDecision, GroupReadGate, GroupState, GroupWriteDecision, GroupWritePolicy, KIND_GROUP_ADMINS, - 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, + KIND_GROUP_CREATE_GROUP, KIND_GROUP_EDIT_METADATA, 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, @@ -35,11 +36,7 @@ use tangle_store_pocket::{ 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, - tangle_v2_event, tangle_v2_group_create_event, tangle_v2_group_event, - tangle_v2_group_metadata_event, tangle_v2_join_event, tangle_v2_put_user_event, -}; +use tangle_test_support::{FixtureKey, TANGLE_V2_RELAY_SECRET_HEX, TANGLE_V2_RELAY_URL}; use tokio::{net::TcpListener, time::timeout}; use tokio_tungstenite::tungstenite::{Message as TungsteniteMessage, client::IntoClientRequest}; @@ -196,6 +193,93 @@ fn pocket_protocol_group_event( ) } +fn tangle_v2_event( + key: FixtureKey, + created_at: u64, + kind: u64, + tags: Vec<Tag>, + content: &str, +) -> Result<Event, String> { + Ok(pocket_protocol_event(key, created_at, kind, tags, content)) +} + +fn tangle_v2_auth_event( + key: FixtureKey, + challenge: &str, + created_at: u64, +) -> Result<Event, String> { + Ok(pocket_protocol_auth_event(key, challenge, created_at)) +} + +fn tangle_v2_group_create_event( + key: FixtureKey, + group_id: &str, + created_at: u64, + flags: &[&str], +) -> Result<Event, String> { + Ok(pocket_protocol_group_create_event( + key, group_id, created_at, flags, + )) +} + +fn tangle_v2_group_metadata_event( + key: FixtureKey, + group_id: &str, + name: &str, + created_at: u64, + flags: &[&str], +) -> Result<Event, String> { + let mut tags = vec![ + Tag::from_parts("h", &[group_id])?, + Tag::from_parts("name", &[name])?, + ]; + for flag in flags { + tags.push(Tag::from_parts(flag, &[])?); + } + tangle_v2_event(key, created_at, KIND_GROUP_EDIT_METADATA.into(), tags, "") +} + +fn tangle_v2_join_event(key: FixtureKey, group_id: &str, created_at: u64) -> Result<Event, String> { + tangle_v2_group_event( + key, + group_id, + created_at, + KIND_GROUP_JOIN_REQUEST.into(), + "", + ) +} + +fn tangle_v2_put_user_event( + key: FixtureKey, + group_id: &str, + target: FixtureKey, + created_at: u64, +) -> Result<Event, String> { + let target_pubkey = target.public_key(); + tangle_v2_event( + key, + created_at, + KIND_GROUP_PUT_USER.into(), + vec![ + Tag::from_parts("h", &[group_id])?, + Tag::from_parts("p", &[target_pubkey.as_str()])?, + ], + "", + ) +} + +fn tangle_v2_group_event( + key: FixtureKey, + group_id: &str, + created_at: u64, + kind: u64, + content: &str, +) -> Result<Event, String> { + Ok(pocket_protocol_group_event( + key, group_id, created_at, kind, content, + )) +} + fn signed_pocket_event( secret_byte: u8, created_at: u64,