@steve02081504/fount-p2p 0.0.44 → 0.0.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +17 -4
- package/dag/storage.mjs +157 -79
- package/discovery/nostr/census.mjs +5 -5
- package/discovery/nostr/census_monitor.mjs +2 -2
- package/discovery/nostr/constants.mjs +1 -0
- package/discovery/nostr/index.mjs +24 -12
- package/discovery/nostr/session.mjs +96 -12
- package/docs/nostr_relay_discovery.md +2 -0
- package/docs/signaling.md +15 -3
- package/docs/transports.md +15 -1
- package/federation/part_query/runtime.mjs +4 -4
- package/index.mjs +5 -0
- package/link/providers/ble_gatt.mjs +2 -2
- package/link/providers/index.mjs +2 -1
- package/link/providers/nostr/index.mjs +19 -6
- package/link/providers/webrtc.mjs +235 -56
- package/link/rtc/polyfill.mjs +9 -6
- package/mailbox/store.mjs +3 -22
- package/node/signaling_config.mjs +30 -8
- package/node/verification.mjs +163 -0
- package/package.json +4 -2
- package/sim/AGENTS.md +1 -1
- package/sim/{cold_start.md → docs/cold_start.md} +1 -1
- package/timeline/prune.mjs +27 -23
- package/timeline/retention.mjs +38 -34
- package/transport/link_registry.mjs +75 -6
- package/transport/peer_health.mjs +43 -25
- package/transport/runtime_bootstrap.mjs +44 -9
- package/utils/async_mutex.mjs +11 -4
- package/utils/shuffle.mjs +1 -1
- package/wire/adapter.mjs +1 -1
- package/pages/index.html +0 -36
- package/pages/index.mjs +0 -62
|
@@ -28,6 +28,15 @@ import { createWebRtcLinkProvider } from '../link/providers/webrtc.mjs'
|
|
|
28
28
|
import { getSignalingRuntimeConfig, onNodeChange } from '../node/instance.mjs'
|
|
29
29
|
import { isConnectivityDebug, nodeDebug, shortHash } from '../node/log.mjs'
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* @param {import('../link/providers/index.mjs').LinkProvider} provider 链路提供者
|
|
33
|
+
* @returns {boolean} 是否由本 registry 自己持有(lan_tcp / ble_gatt 走各自的快速监听路径)
|
|
34
|
+
*/
|
|
35
|
+
function providerIsRegistryOwned(provider) {
|
|
36
|
+
const { id } = provider
|
|
37
|
+
return id.startsWith('lan_tcp') || id.startsWith('ble_gatt')
|
|
38
|
+
}
|
|
39
|
+
|
|
31
40
|
/**
|
|
32
41
|
* @param {import('../link/providers/index.mjs').LinkProvider} provider 链路提供者
|
|
33
42
|
* @returns {boolean} 是否使用原生 probe 路径
|
|
@@ -36,6 +45,16 @@ export function providerHasNativeProbe(provider) {
|
|
|
36
45
|
return provider.caps?.probe === 'native'
|
|
37
46
|
}
|
|
38
47
|
|
|
48
|
+
/**
|
|
49
|
+
* @param {import('../link/providers/index.mjs').LinkProvider} provider 链路提供者
|
|
50
|
+
* @returns {boolean} 是否需要在 runtime 暖机时补挂监听
|
|
51
|
+
*/
|
|
52
|
+
export function providerNeedsListening(provider) {
|
|
53
|
+
if (providerIsRegistryOwned(provider)) return false
|
|
54
|
+
if (providerHasNativeProbe(provider)) return false
|
|
55
|
+
return !!provider.ensureListening
|
|
56
|
+
}
|
|
57
|
+
|
|
39
58
|
/**
|
|
40
59
|
* @param {import('../link/providers/index.mjs').LinkProvider | null} ownedLanTcp 本 registry 持有的 lan_tcp
|
|
41
60
|
* @returns {import('../link/providers/index.mjs').LinkProvider[]} 可快速启动监听的 provider 列表
|
|
@@ -45,10 +64,8 @@ export function collectFastListenProviders(ownedLanTcp) {
|
|
|
45
64
|
const listenProviders = []
|
|
46
65
|
if (ownedLanTcp) listenProviders.push(ownedLanTcp)
|
|
47
66
|
for (const provider of listLinkProviders()) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (!provider.ensureListening) continue
|
|
51
|
-
if (providerHasNativeProbe(provider)) continue
|
|
67
|
+
if (!providerNeedsListening(provider)) continue
|
|
68
|
+
// 快速路径只接同步 isAvailable:要等异步探测的 provider 交给 startEnabledProviderListening。
|
|
52
69
|
if (provider.isAvailable)
|
|
53
70
|
try {
|
|
54
71
|
const available = provider.isAvailable()
|
|
@@ -202,6 +219,24 @@ export function createRuntimeBootstrap(deps) {
|
|
|
202
219
|
return encryptAdvertForScope('network', localIdentity, body)
|
|
203
220
|
}
|
|
204
221
|
|
|
222
|
+
/**
|
|
223
|
+
* 为所有「已注册、已启用、尚未监听」的 link provider 启动监听。
|
|
224
|
+
* `reconcileLinkProviders()` 重新注册的 provider 只有走到这里才会拿到 onInbound/localIdentity,
|
|
225
|
+
* 否则它会静默丢弃全部入站 link-open(见 fount-p2p#38)。
|
|
226
|
+
*
|
|
227
|
+
* 刻意不看 `isAvailable()`:relay 池为空/未探测时 nostr 的 `isAvailable()` 会是 false,而 relay 列表
|
|
228
|
+
* 是在发送时解析的;先挂上监听才能收到入站 link-open,暂时不可用不该让它变哑巴。
|
|
229
|
+
* @returns {Promise<void>}
|
|
230
|
+
*/
|
|
231
|
+
async function startEnabledProviderListening() {
|
|
232
|
+
if (!autoRegisterLinkProviders) return
|
|
233
|
+
await Promise.all(listLinkProviders()
|
|
234
|
+
.filter(provider => isChannelEnabled(provider.id.split(':')[0]))
|
|
235
|
+
.filter(provider => providerNeedsListening(provider))
|
|
236
|
+
.filter(provider => !stopLinkListeners.has(provider.id))
|
|
237
|
+
.map(provider => startProviderListening(provider)))
|
|
238
|
+
}
|
|
239
|
+
|
|
205
240
|
/**
|
|
206
241
|
* @param {number} gen 启动世代
|
|
207
242
|
* @returns {Promise<void>}
|
|
@@ -212,6 +247,8 @@ export function createRuntimeBootstrap(deps) {
|
|
|
212
247
|
signalListenReady = (async () => {
|
|
213
248
|
await lanListenReady.catch(() => { })
|
|
214
249
|
if (generation !== gen || !isLive()) return
|
|
250
|
+
await startEnabledProviderListening().catch(() => { })
|
|
251
|
+
if (generation !== gen || !isLive()) return
|
|
215
252
|
if (!listDiscoveryProviders().length) return
|
|
216
253
|
stopSignalListener = await listenNodeSignals(localIdentity.nodeHash, bytes => {
|
|
217
254
|
void handleIncomingSignal(bytes).catch(() => { })
|
|
@@ -324,11 +361,9 @@ export function createRuntimeBootstrap(deps) {
|
|
|
324
361
|
reconcileLinkProviders()
|
|
325
362
|
reconcileDiscoveryProviders()
|
|
326
363
|
if (generation !== gen || !isLive()) return
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
&& await Promise.resolve(ownedBleGatt.isAvailable()))
|
|
331
|
-
await startProviderListening(ownedBleGatt)
|
|
364
|
+
// 覆盖本轮新注册/重新注册的 provider(例如关掉再打开 nostr):只补 ownedLanTcp/BLE
|
|
365
|
+
// 会让新 provider 永远拿不到 onInbound(见 fount-p2p#38)。
|
|
366
|
+
await startEnabledProviderListening()
|
|
332
367
|
signalListenReady = (async () => {
|
|
333
368
|
if (generation !== gen || !isLive()) return
|
|
334
369
|
if (!listDiscoveryProviders().length) return
|
package/utils/async_mutex.mjs
CHANGED
|
@@ -25,13 +25,21 @@ function mutexState(lockKey) {
|
|
|
25
25
|
*/
|
|
26
26
|
function releaseMutex(lockKey, state) {
|
|
27
27
|
const next = state.queue.shift()
|
|
28
|
-
if (next)
|
|
28
|
+
if (next) queueMicrotask(next)
|
|
29
29
|
else {
|
|
30
30
|
state.locked = false
|
|
31
31
|
mutexes.delete(lockKey)
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* 当前存活的互斥键数量(仅供测试观察锁表是否回落到基线)。
|
|
37
|
+
* @returns {number} `mutexes` 中的键数量
|
|
38
|
+
*/
|
|
39
|
+
export function activeMutexCount() {
|
|
40
|
+
return mutexes.size
|
|
41
|
+
}
|
|
42
|
+
|
|
35
43
|
/**
|
|
36
44
|
* @param {string} key 锁键
|
|
37
45
|
* @param {() => Promise<T>} criticalSection 临界区
|
|
@@ -39,15 +47,14 @@ function releaseMutex(lockKey, state) {
|
|
|
39
47
|
* @template T
|
|
40
48
|
*/
|
|
41
49
|
export async function withAsyncMutex(key, criticalSection) {
|
|
42
|
-
const
|
|
43
|
-
const state = mutexState(lockKey)
|
|
50
|
+
const state = mutexState(key)
|
|
44
51
|
return new Promise((resolve, reject) => {
|
|
45
52
|
/** 执行临界区并最终释放锁 */
|
|
46
53
|
const run = () => {
|
|
47
54
|
Promise.resolve()
|
|
48
55
|
.then(criticalSection)
|
|
49
56
|
.then(resolve, reject)
|
|
50
|
-
.finally(() => releaseMutex(
|
|
57
|
+
.finally(() => releaseMutex(key, state))
|
|
51
58
|
}
|
|
52
59
|
if (state.locked) state.queue.push(run)
|
|
53
60
|
else {
|
package/utils/shuffle.mjs
CHANGED
package/wire/adapter.mjs
CHANGED
package/pages/index.html
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="zh-CN">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title>fount 人口统计</title>
|
|
7
|
-
<style>
|
|
8
|
-
:root { color-scheme: light dark; }
|
|
9
|
-
body { font-family: system-ui, sans-serif; max-width: 720px; margin: 3rem auto; padding: 0 1rem; line-height: 1.6; }
|
|
10
|
-
h1 { font-size: 1.4rem; }
|
|
11
|
-
.card { border: 1px solid #8884; border-radius: 8px; padding: 1rem 1.25rem; margin: 1rem 0; }
|
|
12
|
-
.metric { font-size: 2rem; font-weight: 700; }
|
|
13
|
-
label { display: flex; align-items: center; gap: .5rem; margin: 1rem 0; }
|
|
14
|
-
#status { opacity: .7; font-size: .9rem; }
|
|
15
|
-
code { background: #8882; padding: .1em .4em; border-radius: 4px; }
|
|
16
|
-
</style>
|
|
17
|
-
</head>
|
|
18
|
-
<body>
|
|
19
|
-
<h1>fount 节点人口统计</h1>
|
|
20
|
-
<p>基于 Nostr census 事件(kind 30789,<code>t=fount</code> / <code>x=census</code>)的采样估计。监听逻辑由包内 <code>census_monitor</code> 封装:自动监听默认/传入 relay 并经 NIP-66 发现更多,取人口最大的 relay 作为显示源。在线节点数 = <code>Σ(1/p)</code>(Horvitz–Thompson 估计)。</p>
|
|
21
|
-
<label>
|
|
22
|
-
<input type="checkbox" id="toggle" checked>
|
|
23
|
-
人口统计(census)
|
|
24
|
-
</label>
|
|
25
|
-
<div class="card">
|
|
26
|
-
<div>在线节点数估计</div>
|
|
27
|
-
<div class="metric" id="estimate">0</div>
|
|
28
|
-
<div id="status">初始化…</div>
|
|
29
|
-
</div>
|
|
30
|
-
<div class="card">
|
|
31
|
-
<div>采样事件数 <span id="sample-size">0</span> · 窗口内事件 <span id="window-events">0</span></div>
|
|
32
|
-
</div>
|
|
33
|
-
<footer id="footer" style="opacity:.6;font-size:.85rem">通过 URL 参数 ?relay=wss://... 指定中继;缺省监听默认 relay。</footer>
|
|
34
|
-
<script type="module" src="./index.mjs"></script>
|
|
35
|
-
</body>
|
|
36
|
-
</html>
|
package/pages/index.mjs
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* fount 人口统计前端(薄壳,逻辑由包内 `census_monitor` 封装)。
|
|
3
|
-
* - `createPopulationMonitor` 自动监听全部默认(或传入)relay、经 NIP-66 发现更多,
|
|
4
|
-
* 并取人口估计最大的 relay 作为显示源,把 `{ estimate, sampleSize, eventsInWindow, relayUrl, relays }`
|
|
5
|
-
* 快照连同 relay 地址传给 onUpdate。
|
|
6
|
-
* - 页面只负责:导入监控器、传显示函数与可选 relay 地址。
|
|
7
|
-
*/
|
|
8
|
-
import { createPopulationMonitor } from 'https://esm.sh/@steve02081504/fount-p2p/discovery/nostr/census_monitor.mjs'
|
|
9
|
-
|
|
10
|
-
const statusEl = document.querySelector('#status')
|
|
11
|
-
const toggleEl = document.querySelector('#toggle')
|
|
12
|
-
|
|
13
|
-
const params = new URLSearchParams(location.search)
|
|
14
|
-
const relayUrl = params.get('relay')
|
|
15
|
-
|
|
16
|
-
/** @type {{ stop: () => void } | null} */
|
|
17
|
-
let monitor = null
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* @param {{ estimate: number, sampleSize: number, eventsInWindow: number }} snapshot 快照
|
|
21
|
-
* @returns {void}
|
|
22
|
-
*/
|
|
23
|
-
function render(snapshot) {
|
|
24
|
-
document.querySelector('#estimate').textContent = snapshot.estimate.toLocaleString('zh-CN', { maximumFractionDigits: 1 })
|
|
25
|
-
document.querySelector('#sample-size').textContent = String(snapshot.sampleSize)
|
|
26
|
-
document.querySelector('#window-events').textContent = String(snapshot.eventsInWindow)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** @returns {void} 启动 live 监控(默认/传入 relay 全交给库) */
|
|
30
|
-
function startLive() {
|
|
31
|
-
if (monitor) return
|
|
32
|
-
try {
|
|
33
|
-
monitor = createPopulationMonitor({
|
|
34
|
-
relays: relayUrl ? [relayUrl] : undefined,
|
|
35
|
-
onUpdate: snapshot => {
|
|
36
|
-
render(snapshot)
|
|
37
|
-
statusEl.textContent = `监听 ${snapshot.relayUrl}(census,共 ${snapshot.relays} 个 relay)`
|
|
38
|
-
},
|
|
39
|
-
})
|
|
40
|
-
}
|
|
41
|
-
catch (error) {
|
|
42
|
-
statusEl.textContent = `中继配置无效:${String(error?.message || error)}`
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** @returns {void} 停止监控 */
|
|
47
|
-
function stopLive() {
|
|
48
|
-
monitor?.stop()
|
|
49
|
-
monitor = null
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
toggleEl.addEventListener('change', () => {
|
|
53
|
-
if (!toggleEl.checked) {
|
|
54
|
-
stopLive()
|
|
55
|
-
render({ estimate: 0, sampleSize: 0, eventsInWindow: 0 })
|
|
56
|
-
statusEl.textContent = '已关闭'
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
|
-
startLive()
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
if (toggleEl.checked) startLive()
|