mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-09-14 04:45:07 +00:00
Production gitnostr.com repeatedly received CLOSED responses from relay.ngit.dev after reconnect: its 34-filter live set for 924 full repositories, 89 state-only repositories, and 3,748 roots crossed rust-nostr 0.45's newly selected 1 MiB cumulative subscription-state limit. Fourteen representative REQs were accepted and the remainder were refused, silently leaving persistent live coverage incomplete; cooldown recovery only recreated the same impossible set. Raise the explicitly selected per-connection retained subscription-state allowance to 5 MiB. This remains a finite boundary, matches the largest individual WebSocket message already admitted, and leaves roughly four times the observed working-set headroom without promising the theoretical 500 x 96 KiB maximum. Document the exact serving policy and its lack of NIP-11 negotiation. A scenario opens 17 persistent sub-96 KiB REQs carrying 34 filters. It failed unchanged 2.1.1 at live-14 with the production CLOSED reason and passes with the new bound. Correctness assumes this allowance is enforced per connection by rust-nostr; client-side adaptation to unknown third-party cumulative byte limits and multi-connection sharding remain excluded because NIP-11 exposes no such capability. Validation: focused scenario passed; 658 library tests passed; git diff --check passed; nix build .#ngit-grasp passed.
65 lines
2.2 KiB
Rust
65 lines
2.2 KiB
Rust
//! Connection-wide active-subscription state regression scenarios.
|
|
//!
|
|
//! Repository sync can legitimately keep many byte-budgeted filters live on
|
|
//! one relay connection. The cumulative allowance must accommodate that
|
|
//! coverage even though each individual REQ remains conservatively bounded.
|
|
|
|
mod common;
|
|
|
|
use std::time::Duration;
|
|
|
|
use common::TestRelay;
|
|
use futures_util::{SinkExt, StreamExt};
|
|
use tokio_tungstenite::tungstenite::Message;
|
|
|
|
#[tokio::test]
|
|
async fn repository_scale_live_filter_set_remains_admitted() {
|
|
let relay = TestRelay::start().await;
|
|
let (mut stream, _) = tokio_tungstenite::connect_async(relay.url())
|
|
.await
|
|
.expect("connect to relay");
|
|
|
|
// Two 550-value filters are about 74 KiB. Seventeen persistent REQs model
|
|
// the 34-filter live set that production rejected after crossing the old
|
|
// 1 MiB cumulative default, while every individual message stays below
|
|
// the sync client's 96 KiB REQ budget.
|
|
let ids = (0..550)
|
|
.map(|index| format!("{index:064x}"))
|
|
.collect::<Vec<_>>();
|
|
let filter = serde_json::json!({"#e": ids, "limit": 0});
|
|
|
|
for index in 0..17 {
|
|
let request = serde_json::json!(["REQ", format!("live-{index}"), filter, filter]);
|
|
assert!(request.to_string().len() < 96 * 1024);
|
|
stream
|
|
.send(Message::Text(request.to_string().into()))
|
|
.await
|
|
.expect("send persistent live REQ");
|
|
}
|
|
|
|
let admitted = tokio::time::timeout(Duration::from_secs(10), async {
|
|
let mut eose = 0;
|
|
while let Some(message) = stream.next().await {
|
|
let text = message.expect("valid relay response").into_text().unwrap();
|
|
assert!(
|
|
!text.contains("active subscriptions exceed max size"),
|
|
"repository-scale live coverage was rejected: {text}"
|
|
);
|
|
if text.starts_with("[\"EOSE\",\"live-") {
|
|
eose += 1;
|
|
if eose == 17 {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
panic!("relay closed before acknowledging every live REQ");
|
|
})
|
|
.await;
|
|
|
|
assert!(
|
|
admitted.is_ok(),
|
|
"relay did not acknowledge live coverage in time"
|
|
);
|
|
relay.stop().await;
|
|
}
|