@@ -23,12 +23,28 @@ export const DEFAULT_PROBE_RELAYS = [
2323 'wss://purplepag.es' , 'wss://nostr.mom' ,
2424] ;
2525
26- /** The curated set of relays the prober is allowed to dial (screened). */
26+ /** The curated allowlist of relays the prober always dials (screened). */
2727export function proberRelays ( env = process . env ) {
2828 const raw = env . PROBE_RELAYS ? env . PROBE_RELAYS . split ( ',' ) : DEFAULT_PROBE_RELAYS ;
2929 return [ ...new Set ( raw . map ( ( u ) => safeRelayUrl ( String ( u ) . trim ( ) ) ) . filter ( Boolean ) ) ] ;
3030}
3131
32+ /**
33+ * Build the sweep target set: the allowlist plus already-verified candidates
34+ * (relays that have been online ≥ 1×), screened and capped. Brand-new
35+ * harvested candidates are NOT included — only relays that already proved
36+ * themselves, so a freshly-published relay list can't make us dial new hosts.
37+ */
38+ export function selectTargets ( allowlist , candidates , cap = 1000 ) {
39+ const set = new Set ( allowlist ) ;
40+ for ( const c of candidates ) {
41+ if ( set . size >= cap ) break ;
42+ const u = safeRelayUrl ( c ) ;
43+ if ( u ) set . add ( u ) ;
44+ }
45+ return [ ...set ] . slice ( 0 , cap ) ;
46+ }
47+
3248export const USAGE = `beacon relay-health prober
3349
3450Usage: node probe.js [flags] (or: node src/prober.js [flags])
@@ -38,11 +54,13 @@ Flags (override the matching env var):
3854 --concurrency <n> relays probed in parallel (env PROBE_CONCURRENCY, default 25)
3955 --timeout <ms> per-relay connect/HTTP timeout (env PROBE_TIMEOUT, default 7000)
4056 --interval <ms> sweep interval when scheduling (env PROBE_INTERVAL, default 86400000 = daily)
57+ --max <n> cap relays probed per sweep (env PROBE_MAX, default 1000)
4158 -h, --help show this help
4259
43- Targets: a curated allowlist only (env PROBE_RELAYS, comma list; default: a
44- small built-in set of well-known relays). The harvested directory is NEVER
45- dialed — expand the allowlist deliberately.` ;
60+ Targets: the allowlist (env PROBE_RELAYS, comma list; default: a small built-in
61+ set) PLUS already-verified relays (online ≥ 1×), capped at --max. Brand-new
62+ harvested candidates are never dialed — only relays that already proved
63+ themselves, so a freshly-published relay list can't redirect our outbound.` ;
4664
4765/** Parse prober CLI flags into an overlay (only keys the user passed). */
4866export function parseArgs ( argv = process . argv . slice ( 2 ) ) {
@@ -55,6 +73,7 @@ export function parseArgs(argv = process.argv.slice(2)) {
5573 else if ( a === '--concurrency' ) { const v = numNext ( i ) ; if ( v !== undefined ) { out . concurrency = v ; i ++ ; } }
5674 else if ( a === '--timeout' ) { const v = numNext ( i ) ; if ( v !== undefined ) { out . timeout = v ; i ++ ; } }
5775 else if ( a === '--interval' ) { const v = numNext ( i ) ; if ( v !== undefined ) { out . interval = v ; i ++ ; } }
76+ else if ( a === '--max' ) { const v = numNext ( i ) ; if ( v !== undefined ) { out . max = v ; i ++ ; } }
5877 }
5978 return out ;
6079}
@@ -67,6 +86,7 @@ export function proberConfig(env = process.env, argv = process.argv.slice(2)) {
6786 interval : flags . interval ?? pos ( env . PROBE_INTERVAL , 86_400_000 ) ,
6887 concurrency : flags . concurrency ?? pos ( env . PROBE_CONCURRENCY , 25 ) ,
6988 timeout : flags . timeout ?? pos ( env . PROBE_TIMEOUT , 7_000 ) ,
89+ cap : flags . max ?? pos ( env . PROBE_MAX , 1_000 ) ,
7090 once : flags . once || env . RUN_ONCE === '1' || env . RUN_ONCE === 'true' ,
7191 help : ! ! flags . help ,
7292 } ;
@@ -144,15 +164,23 @@ async function mapPool(items, concurrency, fn) {
144164 await Promise . all ( Array . from ( { length : Math . max ( 1 , Math . min ( concurrency , items . length ) ) } , worker ) ) ;
145165}
146166
147- /** One sweep over the curated allowlist (never the harvested directory). */
167+ /** Sweep targets: allowlist ∪ already-verified relays (stalest first), capped. */
168+ async function sweepTargets ( db , cfg ) {
169+ const verified = await db . collection ( RELAY_DIRECTORY )
170+ . find ( { checksOnline : { $gte : 1 } } , { projection : { relay : 1 , _id : 0 } } )
171+ . sort ( { lastChecked : 1 } ) . limit ( cfg . cap ) . toArray ( ) ;
172+ return selectTargets ( proberRelays ( ) , verified . map ( ( d ) => d . relay ) , cfg . cap ) ;
173+ }
174+
175+ /** One sweep over the allowlist + verified candidates (never new harvest). */
148176export async function sweepOnce ( db , cfg ) {
149- const relays = proberRelays ( ) ; // allowlist only — attacker-fed data is never dialed
177+ const relays = await sweepTargets ( db , cfg ) ;
150178 let online = 0 ;
151179 await mapPool ( relays , cfg . concurrency , async ( relay ) => {
152180 try { if ( ( await probeRelay ( db , relay , cfg ) ) . online ) online ++ ; }
153181 catch ( e ) { console . error ( `[beacon] probe error ${ relay } : ${ e . message } ` ) ; }
154182 } ) ;
155- console . log ( `[beacon] relay sweep (allowlist) : ${ online } /${ relays . length } online` ) ;
183+ console . log ( `[beacon] relay sweep: ${ online } /${ relays . length } online (allowlist + verified, cap ${ cfg . cap } ) ` ) ;
156184 return { total : relays . length , online } ;
157185}
158186
0 commit comments