commit a45d9b443f1ff42f99a846c04678b909e46d8850
parent 561fa31180e332da0796f7008d5e1467d1b50711
Author: triesap <tyson@radroots.org>
Date: Sun, 14 Jun 2026 17:23:24 -0700
protocol: prove hot path coverage
Diffstat:
4 files changed, 257 insertions(+), 0 deletions(-)
diff --git a/crates/tangle_bench/src/lib.rs b/crates/tangle_bench/src/lib.rs
@@ -1632,6 +1632,22 @@ mod tests {
}
#[test]
+ fn protocol_conversion_for_supported_profile_sizes_is_bounded() {
+ let dataset =
+ BenchDataset::generate(BenchDatasetConfig::new(4, 3, 3, 4, 3)).expect("dataset");
+ let mut total_event_json_bytes = 0_usize;
+ for source in dataset.source_events() {
+ let event_json = tangle_protocol::event_to_value(source.event()).to_string();
+ total_event_json_bytes += event_json.len();
+ assert!(
+ tangle_protocol::parse_client_message(&format!("[\"EVENT\",{event_json}]")).is_ok()
+ );
+ }
+
+ assert!(total_event_json_bytes < 1_000_000);
+ }
+
+ #[test]
fn benchmark_profiles_are_explicit_and_unknown_profiles_fail_closed() {
assert_eq!(
BenchmarkProfileName::all()
diff --git a/crates/tangle_protocol/src/lib.rs b/crates/tangle_protocol/src/lib.rs
@@ -1681,6 +1681,104 @@ mod tests {
}
#[test]
+ fn nip01_client_and_relay_message_conformance_vectors_are_exact() {
+ let event_payload = event_json("a", "b", 1, tags_json());
+ let event =
+ parse_event_json(&RawEventJson::new(&event_payload).expect("raw")).expect("event");
+ let subscription_id = SubscriptionId::new("sub-vector").expect("sub");
+ let event_id = EventId::new(&"c".repeat(EventId::HEX_LENGTH)).expect("id");
+ let client_vectors = [
+ (
+ format!("[\"EVENT\",{event_payload}]"),
+ ClientMessage::Event(event.clone()),
+ ),
+ (
+ format!("[\"AUTH\",{event_payload}]"),
+ ClientMessage::Auth(event.clone()),
+ ),
+ (
+ "[\"REQ\",\"sub-vector\",{\"kinds\":[1]}]".to_owned(),
+ ClientMessage::Req {
+ subscription_id: subscription_id.clone(),
+ filters: vec![
+ filter_from_value(&serde_json::json!({"kinds":[1]})).expect("filter"),
+ ],
+ },
+ ),
+ (
+ "[\"COUNT\",\"sub-vector\",{\"kinds\":[1]}]".to_owned(),
+ ClientMessage::Count {
+ subscription_id: subscription_id.clone(),
+ filters: vec![
+ filter_from_value(&serde_json::json!({"kinds":[1]})).expect("filter"),
+ ],
+ },
+ ),
+ (
+ "[\"CLOSE\",\"sub-vector\"]".to_owned(),
+ ClientMessage::Close(subscription_id.clone()),
+ ),
+ ];
+ for (raw, expected) in client_vectors {
+ assert_eq!(parse_client_message(&raw), Ok(expected));
+ }
+ let relay_vectors = [
+ (
+ RelayMessage::Event {
+ subscription_id: subscription_id.clone(),
+ event: event.clone(),
+ },
+ serde_json::json!(["EVENT", "sub-vector", event_to_value(&event)]),
+ ),
+ (
+ RelayMessage::Ok {
+ event_id: event_id.clone(),
+ accepted: true,
+ message: String::new(),
+ },
+ serde_json::json!(["OK", event_id.as_str(), true, ""]),
+ ),
+ (
+ RelayMessage::Eose(subscription_id.clone()),
+ serde_json::json!(["EOSE", "sub-vector"]),
+ ),
+ (
+ RelayMessage::Closed {
+ subscription_id: subscription_id.clone(),
+ message: "unsupported: search filters are not supported".to_owned(),
+ },
+ serde_json::json!([
+ "CLOSED",
+ "sub-vector",
+ "unsupported: search filters are not supported"
+ ]),
+ ),
+ (
+ RelayMessage::Count {
+ subscription_id: subscription_id.clone(),
+ count: 3,
+ },
+ serde_json::json!(["COUNT", "sub-vector", {"count": 3}]),
+ ),
+ (
+ RelayMessage::Notice("invalid: bad envelope".to_owned()),
+ serde_json::json!(["NOTICE", "invalid: bad envelope"]),
+ ),
+ (
+ RelayMessage::Auth("challenge".to_owned()),
+ serde_json::json!(["AUTH", "challenge"]),
+ ),
+ ];
+ for (message, expected) in relay_vectors {
+ assert_eq!(relay_message_to_value(&message), expected);
+ assert_eq!(
+ serde_json::from_str::<serde_json::Value>(&message.encode()).expect("encoded"),
+ expected
+ );
+ }
+ }
+
+ #[test]
fn protocol_conformance_fixtures_cover_dense_envelopes_and_parser_stress() {
let event_payload = event_json(
"a",
@@ -1848,6 +1946,38 @@ mod tests {
}
#[test]
+ fn malformed_client_message_corpus_is_rejected_or_parsed_without_panic() {
+ let valid_event = event_json("a", "b", 1, tags_json());
+ let long_sub = "x".repeat(SubscriptionId::MAX_LENGTH + 1);
+ let oversized_kind = u64::from(u32::MAX) + 1;
+ let corpus = [
+ String::new(),
+ "[".to_owned(),
+ "null".to_owned(),
+ "[null]".to_owned(),
+ "[\"EVENT\",null]".to_owned(),
+ format!("[\"EVENT\",{valid_event},{}]", serde_json::json!({})),
+ "[\"REQ\",{},{}]".to_owned(),
+ "[\"REQ\",\"sub\",{\"ids\":[]}]".to_owned(),
+ "[\"REQ\",\"sub\",{\"ids\":[1]}]".to_owned(),
+ "[\"REQ\",\"sub\",{\"#aa\":[\"value\"]}]".to_owned(),
+ format!("[\"REQ\",\"sub\",{{\"kinds\":[{oversized_kind}]}}]"),
+ format!("[\"REQ\",\"{long_sub}\",{{}}]"),
+ "[\"COUNT\",\"sub\",{\"authors\":[\"BAD\"]}]".to_owned(),
+ "[\"COUNT\",\"sub\",{\"unknown\":true}]".to_owned(),
+ "[\"CLOSE\",\"sub\",{}]".to_owned(),
+ "[\"AUTH\",[]]".to_owned(),
+ "[\"NOTICE\",\"not a client command\"]".to_owned(),
+ ];
+ for raw in corpus {
+ std::panic::catch_unwind(|| {
+ let _ = parse_client_message(&raw);
+ })
+ .expect("parser must not panic");
+ }
+ }
+
+ #[test]
fn parse_event_json_rejects_invalid_event_shapes() {
assert_eq!(
parse_event_json(&RawEventJson::new("{").expect("raw"))
@@ -2299,6 +2429,52 @@ mod tests {
);
}
+ #[test]
+ fn canonical_event_json_preserves_large_tags_without_shape_drift() {
+ let tags = (0..64)
+ .map(|index| Tag::from_parts("t", &[&format!("topic-{index}")]).expect("tag"))
+ .collect::<Vec<_>>();
+ let event = unsigned_event(tags, "large tag set");
+ let value =
+ serde_json::from_str::<serde_json::Value>(&event.canonical_json()).expect("json");
+
+ assert_eq!(value[0], 0);
+ assert_eq!(value[4].as_array().expect("tags").len(), 64);
+ assert_eq!(value[5], "large tag set");
+ }
+
+ #[test]
+ fn parser_fuzz_style_scalar_corpus_is_total() {
+ let ids = ["0", "a", "f", "g", "A", "00", "ff"];
+ for id_seed in ids {
+ let id = id_seed.repeat(EventId::HEX_LENGTH);
+ let raw = serde_json::json!([
+ "EVENT",
+ {
+ "id": id,
+ "pubkey": "1".repeat(PublicKeyHex::HEX_LENGTH),
+ "created_at": 1_714_124_433_u64,
+ "kind": 1,
+ "tags": [["e", "a".repeat(EventId::HEX_LENGTH)]],
+ "content": "fuzz",
+ "sig": "2".repeat(SignatureHex::HEX_LENGTH)
+ }
+ ])
+ .to_string();
+ std::panic::catch_unwind(|| {
+ let _ = parse_client_message(&raw);
+ })
+ .expect("event parser must not panic");
+ }
+ for size in [1_usize, 2, 4, 8, 16, 32, 64, 128] {
+ let values = (0..size)
+ .map(|index| serde_json::Value::String(format!("topic-{index}")))
+ .collect::<Vec<_>>();
+ let raw = serde_json::json!(["REQ", "fuzz", {"#t": values}]).to_string();
+ assert!(parse_client_message(&raw).is_ok());
+ }
+ }
+
fn unsigned_event(tags: Vec<Tag>, content: &str) -> UnsignedEvent {
UnsignedEvent::new(
PublicKeyHex::new(&"1".repeat(PublicKeyHex::HEX_LENGTH)).expect("pubkey"),
diff --git a/crates/tangle_runtime/src/lib.rs b/crates/tangle_runtime/src/lib.rs
@@ -68,3 +68,31 @@ pub fn open_tangle_runtime_from_config_path(
let config = load_base_relay_runtime_config(path)?;
TangleRuntime::open(config).map_err(TangleRuntimeLoadError::OpenRelay)
}
+
+#[cfg(test)]
+mod tests {
+ use crate::pocket_conversion::{pocket_event_to_tangle, tangle_event_to_pocket};
+ use tangle_protocol::{Tag, event_from_value, event_to_value};
+ use tangle_test_support::{FixtureKey, tangle_v2_event};
+
+ #[test]
+ fn pocket_event_conversion_accepts_protocol_event_json_shapes() {
+ let event = tangle_v2_event(
+ FixtureKey::Owner,
+ 1_714_124_433,
+ 30_402,
+ vec![
+ Tag::from_parts("d", &["market"]).expect("d"),
+ Tag::from_parts("t", &["radroots", "farm"]).expect("t"),
+ ],
+ "json parity",
+ )
+ .expect("event");
+ let parsed = event_from_value(&event_to_value(&event)).expect("parsed");
+ let pocket = tangle_event_to_pocket(&parsed).expect("pocket");
+ let converted = pocket_event_to_tangle(&pocket).expect("converted");
+
+ assert_eq!(parsed, event);
+ assert_eq!(converted, event);
+ }
+}
diff --git a/crates/tangle_runtime/src/pocket_conversion.rs b/crates/tangle_runtime/src/pocket_conversion.rs
@@ -264,4 +264,41 @@ mod tests {
assert!(pocket_filter.event_matches(&pocket_event).expect("match"));
}
+
+ #[test]
+ fn pocket_filter_conversion_matches_tangle_filter_matching_for_supported_fields() {
+ let event = tangle_v2_event(
+ FixtureKey::Member,
+ 1_714_124_433,
+ 1,
+ vec![
+ Tag::from_parts("e", &[&"a".repeat(64)]).expect("e"),
+ Tag::from_parts("p", &[FixtureKey::Owner.public_key().as_str()]).expect("p"),
+ Tag::from_parts("t", &["market"]).expect("t"),
+ ],
+ "filter parity",
+ )
+ .expect("event");
+ let pocket_event = tangle_event_to_pocket(&event).expect("event");
+ for value in [
+ serde_json::json!({"ids": [event.id().as_str()]}),
+ serde_json::json!({"authors": [event.unsigned().pubkey().as_str()]}),
+ serde_json::json!({"kinds": [1]}),
+ serde_json::json!({"#e": ["a".repeat(64)]}),
+ serde_json::json!({"#p": [FixtureKey::Owner.public_key().as_str()]}),
+ serde_json::json!({"#t": ["market"]}),
+ serde_json::json!({"since": 1_714_124_400, "until": 1_714_124_500}),
+ serde_json::json!({"limit": 1}),
+ serde_json::json!({"kinds": [2]}),
+ serde_json::json!({"#t": ["other"]}),
+ ] {
+ let filter = filter_from_value(&value).expect("filter");
+ let pocket_filter = tangle_filter_to_pocket(&filter).expect("pocket filter");
+
+ assert_eq!(
+ pocket_filter.event_matches(&pocket_event).expect("match"),
+ filter.matches(&event)
+ );
+ }
+ }
}