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