@f2a/network 0.1.3 → 0.2.0
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 +278 -63
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +29 -2
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/config.d.ts +176 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +386 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/daemon.d.ts +54 -0
- package/dist/cli/daemon.d.ts.map +1 -0
- package/dist/cli/daemon.js +572 -0
- package/dist/cli/daemon.js.map +1 -0
- package/dist/cli/index.js +90 -16
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.d.ts +13 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +352 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/core/e2ee-crypto.d.ts +127 -1
- package/dist/core/e2ee-crypto.d.ts.map +1 -1
- package/dist/core/e2ee-crypto.js +446 -12
- package/dist/core/e2ee-crypto.js.map +1 -1
- package/dist/core/f2a.d.ts +2 -1
- package/dist/core/f2a.d.ts.map +1 -1
- package/dist/core/f2a.js +6 -2
- package/dist/core/f2a.js.map +1 -1
- package/dist/core/identity/encrypted-key-store.d.ts +19 -0
- package/dist/core/identity/encrypted-key-store.d.ts.map +1 -0
- package/dist/core/identity/encrypted-key-store.js +72 -0
- package/dist/core/identity/encrypted-key-store.js.map +1 -0
- package/dist/core/identity/identity-manager.d.ts +133 -0
- package/dist/core/identity/identity-manager.d.ts.map +1 -0
- package/dist/core/identity/identity-manager.js +454 -0
- package/dist/core/identity/identity-manager.js.map +1 -0
- package/dist/core/identity/index.d.ts +8 -0
- package/dist/core/identity/index.d.ts.map +1 -0
- package/dist/core/identity/index.js +7 -0
- package/dist/core/identity/index.js.map +1 -0
- package/dist/core/identity/types.d.ts +70 -0
- package/dist/core/identity/types.d.ts.map +1 -0
- package/dist/core/identity/types.js +17 -0
- package/dist/core/identity/types.js.map +1 -0
- package/dist/core/p2p-network.d.ts +26 -0
- package/dist/core/p2p-network.d.ts.map +1 -1
- package/dist/core/p2p-network.js +434 -105
- package/dist/core/p2p-network.js.map +1 -1
- package/dist/core/reputation-security.d.ts +15 -0
- package/dist/core/reputation-security.d.ts.map +1 -1
- package/dist/core/reputation-security.js +73 -3
- package/dist/core/reputation-security.js.map +1 -1
- package/dist/core/reputation.d.ts +129 -4
- package/dist/core/reputation.d.ts.map +1 -1
- package/dist/core/reputation.js +294 -1
- package/dist/core/reputation.js.map +1 -1
- package/dist/core/review-committee.d.ts +2 -2
- package/dist/core/review-committee.d.ts.map +1 -1
- package/dist/core/review-committee.js +17 -0
- package/dist/core/review-committee.js.map +1 -1
- package/dist/daemon/control-server.d.ts.map +1 -1
- package/dist/daemon/control-server.js +44 -1
- package/dist/daemon/control-server.js.map +1 -1
- package/dist/daemon/webhook.d.ts +3 -0
- package/dist/daemon/webhook.d.ts.map +1 -1
- package/dist/daemon/webhook.js +318 -6
- package/dist/daemon/webhook.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/result.d.ts +1 -1
- package/dist/types/result.d.ts.map +1 -1
- package/dist/types/result.js.map +1 -1
- package/dist/utils/crypto-utils.d.ts +17 -0
- package/dist/utils/crypto-utils.d.ts.map +1 -0
- package/dist/utils/crypto-utils.js +28 -0
- package/dist/utils/crypto-utils.js.map +1 -0
- package/dist/utils/logger.d.ts +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +9 -3
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/rate-limiter.d.ts.map +1 -1
- package/dist/utils/rate-limiter.js +3 -1
- package/dist/utils/rate-limiter.js.map +1 -1
- package/dist/utils/signature.d.ts +47 -1
- package/dist/utils/signature.d.ts.map +1 -1
- package/dist/utils/signature.js +166 -11
- package/dist/utils/signature.js.map +1 -1
- package/package.json +2 -1
package/dist/core/p2p-network.js
CHANGED
|
@@ -6,6 +6,7 @@ import { createLibp2p } from 'libp2p';
|
|
|
6
6
|
import { tcp } from '@libp2p/tcp';
|
|
7
7
|
import { noise } from '@chainsafe/libp2p-noise';
|
|
8
8
|
import { kadDHT } from '@libp2p/kad-dht';
|
|
9
|
+
import { mdns } from '@libp2p/mdns';
|
|
9
10
|
import { peerIdFromString } from '@libp2p/peer-id';
|
|
10
11
|
import { multiaddr } from '@multiformats/multiaddr';
|
|
11
12
|
import { EventEmitter } from 'eventemitter3';
|
|
@@ -15,6 +16,7 @@ import { E2EECrypto } from './e2ee-crypto.js';
|
|
|
15
16
|
import { Logger } from '../utils/logger.js';
|
|
16
17
|
import { validateF2AMessage, validateTaskResponsePayload } from '../utils/validation.js';
|
|
17
18
|
import { MiddlewareManager } from '../utils/middleware.js';
|
|
19
|
+
import { RateLimiter } from '../utils/rate-limiter.js';
|
|
18
20
|
// 类型守卫:检查是否为加密消息
|
|
19
21
|
function isEncryptedMessage(msg) {
|
|
20
22
|
return 'encrypted' in msg && msg.encrypted === true && 'payload' in msg;
|
|
@@ -29,41 +31,86 @@ const PEER_TABLE_HIGH_WATERMARK = 0.9; // 高水位线(90%触发主动清理
|
|
|
29
31
|
const PEER_TABLE_AGGRESSIVE_CLEANUP_THRESHOLD = 0.8; // 激进清理后保留的目标比例(80%)
|
|
30
32
|
/**
|
|
31
33
|
* 简单的异步锁实现,用于保护关键资源的并发访问
|
|
34
|
+
*
|
|
35
|
+
* P1 修复:添加超时机制,防止死锁
|
|
32
36
|
*/
|
|
33
37
|
class AsyncLock {
|
|
34
38
|
locked = false;
|
|
35
39
|
queue = [];
|
|
36
|
-
|
|
40
|
+
/** 默认锁超时时间(毫秒) - P2-1 修复:从 30000ms 改为 10000ms */
|
|
41
|
+
static DEFAULT_TIMEOUT_MS = 10000;
|
|
42
|
+
/**
|
|
43
|
+
* 获取锁
|
|
44
|
+
* @param timeoutMs 超时时间(毫秒),默认 10 秒
|
|
45
|
+
* @throws Error 如果超时未能获取锁
|
|
46
|
+
*/
|
|
47
|
+
async acquire(timeoutMs = AsyncLock.DEFAULT_TIMEOUT_MS) {
|
|
37
48
|
if (!this.locked) {
|
|
38
49
|
this.locked = true;
|
|
39
50
|
return;
|
|
40
51
|
}
|
|
41
|
-
return new Promise(resolve => {
|
|
42
|
-
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
const timeoutId = setTimeout(() => {
|
|
54
|
+
// 从队列中移除此等待者
|
|
55
|
+
const index = this.queue.indexOf(onAcquire);
|
|
56
|
+
if (index !== -1) {
|
|
57
|
+
this.queue.splice(index, 1);
|
|
58
|
+
}
|
|
59
|
+
reject(new Error(`AsyncLock acquire timeout after ${timeoutMs}ms`));
|
|
60
|
+
}, timeoutMs);
|
|
61
|
+
const onAcquire = () => {
|
|
62
|
+
clearTimeout(timeoutId);
|
|
63
|
+
resolve();
|
|
64
|
+
};
|
|
65
|
+
this.queue.push(onAcquire);
|
|
43
66
|
});
|
|
44
67
|
}
|
|
45
68
|
release() {
|
|
46
69
|
const next = this.queue.shift();
|
|
47
70
|
if (next) {
|
|
71
|
+
// 保持 locked = true,直接传递给下一个等待者
|
|
48
72
|
next();
|
|
49
73
|
}
|
|
50
74
|
else {
|
|
51
75
|
this.locked = false;
|
|
52
76
|
}
|
|
53
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* 检查锁是否被持有
|
|
80
|
+
*/
|
|
81
|
+
isLocked() {
|
|
82
|
+
return this.locked;
|
|
83
|
+
}
|
|
54
84
|
}
|
|
55
85
|
export class P2PNetwork extends EventEmitter {
|
|
56
86
|
node = null;
|
|
57
87
|
config;
|
|
58
88
|
peerTable = new Map();
|
|
89
|
+
/** P2.4 修复:已连接 Peer 索引,用于 O(1) 查询 */
|
|
90
|
+
connectedPeers = new Set();
|
|
91
|
+
/** P1 修复:信任的 Peer 白名单,不会被清理 */
|
|
92
|
+
trustedPeers = new Set();
|
|
59
93
|
/** 用于保护 peerTable 并发访问的锁 */
|
|
60
94
|
peerTableLock = new AsyncLock();
|
|
95
|
+
/** P2-4 修复:DISCOVER 消息速率限制器(每个 peer) */
|
|
96
|
+
discoverRateLimiter = new RateLimiter({
|
|
97
|
+
maxRequests: 10, // 每个 peer 每分钟最多 10 次 DISCOVER 消息
|
|
98
|
+
windowMs: 60 * 1000,
|
|
99
|
+
burstMultiplier: 1.2 // 允许轻微突发
|
|
100
|
+
});
|
|
101
|
+
/** P1 修复:保存事件监听器引用,用于 stop() 中移除 */
|
|
102
|
+
boundEventHandlers = {
|
|
103
|
+
peerDiscovery: undefined,
|
|
104
|
+
peerConnect: undefined,
|
|
105
|
+
peerDisconnect: undefined
|
|
106
|
+
};
|
|
61
107
|
agentInfo;
|
|
62
108
|
pendingTasks = new Map();
|
|
63
109
|
cleanupInterval;
|
|
64
110
|
discoveryInterval;
|
|
65
111
|
e2eeCrypto;
|
|
66
112
|
enableE2EE = true;
|
|
113
|
+
identityManager;
|
|
67
114
|
logger;
|
|
68
115
|
middlewareManager;
|
|
69
116
|
constructor(agentInfo, config = {}) {
|
|
@@ -78,6 +125,30 @@ export class P2PNetwork extends EventEmitter {
|
|
|
78
125
|
...config
|
|
79
126
|
};
|
|
80
127
|
this.logger = new Logger({ component: 'P2P' });
|
|
128
|
+
// 初始化信任的 Peer 白名单
|
|
129
|
+
if (this.config.trustedPeers) {
|
|
130
|
+
this.config.trustedPeers.forEach(peerId => this.trustedPeers.add(peerId));
|
|
131
|
+
}
|
|
132
|
+
// 引导节点自动加入白名单
|
|
133
|
+
if (this.config.bootstrapPeers) {
|
|
134
|
+
this.config.bootstrapPeers.forEach(addr => {
|
|
135
|
+
// 从 multiaddr 提取 peer ID
|
|
136
|
+
try {
|
|
137
|
+
const ma = multiaddr(addr);
|
|
138
|
+
const peerId = ma.getPeerId();
|
|
139
|
+
if (peerId)
|
|
140
|
+
this.trustedPeers.add(peerId);
|
|
141
|
+
}
|
|
142
|
+
catch { /* ignore invalid addresses */ }
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* 设置 IdentityManager(用于持久化身份)
|
|
148
|
+
* 必须在 start() 之前调用
|
|
149
|
+
*/
|
|
150
|
+
setIdentityManager(identityManager) {
|
|
151
|
+
this.identityManager = identityManager;
|
|
81
152
|
}
|
|
82
153
|
/**
|
|
83
154
|
* 启动 P2P 网络
|
|
@@ -96,15 +167,38 @@ export class P2PNetwork extends EventEmitter {
|
|
|
96
167
|
clientMode: !this.config.dhtServerMode, // 默认客户端模式
|
|
97
168
|
});
|
|
98
169
|
}
|
|
99
|
-
//
|
|
100
|
-
|
|
170
|
+
// Medium 修复:使用 libp2p 提供的类型定义
|
|
171
|
+
const libp2pOptions = {
|
|
101
172
|
addresses: {
|
|
102
173
|
listen: listenAddresses
|
|
103
174
|
},
|
|
104
175
|
transports: [tcp()],
|
|
105
176
|
connectionEncryption: [noise()],
|
|
106
177
|
services
|
|
107
|
-
}
|
|
178
|
+
};
|
|
179
|
+
// mDNS 本地发现(默认启用)
|
|
180
|
+
if (this.config.enableMDNS !== false) {
|
|
181
|
+
// P1 修复:@libp2p/mdns 的类型定义与 libp2p 的 PeerDiscovery 类型不完全兼容。
|
|
182
|
+
// mdns() 返回的是 @libp2p/mdns 的组件,其事件类型与 libp2p 内部类型定义存在差异。
|
|
183
|
+
// 这是 libp2p 生态系统中已知的问题,参考: https://github.com/libp2p/js-libp2p/issues/XXX
|
|
184
|
+
// 使用 `as any` 绕过 TypeScript 类型检查,运行时行为正确。
|
|
185
|
+
// 当类型定义修复后,应移除此类型断言。
|
|
186
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
187
|
+
libp2pOptions.peerDiscovery = [
|
|
188
|
+
mdns()
|
|
189
|
+
];
|
|
190
|
+
}
|
|
191
|
+
// 如果提供了 IdentityManager,使用持久化的私钥
|
|
192
|
+
if (this.identityManager?.isLoaded()) {
|
|
193
|
+
const privateKey = this.identityManager.getPrivateKey();
|
|
194
|
+
if (privateKey) {
|
|
195
|
+
libp2pOptions.privateKey = privateKey;
|
|
196
|
+
this.logger.info('Using persisted identity', {
|
|
197
|
+
peerId: this.identityManager.getPeerIdString()?.slice(0, 16)
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
this.node = await createLibp2p(libp2pOptions);
|
|
108
202
|
// 设置事件监听
|
|
109
203
|
this.setupEventHandlers();
|
|
110
204
|
// 启动节点
|
|
@@ -116,8 +210,17 @@ export class P2PNetwork extends EventEmitter {
|
|
|
116
210
|
// 更新 agentInfo
|
|
117
211
|
this.agentInfo.peerId = peerId.toString();
|
|
118
212
|
this.agentInfo.multiaddrs = addrs;
|
|
119
|
-
// 初始化 E2EE 加密
|
|
120
|
-
|
|
213
|
+
// 初始化 E2EE 加密 - 使用持久化的密钥对或生成新的
|
|
214
|
+
if (this.identityManager?.isLoaded()) {
|
|
215
|
+
const e2eeKeyPair = this.identityManager.getE2EEKeyPair();
|
|
216
|
+
if (e2eeKeyPair) {
|
|
217
|
+
this.e2eeCrypto.initializeWithKeyPair(e2eeKeyPair.privateKey, e2eeKeyPair.publicKey);
|
|
218
|
+
this.logger.info('Using persisted E2EE key pair');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
await this.e2eeCrypto.initialize();
|
|
223
|
+
}
|
|
121
224
|
this.agentInfo.encryptionPublicKey = this.e2eeCrypto.getPublicKey() || undefined;
|
|
122
225
|
this.logger.info('E2EE encryption enabled', {
|
|
123
226
|
publicKey: this.agentInfo.encryptionPublicKey?.slice(0, 16)
|
|
@@ -161,7 +264,26 @@ export class P2PNetwork extends EventEmitter {
|
|
|
161
264
|
clearInterval(this.discoveryInterval);
|
|
162
265
|
this.discoveryInterval = undefined;
|
|
163
266
|
}
|
|
267
|
+
// P2-4 修复:停止 DISCOVER 消息速率限制器
|
|
268
|
+
this.discoverRateLimiter.stop();
|
|
269
|
+
// P1-1 修复:停止 E2EE 加密模块,清理定时器资源
|
|
270
|
+
if (this.e2eeCrypto && typeof this.e2eeCrypto.stop === 'function') {
|
|
271
|
+
this.e2eeCrypto.stop();
|
|
272
|
+
}
|
|
164
273
|
if (this.node) {
|
|
274
|
+
// P1 修复:移除事件监听器,防止内存泄漏
|
|
275
|
+
// 检查 removeEventListener 是否存在(兼容测试 mock)
|
|
276
|
+
if (typeof this.node.removeEventListener === 'function') {
|
|
277
|
+
if (this.boundEventHandlers.peerDiscovery) {
|
|
278
|
+
this.node.removeEventListener('peer:discovery', this.boundEventHandlers.peerDiscovery);
|
|
279
|
+
}
|
|
280
|
+
if (this.boundEventHandlers.peerConnect) {
|
|
281
|
+
this.node.removeEventListener('peer:connect', this.boundEventHandlers.peerConnect);
|
|
282
|
+
}
|
|
283
|
+
if (this.boundEventHandlers.peerDisconnect) {
|
|
284
|
+
this.node.removeEventListener('peer:disconnect', this.boundEventHandlers.peerDisconnect);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
165
287
|
// 清理待处理任务
|
|
166
288
|
for (const [taskId, { timeout, resolve }] of this.pendingTasks) {
|
|
167
289
|
clearTimeout(timeout);
|
|
@@ -425,61 +547,139 @@ export class P2PNetwork extends EventEmitter {
|
|
|
425
547
|
setupEventHandlers() {
|
|
426
548
|
if (!this.node)
|
|
427
549
|
return;
|
|
550
|
+
// P1 修复:创建绑定的监听器并保存引用,用于 stop() 中移除
|
|
551
|
+
this.boundEventHandlers.peerDiscovery = async (evt) => {
|
|
552
|
+
// P1 修复:async 处理器包裹在 try-catch 中,记录错误日志
|
|
553
|
+
try {
|
|
554
|
+
const peerId = evt.detail.id.toString();
|
|
555
|
+
const multiaddrs = evt.detail.multiaddrs.map(ma => ma.toString());
|
|
556
|
+
this.logger.info('mDNS peer discovered', {
|
|
557
|
+
peerId: peerId.slice(0, 16),
|
|
558
|
+
multiaddrs: multiaddrs.length
|
|
559
|
+
});
|
|
560
|
+
// 更新路由表
|
|
561
|
+
const now = Date.now();
|
|
562
|
+
await this.upsertPeer(peerId, () => ({
|
|
563
|
+
peerId,
|
|
564
|
+
multiaddrs: evt.detail.multiaddrs,
|
|
565
|
+
connected: false,
|
|
566
|
+
// P2 修复:mDNS 发现的节点信誉初始化为 25,表示"未验证"状态
|
|
567
|
+
reputation: 25,
|
|
568
|
+
lastSeen: now
|
|
569
|
+
}), (peer) => ({
|
|
570
|
+
...peer,
|
|
571
|
+
multiaddrs: evt.detail.multiaddrs,
|
|
572
|
+
lastSeen: now
|
|
573
|
+
}));
|
|
574
|
+
// 触发发现事件
|
|
575
|
+
// P2 修复:mDNS 发现的 AgentInfo 使用占位符标记为"待验证"
|
|
576
|
+
const pendingAgentInfo = {
|
|
577
|
+
peerId,
|
|
578
|
+
multiaddrs,
|
|
579
|
+
capabilities: [],
|
|
580
|
+
// P2 修复:使用占位符标记为待验证
|
|
581
|
+
displayName: `[Pending] ${peerId.slice(0, 8)}`,
|
|
582
|
+
agentType: 'custom',
|
|
583
|
+
version: '0.0.0-pending',
|
|
584
|
+
protocolVersion: '1.0.0',
|
|
585
|
+
lastSeen: now
|
|
586
|
+
};
|
|
587
|
+
this.emit('peer:discovered', {
|
|
588
|
+
peerId,
|
|
589
|
+
agentInfo: pendingAgentInfo,
|
|
590
|
+
multiaddrs: evt.detail.multiaddrs
|
|
591
|
+
});
|
|
592
|
+
// P1 修复:mDNS 发现后尝试连接并发送 DISCOVER 消息获取真实 AgentInfo
|
|
593
|
+
// 建议-2 修复:提取为独立方法,减少嵌套深度
|
|
594
|
+
await this.initiateDiscovery(peerId, evt.detail.multiaddrs);
|
|
595
|
+
}
|
|
596
|
+
catch (error) {
|
|
597
|
+
this.logger.error('Error in peer:discovery handler', {
|
|
598
|
+
error: error instanceof Error ? error.message : String(error)
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
};
|
|
428
602
|
// 新连接
|
|
429
|
-
this.
|
|
430
|
-
|
|
431
|
-
this.logger.info('Peer connected', { peerId: peerId.slice(0, 16) });
|
|
432
|
-
this.emit('peer:connected', {
|
|
433
|
-
peerId,
|
|
434
|
-
direction: 'inbound'
|
|
435
|
-
});
|
|
436
|
-
// 从连接获取远程 multiaddr
|
|
437
|
-
let multiaddrs = [];
|
|
603
|
+
this.boundEventHandlers.peerConnect = async (evt) => {
|
|
604
|
+
// P1 修复:async 处理器包裹在 try-catch 中
|
|
438
605
|
try {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
606
|
+
const peerId = evt.detail.toString();
|
|
607
|
+
this.logger.info('Peer connected', { peerId: peerId.slice(0, 16) });
|
|
608
|
+
this.emit('peer:connected', {
|
|
609
|
+
peerId,
|
|
610
|
+
direction: 'inbound'
|
|
611
|
+
});
|
|
612
|
+
// 从连接获取远程 multiaddr
|
|
613
|
+
let multiaddrs = [];
|
|
614
|
+
try {
|
|
615
|
+
if (this.node) {
|
|
616
|
+
const connections = this.node.getConnections();
|
|
617
|
+
const conn = connections.find(c => c.remotePeer.toString() === peerId);
|
|
618
|
+
if (conn && conn.remoteAddr) {
|
|
619
|
+
multiaddrs = [conn.remoteAddr];
|
|
620
|
+
}
|
|
444
621
|
}
|
|
445
622
|
}
|
|
623
|
+
catch {
|
|
624
|
+
// 无法获取 multiaddrs,使用空数组
|
|
625
|
+
}
|
|
626
|
+
// P2.4 修复:使用原子操作更新路由表和连接索引
|
|
627
|
+
const now = Date.now();
|
|
628
|
+
await this.upsertPeer(peerId, () => ({
|
|
629
|
+
peerId,
|
|
630
|
+
multiaddrs,
|
|
631
|
+
connected: true,
|
|
632
|
+
reputation: 50,
|
|
633
|
+
connectedAt: now,
|
|
634
|
+
lastSeen: now
|
|
635
|
+
}), (peer) => ({
|
|
636
|
+
...peer,
|
|
637
|
+
connected: true,
|
|
638
|
+
connectedAt: now,
|
|
639
|
+
lastSeen: now,
|
|
640
|
+
...(multiaddrs.length > 0 ? { multiaddrs } : {})
|
|
641
|
+
}));
|
|
642
|
+
// P2.4 修复:维护连接索引
|
|
643
|
+
this.connectedPeers.add(peerId);
|
|
446
644
|
}
|
|
447
|
-
catch {
|
|
448
|
-
|
|
645
|
+
catch (error) {
|
|
646
|
+
this.logger.error('Error in peer:connect handler', {
|
|
647
|
+
error: error instanceof Error ? error.message : String(error)
|
|
648
|
+
});
|
|
449
649
|
}
|
|
450
|
-
|
|
451
|
-
const now = Date.now();
|
|
452
|
-
await this.upsertPeer(peerId, () => ({
|
|
453
|
-
peerId,
|
|
454
|
-
multiaddrs,
|
|
455
|
-
connected: true,
|
|
456
|
-
reputation: 50,
|
|
457
|
-
connectedAt: now,
|
|
458
|
-
lastSeen: now
|
|
459
|
-
}), (peer) => ({
|
|
460
|
-
...peer,
|
|
461
|
-
connected: true,
|
|
462
|
-
connectedAt: now,
|
|
463
|
-
lastSeen: now,
|
|
464
|
-
...(multiaddrs.length > 0 ? { multiaddrs } : {})
|
|
465
|
-
}));
|
|
466
|
-
});
|
|
650
|
+
};
|
|
467
651
|
// 断开连接
|
|
468
|
-
this.
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
652
|
+
this.boundEventHandlers.peerDisconnect = async (evt) => {
|
|
653
|
+
// P1 修复:async 处理器包裹在 try-catch 中
|
|
654
|
+
try {
|
|
655
|
+
const peerId = evt.detail.toString();
|
|
656
|
+
this.logger.info('Peer disconnected', { peerId: peerId.slice(0, 16) });
|
|
657
|
+
this.emit('peer:disconnected', { peerId });
|
|
658
|
+
// P2.4 修复:从连接索引中移除
|
|
659
|
+
this.connectedPeers.delete(peerId);
|
|
660
|
+
// P1-2 修复:清理对等方的加密资源
|
|
661
|
+
this.e2eeCrypto.unregisterPeer(peerId);
|
|
662
|
+
// 使用原子操作更新路由表
|
|
663
|
+
const updated = await this.updatePeer(peerId, (peer) => ({
|
|
664
|
+
...peer,
|
|
665
|
+
connected: false,
|
|
666
|
+
lastSeen: Date.now()
|
|
667
|
+
}));
|
|
668
|
+
if (!updated) {
|
|
669
|
+
// Peer 不在路由表中,记录警告但不创建条目(已断开)
|
|
670
|
+
this.logger.warn('Peer disconnected but not in routing table', { peerId: peerId.slice(0, 16) });
|
|
671
|
+
}
|
|
481
672
|
}
|
|
482
|
-
|
|
673
|
+
catch (error) {
|
|
674
|
+
this.logger.error('Error in peer:disconnect handler', {
|
|
675
|
+
error: error instanceof Error ? error.message : String(error)
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
// 注册事件监听器
|
|
680
|
+
this.node.addEventListener('peer:discovery', this.boundEventHandlers.peerDiscovery);
|
|
681
|
+
this.node.addEventListener('peer:connect', this.boundEventHandlers.peerConnect);
|
|
682
|
+
this.node.addEventListener('peer:disconnect', this.boundEventHandlers.peerDisconnect);
|
|
483
683
|
// 处理传入的协议流
|
|
484
684
|
this.node.handle(F2A_PROTOCOL, async ({ stream, connection }) => {
|
|
485
685
|
try {
|
|
@@ -516,6 +716,45 @@ export class P2PNetwork extends EventEmitter {
|
|
|
516
716
|
}
|
|
517
717
|
});
|
|
518
718
|
}
|
|
719
|
+
/**
|
|
720
|
+
* 建议-2 修复:提取 mDNS 发现后的连接和 DISCOVER 发送逻辑
|
|
721
|
+
* 减少嵌套深度,提高可读性
|
|
722
|
+
* @param peerId 发现的 Peer ID
|
|
723
|
+
* @param multiaddrs 发现的 multiaddr 列表
|
|
724
|
+
*/
|
|
725
|
+
async initiateDiscovery(peerId, multiaddrs) {
|
|
726
|
+
try {
|
|
727
|
+
if (!this.node || multiaddrs.length === 0) {
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
// 尝试连接到发现的节点
|
|
731
|
+
await this.node.dial(multiaddrs[0]);
|
|
732
|
+
this.logger.info('Initiating connection to mDNS peer for discovery', {
|
|
733
|
+
peerId: peerId.slice(0, 16)
|
|
734
|
+
});
|
|
735
|
+
// 发送 DISCOVER 消息获取真实 AgentInfo
|
|
736
|
+
// 低-1 修复:有意不检查返回值 - 发现消息发送失败不影响主流程,
|
|
737
|
+
// 后续的定期发现广播会重试,不会造成功能缺失
|
|
738
|
+
const discoverMessage = {
|
|
739
|
+
id: randomUUID(),
|
|
740
|
+
type: 'DISCOVER',
|
|
741
|
+
from: this.agentInfo.peerId,
|
|
742
|
+
timestamp: Date.now(),
|
|
743
|
+
payload: { agentInfo: this.agentInfo }
|
|
744
|
+
};
|
|
745
|
+
await this.sendMessage(peerId, discoverMessage, false);
|
|
746
|
+
this.logger.info('Sent DISCOVER to mDNS peer', {
|
|
747
|
+
peerId: peerId.slice(0, 16)
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
catch (connectError) {
|
|
751
|
+
// 连接失败不应阻止发现流程,记录警告即可
|
|
752
|
+
this.logger.warn('Failed to connect/send DISCOVER to mDNS peer', {
|
|
753
|
+
peerId: peerId.slice(0, 16),
|
|
754
|
+
error: connectError instanceof Error ? connectError.message : String(connectError)
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
}
|
|
519
758
|
/**
|
|
520
759
|
* 处理收到的消息
|
|
521
760
|
*/
|
|
@@ -671,8 +910,16 @@ export class P2PNetwork extends EventEmitter {
|
|
|
671
910
|
}
|
|
672
911
|
/**
|
|
673
912
|
* 处理发现消息
|
|
913
|
+
* P2-4 修复:添加速率限制,防止恶意节点大量发送 DISCOVER 消息
|
|
674
914
|
*/
|
|
675
915
|
async handleDiscoverMessage(message, peerId, shouldRespond) {
|
|
916
|
+
// P2-4 修复:检查 DISCOVER 消息速率限制
|
|
917
|
+
if (!this.discoverRateLimiter.allowRequest(peerId)) {
|
|
918
|
+
this.logger.warn('DISCOVER message rate limit exceeded, ignoring', {
|
|
919
|
+
peerId: peerId.slice(0, 16)
|
|
920
|
+
});
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
676
923
|
const payload = message.payload;
|
|
677
924
|
await this.handleDiscover(payload.agentInfo, peerId, shouldRespond);
|
|
678
925
|
}
|
|
@@ -681,7 +928,8 @@ export class P2PNetwork extends EventEmitter {
|
|
|
681
928
|
*/
|
|
682
929
|
async handleCapabilityResponseMessage(message, peerId) {
|
|
683
930
|
const payload = message.payload;
|
|
684
|
-
|
|
931
|
+
// P2-5 修复:upsertPeerFromAgentInfo 现在是 async,需要 await
|
|
932
|
+
await this.upsertPeerFromAgentInfo(payload.agentInfo, peerId);
|
|
685
933
|
}
|
|
686
934
|
/**
|
|
687
935
|
* 处理能力查询消息
|
|
@@ -741,6 +989,8 @@ export class P2PNetwork extends EventEmitter {
|
|
|
741
989
|
});
|
|
742
990
|
return;
|
|
743
991
|
}
|
|
992
|
+
// P1 修复:记录是否需要清理,在锁外执行
|
|
993
|
+
let needsAggressiveCleanup = false;
|
|
744
994
|
// 使用锁保护容量检查和创建操作的原子性
|
|
745
995
|
await this.peerTableLock.acquire();
|
|
746
996
|
try {
|
|
@@ -749,8 +999,8 @@ export class P2PNetwork extends EventEmitter {
|
|
|
749
999
|
// 新 peer,需要检查容量
|
|
750
1000
|
const highWatermark = Math.floor(PEER_TABLE_MAX_SIZE * PEER_TABLE_HIGH_WATERMARK);
|
|
751
1001
|
if (this.peerTable.size >= highWatermark) {
|
|
752
|
-
//
|
|
753
|
-
|
|
1002
|
+
// P1 修复:不在锁内执行耗时清理,仅标记需要清理
|
|
1003
|
+
needsAggressiveCleanup = true;
|
|
754
1004
|
}
|
|
755
1005
|
if (this.peerTable.size >= PEER_TABLE_MAX_SIZE) {
|
|
756
1006
|
// 清理后仍无空间,拒绝新 peer
|
|
@@ -787,6 +1037,15 @@ export class P2PNetwork extends EventEmitter {
|
|
|
787
1037
|
finally {
|
|
788
1038
|
this.peerTableLock.release();
|
|
789
1039
|
}
|
|
1040
|
+
// P1 修复:在锁外异步执行清理,避免阻塞并发操作
|
|
1041
|
+
if (needsAggressiveCleanup) {
|
|
1042
|
+
// 使用 setImmediate 异步执行,不阻塞当前操作
|
|
1043
|
+
setImmediate(() => {
|
|
1044
|
+
this.cleanupStalePeers(true).catch(err => {
|
|
1045
|
+
this.logger.error('Background cleanup failed', { error: err });
|
|
1046
|
+
});
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
790
1049
|
// 注册对等方的加密公钥
|
|
791
1050
|
if (agentInfo.encryptionPublicKey) {
|
|
792
1051
|
this.e2eeCrypto.registerPeerPublicKey(peerId, agentInfo.encryptionPublicKey);
|
|
@@ -817,36 +1076,36 @@ export class P2PNetwork extends EventEmitter {
|
|
|
817
1076
|
}
|
|
818
1077
|
/**
|
|
819
1078
|
* 将发现到的 Agent 信息更新到 Peer 表
|
|
1079
|
+
* P2-5 修复:改为 async/await 模式,确保锁正确等待
|
|
820
1080
|
*/
|
|
821
|
-
upsertPeerFromAgentInfo(agentInfo, peerId) {
|
|
822
|
-
//
|
|
823
|
-
this.peerTableLock.acquire()
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
}
|
|
829
|
-
const existing = this.peerTable.get(peerId);
|
|
830
|
-
if (existing) {
|
|
831
|
-
existing.agentInfo = agentInfo;
|
|
832
|
-
existing.lastSeen = Date.now();
|
|
833
|
-
existing.multiaddrs = agentInfo.multiaddrs.map(ma => multiaddr(ma));
|
|
834
|
-
}
|
|
835
|
-
else {
|
|
836
|
-
this.peerTable.set(peerId, {
|
|
837
|
-
peerId,
|
|
838
|
-
agentInfo,
|
|
839
|
-
multiaddrs: agentInfo.multiaddrs.map(ma => multiaddr(ma)),
|
|
840
|
-
connected: false,
|
|
841
|
-
reputation: 50,
|
|
842
|
-
lastSeen: Date.now()
|
|
843
|
-
});
|
|
844
|
-
}
|
|
1081
|
+
async upsertPeerFromAgentInfo(agentInfo, peerId) {
|
|
1082
|
+
// P2-5 修复:使用 async/await 确保锁正确等待
|
|
1083
|
+
await this.peerTableLock.acquire();
|
|
1084
|
+
try {
|
|
1085
|
+
// 检查是否需要清理以腾出空间
|
|
1086
|
+
if (this.peerTable.size >= PEER_TABLE_MAX_SIZE && !this.peerTable.has(peerId)) {
|
|
1087
|
+
this.cleanupStalePeersLocked(true);
|
|
845
1088
|
}
|
|
846
|
-
|
|
847
|
-
|
|
1089
|
+
const existing = this.peerTable.get(peerId);
|
|
1090
|
+
if (existing) {
|
|
1091
|
+
existing.agentInfo = agentInfo;
|
|
1092
|
+
existing.lastSeen = Date.now();
|
|
1093
|
+
existing.multiaddrs = agentInfo.multiaddrs.map(ma => multiaddr(ma));
|
|
848
1094
|
}
|
|
849
|
-
|
|
1095
|
+
else {
|
|
1096
|
+
this.peerTable.set(peerId, {
|
|
1097
|
+
peerId,
|
|
1098
|
+
agentInfo,
|
|
1099
|
+
multiaddrs: agentInfo.multiaddrs.map(ma => multiaddr(ma)),
|
|
1100
|
+
connected: false,
|
|
1101
|
+
reputation: 50,
|
|
1102
|
+
lastSeen: Date.now()
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
finally {
|
|
1107
|
+
this.peerTableLock.release();
|
|
1108
|
+
}
|
|
850
1109
|
// 注册对等方的加密公钥
|
|
851
1110
|
if (agentInfo.encryptionPublicKey) {
|
|
852
1111
|
this.e2eeCrypto.registerPeerPublicKey(peerId, agentInfo.encryptionPublicKey);
|
|
@@ -881,14 +1140,17 @@ export class P2PNetwork extends EventEmitter {
|
|
|
881
1140
|
this.logger.warn('Received response for unknown task', { taskId: payload.taskId });
|
|
882
1141
|
return;
|
|
883
1142
|
}
|
|
884
|
-
//
|
|
885
|
-
//
|
|
886
|
-
|
|
1143
|
+
// P1 修复:使用原子操作避免竞态条件
|
|
1144
|
+
// 先检查 resolved 标志,再决定是否处理
|
|
1145
|
+
// 这确保即使多个响应并发到达,也只有一个能成功处理
|
|
887
1146
|
if (pending.resolved) {
|
|
888
1147
|
this.logger.warn('Task already resolved, ignoring duplicate response', { taskId: payload.taskId });
|
|
889
1148
|
return;
|
|
890
1149
|
}
|
|
1150
|
+
// 标记为已处理,防止并发响应重复处理
|
|
891
1151
|
pending.resolved = true;
|
|
1152
|
+
// 从 Map 中移除
|
|
1153
|
+
this.pendingTasks.delete(payload.taskId);
|
|
892
1154
|
// 清理资源
|
|
893
1155
|
clearTimeout(pending.timeout);
|
|
894
1156
|
// 处理结果
|
|
@@ -901,6 +1163,7 @@ export class P2PNetwork extends EventEmitter {
|
|
|
901
1163
|
}
|
|
902
1164
|
/**
|
|
903
1165
|
* 连接引导节点
|
|
1166
|
+
* 支持指纹验证,防止中间人攻击
|
|
904
1167
|
*/
|
|
905
1168
|
async connectToBootstrapPeers(peers) {
|
|
906
1169
|
if (!this.node)
|
|
@@ -908,11 +1171,51 @@ export class P2PNetwork extends EventEmitter {
|
|
|
908
1171
|
for (const addr of peers) {
|
|
909
1172
|
try {
|
|
910
1173
|
const ma = multiaddr(addr);
|
|
911
|
-
|
|
912
|
-
this.
|
|
1174
|
+
// 连接引导节点
|
|
1175
|
+
const conn = await this.node.dial(ma);
|
|
1176
|
+
const remotePeerId = conn.remotePeer.toString();
|
|
1177
|
+
// 指纹验证
|
|
1178
|
+
const expectedFingerprint = this.config.bootstrapPeerFingerprints?.[addr]
|
|
1179
|
+
|| this.config.bootstrapPeerFingerprints?.[remotePeerId];
|
|
1180
|
+
if (expectedFingerprint && remotePeerId !== expectedFingerprint) {
|
|
1181
|
+
// 指纹不匹配,记录错误并断开连接
|
|
1182
|
+
this.logger.error('Bootstrap peer fingerprint mismatch', {
|
|
1183
|
+
addr,
|
|
1184
|
+
expected: expectedFingerprint,
|
|
1185
|
+
actual: remotePeerId
|
|
1186
|
+
});
|
|
1187
|
+
// 断开连接
|
|
1188
|
+
try {
|
|
1189
|
+
await this.node.hangUp(ma);
|
|
1190
|
+
}
|
|
1191
|
+
catch (hangUpError) {
|
|
1192
|
+
this.logger.warn('Failed to hang up after fingerprint mismatch', {
|
|
1193
|
+
addr,
|
|
1194
|
+
error: hangUpError instanceof Error ? hangUpError.message : String(hangUpError)
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
1199
|
+
if (!expectedFingerprint) {
|
|
1200
|
+
// 未配置指纹,记录警告但不阻止连接
|
|
1201
|
+
this.logger.warn('Bootstrap peer connected without fingerprint verification', {
|
|
1202
|
+
addr,
|
|
1203
|
+
peerId: remotePeerId
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
else {
|
|
1207
|
+
// 指纹验证成功
|
|
1208
|
+
this.logger.info('Bootstrap peer verified', {
|
|
1209
|
+
addr,
|
|
1210
|
+
peerId: remotePeerId
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
913
1213
|
}
|
|
914
1214
|
catch (error) {
|
|
915
|
-
this.logger.warn('Failed to connect to bootstrap', {
|
|
1215
|
+
this.logger.warn('Failed to connect to bootstrap', {
|
|
1216
|
+
addr,
|
|
1217
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1218
|
+
});
|
|
916
1219
|
}
|
|
917
1220
|
}
|
|
918
1221
|
}
|
|
@@ -959,24 +1262,31 @@ export class P2PNetwork extends EventEmitter {
|
|
|
959
1262
|
const now = Date.now();
|
|
960
1263
|
const threshold = aggressive ? 0 : PEER_STALE_THRESHOLD;
|
|
961
1264
|
let cleaned = 0;
|
|
1265
|
+
let skippedTrusted = 0;
|
|
1266
|
+
// 辅助函数:检查 peer 是否在白名单中
|
|
1267
|
+
const isTrusted = (peerId) => this.trustedPeers.has(peerId);
|
|
962
1268
|
// 激进清理:清理更多类型的条目
|
|
963
1269
|
if (aggressive) {
|
|
964
|
-
// 1. 清理所有未连接且超过 1 小时的 peer
|
|
1270
|
+
// 1. 清理所有未连接且超过 1 小时的 peer(跳过白名单)
|
|
965
1271
|
for (const [peerId, peer] of this.peerTable) {
|
|
1272
|
+
if (isTrusted(peerId)) {
|
|
1273
|
+
skippedTrusted++;
|
|
1274
|
+
continue;
|
|
1275
|
+
}
|
|
966
1276
|
if (!peer.connected && now - peer.lastSeen > 60 * 60 * 1000) {
|
|
967
1277
|
this.peerTable.delete(peerId);
|
|
968
1278
|
cleaned++;
|
|
969
1279
|
}
|
|
970
1280
|
}
|
|
971
|
-
// 2.
|
|
1281
|
+
// 2. 如果仍然超过高水位线,按最后活跃时间排序后删除最旧的(跳过白名单)
|
|
972
1282
|
const highWatermark = Math.floor(PEER_TABLE_MAX_SIZE * PEER_TABLE_HIGH_WATERMARK);
|
|
973
1283
|
if (this.peerTable.size > highWatermark) {
|
|
974
1284
|
const targetSize = Math.floor(PEER_TABLE_MAX_SIZE * PEER_TABLE_AGGRESSIVE_CLEANUP_THRESHOLD);
|
|
975
1285
|
const sorted = Array.from(this.peerTable.entries())
|
|
976
1286
|
.sort((a, b) => a[1].lastSeen - b[1].lastSeen);
|
|
977
|
-
// 优先删除未连接的 peer
|
|
1287
|
+
// 优先删除未连接的 peer(跳过白名单)
|
|
978
1288
|
const toRemove = sorted
|
|
979
|
-
.filter(([
|
|
1289
|
+
.filter(([peerId, peer]) => !isTrusted(peerId) && !peer.connected)
|
|
980
1290
|
.slice(0, this.peerTable.size - targetSize);
|
|
981
1291
|
for (const [peerId] of toRemove) {
|
|
982
1292
|
this.peerTable.delete(peerId);
|
|
@@ -985,35 +1295,46 @@ export class P2PNetwork extends EventEmitter {
|
|
|
985
1295
|
}
|
|
986
1296
|
}
|
|
987
1297
|
else {
|
|
988
|
-
//
|
|
1298
|
+
// 常规清理:清理过期条目(跳过白名单)
|
|
989
1299
|
for (const [peerId, peer] of this.peerTable) {
|
|
1300
|
+
// 跳过白名单中的 peer
|
|
1301
|
+
if (isTrusted(peerId)) {
|
|
1302
|
+
skippedTrusted++;
|
|
1303
|
+
continue;
|
|
1304
|
+
}
|
|
990
1305
|
// 清理条件:长时间未活跃,或者未连接且超过一定时间
|
|
991
1306
|
const shouldClean = now - peer.lastSeen > threshold ||
|
|
992
|
-
(!peer.connected && now - peer.lastSeen > 60 * 60 * 1000); // 未连接超过1小时
|
|
1307
|
+
(!peer.connected && now - peer.lastSeen > 60 * 60 * 1000); // 未连接超过 1 小时
|
|
993
1308
|
if (shouldClean) {
|
|
994
1309
|
this.peerTable.delete(peerId);
|
|
995
1310
|
cleaned++;
|
|
996
1311
|
}
|
|
997
1312
|
}
|
|
998
1313
|
}
|
|
999
|
-
|
|
1000
|
-
this.logger.info('Cleaned up stale peers', { cleaned, remaining: this.peerTable.size, aggressive });
|
|
1001
|
-
}
|
|
1002
|
-
// 如果仍然超过最大容量,按最后活跃时间排序后删除最旧的
|
|
1314
|
+
// 如果仍然超过最大容量,按最后活跃时间排序后删除最旧的(跳过白名单)
|
|
1003
1315
|
if (this.peerTable.size > PEER_TABLE_MAX_SIZE) {
|
|
1004
1316
|
const sorted = Array.from(this.peerTable.entries())
|
|
1005
1317
|
.sort((a, b) => a[1].lastSeen - b[1].lastSeen);
|
|
1006
|
-
// 优先删除未连接的 peer
|
|
1007
|
-
const disconnected = sorted.filter(([
|
|
1318
|
+
// 优先删除未连接的 peer(跳过白名单)
|
|
1319
|
+
const disconnected = sorted.filter(([peerId, _]) => !isTrusted(peerId) && !this.connectedPeers.has(peerId));
|
|
1008
1320
|
const toRemove = disconnected.length > 0
|
|
1009
1321
|
? disconnected.slice(0, this.peerTable.size - PEER_TABLE_MAX_SIZE)
|
|
1010
|
-
: sorted.slice(0, this.peerTable.size - PEER_TABLE_MAX_SIZE);
|
|
1322
|
+
: sorted.filter(([peerId, _]) => !isTrusted(peerId)).slice(0, this.peerTable.size - PEER_TABLE_MAX_SIZE);
|
|
1011
1323
|
for (const [peerId] of toRemove) {
|
|
1012
1324
|
this.peerTable.delete(peerId);
|
|
1013
1325
|
cleaned++;
|
|
1014
1326
|
}
|
|
1015
1327
|
this.logger.info('Removed oldest peers to maintain limit', { removed: toRemove.length });
|
|
1016
1328
|
}
|
|
1329
|
+
if (cleaned > 0 || skippedTrusted > 0) {
|
|
1330
|
+
this.logger.info('Cleaned up stale peers', {
|
|
1331
|
+
cleaned,
|
|
1332
|
+
skippedTrusted,
|
|
1333
|
+
remaining: this.peerTable.size,
|
|
1334
|
+
aggressive,
|
|
1335
|
+
trustedCount: this.trustedPeers.size
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1017
1338
|
}
|
|
1018
1339
|
/**
|
|
1019
1340
|
* 原子操作:获取 peer 信息
|
|
@@ -1092,9 +1413,17 @@ export class P2PNetwork extends EventEmitter {
|
|
|
1092
1413
|
}
|
|
1093
1414
|
/**
|
|
1094
1415
|
* 获取已连接的 Peers
|
|
1416
|
+
* P2.4 修复:使用 connectedPeers Set 索引,O(1) 查询复杂度
|
|
1095
1417
|
*/
|
|
1096
1418
|
getConnectedPeers() {
|
|
1097
|
-
|
|
1419
|
+
const result = [];
|
|
1420
|
+
for (const peerId of this.connectedPeers) {
|
|
1421
|
+
const peer = this.peerTable.get(peerId);
|
|
1422
|
+
if (peer) {
|
|
1423
|
+
result.push(peer);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
return result;
|
|
1098
1427
|
}
|
|
1099
1428
|
/**
|
|
1100
1429
|
* 获取所有已知的 Peers
|