@steve02081504/fount-p2p 0.0.11 → 0.0.13
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/README.md +42 -11
- package/core/bytes_codec.mjs +65 -10
- package/core/composite_key.mjs +5 -5
- package/core/tcp_port.mjs +1 -1
- package/crypto/checkpoint_sign.mjs +2 -2
- package/crypto/crypto.mjs +3 -3
- package/crypto/key.mjs +12 -12
- package/dag/canonicalize_row.mjs +3 -3
- package/dag/index.mjs +2 -3
- package/dag/storage.mjs +36 -36
- package/discovery/advert_peer_hints.mjs +1 -1
- package/discovery/adverts.mjs +109 -0
- package/discovery/bt/index.mjs +154 -113
- package/discovery/bt/probe_child.mjs +2 -1
- package/discovery/bt/runtime.mjs +5 -12
- package/discovery/index.mjs +267 -62
- package/discovery/internal/signal_crypto.mjs +109 -0
- package/discovery/lan.mjs +228 -0
- package/discovery/nostr.mjs +430 -141
- package/federation/chunk_fetch_pending.mjs +14 -18
- package/federation/dag_order_cache.mjs +1 -1
- package/federation/entity_key_chain.mjs +3 -4
- package/federation/manifest_fetch_pending.mjs +1 -3
- package/federation/topo_order_memo.mjs +11 -4
- package/files/assemble.mjs +14 -14
- package/files/assemble_stream.mjs +6 -6
- package/files/chunk_fetch.mjs +2 -2
- package/files/chunk_responder.mjs +29 -20
- package/files/evfs.mjs +31 -30
- package/files/manifest_fetch.mjs +16 -3
- package/files/public_manifest.mjs +11 -11
- package/files/transfer_key_registry.mjs +2 -2
- package/index.mjs +86 -11
- package/infra/cli.mjs +62 -0
- package/infra/debug_log.mjs +56 -0
- package/infra/default_node_dir.mjs +22 -0
- package/infra/priority.mjs +56 -0
- package/infra/service.mjs +140 -0
- package/infra/tunables.json +5 -0
- package/link/channel_mux.mjs +10 -10
- package/link/frame.mjs +76 -131
- package/link/handshake.mjs +19 -20
- package/link/pipe.mjs +53 -79
- package/link/providers/ble_gatt.mjs +19 -31
- package/link/providers/lan_tcp.mjs +17 -32
- package/link/providers/link_id_pipe.mjs +46 -0
- package/link/providers/webrtc.mjs +42 -51
- package/link/rtc.mjs +24 -12
- package/mailbox/consumer_registry.mjs +2 -2
- package/mailbox/deliver_or_store.mjs +6 -6
- package/mailbox/wire.mjs +28 -24
- package/node/entity_store.mjs +6 -6
- package/node/identity.mjs +4 -4
- package/node/instance.mjs +46 -27
- package/node/local_data_revision.mjs +26 -0
- package/node/log.mjs +56 -0
- package/node/network.mjs +46 -14
- package/node/reputation_store.mjs +4 -4
- package/node/reputation_sync.mjs +318 -0
- package/node/routing_profile.mjs +21 -0
- package/node/signaling_config.mjs +30 -11
- package/overlay/index.mjs +35 -14
- package/package.json +13 -2
- package/rooms/scoped_link.mjs +17 -182
- package/schemas/discovery.mjs +1 -2
- package/schemas/federation_pull.mjs +2 -2
- package/schemas/part_query.mjs +1 -1
- package/schemas/remote_event.mjs +1 -1
- package/transport/advert_ingest.mjs +17 -0
- package/transport/group_link_set.mjs +165 -134
- package/transport/ice_servers.mjs +2 -2
- package/transport/link_registry.mjs +283 -124
- package/transport/mesh_keepalive.mjs +217 -0
- package/transport/node_scope.mjs +289 -0
- package/transport/offer_answer.mjs +48 -47
- package/transport/peer_pool.mjs +169 -78
- package/transport/remote_user_room.mjs +18 -23
- package/transport/rtc_connection_budget.mjs +18 -4
- package/transport/{rtc_mdns_filter.mjs → rtc_ice_local_hostname.mjs} +13 -13
- package/transport/runtime_bootstrap.mjs +172 -104
- package/transport/tunables.json +11 -0
- package/transport/tunables.mjs +18 -0
- package/transport/user_room.mjs +93 -161
- package/trust_graph/build.mjs +0 -2
- package/trust_graph/cache.mjs +12 -3
- package/trust_graph/registry.mjs +6 -6
- package/trust_graph/send.mjs +1 -4
- package/utils/async_mutex.mjs +4 -4
- package/utils/emit_safe.mjs +11 -0
- package/utils/json_io.mjs +12 -12
- package/utils/map_pool.mjs +5 -5
- package/utils/shuffle.mjs +13 -0
- package/utils/ttl_map.mjs +29 -4
- package/wire/ingress.mjs +1 -1
- package/wire/part_ingress.mjs +38 -36
- package/wire/part_invoke.mjs +4 -4
- package/wire/part_query.mjs +28 -23
- package/wire/part_query.tunables.json +6 -1
- package/wire/part_query_cache.mjs +1 -1
- package/wire/volatile_signature.mjs +1 -1
- package/discovery/mdns.mjs +0 -191
- package/transport/signal_crypto.mjs +0 -82
package/node/entity_store.mjs
CHANGED
|
@@ -80,9 +80,9 @@ export function createFsEntityStore(baseDir) {
|
|
|
80
80
|
const entries = await fsp.readdir(root, { withFileTypes: true })
|
|
81
81
|
return entries.filter(e => e.isDirectory()).map(e => e.name.toLowerCase()).filter(h => parseEntityHash(h))
|
|
82
82
|
}
|
|
83
|
-
catch (
|
|
84
|
-
if (/** @type {NodeJS.ErrnoException} */
|
|
85
|
-
throw
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (/** @type {NodeJS.ErrnoException} */ error.code === 'ENOENT') return []
|
|
85
|
+
throw error
|
|
86
86
|
}
|
|
87
87
|
},
|
|
88
88
|
|
|
@@ -115,9 +115,9 @@ export function createFsEntityStore(baseDir) {
|
|
|
115
115
|
try {
|
|
116
116
|
return await fsp.readFile(filePath)
|
|
117
117
|
}
|
|
118
|
-
catch (
|
|
119
|
-
if (/** @type {NodeJS.ErrnoException} */
|
|
120
|
-
throw
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (/** @type {NodeJS.ErrnoException} */ error.code === 'ENOENT') return null
|
|
120
|
+
throw error
|
|
121
121
|
}
|
|
122
122
|
},
|
|
123
123
|
|
package/node/identity.mjs
CHANGED
|
@@ -66,9 +66,9 @@ export function getNodeHash() {
|
|
|
66
66
|
*/
|
|
67
67
|
export function getNodeTransportSettings() {
|
|
68
68
|
const data = loadNodeFile()
|
|
69
|
-
const relayUrls =
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
const relayUrls = (data.relayUrls || [])
|
|
70
|
+
.map(url => url.trim())
|
|
71
|
+
.filter(url => url.startsWith('wss://'))
|
|
72
72
|
const batterySaver = !!data.batterySaver
|
|
73
73
|
const mailbox = normalizeMailboxSettings(data.mailbox || {})
|
|
74
74
|
return { relayUrls, batterySaver, mailbox }
|
|
@@ -82,7 +82,7 @@ export function saveNodeTransportSettings(patch) {
|
|
|
82
82
|
const data = loadNodeFile()
|
|
83
83
|
if (patch.batterySaver != null) data.batterySaver = !!patch.batterySaver
|
|
84
84
|
if (patch.relayUrls)
|
|
85
|
-
data.relayUrls = patch.relayUrls.map(url =>
|
|
85
|
+
data.relayUrls = patch.relayUrls.map(url => url.trim()).filter(url => url.startsWith('wss://'))
|
|
86
86
|
if (patch.mailbox)
|
|
87
87
|
data.mailbox = normalizeMailboxSettings({ ...data.mailbox, ...patch.mailbox })
|
|
88
88
|
saveNodeFile(data)
|
package/node/instance.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import path from 'node:path'
|
|
|
3
3
|
import { createFsEntityStore } from './entity_store.mjs'
|
|
4
4
|
import { defaultSignalingRuntimeConfig, resolveSignalingRuntimeConfig } from './signaling_config.mjs'
|
|
5
5
|
|
|
6
|
-
/** @typedef {{ warn?: (...args: unknown[]) => void, error?: (...args: unknown[]) => void }} NodeLogger */
|
|
6
|
+
/** @typedef {{ warn?: (...args: unknown[]) => void, error?: (...args: unknown[]) => void, info?: (...args: unknown[]) => void, log?: (...args: unknown[]) => void }} NodeLogger */
|
|
7
7
|
|
|
8
8
|
/** @typedef {import('./signaling_config.mjs').SignalingRuntimeConfig} SignalingRuntimeConfig */
|
|
9
9
|
|
|
@@ -11,7 +11,7 @@ import { defaultSignalingRuntimeConfig, resolveSignalingRuntimeConfig } from './
|
|
|
11
11
|
* @typedef {{
|
|
12
12
|
* nodeDir: string
|
|
13
13
|
* entityStore: import('./entity_store.mjs').EntityStore
|
|
14
|
-
* logger: NodeLogger
|
|
14
|
+
* logger: NodeLogger | null
|
|
15
15
|
* signaling: SignalingRuntimeConfig
|
|
16
16
|
* }} NodeRuntime
|
|
17
17
|
*/
|
|
@@ -22,37 +22,28 @@ let runtime = null
|
|
|
22
22
|
/** @type {Set<(event: string, payload?: unknown) => void>} */
|
|
23
23
|
const changeListeners = new Set()
|
|
24
24
|
|
|
25
|
-
/** @type {NodeLogger} */
|
|
26
|
-
const noopLogger = {
|
|
27
|
-
/**
|
|
28
|
-
*
|
|
29
|
-
*/
|
|
30
|
-
warn() { },
|
|
31
|
-
/**
|
|
32
|
-
*
|
|
33
|
-
*/
|
|
34
|
-
error() { },
|
|
35
|
-
}
|
|
36
|
-
|
|
37
25
|
/**
|
|
38
|
-
* @param {{ nodeDir: string, entityStore?: import('./entity_store.mjs').EntityStore
|
|
39
|
-
* @returns {NodeRuntime}
|
|
26
|
+
* @param {{ nodeDir: string, entityStore?: import('./entity_store.mjs').EntityStore }} options - 节点目录与可选 entity store
|
|
27
|
+
* @returns {NodeRuntime} 初始化后的运行时
|
|
40
28
|
*/
|
|
41
29
|
export function initNode(options) {
|
|
30
|
+
if (runtime) throw new Error('p2p: initNode already called — use setNodeLogger / setSignalingRuntimeConfig or resetNodeForTests')
|
|
31
|
+
if (options?.logger !== undefined || options?.signaling !== undefined)
|
|
32
|
+
throw new Error('p2p: initNode only accepts nodeDir/entityStore — use setNodeLogger / setSignalingRuntimeConfig')
|
|
42
33
|
const nodeDir = path.resolve(String(options.nodeDir || '').trim())
|
|
43
34
|
if (!nodeDir) throw new Error('p2p: initNode requires nodeDir')
|
|
44
35
|
const entityStore = options.entityStore ?? createFsEntityStore(path.join(nodeDir, 'entities'))
|
|
45
36
|
runtime = {
|
|
46
37
|
nodeDir,
|
|
47
38
|
entityStore,
|
|
48
|
-
logger:
|
|
49
|
-
signaling:
|
|
39
|
+
logger: console,
|
|
40
|
+
signaling: resolveSignalingRuntimeConfig(),
|
|
50
41
|
}
|
|
51
42
|
return runtime
|
|
52
43
|
}
|
|
53
44
|
|
|
54
45
|
/**
|
|
55
|
-
* @returns {NodeRuntime}
|
|
46
|
+
* @returns {NodeRuntime} 当前节点运行时
|
|
56
47
|
*/
|
|
57
48
|
export function getNode() {
|
|
58
49
|
if (!runtime) throw new Error('p2p: node not initialized — call initNode() first')
|
|
@@ -67,7 +58,26 @@ export function isNodeInitialized() {
|
|
|
67
58
|
}
|
|
68
59
|
|
|
69
60
|
/**
|
|
70
|
-
* @
|
|
61
|
+
* @param {NodeLogger | null} logger - 节点日志器,null 表示静默
|
|
62
|
+
* @returns {void}
|
|
63
|
+
*/
|
|
64
|
+
export function setNodeLogger(logger) {
|
|
65
|
+
if (!runtime) throw new Error('p2p: setNodeLogger requires initNode')
|
|
66
|
+
runtime.logger = logger ?? null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {Partial<SignalingRuntimeConfig>} config - signaling 运行时补丁
|
|
71
|
+
* @returns {void}
|
|
72
|
+
*/
|
|
73
|
+
export function setSignalingRuntimeConfig(config) {
|
|
74
|
+
if (!runtime) throw new Error('p2p: setSignalingRuntimeConfig requires initNode')
|
|
75
|
+
runtime.signaling = resolveSignalingRuntimeConfig({ ...runtime.signaling, ...config })
|
|
76
|
+
emitNodeChange('signaling-changed', runtime.signaling)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @returns {SignalingRuntimeConfig} 当前 signaling 配置
|
|
71
81
|
*/
|
|
72
82
|
export function getSignalingRuntimeConfig() {
|
|
73
83
|
return runtime?.signaling ?? defaultSignalingRuntimeConfig()
|
|
@@ -81,22 +91,22 @@ export function getNodeDir() {
|
|
|
81
91
|
}
|
|
82
92
|
|
|
83
93
|
/**
|
|
84
|
-
* @returns {import('./entity_store.mjs').EntityStore}
|
|
94
|
+
* @returns {import('./entity_store.mjs').EntityStore} 当前 entity store
|
|
85
95
|
*/
|
|
86
96
|
export function getEntityStore() {
|
|
87
97
|
return getNode().entityStore
|
|
88
98
|
}
|
|
89
99
|
|
|
90
100
|
/**
|
|
91
|
-
* @returns {NodeLogger
|
|
101
|
+
* @returns {NodeLogger | null} 当前节点日志器
|
|
92
102
|
*/
|
|
93
103
|
export function getNodeLogger() {
|
|
94
|
-
return runtime?.logger ??
|
|
104
|
+
return runtime?.logger ?? null
|
|
95
105
|
}
|
|
96
106
|
|
|
97
107
|
/**
|
|
98
|
-
* @param {string} event
|
|
99
|
-
* @param {unknown} [payload]
|
|
108
|
+
* @param {string} event - 变更事件名
|
|
109
|
+
* @param {unknown} [payload] - 事件载荷
|
|
100
110
|
* @returns {void}
|
|
101
111
|
*/
|
|
102
112
|
export function emitNodeChange(event, payload) {
|
|
@@ -106,10 +116,19 @@ export function emitNodeChange(event, payload) {
|
|
|
106
116
|
}
|
|
107
117
|
|
|
108
118
|
/**
|
|
109
|
-
* @param {(event: string, payload?: unknown) => void} listener
|
|
110
|
-
* @returns {() => void}
|
|
119
|
+
* @param {(event: string, payload?: unknown) => void} listener - 变更回调
|
|
120
|
+
* @returns {() => void} 取消监听的 dispose
|
|
111
121
|
*/
|
|
112
122
|
export function onNodeChange(listener) {
|
|
113
123
|
changeListeners.add(listener)
|
|
114
124
|
return () => changeListeners.delete(listener)
|
|
115
125
|
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 测试专用:重置节点运行时。
|
|
129
|
+
* @returns {void}
|
|
130
|
+
*/
|
|
131
|
+
export function resetNodeForTests() {
|
|
132
|
+
runtime = null
|
|
133
|
+
changeListeners.clear()
|
|
134
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** 本机落盘数据 revision:信任图等订阅方只读,store 不反向依赖 trust_graph。 */
|
|
2
|
+
|
|
3
|
+
let revision = 0
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 本机数据 revision +1(信任图等订阅方)。
|
|
7
|
+
* @returns {void}
|
|
8
|
+
*/
|
|
9
|
+
export function bumpLocalDataRevision() {
|
|
10
|
+
revision++
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @returns {number} 当前本机数据 revision
|
|
15
|
+
*/
|
|
16
|
+
export function getLocalDataRevision() {
|
|
17
|
+
return revision
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 测试用:清零 revision。
|
|
22
|
+
* @returns {void}
|
|
23
|
+
*/
|
|
24
|
+
export function resetLocalDataRevisionForTests() {
|
|
25
|
+
revision = 0
|
|
26
|
+
}
|
package/node/log.mjs
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { getNodeLogger } from './instance.mjs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 连接/发现诊断日志开关。
|
|
5
|
+
* CLI(非 --quiet)默认打开;非 CLI 用 `setConnectivityDebug(true)`。
|
|
6
|
+
* @type {boolean}
|
|
7
|
+
*/
|
|
8
|
+
let connectivityDebug = false
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {boolean} enabled 是否输出连接诊断
|
|
12
|
+
* @returns {void}
|
|
13
|
+
*/
|
|
14
|
+
export function setConnectivityDebug(enabled) {
|
|
15
|
+
connectivityDebug = !!enabled
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @returns {boolean} 当前是否输出连接诊断
|
|
20
|
+
*/
|
|
21
|
+
export function isConnectivityDebug() {
|
|
22
|
+
return connectivityDebug
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 缩短 nodeHash 便于对照两边日志。
|
|
27
|
+
* @param {string | null | undefined} hash 完整 hash
|
|
28
|
+
* @param {number} [n=8] 前缀长度
|
|
29
|
+
* @returns {string} 短 hash
|
|
30
|
+
*/
|
|
31
|
+
export function shortHash(hash, n = 8) {
|
|
32
|
+
const value = String(hash || '')
|
|
33
|
+
return value.length <= n ? value : value.slice(0, n)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 连接诊断 info(未开启或无 logger 时静默)。
|
|
38
|
+
* @param {string} message 消息
|
|
39
|
+
* @param {object} [extra] 附加字段
|
|
40
|
+
* @returns {void}
|
|
41
|
+
*/
|
|
42
|
+
export function nodeDebug(message, extra) {
|
|
43
|
+
if (!connectivityDebug) return
|
|
44
|
+
const logger = getNodeLogger()
|
|
45
|
+
if (!logger?.info) return
|
|
46
|
+
if (extra === undefined) logger.info(message)
|
|
47
|
+
else logger.info(message, extra)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 测试专用:关掉连接诊断。
|
|
52
|
+
* @returns {void}
|
|
53
|
+
*/
|
|
54
|
+
export function resetConnectivityDebugForTests() {
|
|
55
|
+
connectivityDebug = false
|
|
56
|
+
}
|
package/node/network.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { isEntityHash128 } from '../core/entity_id.mjs'
|
|
2
2
|
import { isHex64, normalizeHex64 } from '../core/hexIds.mjs'
|
|
3
|
-
import { invalidateTrustGraphCache } from '../trust_graph/cache.mjs'
|
|
4
3
|
|
|
5
4
|
import { loadDenylist } from './denylist.mjs'
|
|
6
5
|
import { getNodeDir, isNodeInitialized } from './instance.mjs'
|
|
6
|
+
import { bumpLocalDataRevision } from './local_data_revision.mjs'
|
|
7
7
|
import { readNodeJsonSync, writeNodeJsonSync } from './storage.mjs'
|
|
8
8
|
|
|
9
9
|
|
|
@@ -28,6 +28,7 @@ import { readNodeJsonSync, writeNodeJsonSync } from './storage.mjs'
|
|
|
28
28
|
|
|
29
29
|
const DATA_NAME = 'network'
|
|
30
30
|
const MAX_EXPLORE = 500
|
|
31
|
+
const MAX_TRUSTED = 64
|
|
31
32
|
const MAX_HINTS = 256
|
|
32
33
|
const MAX_HINTS_PER_SOURCE = 12
|
|
33
34
|
const DEFAULT_EXPLORE_TTL_MS = 7 * 24 * 60 * 60 * 1000
|
|
@@ -52,18 +53,18 @@ export function normalizeNetwork(raw) {
|
|
|
52
53
|
* @returns {string[]} 去重 nodeHash 列表
|
|
53
54
|
*/
|
|
54
55
|
const pickIds = key => [...new Set(
|
|
55
|
-
(
|
|
56
|
-
.map(id => normalizeHex64(id) ||
|
|
56
|
+
(file[key] || [])
|
|
57
|
+
.map(id => normalizeHex64(id) || id.trim())
|
|
57
58
|
.filter(id => isHex64(id)),
|
|
58
59
|
)]
|
|
59
|
-
const hints = (
|
|
60
|
+
const hints = (file.hints || [])
|
|
60
61
|
.map(hint => ({
|
|
61
|
-
nodeHash: normalizeHex64(hint
|
|
62
|
-
source:
|
|
63
|
-
kind:
|
|
64
|
-
weight: Number.isFinite(Number(hint
|
|
65
|
-
expiresAt: Number(hint
|
|
66
|
-
...hint
|
|
62
|
+
nodeHash: normalizeHex64(hint.nodeHash) || '',
|
|
63
|
+
source: hint.source?.trim() || '',
|
|
64
|
+
kind: hint.kind?.trim() || '',
|
|
65
|
+
weight: Number.isFinite(Number(hint.weight)) ? Number(hint.weight) : 0.1,
|
|
66
|
+
expiresAt: Number(hint.expiresAt) || 0,
|
|
67
|
+
...hint.groupId ? { groupId: hint.groupId.trim() } : {},
|
|
67
68
|
}))
|
|
68
69
|
.filter(hint => isHex64(hint.nodeHash))
|
|
69
70
|
return {
|
|
@@ -97,10 +98,10 @@ export function capHintsBySource(hints, maxPerSource = MAX_HINTS_PER_SOURCE) {
|
|
|
97
98
|
const counts = new Map()
|
|
98
99
|
const out = []
|
|
99
100
|
for (const hint of [...hints].reverse()) {
|
|
100
|
-
const
|
|
101
|
-
const n = counts.get(
|
|
101
|
+
const source = String(hint.source || 'unknown')
|
|
102
|
+
const n = counts.get(source) ?? 0
|
|
102
103
|
if (n >= maxPerSource) continue
|
|
103
|
-
counts.set(
|
|
104
|
+
counts.set(source, n + 1)
|
|
104
105
|
out.unshift(hint)
|
|
105
106
|
}
|
|
106
107
|
return out
|
|
@@ -115,10 +116,11 @@ export function saveNetwork(data) {
|
|
|
115
116
|
const now = Date.now()
|
|
116
117
|
clean.hints = capHintsBySource(clean.hints.filter(h => !h.expiresAt || h.expiresAt > now)).slice(-MAX_HINTS)
|
|
117
118
|
clean.explorePeers = clean.explorePeers.slice(-MAX_EXPLORE)
|
|
119
|
+
clean.trustedPeers = clean.trustedPeers.slice(-MAX_TRUSTED)
|
|
118
120
|
writeNodeJsonSync(DATA_NAME, clean)
|
|
119
121
|
networkCache = clean
|
|
120
122
|
networkCacheNodeDir = isNodeInitialized() ? getNodeDir() : ''
|
|
121
|
-
|
|
123
|
+
bumpLocalDataRevision()
|
|
122
124
|
}
|
|
123
125
|
|
|
124
126
|
/**
|
|
@@ -196,6 +198,36 @@ export function mergeNetworkPeerPools(patch = {}) {
|
|
|
196
198
|
saveNetwork(net)
|
|
197
199
|
}
|
|
198
200
|
|
|
201
|
+
/**
|
|
202
|
+
* 稳定探索对端升入熟人池(从 explore 移除并追加 trusted)。
|
|
203
|
+
* @param {string} nodeHash 对端 nodeHash
|
|
204
|
+
* @returns {void}
|
|
205
|
+
*/
|
|
206
|
+
export function promoteExplorePeer(nodeHash) {
|
|
207
|
+
const net = loadNetwork()
|
|
208
|
+
const id = normalizeHex64(nodeHash)
|
|
209
|
+
if (!isHex64(id)) return
|
|
210
|
+
net.explorePeers = net.explorePeers.filter(peer => peer !== id)
|
|
211
|
+
if (!net.trustedPeers.includes(id)) net.trustedPeers.push(id)
|
|
212
|
+
net.lastRosterAt = Date.now()
|
|
213
|
+
saveNetwork(net)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* 整表替换 trusted/explore 池(可缩池)。
|
|
218
|
+
* @param {{ trustedPeers?: string[], explorePeers?: string[] }} pools - 要替换的 peer 池
|
|
219
|
+
* @returns {void}
|
|
220
|
+
*/
|
|
221
|
+
export function replaceNetworkPeerPools(pools = {}) {
|
|
222
|
+
const net = loadNetwork()
|
|
223
|
+
if (Array.isArray(pools.trustedPeers))
|
|
224
|
+
net.trustedPeers = pools.trustedPeers.map(id => normalizeHex64(id)).filter(id => isHex64(id))
|
|
225
|
+
if (Array.isArray(pools.explorePeers))
|
|
226
|
+
net.explorePeers = pools.explorePeers.map(id => normalizeHex64(id)).filter(id => isHex64(id))
|
|
227
|
+
net.lastRosterAt = Date.now()
|
|
228
|
+
saveNetwork(net)
|
|
229
|
+
}
|
|
230
|
+
|
|
199
231
|
/**
|
|
200
232
|
* @param {string} groupId 群 scope
|
|
201
233
|
* @param {'node' | 'subject' | 'entity'} scope denylist 作用域
|
|
@@ -18,10 +18,10 @@ import {
|
|
|
18
18
|
} from '../reputation/engine.mjs'
|
|
19
19
|
import { pickNodeScoreFromReputation } from '../reputation/pick_score.mjs'
|
|
20
20
|
import reputationTunables from '../reputation/tunables.json' with { type: 'json' }
|
|
21
|
-
import { invalidateTrustGraphCache } from '../trust_graph/cache.mjs'
|
|
22
21
|
import { withAsyncMutex } from '../utils/async_mutex.mjs'
|
|
23
22
|
|
|
24
23
|
import { getNodeDir, getNodeLogger, isNodeInitialized } from './instance.mjs'
|
|
24
|
+
import { bumpLocalDataRevision } from './local_data_revision.mjs'
|
|
25
25
|
import { readNodeJsonSync, writeNodeJsonSync } from './storage.mjs'
|
|
26
26
|
|
|
27
27
|
const DATA_NAME = 'reputation'
|
|
@@ -88,7 +88,7 @@ export function saveReputation(data) {
|
|
|
88
88
|
pruneReputationFile(data)
|
|
89
89
|
writeNodeJsonSync(DATA_NAME, data)
|
|
90
90
|
reputationCache = data
|
|
91
|
-
|
|
91
|
+
bumpLocalDataRevision()
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/**
|
|
@@ -211,7 +211,7 @@ export async function applyVolatileSlashAlert(alert) {
|
|
|
211
211
|
* @param {object} [groupSettings] 群设置
|
|
212
212
|
* @returns {object} VOLATILE 载荷
|
|
213
213
|
*/
|
|
214
|
-
export function
|
|
214
|
+
export function buildUnverifiedSlashAlert(senderPubKeyHash, content, groupSettings = {}) {
|
|
215
215
|
const targetPubKeyHash = assertHex64(content.targetPubKeyHash, 'slash target')
|
|
216
216
|
const claim = Number.isFinite(Number(content.claim)) ? Number(content.claim) : reputationTunables.slashDefaultClaim
|
|
217
217
|
const sender = assertHex64(senderPubKeyHash, 'slash sender')
|
|
@@ -268,7 +268,7 @@ export function applyDecayCollusionAfterSlash(targetPubKeyHash, inviteEdges) {
|
|
|
268
268
|
void mutateReputation(data => {
|
|
269
269
|
const applied = applyDecayCollusionAfterSlashPure(data, targetPubKeyHash, inviteEdges)
|
|
270
270
|
if (applied.length)
|
|
271
|
-
getNodeLogger()
|
|
271
|
+
getNodeLogger()?.warn?.('reputation: collusion decay after slash', {
|
|
272
272
|
target: targetPubKeyHash.trim().toLowerCase(),
|
|
273
273
|
upstreamCount: applied.length,
|
|
274
274
|
hops: applied.map(row => row.hop),
|