@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.
Files changed (91) hide show
  1. package/README.md +278 -63
  2. package/dist/cli/commands.d.ts.map +1 -1
  3. package/dist/cli/commands.js +29 -2
  4. package/dist/cli/commands.js.map +1 -1
  5. package/dist/cli/config.d.ts +176 -0
  6. package/dist/cli/config.d.ts.map +1 -0
  7. package/dist/cli/config.js +386 -0
  8. package/dist/cli/config.js.map +1 -0
  9. package/dist/cli/daemon.d.ts +54 -0
  10. package/dist/cli/daemon.d.ts.map +1 -0
  11. package/dist/cli/daemon.js +572 -0
  12. package/dist/cli/daemon.js.map +1 -0
  13. package/dist/cli/index.js +90 -16
  14. package/dist/cli/index.js.map +1 -1
  15. package/dist/cli/init.d.ts +13 -0
  16. package/dist/cli/init.d.ts.map +1 -0
  17. package/dist/cli/init.js +352 -0
  18. package/dist/cli/init.js.map +1 -0
  19. package/dist/core/e2ee-crypto.d.ts +127 -1
  20. package/dist/core/e2ee-crypto.d.ts.map +1 -1
  21. package/dist/core/e2ee-crypto.js +446 -12
  22. package/dist/core/e2ee-crypto.js.map +1 -1
  23. package/dist/core/f2a.d.ts +2 -1
  24. package/dist/core/f2a.d.ts.map +1 -1
  25. package/dist/core/f2a.js +6 -2
  26. package/dist/core/f2a.js.map +1 -1
  27. package/dist/core/identity/encrypted-key-store.d.ts +19 -0
  28. package/dist/core/identity/encrypted-key-store.d.ts.map +1 -0
  29. package/dist/core/identity/encrypted-key-store.js +72 -0
  30. package/dist/core/identity/encrypted-key-store.js.map +1 -0
  31. package/dist/core/identity/identity-manager.d.ts +133 -0
  32. package/dist/core/identity/identity-manager.d.ts.map +1 -0
  33. package/dist/core/identity/identity-manager.js +454 -0
  34. package/dist/core/identity/identity-manager.js.map +1 -0
  35. package/dist/core/identity/index.d.ts +8 -0
  36. package/dist/core/identity/index.d.ts.map +1 -0
  37. package/dist/core/identity/index.js +7 -0
  38. package/dist/core/identity/index.js.map +1 -0
  39. package/dist/core/identity/types.d.ts +70 -0
  40. package/dist/core/identity/types.d.ts.map +1 -0
  41. package/dist/core/identity/types.js +17 -0
  42. package/dist/core/identity/types.js.map +1 -0
  43. package/dist/core/p2p-network.d.ts +26 -0
  44. package/dist/core/p2p-network.d.ts.map +1 -1
  45. package/dist/core/p2p-network.js +434 -105
  46. package/dist/core/p2p-network.js.map +1 -1
  47. package/dist/core/reputation-security.d.ts +15 -0
  48. package/dist/core/reputation-security.d.ts.map +1 -1
  49. package/dist/core/reputation-security.js +73 -3
  50. package/dist/core/reputation-security.js.map +1 -1
  51. package/dist/core/reputation.d.ts +129 -4
  52. package/dist/core/reputation.d.ts.map +1 -1
  53. package/dist/core/reputation.js +294 -1
  54. package/dist/core/reputation.js.map +1 -1
  55. package/dist/core/review-committee.d.ts +2 -2
  56. package/dist/core/review-committee.d.ts.map +1 -1
  57. package/dist/core/review-committee.js +17 -0
  58. package/dist/core/review-committee.js.map +1 -1
  59. package/dist/daemon/control-server.d.ts.map +1 -1
  60. package/dist/daemon/control-server.js +44 -1
  61. package/dist/daemon/control-server.js.map +1 -1
  62. package/dist/daemon/webhook.d.ts +3 -0
  63. package/dist/daemon/webhook.d.ts.map +1 -1
  64. package/dist/daemon/webhook.js +318 -6
  65. package/dist/daemon/webhook.js.map +1 -1
  66. package/dist/index.d.ts +3 -3
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +7 -3
  69. package/dist/index.js.map +1 -1
  70. package/dist/types/index.d.ts +4 -0
  71. package/dist/types/index.d.ts.map +1 -1
  72. package/dist/types/index.js.map +1 -1
  73. package/dist/types/result.d.ts +1 -1
  74. package/dist/types/result.d.ts.map +1 -1
  75. package/dist/types/result.js.map +1 -1
  76. package/dist/utils/crypto-utils.d.ts +17 -0
  77. package/dist/utils/crypto-utils.d.ts.map +1 -0
  78. package/dist/utils/crypto-utils.js +28 -0
  79. package/dist/utils/crypto-utils.js.map +1 -0
  80. package/dist/utils/logger.d.ts +1 -0
  81. package/dist/utils/logger.d.ts.map +1 -1
  82. package/dist/utils/logger.js +9 -3
  83. package/dist/utils/logger.js.map +1 -1
  84. package/dist/utils/rate-limiter.d.ts.map +1 -1
  85. package/dist/utils/rate-limiter.js +3 -1
  86. package/dist/utils/rate-limiter.js.map +1 -1
  87. package/dist/utils/signature.d.ts +47 -1
  88. package/dist/utils/signature.d.ts.map +1 -1
  89. package/dist/utils/signature.js +166 -11
  90. package/dist/utils/signature.js.map +1 -1
  91. package/package.json +2 -1
@@ -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
- async acquire() {
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
- this.queue.push(resolve);
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
- // 注意:不传 privateKey,让 libp2p 自动生成 PeerId
100
- this.node = await createLibp2p({
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
- await this.e2eeCrypto.initialize();
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.node.addEventListener('peer:connect', async (evt) => {
430
- const peerId = evt.detail.toString();
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
- if (this.node) {
440
- const connections = this.node.getConnections();
441
- const conn = connections.find(c => c.remotePeer.toString() === peerId);
442
- if (conn && conn.remoteAddr) {
443
- multiaddrs = [conn.remoteAddr];
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
- // 无法获取 multiaddrs,使用空数组
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.node.addEventListener('peer:disconnect', async (evt) => {
469
- const peerId = evt.detail.toString();
470
- this.logger.info('Peer disconnected', { peerId: peerId.slice(0, 16) });
471
- this.emit('peer:disconnected', { peerId });
472
- // 使用原子操作更新路由表
473
- const updated = await this.updatePeer(peerId, (peer) => ({
474
- ...peer,
475
- connected: false,
476
- lastSeen: Date.now()
477
- }));
478
- if (!updated) {
479
- // Peer 不在路由表中,记录警告但不创建条目(已断开)
480
- this.logger.warn('Peer disconnected but not in routing table', { peerId: peerId.slice(0, 16) });
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
- this.upsertPeerFromAgentInfo(payload.agentInfo, peerId);
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
- this.cleanupStalePeersLocked(true);
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().then(() => {
824
- try {
825
- // 检查是否需要清理以腾出空间
826
- if (this.peerTable.size >= PEER_TABLE_MAX_SIZE && !this.peerTable.has(peerId)) {
827
- this.cleanupStalePeersLocked(true);
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
- finally {
847
- this.peerTableLock.release();
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
- // 使用原子操作避免竞态条件:先删除 Map 条目,再检查 resolved 标志
885
- // 这确保即使多个响应并发到达,也只有一个能成功获取到 pending 条目
886
- this.pendingTasks.delete(payload.taskId);
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
- await this.node.dial(ma);
912
- this.logger.info('Connected to bootstrap', { addr });
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', { addr });
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(([_, peer]) => !peer.connected)
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
- if (cleaned > 0) {
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(([_, peer]) => !peer.connected);
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
- return Array.from(this.peerTable.values()).filter(p => p.connected);
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