commit d5c68ab256ecddefc836164de764cff7bc005d38
parent 936e8bae39e0e824ecafcb40ff7162e43f3ed45a
Author: triesap <tyson@radroots.org>
Date: Tue, 16 Jun 2026 14:14:19 -0700
runtime: extract HLL group targets
Diffstat:
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, "");
}