@steve02081504/fount-p2p 0.0.1 → 0.0.2
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 +2 -1
- package/core/entity_id.mjs +4 -4
- package/deno.json +4 -0
- package/discovery/bt.mjs +1 -1
- package/entity/profile.mjs +1 -1
- package/entity/replica.mjs +4 -4
- package/federation/{operator_key_chain.mjs → entity_key_chain.mjs} +32 -33
- package/files/chunk_fetch.mjs +1 -1
- package/files/transfer_key.mjs +2 -4
- package/governance/join_pow.mjs +1 -1
- package/link/channel_mux.mjs +1 -1
- package/link/link.mjs +1 -1
- package/link/sdp_fingerprint.mjs +1 -1
- package/mailbox/store.mjs +1 -1
- package/node/denylist.mjs +2 -2
- package/node/identity.mjs +5 -5
- package/node/personal_block.mjs +1 -1
- package/overlay/index.mjs +1 -1
- package/package.json +2 -1
- package/transport/stale_peer_log.mjs +0 -3
- package/wire/part_invoke.mjs +1 -1
- package/wire/rate_bucket.mjs +1 -1
- package/utils/debug_log.mjs +0 -16
package/README.md
CHANGED
|
@@ -44,7 +44,8 @@ Root contains only the facade and package metadata; all modules live in layered
|
|
|
44
44
|
## Tests
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
|
-
npm test # pure + integration (
|
|
47
|
+
npm test # package pure + integration (Node)
|
|
48
|
+
npm run test:fount # cross-repo bridge (Deno; requires fount on PATH)
|
|
48
49
|
npm run test:live # RTC / link smoke (requires node-datachannel)
|
|
49
50
|
npm run test:sim # tunables co-evolution sim (dev only, not published; --social-tunables to write back)
|
|
50
51
|
```
|
package/core/entity_id.mjs
CHANGED
|
@@ -34,18 +34,18 @@ export function hashFromPubKeyHex(pubKeyHex) {
|
|
|
34
34
|
/**
|
|
35
35
|
* @param {string} nodeHash 成员所属节点 hash
|
|
36
36
|
* @param {string} recoveryPubKeyHex 32 字节 recovery 公钥 hex(稳定身份锚)
|
|
37
|
-
* @returns {string}
|
|
37
|
+
* @returns {string} entityHash
|
|
38
38
|
*/
|
|
39
|
-
export function
|
|
39
|
+
export function entityHashFromRecoveryPubKeyHex(nodeHash, recoveryPubKeyHex) {
|
|
40
40
|
return encodeEntityHash(nodeHash, hashFromPubKeyHex(recoveryPubKeyHex))
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* @param {string} nodeHash 成员所属节点 hash
|
|
45
45
|
* @param {string} subjectHash 成员签名 pubKeyHash(DAG sender)
|
|
46
|
-
* @returns {string}
|
|
46
|
+
* @returns {string} entityHash
|
|
47
47
|
*/
|
|
48
|
-
export function
|
|
48
|
+
export function entityHashFromSubjectHash(nodeHash, subjectHash) {
|
|
49
49
|
const node = normalizeHex64(nodeHash)
|
|
50
50
|
const subject = normalizeHex64(subjectHash)
|
|
51
51
|
if (!isHex64(node) || !isHex64(subject)) throw new Error('invalid subject hash')
|
package/deno.json
ADDED
package/discovery/bt.mjs
CHANGED
|
@@ -53,7 +53,7 @@ export async function waitPoweredOn(runtime, timeout) {
|
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
55
|
* 探测本机 noble 运行时是否具备 BT 扫描所需 API。
|
|
56
|
-
* @returns {Promise<boolean>}
|
|
56
|
+
* @returns {Promise<boolean>} noble 可加载且具备 startScanningAsync 与 waitForPoweredOn(Async) 时为 true
|
|
57
57
|
*/
|
|
58
58
|
export async function canUseBluetoothDiscovery() {
|
|
59
59
|
try {
|
package/entity/profile.mjs
CHANGED
|
@@ -113,7 +113,7 @@ export async function getProfile(entityHash, replicaUsername = null, options = {
|
|
|
113
113
|
|
|
114
114
|
if (options.skipPresentation) return merged
|
|
115
115
|
|
|
116
|
-
let infoDefaults = options
|
|
116
|
+
let { infoDefaults } = options
|
|
117
117
|
if (!infoDefaults && replicaUsername)
|
|
118
118
|
infoDefaults = await resolveInfoDefaultsForEntity(replicaUsername, parsed.entityHash, locales)
|
|
119
119
|
if (!infoDefaults)
|
package/entity/replica.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { parseEntityHash } from '../core/entity_id.mjs'
|
|
2
|
-
import {
|
|
2
|
+
import { entityHashFromKeys, getNodeHash } from '../node/identity.mjs'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @param {string} recoveryPubKeyHex 64 位十六进制 recovery 公钥
|
|
6
|
-
* @returns {string | null}
|
|
6
|
+
* @returns {string | null} 本节点 entityHash
|
|
7
7
|
*/
|
|
8
|
-
export function
|
|
9
|
-
return
|
|
8
|
+
export function resolveLocalEntityHashFromRecoveryPubKeyHex(recoveryPubKeyHex) {
|
|
9
|
+
return entityHashFromKeys(getNodeHash(), recoveryPubKeyHex)
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Entity 密钥历史链:entityHash 锚定 recovery 公钥,活跃钥可轮换。
|
|
3
3
|
*/
|
|
4
4
|
import { Buffer } from 'node:buffer'
|
|
5
5
|
|
|
@@ -10,7 +10,7 @@ import { isHex64, normalizeHex64 } from '../core/hexIds.mjs'
|
|
|
10
10
|
/**
|
|
11
11
|
*
|
|
12
12
|
*/
|
|
13
|
-
export const
|
|
13
|
+
export const ENTITY_KEY_REVOKE_DOMAIN = 'fount-entity-key-revoke'
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* @typedef {{
|
|
@@ -19,7 +19,7 @@ export const OPERATOR_KEY_REVOKE_DOMAIN = 'fount-operator-key-revoke'
|
|
|
19
19
|
* attestedBy: 'recovery' | 'active' | 'revoked',
|
|
20
20
|
* validFrom?: number,
|
|
21
21
|
* revokedGenerations?: number[],
|
|
22
|
-
* }}
|
|
22
|
+
* }} EntityKeyHistoryEntry
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
/**
|
|
@@ -42,7 +42,7 @@ export function activeSenderHashFromPubKeyHex(activePubKeyHex) {
|
|
|
42
42
|
* @param {string} recoveryPubKeyHex recovery 公钥
|
|
43
43
|
* @param {string} activePubKeyHex 初始活跃公钥
|
|
44
44
|
* @param {number} [validFrom] 生效时间
|
|
45
|
-
* @returns {
|
|
45
|
+
* @returns {EntityKeyHistoryEntry[]} 创世链
|
|
46
46
|
*/
|
|
47
47
|
export function createGenesisKeyHistory(recoveryPubKeyHex, activePubKeyHex, validFrom = Date.now()) {
|
|
48
48
|
return [{
|
|
@@ -54,7 +54,7 @@ export function createGenesisKeyHistory(recoveryPubKeyHex, activePubKeyHex, vali
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
|
-
* @param {
|
|
57
|
+
* @param {EntityKeyHistoryEntry[]} keyHistory 密钥历史
|
|
58
58
|
* @param {number} generation 代际
|
|
59
59
|
* @returns {string | null} 活跃公钥 hex
|
|
60
60
|
*/
|
|
@@ -64,7 +64,7 @@ export function resolveActiveKeyAtGeneration(keyHistory, generation) {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
|
-
* @param {
|
|
67
|
+
* @param {EntityKeyHistoryEntry[]} keyHistory 密钥历史
|
|
68
68
|
* @returns {number} 最新代际,无则 -1
|
|
69
69
|
*/
|
|
70
70
|
export function resolveLatestActiveGeneration(keyHistory) {
|
|
@@ -73,7 +73,7 @@ export function resolveLatestActiveGeneration(keyHistory) {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
|
-
* @param {
|
|
76
|
+
* @param {EntityKeyHistoryEntry[]} keyHistory 密钥历史
|
|
77
77
|
* @param {number} generation 代际
|
|
78
78
|
* @returns {boolean} 是否已吊销
|
|
79
79
|
*/
|
|
@@ -84,7 +84,7 @@ export function isActiveGenerationRevoked(keyHistory, generation) {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
|
-
* @param {
|
|
87
|
+
* @param {EntityKeyHistoryEntry[]} keyHistory 密钥历史
|
|
88
88
|
* @param {string} recoveryPubKeyHex recovery 公钥
|
|
89
89
|
* @param {string} senderPubKeyHash 事件 sender(64 hex pubKeyHash)
|
|
90
90
|
* @returns {boolean} sender 是否为未吊销的活跃钥
|
|
@@ -112,18 +112,18 @@ export function isRecoverySender(recoveryPubKeyHex, senderPubKeyHash) {
|
|
|
112
112
|
|
|
113
113
|
/**
|
|
114
114
|
* @param {object} state 物化状态
|
|
115
|
-
* @param {object} event
|
|
115
|
+
* @param {object} event entity_key_rotate 事件
|
|
116
116
|
* @returns {object} 更新后状态
|
|
117
117
|
*/
|
|
118
|
-
export function
|
|
118
|
+
export function reduceEntityKeyRotate(state, event) {
|
|
119
119
|
const generation = Number(event.content?.generation)
|
|
120
120
|
const activePubKeyHex = normalizeHex64(event.content?.activePubKeyHex || '')
|
|
121
121
|
if (!Number.isFinite(generation) || generation < 0 || !isHex64(activePubKeyHex))
|
|
122
122
|
return state
|
|
123
|
-
state.
|
|
124
|
-
if (state.
|
|
123
|
+
state.entityKeyHistory = state.entityKeyHistory || []
|
|
124
|
+
if (state.entityKeyHistory.some(row => row.generation === generation))
|
|
125
125
|
return state
|
|
126
|
-
state.
|
|
126
|
+
state.entityKeyHistory.push({
|
|
127
127
|
generation,
|
|
128
128
|
activePubKeyHex,
|
|
129
129
|
attestedBy: generation === 0 ? 'recovery' : 'active',
|
|
@@ -134,10 +134,10 @@ export function reduceOperatorKeyRotate(state, event) {
|
|
|
134
134
|
|
|
135
135
|
/**
|
|
136
136
|
* @param {object} state 物化状态
|
|
137
|
-
* @param {object} event
|
|
137
|
+
* @param {object} event entity_key_revoke 事件
|
|
138
138
|
* @returns {object} 更新后状态
|
|
139
139
|
*/
|
|
140
|
-
export function
|
|
140
|
+
export function reduceEntityKeyRevoke(state, event) {
|
|
141
141
|
const newGeneration = Number(event.content?.newGeneration)
|
|
142
142
|
const activePubKeyHex = normalizeHex64(event.content?.activePubKeyHex || '')
|
|
143
143
|
const revokeGenerations = Array.isArray(event.content?.revokeGenerations)
|
|
@@ -145,17 +145,17 @@ export function reduceOperatorKeyRevoke(state, event) {
|
|
|
145
145
|
: []
|
|
146
146
|
if (!Number.isFinite(newGeneration) || newGeneration < 0 || !isHex64(activePubKeyHex))
|
|
147
147
|
return state
|
|
148
|
-
state.
|
|
148
|
+
state.entityKeyHistory = state.entityKeyHistory || []
|
|
149
149
|
for (const gen of revokeGenerations) {
|
|
150
|
-
const entry = state.
|
|
150
|
+
const entry = state.entityKeyHistory.find(row => row.generation === gen)
|
|
151
151
|
if (entry) {
|
|
152
152
|
entry.revokedGenerations = entry.revokedGenerations || []
|
|
153
153
|
if (!entry.revokedGenerations.includes(gen))
|
|
154
154
|
entry.revokedGenerations.push(gen)
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
|
-
if (!state.
|
|
158
|
-
state.
|
|
157
|
+
if (!state.entityKeyHistory.some(row => row.generation === newGeneration))
|
|
158
|
+
state.entityKeyHistory.push({
|
|
159
159
|
generation: newGeneration,
|
|
160
160
|
activePubKeyHex,
|
|
161
161
|
attestedBy: 'recovery',
|
|
@@ -166,35 +166,34 @@ export function reduceOperatorKeyRevoke(state, event) {
|
|
|
166
166
|
}
|
|
167
167
|
/**
|
|
168
168
|
* @param {object[]} events 时间线事件(拓扑序)
|
|
169
|
-
* @returns {{ recoveryPubKeyHex: string | null,
|
|
169
|
+
* @returns {{ recoveryPubKeyHex: string | null, entityKeyHistory: EntityKeyHistoryEntry[] }} 折叠密钥链
|
|
170
170
|
*/
|
|
171
|
-
export function
|
|
172
|
-
/** @type {
|
|
173
|
-
let
|
|
171
|
+
export function foldEntityKeyHistoryFromEvents(events) {
|
|
172
|
+
/** @type {EntityKeyHistoryEntry[]} */
|
|
173
|
+
let entityKeyHistory = []
|
|
174
174
|
for (const event of events || []) {
|
|
175
|
-
if (event.type === '
|
|
176
|
-
const state =
|
|
177
|
-
|
|
175
|
+
if (event.type === 'entity_key_rotate') {
|
|
176
|
+
const state = reduceEntityKeyRotate({ entityKeyHistory }, event)
|
|
177
|
+
entityKeyHistory = state.entityKeyHistory
|
|
178
178
|
}
|
|
179
|
-
if (event.type === '
|
|
180
|
-
const state =
|
|
181
|
-
|
|
179
|
+
if (event.type === 'entity_key_revoke') {
|
|
180
|
+
const state = reduceEntityKeyRevoke({ entityKeyHistory }, event)
|
|
181
|
+
entityKeyHistory = state.entityKeyHistory
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
|
-
return { recoveryPubKeyHex: null,
|
|
184
|
+
return { recoveryPubKeyHex: null, entityKeyHistory }
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
/**
|
|
188
188
|
* @param {object} revokeBody 吊销正文
|
|
189
189
|
* @returns {Buffer} 固定域签名消息
|
|
190
190
|
*/
|
|
191
|
-
export function
|
|
191
|
+
export function entityKeyRevokeSignBytes(revokeBody) {
|
|
192
192
|
const body = {
|
|
193
193
|
revokeGenerations: (revokeBody.revokeGenerations || []).map(g => Number(g)),
|
|
194
194
|
newGeneration: Number(revokeBody.newGeneration),
|
|
195
195
|
activePubKeyHex: normalizeHex64(revokeBody.activePubKeyHex || ''),
|
|
196
196
|
entityHash: String(revokeBody.entityHash || '').trim().toLowerCase(),
|
|
197
197
|
}
|
|
198
|
-
return Buffer.from(`${
|
|
198
|
+
return Buffer.from(`${ENTITY_KEY_REVOKE_DOMAIN}\0${canonicalStringify(body)}`, 'utf8')
|
|
199
199
|
}
|
|
200
|
-
|
package/files/chunk_fetch.mjs
CHANGED
|
@@ -46,7 +46,7 @@ function chunkFetchPeerTargets() {
|
|
|
46
46
|
*/
|
|
47
47
|
export async function fetchChunk(context) {
|
|
48
48
|
const hash = String(context.ciphertextHash || '').trim().toLowerCase()
|
|
49
|
-
const username = context
|
|
49
|
+
const { username } = context
|
|
50
50
|
if (!hash || !username) return null
|
|
51
51
|
|
|
52
52
|
if (await hasChunk(hash))
|
package/files/transfer_key.mjs
CHANGED
|
@@ -24,8 +24,7 @@ export async function resolveContentKey(descriptor, manifest, deps = {}) {
|
|
|
24
24
|
return null
|
|
25
25
|
|
|
26
26
|
if (type === 'file-master-key-wrap') {
|
|
27
|
-
const groupId = descriptor
|
|
28
|
-
const fileId = descriptor.fileId
|
|
27
|
+
const { groupId, fileId } = descriptor
|
|
29
28
|
if (!groupId || !fileId || !descriptor.wrappedKey || !deps.getGroupFileMasterKey) return null
|
|
30
29
|
const groupKey = await deps.getGroupFileMasterKey(String(groupId), descriptor.keyGeneration)
|
|
31
30
|
if (!groupKey) return null
|
|
@@ -33,8 +32,7 @@ export async function resolveContentKey(descriptor, manifest, deps = {}) {
|
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
if (type === 'vault-wrap') {
|
|
36
|
-
const entityHash = descriptor
|
|
37
|
-
const fileId = descriptor.fileId
|
|
35
|
+
const { entityHash, fileId } = descriptor
|
|
38
36
|
if (!entityHash || !fileId || !descriptor.wrappedKey || !deps.getVaultMasterKey) return null
|
|
39
37
|
const vaultKey = await deps.getVaultMasterKey(String(entityHash))
|
|
40
38
|
if (!vaultKey) return null
|
package/governance/join_pow.mjs
CHANGED
|
@@ -101,7 +101,7 @@ export function verifyJoinPow(powSolution, opts) {
|
|
|
101
101
|
if (!powSolution || typeof powSolution !== 'object') return { ok: false, achievedBits: 0 }
|
|
102
102
|
|
|
103
103
|
const anchorRef = String(powSolution.anchorRef ?? '').trim()
|
|
104
|
-
const nonce = powSolution
|
|
104
|
+
const { nonce } = powSolution
|
|
105
105
|
const epoch = Number(powSolution.epoch)
|
|
106
106
|
const joinerNodeHash = String(powSolution.joinerNodeHash ?? opts.senderNodeHash ?? '').trim().toLowerCase()
|
|
107
107
|
const senderNodeHash = String(opts.senderNodeHash ?? '').trim().toLowerCase()
|
package/link/channel_mux.mjs
CHANGED
|
@@ -131,7 +131,7 @@ export function onBufferedAmountLow(channel, cb) {
|
|
|
131
131
|
* @returns {{ enqueue: (action: string, bytes: Uint8Array, preferredChannel?: 'control' | 'bulk') => void, flush: (channelName?: 'control' | 'bulk') => void, pending: () => { control: number, bulk: number }, clear: () => void }} 发送队列 API
|
|
132
132
|
*/
|
|
133
133
|
export function createChannelSendQueues(opts) {
|
|
134
|
-
const getChannel = opts
|
|
134
|
+
const { getChannel } = opts
|
|
135
135
|
const highWatermarkBytes = Math.max(CHANNEL_LOW_THRESHOLD_BYTES, Number(opts.highWatermarkBytes) || CHANNEL_HIGH_WATERMARK_BYTES)
|
|
136
136
|
/** @type {{ control: Array<{ priority: number, seq: number, bytes: Uint8Array }>, bulk: Array<{ priority: number, seq: number, bytes: Uint8Array }> }} */
|
|
137
137
|
const queues = { control: [], bulk: [] }
|
package/link/link.mjs
CHANGED
|
@@ -326,7 +326,7 @@ export async function createLink(opts) {
|
|
|
326
326
|
* @returns {boolean} 应重新入队则 true
|
|
327
327
|
*/
|
|
328
328
|
function shouldRetryQueuedIce(error) {
|
|
329
|
-
return /without
|
|
329
|
+
return /without ice transport/i.test(String(error?.message ?? error ?? ''))
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
/**
|
package/link/sdp_fingerprint.mjs
CHANGED
|
@@ -20,6 +20,6 @@ export function normalizeDtlsFingerprint(value) {
|
|
|
20
20
|
* @returns {string | null} 规范化 fingerprint,未找到时返回 null
|
|
21
21
|
*/
|
|
22
22
|
export function extractDtlsFingerprint(sdp) {
|
|
23
|
-
const line = String(sdp || '').match(/^a=fingerprint:sha-256\s+([
|
|
23
|
+
const line = String(sdp || '').match(/^a=fingerprint:sha-256\s+([\d:A-Fa-f]+)$/mu)?.[1]
|
|
24
24
|
return normalizeDtlsFingerprint(line)
|
|
25
25
|
}
|
package/mailbox/store.mjs
CHANGED
|
@@ -162,7 +162,7 @@ export async function storeMailboxRecord(record) {
|
|
|
162
162
|
const toPubKeyHash = normalizeHex64(record.toPubKeyHash)
|
|
163
163
|
if (!isHex64(toPubKeyHash)) return false
|
|
164
164
|
const hop = normalizeMailboxHop(record.hop)
|
|
165
|
-
const tier = record
|
|
165
|
+
const { tier } = record
|
|
166
166
|
if (!['trusted', 'normal', 'quarantine'].includes(tier)) return false
|
|
167
167
|
const app = String(record.app || '').trim()
|
|
168
168
|
if (!app) return false
|
package/node/denylist.mjs
CHANGED
|
@@ -69,7 +69,7 @@ export function invalidateDenylistIndex() {
|
|
|
69
69
|
function getDenylistIndex() {
|
|
70
70
|
if (cachedIndex) return cachedIndex
|
|
71
71
|
const raw = readNodeJsonSync(DATA_NAME)
|
|
72
|
-
const blocked = normalizeDenylist(raw)
|
|
72
|
+
const { blocked } = normalizeDenylist(raw)
|
|
73
73
|
cachedIndex = buildDenylistIndex(blocked)
|
|
74
74
|
return cachedIndex
|
|
75
75
|
}
|
|
@@ -111,7 +111,7 @@ export function loadDenylist() {
|
|
|
111
111
|
* @returns {void}
|
|
112
112
|
*/
|
|
113
113
|
export function saveDenylist(list) {
|
|
114
|
-
const blocked = normalizeDenylist(list)
|
|
114
|
+
const { blocked } = normalizeDenylist(list)
|
|
115
115
|
writeNodeJsonSync(DATA_NAME, { blocked })
|
|
116
116
|
cachedIndex = buildDenylistIndex(blocked)
|
|
117
117
|
}
|
package/node/identity.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { entityHashFromRecoveryPubKeyHex } from '../core/entity_id.mjs'
|
|
4
4
|
import { isHex64 } from '../core/hexIds.mjs'
|
|
5
5
|
import { nodeHashFromSeed } from '../entity/node_hash.mjs'
|
|
6
6
|
import { normalizeMailboxSettings } from '../mailbox/settings.mjs'
|
|
@@ -8,7 +8,7 @@ import { normalizeMailboxSettings } from '../mailbox/settings.mjs'
|
|
|
8
8
|
import { emitNodeChange } from './instance.mjs'
|
|
9
9
|
import { readNodeJsonSync, writeNodeJsonSync } from './storage.mjs'
|
|
10
10
|
|
|
11
|
-
const NODE_SEED_HEX_RE = /^[
|
|
11
|
+
const NODE_SEED_HEX_RE = /^[\da-f]{64}$/iu
|
|
12
12
|
const NODE_JSON = 'node'
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -90,10 +90,10 @@ export function ensureNodeDefaults() {
|
|
|
90
90
|
/**
|
|
91
91
|
* @param {string} nodeHash 64 位十六进制
|
|
92
92
|
* @param {string} recoveryPubKeyHex 64 位十六进制 recovery 公钥(稳定身份锚)
|
|
93
|
-
* @returns {string | null}
|
|
93
|
+
* @returns {string | null} entityHash(非法 hex 时 null)
|
|
94
94
|
*/
|
|
95
|
-
export function
|
|
95
|
+
export function entityHashFromKeys(nodeHash, recoveryPubKeyHex) {
|
|
96
96
|
const pub = String(recoveryPubKeyHex || '').trim().toLowerCase().replace(/^0x/iu, '')
|
|
97
97
|
if (!isHex64(nodeHash) || !isHex64(pub)) return null
|
|
98
|
-
return
|
|
98
|
+
return entityHashFromRecoveryPubKeyHex(nodeHash, pub)
|
|
99
99
|
}
|
package/node/personal_block.mjs
CHANGED
package/overlay/index.mjs
CHANGED
|
@@ -25,7 +25,7 @@ function routeSignBytes(reqId, path) {
|
|
|
25
25
|
export function createOverlayRouter(registry, ttl = 3) {
|
|
26
26
|
const selfNodeHash = registry.localIdentity.nodeHash
|
|
27
27
|
const selfPubKey = registry.localIdentity.nodePubKey
|
|
28
|
-
const secretKey = registry.localIdentity
|
|
28
|
+
const { secretKey } = registry.localIdentity
|
|
29
29
|
const seenReqs = createLruMap(4096)
|
|
30
30
|
/** @type {Map<string, { resolve: (path: string[]) => void, reject: (err: Error) => void, timer: number }>} */
|
|
31
31
|
const pendingRoutes = new Map()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steve02081504/fount-p2p",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "fount federation P2P layer — link, trust graph, mailbox, DAG, EVFS.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"network",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
21
|
"test": "node --test --test-concurrency=1 --test-force-exit test/pure/*.test.mjs test/integration/*.test.mjs",
|
|
22
|
+
"test:fount": "deno test -A --config deno.json test/fount/",
|
|
22
23
|
"test:live": "node --test --test-concurrency=1 --test-force-exit test/live/*.test.mjs",
|
|
23
24
|
"test:sim": "node --test --test-concurrency=1 --test-force-exit sim/test/*.test.mjs"
|
|
24
25
|
},
|
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 无回退行为——仅在发生处记录异常「身份映射滞后于活跃连接」
|
|
5
5
|
* (群/房间 + peerId + nodeHash),以便追踪 onPeerLeave 遗漏。
|
|
6
|
-
* 计数通过 catchup stats 暴露给测试;磁盘记录可在 debug_logs/ 下 grep。
|
|
7
6
|
*/
|
|
8
|
-
import { debugLog } from '../utils/debug_log.mjs'
|
|
9
7
|
|
|
10
8
|
/** @type {Map<string, number>} scopeId → 累计修剪次数 */
|
|
11
9
|
const pruneCounts = new Map()
|
|
@@ -31,7 +29,6 @@ export function recordStalePeerPrune(scope, staleEntries, meta = {}) {
|
|
|
31
29
|
lines.push(JSON.stringify(record))
|
|
32
30
|
}
|
|
33
31
|
while (recent.length > RECENT_CAP) recent.shift()
|
|
34
|
-
void debugLog('federation_stale_peer', `${lines.join('\n')}\n`).catch(() => { /* best-effort observability */ })
|
|
35
32
|
}
|
|
36
33
|
|
|
37
34
|
/**
|
package/wire/part_invoke.mjs
CHANGED
package/wire/rate_bucket.mjs
CHANGED
|
@@ -18,7 +18,7 @@ const buckets = createLruMap(BUCKET_MAP_MAX)
|
|
|
18
18
|
export function consumeWireRateBucket(bucketKey, limits) {
|
|
19
19
|
const byteCount = Math.max(0, Number(limits.byteCount) || 0)
|
|
20
20
|
const maxBytes = Number(limits.maxBytesPerWindow) || 0
|
|
21
|
-
const maxCount = limits
|
|
21
|
+
const { maxCount } = limits
|
|
22
22
|
const now = Date.now()
|
|
23
23
|
let bucket = buckets.get(bucketKey)
|
|
24
24
|
if (!bucket) {
|
package/utils/debug_log.mjs
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
|
|
4
|
-
const dir = path.resolve(process.cwd(), 'debug_logs')
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* P2P 观测落盘:写入进程 cwd 下 `debug_logs/`(与 monorepo `debugLog` 行为对齐)。
|
|
8
|
-
* @param {string} name 日志文件名(不含扩展名)
|
|
9
|
-
* @param {unknown} data 调试数据
|
|
10
|
-
* @returns {Promise<void>}
|
|
11
|
-
*/
|
|
12
|
-
export async function debugLog(name, data) {
|
|
13
|
-
const text = Object(data) instanceof String ? data : JSON.stringify(data)
|
|
14
|
-
await fs.promises.mkdir(dir, { recursive: true })
|
|
15
|
-
await fs.promises.appendFile(path.join(dir, `${name}.log`), text)
|
|
16
|
-
}
|