@steve02081504/fount-p2p 0.0.4 → 0.0.5

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.
Files changed (43) hide show
  1. package/core/bytes_codec.mjs +0 -27
  2. package/core/constants.mjs +0 -31
  3. package/core/hexIds.mjs +0 -13
  4. package/crypto/key.mjs +14 -15
  5. package/dag/index.mjs +0 -73
  6. package/dag/storage.mjs +0 -22
  7. package/federation/entity_key_chain.mjs +0 -9
  8. package/federation/topo_order_memo.mjs +0 -15
  9. package/files/assemble.mjs +55 -39
  10. package/files/assemble_stream.mjs +96 -44
  11. package/files/chunk_provider_registry.mjs +0 -6
  12. package/files/chunk_store.mjs +1 -11
  13. package/files/evfs.mjs +64 -37
  14. package/files/manifest.mjs +11 -2
  15. package/files/manifest_acl_registry.mjs +0 -6
  16. package/files/transfer_key.mjs +12 -6
  17. package/files/transfer_key_registry.mjs +0 -7
  18. package/governance/branch.mjs +0 -11
  19. package/link/channel_mux.mjs +2 -0
  20. package/mailbox/importance.mjs +0 -33
  21. package/mailbox/settings.mjs +0 -9
  22. package/node/denylist.mjs +0 -24
  23. package/node/network.mjs +0 -51
  24. package/node/personal_block.mjs +0 -35
  25. package/node/reputation_store.mjs +0 -9
  26. package/node/retention_policy.mjs +0 -15
  27. package/package.json +1 -1
  28. package/registries/event_type.mjs +0 -12
  29. package/registries/inbound.mjs +0 -22
  30. package/reputation/engine.mjs +0 -11
  31. package/schemas/part_query.mjs +156 -0
  32. package/transport/ice_servers.mjs +0 -8
  33. package/transport/peer_identity_maps.mjs +0 -58
  34. package/transport/remote_user_room.mjs +0 -12
  35. package/transport/rtc_connection_budget.mjs +2 -0
  36. package/transport/user_room.mjs +2 -11
  37. package/trust_graph/cache.mjs +0 -13
  38. package/trust_graph/engine.mjs +0 -24
  39. package/utils/async_mutex.mjs +0 -9
  40. package/wire/part_fanout.mjs +0 -19
  41. package/wire/part_query.mjs +487 -0
  42. package/wire/part_query.tunables.json +14 -0
  43. package/wire/part_query_cache.mjs +94 -0
@@ -1,18 +1,15 @@
1
1
  import { Buffer } from 'node:buffer'
2
- import { createHash } from 'node:crypto'
2
+ import { createHash, randomBytes } from 'node:crypto'
3
3
  import { Readable } from 'node:stream'
4
4
 
5
5
  import { FEDERATION_CHUNK_MAX_BYTES } from '../core/constants.mjs'
6
- import {
7
- encryptConvergentPlaintext,
8
- encryptRandomPlaintext,
9
- } from '../crypto/key.mjs'
10
6
 
7
+ import { encryptSliceToPart } from './assemble.mjs'
11
8
  import { decryptPart } from './transfer_key.mjs'
12
9
 
13
10
  /**
14
11
  * @typedef {import('./manifest.mjs').CeMode} CeMode
15
- * @typedef {{ hash: string, size: number, raw: Buffer }} EncryptedPart
12
+ * @typedef {{ hash: string, size: number, raw: Buffer, contentHash?: string }} EncryptedPart
16
13
  */
17
14
 
18
15
  /**
@@ -21,15 +18,15 @@ import { decryptPart } from './transfer_key.mjs'
21
18
  * @param {CeMode} [ceMode] 加密模式
22
19
  * @param {(part: EncryptedPart) => Promise<void>} onPart 每块回调
23
20
  * @param {number} [maxBytes] 最大字节
24
- * @returns {Promise<{ contentHash: string, parts: Array<{ hash: string, size: number }>, contentKey?: Buffer }>} 分块结果
21
+ * @returns {Promise<{ contentHash: string, parts: Array<{ hash: string, size: number, contentHash?: string }>, contentKey?: Buffer }>} 分块结果
25
22
  */
26
23
  export async function encryptReadableToParts(readable, ceMode = 'convergent', onPart, maxBytes = Infinity) {
27
24
  const digest = createHash('sha256')
28
25
  /** @type {Buffer[]} */
29
26
  let pending = Buffer.alloc(0)
30
- /** @type {Array<{ hash: string, size: number }>} */
27
+ /** @type {Array<{ hash: string, size: number, contentHash?: string }>} */
31
28
  const parts = []
32
- let contentKey = null
29
+ const contentKey = ceMode === 'random' ? randomBytes(32) : null
33
30
  let total = 0
34
31
 
35
32
  /**
@@ -38,12 +35,11 @@ export async function encryptReadableToParts(readable, ceMode = 'convergent', on
38
35
  */
39
36
  const flushSlice = async (slice) => {
40
37
  digest.update(slice)
41
- const enc = ceMode === 'random'
42
- ? encryptRandomPlaintext(slice)
43
- : encryptConvergentPlaintext(slice)
44
- if (ceMode === 'random') contentKey = enc.contentKey
45
- const part = { hash: enc.ciphertextHash, size: enc.raw.length, raw: enc.raw }
46
- parts.push({ hash: part.hash, size: part.size })
38
+ const part = encryptSliceToPart(slice, ceMode, contentKey)
39
+ /** @type {{ hash: string, size: number, contentHash?: string }} */
40
+ const meta = { hash: part.hash, size: part.size }
41
+ if (part.contentHash) meta.contentHash = part.contentHash
42
+ parts.push(meta)
47
43
  await onPart(part)
48
44
  }
49
45
 
@@ -71,55 +67,111 @@ export async function encryptReadableToParts(readable, ceMode = 'convergent', on
71
67
  }
72
68
 
73
69
  /**
74
- * manifest 各密文块解密后串联为可读流。
70
+ * @param {import('node:stream').Readable} stream 密文流
71
+ * @returns {Promise<Buffer | null>} 下一可读块;流结束为 null
72
+ */
73
+ function readStreamChunk(stream) {
74
+ const chunk = stream.read()
75
+ if (chunk) return Promise.resolve(Buffer.from(chunk))
76
+ if (stream.readableEnded) return Promise.resolve(null)
77
+ return new Promise((resolve, reject) => {
78
+ /**
79
+ *
80
+ */
81
+ const onReadable = () => {
82
+ cleanup()
83
+ const next = stream.read()
84
+ resolve(next ? Buffer.from(next) : Buffer.alloc(0))
85
+ }
86
+ /**
87
+ *
88
+ */
89
+ const onEnd = () => {
90
+ cleanup()
91
+ resolve(null)
92
+ }
93
+ /**
94
+ * @param {Error} err 错误
95
+ */
96
+ const onError = err => {
97
+ cleanup()
98
+ reject(err)
99
+ }
100
+ /**
101
+ *
102
+ */
103
+ const cleanup = () => {
104
+ stream.off('readable', onReadable)
105
+ stream.off('end', onEnd)
106
+ stream.off('error', onError)
107
+ }
108
+ stream.once('readable', onReadable)
109
+ stream.once('end', onEnd)
110
+ stream.once('error', onError)
111
+ })
112
+ }
113
+
114
+ /**
115
+ * 将 manifest 各密文块按 part.size 拼齐后解密,串联为明文可读流。
75
116
  * @param {import('./manifest.mjs').FileManifest} manifest 清单
76
117
  * @param {import('node:stream').Readable[]} partStreams 按序密文流
77
118
  * @param {Buffer | null} contentKey 随机密钥
78
119
  * @returns {Readable} 明文流
79
120
  */
80
121
  export function createManifestPlaintextStream(manifest, partStreams, contentKey) {
122
+ if (partStreams.length !== manifest.parts.length)
123
+ throw new Error('part stream count mismatch')
124
+
81
125
  let partIndex = 0
82
- let currentStream = null
126
+ /** @type {Buffer} */
127
+ let pending = Buffer.alloc(0)
83
128
  const digest = createHash('sha256')
129
+ let finished = false
84
130
 
85
131
  return new Readable({
86
132
  /**
87
133
  *
88
134
  */
89
135
  async read() {
136
+ if (finished) return
90
137
  try {
91
- while (true) {
92
- if (!currentStream) {
93
- if (partIndex >= partStreams.length) {
94
- this.push(null)
95
- return
96
- }
97
- currentStream = partStreams[partIndex++]
138
+ while (partIndex < manifest.parts.length) {
139
+ const need = Number(manifest.parts[partIndex].size) || 0
140
+ const stream = partStreams[partIndex]
141
+ while (pending.length < need) {
142
+ const more = await readStreamChunk(stream)
143
+ if (more === null) break
144
+ if (more.length) pending = Buffer.concat([pending, more])
145
+ }
146
+ if (pending.length < need) {
147
+ this.destroy(new Error('short ciphertext part'))
148
+ return
98
149
  }
99
- const chunk = currentStream.read()
100
- if (chunk === null) {
101
- currentStream = await new Promise((resolve, reject) => {
102
- currentStream.once('end', () => resolve(null))
103
- currentStream.once('error', reject)
104
- currentStream.resume()
105
- })
106
- if (currentStream === null) {
107
- currentStream = null
108
- continue
109
- }
150
+ const enc = pending.subarray(0, need)
151
+ pending = pending.subarray(need)
152
+ if (pending.length) {
153
+ this.destroy(new Error('trailing ciphertext in part stream'))
154
+ return
110
155
  }
111
- if (chunk?.length) {
112
- const plain = decryptPart(Buffer.from(chunk), manifest, contentKey)
113
- if (!plain) {
114
- this.destroy(new Error('decrypt failed'))
115
- return
116
- }
117
- digest.update(plain)
118
- if (!this.push(plain))
119
- return
156
+ const plain = decryptPart(enc, manifest, contentKey, partIndex)
157
+ if (!plain) {
158
+ this.destroy(new Error('decrypt failed'))
159
+ return
160
+ }
161
+ digest.update(plain)
162
+ partIndex++
163
+ this.push(plain)
164
+ return
165
+ }
166
+ if (manifest.contentHash) {
167
+ const got = digest.digest('hex')
168
+ if (got !== String(manifest.contentHash).toLowerCase()) {
169
+ this.destroy(new Error('contentHash mismatch'))
120
170
  return
121
171
  }
122
172
  }
173
+ finished = true
174
+ this.push(null)
123
175
  }
124
176
  catch (error) {
125
177
  this.destroy(error instanceof Error ? error : new Error(String(error)))
@@ -32,12 +32,6 @@ export function unregisterChunkProviders(ownerId) {
32
32
  nodeHashProvidersByOwner.delete(key)
33
33
  }
34
34
 
35
- /** @returns {void} */
36
- export function clearChunkProviderRegistry() {
37
- federationFetchersByOwner.clear()
38
- nodeHashProvidersByOwner.clear()
39
- }
40
-
41
35
  /**
42
36
  * @param {string} username 用户
43
37
  * @param {string} groupId 群 ID
@@ -2,7 +2,6 @@ import { Buffer } from 'node:buffer'
2
2
  import fs from 'node:fs'
3
3
  import fsp from 'node:fs/promises'
4
4
  import { dirname, join } from 'node:path'
5
- import { Readable } from 'node:stream'
6
5
  import { pipeline } from 'node:stream/promises'
7
6
 
8
7
  import { isHex64 } from '../core/hexIds.mjs'
@@ -74,14 +73,5 @@ export async function putChunk(hash, data) {
74
73
  export async function putChunkFromStream(hash, readable) {
75
74
  const filePath = chunkStorePath(hash)
76
75
  await fsp.mkdir(dirname(filePath), { recursive: true })
77
- const writable = fs.createWriteStream(filePath)
78
- await pipeline(readable, writable)
79
- }
80
-
81
- /**
82
- * @param {string} hash 64 位十六进制
83
- * @returns {Readable} chunk 可读流
84
- */
85
- export function chunkToReadable(hash) {
86
- return createChunkReadStream(hash)
76
+ await pipeline(readable, fs.createWriteStream(filePath))
87
77
  }
package/files/evfs.mjs CHANGED
@@ -4,14 +4,52 @@ import { Readable } from 'node:stream'
4
4
  import { FEDERATION_CHUNK_MAX_BYTES } from '../core/constants.mjs'
5
5
  import { getEntityStore } from '../node/instance.mjs'
6
6
 
7
- import { buildFileManifestFromEnc, encryptPlaintextToParts, encryptPlaintextToMultiPartsAsync } from './assemble.mjs'
8
- import { encryptReadableToParts } from './assemble_stream.mjs'
7
+ import { buildFileManifestFromEnc, encryptPlaintextToMultiPartsAsync, encryptPlaintextToParts, manifestPartsForPersist } from './assemble.mjs'
8
+ import { createManifestPlaintextStream, encryptReadableToParts } from './assemble_stream.mjs'
9
9
  import { fetchChunk } from './chunk_fetch.mjs'
10
- import { getChunk, hasChunk, putChunk } from './chunk_store.mjs'
10
+ import { createChunkReadStream, getChunk, hasChunk, putChunk } from './chunk_store.mjs'
11
11
  import { normalizeFileManifest, publicTransferKeyDescriptor } from './manifest.mjs'
12
- import { assembleManifestPlaintext } from './transfer_key.mjs'
12
+ import { assembleManifestPlaintext, resolveContentKey } from './transfer_key.mjs'
13
13
  import { readDagManifestPlaintext, resolveTransferKeyDeps } from './transfer_key_registry.mjs'
14
14
 
15
+ /**
16
+ * @param {string} replicaUsername 副本用户名
17
+ * @param {import('./manifest.mjs').FileManifest} manifest 清单
18
+ * @returns {{ getGroupFileMasterKey?: Function, getVaultMasterKey?: Function }} 密钥依赖
19
+ */
20
+ function transferKeyDepsForReplica(replicaUsername, manifest) {
21
+ const rawDeps = resolveTransferKeyDeps(undefined, manifest)
22
+ return {
23
+ getGroupFileMasterKey: rawDeps.getGroupFileMasterKey
24
+ ? (groupId, keyGeneration) => rawDeps.getGroupFileMasterKey(replicaUsername, groupId, keyGeneration)
25
+ : undefined,
26
+ getVaultMasterKey: rawDeps.getVaultMasterKey
27
+ ? entityHash => rawDeps.getVaultMasterKey(replicaUsername, entityHash)
28
+ : undefined,
29
+ }
30
+ }
31
+
32
+ /**
33
+ * @param {string} username 拉取身份
34
+ * @param {import('./manifest.mjs').FileManifest} manifest 清单
35
+ * @param {{ fetchChunk?: Function }} [opts] miss 拉取
36
+ * @returns {Promise<boolean>} 全部 part 是否已就位
37
+ */
38
+ async function ensureManifestPartsLocal(username, manifest, opts = {}) {
39
+ for (const part of manifest.parts) {
40
+ if (await hasChunk(part.hash)) continue
41
+ const fetchedChunk = await (opts.fetchChunk || fetchChunk)({
42
+ username,
43
+ ciphertextHash: part.hash,
44
+ ownerEntityHash: manifest.ownerEntityHash,
45
+ groupId: manifest.transferKeyDescriptor.groupId,
46
+ })
47
+ if (!fetchedChunk) return false
48
+ await putChunk(part.hash, fetchedChunk)
49
+ }
50
+ return true
51
+ }
52
+
15
53
  /**
16
54
  * @param {string} ownerEntityHash 所有者
17
55
  * @param {string} logicalPath 路径
@@ -54,38 +92,14 @@ export async function readManifestPlaintext(replicaUsername, manifest, opts = {}
54
92
  }
55
93
 
56
94
  const username = opts.username || replicaUsername
95
+ if (!await ensureManifestPartsLocal(username, manifest, opts)) return null
96
+
57
97
  /** @type {Buffer[]} */
58
98
  const partBytes = []
59
- for (const part of manifest.parts) {
60
- let ciphertextBytes = null
61
- if (await hasChunk(part.hash))
62
- ciphertextBytes = await getChunk(part.hash)
63
- else {
64
- const fetchedChunk = await (opts.fetchChunk || fetchChunk)({
65
- username,
66
- ciphertextHash: part.hash,
67
- ownerEntityHash: manifest.ownerEntityHash,
68
- groupId: manifest.transferKeyDescriptor.groupId,
69
- })
70
- if (fetchedChunk) {
71
- await putChunk(part.hash, fetchedChunk)
72
- ciphertextBytes = Buffer.from(fetchedChunk)
73
- }
74
- }
75
- if (!ciphertextBytes) return null
76
- partBytes.push(ciphertextBytes)
77
- }
99
+ for (const part of manifest.parts)
100
+ partBytes.push(await getChunk(part.hash))
78
101
 
79
- const rawDeps = resolveTransferKeyDeps(undefined, manifest)
80
- const deps = {
81
- getGroupFileMasterKey: rawDeps.getGroupFileMasterKey
82
- ? (groupId, keyGeneration) => rawDeps.getGroupFileMasterKey(replicaUsername, groupId, keyGeneration)
83
- : undefined,
84
- getVaultMasterKey: rawDeps.getVaultMasterKey
85
- ? entityHash => rawDeps.getVaultMasterKey(replicaUsername, entityHash)
86
- : undefined,
87
- }
88
- return assembleManifestPlaintext(manifest, partBytes, deps)
102
+ return assembleManifestPlaintext(manifest, partBytes, transferKeyDepsForReplica(replicaUsername, manifest))
89
103
  }
90
104
 
91
105
  /**
@@ -95,9 +109,22 @@ export async function readManifestPlaintext(replicaUsername, manifest, opts = {}
95
109
  * @returns {Promise<import('node:stream').Readable | null>} 明文流
96
110
  */
97
111
  export async function readManifestPlaintextStream(replicaUsername, manifest, opts = {}) {
98
- const plain = await readManifestPlaintext(replicaUsername, manifest, opts)
99
- if (!plain) return null
100
- return Readable.from(plain)
112
+ const dagGroupId = manifest.meta?.groupId
113
+ if (Array.isArray(manifest.meta?.dagParts) && dagGroupId) {
114
+ const plain = await readManifestPlaintext(replicaUsername, manifest, opts)
115
+ if (!plain) return null
116
+ return Readable.from([plain])
117
+ }
118
+
119
+ const username = opts.username || replicaUsername
120
+ if (!await ensureManifestPartsLocal(username, manifest, opts)) return null
121
+
122
+ const deps = transferKeyDepsForReplica(replicaUsername, manifest)
123
+ const contentKey = await resolveContentKey(manifest.transferKeyDescriptor, manifest, deps)
124
+ if (manifest.ceMode === 'random' && !contentKey) return null
125
+
126
+ const partStreams = manifest.parts.map(part => createChunkReadStream(part.hash))
127
+ return createManifestPlaintextStream(manifest, partStreams, contentKey)
101
128
  }
102
129
 
103
130
  /**
@@ -178,7 +205,7 @@ export async function putFileManifestFromStream(params) {
178
205
  size: plainSize,
179
206
  contentHash: enc.contentHash,
180
207
  ceMode,
181
- parts: enc.parts,
208
+ parts: manifestPartsForPersist(enc.parts),
182
209
  transferKeyDescriptor: transferKeyDescriptor || publicTransferKeyDescriptor(),
183
210
  meta,
184
211
  })
@@ -3,7 +3,7 @@ import { isHex64 } from '../core/hexIds.mjs'
3
3
 
4
4
  /** @typedef {'plain' | 'convergent' | 'random'} CeMode */
5
5
 
6
- /** @typedef {{ hash: string, size: number }} ManifestPart */
6
+ /** @typedef {{ hash: string, size: number, contentHash?: string }} ManifestPart */
7
7
 
8
8
  /**
9
9
  * @typedef {{
@@ -48,7 +48,16 @@ export function normalizeFileManifest(input) {
48
48
  if (!CE_MODES.has(ceMode)) return null
49
49
  const parts = Array.isArray(input.parts)
50
50
  ? input.parts
51
- .map(part => ({ hash: String(part?.hash || '').trim().toLowerCase(), size: Number(part?.size) || 0 }))
51
+ .map(part => {
52
+ /** @type {ManifestPart} */
53
+ const out = {
54
+ hash: String(part?.hash || '').trim().toLowerCase(),
55
+ size: Number(part?.size) || 0,
56
+ }
57
+ const partContentHash = String(part?.contentHash || '').trim().toLowerCase()
58
+ if (isHex64(partContentHash)) out.contentHash = partContentHash
59
+ return out
60
+ })
52
61
  .filter(part => isHex64(part.hash))
53
62
  : []
54
63
  if (!parts.length) return null
@@ -61,12 +61,6 @@ export function unregisterManifestAclMatcher(ownerId) {
61
61
  matchersByOwner.delete(String(ownerId))
62
62
  }
63
63
 
64
- /** @returns {void} */
65
- export function clearManifestAclRegistry() {
66
- handlersByType.clear()
67
- matchersByOwner.clear()
68
- }
69
-
70
64
  /**
71
65
  * @param {import('./manifest.mjs').FileManifest | null | undefined} manifest manifest
72
66
  * @param {string} ownerEntityHash 所有者
@@ -46,17 +46,23 @@ export async function resolveContentKey(descriptor, manifest, deps = {}) {
46
46
  * @param {Buffer | Uint8Array} encryptedPartBytes 密文块
47
47
  * @param {FileManifest} manifest manifest
48
48
  * @param {Buffer | null} contentKey random 模式密钥
49
+ * @param {number} [partIndex] 分块下标(多块 convergent 用 part.contentHash)
49
50
  * @returns {Buffer | null} 明文
50
51
  */
51
- export function decryptPart(encryptedPartBytes, manifest, contentKey) {
52
+ export function decryptPart(encryptedPartBytes, manifest, contentKey, partIndex = 0) {
52
53
  if (manifest.ceMode === 'plain')
53
54
  return Buffer.from(encryptedPartBytes)
54
55
 
55
- if (manifest.ceMode === 'convergent')
56
- return decryptConvergentCiphertext(encryptedPartBytes, manifest.contentHash)
56
+ if (manifest.ceMode === 'convergent') {
57
+ const partPlainHash = manifest.parts[partIndex]?.contentHash || manifest.contentHash
58
+ return decryptConvergentCiphertext(encryptedPartBytes, partPlainHash)
59
+ }
57
60
 
58
- if (manifest.ceMode === 'random' && contentKey)
59
- return decryptRandomCiphertext(encryptedPartBytes, contentKey, manifest.contentHash)
61
+ if (manifest.ceMode === 'random' && contentKey) {
62
+ // 多块时各块明文 hash ≠ 整文件 contentHash;完整性在 assemble 末尾校验
63
+ const verifyHash = manifest.parts.length === 1 ? manifest.contentHash : ''
64
+ return decryptRandomCiphertext(encryptedPartBytes, contentKey, verifyHash)
65
+ }
60
66
 
61
67
  return null
62
68
  }
@@ -73,7 +79,7 @@ export async function assembleManifestPlaintext(manifest, partBytes, deps = {})
73
79
  /** @type {Buffer[]} */
74
80
  const plains = []
75
81
  for (let index = 0; index < manifest.parts.length; index++) {
76
- const plain = decryptPart(partBytes[index], manifest, contentKey)
82
+ const plain = decryptPart(partBytes[index], manifest, contentKey, index)
77
83
  if (!plain) return null
78
84
  plains.push(plain)
79
85
  }
@@ -57,13 +57,6 @@ export function unregisterTransferKeyDeps(ownerId) {
57
57
  unregisterManifestOwnerMatchers(key)
58
58
  }
59
59
 
60
- /** @returns {void} */
61
- export function clearTransferKeyRegistry() {
62
- transferDepsByOwner.clear()
63
- dagPlaintextReadersByOwner.clear()
64
- manifestOwnerMatchers.length = 0
65
- }
66
-
67
60
  /**
68
61
  * 从 manifest 推断 transfer key 注册方 id(按 registerManifestOwnerMatcher 注册顺序匹配)。
69
62
  * @param {import('./manifest.mjs').FileManifest} manifest 清单
@@ -93,17 +93,6 @@ export function descendantClosureFromTip(rootId, byId) {
93
93
  return out
94
94
  }
95
95
 
96
- /**
97
- * 事件是否在 root 的后代闭包中(含 root)。
98
- * @param {string} eventId 事件 id
99
- * @param {string} rootId 根 id
100
- * @param {Map<string, object>} byId id→事件
101
- * @returns {boolean} 是否为 root 的后代(含 root 自身)
102
- */
103
- export function isDescendantOfTip(eventId, rootId, byId) {
104
- return descendantClosureFromTip(rootId, byId).has(eventId)
105
- }
106
-
107
96
  /**
108
97
  * @param {string} tipId 叶 id
109
98
  * @param {Map<string, object>} byId id→事件
@@ -38,6 +38,8 @@ const DEFAULT_ACTION_PRIORITIES = Object.freeze({
38
38
  discovery_query_response: 3,
39
39
  part_invoke: 3,
40
40
  part_invoke_response: 3,
41
+ part_query_req: 5,
42
+ part_query_res: 5,
41
43
  char_rpc: 3,
42
44
  fed_chunk_put: 5,
43
45
  fed_chunk_get: 5,
@@ -6,39 +6,6 @@
6
6
 
7
7
  const TIER_ORDER = { quarantine: 0, normal: 1, trusted: 2 }
8
8
 
9
- /**
10
- * @param {number} score 信誉分
11
- * @returns {MailboxTier} 分层
12
- */
13
- export function mailboxTierFromScore(score) {
14
- const numericScore = Number(score)
15
- if (!Number.isFinite(numericScore)) return 'quarantine'
16
- if (numericScore >= 0.45) return 'trusted'
17
- if (numericScore >= 0.12) return 'normal'
18
- return 'quarantine'
19
- }
20
-
21
- /**
22
- * @param {object} opts 参数
23
- * @param {number} [opts.senderScore] 发件方信誉
24
- * @param {number} [opts.recipientScore] 收件方关系信誉(可选)
25
- * @param {boolean} [opts.knownMember] 是否本地已知成员/节点
26
- * @param {number} [opts.hop] 转发跳数
27
- * @returns {{ tier: MailboxTier, score: number }} 分层与分数
28
- */
29
- export function scoreMailboxImportance(opts = {}) {
30
- const sender = Number(opts.senderScore ?? 0)
31
- const recipient = Number(opts.recipientScore ?? sender)
32
- const known = !!opts.knownMember
33
- const hop = Math.max(0, Number(opts.hop) || 0)
34
- let score = sender * 0.65 + recipient * 0.35
35
- if (known) score += 0.15
36
- score -= hop * 0.08
37
- if (score < 0) score = 0
38
- if (score > 1) score = 1
39
- return { tier: mailboxTierFromScore(score), score }
40
- }
41
-
42
9
  /**
43
10
  * 按 tier 与 storedAt 排序(低 tier 先淘汰)。
44
11
  * @param {object[]} rows 记录
@@ -1,4 +1,3 @@
1
- import { getNodeTransportSettings } from '../node/identity.mjs'
2
1
  import {
3
2
  resolveMailboxRelayFanout,
4
3
  resolveMailboxWantFanout,
@@ -43,11 +42,3 @@ export function resolveMailboxRoutingForPeerCount(peerCount, raw = {}, batterySa
43
42
  batterySaver: true,
44
43
  }
45
44
  }
46
-
47
- /**
48
- * @returns {{ maxHop: number, relayFanoutTrusted: number, relayFanoutNormal: number, wantFanout: number, batterySaver: boolean }} mailbox 路由配置
49
- */
50
- export function getMailboxRoutingSettings() {
51
- const { batterySaver, mailbox } = getNodeTransportSettings()
52
- return resolveMailboxRoutingForPeerCount(0, mailbox, batterySaver)
53
- }
package/node/denylist.mjs CHANGED
@@ -56,13 +56,6 @@ function buildDenylistIndex(blocked) {
56
56
  return index
57
57
  }
58
58
 
59
- /**
60
- * @returns {void}
61
- */
62
- export function invalidateDenylistIndex() {
63
- cachedIndex = null
64
- }
65
-
66
59
  /**
67
60
  * @returns {DenylistIndex} 缓存索引
68
61
  */
@@ -244,23 +237,6 @@ export async function addDenylistFromBanContent(banContent, groupId) {
244
237
  await addDenylistEntry({ scope: 'subject', value: pk, ...sourceGroupId ? { groupId: sourceGroupId } : {} })
245
238
  }
246
239
 
247
- /**
248
- * @param {string} entityHash 128 位十六进制
249
- * @param {boolean} block true=拉黑
250
- * @returns {Promise<boolean>} 当前是否拉黑
251
- */
252
- export async function setEntityBlocked(entityHash, block) {
253
- const id = String(entityHash || '').trim().toLowerCase()
254
- if (!isEntityHash128(id)) throw new Error('invalid entityHash')
255
- await mutateDenylist(() => {
256
- const list = loadDenylist()
257
- const without = list.blocked.filter(e => !(e.scope === 'entity' && e.value === id))
258
- if (block) without.push({ scope: 'entity', value: id })
259
- saveDenylist({ blocked: without })
260
- })
261
- return block
262
- }
263
-
264
240
  /**
265
241
  * 追加群 scope 拉黑项。
266
242
  * @param {string} groupId 群 ID
package/node/network.mjs CHANGED
@@ -121,20 +121,6 @@ export function saveNetwork(data) {
121
121
  invalidateTrustGraphCache()
122
122
  }
123
123
 
124
- /**
125
- * @param {string} nodeHash 64 位十六进制
126
- * @param {'trusted' | 'explore'} tier 池档位
127
- * @returns {void}
128
- */
129
- export function addNetworkPeer(nodeHash, tier = 'explore') {
130
- const id = normalizeHex64(nodeHash)
131
- if (!isHex64(id)) return
132
- const net = loadNetwork()
133
- const list = tier === 'trusted' ? net.trustedPeers : net.explorePeers
134
- if (!list.includes(id)) list.push(id)
135
- saveNetwork(net)
136
- }
137
-
138
124
  /**
139
125
  * @param {{ nodeHash: string, source: string, kind: string, weight?: number, expiresAt?: number, ttlMs?: number, groupId?: string }} hint 扩边 hint
140
126
  * @returns {void}
@@ -165,35 +151,6 @@ export function applyNetworkHint(hint) {
165
151
  saveNetwork(net)
166
152
  }
167
153
 
168
- /**
169
- * @param {{ remoteNodeHash?: string }[]} roster Trystero roster
170
- * @param {string} [groupId] 来源群
171
- * @param {string} [source='roster'] hint 来源标签
172
- * @returns {void}
173
- */
174
- export function recordExplorePeersFromRoster(roster, groupId = '', source = 'roster') {
175
- if (!roster?.length) return
176
- const net = loadNetwork()
177
- const now = Date.now()
178
- for (const peer of roster) {
179
- const nodeHash = normalizeHex64(peer?.remoteNodeHash)
180
- if (!isHex64(nodeHash)) continue
181
- if (!net.explorePeers.includes(nodeHash))
182
- net.explorePeers.push(nodeHash)
183
- net.hints.push({
184
- nodeHash,
185
- source: String(source || 'roster'),
186
- kind: 'roster',
187
- weight: 0.1,
188
- expiresAt: now + 24 * 60 * 60 * 1000,
189
- ...groupId ? { groupId: String(groupId).trim() } : {},
190
- })
191
- }
192
- net.hints = capHintsBySource(net.hints).slice(-MAX_HINTS)
193
- net.lastRosterAt = now
194
- saveNetwork(net)
195
- }
196
-
197
154
  /**
198
155
  * 疑似分区/eclipse 后:用 trusted 锚点加宽 explore,便于恢复联邦可达。
199
156
  * @returns {void}
@@ -239,14 +196,6 @@ export function mergeNetworkPeerPools(patch = {}) {
239
196
  saveNetwork(net)
240
197
  }
241
198
 
242
- /**
243
- * @param {string[]} nodeHashes trusted 候选
244
- * @returns {void}
245
- */
246
- export function mergeTrustedPeers(nodeHashes) {
247
- mergeNetworkPeerPools({ trustedPeers: nodeHashes })
248
- }
249
-
250
199
  /**
251
200
  * @param {string} groupId 群 scope
252
201
  * @param {'node' | 'subject' | 'entity'} scope denylist 作用域