@steve02081504/fount-p2p 0.0.5 → 0.0.7
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/core/tcp_port.mjs +11 -0
- package/dag/canonicalize_row.mjs +5 -5
- package/discovery/advert_peer_hints.mjs +21 -0
- package/discovery/{bt.mjs → bt/index.mjs} +151 -55
- package/discovery/bt/peer_hints.mjs +41 -0
- package/discovery/bt/runtime.mjs +46 -0
- package/discovery/lan_peer_hints.mjs +43 -0
- package/discovery/mdns.mjs +10 -6
- package/discovery/nostr.mjs +3 -3
- package/federation/chunk_fetch_pending.mjs +3 -3
- package/federation/chunk_fetch_scheduler.mjs +3 -3
- package/federation/dag_order_cache.mjs +3 -3
- package/federation/topo_order_memo.mjs +3 -3
- package/files/chunk_responder.mjs +1 -1
- package/files/evfs.mjs +26 -26
- package/files/transfer_key.mjs +9 -9
- package/files/transfer_key_registry.mjs +9 -9
- package/governance/join_pow.mjs +17 -17
- package/index.mjs +3 -2
- package/link/channel_mux.mjs +7 -7
- package/link/frame.mjs +5 -5
- package/link/handshake.mjs +63 -36
- package/link/pipe.mjs +404 -0
- package/link/providers/ble_gatt.mjs +339 -0
- package/link/providers/index.mjs +90 -0
- package/link/providers/lan_tcp.mjs +342 -0
- package/link/providers/levels.mjs +9 -0
- package/link/providers/webrtc.mjs +385 -0
- package/mailbox/deliver_or_store.mjs +13 -13
- package/mailbox/wire.mjs +4 -4
- package/node/reputation_store.mjs +5 -5
- package/node/retention_policy.mjs +8 -8
- package/overlay/index.mjs +6 -6
- package/package.json +3 -2
- package/registries/inbound.mjs +8 -8
- package/rooms/scoped_link.mjs +23 -21
- package/timeline/append_core.mjs +3 -3
- package/transport/group_link_set.mjs +35 -33
- package/transport/link_registry.mjs +250 -63
- package/transport/peer_pool.mjs +4 -4
- package/transport/user_room.mjs +12 -14
- package/utils/ttl_map.mjs +41 -0
- package/wire/group_part.mjs +3 -3
- package/wire/part_ingress.mjs +14 -14
- package/wire/part_query.mjs +81 -82
- package/wire/part_query_cache.mjs +7 -0
- package/link/link.mjs +0 -617
package/wire/group_part.mjs
CHANGED
|
@@ -29,14 +29,14 @@ function wrapWireOn(wire, groupId) {
|
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* 群联邦房间挂载 part_wire(要求线载荷带 `groupId`)。
|
|
32
|
-
* @param {{ replicaUsername?: string }}
|
|
32
|
+
* @param {{ replicaUsername?: string }} wireContext 入站上下文
|
|
33
33
|
* @param {string} groupId 群 ID
|
|
34
34
|
* @param {import('./part_ingress.mjs').PartWireAdapter} wire Trystero 适配器
|
|
35
35
|
* @param {{ allowPartInvoke?: (payload: object) => boolean }} [options] 入站过滤
|
|
36
36
|
* @returns {void}
|
|
37
37
|
*/
|
|
38
|
-
export function attachGroupPartWire(
|
|
39
|
-
attachPartWire(
|
|
38
|
+
export function attachGroupPartWire(wireContext, groupId, wire, options = {}) {
|
|
39
|
+
attachPartWire(wireContext, {
|
|
40
40
|
send: wire.send.bind(wire),
|
|
41
41
|
on: wrapWireOn(wire, groupId),
|
|
42
42
|
}, options)
|
package/wire/part_ingress.mjs
CHANGED
|
@@ -45,16 +45,16 @@ function parsePartTimelinePut(data, partpath) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
|
-
* @param {PartWireContext}
|
|
48
|
+
* @param {PartWireContext} wireContext 入站上下文
|
|
49
49
|
* @param {object} payload part_invoke 请求
|
|
50
50
|
* @returns {Promise<PartInvokeResponse | null>} RPC 处理器返回值
|
|
51
51
|
*/
|
|
52
|
-
async function dispatchPartInvoke(
|
|
52
|
+
async function dispatchPartInvoke(wireContext, payload) {
|
|
53
53
|
const partpath = normalizePartpath(payload?.partpath)
|
|
54
54
|
const invoke = payload?.invoke
|
|
55
55
|
if (!partpath || !isPlainObject(invoke)) return null
|
|
56
56
|
return dispatchRpcInbound({
|
|
57
|
-
replicaUsername:
|
|
57
|
+
replicaUsername: wireContext.replicaUsername,
|
|
58
58
|
requesterNodeHash: payload.nodeHash ? String(payload.nodeHash).trim() : null,
|
|
59
59
|
groupId: payload.groupId ? String(payload.groupId).trim() : undefined,
|
|
60
60
|
peerId: payload.peerId,
|
|
@@ -70,12 +70,12 @@ async function dispatchPartInvoke(ctx, payload) {
|
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
72
|
* 挂载 part_timeline_put / part_invoke / part_invoke_response。
|
|
73
|
-
* @param {PartWireContext}
|
|
73
|
+
* @param {PartWireContext} wireContext 入站上下文
|
|
74
74
|
* @param {PartWireAdapter} wire Trystero 适配器
|
|
75
75
|
* @param {{ allowPartInvoke?: (payload: object) => boolean }} [options] 入站过滤
|
|
76
76
|
* @returns {void}
|
|
77
77
|
*/
|
|
78
|
-
export function attachPartWire(
|
|
78
|
+
export function attachPartWire(wireContext, wire, options = {}) {
|
|
79
79
|
wire.on('part_timeline_put', data => {
|
|
80
80
|
if (!isPlainObject(data)) return
|
|
81
81
|
const partpath = normalizePartpath(data.partpath)
|
|
@@ -83,7 +83,7 @@ export function attachPartWire(ctx, wire, options = {}) {
|
|
|
83
83
|
const message = parsePartTimelinePut(data, partpath)
|
|
84
84
|
if (!message) return
|
|
85
85
|
void dispatchDeliveryInbound({
|
|
86
|
-
replicaUsername:
|
|
86
|
+
replicaUsername: wireContext.replicaUsername,
|
|
87
87
|
requesterNodeHash: data.nodeHash ? String(data.nodeHash).trim() : null,
|
|
88
88
|
}, message)
|
|
89
89
|
})
|
|
@@ -93,9 +93,9 @@ export function attachPartWire(ctx, wire, options = {}) {
|
|
|
93
93
|
if (options.allowPartInvoke?.(data) === false) return
|
|
94
94
|
const payload = { ...data, peerId }
|
|
95
95
|
if (payload.requestId)
|
|
96
|
-
void handleIncomingPartInvokeRequest(
|
|
96
|
+
void handleIncomingPartInvokeRequest(wireContext, payload, wire, peerId)
|
|
97
97
|
else
|
|
98
|
-
void handleIncomingPartInvokeFireAndForget(
|
|
98
|
+
void handleIncomingPartInvokeFireAndForget(wireContext, payload, wire, peerId)
|
|
99
99
|
})
|
|
100
100
|
|
|
101
101
|
wire.on('part_invoke_response', (data, peerId) => {
|
|
@@ -105,17 +105,17 @@ export function attachPartWire(ctx, wire, options = {}) {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
|
-
* @param {PartWireContext}
|
|
108
|
+
* @param {PartWireContext} wireContext 入站上下文
|
|
109
109
|
* @param {object} payload part_invoke 请求(含 requestId)
|
|
110
110
|
* @param {PartWireAdapter} wire 发送适配器
|
|
111
111
|
* @param {string} peerId 对端
|
|
112
112
|
* @returns {Promise<void>}
|
|
113
113
|
*/
|
|
114
|
-
export async function handleIncomingPartInvokeRequest(
|
|
114
|
+
export async function handleIncomingPartInvokeRequest(wireContext, payload, wire, peerId) {
|
|
115
115
|
const partpath = normalizePartpath(payload?.partpath)
|
|
116
116
|
if (!partpath || !payload.requestId) return
|
|
117
117
|
|
|
118
|
-
const response = await dispatchPartInvoke(
|
|
118
|
+
const response = await dispatchPartInvoke(wireContext, { ...payload, peerId })
|
|
119
119
|
if (response == null || !isPartInvokeResponse(response)) return
|
|
120
120
|
|
|
121
121
|
try {
|
|
@@ -129,17 +129,17 @@ export async function handleIncomingPartInvokeRequest(ctx, payload, wire, peerId
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
/**
|
|
132
|
-
* @param {PartWireContext}
|
|
132
|
+
* @param {PartWireContext} wireContext 入站上下文
|
|
133
133
|
* @param {object} payload part_invoke 请求(无 requestId)
|
|
134
134
|
* @param {PartWireAdapter} wire 发送适配器
|
|
135
135
|
* @param {string} peerId 对端
|
|
136
136
|
* @returns {Promise<void>}
|
|
137
137
|
*/
|
|
138
|
-
export async function handleIncomingPartInvokeFireAndForget(
|
|
138
|
+
export async function handleIncomingPartInvokeFireAndForget(wireContext, payload, wire, peerId) {
|
|
139
139
|
const partpath = normalizePartpath(payload?.partpath)
|
|
140
140
|
if (!partpath) return
|
|
141
141
|
|
|
142
|
-
const response = await dispatchPartInvoke(
|
|
142
|
+
const response = await dispatchPartInvoke(wireContext, { ...payload, peerId })
|
|
143
143
|
const followUp = unwrapPartInvokeResult(response)
|
|
144
144
|
if (!isPartInvoke(followUp)) return
|
|
145
145
|
try {
|
package/wire/part_query.mjs
CHANGED
|
@@ -16,8 +16,8 @@ import { resolveFederationFanoutTopK } from '../trust_graph/resolve.mjs'
|
|
|
16
16
|
import trustGraphTunables from '../trust_graph/tunables.json' with { type: 'json' }
|
|
17
17
|
|
|
18
18
|
import { isPlainObject } from './ingress.mjs'
|
|
19
|
-
import { createPartQueryCache, partQueryCache } from './part_query_cache.mjs'
|
|
20
19
|
import partQueryTunables from './part_query.tunables.json' with { type: 'json' }
|
|
20
|
+
import { createPartQueryCache, partQueryCache } from './part_query_cache.mjs'
|
|
21
21
|
import { consumeWireRateBucket } from './rate_bucket.mjs'
|
|
22
22
|
import { finishMultiWireWaiters, registerMultiWireWait } from './wait.mjs'
|
|
23
23
|
|
|
@@ -34,7 +34,7 @@ import { finishMultiWireWaiters, registerMultiWireWait } from './wait.mjs'
|
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
|
-
* @typedef {(
|
|
37
|
+
* @typedef {(queryContext: QueryInboundContext, query: unknown) => Promise<unknown[] | null | undefined> | unknown[] | null | undefined} QueryInboundHandler
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
40
|
/**
|
|
@@ -55,14 +55,14 @@ import { finishMultiWireWaiters, registerMultiWireWait } from './wait.mjs'
|
|
|
55
55
|
* getNodeHash?: () => string
|
|
56
56
|
* now?: () => number
|
|
57
57
|
* state?: PartQueryNodeState
|
|
58
|
-
* }}
|
|
58
|
+
* }} PartQueryDependencies
|
|
59
59
|
*/
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
62
|
* @typedef {{
|
|
63
63
|
* upstreamPeerId: string
|
|
64
64
|
* wire: PartWireAdapter
|
|
65
|
-
*
|
|
65
|
+
* request: PartQueryReq
|
|
66
66
|
* localRows: unknown[]
|
|
67
67
|
* remoteRows: unknown[]
|
|
68
68
|
* expected: number
|
|
@@ -70,7 +70,7 @@ import { finishMultiWireWaiters, registerMultiWireWait } from './wait.mjs'
|
|
|
70
70
|
* respondedPeers: Set<string>
|
|
71
71
|
* flushed: boolean
|
|
72
72
|
* timer: ReturnType<typeof setTimeout> | null
|
|
73
|
-
*
|
|
73
|
+
* dependencies: PartQueryDependencies
|
|
74
74
|
* state: PartQueryNodeState
|
|
75
75
|
* }} RelayPending
|
|
76
76
|
*/
|
|
@@ -97,11 +97,11 @@ export function createPartQueryNodeState(options = {}) {
|
|
|
97
97
|
const defaultState = createPartQueryNodeState({ cache: partQueryCache })
|
|
98
98
|
|
|
99
99
|
/**
|
|
100
|
-
* @param {
|
|
100
|
+
* @param {PartQueryDependencies} [dependencies] 依赖
|
|
101
101
|
* @returns {PartQueryNodeState} 节点状态
|
|
102
102
|
*/
|
|
103
|
-
function resolveState(
|
|
104
|
-
return
|
|
103
|
+
function resolveState(dependencies = {}) {
|
|
104
|
+
return dependencies.state || defaultState
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
/**
|
|
@@ -167,27 +167,27 @@ export function mergeQueryRows(lists, maxHits, rowKey) {
|
|
|
167
167
|
|
|
168
168
|
/**
|
|
169
169
|
* @param {PartQueryNodeState} state 节点状态
|
|
170
|
-
* @param {QueryInboundContext}
|
|
170
|
+
* @param {QueryInboundContext} queryContext 入站上下文
|
|
171
171
|
* @param {string} partpath part 路径
|
|
172
172
|
* @param {string} kind 查询标签
|
|
173
173
|
* @param {unknown} query 查询体
|
|
174
174
|
* @returns {Promise<unknown[]>} 本地 rows
|
|
175
175
|
*/
|
|
176
|
-
async function runLocalHandler(state,
|
|
176
|
+
async function runLocalHandler(state, queryContext, partpath, kind, query) {
|
|
177
177
|
const handler = state.handlers.get(handlerKey(partpath, kind))
|
|
178
178
|
if (!handler) return []
|
|
179
|
-
const rows = await handler(
|
|
179
|
+
const rows = await handler(queryContext, query)
|
|
180
180
|
return Array.isArray(rows) ? rows : []
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
/**
|
|
184
184
|
* @param {string} username trust graph 上下文
|
|
185
185
|
* @param {Set<string>} exclude 排除节点
|
|
186
|
-
* @param {
|
|
186
|
+
* @param {PartQueryDependencies} dependencies 可注入依赖
|
|
187
187
|
* @returns {Promise<string[]>} 邻居 nodeHash
|
|
188
188
|
*/
|
|
189
|
-
async function selectQueryNeighbors(username, exclude,
|
|
190
|
-
if (
|
|
189
|
+
async function selectQueryNeighbors(username, exclude, dependencies) {
|
|
190
|
+
if (dependencies.selectNeighbors) return dependencies.selectNeighbors(exclude)
|
|
191
191
|
const graph = await buildMergedGraph(username)
|
|
192
192
|
const fanoutCap = Math.max(1, Math.floor(Number(partQueryTunables.fanoutCap) || 4))
|
|
193
193
|
const k = Math.min(fanoutCap, resolveFederationFanoutTopK(graph.size, trustGraphTunables))
|
|
@@ -207,24 +207,24 @@ async function selectQueryNeighbors(username, exclude, deps) {
|
|
|
207
207
|
* @param {string} nodeHash 目标
|
|
208
208
|
* @param {string} action action 名
|
|
209
209
|
* @param {unknown} payload 载荷
|
|
210
|
-
* @param {
|
|
210
|
+
* @param {PartQueryDependencies} dependencies 依赖
|
|
211
211
|
* @returns {Promise<boolean>} 是否发出
|
|
212
212
|
*/
|
|
213
|
-
async function deliverQuery(username, nodeHash, action, payload,
|
|
214
|
-
if (
|
|
213
|
+
async function deliverQuery(username, nodeHash, action, payload, dependencies) {
|
|
214
|
+
if (dependencies.deliver) return Boolean(await dependencies.deliver(nodeHash, action, payload))
|
|
215
215
|
return requireTrustGraphProvider(DEFAULT_TRUST_GRAPH_OWNER).sendToNode(username, nodeHash, action, payload)
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
/**
|
|
219
|
-
* @param {PartQueryReq}
|
|
219
|
+
* @param {PartQueryReq} request 请求
|
|
220
220
|
* @param {unknown[]} rows 行
|
|
221
221
|
* @param {() => string} nodeHashOf 本机 hash
|
|
222
222
|
* @returns {PartQueryRes} 响应载荷
|
|
223
223
|
*/
|
|
224
|
-
function
|
|
225
|
-
const capped = clampPartQueryRows(rows,
|
|
224
|
+
function buildResponse(request, rows, nodeHashOf) {
|
|
225
|
+
const capped = clampPartQueryRows(rows, request.budget.maxHits) || []
|
|
226
226
|
return {
|
|
227
|
-
requestId:
|
|
227
|
+
requestId: request.requestId,
|
|
228
228
|
fromNodeHash: nodeHashOf(),
|
|
229
229
|
rows: capped,
|
|
230
230
|
}
|
|
@@ -241,62 +241,62 @@ function flushRelayPending(pending) {
|
|
|
241
241
|
clearTimeout(pending.timer)
|
|
242
242
|
pending.timer = null
|
|
243
243
|
}
|
|
244
|
-
pending.state.relayPending.delete(pending.
|
|
245
|
-
const merged = mergeQueryRows([pending.localRows, pending.remoteRows], pending.
|
|
246
|
-
const now = pending.
|
|
247
|
-
pending.state.cache.set(pending.
|
|
248
|
-
const nodeHashOf = pending.
|
|
244
|
+
pending.state.relayPending.delete(pending.request.requestId)
|
|
245
|
+
const merged = mergeQueryRows([pending.localRows, pending.remoteRows], pending.request.budget.maxHits)
|
|
246
|
+
const now = pending.dependencies.now || Date.now
|
|
247
|
+
pending.state.cache.set(pending.request.partpath, pending.request.kind, pending.request.query, merged, now())
|
|
248
|
+
const nodeHashOf = pending.dependencies.getNodeHash || getNodeHash
|
|
249
249
|
try {
|
|
250
|
-
pending.wire.send('part_query_res',
|
|
250
|
+
pending.wire.send('part_query_res', buildResponse(pending.request, merged, nodeHashOf), pending.upstreamPeerId)
|
|
251
251
|
}
|
|
252
252
|
catch { /* disconnected */ }
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
/**
|
|
256
|
-
* @param {
|
|
256
|
+
* @param {{ replicaUsername?: string }} wireContext attach 上下文
|
|
257
257
|
* @param {PartWireAdapter} wire wire
|
|
258
|
-
* @param {PartQueryReq}
|
|
258
|
+
* @param {PartQueryReq} request 已校验请求
|
|
259
259
|
* @param {string} peerId 来路
|
|
260
|
-
* @param {
|
|
260
|
+
* @param {PartQueryDependencies} dependencies 依赖
|
|
261
261
|
* @returns {Promise<void>}
|
|
262
262
|
*/
|
|
263
|
-
async function
|
|
264
|
-
const state = resolveState(
|
|
265
|
-
const nodeHashOf =
|
|
266
|
-
const now =
|
|
267
|
-
const username = String(
|
|
263
|
+
async function processIncomingRequest(wireContext, wire, request, peerId, dependencies) {
|
|
264
|
+
const state = resolveState(dependencies)
|
|
265
|
+
const nodeHashOf = dependencies.getNodeHash || getNodeHash
|
|
266
|
+
const now = dependencies.now || Date.now
|
|
267
|
+
const username = String(wireContext.replicaUsername || '')
|
|
268
268
|
|
|
269
|
-
const cached = state.cache.get(
|
|
269
|
+
const cached = state.cache.get(request.partpath, request.kind, request.query, now())
|
|
270
270
|
if (cached) {
|
|
271
|
-
try { wire.send('part_query_res',
|
|
271
|
+
try { wire.send('part_query_res', buildResponse(request, cached, nodeHashOf), peerId) }
|
|
272
272
|
catch { /* disconnected */ }
|
|
273
273
|
return
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
const localRows = await runLocalHandler(state, {
|
|
277
|
-
replicaUsername:
|
|
278
|
-
requesterNodeHash:
|
|
277
|
+
replicaUsername: wireContext.replicaUsername,
|
|
278
|
+
requesterNodeHash: request.originNodeHash,
|
|
279
279
|
peerId,
|
|
280
|
-
},
|
|
280
|
+
}, request.partpath, request.kind, request.query)
|
|
281
281
|
|
|
282
|
-
const nextTtl =
|
|
282
|
+
const nextTtl = request.ttl - 1
|
|
283
283
|
if (nextTtl <= 0) {
|
|
284
|
-
state.cache.set(
|
|
285
|
-
try { wire.send('part_query_res',
|
|
284
|
+
state.cache.set(request.partpath, request.kind, request.query, localRows, now())
|
|
285
|
+
try { wire.send('part_query_res', buildResponse(request, localRows, nodeHashOf), peerId) }
|
|
286
286
|
catch { /* disconnected */ }
|
|
287
287
|
return
|
|
288
288
|
}
|
|
289
289
|
|
|
290
290
|
const selfHash = nodeHashOf()
|
|
291
|
-
const exclude = new Set([selfHash,
|
|
292
|
-
const neighbors = username ? await selectQueryNeighbors(username, exclude,
|
|
293
|
-
const forwardPayload = { ...
|
|
291
|
+
const exclude = new Set([selfHash, request.originNodeHash, String(peerId || '').trim().toLowerCase()].filter(Boolean))
|
|
292
|
+
const neighbors = username ? await selectQueryNeighbors(username, exclude, dependencies) : []
|
|
293
|
+
const forwardPayload = { ...request, ttl: nextTtl }
|
|
294
294
|
|
|
295
295
|
/** @type {RelayPending} */
|
|
296
296
|
const pending = {
|
|
297
297
|
upstreamPeerId: peerId,
|
|
298
298
|
wire,
|
|
299
|
-
|
|
299
|
+
request,
|
|
300
300
|
localRows,
|
|
301
301
|
remoteRows: [],
|
|
302
302
|
expected: 0,
|
|
@@ -304,14 +304,14 @@ async function processIncomingReq(ctx, wire, req, peerId, deps) {
|
|
|
304
304
|
respondedPeers: new Set(),
|
|
305
305
|
flushed: false,
|
|
306
306
|
timer: null,
|
|
307
|
-
|
|
307
|
+
dependencies,
|
|
308
308
|
state,
|
|
309
309
|
}
|
|
310
|
-
state.relayPending.set(
|
|
310
|
+
state.relayPending.set(request.requestId, pending)
|
|
311
311
|
|
|
312
312
|
let sent = 0
|
|
313
313
|
for (const target of neighbors)
|
|
314
|
-
if (await deliverQuery(username, target, 'part_query_req', forwardPayload,
|
|
314
|
+
if (await deliverQuery(username, target, 'part_query_req', forwardPayload, dependencies)) sent++
|
|
315
315
|
pending.expected = sent
|
|
316
316
|
|
|
317
317
|
if (sent === 0 || pending.received >= pending.expected) {
|
|
@@ -319,69 +319,69 @@ async function processIncomingReq(ctx, wire, req, peerId, deps) {
|
|
|
319
319
|
return
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
-
pending.timer = setTimeout(() => flushRelayPending(pending), resolvePartQueryHopTimeoutMs(
|
|
322
|
+
pending.timer = setTimeout(() => flushRelayPending(pending), resolvePartQueryHopTimeoutMs(request.ttl))
|
|
323
323
|
}
|
|
324
324
|
|
|
325
325
|
/**
|
|
326
326
|
* 挂载 part_query_req / part_query_res。
|
|
327
|
-
* @param {{ replicaUsername?: string }}
|
|
327
|
+
* @param {{ replicaUsername?: string }} wireContext 入站上下文
|
|
328
328
|
* @param {PartWireAdapter} wire wire
|
|
329
|
-
* @param {
|
|
329
|
+
* @param {PartQueryDependencies} [dependencies] 可注入依赖(含 per-node state)
|
|
330
330
|
* @returns {void}
|
|
331
331
|
*/
|
|
332
|
-
export function attachPartQueryWire(
|
|
333
|
-
const state = resolveState(
|
|
332
|
+
export function attachPartQueryWire(wireContext, wire, dependencies = {}) {
|
|
333
|
+
const state = resolveState(dependencies)
|
|
334
334
|
wire.on('part_query_req', (data, peerId) => {
|
|
335
335
|
if (!isPlainObject(data)) return
|
|
336
|
-
const
|
|
337
|
-
if (!
|
|
338
|
-
if (!state.takeDedupe(
|
|
339
|
-
const source = String(peerId ||
|
|
336
|
+
const request = parsePartQueryReq(data)
|
|
337
|
+
if (!request) return
|
|
338
|
+
if (!state.takeDedupe(request.requestId)) return
|
|
339
|
+
const source = String(peerId || request.originNodeHash || '').trim().toLowerCase()
|
|
340
340
|
if (source && !consumeWireRateBucket(`part_query:${source}`, {
|
|
341
341
|
maxCount: partQueryTunables.ratePerSourcePerMin,
|
|
342
342
|
})) return
|
|
343
|
-
void
|
|
343
|
+
void processIncomingRequest(wireContext, wire, request, String(peerId || ''), dependencies)
|
|
344
344
|
})
|
|
345
345
|
|
|
346
346
|
wire.on('part_query_res', (data, peerId) => {
|
|
347
|
-
const
|
|
348
|
-
if (!
|
|
349
|
-
|
|
347
|
+
const response = parsePartQueryRes(data)
|
|
348
|
+
if (!response) return
|
|
349
|
+
handleIncomingPartQueryResponse(response, String(peerId || ''), dependencies)
|
|
350
350
|
})
|
|
351
351
|
}
|
|
352
352
|
|
|
353
353
|
/**
|
|
354
|
-
* @param {PartQueryRes}
|
|
354
|
+
* @param {PartQueryRes} response 响应
|
|
355
355
|
* @param {string} peerId 来路
|
|
356
|
-
* @param {
|
|
356
|
+
* @param {PartQueryDependencies} [dependencies] 依赖
|
|
357
357
|
* @returns {void}
|
|
358
358
|
*/
|
|
359
|
-
export function
|
|
360
|
-
const state = resolveState(
|
|
359
|
+
export function handleIncomingPartQueryResponse(response, peerId = '', dependencies = {}) {
|
|
360
|
+
const state = resolveState(dependencies)
|
|
361
361
|
// 同一 peer 只计一次,防重复回包灌水/提早凑齐 expected
|
|
362
|
-
const responderKey = String(peerId ||
|
|
363
|
-
const relay = state.relayPending.get(
|
|
362
|
+
const responderKey = String(peerId || response.fromNodeHash || '').trim().toLowerCase()
|
|
363
|
+
const relay = state.relayPending.get(response.requestId)
|
|
364
364
|
if (relay) {
|
|
365
365
|
if (responderKey) {
|
|
366
366
|
if (relay.respondedPeers.has(responderKey)) return
|
|
367
367
|
relay.respondedPeers.add(responderKey)
|
|
368
368
|
}
|
|
369
|
-
relay.remoteRows.push(...
|
|
369
|
+
relay.remoteRows.push(...response.rows)
|
|
370
370
|
relay.received += 1
|
|
371
371
|
if (relay.expected > 0 && relay.received >= relay.expected) flushRelayPending(relay)
|
|
372
372
|
return
|
|
373
373
|
}
|
|
374
374
|
|
|
375
|
-
const bag = state.originBags.get(
|
|
375
|
+
const bag = state.originBags.get(response.requestId)
|
|
376
376
|
if (!bag) return
|
|
377
377
|
if (responderKey) {
|
|
378
378
|
if (bag.respondedPeers.has(responderKey)) return
|
|
379
379
|
bag.respondedPeers.add(responderKey)
|
|
380
380
|
}
|
|
381
|
-
bag.rows = mergeQueryRows([bag.rows,
|
|
381
|
+
bag.rows = mergeQueryRows([bag.rows, response.rows], bag.maxHits, bag.rowKey)
|
|
382
382
|
bag.received += 1
|
|
383
383
|
if (bag.expected > 0 && bag.received >= bag.expected)
|
|
384
|
-
finishMultiWireWaiters(state.originWaits,
|
|
384
|
+
finishMultiWireWaiters(state.originWaits, response.requestId, '')
|
|
385
385
|
}
|
|
386
386
|
|
|
387
387
|
/**
|
|
@@ -396,14 +396,13 @@ export function handleIncomingPartQueryRes(res, peerId = '', deps = {}) {
|
|
|
396
396
|
* maxHits?: number
|
|
397
397
|
* rowKey?: (row: unknown) => string
|
|
398
398
|
* budget?: { maxHits?: number }
|
|
399
|
-
* } &
|
|
399
|
+
* } & PartQueryDependencies} [options] 选项
|
|
400
400
|
* @returns {Promise<unknown[]>} 合并后的 rows
|
|
401
401
|
*/
|
|
402
402
|
export async function queryNetwork(username, partpath, kind, query, options = {}) {
|
|
403
|
-
const
|
|
404
|
-
const
|
|
405
|
-
const
|
|
406
|
-
const nodeHashOf = deps.getNodeHash || getNodeHash
|
|
403
|
+
const state = resolveState(options)
|
|
404
|
+
const now = options.now || Date.now
|
|
405
|
+
const nodeHashOf = options.getNodeHash || getNodeHash
|
|
407
406
|
|
|
408
407
|
const cached = state.cache.get(partpath, kind, query, now())
|
|
409
408
|
if (cached) return cached
|
|
@@ -428,7 +427,7 @@ export async function queryNetwork(username, partpath, kind, query, options = {}
|
|
|
428
427
|
}, partpath, kind, query)
|
|
429
428
|
|
|
430
429
|
/** @type {PartQueryReq} */
|
|
431
|
-
const
|
|
430
|
+
const request = {
|
|
432
431
|
requestId: randomUUID(),
|
|
433
432
|
originNodeHash: nodeHashOf(),
|
|
434
433
|
partpath: String(partpath || '').trim(),
|
|
@@ -437,7 +436,7 @@ export async function queryNetwork(username, partpath, kind, query, options = {}
|
|
|
437
436
|
ttl,
|
|
438
437
|
budget: { maxHits },
|
|
439
438
|
}
|
|
440
|
-
const parsed = parsePartQueryReq(
|
|
439
|
+
const parsed = parsePartQueryReq(request)
|
|
441
440
|
if (!parsed) return mergeQueryRows([localRows], maxHits, options.rowKey)
|
|
442
441
|
|
|
443
442
|
// 预占 dedupe:若查询绕环回流到本机,入站侧直接丢弃
|
|
@@ -456,10 +455,10 @@ export async function queryNetwork(username, partpath, kind, query, options = {}
|
|
|
456
455
|
|
|
457
456
|
const selfHash = nodeHashOf()
|
|
458
457
|
const exclude = new Set([selfHash, parsed.originNodeHash])
|
|
459
|
-
const neighbors = await selectQueryNeighbors(username, exclude,
|
|
458
|
+
const neighbors = await selectQueryNeighbors(username, exclude, options)
|
|
460
459
|
let sent = 0
|
|
461
460
|
for (const target of neighbors)
|
|
462
|
-
if (await deliverQuery(username, target, 'part_query_req', parsed,
|
|
461
|
+
if (await deliverQuery(username, target, 'part_query_req', parsed, options)) sent++
|
|
463
462
|
bag.expected = sent
|
|
464
463
|
|
|
465
464
|
// deliver 可能同步回流;在赋值 expected 后再检查是否已齐
|
|
@@ -22,6 +22,13 @@ export function partQueryCacheKey(partpath, kind, query) {
|
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* LRU + TTL 中继缓存(未验证线索;发起端仍需验签复核)。
|
|
25
|
+
* @param {{ maxKeys?: number, ttlMs?: number, maxHits?: number }} [options] 容量 / TTL / 单键 rows 上限
|
|
26
|
+
* @returns {{
|
|
27
|
+
* get: (partpath: string, kind: string, query: unknown, now?: number) => unknown[] | null
|
|
28
|
+
* set: (partpath: string, kind: string, query: unknown, rows: unknown[], now?: number) => void
|
|
29
|
+
* clear: () => void
|
|
30
|
+
* readonly size: number
|
|
31
|
+
* }} 缓存 API
|
|
25
32
|
*/
|
|
26
33
|
export function createPartQueryCache(options = {}) {
|
|
27
34
|
const maxKeys = Math.max(1, Math.floor(Number(options.maxKeys) || partQueryTunables.cacheMaxKeys))
|