tangle


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

commit ce63a11a05af95a61063984bb82cd006b2282cd3
parent a7bf20f220393b2905e4cd4fbb71564de71d1c62
Author: triesap <tyson@radroots.org>
Date:   Sun, 14 Jun 2026 08:40:29 -0700

ops: add truthfulness coverage

Diffstat:
Acrates/tangle_runtime/tests/ops_truthfulness.rs | 203+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 203 insertions(+), 0 deletions(-)

diff --git a/crates/tangle_runtime/tests/ops_truthfulness.rs b/crates/tangle_runtime/tests/ops_truthfulness.rs @@ -0,0 +1,203 @@ +#![forbid(unsafe_code)] + +use serde_json::json; +use std::path::{Path, PathBuf}; +use tangle_protocol::{RelayMessage, Tag, UnixTimestamp}; +use tangle_runtime::{ + config::{BaseRelayRuntimeConfig, parse_base_relay_runtime_config_json}, + logging::{TANGLE_LOG_REDACTED, TangleLogRedactor}, + nip11::BaseRelayInfoConfig, + ops::BaseRelayReadinessCheckStatus, + rate_limits::{TangleRateLimitKey, TangleRateLimitScope, TangleRateLimiter}, + relay::auth::BaseAuthState, + runtime::TangleRuntime, +}; +use tangle_test_support::{ + FixtureKey, TANGLE_V2_RELAY_SECRET_HEX, TANGLE_V2_RELAY_URL, tangle_v2_auth_event, + tangle_v2_event, +}; + +#[test] +fn operations_surfaces_match_enforced_runtime_contracts() { + let root = temp_root("ops-truthfulness"); + let _ = std::fs::remove_dir_all(&root); + let config = runtime_config(&root); + let document = BaseRelayInfoConfig::new("tangle", &config) + .expect("info config") + .build_document() + .expect("document"); + + assert_eq!(document.supported_nips, vec![1, 11, 29, 42, 45, 70]); + assert!(!document.supported_nips.contains(&77)); + assert_eq!(document.limitation.max_message_length, 1_048_576); + 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.default_limit, 100); + assert!(document.limitation.restricted_writes); + + let redactor = TangleLogRedactor::from_runtime_config(&config); + assert_eq!( + redactor.redact(format!("relay secret {TANGLE_V2_RELAY_SECRET_HEX}")), + format!("relay secret {TANGLE_LOG_REDACTED}") + ); + assert!(!format!("{redactor:?}").contains(TANGLE_V2_RELAY_SECRET_HEX)); + + let rate_limits = config.rate_limits(); + assert_eq!(rate_limits.auth().failures().max_hits(), 1); + assert_eq!(rate_limits.req().broad().window_seconds(), 60); + let limiter = TangleRateLimiter::new(); + let key = + TangleRateLimitKey::pubkey(TangleRateLimitScope::Auth, FixtureKey::Member.public_key()); + assert!( + limiter + .record( + key.clone(), + rate_limits.auth().failures(), + UnixTimestamp::new(100) + ) + .is_allowed() + ); + assert!( + !limiter + .record( + key.clone(), + rate_limits.auth().failures(), + UnixTimestamp::new(101) + ) + .is_allowed() + ); + + let runtime = TangleRuntime::open(config.clone()).expect("runtime"); + let pre_bind = runtime.readiness_state().response(); + assert_eq!(pre_bind.status, "not_ready"); + assert_eq!(pre_bind.checks.server_bind, "not_ready"); + assert_eq!(pre_bind.checks.group_projection, "ready"); + assert_eq!(pre_bind.checks.group_outbox_replay, "ready"); + let bound = runtime + .readiness_state() + .clone() + .with_server_bind(BaseRelayReadinessCheckStatus::Ready) + .response(); + assert_eq!(bound.status, "ready"); + assert_eq!(bound.checks.server_bind, "ready"); + + let mut relay = config.open_relay().expect("relay"); + let protected = tangle_v2_event( + FixtureKey::Member, + 1_714_124_433, + 1, + vec![Tag::from_parts("-", &[]).expect("protected")], + "protected", + ) + .expect("protected event"); + assert_eq!( + relay.handle_event(protected.clone()).expect("unauth"), + RelayMessage::Ok { + event_id: protected.id().clone(), + accepted: false, + message: "auth-required: protected event requires authenticated event author" + .to_owned() + } + ); + + let mut auth = BaseAuthState::new(TANGLE_V2_RELAY_URL, 300, 600).expect("auth"); + auth.issue_challenge("challenge-a", UnixTimestamp::new(1_714_124_433)) + .expect("challenge"); + auth.authenticate( + &tangle_v2_auth_event(FixtureKey::Member, "challenge-a", 1_714_124_433).expect("auth"), + UnixTimestamp::new(1_714_124_433), + ) + .expect("author auth"); + assert_eq!( + relay + .handle_event_with_auth(protected.clone(), &auth) + .expect("author write"), + RelayMessage::Ok { + event_id: protected.id().clone(), + accepted: true, + message: String::new() + } + ); + + let _ = std::fs::remove_dir_all(root); +} + +fn runtime_config(root: &Path) -> BaseRelayRuntimeConfig { + parse_base_relay_runtime_config_json( + &json!({ + "server": { + "listen_addr": "127.0.0.1:0", + "relay_url": TANGLE_V2_RELAY_URL + }, + "pocket": { + "data_directory": root.join("pocket"), + "map_size_bytes": 1073741824_u64, + "reader_slots": 128, + "sync_policy": "flush_on_shutdown" + }, + "groups": { + "enabled": true, + "canonical_relay_url": TANGLE_V2_RELAY_URL, + "relay_secret": TANGLE_V2_RELAY_SECRET_HEX, + "owner_pubkeys": [FixtureKey::Owner.public_key().as_str()], + "admin_pubkeys": [FixtureKey::Admin.public_key().as_str()] + }, + "auth": { + "challenge_ttl_seconds": 300, + "created_at_skew_seconds": 600 + }, + "limits": { + "max_message_length": 1048576, + "max_subid_length": 64, + "max_subscriptions_per_connection": 64, + "max_filters_per_request": 10, + "max_tag_values_per_filter": 100, + "max_limit": 500, + "default_limit": 100, + "max_event_tags": 200, + "max_content_length": 65536, + "broadcast_channel_capacity": 16, + "per_connection_outbound_queue": 8 + }, + "rate_limits": { + "auth": { + "per_pubkey": {"window_seconds": 60, "max_hits": 30}, + "failures": {"window_seconds": 60, "max_hits": 1} + }, + "event": { + "per_pubkey": {"window_seconds": 60, "max_hits": 120}, + "per_kind": {"window_seconds": 60, "max_hits": 1000} + }, + "group": { + "write_per_pubkey": {"window_seconds": 60, "max_hits": 60}, + "write_per_group": {"window_seconds": 60, "max_hits": 90}, + "write_per_kind": {"window_seconds": 60, "max_hits": 300}, + "join_flow": {"window_seconds": 300, "max_hits": 10} + }, + "req": { + "per_ip": {"window_seconds": 60, "max_hits": 600}, + "per_connection": {"window_seconds": 60, "max_hits": 120}, + "per_pubkey": {"window_seconds": 60, "max_hits": 240}, + "per_group": {"window_seconds": 60, "max_hits": 240}, + "per_kind": {"window_seconds": 60, "max_hits": 500}, + "broad": {"window_seconds": 60, "max_hits": 30} + }, + "count": { + "per_ip": {"window_seconds": 60, "max_hits": 300}, + "per_connection": {"window_seconds": 60, "max_hits": 60}, + "per_pubkey": {"window_seconds": 60, "max_hits": 120}, + "per_group": {"window_seconds": 60, "max_hits": 120}, + "per_kind": {"window_seconds": 60, "max_hits": 240}, + "broad": {"window_seconds": 60, "max_hits": 20} + } + } + }) + .to_string(), + ) + .expect("config") +} + +fn temp_root(name: &str) -> PathBuf { + std::env::temp_dir().join(format!("tangle-ops-{name}-{}", std::process::id())) +}