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:
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,