@f2a/network 0.1.2 → 0.1.3
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/package.json +8 -1
- package/.github/workflows/ci.yml +0 -113
- package/.github/workflows/publish.yml +0 -60
- package/MONOREPO.md +0 -58
- package/SKILL.md +0 -137
- package/dist/adapters/openclaw.d.ts +0 -103
- package/dist/adapters/openclaw.d.ts.map +0 -1
- package/dist/adapters/openclaw.js +0 -297
- package/dist/adapters/openclaw.js.map +0 -1
- package/dist/core/connection-manager.d.ts +0 -80
- package/dist/core/connection-manager.d.ts.map +0 -1
- package/dist/core/connection-manager.js +0 -235
- package/dist/core/connection-manager.js.map +0 -1
- package/dist/core/connection-manager.test.d.ts +0 -2
- package/dist/core/connection-manager.test.d.ts.map +0 -1
- package/dist/core/connection-manager.test.js +0 -52
- package/dist/core/connection-manager.test.js.map +0 -1
- package/dist/core/identity.d.ts +0 -47
- package/dist/core/identity.d.ts.map +0 -1
- package/dist/core/identity.js +0 -130
- package/dist/core/identity.js.map +0 -1
- package/dist/core/identity.test.d.ts +0 -2
- package/dist/core/identity.test.d.ts.map +0 -1
- package/dist/core/identity.test.js +0 -43
- package/dist/core/identity.test.js.map +0 -1
- package/dist/core/serverless.d.ts +0 -155
- package/dist/core/serverless.d.ts.map +0 -1
- package/dist/core/serverless.js +0 -615
- package/dist/core/serverless.js.map +0 -1
- package/dist/daemon/webhook.test.d.ts +0 -2
- package/dist/daemon/webhook.test.d.ts.map +0 -1
- package/dist/daemon/webhook.test.js +0 -24
- package/dist/daemon/webhook.test.js.map +0 -1
- package/dist/protocol/messages.d.ts +0 -739
- package/dist/protocol/messages.d.ts.map +0 -1
- package/dist/protocol/messages.js +0 -188
- package/dist/protocol/messages.js.map +0 -1
- package/dist/protocol/messages.test.d.ts +0 -2
- package/dist/protocol/messages.test.d.ts.map +0 -1
- package/dist/protocol/messages.test.js +0 -55
- package/dist/protocol/messages.test.js.map +0 -1
- package/docs/F2A-PROTOCOL.md +0 -61
- package/docs/MOBILE_BOOTSTRAP_DESIGN.md +0 -126
- package/docs/a2a-lessons.md +0 -316
- package/docs/middleware-guide.md +0 -448
- package/docs/readme-update-checklist.md +0 -90
- package/docs/reputation-guide.md +0 -396
- package/docs/rfcs/001-reputation-system.md +0 -712
- package/docs/security-design.md +0 -247
- package/install.sh +0 -231
- package/packages/openclaw-adapter/README.md +0 -510
- package/packages/openclaw-adapter/openclaw.plugin.json +0 -106
- package/packages/openclaw-adapter/package.json +0 -40
- package/packages/openclaw-adapter/src/announcement-queue.test.ts +0 -449
- package/packages/openclaw-adapter/src/announcement-queue.ts +0 -403
- package/packages/openclaw-adapter/src/capability-detector.test.ts +0 -99
- package/packages/openclaw-adapter/src/capability-detector.ts +0 -183
- package/packages/openclaw-adapter/src/claim-handlers.test.ts +0 -974
- package/packages/openclaw-adapter/src/claim-handlers.ts +0 -482
- package/packages/openclaw-adapter/src/connector.business.test.ts +0 -583
- package/packages/openclaw-adapter/src/connector.ts +0 -795
- package/packages/openclaw-adapter/src/index.test.ts +0 -82
- package/packages/openclaw-adapter/src/index.ts +0 -18
- package/packages/openclaw-adapter/src/integration.e2e.test.ts +0 -829
- package/packages/openclaw-adapter/src/logger.ts +0 -51
- package/packages/openclaw-adapter/src/network-client.test.ts +0 -266
- package/packages/openclaw-adapter/src/network-client.ts +0 -251
- package/packages/openclaw-adapter/src/network-recovery.test.ts +0 -465
- package/packages/openclaw-adapter/src/node-manager.test.ts +0 -136
- package/packages/openclaw-adapter/src/node-manager.ts +0 -429
- package/packages/openclaw-adapter/src/plugin.test.ts +0 -439
- package/packages/openclaw-adapter/src/plugin.ts +0 -104
- package/packages/openclaw-adapter/src/reputation.test.ts +0 -221
- package/packages/openclaw-adapter/src/reputation.ts +0 -368
- package/packages/openclaw-adapter/src/task-guard.test.ts +0 -502
- package/packages/openclaw-adapter/src/task-guard.ts +0 -860
- package/packages/openclaw-adapter/src/task-queue.concurrency.test.ts +0 -462
- package/packages/openclaw-adapter/src/task-queue.edge-cases.test.ts +0 -284
- package/packages/openclaw-adapter/src/task-queue.persistence.test.ts +0 -408
- package/packages/openclaw-adapter/src/task-queue.ts +0 -668
- package/packages/openclaw-adapter/src/tool-handlers.test.ts +0 -906
- package/packages/openclaw-adapter/src/tool-handlers.ts +0 -574
- package/packages/openclaw-adapter/src/types.ts +0 -361
- package/packages/openclaw-adapter/src/webhook-pusher.test.ts +0 -188
- package/packages/openclaw-adapter/src/webhook-pusher.ts +0 -220
- package/packages/openclaw-adapter/src/webhook-server.test.ts +0 -580
- package/packages/openclaw-adapter/src/webhook-server.ts +0 -202
- package/packages/openclaw-adapter/tsconfig.json +0 -20
- package/src/cli/commands.test.ts +0 -157
- package/src/cli/commands.ts +0 -129
- package/src/cli/index.test.ts +0 -77
- package/src/cli/index.ts +0 -234
- package/src/core/autonomous-economy.test.ts +0 -291
- package/src/core/autonomous-economy.ts +0 -428
- package/src/core/e2ee-crypto.test.ts +0 -125
- package/src/core/e2ee-crypto.ts +0 -246
- package/src/core/f2a.test.ts +0 -269
- package/src/core/f2a.ts +0 -618
- package/src/core/p2p-network.test.ts +0 -199
- package/src/core/p2p-network.ts +0 -1432
- package/src/core/reputation-security.test.ts +0 -403
- package/src/core/reputation-security.ts +0 -562
- package/src/core/reputation.test.ts +0 -260
- package/src/core/reputation.ts +0 -576
- package/src/core/review-committee.test.ts +0 -380
- package/src/core/review-committee.ts +0 -401
- package/src/core/token-manager.test.ts +0 -133
- package/src/core/token-manager.ts +0 -140
- package/src/daemon/control-server.test.ts +0 -216
- package/src/daemon/control-server.ts +0 -292
- package/src/daemon/index.test.ts +0 -85
- package/src/daemon/index.ts +0 -89
- package/src/daemon/main.ts +0 -44
- package/src/daemon/start.ts +0 -29
- package/src/daemon/webhook.test.ts +0 -68
- package/src/daemon/webhook.ts +0 -105
- package/src/index.test.ts +0 -436
- package/src/index.ts +0 -72
- package/src/types/index.test.ts +0 -87
- package/src/types/index.ts +0 -341
- package/src/types/result.ts +0 -68
- package/src/utils/benchmark.ts +0 -237
- package/src/utils/logger.ts +0 -331
- package/src/utils/middleware.ts +0 -229
- package/src/utils/rate-limiter.ts +0 -207
- package/src/utils/signature.ts +0 -136
- package/src/utils/validation.ts +0 -186
- package/tests/docker/Dockerfile.node +0 -23
- package/tests/docker/Dockerfile.runner +0 -18
- package/tests/docker/docker-compose.test.yml +0 -73
- package/tests/integration/message-passing.test.ts +0 -109
- package/tests/integration/multi-node.test.ts +0 -92
- package/tests/integration/p2p-connection.test.ts +0 -83
- package/tests/integration/test-config.ts +0 -32
- package/tsconfig.json +0 -21
- package/vitest.config.ts +0 -26
package/src/core/p2p-network.ts
DELETED
|
@@ -1,1432 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* P2P 网络管理器
|
|
3
|
-
* 基于 libp2p 实现 Agent 发现与通信
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { createLibp2p } from 'libp2p';
|
|
7
|
-
import { tcp } from '@libp2p/tcp';
|
|
8
|
-
import { noise } from '@chainsafe/libp2p-noise';
|
|
9
|
-
import { kadDHT } from '@libp2p/kad-dht';
|
|
10
|
-
import { peerIdFromString } from '@libp2p/peer-id';
|
|
11
|
-
import type { PeerId } from '@libp2p/interface';
|
|
12
|
-
import { multiaddr } from '@multiformats/multiaddr';
|
|
13
|
-
import type { Multiaddr } from '@multiformats/multiaddr';
|
|
14
|
-
import { EventEmitter } from 'eventemitter3';
|
|
15
|
-
import { randomUUID } from 'crypto';
|
|
16
|
-
import type { Libp2p } from '@libp2p/interface';
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
P2PNetworkConfig,
|
|
20
|
-
AgentInfo,
|
|
21
|
-
AgentCapability,
|
|
22
|
-
F2AMessage,
|
|
23
|
-
PeerInfo,
|
|
24
|
-
PeerDiscoveredEvent,
|
|
25
|
-
PeerConnectedEvent,
|
|
26
|
-
PeerDisconnectedEvent,
|
|
27
|
-
Result,
|
|
28
|
-
TaskRequestPayload,
|
|
29
|
-
TaskResponsePayload,
|
|
30
|
-
DiscoverPayload,
|
|
31
|
-
CapabilityQueryPayload,
|
|
32
|
-
CapabilityResponsePayload,
|
|
33
|
-
success,
|
|
34
|
-
failureFromError,
|
|
35
|
-
createError
|
|
36
|
-
} from '../types/index.js';
|
|
37
|
-
import { E2EECrypto, EncryptedMessage } from './e2ee-crypto.js';
|
|
38
|
-
import { Logger } from '../utils/logger.js';
|
|
39
|
-
import { validateF2AMessage, validateTaskRequestPayload, validateTaskResponsePayload } from '../utils/validation.js';
|
|
40
|
-
import { MiddlewareManager, Middleware } from '../utils/middleware.js';
|
|
41
|
-
import { RequestSigner, loadSignatureConfig, SignedMessage } from '../utils/signature.js';
|
|
42
|
-
|
|
43
|
-
// DHT 服务类型定义
|
|
44
|
-
interface DHTService {
|
|
45
|
-
findPeer(peerId: PeerId): Promise<{ multiaddrs: Multiaddr[] } | null>;
|
|
46
|
-
routingTable?: { size: number };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
interface Libp2pServices {
|
|
50
|
-
dht?: DHTService;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// 加密消息类型定义
|
|
54
|
-
interface EncryptedF2AMessage extends F2AMessage {
|
|
55
|
-
encrypted: true;
|
|
56
|
-
payload: EncryptedMessage;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// 类型守卫:检查是否为加密消息
|
|
60
|
-
function isEncryptedMessage(msg: F2AMessage): msg is EncryptedF2AMessage {
|
|
61
|
-
return 'encrypted' in msg && msg.encrypted === true && 'payload' in msg;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// 加密消息处理结果
|
|
65
|
-
interface DecryptResult {
|
|
66
|
-
action: 'continue' | 'return';
|
|
67
|
-
message: F2AMessage;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// F2A 协议标识
|
|
71
|
-
const F2A_PROTOCOL = '/f2a/1.0.0';
|
|
72
|
-
|
|
73
|
-
// 清理配置
|
|
74
|
-
const PEER_TABLE_CLEANUP_INTERVAL = 5 * 60 * 1000; // 5分钟
|
|
75
|
-
const PEER_STALE_THRESHOLD = 24 * 60 * 60 * 1000; // 24小时
|
|
76
|
-
const PEER_TABLE_MAX_SIZE = 1000; // 最大peer数
|
|
77
|
-
const PEER_TABLE_HIGH_WATERMARK = 0.9; // 高水位线(90%触发主动清理)
|
|
78
|
-
const PEER_TABLE_AGGRESSIVE_CLEANUP_THRESHOLD = 0.8; // 激进清理后保留的目标比例(80%)
|
|
79
|
-
|
|
80
|
-
export interface P2PNetworkEvents {
|
|
81
|
-
'peer:discovered': (event: PeerDiscoveredEvent) => void;
|
|
82
|
-
'peer:connected': (event: PeerConnectedEvent) => void;
|
|
83
|
-
'peer:disconnected': (event: PeerDisconnectedEvent) => void;
|
|
84
|
-
'message:received': (message: F2AMessage, peerId: string) => void;
|
|
85
|
-
'error': (error: Error) => void;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** 发现选项 */
|
|
89
|
-
export interface DiscoverOptions {
|
|
90
|
-
/** 发现超时毫秒(默认 2000) */
|
|
91
|
-
timeoutMs?: number;
|
|
92
|
-
/** 是否等待首个响应即返回(默认 false) */
|
|
93
|
-
waitForFirstResponse?: boolean;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* 简单的异步锁实现,用于保护关键资源的并发访问
|
|
98
|
-
*/
|
|
99
|
-
class AsyncLock {
|
|
100
|
-
private locked = false;
|
|
101
|
-
private queue: Array<() => void> = [];
|
|
102
|
-
|
|
103
|
-
async acquire(): Promise<void> {
|
|
104
|
-
if (!this.locked) {
|
|
105
|
-
this.locked = true;
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
return new Promise<void>(resolve => {
|
|
109
|
-
this.queue.push(resolve);
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
release(): void {
|
|
114
|
-
const next = this.queue.shift();
|
|
115
|
-
if (next) {
|
|
116
|
-
next();
|
|
117
|
-
} else {
|
|
118
|
-
this.locked = false;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export class P2PNetwork extends EventEmitter<P2PNetworkEvents> {
|
|
124
|
-
private node: Libp2p | null = null;
|
|
125
|
-
private config: P2PNetworkConfig;
|
|
126
|
-
private peerTable: Map<string, PeerInfo> = new Map();
|
|
127
|
-
/** 用于保护 peerTable 并发访问的锁 */
|
|
128
|
-
private peerTableLock = new AsyncLock();
|
|
129
|
-
private agentInfo: AgentInfo;
|
|
130
|
-
private pendingTasks: Map<string, {
|
|
131
|
-
resolve: (result: unknown) => void;
|
|
132
|
-
reject: (error: string) => void;
|
|
133
|
-
timeout: NodeJS.Timeout;
|
|
134
|
-
resolved: boolean;
|
|
135
|
-
}> = new Map();
|
|
136
|
-
private cleanupInterval?: NodeJS.Timeout;
|
|
137
|
-
private discoveryInterval?: NodeJS.Timeout;
|
|
138
|
-
private e2eeCrypto: E2EECrypto;
|
|
139
|
-
private enableE2EE: boolean = true;
|
|
140
|
-
private logger: Logger;
|
|
141
|
-
private middlewareManager: MiddlewareManager;
|
|
142
|
-
|
|
143
|
-
constructor(agentInfo: AgentInfo, config: P2PNetworkConfig = {}) {
|
|
144
|
-
super();
|
|
145
|
-
this.agentInfo = agentInfo;
|
|
146
|
-
this.e2eeCrypto = new E2EECrypto();
|
|
147
|
-
this.middlewareManager = new MiddlewareManager();
|
|
148
|
-
this.config = {
|
|
149
|
-
listenPort: 0,
|
|
150
|
-
enableMDNS: true,
|
|
151
|
-
enableDHT: false,
|
|
152
|
-
...config
|
|
153
|
-
};
|
|
154
|
-
this.logger = new Logger({ component: 'P2P' });
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* 启动 P2P 网络
|
|
159
|
-
*/
|
|
160
|
-
async start(): Promise<Result<{ peerId: string; addresses: string[] }>> {
|
|
161
|
-
try {
|
|
162
|
-
// 构建监听地址
|
|
163
|
-
const listenAddresses = this.config.listenAddresses || [
|
|
164
|
-
`/ip4/0.0.0.0/tcp/${this.config.listenPort}`
|
|
165
|
-
];
|
|
166
|
-
|
|
167
|
-
// 创建 libp2p 节点 - 启用 noise 加密
|
|
168
|
-
const services: Record<string, any> = {};
|
|
169
|
-
|
|
170
|
-
// 只有显式启用 DHT 时才添加
|
|
171
|
-
if (this.config.enableDHT === true) {
|
|
172
|
-
services.dht = kadDHT({
|
|
173
|
-
clientMode: !this.config.dhtServerMode, // 默认客户端模式
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// 注意:不传 privateKey,让 libp2p 自动生成 PeerId
|
|
178
|
-
this.node = await createLibp2p({
|
|
179
|
-
addresses: {
|
|
180
|
-
listen: listenAddresses
|
|
181
|
-
},
|
|
182
|
-
transports: [tcp()],
|
|
183
|
-
connectionEncryption: [noise()],
|
|
184
|
-
services
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
// 设置事件监听
|
|
188
|
-
this.setupEventHandlers();
|
|
189
|
-
|
|
190
|
-
// 启动节点
|
|
191
|
-
await this.node.start();
|
|
192
|
-
|
|
193
|
-
// 获取实际监听地址
|
|
194
|
-
const addrs = this.node.getMultiaddrs().map(ma => ma.toString());
|
|
195
|
-
|
|
196
|
-
// 从节点获取 peer ID
|
|
197
|
-
const peerId = this.node.peerId;
|
|
198
|
-
|
|
199
|
-
// 更新 agentInfo
|
|
200
|
-
this.agentInfo.peerId = peerId.toString();
|
|
201
|
-
this.agentInfo.multiaddrs = addrs;
|
|
202
|
-
|
|
203
|
-
// 初始化 E2EE 加密
|
|
204
|
-
await this.e2eeCrypto.initialize();
|
|
205
|
-
this.agentInfo.encryptionPublicKey = this.e2eeCrypto.getPublicKey() || undefined;
|
|
206
|
-
this.logger.info('E2EE encryption enabled', {
|
|
207
|
-
publicKey: this.agentInfo.encryptionPublicKey?.slice(0, 16)
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
// 连接引导节点
|
|
211
|
-
if (this.config.bootstrapPeers) {
|
|
212
|
-
await this.connectToBootstrapPeers(this.config.bootstrapPeers);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// 启动定期发现广播
|
|
216
|
-
this.startDiscoveryBroadcast();
|
|
217
|
-
|
|
218
|
-
// 启动定期清理任务
|
|
219
|
-
this.startCleanupTask();
|
|
220
|
-
|
|
221
|
-
// 延迟 2 秒后广播发现消息,等待连接稳定
|
|
222
|
-
setTimeout(() => {
|
|
223
|
-
this.broadcastDiscovery();
|
|
224
|
-
}, 2000);
|
|
225
|
-
|
|
226
|
-
// 如果启用 DHT,等待 DHT 就绪
|
|
227
|
-
if (this.config.enableDHT !== false && this.node.services.dht) {
|
|
228
|
-
this.logger.info('DHT enabled, waiting for routing table');
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
this.logger.info('Started', { peerId: peerId.toString().slice(0, 16) });
|
|
232
|
-
this.logger.info('Listening', { addresses: addrs });
|
|
233
|
-
this.logger.info('Connection encryption enabled', { protocol: 'Noise' });
|
|
234
|
-
|
|
235
|
-
return success({ peerId: peerId.toString(), addresses: addrs });
|
|
236
|
-
} catch (error) {
|
|
237
|
-
return failureFromError('NETWORK_NOT_STARTED', 'Failed to start P2P network', error as Error);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* 停止 P2P 网络
|
|
243
|
-
*/
|
|
244
|
-
async stop(): Promise<void> {
|
|
245
|
-
// 停止清理任务
|
|
246
|
-
if (this.cleanupInterval) {
|
|
247
|
-
clearInterval(this.cleanupInterval);
|
|
248
|
-
this.cleanupInterval = undefined;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// 停止发现广播定时器
|
|
252
|
-
if (this.discoveryInterval) {
|
|
253
|
-
clearInterval(this.discoveryInterval);
|
|
254
|
-
this.discoveryInterval = undefined;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (this.node) {
|
|
258
|
-
// 清理待处理任务
|
|
259
|
-
for (const [taskId, { timeout, resolve }] of this.pendingTasks) {
|
|
260
|
-
clearTimeout(timeout);
|
|
261
|
-
resolve({ success: false, error: 'Network stopped' });
|
|
262
|
-
}
|
|
263
|
-
this.pendingTasks.clear();
|
|
264
|
-
|
|
265
|
-
await this.node.stop();
|
|
266
|
-
this.node = null;
|
|
267
|
-
this.logger.info('Stopped');
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* 发现网络中的 Agent(按能力过滤)
|
|
273
|
-
* @param capability 可选的能力过滤
|
|
274
|
-
* @param options 发现选项
|
|
275
|
-
*/
|
|
276
|
-
async discoverAgents(capability?: string, options?: DiscoverOptions): Promise<AgentInfo[]> {
|
|
277
|
-
const timeoutMs = options?.timeoutMs ?? 2000;
|
|
278
|
-
const waitForFirst = options?.waitForFirstResponse ?? false;
|
|
279
|
-
|
|
280
|
-
// 使用锁保护创建快照,防止并发修改
|
|
281
|
-
const agents: AgentInfo[] = [];
|
|
282
|
-
const seenPeerIds = new Set<string>();
|
|
283
|
-
|
|
284
|
-
await this.peerTableLock.acquire();
|
|
285
|
-
try {
|
|
286
|
-
for (const peer of this.peerTable.values()) {
|
|
287
|
-
if (peer.agentInfo) {
|
|
288
|
-
if (!capability || this.hasCapability(peer.agentInfo, capability)) {
|
|
289
|
-
agents.push(peer.agentInfo);
|
|
290
|
-
seenPeerIds.add(peer.agentInfo.peerId);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
} finally {
|
|
295
|
-
this.peerTableLock.release();
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// 如果已经有足够的 agents 且不需要等待响应,直接返回
|
|
299
|
-
if (agents.length > 0 && !waitForFirst) {
|
|
300
|
-
return agents;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// 广播能力查询以发现更多节点
|
|
304
|
-
await this.broadcast({
|
|
305
|
-
id: randomUUID(),
|
|
306
|
-
type: 'CAPABILITY_QUERY',
|
|
307
|
-
from: this.agentInfo.peerId,
|
|
308
|
-
timestamp: Date.now(),
|
|
309
|
-
payload: { capabilityName: capability } as CapabilityQueryPayload
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
// 使用 Promise.race 等待首个响应或超时
|
|
313
|
-
if (waitForFirst) {
|
|
314
|
-
await new Promise<void>(resolve => {
|
|
315
|
-
const timeout = setTimeout(() => {
|
|
316
|
-
this.off('peer:discovered', onPeerDiscovered);
|
|
317
|
-
resolve();
|
|
318
|
-
}, timeoutMs);
|
|
319
|
-
|
|
320
|
-
const onPeerDiscovered = (event: PeerDiscoveredEvent) => {
|
|
321
|
-
if (!capability || this.hasCapability(event.agentInfo, capability)) {
|
|
322
|
-
// 使用 Set 原子检查,防止重复添加
|
|
323
|
-
if (!seenPeerIds.has(event.agentInfo.peerId)) {
|
|
324
|
-
agents.push(event.agentInfo);
|
|
325
|
-
seenPeerIds.add(event.agentInfo.peerId);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
clearTimeout(timeout);
|
|
329
|
-
this.off('peer:discovered', onPeerDiscovered);
|
|
330
|
-
resolve();
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
this.on('peer:discovered', onPeerDiscovered);
|
|
334
|
-
});
|
|
335
|
-
} else {
|
|
336
|
-
// 等待响应(可配置超时)
|
|
337
|
-
await new Promise(resolve => setTimeout(resolve, timeoutMs));
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// 再次收集 - 使用锁保护创建快照
|
|
341
|
-
await this.peerTableLock.acquire();
|
|
342
|
-
try {
|
|
343
|
-
for (const peer of this.peerTable.values()) {
|
|
344
|
-
if (peer.agentInfo && !seenPeerIds.has(peer.agentInfo.peerId)) {
|
|
345
|
-
if (!capability || this.hasCapability(peer.agentInfo, capability)) {
|
|
346
|
-
agents.push(peer.agentInfo);
|
|
347
|
-
seenPeerIds.add(peer.agentInfo.peerId);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
} finally {
|
|
352
|
-
this.peerTableLock.release();
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
return agents;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* 向特定 Peer 发送任务请求
|
|
360
|
-
*/
|
|
361
|
-
async sendTaskRequest(
|
|
362
|
-
peerId: string,
|
|
363
|
-
taskType: string,
|
|
364
|
-
description: string,
|
|
365
|
-
parameters?: Record<string, unknown>,
|
|
366
|
-
timeout: number = 30000
|
|
367
|
-
): Promise<Result<unknown>> {
|
|
368
|
-
const taskId = randomUUID();
|
|
369
|
-
|
|
370
|
-
const message: F2AMessage = {
|
|
371
|
-
id: taskId,
|
|
372
|
-
type: 'TASK_REQUEST',
|
|
373
|
-
from: this.agentInfo.peerId,
|
|
374
|
-
to: peerId,
|
|
375
|
-
timestamp: Date.now(),
|
|
376
|
-
payload: {
|
|
377
|
-
taskId,
|
|
378
|
-
taskType,
|
|
379
|
-
description,
|
|
380
|
-
parameters,
|
|
381
|
-
timeout: Math.floor(timeout / 1000)
|
|
382
|
-
} as TaskRequestPayload
|
|
383
|
-
};
|
|
384
|
-
|
|
385
|
-
// 发送消息(启用 E2EE 加密)
|
|
386
|
-
const sendResult = await this.sendMessage(peerId, message, true);
|
|
387
|
-
if (!sendResult.success) {
|
|
388
|
-
return sendResult;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// 等待响应
|
|
392
|
-
return new Promise((resolve) => {
|
|
393
|
-
const taskEntry = {
|
|
394
|
-
resolve: (result: unknown) => {
|
|
395
|
-
if (!taskEntry.resolved) {
|
|
396
|
-
taskEntry.resolved = true;
|
|
397
|
-
// 从 Map 中移除,确保不会重复处理
|
|
398
|
-
this.pendingTasks.delete(taskId);
|
|
399
|
-
resolve(success(result));
|
|
400
|
-
}
|
|
401
|
-
},
|
|
402
|
-
reject: (error: string) => {
|
|
403
|
-
if (!taskEntry.resolved) {
|
|
404
|
-
taskEntry.resolved = true;
|
|
405
|
-
// 从 Map 中移除,确保不会重复处理
|
|
406
|
-
this.pendingTasks.delete(taskId);
|
|
407
|
-
resolve({ success: false, error: createError('TASK_FAILED', error) } as Result<unknown>);
|
|
408
|
-
}
|
|
409
|
-
},
|
|
410
|
-
timeout: setTimeout(() => {
|
|
411
|
-
if (!taskEntry.resolved) {
|
|
412
|
-
taskEntry.resolved = true;
|
|
413
|
-
this.pendingTasks.delete(taskId);
|
|
414
|
-
resolve(failureFromError('TIMEOUT', 'Task timeout'));
|
|
415
|
-
}
|
|
416
|
-
}, timeout),
|
|
417
|
-
resolved: false
|
|
418
|
-
};
|
|
419
|
-
|
|
420
|
-
this.pendingTasks.set(taskId, taskEntry);
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
/**
|
|
425
|
-
* 发送任务响应
|
|
426
|
-
*/
|
|
427
|
-
async sendTaskResponse(
|
|
428
|
-
peerId: string,
|
|
429
|
-
taskId: string,
|
|
430
|
-
status: 'success' | 'error' | 'rejected' | 'delegated',
|
|
431
|
-
result?: unknown,
|
|
432
|
-
error?: string
|
|
433
|
-
): Promise<Result<void>> {
|
|
434
|
-
const message: F2AMessage = {
|
|
435
|
-
id: randomUUID(),
|
|
436
|
-
type: 'TASK_RESPONSE',
|
|
437
|
-
from: this.agentInfo.peerId,
|
|
438
|
-
to: peerId,
|
|
439
|
-
timestamp: Date.now(),
|
|
440
|
-
payload: {
|
|
441
|
-
taskId,
|
|
442
|
-
status,
|
|
443
|
-
result,
|
|
444
|
-
error
|
|
445
|
-
} as TaskResponsePayload
|
|
446
|
-
};
|
|
447
|
-
|
|
448
|
-
// 任务响应也启用 E2EE 加密
|
|
449
|
-
return this.sendMessage(peerId, message, true);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
/**
|
|
453
|
-
* 广播发现消息
|
|
454
|
-
*/
|
|
455
|
-
private async broadcastDiscovery(): Promise<void> {
|
|
456
|
-
const message: F2AMessage = {
|
|
457
|
-
id: randomUUID(),
|
|
458
|
-
type: 'DISCOVER',
|
|
459
|
-
from: this.agentInfo.peerId,
|
|
460
|
-
timestamp: Date.now(),
|
|
461
|
-
payload: { agentInfo: this.agentInfo } as DiscoverPayload
|
|
462
|
-
};
|
|
463
|
-
|
|
464
|
-
await this.broadcast(message);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
* 广播消息到全网
|
|
469
|
-
*/
|
|
470
|
-
private async broadcast(message: F2AMessage): Promise<void> {
|
|
471
|
-
if (!this.node) return;
|
|
472
|
-
|
|
473
|
-
// 向所有已连接的对等节点发送
|
|
474
|
-
const peers = this.node.getPeers();
|
|
475
|
-
const results = await Promise.allSettled(
|
|
476
|
-
peers.map(peer => this.sendMessage(peer.toString(), message))
|
|
477
|
-
);
|
|
478
|
-
|
|
479
|
-
// 记录发送失败的情况
|
|
480
|
-
const failures = results.filter(r =>
|
|
481
|
-
r.status === 'rejected' ||
|
|
482
|
-
(r.status === 'fulfilled' && !r.value.success)
|
|
483
|
-
);
|
|
484
|
-
if (failures.length > 0) {
|
|
485
|
-
this.logger.warn('Broadcast failed to some peers', {
|
|
486
|
-
failed: failures.length,
|
|
487
|
-
total: peers.length
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
/**
|
|
493
|
-
* 向特定 Peer 发送消息
|
|
494
|
-
* @param peerId 目标 Peer ID
|
|
495
|
-
* @param message 消息内容
|
|
496
|
-
* @param encrypt 是否启用 E2EE 加密(默认 false,发现类消息不需要加密)
|
|
497
|
-
*/
|
|
498
|
-
private async sendMessage(peerId: string, message: F2AMessage, encrypt: boolean = false): Promise<Result<void>> {
|
|
499
|
-
if (!this.node) {
|
|
500
|
-
return failureFromError('NETWORK_NOT_STARTED', 'P2P network not started');
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
try {
|
|
504
|
-
// 检查是否已连接
|
|
505
|
-
const connections = this.node.getConnections();
|
|
506
|
-
const existingConn = connections.find(c => c.remotePeer.toString() === peerId);
|
|
507
|
-
|
|
508
|
-
let peer;
|
|
509
|
-
if (existingConn) {
|
|
510
|
-
// 已连接,直接使用现有连接
|
|
511
|
-
peer = existingConn;
|
|
512
|
-
} else {
|
|
513
|
-
// 未连接,需要 dial
|
|
514
|
-
const peerInfo = this.peerTable.get(peerId);
|
|
515
|
-
if (!peerInfo || peerInfo.multiaddrs.length === 0) {
|
|
516
|
-
return failureFromError('PEER_NOT_FOUND', `Peer ${peerId} not found`);
|
|
517
|
-
}
|
|
518
|
-
peer = await this.node.dial(peerInfo.multiaddrs[0]);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// 准备消息数据(根据是否启用 E2EE 加密)
|
|
522
|
-
let data: Buffer;
|
|
523
|
-
if (encrypt && this.enableE2EE) {
|
|
524
|
-
// 检查是否有共享密钥
|
|
525
|
-
if (!this.e2eeCrypto.canEncryptTo(peerId)) {
|
|
526
|
-
return failureFromError(
|
|
527
|
-
'ENCRYPTION_NOT_READY',
|
|
528
|
-
'No shared secret with peer. Wait for key exchange to complete.'
|
|
529
|
-
);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// 加密消息内容
|
|
533
|
-
const encrypted = this.e2eeCrypto.encrypt(peerId, JSON.stringify(message));
|
|
534
|
-
if (!encrypted) {
|
|
535
|
-
return failureFromError(
|
|
536
|
-
'ENCRYPTION_FAILED',
|
|
537
|
-
'Failed to encrypt message. Cannot proceed in secure mode.'
|
|
538
|
-
);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
data = Buffer.from(JSON.stringify({
|
|
542
|
-
...message,
|
|
543
|
-
encrypted: true,
|
|
544
|
-
payload: encrypted
|
|
545
|
-
}));
|
|
546
|
-
} else {
|
|
547
|
-
data = Buffer.from(JSON.stringify(message));
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
// 使用协议流发送消息
|
|
551
|
-
const stream = await peer.newStream(F2A_PROTOCOL);
|
|
552
|
-
await stream.sink([data]);
|
|
553
|
-
await stream.close();
|
|
554
|
-
|
|
555
|
-
return success(undefined);
|
|
556
|
-
} catch (error) {
|
|
557
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
558
|
-
return failureFromError('CONNECTION_FAILED', err.message, err);
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
/**
|
|
563
|
-
* 设置 libp2p 事件处理
|
|
564
|
-
*/
|
|
565
|
-
private setupEventHandlers(): void {
|
|
566
|
-
if (!this.node) return;
|
|
567
|
-
|
|
568
|
-
// 新连接
|
|
569
|
-
this.node.addEventListener('peer:connect', async (evt) => {
|
|
570
|
-
const peerId = evt.detail.toString();
|
|
571
|
-
this.logger.info('Peer connected', { peerId: peerId.slice(0, 16) });
|
|
572
|
-
|
|
573
|
-
this.emit('peer:connected', {
|
|
574
|
-
peerId,
|
|
575
|
-
direction: 'inbound'
|
|
576
|
-
});
|
|
577
|
-
|
|
578
|
-
// 从连接获取远程 multiaddr
|
|
579
|
-
let multiaddrs: Multiaddr[] = [];
|
|
580
|
-
try {
|
|
581
|
-
if (this.node) {
|
|
582
|
-
const connections = this.node.getConnections();
|
|
583
|
-
const conn = connections.find(c => c.remotePeer.toString() === peerId);
|
|
584
|
-
if (conn && conn.remoteAddr) {
|
|
585
|
-
multiaddrs = [conn.remoteAddr];
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
} catch {
|
|
589
|
-
// 无法获取 multiaddrs,使用空数组
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
// 使用原子操作更新路由表
|
|
593
|
-
const now = Date.now();
|
|
594
|
-
await this.upsertPeer(
|
|
595
|
-
peerId,
|
|
596
|
-
() => ({
|
|
597
|
-
peerId,
|
|
598
|
-
multiaddrs,
|
|
599
|
-
connected: true,
|
|
600
|
-
reputation: 50,
|
|
601
|
-
connectedAt: now,
|
|
602
|
-
lastSeen: now
|
|
603
|
-
}),
|
|
604
|
-
(peer) => ({
|
|
605
|
-
...peer,
|
|
606
|
-
connected: true,
|
|
607
|
-
connectedAt: now,
|
|
608
|
-
lastSeen: now,
|
|
609
|
-
...(multiaddrs.length > 0 ? { multiaddrs } : {})
|
|
610
|
-
})
|
|
611
|
-
);
|
|
612
|
-
});
|
|
613
|
-
|
|
614
|
-
// 断开连接
|
|
615
|
-
this.node.addEventListener('peer:disconnect', async (evt) => {
|
|
616
|
-
const peerId = evt.detail.toString();
|
|
617
|
-
this.logger.info('Peer disconnected', { peerId: peerId.slice(0, 16) });
|
|
618
|
-
|
|
619
|
-
this.emit('peer:disconnected', { peerId });
|
|
620
|
-
|
|
621
|
-
// 使用原子操作更新路由表
|
|
622
|
-
const updated = await this.updatePeer(peerId, (peer) => ({
|
|
623
|
-
...peer,
|
|
624
|
-
connected: false,
|
|
625
|
-
lastSeen: Date.now()
|
|
626
|
-
}));
|
|
627
|
-
|
|
628
|
-
if (!updated) {
|
|
629
|
-
// Peer 不在路由表中,记录警告但不创建条目(已断开)
|
|
630
|
-
this.logger.warn('Peer disconnected but not in routing table', { peerId: peerId.slice(0, 16) });
|
|
631
|
-
}
|
|
632
|
-
});
|
|
633
|
-
|
|
634
|
-
// 处理传入的协议流
|
|
635
|
-
this.node.handle(F2A_PROTOCOL, async ({ stream, connection }) => {
|
|
636
|
-
try {
|
|
637
|
-
// 读取数据
|
|
638
|
-
const chunks: Uint8Array[] = [];
|
|
639
|
-
for await (const chunk of stream.source) {
|
|
640
|
-
// chunk 可能是 Uint8Array 或 Uint8ArrayList(来自旧版本库)
|
|
641
|
-
// 统一转换为 Uint8Array
|
|
642
|
-
const data = chunk instanceof Uint8Array
|
|
643
|
-
? chunk
|
|
644
|
-
: new Uint8Array((chunk as { subarray(): Uint8Array }).subarray());
|
|
645
|
-
chunks.push(data);
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
const data = Buffer.concat(chunks);
|
|
649
|
-
|
|
650
|
-
// 安全解析 JSON,捕获解析错误
|
|
651
|
-
let message: F2AMessage;
|
|
652
|
-
try {
|
|
653
|
-
message = JSON.parse(data.toString());
|
|
654
|
-
} catch (parseError) {
|
|
655
|
-
this.logger.error('Failed to parse message JSON', {
|
|
656
|
-
error: parseError instanceof Error ? parseError.message : String(parseError),
|
|
657
|
-
dataSize: data.length,
|
|
658
|
-
peerId: connection.remotePeer.toString().slice(0, 16)
|
|
659
|
-
});
|
|
660
|
-
return;
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
const peerId = connection.remotePeer.toString();
|
|
664
|
-
|
|
665
|
-
// 处理消息
|
|
666
|
-
await this.handleMessage(message, peerId);
|
|
667
|
-
} catch (error) {
|
|
668
|
-
this.logger.error('Error handling message', { error });
|
|
669
|
-
}
|
|
670
|
-
});
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
/**
|
|
674
|
-
* 处理收到的消息
|
|
675
|
-
*/
|
|
676
|
-
private async handleMessage(message: F2AMessage, peerId: string): Promise<void> {
|
|
677
|
-
// 验证消息格式
|
|
678
|
-
const validation = validateF2AMessage(message);
|
|
679
|
-
if (!validation.success) {
|
|
680
|
-
this.logger.warn('Invalid message format', {
|
|
681
|
-
errors: validation.error.errors,
|
|
682
|
-
peerId: peerId.slice(0, 16)
|
|
683
|
-
});
|
|
684
|
-
return;
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
this.logger.info('Received message', { type: message.type, peerId: peerId.slice(0, 16) });
|
|
688
|
-
|
|
689
|
-
// 更新最后活跃时间
|
|
690
|
-
const peerInfo = this.peerTable.get(peerId);
|
|
691
|
-
if (peerInfo) {
|
|
692
|
-
peerInfo.lastSeen = Date.now();
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
// 处理加密消息
|
|
696
|
-
const decryptResult = await this.handleEncryptedMessage(message, peerId);
|
|
697
|
-
if (decryptResult.action === 'return') {
|
|
698
|
-
return;
|
|
699
|
-
}
|
|
700
|
-
message = decryptResult.message;
|
|
701
|
-
|
|
702
|
-
// 执行中间件链
|
|
703
|
-
const middlewareResult = await this.middlewareManager.execute({
|
|
704
|
-
message,
|
|
705
|
-
peerId,
|
|
706
|
-
agentInfo: peerInfo?.agentInfo,
|
|
707
|
-
metadata: new Map()
|
|
708
|
-
});
|
|
709
|
-
|
|
710
|
-
if (middlewareResult.action === 'drop') {
|
|
711
|
-
this.logger.info('Message dropped by middleware', {
|
|
712
|
-
reason: middlewareResult.reason,
|
|
713
|
-
peerId: peerId.slice(0, 16)
|
|
714
|
-
});
|
|
715
|
-
return;
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
// 使用可能被中间件修改后的消息
|
|
719
|
-
message = middlewareResult.context.message;
|
|
720
|
-
|
|
721
|
-
// 根据消息类型分发处理
|
|
722
|
-
await this.dispatchMessage(message, peerId);
|
|
723
|
-
|
|
724
|
-
// 转发给上层处理
|
|
725
|
-
this.emit('message:received', message, peerId);
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
/**
|
|
729
|
-
* 处理加密消息
|
|
730
|
-
* @returns 处理结果,包含是否继续处理和解密后的消息
|
|
731
|
-
*/
|
|
732
|
-
private async handleEncryptedMessage(message: F2AMessage, peerId: string): Promise<DecryptResult> {
|
|
733
|
-
if (!isEncryptedMessage(message)) {
|
|
734
|
-
return { action: 'continue', message };
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
const encryptedPayload = message.payload;
|
|
738
|
-
const decrypted = this.e2eeCrypto.decrypt(encryptedPayload);
|
|
739
|
-
|
|
740
|
-
if (decrypted) {
|
|
741
|
-
try {
|
|
742
|
-
const decryptedMessage = JSON.parse(decrypted);
|
|
743
|
-
|
|
744
|
-
// 安全验证:验证解密后的消息发送方身份
|
|
745
|
-
if (encryptedPayload.senderPublicKey) {
|
|
746
|
-
const verificationResult = this.verifySenderIdentity(
|
|
747
|
-
decryptedMessage,
|
|
748
|
-
peerId,
|
|
749
|
-
encryptedPayload.senderPublicKey
|
|
750
|
-
);
|
|
751
|
-
if (!verificationResult.valid) {
|
|
752
|
-
return { action: 'return', message };
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
return { action: 'continue', message: decryptedMessage };
|
|
757
|
-
} catch (error) {
|
|
758
|
-
this.logger.error('Failed to parse decrypted message', { error });
|
|
759
|
-
return { action: 'return', message };
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
// 解密失败,通知发送方
|
|
764
|
-
await this.sendDecryptFailureResponse(message.id, peerId);
|
|
765
|
-
return { action: 'return', message };
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
/**
|
|
769
|
-
* 验证发送方身份
|
|
770
|
-
*/
|
|
771
|
-
private verifySenderIdentity(
|
|
772
|
-
message: F2AMessage,
|
|
773
|
-
peerId: string,
|
|
774
|
-
senderPublicKey: string
|
|
775
|
-
): { valid: boolean } {
|
|
776
|
-
// 验证发送方公钥是否已注册且属于该 peerId
|
|
777
|
-
const registeredKey = this.e2eeCrypto.getPeerPublicKey(peerId);
|
|
778
|
-
if (registeredKey && registeredKey !== senderPublicKey) {
|
|
779
|
-
this.logger.error('Sender identity verification failed: public key mismatch', {
|
|
780
|
-
peerId: peerId.slice(0, 16),
|
|
781
|
-
claimedKey: senderPublicKey.slice(0, 16),
|
|
782
|
-
registeredKey: registeredKey.slice(0, 16)
|
|
783
|
-
});
|
|
784
|
-
return { valid: false };
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
// 如果发送方声称的身份与消息来源不匹配,拒绝处理
|
|
788
|
-
if (message.from && message.from !== peerId) {
|
|
789
|
-
this.logger.error('Sender identity verification failed: from field mismatch', {
|
|
790
|
-
claimedFrom: message.from?.slice(0, 16),
|
|
791
|
-
actualPeerId: peerId.slice(0, 16)
|
|
792
|
-
});
|
|
793
|
-
return { valid: false };
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
return { valid: true };
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
/**
|
|
800
|
-
* 发送解密失败响应
|
|
801
|
-
*/
|
|
802
|
-
private async sendDecryptFailureResponse(originalMessageId: string, peerId: string): Promise<void> {
|
|
803
|
-
this.logger.error('Failed to decrypt message', { peerId: peerId.slice(0, 16) });
|
|
804
|
-
|
|
805
|
-
const decryptFailResponse: F2AMessage = {
|
|
806
|
-
id: randomUUID(),
|
|
807
|
-
type: 'DECRYPT_FAILED',
|
|
808
|
-
from: this.agentInfo.peerId,
|
|
809
|
-
to: peerId,
|
|
810
|
-
timestamp: Date.now(),
|
|
811
|
-
payload: {
|
|
812
|
-
originalMessageId,
|
|
813
|
-
error: 'DECRYPTION_FAILED',
|
|
814
|
-
message: 'Unable to decrypt message. Key exchange may be incomplete or keys mismatched.'
|
|
815
|
-
}
|
|
816
|
-
};
|
|
817
|
-
|
|
818
|
-
try {
|
|
819
|
-
await this.sendMessage(peerId, decryptFailResponse, false);
|
|
820
|
-
} catch (sendError) {
|
|
821
|
-
this.logger.error('Failed to send decrypt failure response', {
|
|
822
|
-
peerId: peerId.slice(0, 16),
|
|
823
|
-
error: sendError
|
|
824
|
-
});
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
/**
|
|
829
|
-
* 根据消息类型分发处理
|
|
830
|
-
*/
|
|
831
|
-
private async dispatchMessage(message: F2AMessage, peerId: string): Promise<void> {
|
|
832
|
-
switch (message.type) {
|
|
833
|
-
case 'DISCOVER':
|
|
834
|
-
await this.handleDiscoverMessage(message, peerId, true);
|
|
835
|
-
break;
|
|
836
|
-
|
|
837
|
-
case 'DISCOVER_RESP':
|
|
838
|
-
await this.handleDiscoverMessage(message, peerId, false);
|
|
839
|
-
break;
|
|
840
|
-
|
|
841
|
-
case 'CAPABILITY_QUERY':
|
|
842
|
-
await this.handleCapabilityQueryMessage(message, peerId);
|
|
843
|
-
break;
|
|
844
|
-
|
|
845
|
-
case 'CAPABILITY_RESPONSE':
|
|
846
|
-
await this.handleCapabilityResponseMessage(message, peerId);
|
|
847
|
-
break;
|
|
848
|
-
|
|
849
|
-
case 'TASK_RESPONSE':
|
|
850
|
-
await this.handleTaskResponseMessage(message);
|
|
851
|
-
break;
|
|
852
|
-
|
|
853
|
-
case 'DECRYPT_FAILED':
|
|
854
|
-
await this.handleDecryptFailedMessage(message, peerId);
|
|
855
|
-
break;
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
/**
|
|
860
|
-
* 处理发现消息
|
|
861
|
-
*/
|
|
862
|
-
private async handleDiscoverMessage(message: F2AMessage, peerId: string, shouldRespond: boolean): Promise<void> {
|
|
863
|
-
const payload = message.payload as DiscoverPayload;
|
|
864
|
-
await this.handleDiscover(payload.agentInfo, peerId, shouldRespond);
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
/**
|
|
868
|
-
* 处理能力响应消息
|
|
869
|
-
*/
|
|
870
|
-
private async handleCapabilityResponseMessage(message: F2AMessage, peerId: string): Promise<void> {
|
|
871
|
-
const payload = message.payload as CapabilityResponsePayload;
|
|
872
|
-
this.upsertPeerFromAgentInfo(payload.agentInfo, peerId);
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
/**
|
|
876
|
-
* 处理能力查询消息
|
|
877
|
-
*/
|
|
878
|
-
private async handleCapabilityQueryMessage(message: F2AMessage, peerId: string): Promise<void> {
|
|
879
|
-
const payload = message.payload as CapabilityQueryPayload;
|
|
880
|
-
await this.handleCapabilityQuery(payload, peerId);
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
/**
|
|
884
|
-
* 处理任务响应消息
|
|
885
|
-
*/
|
|
886
|
-
private async handleTaskResponseMessage(message: F2AMessage): Promise<void> {
|
|
887
|
-
const payloadValidation = validateTaskResponsePayload(message.payload);
|
|
888
|
-
if (!payloadValidation.success) {
|
|
889
|
-
this.logger.warn('Invalid task response payload', {
|
|
890
|
-
errors: payloadValidation.error.errors
|
|
891
|
-
});
|
|
892
|
-
return;
|
|
893
|
-
}
|
|
894
|
-
const payload = message.payload as TaskResponsePayload;
|
|
895
|
-
this.handleTaskResponse(payload);
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
/**
|
|
899
|
-
* 处理解密失败通知消息
|
|
900
|
-
*/
|
|
901
|
-
private async handleDecryptFailedMessage(message: F2AMessage, peerId: string): Promise<void> {
|
|
902
|
-
const { originalMessageId, error, message: errorMsg } = message.payload as {
|
|
903
|
-
originalMessageId: string;
|
|
904
|
-
error: string;
|
|
905
|
-
message: string;
|
|
906
|
-
};
|
|
907
|
-
|
|
908
|
-
this.logger.error('Received decrypt failure notification', {
|
|
909
|
-
peerId: peerId.slice(0, 16),
|
|
910
|
-
originalMessageId,
|
|
911
|
-
error,
|
|
912
|
-
message: errorMsg
|
|
913
|
-
});
|
|
914
|
-
|
|
915
|
-
// 尝试重新注册公钥以重新建立加密通道
|
|
916
|
-
const peerInfo = this.peerTable.get(peerId);
|
|
917
|
-
if (peerInfo?.agentInfo?.encryptionPublicKey) {
|
|
918
|
-
this.e2eeCrypto.registerPeerPublicKey(peerId, peerInfo.agentInfo.encryptionPublicKey);
|
|
919
|
-
this.logger.info('Re-registered encryption key after decrypt failure', {
|
|
920
|
-
peerId: peerId.slice(0, 16)
|
|
921
|
-
});
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
// 发出事件通知上层应用
|
|
925
|
-
this.emit('error', new Error(`Decrypt failed for message ${originalMessageId}: ${errorMsg}`));
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
/**
|
|
929
|
-
* 处理发现消息
|
|
930
|
-
*/
|
|
931
|
-
/**
|
|
932
|
-
* 处理发现消息
|
|
933
|
-
*/
|
|
934
|
-
private async handleDiscover(agentInfo: AgentInfo, peerId: string, shouldRespond: boolean): Promise<void> {
|
|
935
|
-
// 安全验证:确保 agentInfo.peerId 与发送方一致,防止伪造
|
|
936
|
-
if (agentInfo.peerId !== peerId) {
|
|
937
|
-
this.logger.warn('Discovery message rejected: peerId mismatch', {
|
|
938
|
-
claimedPeerId: agentInfo.peerId?.slice(0, 16),
|
|
939
|
-
actualPeerId: peerId.slice(0, 16)
|
|
940
|
-
});
|
|
941
|
-
return;
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
// 使用锁保护容量检查和创建操作的原子性
|
|
945
|
-
await this.peerTableLock.acquire();
|
|
946
|
-
try {
|
|
947
|
-
// 检查是否需要清理以腾出空间
|
|
948
|
-
if (!this.peerTable.has(peerId)) {
|
|
949
|
-
// 新 peer,需要检查容量
|
|
950
|
-
const highWatermark = Math.floor(PEER_TABLE_MAX_SIZE * PEER_TABLE_HIGH_WATERMARK);
|
|
951
|
-
if (this.peerTable.size >= highWatermark) {
|
|
952
|
-
// 接近容量上限,触发激进清理
|
|
953
|
-
this.cleanupStalePeersLocked(true);
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
if (this.peerTable.size >= PEER_TABLE_MAX_SIZE) {
|
|
957
|
-
// 清理后仍无空间,拒绝新 peer
|
|
958
|
-
this.logger.warn('Peer table full, rejecting new peer', {
|
|
959
|
-
peerId: peerId.slice(0, 16),
|
|
960
|
-
currentSize: this.peerTable.size,
|
|
961
|
-
maxSize: PEER_TABLE_MAX_SIZE
|
|
962
|
-
});
|
|
963
|
-
return;
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
// 更新路由表
|
|
968
|
-
const now = Date.now();
|
|
969
|
-
const existing = this.peerTable.get(peerId);
|
|
970
|
-
if (existing) {
|
|
971
|
-
this.peerTable.set(peerId, {
|
|
972
|
-
...existing,
|
|
973
|
-
agentInfo,
|
|
974
|
-
lastSeen: now,
|
|
975
|
-
multiaddrs: agentInfo.multiaddrs.map(ma => multiaddr(ma))
|
|
976
|
-
});
|
|
977
|
-
} else {
|
|
978
|
-
this.peerTable.set(peerId, {
|
|
979
|
-
peerId,
|
|
980
|
-
agentInfo,
|
|
981
|
-
multiaddrs: agentInfo.multiaddrs.map(ma => multiaddr(ma)),
|
|
982
|
-
connected: false,
|
|
983
|
-
reputation: 50,
|
|
984
|
-
lastSeen: now
|
|
985
|
-
});
|
|
986
|
-
}
|
|
987
|
-
} finally {
|
|
988
|
-
this.peerTableLock.release();
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
// 注册对等方的加密公钥
|
|
992
|
-
if (agentInfo.encryptionPublicKey) {
|
|
993
|
-
this.e2eeCrypto.registerPeerPublicKey(peerId, agentInfo.encryptionPublicKey);
|
|
994
|
-
this.logger.info('Registered encryption key', { peerId: peerId.slice(0, 16) });
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
// 仅对 DISCOVER 请求响应,避免发现响应循环
|
|
998
|
-
if (shouldRespond) {
|
|
999
|
-
const responseResult = await this.sendMessage(peerId, {
|
|
1000
|
-
id: randomUUID(),
|
|
1001
|
-
type: 'DISCOVER_RESP',
|
|
1002
|
-
from: this.agentInfo.peerId,
|
|
1003
|
-
to: peerId,
|
|
1004
|
-
timestamp: Date.now(),
|
|
1005
|
-
payload: { agentInfo: this.agentInfo } as DiscoverPayload
|
|
1006
|
-
});
|
|
1007
|
-
|
|
1008
|
-
if (!responseResult.success) {
|
|
1009
|
-
this.logger.warn('Failed to send discover response', {
|
|
1010
|
-
peerId: peerId.slice(0, 16),
|
|
1011
|
-
error: responseResult.error
|
|
1012
|
-
});
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
this.emit('peer:discovered', {
|
|
1017
|
-
peerId,
|
|
1018
|
-
agentInfo,
|
|
1019
|
-
multiaddrs: agentInfo.multiaddrs.map(ma => multiaddr(ma))
|
|
1020
|
-
});
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
/**
|
|
1024
|
-
* 将发现到的 Agent 信息更新到 Peer 表
|
|
1025
|
-
*/
|
|
1026
|
-
private upsertPeerFromAgentInfo(agentInfo: AgentInfo, peerId: string): void {
|
|
1027
|
-
// 使用锁保护,确保线程安全
|
|
1028
|
-
this.peerTableLock.acquire().then(() => {
|
|
1029
|
-
try {
|
|
1030
|
-
// 检查是否需要清理以腾出空间
|
|
1031
|
-
if (this.peerTable.size >= PEER_TABLE_MAX_SIZE && !this.peerTable.has(peerId)) {
|
|
1032
|
-
this.cleanupStalePeersLocked(true);
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
const existing = this.peerTable.get(peerId);
|
|
1036
|
-
if (existing) {
|
|
1037
|
-
existing.agentInfo = agentInfo;
|
|
1038
|
-
existing.lastSeen = Date.now();
|
|
1039
|
-
existing.multiaddrs = agentInfo.multiaddrs.map(ma => multiaddr(ma));
|
|
1040
|
-
} else {
|
|
1041
|
-
this.peerTable.set(peerId, {
|
|
1042
|
-
peerId,
|
|
1043
|
-
agentInfo,
|
|
1044
|
-
multiaddrs: agentInfo.multiaddrs.map(ma => multiaddr(ma)),
|
|
1045
|
-
connected: false,
|
|
1046
|
-
reputation: 50,
|
|
1047
|
-
lastSeen: Date.now()
|
|
1048
|
-
});
|
|
1049
|
-
}
|
|
1050
|
-
} finally {
|
|
1051
|
-
this.peerTableLock.release();
|
|
1052
|
-
}
|
|
1053
|
-
});
|
|
1054
|
-
|
|
1055
|
-
// 注册对等方的加密公钥
|
|
1056
|
-
if (agentInfo.encryptionPublicKey) {
|
|
1057
|
-
this.e2eeCrypto.registerPeerPublicKey(peerId, agentInfo.encryptionPublicKey);
|
|
1058
|
-
this.logger.info('Registered encryption key', { peerId: peerId.slice(0, 16) });
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
/**
|
|
1063
|
-
* 处理能力查询
|
|
1064
|
-
*/
|
|
1065
|
-
private async handleCapabilityQuery(
|
|
1066
|
-
query: CapabilityQueryPayload,
|
|
1067
|
-
peerId: string
|
|
1068
|
-
): Promise<void> {
|
|
1069
|
-
// 检查是否匹配
|
|
1070
|
-
const matches = !query.capabilityName ||
|
|
1071
|
-
this.hasCapability(this.agentInfo, query.capabilityName);
|
|
1072
|
-
|
|
1073
|
-
if (matches) {
|
|
1074
|
-
// 发送能力响应
|
|
1075
|
-
await this.sendMessage(peerId, {
|
|
1076
|
-
id: randomUUID(),
|
|
1077
|
-
type: 'CAPABILITY_RESPONSE',
|
|
1078
|
-
from: this.agentInfo.peerId,
|
|
1079
|
-
to: peerId,
|
|
1080
|
-
timestamp: Date.now(),
|
|
1081
|
-
payload: { agentInfo: this.agentInfo } as CapabilityResponsePayload
|
|
1082
|
-
});
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
/**
|
|
1087
|
-
* 处理任务响应
|
|
1088
|
-
*/
|
|
1089
|
-
private handleTaskResponse(payload: TaskResponsePayload): void {
|
|
1090
|
-
const pending = this.pendingTasks.get(payload.taskId);
|
|
1091
|
-
if (!pending) {
|
|
1092
|
-
this.logger.warn('Received response for unknown task', { taskId: payload.taskId });
|
|
1093
|
-
return;
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
// 使用原子操作避免竞态条件:先删除 Map 条目,再检查 resolved 标志
|
|
1097
|
-
// 这确保即使多个响应并发到达,也只有一个能成功获取到 pending 条目
|
|
1098
|
-
this.pendingTasks.delete(payload.taskId);
|
|
1099
|
-
|
|
1100
|
-
if (pending.resolved) {
|
|
1101
|
-
this.logger.warn('Task already resolved, ignoring duplicate response', { taskId: payload.taskId });
|
|
1102
|
-
return;
|
|
1103
|
-
}
|
|
1104
|
-
pending.resolved = true;
|
|
1105
|
-
|
|
1106
|
-
// 清理资源
|
|
1107
|
-
clearTimeout(pending.timeout);
|
|
1108
|
-
|
|
1109
|
-
// 处理结果
|
|
1110
|
-
if (payload.status === 'success') {
|
|
1111
|
-
pending.resolve(payload.result);
|
|
1112
|
-
} else {
|
|
1113
|
-
pending.reject(payload.error || 'Task failed');
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
/**
|
|
1118
|
-
* 连接引导节点
|
|
1119
|
-
*/
|
|
1120
|
-
private async connectToBootstrapPeers(peers: string[]): Promise<void> {
|
|
1121
|
-
if (!this.node) return;
|
|
1122
|
-
|
|
1123
|
-
for (const addr of peers) {
|
|
1124
|
-
try {
|
|
1125
|
-
const ma = multiaddr(addr);
|
|
1126
|
-
await this.node.dial(ma);
|
|
1127
|
-
this.logger.info('Connected to bootstrap', { addr });
|
|
1128
|
-
} catch (error) {
|
|
1129
|
-
this.logger.warn('Failed to connect to bootstrap', { addr });
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
/**
|
|
1135
|
-
* 启动定期发现广播
|
|
1136
|
-
*/
|
|
1137
|
-
private startDiscoveryBroadcast(): void {
|
|
1138
|
-
// 立即广播一次
|
|
1139
|
-
this.broadcastDiscovery();
|
|
1140
|
-
|
|
1141
|
-
// 每 30 秒广播一次
|
|
1142
|
-
this.discoveryInterval = setInterval(() => {
|
|
1143
|
-
this.broadcastDiscovery();
|
|
1144
|
-
}, 30000);
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
/**
|
|
1148
|
-
* 启动定期清理任务
|
|
1149
|
-
*/
|
|
1150
|
-
private startCleanupTask(): void {
|
|
1151
|
-
// 立即执行一次
|
|
1152
|
-
this.cleanupStalePeers();
|
|
1153
|
-
|
|
1154
|
-
// 每 5 分钟清理一次
|
|
1155
|
-
this.cleanupInterval = setInterval(() => {
|
|
1156
|
-
this.cleanupStalePeers();
|
|
1157
|
-
}, PEER_TABLE_CLEANUP_INTERVAL);
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
/**
|
|
1161
|
-
* 清理过期的 Peer 记录(带锁保护)
|
|
1162
|
-
* @param aggressive 是否使用激进清理策略(清理更多条目)
|
|
1163
|
-
*/
|
|
1164
|
-
private async cleanupStalePeers(aggressive = false): Promise<void> {
|
|
1165
|
-
await this.peerTableLock.acquire();
|
|
1166
|
-
try {
|
|
1167
|
-
this.cleanupStalePeersLocked(aggressive);
|
|
1168
|
-
} finally {
|
|
1169
|
-
this.peerTableLock.release();
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
/**
|
|
1174
|
-
* 清理过期的 Peer 记录(内部方法,调用前必须持有锁)
|
|
1175
|
-
* @param aggressive 是否使用激进清理策略(清理更多条目)
|
|
1176
|
-
*/
|
|
1177
|
-
private cleanupStalePeersLocked(aggressive = false): void {
|
|
1178
|
-
const now = Date.now();
|
|
1179
|
-
const threshold = aggressive ? 0 : PEER_STALE_THRESHOLD;
|
|
1180
|
-
let cleaned = 0;
|
|
1181
|
-
|
|
1182
|
-
// 激进清理:清理更多类型的条目
|
|
1183
|
-
if (aggressive) {
|
|
1184
|
-
// 1. 清理所有未连接且超过 1 小时的 peer
|
|
1185
|
-
for (const [peerId, peer] of this.peerTable) {
|
|
1186
|
-
if (!peer.connected && now - peer.lastSeen > 60 * 60 * 1000) {
|
|
1187
|
-
this.peerTable.delete(peerId);
|
|
1188
|
-
cleaned++;
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
// 2. 如果仍然超过高水位线,按最后活跃时间排序后删除最旧的
|
|
1193
|
-
const highWatermark = Math.floor(PEER_TABLE_MAX_SIZE * PEER_TABLE_HIGH_WATERMARK);
|
|
1194
|
-
if (this.peerTable.size > highWatermark) {
|
|
1195
|
-
const targetSize = Math.floor(PEER_TABLE_MAX_SIZE * PEER_TABLE_AGGRESSIVE_CLEANUP_THRESHOLD);
|
|
1196
|
-
const sorted = Array.from(this.peerTable.entries())
|
|
1197
|
-
.sort((a, b) => a[1].lastSeen - b[1].lastSeen);
|
|
1198
|
-
|
|
1199
|
-
// 优先删除未连接的 peer
|
|
1200
|
-
const toRemove = sorted
|
|
1201
|
-
.filter(([_, peer]) => !peer.connected)
|
|
1202
|
-
.slice(0, this.peerTable.size - targetSize);
|
|
1203
|
-
|
|
1204
|
-
for (const [peerId] of toRemove) {
|
|
1205
|
-
this.peerTable.delete(peerId);
|
|
1206
|
-
cleaned++;
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
} else {
|
|
1210
|
-
// 常规清理:清理过期条目
|
|
1211
|
-
for (const [peerId, peer] of this.peerTable) {
|
|
1212
|
-
// 清理条件:长时间未活跃,或者未连接且超过一定时间
|
|
1213
|
-
const shouldClean =
|
|
1214
|
-
now - peer.lastSeen > threshold ||
|
|
1215
|
-
(!peer.connected && now - peer.lastSeen > 60 * 60 * 1000); // 未连接超过1小时
|
|
1216
|
-
|
|
1217
|
-
if (shouldClean) {
|
|
1218
|
-
this.peerTable.delete(peerId);
|
|
1219
|
-
cleaned++;
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
if (cleaned > 0) {
|
|
1225
|
-
this.logger.info('Cleaned up stale peers', { cleaned, remaining: this.peerTable.size, aggressive });
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
// 如果仍然超过最大容量,按最后活跃时间排序后删除最旧的
|
|
1229
|
-
if (this.peerTable.size > PEER_TABLE_MAX_SIZE) {
|
|
1230
|
-
const sorted = Array.from(this.peerTable.entries())
|
|
1231
|
-
.sort((a, b) => a[1].lastSeen - b[1].lastSeen);
|
|
1232
|
-
|
|
1233
|
-
// 优先删除未连接的 peer
|
|
1234
|
-
const disconnected = sorted.filter(([_, peer]) => !peer.connected);
|
|
1235
|
-
const toRemove = disconnected.length > 0
|
|
1236
|
-
? disconnected.slice(0, this.peerTable.size - PEER_TABLE_MAX_SIZE)
|
|
1237
|
-
: sorted.slice(0, this.peerTable.size - PEER_TABLE_MAX_SIZE);
|
|
1238
|
-
|
|
1239
|
-
for (const [peerId] of toRemove) {
|
|
1240
|
-
this.peerTable.delete(peerId);
|
|
1241
|
-
cleaned++;
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
this.logger.info('Removed oldest peers to maintain limit', { removed: toRemove.length });
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
/**
|
|
1249
|
-
* 原子操作:获取 peer 信息
|
|
1250
|
-
*/
|
|
1251
|
-
private getPeer(peerId: string): PeerInfo | undefined {
|
|
1252
|
-
return this.peerTable.get(peerId);
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
/**
|
|
1256
|
-
* 原子操作:设置 peer 信息
|
|
1257
|
-
*/
|
|
1258
|
-
private setPeer(peerId: string, info: PeerInfo): void {
|
|
1259
|
-
this.peerTable.set(peerId, info);
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
/**
|
|
1263
|
-
* 原子操作:更新 peer 信息(线程安全)
|
|
1264
|
-
* @param peerId Peer ID
|
|
1265
|
-
* @param updater 更新函数,接收当前值,返回新值
|
|
1266
|
-
* @returns 更新后的 peer 信息,如果 peer 不存在则返回 undefined
|
|
1267
|
-
*/
|
|
1268
|
-
private async updatePeer(
|
|
1269
|
-
peerId: string,
|
|
1270
|
-
updater: (peer: PeerInfo) => PeerInfo
|
|
1271
|
-
): Promise<PeerInfo | undefined> {
|
|
1272
|
-
await this.peerTableLock.acquire();
|
|
1273
|
-
try {
|
|
1274
|
-
const peer = this.peerTable.get(peerId);
|
|
1275
|
-
if (!peer) return undefined;
|
|
1276
|
-
const updated = updater(peer);
|
|
1277
|
-
this.peerTable.set(peerId, updated);
|
|
1278
|
-
return updated;
|
|
1279
|
-
} finally {
|
|
1280
|
-
this.peerTableLock.release();
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
/**
|
|
1285
|
-
* 原子操作:安全地更新或创建 peer
|
|
1286
|
-
* @param peerId Peer ID
|
|
1287
|
-
* @param creator 创建新 peer 的函数(如果不存在)
|
|
1288
|
-
* @param updater 更新函数(如果存在)
|
|
1289
|
-
*/
|
|
1290
|
-
private async upsertPeer(
|
|
1291
|
-
peerId: string,
|
|
1292
|
-
creator: () => PeerInfo,
|
|
1293
|
-
updater: (peer: PeerInfo) => PeerInfo
|
|
1294
|
-
): Promise<PeerInfo> {
|
|
1295
|
-
await this.peerTableLock.acquire();
|
|
1296
|
-
try {
|
|
1297
|
-
const existing = this.peerTable.get(peerId);
|
|
1298
|
-
if (existing) {
|
|
1299
|
-
const updated = updater(existing);
|
|
1300
|
-
this.peerTable.set(peerId, updated);
|
|
1301
|
-
return updated;
|
|
1302
|
-
} else {
|
|
1303
|
-
const created = creator();
|
|
1304
|
-
this.peerTable.set(peerId, created);
|
|
1305
|
-
return created;
|
|
1306
|
-
}
|
|
1307
|
-
} finally {
|
|
1308
|
-
this.peerTableLock.release();
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
|
|
1312
|
-
/**
|
|
1313
|
-
* 原子操作:删除 peer
|
|
1314
|
-
*/
|
|
1315
|
-
private async deletePeer(peerId: string): Promise<boolean> {
|
|
1316
|
-
await this.peerTableLock.acquire();
|
|
1317
|
-
try {
|
|
1318
|
-
return this.peerTable.delete(peerId);
|
|
1319
|
-
} finally {
|
|
1320
|
-
this.peerTableLock.release();
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
/**
|
|
1325
|
-
* 检查 Agent 是否有特定能力
|
|
1326
|
-
*/
|
|
1327
|
-
private hasCapability(agentInfo: AgentInfo, capabilityName: string): boolean {
|
|
1328
|
-
return agentInfo.capabilities.some(c => c.name === capabilityName);
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
/**
|
|
1332
|
-
* 获取已连接的 Peers
|
|
1333
|
-
*/
|
|
1334
|
-
getConnectedPeers(): PeerInfo[] {
|
|
1335
|
-
return Array.from(this.peerTable.values()).filter(p => p.connected);
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
/**
|
|
1339
|
-
* 获取所有已知的 Peers
|
|
1340
|
-
*/
|
|
1341
|
-
getAllPeers(): PeerInfo[] {
|
|
1342
|
-
return Array.from(this.peerTable.values());
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
/**
|
|
1346
|
-
* 获取节点 ID
|
|
1347
|
-
*/
|
|
1348
|
-
getPeerId(): string | null {
|
|
1349
|
-
return this.agentInfo.peerId;
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
/**
|
|
1353
|
-
* 获取 E2EE 加密公钥
|
|
1354
|
-
*/
|
|
1355
|
-
getEncryptionPublicKey(): string | null {
|
|
1356
|
-
return this.e2eeCrypto.getPublicKey();
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
/**
|
|
1360
|
-
* 获取已注册的加密对等方数量
|
|
1361
|
-
*/
|
|
1362
|
-
getEncryptedPeerCount(): number {
|
|
1363
|
-
return this.e2eeCrypto.getRegisteredPeerCount();
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
/**
|
|
1367
|
-
* 通过 DHT 查找节点 (全局发现)
|
|
1368
|
-
*/
|
|
1369
|
-
async findPeerViaDHT(peerId: string): Promise<Result<string[]>> {
|
|
1370
|
-
if (!this.node) {
|
|
1371
|
-
return failureFromError('NETWORK_NOT_STARTED', 'P2P network not started');
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
const dht = (this.node.services as Libp2pServices).dht;
|
|
1375
|
-
if (!dht) {
|
|
1376
|
-
return failureFromError('DHT_NOT_AVAILABLE', 'DHT service not enabled');
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
try {
|
|
1380
|
-
const peerIdObj = peerIdFromString(peerId);
|
|
1381
|
-
const peerInfo = await dht.findPeer(peerIdObj);
|
|
1382
|
-
|
|
1383
|
-
if (peerInfo && peerInfo.multiaddrs.length > 0) {
|
|
1384
|
-
return success(peerInfo.multiaddrs.map(ma => ma.toString()));
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
|
-
return failureFromError('PEER_NOT_FOUND', `Peer ${peerId} not found in DHT`);
|
|
1388
|
-
} catch (error) {
|
|
1389
|
-
return failureFromError('DHT_LOOKUP_FAILED', 'DHT lookup failed', error as Error);
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
/**
|
|
1394
|
-
* 获取 DHT 路由表大小
|
|
1395
|
-
*/
|
|
1396
|
-
getDHTPeerCount(): number {
|
|
1397
|
-
const dht = (this.node?.services as Libp2pServices)?.dht;
|
|
1398
|
-
return dht?.routingTable?.size || 0;
|
|
1399
|
-
}
|
|
1400
|
-
|
|
1401
|
-
/**
|
|
1402
|
-
* 检查 DHT 是否启用
|
|
1403
|
-
*/
|
|
1404
|
-
isDHTEnabled(): boolean {
|
|
1405
|
-
return !!(this.node?.services as Libp2pServices)?.dht;
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
/**
|
|
1409
|
-
* 注册中间件
|
|
1410
|
-
* @param middleware 中间件实例
|
|
1411
|
-
*/
|
|
1412
|
-
useMiddleware(middleware: Middleware): void {
|
|
1413
|
-
this.middlewareManager.use(middleware);
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
/**
|
|
1417
|
-
* 移除中间件
|
|
1418
|
-
* @param name 中间件名称
|
|
1419
|
-
* @returns 是否成功移除
|
|
1420
|
-
*/
|
|
1421
|
-
removeMiddleware(name: string): boolean {
|
|
1422
|
-
return this.middlewareManager.remove(name);
|
|
1423
|
-
}
|
|
1424
|
-
|
|
1425
|
-
/**
|
|
1426
|
-
* 获取已注册的中间件列表
|
|
1427
|
-
* @returns 中间件名称列表
|
|
1428
|
-
*/
|
|
1429
|
-
listMiddlewares(): string[] {
|
|
1430
|
-
return this.middlewareManager.list();
|
|
1431
|
-
}
|
|
1432
|
-
}
|