tangle


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

commit f6c6c9f246373789abcdaf60c78964e660a7831f
parent 871a5f84cc81d508d579f7fd6e9bb50069801f83
Author: triesap <tyson@radroots.org>
Date:   Sun, 14 Jun 2026 12:49:44 -0700

config: require explicit query complexity

- add max_query_complexity to runtime config parsing
- remove the derived query complexity budget
- advertise the enforced budget through NIP-11
- cover required config and limitation truthfulness tests

Diffstat:
Mcrates/tangle/tests/version.rs | 2++
Mcrates/tangle_runtime/src/config.rs | 33+++++++++++++++++++++++++--------
Mcrates/tangle_runtime/src/nip11.rs | 4++++
Mcrates/tangle_runtime/src/runtime.rs | 1+
Mcrates/tangle_runtime/src/server.rs | 2++
Mcrates/tangle_runtime/src/session.rs | 1+
Mcrates/tangle_runtime/tests/base_relay_v2.rs | 2++
Mcrates/tangle_runtime/tests/ops_truthfulness.rs | 2++
Mcrates/tangle_runtime/tests/phase2_acceptance_targets.rs | 1+
9 files changed, 40 insertions(+), 8 deletions(-)

diff --git a/crates/tangle/tests/version.rs b/crates/tangle/tests/version.rs @@ -130,6 +130,7 @@ fn tangle_run_starts_server_and_stays_alive_until_shutdown() { "max_subscriptions_per_connection": 64, "max_filters_per_request": 10, "max_tag_values_per_filter": 100, + "max_query_complexity": 2048, "max_limit": 500, "default_limit": 100, "max_event_tags": 200, @@ -194,6 +195,7 @@ fn tangle_run_starts_server_and_stays_alive_until_shutdown() { assert_eq!(nip11_value["limitation"]["max_subscriptions"], 64); assert_eq!(nip11_value["limitation"]["max_filters"], 10); assert_eq!(nip11_value["limitation"]["max_limit"], 500); + assert_eq!(nip11_value["limitation"]["max_query_complexity"], 2_048); assert_eq!(nip11_value["limitation"]["max_subid_length"], 64); assert_eq!(nip11_value["limitation"]["max_event_tags"], 200); assert_eq!(nip11_value["limitation"]["max_content_length"], 65_536); diff --git a/crates/tangle_runtime/src/config.rs b/crates/tangle_runtime/src/config.rs @@ -152,6 +152,7 @@ pub struct BaseRelayRuntimeLimitsConfig { max_subscriptions_per_connection: usize, max_filters_per_request: usize, max_tag_values_per_filter: usize, + max_query_complexity: usize, max_limit: u64, default_limit: u64, max_event_tags: usize, @@ -176,6 +177,7 @@ impl BaseRelayRuntimeLimitsConfig { "limits.max_tag_values_per_filter", document.max_tag_values_per_filter, )?; + require_positive("limits.max_query_complexity", document.max_query_complexity)?; require_positive_u64("limits.max_limit", document.max_limit)?; require_positive_u64("limits.default_limit", document.default_limit)?; require_positive("limits.max_event_tags", document.max_event_tags)?; @@ -205,6 +207,7 @@ impl BaseRelayRuntimeLimitsConfig { max_subscriptions_per_connection: document.max_subscriptions_per_connection, max_filters_per_request: document.max_filters_per_request, max_tag_values_per_filter: document.max_tag_values_per_filter, + max_query_complexity: document.max_query_complexity, max_limit: document.max_limit, default_limit: document.default_limit, max_event_tags: document.max_event_tags, @@ -234,6 +237,10 @@ impl BaseRelayRuntimeLimitsConfig { self.max_tag_values_per_filter } + pub fn max_query_complexity(self) -> usize { + self.max_query_complexity + } + pub fn max_limit(self) -> u64 { self.max_limit } @@ -265,20 +272,13 @@ impl BaseRelayRuntimeLimitsConfig { max_subscriptions: self.max_subscriptions_per_connection, max_filters_per_request: self.max_filters_per_request, max_tag_values_per_filter: self.max_tag_values_per_filter, - max_query_complexity: self.query_complexity_budget(), + max_query_complexity: self.max_query_complexity, max_event_tags: self.max_event_tags, max_content_length: self.max_content_length, max_limit: self.max_limit, default_limit: self.default_limit, }) } - - fn query_complexity_budget(self) -> usize { - usize::try_from(self.max_limit) - .unwrap_or(usize::MAX) - .saturating_add(self.max_tag_values_per_filter) - .saturating_add(self.max_filters_per_request) - } } #[derive(Debug, Deserialize)] @@ -332,6 +332,7 @@ struct BaseRelayRuntimeLimitsDocument { max_subscriptions_per_connection: usize, max_filters_per_request: usize, max_tag_values_per_filter: usize, + max_query_complexity: usize, max_limit: u64, default_limit: u64, max_event_tags: usize, @@ -616,6 +617,7 @@ mod tests { assert_eq!(config.limits().max_subscriptions_per_connection(), 64); assert_eq!(config.limits().max_filters_per_request(), 10); assert_eq!(config.limits().max_tag_values_per_filter(), 100); + assert_eq!(config.limits().max_query_complexity(), 2_048); assert_eq!(config.limits().max_limit(), 500); assert_eq!(config.limits().default_limit(), 100); assert_eq!(config.limits().max_event_tags(), 200); @@ -682,6 +684,7 @@ mod tests { "max_subscriptions_per_connection": 64, "max_filters_per_request": 10, "max_tag_values_per_filter": 100, + "max_query_complexity": 2048, "max_limit": 500, "default_limit": 100, "max_event_tags": 200, @@ -757,6 +760,7 @@ mod tests { "max_subscriptions_per_connection": 64, "max_filters_per_request": 10, "max_tag_values_per_filter": 100, + "max_query_complexity": 2048, "max_limit": 500, "default_limit": 100, "max_event_tags": 200, @@ -829,6 +833,7 @@ mod tests { "max_subscriptions_per_connection": 64, "max_filters_per_request": 10, "max_tag_values_per_filter": 100, + "max_query_complexity": 2048, "max_limit": 500, "default_limit": 100, "max_event_tags": 200, @@ -877,4 +882,16 @@ mod tests { .contains("unknown field `max_unimplemented_limit`") ); } + + #[test] + fn base_relay_runtime_config_requires_explicit_query_complexity() { + let raw = include_str!("../../../ops/production/tangle-v2.example.json") + .replace(" \"max_query_complexity\": 2048,\n", ""); + assert!( + parse_base_relay_runtime_config_json(&raw) + .expect_err("missing query complexity") + .prefixed_message() + .contains("missing field `max_query_complexity`") + ); + } } diff --git a/crates/tangle_runtime/src/nip11.rs b/crates/tangle_runtime/src/nip11.rs @@ -102,6 +102,7 @@ impl BaseRelayInfoConfig { max_subscriptions: self.limits.max_subscriptions_per_connection(), max_filters: self.limits.max_filters_per_request(), max_limit: self.limits.max_limit(), + max_query_complexity: self.limits.max_query_complexity(), max_subid_length: self.limits.max_subid_length(), max_event_tags: self.limits.max_event_tags(), max_content_length: self.limits.max_content_length(), @@ -143,6 +144,7 @@ pub struct BaseRelayInfoLimitationDocument { pub max_subscriptions: usize, pub max_filters: usize, pub max_limit: u64, + pub max_query_complexity: usize, pub max_subid_length: usize, pub max_event_tags: usize, pub max_content_length: usize, @@ -251,6 +253,7 @@ mod tests { assert_eq!(document.limitation.max_subscriptions, 64); assert_eq!(document.limitation.max_filters, 10); assert_eq!(document.limitation.max_limit, 500); + assert_eq!(document.limitation.max_query_complexity, 2_048); assert_eq!(document.limitation.max_subid_length, 64); assert_eq!(document.limitation.max_event_tags, 200); assert_eq!(document.limitation.max_content_length, 65_536); @@ -363,6 +366,7 @@ mod tests { "max_subscriptions_per_connection": 64, "max_filters_per_request": 10, "max_tag_values_per_filter": 100, + "max_query_complexity": 2048, "max_limit": 500, "default_limit": 100, "max_event_tags": 200, diff --git a/crates/tangle_runtime/src/runtime.rs b/crates/tangle_runtime/src/runtime.rs @@ -1957,6 +1957,7 @@ mod tests { "max_subscriptions_per_connection": 64, "max_filters_per_request": 10, "max_tag_values_per_filter": 100, + "max_query_complexity": 2048, "max_limit": 500, "default_limit": 100, "max_event_tags": 200, diff --git a/crates/tangle_runtime/src/server.rs b/crates/tangle_runtime/src/server.rs @@ -474,6 +474,7 @@ mod tests { assert_eq!(nip11_value["limitation"]["max_subscriptions"], 64); assert_eq!(nip11_value["limitation"]["max_filters"], 10); assert_eq!(nip11_value["limitation"]["max_limit"], 500); + assert_eq!(nip11_value["limitation"]["max_query_complexity"], 2_048); assert_eq!(nip11_value["limitation"]["max_subid_length"], 64); assert_eq!(nip11_value["limitation"]["max_event_tags"], 200); assert_eq!(nip11_value["limitation"]["max_content_length"], 65_536); @@ -539,6 +540,7 @@ mod tests { "max_subscriptions_per_connection": 64, "max_filters_per_request": 10, "max_tag_values_per_filter": 100, + "max_query_complexity": 2048, "max_limit": 500, "default_limit": 100, "max_event_tags": 200, diff --git a/crates/tangle_runtime/src/session.rs b/crates/tangle_runtime/src/session.rs @@ -740,6 +740,7 @@ mod tests { "max_subscriptions_per_connection": 64, "max_filters_per_request": 10, "max_tag_values_per_filter": 100, + "max_query_complexity": 2048, "max_limit": 500, "default_limit": 100, "max_event_tags": 200, diff --git a/crates/tangle_runtime/tests/base_relay_v2.rs b/crates/tangle_runtime/tests/base_relay_v2.rs @@ -96,6 +96,7 @@ fn nip11_integration_reports_group_contracts() { assert_eq!(document.limitation.max_subscriptions, 64); assert_eq!(document.limitation.max_filters, 10); assert_eq!(document.limitation.max_limit, 500); + assert_eq!(document.limitation.max_query_complexity, 2_048); assert_eq!(document.limitation.max_subid_length, 64); assert_eq!(document.limitation.max_event_tags, 200); assert_eq!(document.limitation.max_content_length, 65_536); @@ -1995,6 +1996,7 @@ fn runtime_config(groups_enabled: bool) -> BaseRelayRuntimeConfig { "max_subscriptions_per_connection": 64, "max_filters_per_request": 10, "max_tag_values_per_filter": 100, + "max_query_complexity": 2048, "max_limit": 500, "default_limit": 100, "max_event_tags": 200, diff --git a/crates/tangle_runtime/tests/ops_truthfulness.rs b/crates/tangle_runtime/tests/ops_truthfulness.rs @@ -33,6 +33,7 @@ fn operations_surfaces_match_enforced_runtime_contracts() { assert_eq!(document.limitation.max_subscriptions, 64); assert_eq!(document.limitation.max_filters, 10); assert_eq!(document.limitation.max_limit, 500); + assert_eq!(document.limitation.max_query_complexity, 2_048); assert_eq!(document.limitation.default_limit, 100); assert!(document.limitation.restricted_writes); @@ -153,6 +154,7 @@ fn runtime_config(root: &Path) -> BaseRelayRuntimeConfig { "max_subscriptions_per_connection": 64, "max_filters_per_request": 10, "max_tag_values_per_filter": 100, + "max_query_complexity": 2048, "max_limit": 500, "default_limit": 100, "max_event_tags": 200, diff --git a/crates/tangle_runtime/tests/phase2_acceptance_targets.rs b/crates/tangle_runtime/tests/phase2_acceptance_targets.rs @@ -1623,6 +1623,7 @@ fn runtime_config_value(root: &Path, listen_addr: SocketAddr) -> Value { "max_subscriptions_per_connection": 64, "max_filters_per_request": 10, "max_tag_values_per_filter": 100, + "max_query_complexity": 2048, "max_limit": 500, "default_limit": 100, "max_event_tags": 200,