@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
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { ReputationSystem } from './reputation';
|
|
3
|
-
import { ReputationConfig } from './types';
|
|
4
|
-
|
|
5
|
-
// Mock fs - 使用 factory 函数返回 mock 函数
|
|
6
|
-
vi.mock('fs', () => {
|
|
7
|
-
return {
|
|
8
|
-
existsSync: vi.fn(),
|
|
9
|
-
readFileSync: vi.fn(),
|
|
10
|
-
writeFileSync: vi.fn(),
|
|
11
|
-
mkdirSync: vi.fn(),
|
|
12
|
-
renameSync: vi.fn(),
|
|
13
|
-
unlinkSync: vi.fn(),
|
|
14
|
-
};
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
// Mock path
|
|
18
|
-
vi.mock('path', () => ({
|
|
19
|
-
join: vi.fn((...args) => args.join('/')),
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
|
-
describe('ReputationSystem', () => {
|
|
23
|
-
let system: ReputationSystem;
|
|
24
|
-
const mockConfig: ReputationConfig = {
|
|
25
|
-
initialScore: 50,
|
|
26
|
-
decayFactor: 0.95,
|
|
27
|
-
historyLimit: 10,
|
|
28
|
-
minScoreForDelegation: 30,
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
beforeEach(async () => {
|
|
32
|
-
vi.clearAllMocks();
|
|
33
|
-
vi.useFakeTimers();
|
|
34
|
-
const { existsSync } = await import('fs');
|
|
35
|
-
vi.mocked(existsSync).mockReturnValue(false);
|
|
36
|
-
system = new ReputationSystem(mockConfig, './test-data');
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
afterEach(() => {
|
|
40
|
-
vi.useRealTimers();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe('getReputation', () => {
|
|
44
|
-
it('should return default reputation for new peer', () => {
|
|
45
|
-
const rep = system.getReputation('peer-1');
|
|
46
|
-
expect(rep.peerId).toBe('peer-1');
|
|
47
|
-
expect(rep.score).toBe(50);
|
|
48
|
-
expect(rep.totalTasks).toBe(0);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('should return same reputation for existing peer', () => {
|
|
52
|
-
const rep1 = system.getReputation('peer-1');
|
|
53
|
-
const rep2 = system.getReputation('peer-1');
|
|
54
|
-
expect(rep1).toBe(rep2);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
describe('recordSuccess', () => {
|
|
59
|
-
it('should increase score on success', () => {
|
|
60
|
-
system.recordSuccess('peer-1', 'task-1', 100);
|
|
61
|
-
const rep = system.getReputation('peer-1');
|
|
62
|
-
expect(rep.score).toBe(60);
|
|
63
|
-
expect(rep.successfulTasks).toBe(1);
|
|
64
|
-
expect(rep.totalTasks).toBe(1);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('should update average response time using EMA', () => {
|
|
68
|
-
system.recordSuccess('peer-1', 'task-1', 100);
|
|
69
|
-
system.recordSuccess('peer-1', 'task-2', 200);
|
|
70
|
-
const rep = system.getReputation('peer-1');
|
|
71
|
-
// EMA: 100 * 0.7 + 200 * 0.3 = 70 + 60 = 130
|
|
72
|
-
expect(rep.avgResponseTime).toBe(130);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('should cap score at 100', () => {
|
|
76
|
-
for (let i = 0; i < 10; i++) {
|
|
77
|
-
system.recordSuccess('peer-1', `task-${i}`, 100);
|
|
78
|
-
}
|
|
79
|
-
const rep = system.getReputation('peer-1');
|
|
80
|
-
expect(rep.score).toBe(100);
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
describe('recordFailure', () => {
|
|
85
|
-
it('should decrease score on failure', () => {
|
|
86
|
-
system.recordFailure('peer-1', 'task-1', 'timeout');
|
|
87
|
-
const rep = system.getReputation('peer-1');
|
|
88
|
-
expect(rep.score).toBe(30);
|
|
89
|
-
expect(rep.failedTasks).toBe(1);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('should not go below 0', () => {
|
|
93
|
-
system.recordFailure('peer-1', 'task-1');
|
|
94
|
-
system.recordFailure('peer-1', 'task-2');
|
|
95
|
-
system.recordFailure('peer-1', 'task-3');
|
|
96
|
-
const rep = system.getReputation('peer-1');
|
|
97
|
-
expect(rep.score).toBe(0);
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
describe('recordRejection', () => {
|
|
102
|
-
it('should decrease score on rejection', () => {
|
|
103
|
-
system.recordRejection('peer-1', 'task-1', 'busy');
|
|
104
|
-
const rep = system.getReputation('peer-1');
|
|
105
|
-
expect(rep.score).toBe(45);
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
describe('isAllowed', () => {
|
|
110
|
-
it('should return true for allowed peer', () => {
|
|
111
|
-
system.recordSuccess('peer-1', 'task-1', 100);
|
|
112
|
-
system.recordSuccess('peer-1', 'task-2', 100);
|
|
113
|
-
system.recordSuccess('peer-1', 'task-3', 100);
|
|
114
|
-
expect(system.isAllowed('peer-1')).toBe(true);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('should return false for disallowed peer when enabled', () => {
|
|
118
|
-
// Create a system with enabled reputation check
|
|
119
|
-
// enabled: true - 启用信誉检查,低于 minScoreForService 的节点将被拒绝
|
|
120
|
-
// minScoreForService: 35 - 服务最低信誉门槛(默认50分,失败一次扣20分,所以30分会低于35)
|
|
121
|
-
const strictConfig = { ...mockConfig, enabled: true, minScoreForService: 35 };
|
|
122
|
-
const strictSystem = new ReputationSystem(strictConfig, './test-data');
|
|
123
|
-
strictSystem.recordFailure('peer-1', 'task-1'); // score goes from 50 to 30
|
|
124
|
-
expect(strictSystem.isAllowed('peer-1')).toBe(false);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
describe('getAllReputations', () => {
|
|
129
|
-
it('should return all reputations sorted by score', () => {
|
|
130
|
-
system.recordSuccess('peer-a', 'task-1', 100);
|
|
131
|
-
system.recordSuccess('peer-a', 'task-2', 100);
|
|
132
|
-
system.recordSuccess('peer-b', 'task-1', 100);
|
|
133
|
-
|
|
134
|
-
const allReps = system.getAllReputations();
|
|
135
|
-
expect(allReps[0].peerId).toBe('peer-a');
|
|
136
|
-
expect(allReps[0].score).toBe(70);
|
|
137
|
-
expect(allReps[1].peerId).toBe('peer-b');
|
|
138
|
-
expect(allReps[1].score).toBe(60);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('should limit results when specified', () => {
|
|
142
|
-
system.recordSuccess('peer-a', 'task-1', 100);
|
|
143
|
-
system.recordSuccess('peer-b', 'task-1', 100);
|
|
144
|
-
system.recordSuccess('peer-c', 'task-1', 100);
|
|
145
|
-
|
|
146
|
-
const allReps = system.getAllReputations();
|
|
147
|
-
// getAllReputations doesn't have limit param, so we get all
|
|
148
|
-
expect(allReps.length).toBe(3);
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
// P0 修复:测试防抖保存功能
|
|
153
|
-
describe('debounced save', () => {
|
|
154
|
-
it('should debounce multiple save calls', async () => {
|
|
155
|
-
const { writeFileSync, renameSync } = await import('fs');
|
|
156
|
-
|
|
157
|
-
// 连续调用多次 recordSuccess,每个都会触发 save
|
|
158
|
-
system.recordSuccess('peer-1', 'task-1', 100);
|
|
159
|
-
system.recordSuccess('peer-1', 'task-2', 100);
|
|
160
|
-
system.recordSuccess('peer-1', 'task-3', 100);
|
|
161
|
-
|
|
162
|
-
// 此时应该还没有写入(防抖延迟内)
|
|
163
|
-
expect(writeFileSync).not.toHaveBeenCalled();
|
|
164
|
-
|
|
165
|
-
// 等待足够长的时间让定时器执行
|
|
166
|
-
vi.advanceTimersByTime(200);
|
|
167
|
-
|
|
168
|
-
// 执行 setImmediate 回调
|
|
169
|
-
await vi.runAllTimersAsync();
|
|
170
|
-
|
|
171
|
-
// 应该有写入调用(防抖将多次合并为一次或多次,取决于实现)
|
|
172
|
-
expect(writeFileSync).toHaveBeenCalled();
|
|
173
|
-
expect(renameSync).toHaveBeenCalled();
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it('should use async write to avoid blocking', async () => {
|
|
177
|
-
const { writeFileSync } = await import('fs');
|
|
178
|
-
|
|
179
|
-
system.recordSuccess('peer-1', 'task-1', 100);
|
|
180
|
-
|
|
181
|
-
// 在 timer 执行前,writeFileSync 不应该被调用(因为是 setImmediate)
|
|
182
|
-
expect(writeFileSync).not.toHaveBeenCalled();
|
|
183
|
-
|
|
184
|
-
// 等待足够时间
|
|
185
|
-
vi.advanceTimersByTime(200);
|
|
186
|
-
await vi.runAllTimersAsync();
|
|
187
|
-
|
|
188
|
-
// 现在应该被调用了
|
|
189
|
-
expect(writeFileSync).toHaveBeenCalled();
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
describe('flush', () => {
|
|
194
|
-
it('should immediately save pending data', async () => {
|
|
195
|
-
const { writeFileSync, renameSync } = await import('fs');
|
|
196
|
-
|
|
197
|
-
// 调用 recordSuccess 触发 save(设置 savePending = true)
|
|
198
|
-
system.recordSuccess('peer-1', 'task-1', 100);
|
|
199
|
-
|
|
200
|
-
// 不等待任何 timer,直接调用 flush
|
|
201
|
-
// flush 应该立即同步保存待保存的数据
|
|
202
|
-
system.flush();
|
|
203
|
-
|
|
204
|
-
// flush 是同步的,应该立即调用 writeFileSync
|
|
205
|
-
expect(writeFileSync).toHaveBeenCalled();
|
|
206
|
-
expect(renameSync).toHaveBeenCalled();
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
it('should not save if no pending data', async () => {
|
|
210
|
-
const { writeFileSync } = await import('fs');
|
|
211
|
-
|
|
212
|
-
// 创建一个新的 system,没有任何操作
|
|
213
|
-
const freshSystem = new ReputationSystem(mockConfig, './test-data-fresh');
|
|
214
|
-
|
|
215
|
-
// 没有待保存的数据,flush 不应该触发写入
|
|
216
|
-
freshSystem.flush();
|
|
217
|
-
|
|
218
|
-
expect(writeFileSync).not.toHaveBeenCalled();
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
});
|
|
@@ -1,368 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 信誉系统
|
|
3
|
-
* 管理 Peer 的信誉分数
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { existsSync, readFileSync, writeFileSync, renameSync, unlinkSync } from 'fs';
|
|
7
|
-
import { join } from 'path';
|
|
8
|
-
import type {
|
|
9
|
-
ReputationEntry,
|
|
10
|
-
ReputationEvent,
|
|
11
|
-
ReputationConfig,
|
|
12
|
-
TaskResponse
|
|
13
|
-
} from './types.js';
|
|
14
|
-
import { pluginLogger as logger } from './logger.js';
|
|
15
|
-
|
|
16
|
-
/** 防抖保存配置 */
|
|
17
|
-
interface DebounceConfig {
|
|
18
|
-
/** 防抖延迟时间(毫秒) */
|
|
19
|
-
delayMs: number;
|
|
20
|
-
/** 最大等待时间(毫秒) */
|
|
21
|
-
maxWaitMs: number;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export class ReputationSystem {
|
|
25
|
-
private config: ReputationConfig;
|
|
26
|
-
private entries: Map<string, ReputationEntry> = new Map();
|
|
27
|
-
private dataPath: string;
|
|
28
|
-
|
|
29
|
-
// 防抖写入相关
|
|
30
|
-
private savePending: boolean = false;
|
|
31
|
-
private saveTimer?: NodeJS.Timeout;
|
|
32
|
-
private lastSaveTime: number = Date.now(); // 初始化为当前时间,避免首次调用立即触发
|
|
33
|
-
private debounceConfig: DebounceConfig = {
|
|
34
|
-
delayMs: 100, // 100ms 防抖延迟
|
|
35
|
-
maxWaitMs: 1000 // 最多等待 1 秒
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
constructor(config: ReputationConfig, dataDir: string) {
|
|
39
|
-
this.config = config;
|
|
40
|
-
this.dataPath = join(dataDir, 'reputation.json');
|
|
41
|
-
this.load();
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* 获取 Peer 信誉
|
|
46
|
-
*/
|
|
47
|
-
getReputation(peerId: string): ReputationEntry {
|
|
48
|
-
if (!this.entries.has(peerId)) {
|
|
49
|
-
this.entries.set(peerId, this.createDefaultEntry(peerId));
|
|
50
|
-
}
|
|
51
|
-
return this.entries.get(peerId)!;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* 记录任务成功
|
|
56
|
-
*/
|
|
57
|
-
recordSuccess(peerId: string, taskId: string, latency: number): void {
|
|
58
|
-
const entry = this.getReputation(peerId);
|
|
59
|
-
|
|
60
|
-
entry.successfulTasks++;
|
|
61
|
-
entry.totalTasks++;
|
|
62
|
-
entry.score = Math.min(100, entry.score + 10);
|
|
63
|
-
entry.avgResponseTime = this.updateAvgResponseTime(entry, latency);
|
|
64
|
-
entry.lastInteraction = Date.now();
|
|
65
|
-
|
|
66
|
-
entry.history.push({
|
|
67
|
-
type: 'task_success',
|
|
68
|
-
taskId,
|
|
69
|
-
delta: +10,
|
|
70
|
-
timestamp: Date.now()
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
this.trimHistory(entry);
|
|
74
|
-
this.save();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* 记录任务失败
|
|
79
|
-
*/
|
|
80
|
-
recordFailure(peerId: string, taskId: string, reason?: string): void {
|
|
81
|
-
const entry = this.getReputation(peerId);
|
|
82
|
-
|
|
83
|
-
entry.failedTasks++;
|
|
84
|
-
entry.totalTasks++;
|
|
85
|
-
entry.score = Math.max(0, entry.score - 20);
|
|
86
|
-
entry.lastInteraction = Date.now();
|
|
87
|
-
|
|
88
|
-
entry.history.push({
|
|
89
|
-
type: 'task_failure',
|
|
90
|
-
taskId,
|
|
91
|
-
delta: -20,
|
|
92
|
-
timestamp: Date.now(),
|
|
93
|
-
reason
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
this.trimHistory(entry);
|
|
97
|
-
this.save();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* 记录任务拒绝
|
|
102
|
-
*/
|
|
103
|
-
recordRejection(peerId: string, taskId: string, reason?: string): void {
|
|
104
|
-
const entry = this.getReputation(peerId);
|
|
105
|
-
|
|
106
|
-
entry.totalTasks++;
|
|
107
|
-
entry.score = Math.max(0, entry.score - 5);
|
|
108
|
-
entry.lastInteraction = Date.now();
|
|
109
|
-
|
|
110
|
-
entry.history.push({
|
|
111
|
-
type: 'task_rejected',
|
|
112
|
-
taskId,
|
|
113
|
-
delta: -5,
|
|
114
|
-
timestamp: Date.now(),
|
|
115
|
-
reason
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
this.trimHistory(entry);
|
|
119
|
-
this.save();
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* 记录超时
|
|
124
|
-
*/
|
|
125
|
-
recordTimeout(peerId: string, taskId: string): void {
|
|
126
|
-
const entry = this.getReputation(peerId);
|
|
127
|
-
|
|
128
|
-
entry.totalTasks++;
|
|
129
|
-
entry.score = Math.max(0, entry.score - 15);
|
|
130
|
-
entry.lastInteraction = Date.now();
|
|
131
|
-
|
|
132
|
-
entry.history.push({
|
|
133
|
-
type: 'timeout',
|
|
134
|
-
taskId,
|
|
135
|
-
delta: -15,
|
|
136
|
-
timestamp: Date.now()
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
this.trimHistory(entry);
|
|
140
|
-
this.save();
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* 记录恶意行为
|
|
145
|
-
*/
|
|
146
|
-
recordMalicious(peerId: string, reason: string): void {
|
|
147
|
-
const entry = this.getReputation(peerId);
|
|
148
|
-
|
|
149
|
-
entry.score = Math.max(0, entry.score - 50);
|
|
150
|
-
entry.lastInteraction = Date.now();
|
|
151
|
-
|
|
152
|
-
entry.history.push({
|
|
153
|
-
type: 'malicious',
|
|
154
|
-
delta: -50,
|
|
155
|
-
timestamp: Date.now(),
|
|
156
|
-
reason
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
this.trimHistory(entry);
|
|
160
|
-
this.save();
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* 检查是否允许服务
|
|
165
|
-
*/
|
|
166
|
-
isAllowed(peerId: string): boolean {
|
|
167
|
-
if (!this.config.enabled) return true;
|
|
168
|
-
|
|
169
|
-
const entry = this.getReputation(peerId);
|
|
170
|
-
return entry.score >= this.config.minScoreForService;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* 获取所有信誉记录
|
|
175
|
-
*/
|
|
176
|
-
getAllReputations(): ReputationEntry[] {
|
|
177
|
-
return Array.from(this.entries.values());
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* 清理过期记录
|
|
182
|
-
*/
|
|
183
|
-
cleanup(maxAgeDays: number = 30): void {
|
|
184
|
-
const cutoff = Date.now() - (maxAgeDays * 24 * 60 * 60 * 1000);
|
|
185
|
-
|
|
186
|
-
for (const [peerId, entry] of this.entries) {
|
|
187
|
-
if (entry.lastInteraction < cutoff && entry.totalTasks === 0) {
|
|
188
|
-
this.entries.delete(peerId);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
this.save();
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* 获取高信誉节点
|
|
197
|
-
*/
|
|
198
|
-
getHighReputationNodes(minScore: number): ReputationEntry[] {
|
|
199
|
-
return Array.from(this.entries.values())
|
|
200
|
-
.filter(entry => entry.score >= minScore);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* 创建默认条目
|
|
205
|
-
*/
|
|
206
|
-
private createDefaultEntry(peerId: string): ReputationEntry {
|
|
207
|
-
return {
|
|
208
|
-
peerId,
|
|
209
|
-
score: this.config.initialScore,
|
|
210
|
-
successfulTasks: 0,
|
|
211
|
-
failedTasks: 0,
|
|
212
|
-
totalTasks: 0,
|
|
213
|
-
avgResponseTime: 0,
|
|
214
|
-
lastInteraction: 0,
|
|
215
|
-
history: []
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* 更新平均响应时间
|
|
221
|
-
*/
|
|
222
|
-
private updateAvgResponseTime(entry: ReputationEntry, newLatency: number): number {
|
|
223
|
-
if (entry.avgResponseTime === 0) {
|
|
224
|
-
return newLatency;
|
|
225
|
-
}
|
|
226
|
-
// 指数移动平均
|
|
227
|
-
return entry.avgResponseTime * 0.7 + newLatency * 0.3;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* 修剪历史记录
|
|
232
|
-
*/
|
|
233
|
-
private trimHistory(entry: ReputationEntry, maxSize: number = 100): void {
|
|
234
|
-
if (entry.history.length > maxSize) {
|
|
235
|
-
entry.history = entry.history.slice(-maxSize);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* 加载数据
|
|
241
|
-
*/
|
|
242
|
-
private load(): void {
|
|
243
|
-
if (existsSync(this.dataPath)) {
|
|
244
|
-
try {
|
|
245
|
-
const data = JSON.parse(readFileSync(this.dataPath, 'utf-8'));
|
|
246
|
-
for (const entry of data) {
|
|
247
|
-
this.entries.set(entry.peerId, entry);
|
|
248
|
-
}
|
|
249
|
-
logger.info('加载了 %d 条信誉记录', this.entries.size);
|
|
250
|
-
} catch (e) {
|
|
251
|
-
logger.error('加载信誉数据失败: error=%s', e);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* 保存数据(防抖 + 异步写入)
|
|
258
|
-
*
|
|
259
|
-
* P0 修复:使用防抖机制避免高并发下的阻塞问题
|
|
260
|
-
* - 短时间内多次调用只触发一次实际写入
|
|
261
|
-
* - 使用异步写入避免阻塞主线程
|
|
262
|
-
* - 原子写入保证数据完整性
|
|
263
|
-
*/
|
|
264
|
-
private save(): void {
|
|
265
|
-
// 标记有待保存的数据
|
|
266
|
-
this.savePending = true;
|
|
267
|
-
|
|
268
|
-
// 如果已有定时器,先清除
|
|
269
|
-
if (this.saveTimer) {
|
|
270
|
-
clearTimeout(this.saveTimer);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const now = Date.now();
|
|
274
|
-
const timeSinceLastSave = now - this.lastSaveTime;
|
|
275
|
-
|
|
276
|
-
// 如果距离上次保存超过 maxWaitMs,立即保存
|
|
277
|
-
if (timeSinceLastSave >= this.debounceConfig.maxWaitMs) {
|
|
278
|
-
this.doSave();
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// 否则设置防抖定时器
|
|
283
|
-
this.saveTimer = setTimeout(() => {
|
|
284
|
-
if (this.savePending) {
|
|
285
|
-
this.doSave();
|
|
286
|
-
}
|
|
287
|
-
}, this.debounceConfig.delayMs);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* 执行实际的保存操作(异步)
|
|
292
|
-
*/
|
|
293
|
-
private doSave(): void {
|
|
294
|
-
this.savePending = false;
|
|
295
|
-
this.saveTimer = undefined;
|
|
296
|
-
this.lastSaveTime = Date.now();
|
|
297
|
-
|
|
298
|
-
const tempPath = `${this.dataPath}.tmp`;
|
|
299
|
-
|
|
300
|
-
// 使用 setImmediate 将文件操作移到下一个事件循环
|
|
301
|
-
// 避免阻塞当前操作
|
|
302
|
-
setImmediate(() => {
|
|
303
|
-
try {
|
|
304
|
-
const data = Array.from(this.entries.values());
|
|
305
|
-
const jsonContent = JSON.stringify(data, null, 2);
|
|
306
|
-
|
|
307
|
-
// 1. 写入临时文件
|
|
308
|
-
writeFileSync(tempPath, jsonContent, { encoding: 'utf-8' });
|
|
309
|
-
|
|
310
|
-
// 2. 原子重命名(在 POSIX 系统上是原子操作)
|
|
311
|
-
renameSync(tempPath, this.dataPath);
|
|
312
|
-
} catch (e) {
|
|
313
|
-
logger.error('保存信誉数据失败: error=%s', e);
|
|
314
|
-
|
|
315
|
-
// 清理临时文件(如果存在)
|
|
316
|
-
try {
|
|
317
|
-
if (existsSync(tempPath)) {
|
|
318
|
-
unlinkSync(tempPath);
|
|
319
|
-
}
|
|
320
|
-
} catch {
|
|
321
|
-
// 忽略清理错误
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* 强制同步保存(用于关闭时)
|
|
329
|
-
*/
|
|
330
|
-
flush(): void {
|
|
331
|
-
// 清除防抖定时器
|
|
332
|
-
if (this.saveTimer) {
|
|
333
|
-
clearTimeout(this.saveTimer);
|
|
334
|
-
this.saveTimer = undefined;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// 如果有待保存的数据,立即同步保存
|
|
338
|
-
if (this.savePending) {
|
|
339
|
-
this.savePending = false;
|
|
340
|
-
this.doSaveSync();
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* 同步保存操作(用于关闭时确保数据持久化)
|
|
346
|
-
*/
|
|
347
|
-
private doSaveSync(): void {
|
|
348
|
-
const tempPath = `${this.dataPath}.tmp`;
|
|
349
|
-
|
|
350
|
-
try {
|
|
351
|
-
const data = Array.from(this.entries.values());
|
|
352
|
-
const jsonContent = JSON.stringify(data, null, 2);
|
|
353
|
-
|
|
354
|
-
writeFileSync(tempPath, jsonContent, { encoding: 'utf-8' });
|
|
355
|
-
renameSync(tempPath, this.dataPath);
|
|
356
|
-
} catch (e) {
|
|
357
|
-
logger.error('同步保存信誉数据失败: error=%s', e);
|
|
358
|
-
|
|
359
|
-
try {
|
|
360
|
-
if (existsSync(tempPath)) {
|
|
361
|
-
unlinkSync(tempPath);
|
|
362
|
-
}
|
|
363
|
-
} catch {
|
|
364
|
-
// 忽略清理错误
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|