web-dc-api 0.0.50 → 0.0.51
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/dist/dc.min.js +2 -2
- package/dist/index.cjs.js +3 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.esm.js +2 -2
- package/lib/common/dcutil.ts +1 -1
- package/lib/common/define.ts +2 -2
- package/lib/implements/threaddb/net/net.ts +1356 -1172
- package/lib/modules/database-module.ts +1 -1
- package/package.json +1 -1
|
@@ -1,63 +1,90 @@
|
|
|
1
|
-
import crypto from
|
|
2
|
-
|
|
3
|
-
import {
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
peerIdFromPublicKey,
|
|
5
|
+
peerIdFromPrivateKey,
|
|
6
|
+
peerIdFromMultihash,
|
|
7
|
+
peerIdFromString,
|
|
8
|
+
} from "@libp2p/peer-id";
|
|
4
9
|
import { keys } from "@libp2p/crypto";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
Multiaddr as TMultiaddr,
|
|
12
|
+
multiaddr,
|
|
13
|
+
protocols,
|
|
14
|
+
} from "@multiformats/multiaddr"; // 多地址库
|
|
15
|
+
import { Head } from "../core/head";
|
|
16
|
+
import { ThreadID } from "@textile/threads-id";
|
|
17
|
+
import { Ed25519PrivKey, Ed25519PubKey } from "../../../common/dc-key/ed25519";
|
|
18
|
+
import type {
|
|
19
|
+
PeerId,
|
|
20
|
+
PublicKey,
|
|
21
|
+
PrivateKey,
|
|
22
|
+
Ed25519PublicKey,
|
|
23
|
+
} from "@libp2p/interface";
|
|
24
|
+
import { SymmetricKey, Key as ThreadKey } from "../common/key";
|
|
25
|
+
import { validateIDData } from "../lsstoreds/global";
|
|
26
|
+
import {
|
|
27
|
+
ThreadInfo,
|
|
28
|
+
IThreadLogInfo,
|
|
29
|
+
SymKey,
|
|
30
|
+
IThreadInfo,
|
|
31
|
+
ThreadMuliaddr,
|
|
32
|
+
} from "../core/core";
|
|
33
|
+
import { ThreadToken } from "../core/identity";
|
|
34
|
+
import { ILogstore } from "../core/logstore";
|
|
35
|
+
import { Datastore } from "interface-datastore";
|
|
36
|
+
import { Blocks } from "helia";
|
|
37
|
+
import { DAGCBOR } from "@helia/dag-cbor"; // DAGService
|
|
38
|
+
import * as dagCBOR from "@ipld/dag-cbor";
|
|
39
|
+
import { IRecord, IThreadRecord } from "../core/record";
|
|
40
|
+
import { DCGrpcServer } from "./grpcserver";
|
|
21
41
|
import { Libp2p } from "@libp2p/interface";
|
|
22
|
-
import {dc_protocol} from
|
|
23
|
-
import {EventFromNode,Event} from
|
|
24
|
-
import {Node} from
|
|
25
|
-
import {IPLDNode} from
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
import {
|
|
42
|
+
import { dc_protocol } from "../../../common/define";
|
|
43
|
+
import { EventFromNode, Event } from "../cbor/event";
|
|
44
|
+
import { Node } from "../cbor/node";
|
|
45
|
+
import { IPLDNode } from "../core/core";
|
|
46
|
+
import {
|
|
47
|
+
ThreadRecord,
|
|
48
|
+
TimestampedRecord,
|
|
49
|
+
PeerRecords,
|
|
50
|
+
netPullingLimit,
|
|
51
|
+
} from "./define";
|
|
52
|
+
import { CID } from "multiformats/cid";
|
|
53
|
+
import { getHeadUndef } from "../core/head";
|
|
54
|
+
import {
|
|
55
|
+
GetRecord,
|
|
56
|
+
CreateRecord,
|
|
57
|
+
logToProto,
|
|
58
|
+
logFromProto,
|
|
59
|
+
CreateRecordConfig,
|
|
60
|
+
} from "../cbor/record";
|
|
61
|
+
import { IThreadEvent } from "../core/event";
|
|
62
|
+
import { DBGrpcClient } from "./grpcClient";
|
|
63
|
+
import { DBClient } from "../dbclient";
|
|
33
64
|
import { Client } from "../../../common/dcapi";
|
|
34
|
-
import {App,Connector,Net,PubKey,Token}
|
|
35
|
-
import {ChainUtil} from
|
|
36
|
-
import { DcUtil } from
|
|
37
|
-
import {CreateEvent} from
|
|
38
|
-
import {Errors} from
|
|
39
|
-
import {net as net_pb} from "../pb/net_pb";
|
|
40
|
-
import {PermanentAddrTTL} from "../common/logstore";
|
|
41
|
-
import {PeerStatus} from
|
|
42
|
-
import {
|
|
43
|
-
PeerIDConverter,
|
|
44
|
-
MultiaddrConverter,
|
|
45
|
-
CidConverter,
|
|
46
|
-
ThreadIDConverter,
|
|
47
|
-
KeyConverter,
|
|
65
|
+
import { App, Connector, Net, PubKey, Token } from "../core/app";
|
|
66
|
+
import { ChainUtil } from "../../../common/chain";
|
|
67
|
+
import { DcUtil } from "../../../common/dcutil";
|
|
68
|
+
import { CreateEvent } from "../cbor/event";
|
|
69
|
+
import { Errors } from "../core/db";
|
|
70
|
+
import { net as net_pb } from "../pb/net_pb";
|
|
71
|
+
import { PermanentAddrTTL } from "../common/logstore";
|
|
72
|
+
import { PeerStatus } from "../../../common/types/types";
|
|
73
|
+
import {
|
|
74
|
+
PeerIDConverter,
|
|
75
|
+
MultiaddrConverter,
|
|
76
|
+
CidConverter,
|
|
77
|
+
ThreadIDConverter,
|
|
78
|
+
KeyConverter,
|
|
48
79
|
ProtoKeyConverter,
|
|
49
|
-
json
|
|
50
|
-
} from
|
|
80
|
+
json,
|
|
81
|
+
} from "../pb/proto-custom-types";
|
|
51
82
|
import * as buffer from "buffer/";
|
|
52
|
-
import { AsyncMutex } from
|
|
53
|
-
import { DCContext } from
|
|
54
|
-
import { p } from
|
|
83
|
+
import { AsyncMutex } from "../common/AsyncMutex";
|
|
84
|
+
import { DCContext } from "../../../interfaces";
|
|
85
|
+
import { p } from "lib/common/blowfish/const";
|
|
55
86
|
const { Buffer } = buffer;
|
|
56
87
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
88
|
/**
|
|
62
89
|
* Creates a new ThreadRecord
|
|
63
90
|
*/
|
|
@@ -65,26 +92,34 @@ function newRecord(r: IRecord, id: ThreadID, lid: PeerId): IThreadRecord {
|
|
|
65
92
|
return new ThreadRecord(r, id, lid);
|
|
66
93
|
}
|
|
67
94
|
|
|
68
|
-
|
|
69
|
-
// 定义 Network 类
|
|
95
|
+
// 定义 Network 类
|
|
70
96
|
export class Network implements Net {
|
|
71
97
|
bstore: Blocks;
|
|
72
|
-
private logstore: ILogstore;
|
|
98
|
+
private logstore: ILogstore;
|
|
73
99
|
private dcChain: ChainUtil;
|
|
74
100
|
private dc: DcUtil;
|
|
75
|
-
private dagService: DAGCBOR;
|
|
76
|
-
private hostID: string;
|
|
77
|
-
private context: DCContext;
|
|
78
|
-
private server:DCGrpcServer;
|
|
101
|
+
private dagService: DAGCBOR;
|
|
102
|
+
private hostID: string;
|
|
103
|
+
private context: DCContext;
|
|
104
|
+
private server: DCGrpcServer;
|
|
79
105
|
private libp2p: Libp2p;
|
|
80
106
|
private connectors: Record<string, Connector>;
|
|
81
107
|
private cachePeers: Record<string, TMultiaddr> = {};
|
|
82
108
|
private threadMutexes: Record<string, AsyncMutex> = {};
|
|
83
109
|
|
|
84
|
-
constructor(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
110
|
+
constructor(
|
|
111
|
+
dcUtil: DcUtil,
|
|
112
|
+
dcChain: ChainUtil,
|
|
113
|
+
libp2p: Libp2p,
|
|
114
|
+
grpcServer: DCGrpcServer,
|
|
115
|
+
logstore: ILogstore,
|
|
116
|
+
bstore: Blocks,
|
|
117
|
+
dagService: DAGCBOR,
|
|
118
|
+
context: DCContext
|
|
119
|
+
) {
|
|
120
|
+
this.logstore = logstore;
|
|
121
|
+
this.hostID = libp2p.peerId.toString();
|
|
122
|
+
this.context = context;
|
|
88
123
|
this.bstore = bstore;
|
|
89
124
|
this.dagService = dagService;
|
|
90
125
|
this.libp2p = libp2p;
|
|
@@ -92,74 +127,88 @@ export class Network implements Net {
|
|
|
92
127
|
this.connectors = {};
|
|
93
128
|
this.dcChain = dcChain;
|
|
94
129
|
this.dc = dcUtil;
|
|
95
|
-
}
|
|
130
|
+
}
|
|
96
131
|
|
|
97
|
-
|
|
98
|
-
|
|
132
|
+
// 签名,后续应该改成发送到钱包iframe中签名,发送数据包含payload和用户公钥
|
|
133
|
+
sign = async (payload: Uint8Array): Promise<Uint8Array> => {
|
|
99
134
|
if (!this.context) {
|
|
100
135
|
throw new Error("privKey is null");
|
|
101
136
|
}
|
|
102
137
|
const signature = this.context.sign(payload);
|
|
103
138
|
return signature;
|
|
104
|
-
}
|
|
139
|
+
};
|
|
105
140
|
|
|
106
|
-
async getClient(peerId: PeerId):Promise<[Client | null, Error | null]>{
|
|
107
|
-
try{
|
|
141
|
+
async getClient(peerId: PeerId): Promise<[Client | null, Error | null]> {
|
|
142
|
+
try {
|
|
108
143
|
let cachedFlag = true;
|
|
109
|
-
const cacheAddr = this.cachePeers[peerId.toString()];
|
|
144
|
+
const cacheAddr = this.cachePeers[peerId.toString()];
|
|
110
145
|
let peerAddr: TMultiaddr | null = cacheAddr || null;
|
|
111
|
-
let peerStatus: PeerStatus | null =
|
|
146
|
+
let peerStatus: PeerStatus | null = PeerStatus.PeerStatusOnline;
|
|
112
147
|
if (!cacheAddr) {
|
|
113
148
|
cachedFlag = false;
|
|
114
|
-
[peerAddr, peerStatus] = await this.dcChain.getDcNodeWebrtcDirectAddr(
|
|
149
|
+
[peerAddr, peerStatus] = await this.dcChain.getDcNodeWebrtcDirectAddr(
|
|
150
|
+
peerId.toString()
|
|
151
|
+
);
|
|
115
152
|
}
|
|
116
|
-
|
|
153
|
+
|
|
117
154
|
if (!peerAddr) {
|
|
118
155
|
throw new Error("peerAddr is null");
|
|
119
|
-
}
|
|
156
|
+
}
|
|
120
157
|
if (peerStatus !== PeerStatus.PeerStatusOnline) {
|
|
121
158
|
throw new Error("peerStatus is not online");
|
|
122
159
|
}
|
|
123
|
-
if (!this.context.publicKey){
|
|
160
|
+
if (!this.context.publicKey) {
|
|
124
161
|
throw new Error("publicKey is null");
|
|
125
162
|
}
|
|
126
163
|
|
|
127
164
|
const addr = multiaddr(peerAddr);
|
|
128
|
-
const client = new Client(this.libp2p,this.bstore,addr, dc_protocol);
|
|
165
|
+
const client = new Client(this.libp2p, this.bstore, addr, dc_protocol);
|
|
129
166
|
//获取token
|
|
130
|
-
const token = await client.GetToken(
|
|
131
|
-
|
|
132
|
-
|
|
167
|
+
const token = await client.GetToken(
|
|
168
|
+
this.context.appInfo.appId || "",
|
|
169
|
+
this.context.publicKey.string(),
|
|
170
|
+
(payload: Uint8Array): Promise<Uint8Array> => {
|
|
171
|
+
return this.sign(payload);
|
|
172
|
+
}
|
|
173
|
+
);
|
|
133
174
|
if (!token) {
|
|
134
175
|
if (cachedFlag) {
|
|
135
|
-
let _:PeerStatus| null = null;
|
|
136
|
-
[peerAddr,_] = await this.dcChain.getDcNodeWebrtcDirectAddr(
|
|
176
|
+
let _: PeerStatus | null = null;
|
|
177
|
+
[peerAddr, _] = await this.dcChain.getDcNodeWebrtcDirectAddr(
|
|
178
|
+
peerId.toString()
|
|
179
|
+
);
|
|
137
180
|
delete this.cachePeers[peerId.toString()];
|
|
138
181
|
if (!peerAddr) {
|
|
139
182
|
throw new Error("peerAddr is null");
|
|
140
|
-
}
|
|
183
|
+
}
|
|
141
184
|
const addr = multiaddr(peerAddr);
|
|
142
|
-
const client = new Client(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
185
|
+
const client = new Client(
|
|
186
|
+
this.libp2p,
|
|
187
|
+
this.bstore,
|
|
188
|
+
addr,
|
|
189
|
+
dc_protocol
|
|
190
|
+
);
|
|
191
|
+
const token = await client.GetToken(
|
|
192
|
+
this.context.appInfo.appId || "",
|
|
193
|
+
this.context.publicKey.string(),
|
|
194
|
+
(payload: Uint8Array) => {
|
|
195
|
+
return this.sign(payload);
|
|
196
|
+
}
|
|
197
|
+
);
|
|
146
198
|
if (token) {
|
|
147
199
|
this.cachePeers[peerId.toString()] = peerAddr;
|
|
148
200
|
return [client, null];
|
|
149
201
|
}
|
|
150
202
|
}
|
|
151
203
|
throw new Error("get token is null");
|
|
152
|
-
|
|
153
204
|
}
|
|
154
205
|
this.cachePeers[peerId.toString()] = peerAddr;
|
|
155
206
|
return [client, null];
|
|
156
|
-
}catch(err){
|
|
157
|
-
return [null,err as Error];
|
|
207
|
+
} catch (err) {
|
|
208
|
+
return [null, err as Error];
|
|
158
209
|
}
|
|
159
210
|
}
|
|
160
211
|
|
|
161
|
-
|
|
162
|
-
|
|
163
212
|
getMutexForThread(threadId: string): AsyncMutex {
|
|
164
213
|
if (!this.threadMutexes[threadId]) {
|
|
165
214
|
this.threadMutexes[threadId] = new AsyncMutex();
|
|
@@ -167,328 +216,358 @@ export class Network implements Net {
|
|
|
167
216
|
return this.threadMutexes[threadId];
|
|
168
217
|
}
|
|
169
218
|
|
|
170
|
-
async getPeers(id: ThreadID):Promise<PeerId[]| undefined>{
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (!peerId) {
|
|
182
|
-
continue
|
|
219
|
+
async getPeers(id: ThreadID): Promise<PeerId[] | undefined> {
|
|
220
|
+
const peers = await this.dcChain.getObjNodes(id.toString());
|
|
221
|
+
if (!peers) {
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
const peerIds: PeerId[] = [];
|
|
225
|
+
for (const peer of peers) {
|
|
226
|
+
const peerStr = Buffer.from(peer.slice(2), "hex").toString("utf8");
|
|
227
|
+
const peerId = peerIdFromString(peerStr);
|
|
228
|
+
if (!peerId) {
|
|
229
|
+
continue;
|
|
183
230
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
231
|
+
peerIds.push(peerId);
|
|
232
|
+
}
|
|
233
|
+
return peerIds;
|
|
187
234
|
}
|
|
188
235
|
|
|
189
|
-
/**
|
|
190
|
-
* 创建threaddb
|
|
191
|
-
*/
|
|
192
|
-
async createThread(
|
|
193
|
-
|
|
236
|
+
/**
|
|
237
|
+
* 创建threaddb
|
|
238
|
+
*/
|
|
239
|
+
async createThread(
|
|
240
|
+
id: ThreadID,
|
|
241
|
+
options: {
|
|
242
|
+
token: ThreadToken;
|
|
243
|
+
logKey?: Ed25519PrivKey | Ed25519PubKey;
|
|
244
|
+
threadKey?: ThreadKey;
|
|
245
|
+
}
|
|
246
|
+
): Promise<ThreadInfo> {
|
|
247
|
+
if (!this.context.publicKey) {
|
|
194
248
|
throw new Error("Identity creation failed.");
|
|
195
|
-
}
|
|
196
|
-
const identity = this.context.publicKey;
|
|
197
|
-
|
|
249
|
+
}
|
|
250
|
+
const identity = this.context.publicKey;
|
|
198
251
|
|
|
199
|
-
await this.ensureUniqueLog(id, options.logKey, identity);
|
|
200
|
-
const threadKey = options.threadKey || this.generateRandomKey();
|
|
252
|
+
await this.ensureUniqueLog(id, options.logKey, identity);
|
|
253
|
+
const threadKey = options.threadKey || this.generateRandomKey();
|
|
201
254
|
|
|
202
|
-
const threadInfo
|
|
255
|
+
const threadInfo = new ThreadInfo(id, [], [], threadKey);
|
|
203
256
|
|
|
204
|
-
await this.logstore.addThread(threadInfo);
|
|
205
|
-
const logInfo = await this.createLog(id, options.logKey, identity);
|
|
257
|
+
await this.logstore.addThread(threadInfo);
|
|
258
|
+
const logInfo = await this.createLog(id, options.logKey, identity);
|
|
206
259
|
threadInfo.logs.push(logInfo);
|
|
207
|
-
return threadInfo;
|
|
208
|
-
}
|
|
260
|
+
return threadInfo;
|
|
261
|
+
}
|
|
209
262
|
|
|
263
|
+
/**
|
|
264
|
+
* 从多地址添加threaddb
|
|
265
|
+
*
|
|
266
|
+
* @param addr 包含threaddb ID的多地址
|
|
267
|
+
* @param options threaddb 选项
|
|
268
|
+
* @returns 带有地址的threaddb 信息
|
|
269
|
+
*/
|
|
270
|
+
async addThread(
|
|
271
|
+
addr: ThreadMuliaddr,
|
|
272
|
+
options: {
|
|
273
|
+
token?: ThreadToken | undefined;
|
|
274
|
+
logKey?: Ed25519PrivKey | Ed25519PubKey | undefined;
|
|
275
|
+
threadKey?: ThreadKey | undefined;
|
|
276
|
+
} = {}
|
|
277
|
+
): Promise<ThreadInfo> {
|
|
278
|
+
try {
|
|
279
|
+
// 从多地址提取threaddb ID
|
|
280
|
+
const idStr = addr.id.toString();
|
|
281
|
+
if (!idStr) {
|
|
282
|
+
throw new Error("Invalid thread address");
|
|
283
|
+
}
|
|
284
|
+
const id = ThreadID.fromString(idStr);
|
|
285
|
+
// 验证身份
|
|
286
|
+
let identity = await this.validate(id, options.token);
|
|
287
|
+
if (identity) {
|
|
288
|
+
console.debug(`Adding thread with identity: ${identity.toString()}`);
|
|
289
|
+
} else {
|
|
290
|
+
identity = this.context.publicKey;
|
|
291
|
+
}
|
|
210
292
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
*
|
|
214
|
-
* @param addr 包含threaddb ID的多地址
|
|
215
|
-
* @param options threaddb 选项
|
|
216
|
-
* @returns 带有地址的threaddb 信息
|
|
217
|
-
*/
|
|
218
|
-
async addThread(
|
|
219
|
-
addr: ThreadMuliaddr,
|
|
220
|
-
options: { token?: ThreadToken | undefined; logKey?: Ed25519PrivKey | Ed25519PubKey| undefined; threadKey?: ThreadKey| undefined } = {}
|
|
221
|
-
): Promise<ThreadInfo> {
|
|
222
|
-
try {
|
|
223
|
-
// 从多地址提取threaddb ID
|
|
224
|
-
const idStr = addr.id.toString();
|
|
225
|
-
if (!idStr) {
|
|
226
|
-
throw new Error("Invalid thread address");
|
|
227
|
-
}
|
|
228
|
-
const id = ThreadID.fromString(idStr);
|
|
229
|
-
// 验证身份
|
|
230
|
-
let identity = await this.validate(id, options.token);
|
|
231
|
-
if (identity) {
|
|
232
|
-
console.debug(`Adding thread with identity: ${identity.toString()}`);
|
|
233
|
-
} else {
|
|
234
|
-
identity = this.context.publicKey;
|
|
235
|
-
}
|
|
293
|
+
// 确保日志唯一性
|
|
294
|
+
await this.ensureUniqueLog(id, options.logKey, identity);
|
|
236
295
|
|
|
237
|
-
|
|
238
|
-
|
|
296
|
+
// 分离threaddb 组件以获取对等点地址
|
|
297
|
+
// const threadComp = `/thread/${id.toString()}`;
|
|
298
|
+
// const peerAddr = multiaddr(addr.toString().split(threadComp)[0]);
|
|
299
|
+
const peerAddr = addr.addr;
|
|
300
|
+
// 获取对等点信息
|
|
301
|
+
const peerId = peerAddr.getPeerId();
|
|
302
|
+
if (!peerId) {
|
|
303
|
+
throw new Error("Invalid peer address");
|
|
304
|
+
}
|
|
239
305
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
// const peerAddr = multiaddr(addr.toString().split(threadComp)[0]);
|
|
243
|
-
const peerAddr = addr.addr
|
|
244
|
-
// 获取对等点信息
|
|
245
|
-
const peerId = peerAddr.getPeerId();
|
|
246
|
-
if (!peerId) {
|
|
247
|
-
throw new Error("Invalid peer address");
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const pid = peerIdFromString(peerId);
|
|
251
|
-
const addFromSelf = pid.toString() === this.hostID;
|
|
306
|
+
const pid = peerIdFromString(peerId);
|
|
307
|
+
const addFromSelf = pid.toString() === this.hostID;
|
|
252
308
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
309
|
+
// 如果我们从自己添加,检查threaddb 是否存在
|
|
310
|
+
if (addFromSelf) {
|
|
311
|
+
try {
|
|
312
|
+
await this.logstore.getThread(id);
|
|
313
|
+
} catch (err: any) {
|
|
314
|
+
if (err.message === "Thread not found") {
|
|
315
|
+
throw new Error(`Cannot retrieve thread from self: ${err.message}`);
|
|
316
|
+
}
|
|
317
|
+
throw err;
|
|
260
318
|
}
|
|
261
|
-
throw err;
|
|
262
319
|
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// 添加threaddb 到存储
|
|
266
|
-
const threadInfo = new ThreadInfo(id, [], [], options.threadKey);
|
|
267
|
-
await this.logstore.addThread(threadInfo);
|
|
268
320
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
321
|
+
// 添加threaddb 到存储
|
|
322
|
+
const threadInfo = new ThreadInfo(id, [], [], options.threadKey);
|
|
323
|
+
await this.logstore.addThread(threadInfo);
|
|
324
|
+
|
|
325
|
+
// 如果可以读取或有日志密钥,则创建日志
|
|
326
|
+
if (
|
|
327
|
+
(options.threadKey && options.threadKey.canRead()) ||
|
|
328
|
+
options.logKey
|
|
329
|
+
) {
|
|
330
|
+
const logInfo = await this.createLog(id, options.logKey, identity);
|
|
331
|
+
threadInfo.logs.push(logInfo);
|
|
332
|
+
}
|
|
333
|
+
// 如果不是从自己添加,则连接并获取日志
|
|
334
|
+
if (!addFromSelf) {
|
|
335
|
+
// 从对等点更新日志
|
|
336
|
+
await this.updateLogsFromPeer(id, pid);
|
|
337
|
+
}
|
|
279
338
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
339
|
+
// 返回带有地址的threaddb 信息
|
|
340
|
+
return this.getThreadWithAddrs(id);
|
|
341
|
+
} catch (err) {
|
|
342
|
+
throw new Error(
|
|
343
|
+
`Failed to add thread: ${
|
|
344
|
+
err instanceof Error ? err.message : String(err)
|
|
345
|
+
}`
|
|
346
|
+
);
|
|
347
|
+
}
|
|
284
348
|
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
349
|
|
|
350
|
+
/**
|
|
351
|
+
* 获取threaddb 信息
|
|
352
|
+
* 返回包含地址的threaddb 信息对象
|
|
353
|
+
*
|
|
354
|
+
* @param id threaddb ID
|
|
355
|
+
* @param options 选项,可以包含令牌等
|
|
356
|
+
* @returns 包含地址的threaddb 信息
|
|
357
|
+
* @throws 如果threaddb 验证失败或threaddb 不存在
|
|
358
|
+
*/
|
|
359
|
+
async getThread(
|
|
360
|
+
id: ThreadID,
|
|
361
|
+
options: { token?: ThreadToken } = {}
|
|
362
|
+
): Promise<ThreadInfo> {
|
|
363
|
+
try {
|
|
364
|
+
// 验证threaddb ID和令牌
|
|
365
|
+
await this.validate(id, options.token);
|
|
288
366
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
async getThread(
|
|
299
|
-
id: ThreadID,
|
|
300
|
-
options: { token?: ThreadToken } = {}
|
|
301
|
-
): Promise<ThreadInfo> {
|
|
302
|
-
try {
|
|
303
|
-
// 验证threaddb ID和令牌
|
|
304
|
-
await this.validate(id, options.token);
|
|
305
|
-
|
|
306
|
-
// 获取带有地址的threaddb 信息
|
|
307
|
-
return this.getThreadWithAddrs(id);
|
|
308
|
-
} catch (err) {
|
|
309
|
-
throw new Error(`Error getting thread ${id.toString()}: ${err instanceof Error ? err.message : String(err)}`);
|
|
367
|
+
// 获取带有地址的threaddb 信息
|
|
368
|
+
return this.getThreadWithAddrs(id);
|
|
369
|
+
} catch (err) {
|
|
370
|
+
throw new Error(
|
|
371
|
+
`Error getting thread ${id.toString()}: ${
|
|
372
|
+
err instanceof Error ? err.message : String(err)
|
|
373
|
+
}`
|
|
374
|
+
);
|
|
375
|
+
}
|
|
310
376
|
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
377
|
|
|
378
|
+
/**
|
|
379
|
+
* 从连接的远程对等点获取线程信息
|
|
380
|
+
*
|
|
381
|
+
* @param id 线程ID
|
|
382
|
+
* @param options 线程选项
|
|
383
|
+
* @returns 线程信息对象
|
|
384
|
+
* @throws 如果连接刷新失败或获取线程信息失败
|
|
385
|
+
*/
|
|
386
|
+
async getThreadFromPeer(
|
|
387
|
+
id: ThreadID,
|
|
388
|
+
peerId: PeerId,
|
|
389
|
+
options: { token?: ThreadToken } = {}
|
|
390
|
+
): Promise<ThreadInfo> {
|
|
391
|
+
try {
|
|
392
|
+
const [client, _] = await this.getClient(peerId);
|
|
393
|
+
if (!client) {
|
|
394
|
+
throw new Error("Failed to get client");
|
|
395
|
+
}
|
|
396
|
+
const dbClient = new DBClient(client, this.dc, this, this.logstore);
|
|
314
397
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
398
|
+
// 发送请求
|
|
399
|
+
try {
|
|
400
|
+
const threadInfo = dbClient.getThreadFromPeer(id, peerId, options);
|
|
401
|
+
return threadInfo;
|
|
402
|
+
} catch (err) {
|
|
403
|
+
throw new Error(
|
|
404
|
+
`Error getting thread from peer: ${
|
|
405
|
+
err instanceof Error ? err.message : String(err)
|
|
406
|
+
}`
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
} catch (err) {
|
|
410
|
+
throw new Error(
|
|
411
|
+
`Error getting thread from peer: ${
|
|
412
|
+
err instanceof Error ? err.message : String(err)
|
|
413
|
+
}`
|
|
414
|
+
);
|
|
332
415
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* 删除线程
|
|
420
|
+
*
|
|
421
|
+
* @param id 线程ID
|
|
422
|
+
* @param options 线程选项
|
|
423
|
+
* @returns 无返回值
|
|
424
|
+
* @throws 如果验证失败或线程正在被使用
|
|
425
|
+
*/
|
|
426
|
+
async deleteThread(
|
|
427
|
+
id: ThreadID,
|
|
428
|
+
options: { token?: ThreadToken; apiToken?: Token } = {}
|
|
429
|
+
): Promise<void> {
|
|
336
430
|
try {
|
|
337
|
-
|
|
338
|
-
|
|
431
|
+
// 验证线程ID和令牌
|
|
432
|
+
await this.validate(id, options.token);
|
|
433
|
+
|
|
434
|
+
// 检查线程是否被应用使用
|
|
435
|
+
const [_, ok] = this.getConnectorProtected(id, options.apiToken);
|
|
436
|
+
if (!ok) {
|
|
437
|
+
throw new Error("Cannot delete thread: thread in use");
|
|
438
|
+
}
|
|
439
|
+
console.debug(`Deleting thread ${id.toString()}...`);
|
|
440
|
+
const mutex = this.getMutexForThread(id.toString());
|
|
441
|
+
await mutex.acquire();
|
|
442
|
+
try {
|
|
443
|
+
// 执行删除操作
|
|
444
|
+
await this.logstore.deleteThread(id);
|
|
445
|
+
delete this.connectors[id.toString()];
|
|
446
|
+
} finally {
|
|
447
|
+
mutex.release();
|
|
448
|
+
}
|
|
339
449
|
} catch (err) {
|
|
340
|
-
throw new Error(
|
|
450
|
+
throw new Error(
|
|
451
|
+
`Failed to delete thread: ${
|
|
452
|
+
err instanceof Error ? err.message : String(err)
|
|
453
|
+
}`
|
|
454
|
+
);
|
|
341
455
|
}
|
|
342
|
-
} catch (err) {
|
|
343
|
-
throw new Error(`Error getting thread from peer: ${err instanceof Error ? err.message : String(err)}`);
|
|
344
456
|
}
|
|
345
|
-
}
|
|
346
457
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
async deleteThread(
|
|
357
|
-
id: ThreadID,
|
|
358
|
-
options: { token?: ThreadToken, apiToken?: Token } = {}
|
|
359
|
-
): Promise<void> {
|
|
360
|
-
try {
|
|
361
|
-
// 验证线程ID和令牌
|
|
362
|
-
await this.validate(id, options.token)
|
|
363
|
-
|
|
364
|
-
// 检查线程是否被应用使用
|
|
365
|
-
const [_, ok] = this.getConnectorProtected(id, options.apiToken);
|
|
366
|
-
if (!ok) {
|
|
367
|
-
throw new Error("Cannot delete thread: thread in use");
|
|
458
|
+
/**
|
|
459
|
+
* 验证threaddb ID 和 Token
|
|
460
|
+
*/
|
|
461
|
+
async validate(
|
|
462
|
+
id: ThreadID,
|
|
463
|
+
token?: ThreadToken
|
|
464
|
+
): Promise<Ed25519PubKey | undefined> {
|
|
465
|
+
if (!validateIDData(id.toBytes())) {
|
|
466
|
+
throw new Error("Invalid thread ID.");
|
|
368
467
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
await mutex.acquire();
|
|
372
|
-
try {
|
|
373
|
-
// 执行删除操作
|
|
374
|
-
await this.logstore.deleteThread(id);
|
|
375
|
-
delete this.connectors[id.toString()];
|
|
376
|
-
} finally {
|
|
377
|
-
mutex.release();
|
|
468
|
+
if (!token) {
|
|
469
|
+
return;
|
|
378
470
|
}
|
|
379
|
-
|
|
380
|
-
throw new Error(`Failed to delete thread: ${err instanceof Error ? err.message : String(err)}`);
|
|
471
|
+
return token.pubKey();
|
|
381
472
|
}
|
|
382
|
-
}
|
|
383
473
|
|
|
384
|
-
/**
|
|
385
|
-
*
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
return
|
|
396
|
-
}
|
|
397
|
-
return token.pubKey()
|
|
398
|
-
}
|
|
474
|
+
/**
|
|
475
|
+
* 确保日志唯一性
|
|
476
|
+
* 检查给定threaddb 是否已存在具有相同密钥或身份的日志
|
|
477
|
+
*/
|
|
478
|
+
async ensureUniqueLog(
|
|
479
|
+
id: ThreadID,
|
|
480
|
+
key?: Ed25519PrivKey | Ed25519PubKey,
|
|
481
|
+
identity?: PublicKey
|
|
482
|
+
): Promise<void> {
|
|
483
|
+
try {
|
|
484
|
+
const thrd = await this.logstore.getThread(id);
|
|
399
485
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
if (key) {
|
|
413
|
-
// 根据密钥类型处理
|
|
414
|
-
if (key instanceof Ed25519PubKey) {
|
|
415
|
-
lid = peerIdFromPublicKey(key);
|
|
416
|
-
} else if (key instanceof Ed25519PrivKey) {
|
|
417
|
-
lid = peerIdFromPrivateKey(key);
|
|
486
|
+
// threaddb 存在,继续检查
|
|
487
|
+
let lid: PeerId;
|
|
488
|
+
|
|
489
|
+
if (key) {
|
|
490
|
+
// 根据密钥类型处理
|
|
491
|
+
if (key instanceof Ed25519PubKey) {
|
|
492
|
+
lid = peerIdFromPublicKey(key);
|
|
493
|
+
} else if (key instanceof Ed25519PrivKey) {
|
|
494
|
+
lid = peerIdFromPrivateKey(key);
|
|
495
|
+
} else {
|
|
496
|
+
throw new Error("Invalid log key");
|
|
497
|
+
}
|
|
418
498
|
} else {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
499
|
+
// 没有提供密钥,检查是否有此身份的日志
|
|
500
|
+
if (!identity) {
|
|
501
|
+
throw new Error("Either key or identity must be provided");
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
try {
|
|
505
|
+
const lidb = await this.logstore.metadata.getBytes(
|
|
506
|
+
id,
|
|
507
|
+
identity.toString()
|
|
508
|
+
);
|
|
509
|
+
if (!lidb || lidb.length === 0) {
|
|
510
|
+
// 检查是否有旧式"own"(未索引)日志
|
|
511
|
+
if (identity.equals(this.context.publicKey)) {
|
|
512
|
+
const firstPrivKeyLog = thrd.getFirstPrivKeyLog();
|
|
513
|
+
if (firstPrivKeyLog && firstPrivKeyLog.privKey) {
|
|
514
|
+
throw new Error("Thread exists");
|
|
515
|
+
}
|
|
435
516
|
}
|
|
517
|
+
return;
|
|
436
518
|
}
|
|
519
|
+
const lidbstr = new TextDecoder().decode(lidb);
|
|
520
|
+
// 从字节转换为PeerId
|
|
521
|
+
lid = peerIdFromString(lidbstr);
|
|
522
|
+
} catch (error) {
|
|
523
|
+
throw error;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
try {
|
|
528
|
+
const lginfo = await this.logstore.getLog(id, lid);
|
|
529
|
+
if (lginfo) {
|
|
530
|
+
// 如果到达这里,说明日志存在
|
|
531
|
+
throw new Error("Log exists");
|
|
532
|
+
}
|
|
533
|
+
} catch (error: any) {
|
|
534
|
+
if (error.message === Errors.ErrLogNotFound.message) {
|
|
437
535
|
return;
|
|
438
536
|
}
|
|
439
|
-
const lidbstr = new TextDecoder().decode(lidb);
|
|
440
|
-
// 从字节转换为PeerId
|
|
441
|
-
lid = peerIdFromString(lidbstr);
|
|
442
|
-
} catch (error) {
|
|
443
537
|
throw error;
|
|
444
538
|
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
try {
|
|
448
|
-
const lginfo = await this.logstore.getLog(id, lid);
|
|
449
|
-
if (lginfo) {
|
|
450
|
-
// 如果到达这里,说明日志存在
|
|
451
|
-
throw new Error("Log exists");
|
|
452
|
-
}
|
|
453
539
|
} catch (error: any) {
|
|
454
|
-
if (error.message === Errors.
|
|
540
|
+
if (error.message === Errors.ErrThreadNotFound.message) {
|
|
455
541
|
return;
|
|
456
542
|
}
|
|
457
543
|
throw error;
|
|
458
544
|
}
|
|
459
|
-
} catch (error: any) {
|
|
460
|
-
if (error.message === Errors.ErrThreadNotFound.message) {
|
|
461
|
-
return;
|
|
462
|
-
}
|
|
463
|
-
throw error;
|
|
464
545
|
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
|
|
468
546
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
return tinfo;
|
|
487
|
-
} catch (err) {
|
|
488
|
-
throw err;
|
|
547
|
+
/**
|
|
548
|
+
* Get thread information with addresses
|
|
549
|
+
*/
|
|
550
|
+
async getThreadWithAddrs(id: ThreadID): Promise<ThreadInfo> {
|
|
551
|
+
try {
|
|
552
|
+
const tinfo = await this.logstore.getThread(id);
|
|
553
|
+
// Get host addresses
|
|
554
|
+
const hostAddrs = this.libp2p.getMultiaddrs();
|
|
555
|
+
const resultAddrs: ThreadMuliaddr[] = [];
|
|
556
|
+
|
|
557
|
+
// Encapsulate each address with peer and thread components
|
|
558
|
+
for (const addr of hostAddrs) {
|
|
559
|
+
const withPeerId = addr.encapsulate(
|
|
560
|
+
`/p2p/${this.libp2p.peerId.toString()}`
|
|
561
|
+
);
|
|
562
|
+
const threadMultiaddr = new ThreadMuliaddr(withPeerId, tinfo.id);
|
|
563
|
+
resultAddrs.push(threadMultiaddr);
|
|
489
564
|
}
|
|
565
|
+
tinfo.addrs = resultAddrs;
|
|
566
|
+
return tinfo;
|
|
567
|
+
} catch (err) {
|
|
568
|
+
throw err;
|
|
490
569
|
}
|
|
491
|
-
|
|
570
|
+
}
|
|
492
571
|
|
|
493
572
|
/**
|
|
494
573
|
* Pull thread updates from peers
|
|
@@ -503,40 +582,94 @@ async ensureUniqueLog(id: ThreadID, key?: Ed25519PrivKey | Ed25519PubKey, identi
|
|
|
503
582
|
} finally {
|
|
504
583
|
mutex.release();
|
|
505
584
|
}
|
|
506
|
-
|
|
507
|
-
const [connector, appConnected] =
|
|
508
|
-
|
|
585
|
+
|
|
586
|
+
const [connector, appConnected] = this.getConnector(id);
|
|
587
|
+
|
|
509
588
|
// Handle records
|
|
510
589
|
const tRecords: TimestampedRecord[] = [];
|
|
511
|
-
|
|
512
|
-
for (const [lidStr, rec
|
|
590
|
+
|
|
591
|
+
for (const [lidStr, rec] of Object.entries(recs)) {
|
|
513
592
|
const lid = peerIdFromString(lidStr);
|
|
514
593
|
const rs = rec as PeerRecords;
|
|
515
594
|
if (appConnected) {
|
|
516
|
-
|
|
517
|
-
|
|
595
|
+
// 使用并发控制,但不分批 - 避免慢任务拖累整批
|
|
596
|
+
const maxConcurrency = 10;
|
|
597
|
+
let activePromises: Promise<void>[] = [];
|
|
598
|
+
let processedCount = 0;
|
|
599
|
+
|
|
600
|
+
const processRecord = async (r: any, index: number): Promise<void> => {
|
|
601
|
+
try {
|
|
602
|
+
const block = await r.getBlock(this.bstore);
|
|
603
|
+
const event =
|
|
604
|
+
block instanceof Event
|
|
605
|
+
? block
|
|
606
|
+
: await EventFromNode(block as Node);
|
|
607
|
+
|
|
608
|
+
const header = await event.getHeader(this.bstore);
|
|
609
|
+
const body = await event.getBody(this.bstore);
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
// Store internal blocks locally
|
|
613
|
+
await this.addMany([event, header, body]);
|
|
614
|
+
|
|
615
|
+
} catch (err) {
|
|
616
|
+
console.error(`预加载记录 ${index + 1} 失败:`, err);
|
|
617
|
+
// 继续处理其他记录
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
// 处理所有记录,但控制并发数
|
|
518
622
|
for (let i = 0; i < rs.records.length; i++) {
|
|
519
623
|
const r = rs.records[i]!;
|
|
624
|
+
// 创建处理Promise
|
|
625
|
+
const promise = processRecord(r, i).finally(() => {
|
|
626
|
+
processedCount++;
|
|
627
|
+
// 从活跃Promise列表中移除
|
|
628
|
+
const index = activePromises.indexOf(promise);
|
|
629
|
+
if (index > -1) {
|
|
630
|
+
activePromises.splice(index, 1);
|
|
631
|
+
}
|
|
632
|
+
});
|
|
520
633
|
|
|
521
|
-
|
|
522
|
-
const block = await r.getBlock( this.bstore);
|
|
523
|
-
const event = block instanceof Event ? block : await EventFromNode(block as Node);
|
|
634
|
+
activePromises.push(promise);
|
|
524
635
|
|
|
636
|
+
// 当达到最大并发数时,等待最快完成的一个
|
|
637
|
+
if (activePromises.length >= maxConcurrency) {
|
|
638
|
+
await Promise.race(activePromises);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
// 等待所有剩余的Promise完成
|
|
642
|
+
await Promise.all(activePromises);
|
|
643
|
+
|
|
644
|
+
console.log(`所有 ${rs.records.length} 条记录预加载完成`);
|
|
645
|
+
//开始正式处理,前面数据已经拉取到本地,这时处理速度很快
|
|
646
|
+
let indexCounter = rs.counter - rs.records.length + 1;
|
|
647
|
+
|
|
648
|
+
for (let i = 0; i < rs.records.length; i++) {
|
|
649
|
+
const r = rs.records[i]!;
|
|
650
|
+
|
|
651
|
+
// Get blocks for validation
|
|
652
|
+
const block = await r.getBlock(this.bstore);
|
|
653
|
+
const event =
|
|
654
|
+
block instanceof Event
|
|
655
|
+
? block
|
|
656
|
+
: await EventFromNode(block as Node);
|
|
657
|
+
|
|
525
658
|
const header = await event.getHeader(this.bstore);
|
|
526
659
|
const body = await event.getBody(this.bstore);
|
|
527
|
-
|
|
660
|
+
|
|
528
661
|
// Store internal blocks locally
|
|
529
662
|
await this.addMany([event, header, body]);
|
|
530
|
-
|
|
663
|
+
|
|
531
664
|
const tRecord = newRecord(r, id, lid);
|
|
532
665
|
const counter = indexCounter + i;
|
|
533
|
-
const createtime = await connector!.getNetRecordCreateTime(
|
|
534
|
-
|
|
666
|
+
const createtime = await connector!.getNetRecordCreateTime(tRecord);
|
|
667
|
+
|
|
535
668
|
tRecords.push({
|
|
536
669
|
record: r,
|
|
537
670
|
counter: counter,
|
|
538
671
|
createtime: createtime,
|
|
539
|
-
logid: lid
|
|
672
|
+
logid: lid,
|
|
540
673
|
});
|
|
541
674
|
}
|
|
542
675
|
} else {
|
|
@@ -544,26 +677,27 @@ async ensureUniqueLog(id: ThreadID, key?: Ed25519PrivKey | Ed25519PubKey, identi
|
|
|
544
677
|
}
|
|
545
678
|
}
|
|
546
679
|
|
|
547
|
-
tRecords.sort((a, b) =>
|
|
680
|
+
tRecords.sort((a, b) => {
|
|
548
681
|
if (a.createtime < b.createtime) return -1;
|
|
549
682
|
if (a.createtime > b.createtime) return 1;
|
|
550
683
|
return 0;
|
|
551
|
-
});
|
|
684
|
+
});
|
|
552
685
|
let i = 0;
|
|
686
|
+
|
|
553
687
|
// Process each record in order
|
|
554
688
|
for (const r of tRecords) {
|
|
555
|
-
|
|
689
|
+
await this.putRecords(id, r.logid, [r.record], r.counter);
|
|
556
690
|
}
|
|
691
|
+
|
|
557
692
|
} catch (err) {
|
|
558
693
|
throw err;
|
|
559
|
-
}
|
|
694
|
+
}
|
|
560
695
|
}
|
|
561
696
|
|
|
562
|
-
|
|
563
|
-
async addMany( nodes: IPLDNode[]): Promise<void> {
|
|
697
|
+
async addMany(nodes: IPLDNode[]): Promise<void> {
|
|
564
698
|
for (const node of nodes) {
|
|
565
699
|
await this.bstore.put(node.cid(), node.data());
|
|
566
|
-
}
|
|
700
|
+
}
|
|
567
701
|
}
|
|
568
702
|
/**
|
|
569
703
|
* Pull thread records from peers
|
|
@@ -572,110 +706,119 @@ async ensureUniqueLog(id: ThreadID, key?: Ed25519PrivKey | Ed25519PubKey, identi
|
|
|
572
706
|
try {
|
|
573
707
|
let [offsets, peers] = await this.threadOffsets(tid);
|
|
574
708
|
try {
|
|
575
|
-
const extPeers = await this.getPeers(
|
|
709
|
+
const extPeers = await this.getPeers(tid);
|
|
576
710
|
if (extPeers && extPeers.length > 0) {
|
|
577
711
|
peers = extPeers;
|
|
578
712
|
}
|
|
579
713
|
} catch (err) {
|
|
580
|
-
console.error(
|
|
714
|
+
console.error(
|
|
715
|
+
`Error getting peers for thread ${tid}: ${
|
|
716
|
+
err instanceof Error ? err.message : String(err)
|
|
717
|
+
}`
|
|
718
|
+
);
|
|
581
719
|
// Ignore getPeers errors
|
|
582
720
|
}
|
|
583
721
|
const pulledRecs: Record<string, PeerRecords> = {};
|
|
584
|
-
|
|
722
|
+
|
|
585
723
|
// Continue pulling until no more records
|
|
586
724
|
while (true) {
|
|
587
|
-
const recs = await this.getRecords(
|
|
725
|
+
const recs = await this.getRecords(
|
|
726
|
+
peers,
|
|
727
|
+
tid,
|
|
728
|
+
offsets,
|
|
729
|
+
netPullingLimit
|
|
730
|
+
);
|
|
588
731
|
let continueFlag = false;
|
|
589
|
-
|
|
590
732
|
for (const [lidStr, rs] of Object.entries(recs)) {
|
|
591
733
|
if (rs.records.length > 0) {
|
|
592
734
|
const existing = pulledRecs[lidStr] || { records: [], counter: 0 };
|
|
593
735
|
const records = [...existing.records, ...rs.records];
|
|
594
|
-
|
|
736
|
+
|
|
595
737
|
pulledRecs[lidStr] = {
|
|
596
738
|
records: records,
|
|
597
|
-
counter: rs.counter
|
|
739
|
+
counter: rs.counter,
|
|
598
740
|
};
|
|
599
|
-
|
|
741
|
+
|
|
600
742
|
const lastRecord = rs.records[rs.records.length - 1]!;
|
|
601
743
|
offsets[lidStr] = {
|
|
602
744
|
id: lastRecord.cid(),
|
|
603
|
-
counter: rs.counter
|
|
745
|
+
counter: rs.counter,
|
|
604
746
|
};
|
|
605
|
-
|
|
747
|
+
|
|
606
748
|
if (rs.records.length >= netPullingLimit) {
|
|
607
749
|
continueFlag = true;
|
|
608
750
|
}
|
|
609
751
|
}
|
|
610
752
|
}
|
|
611
|
-
|
|
753
|
+
|
|
612
754
|
if (!continueFlag) {
|
|
613
755
|
break;
|
|
614
756
|
}
|
|
615
757
|
}
|
|
616
|
-
|
|
617
758
|
return pulledRecs;
|
|
618
759
|
} catch (err) {
|
|
619
760
|
throw err;
|
|
620
761
|
}
|
|
621
762
|
}
|
|
622
763
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
let
|
|
633
|
-
let
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
if (key instanceof Ed25519PrivKey) {
|
|
764
|
+
/**
|
|
765
|
+
* 创建日志,分配 privKey 和 pubKey
|
|
766
|
+
*/
|
|
767
|
+
async createLog(
|
|
768
|
+
id: ThreadID,
|
|
769
|
+
key?: Ed25519PrivKey | Ed25519PubKey,
|
|
770
|
+
identity?: PublicKey
|
|
771
|
+
): Promise<IThreadLogInfo> {
|
|
772
|
+
let privKey: PrivateKey | undefined;
|
|
773
|
+
let pubKey: PublicKey;
|
|
774
|
+
let peerId: PeerId;
|
|
775
|
+
if (!key) {
|
|
776
|
+
const keyPair = await keys.generateKeyPair("Ed25519");
|
|
777
|
+
privKey = new Ed25519PrivKey(keyPair.raw);
|
|
778
|
+
pubKey = keyPair.publicKey;
|
|
779
|
+
peerId = peerIdFromPrivateKey(privKey);
|
|
780
|
+
} else {
|
|
781
|
+
if (key instanceof Ed25519PrivKey) {
|
|
642
782
|
privKey = key;
|
|
643
|
-
pubKey = key.publicKey;
|
|
644
|
-
peerId = peerIdFromPrivateKey(key);
|
|
645
|
-
}else if(key instanceof Ed25519PubKey){
|
|
783
|
+
pubKey = key.publicKey;
|
|
784
|
+
peerId = peerIdFromPrivateKey(key);
|
|
785
|
+
} else if (key instanceof Ed25519PubKey) {
|
|
646
786
|
pubKey = key;
|
|
647
787
|
peerId = peerIdFromPublicKey(key);
|
|
648
|
-
}else{
|
|
649
|
-
throw new Error("Invalid key type.");
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
const addr = multiaddr(`/p2p/${this.hostID}`); // 基于 hostID 生成地址
|
|
653
|
-
const head : Head = {
|
|
654
|
-
counter: 0,
|
|
788
|
+
} else {
|
|
789
|
+
throw new Error("Invalid key type.");
|
|
790
|
+
}
|
|
655
791
|
}
|
|
656
|
-
const
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
792
|
+
const addr = multiaddr(`/p2p/${this.hostID}`); // 基于 hostID 生成地址
|
|
793
|
+
const head: Head = {
|
|
794
|
+
counter: 0,
|
|
795
|
+
};
|
|
796
|
+
const logInfo: IThreadLogInfo = {
|
|
797
|
+
privKey,
|
|
798
|
+
pubKey,
|
|
799
|
+
id: peerId,
|
|
800
|
+
addrs: [addr],
|
|
801
|
+
managed: true,
|
|
802
|
+
head: head,
|
|
803
|
+
} as IThreadLogInfo;
|
|
804
|
+
|
|
805
|
+
// 将日志添加到threaddb存储
|
|
806
|
+
await this.logstore.addLog(id, logInfo);
|
|
807
|
+
const logIDBytes = new TextEncoder().encode(peerId.toString());
|
|
808
|
+
await this.logstore.metadata.putBytes(
|
|
809
|
+
id,
|
|
810
|
+
identity?.toString() || "",
|
|
811
|
+
logIDBytes
|
|
812
|
+
);
|
|
813
|
+
return logInfo;
|
|
814
|
+
}
|
|
678
815
|
|
|
816
|
+
/**
|
|
817
|
+
* 生成随机threaddb 密钥
|
|
818
|
+
*/
|
|
819
|
+
generateRandomKey(): ThreadKey {
|
|
820
|
+
return new ThreadKey(SymmetricKey.new(), SymmetricKey.new());
|
|
821
|
+
}
|
|
679
822
|
|
|
680
823
|
/**
|
|
681
824
|
* Add connector for thread
|
|
@@ -690,169 +833,190 @@ async ensureUniqueLog(id: ThreadID, key?: Ed25519PrivKey | Ed25519PubKey, identi
|
|
|
690
833
|
getConnector(id: ThreadID): [Connector | null, boolean] {
|
|
691
834
|
const conn = this.connectors[id.toString()];
|
|
692
835
|
return [conn || null, !!conn];
|
|
693
|
-
|
|
694
836
|
}
|
|
695
837
|
|
|
696
|
-
|
|
697
838
|
/**
|
|
698
839
|
* Get thread offsets and peers
|
|
699
840
|
*/
|
|
700
|
-
async threadOffsets(
|
|
841
|
+
async threadOffsets(
|
|
842
|
+
tid: ThreadID
|
|
843
|
+
): Promise<[Record<string, Head>, PeerId[]]> {
|
|
701
844
|
const info = await this.logstore.getThread(tid);
|
|
702
|
-
|
|
845
|
+
|
|
703
846
|
const offsets: Record<string, Head> = {};
|
|
704
847
|
const addrs: TMultiaddr[] = [];
|
|
705
|
-
|
|
848
|
+
|
|
706
849
|
// Process all logs in thread
|
|
707
850
|
for (const lg of info.logs) {
|
|
708
851
|
// Check if head is known
|
|
709
852
|
let has = false;
|
|
710
|
-
|
|
853
|
+
|
|
711
854
|
if (lg.head?.id) {
|
|
712
|
-
has = await this.isKnown(
|
|
855
|
+
has = await this.isKnown(lg.head.id);
|
|
713
856
|
}
|
|
714
|
-
|
|
857
|
+
|
|
715
858
|
if (has && lg.head) {
|
|
716
859
|
offsets[lg.id.toString()] = lg.head;
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
}else{
|
|
860
|
+
// Collect addresses
|
|
861
|
+
} else {
|
|
720
862
|
offsets[lg.id.toString()] = await getHeadUndef();
|
|
721
863
|
}
|
|
722
864
|
if (lg.addrs && lg.addrs.length > 0) {
|
|
723
865
|
addrs.push(...lg.addrs);
|
|
724
866
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
867
|
}
|
|
728
|
-
|
|
868
|
+
|
|
729
869
|
// Get unique peer IDs
|
|
730
870
|
const peers = await this.uniquePeers(addrs);
|
|
731
|
-
|
|
871
|
+
|
|
732
872
|
return [offsets, peers];
|
|
733
873
|
}
|
|
734
874
|
|
|
735
|
-
async updateLogsFromPeer(tid: ThreadID,peerId: PeerId): Promise<void> {
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
}
|
|
741
|
-
const dbClient = new DBClient(client,this.dc,this,this.logstore);
|
|
742
|
-
await dbClient.scheduleUpdateLogs(tid);
|
|
743
|
-
} catch (err) {
|
|
744
|
-
throw new Error(`Getting records for thread ${tid} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
/**
|
|
750
|
-
* 从对等点获取新的日志和记录并添加到本地存储
|
|
751
|
-
*
|
|
752
|
-
* @param tid threaddb ID
|
|
753
|
-
* @param pid 对等点ID
|
|
754
|
-
* @returns 无返回值,但会抛出错误
|
|
755
|
-
*/
|
|
756
|
-
async updateRecordsFromPeer(tid: ThreadID,peerId: PeerId|null,client?:DBClient): Promise<void> {
|
|
757
|
-
try {
|
|
758
|
-
// 获取threaddb 偏移量
|
|
759
|
-
const [offsets, peers] = await this.threadOffsets(tid);
|
|
760
|
-
|
|
761
|
-
// 构建获取记录的请求
|
|
762
|
-
const { req, serviceKey } = await this.buildGetRecordsRequest(tid, offsets, netPullingLimit);
|
|
763
|
-
let recs : Record<string, PeerRecords> = {};
|
|
764
|
-
if (client) {
|
|
765
|
-
recs = await this.getRecordsWithDbClient(client, req, serviceKey);
|
|
766
|
-
}else{
|
|
767
|
-
if (!peerId) {
|
|
768
|
-
throw new Error("A peer-id is required to request records");
|
|
875
|
+
async updateLogsFromPeer(tid: ThreadID, peerId: PeerId): Promise<void> {
|
|
876
|
+
try {
|
|
877
|
+
const [client, _] = await this.getClient(peerId);
|
|
878
|
+
if (!client) {
|
|
879
|
+
return;
|
|
769
880
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
881
|
+
const dbClient = new DBClient(client, this.dc, this, this.logstore);
|
|
882
|
+
await dbClient.scheduleUpdateLogs(tid);
|
|
883
|
+
} catch (err) {
|
|
884
|
+
throw new Error(
|
|
885
|
+
`Getting records for thread ${tid} failed: ${
|
|
886
|
+
err instanceof Error ? err.message : String(err)
|
|
887
|
+
}`
|
|
775
888
|
);
|
|
776
889
|
}
|
|
777
|
-
// 处理接收到的记录
|
|
778
|
-
for (const [lidStr, rs] of Object.entries(recs)) {
|
|
779
|
-
try {
|
|
780
|
-
// 将字符串ID转换为PeerId对象
|
|
781
|
-
const lid = peerIdFromString(lidStr);
|
|
782
|
-
|
|
783
|
-
// 将记录添加到本地存储
|
|
784
|
-
await this.putRecords( tid, lid, rs.records, rs.counter);
|
|
785
|
-
} catch (err) {
|
|
786
|
-
throw new Error(`Putting records from log ${lidStr} (thread ${tid}) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
// 检查是否可能有更多记录需要获取
|
|
790
|
-
for (const [lidStr, rs] of Object.entries(recs)) {
|
|
791
|
-
try {
|
|
792
|
-
const lid = peerIdFromString(lidStr);
|
|
793
|
-
const head = await this.currentHead(tid, lid);
|
|
794
|
-
|
|
795
|
-
// 如果我们收到了最大数量的记录,并且可能还有更多
|
|
796
|
-
if (head.counter <= rs.counter && rs.records.length === netPullingLimit) {
|
|
797
|
-
// 递归调用继续获取更多记录
|
|
798
|
-
return this.updateRecordsFromPeer(tid, peerId,client);
|
|
799
|
-
}
|
|
800
|
-
} catch (err) {
|
|
801
|
-
// 忽略获取头部的错误,继续检查其他日志
|
|
802
|
-
console.warn(`Error checking head for log ${lidStr}: ${err instanceof Error ? err.message : String(err)}`);
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
} catch (err) {
|
|
806
|
-
throw new Error(`Getting records for thread ${tid} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
807
890
|
}
|
|
808
|
-
}
|
|
809
891
|
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
892
|
+
/**
|
|
893
|
+
* 从对等点获取新的日志和记录并添加到本地存储
|
|
894
|
+
*
|
|
895
|
+
* @param tid threaddb ID
|
|
896
|
+
* @param pid 对等点ID
|
|
897
|
+
* @returns 无返回值,但会抛出错误
|
|
898
|
+
*/
|
|
899
|
+
async updateRecordsFromPeer(
|
|
900
|
+
tid: ThreadID,
|
|
901
|
+
peerId: PeerId | null,
|
|
902
|
+
client?: DBClient
|
|
903
|
+
): Promise<void> {
|
|
904
|
+
try {
|
|
905
|
+
// 获取threaddb 偏移量
|
|
906
|
+
const [offsets, peers] = await this.threadOffsets(tid);
|
|
907
|
+
|
|
908
|
+
// 构建获取记录的请求
|
|
909
|
+
const { req, serviceKey } = await this.buildGetRecordsRequest(
|
|
910
|
+
tid,
|
|
911
|
+
offsets,
|
|
912
|
+
netPullingLimit
|
|
913
|
+
);
|
|
914
|
+
let recs: Record<string, PeerRecords> = {};
|
|
915
|
+
if (client) {
|
|
916
|
+
recs = await this.getRecordsWithDbClient(client, req, serviceKey);
|
|
917
|
+
} else {
|
|
918
|
+
if (!peerId) {
|
|
919
|
+
throw new Error("A peer-id is required to request records");
|
|
920
|
+
}
|
|
921
|
+
// 从对等点获取记录
|
|
922
|
+
recs = await this.getRecordsFromPeer(peerId, req, serviceKey);
|
|
923
|
+
}
|
|
924
|
+
// 处理接收到的记录
|
|
925
|
+
for (const [lidStr, rs] of Object.entries(recs)) {
|
|
926
|
+
try {
|
|
927
|
+
// 将字符串ID转换为PeerId对象
|
|
928
|
+
const lid = peerIdFromString(lidStr);
|
|
929
|
+
|
|
930
|
+
// 将记录添加到本地存储
|
|
931
|
+
await this.putRecords(tid, lid, rs.records, rs.counter);
|
|
932
|
+
} catch (err) {
|
|
933
|
+
throw new Error(
|
|
934
|
+
`Putting records from log ${lidStr} (thread ${tid}) failed: ${
|
|
935
|
+
err instanceof Error ? err.message : String(err)
|
|
936
|
+
}`
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
// 检查是否可能有更多记录需要获取
|
|
941
|
+
for (const [lidStr, rs] of Object.entries(recs)) {
|
|
942
|
+
try {
|
|
943
|
+
const lid = peerIdFromString(lidStr);
|
|
944
|
+
const head = await this.currentHead(tid, lid);
|
|
945
|
+
|
|
946
|
+
// 如果我们收到了最大数量的记录,并且可能还有更多
|
|
947
|
+
if (
|
|
948
|
+
head.counter <= rs.counter &&
|
|
949
|
+
rs.records.length === netPullingLimit
|
|
950
|
+
) {
|
|
951
|
+
// 递归调用继续获取更多记录
|
|
952
|
+
return this.updateRecordsFromPeer(tid, peerId, client);
|
|
953
|
+
}
|
|
954
|
+
} catch (err) {
|
|
955
|
+
// 忽略获取头部的错误,继续检查其他日志
|
|
956
|
+
console.warn(
|
|
957
|
+
`Error checking head for log ${lidStr}: ${
|
|
958
|
+
err instanceof Error ? err.message : String(err)
|
|
959
|
+
}`
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
} catch (err) {
|
|
964
|
+
throw new Error(
|
|
965
|
+
`Getting records for thread ${tid} failed: ${
|
|
966
|
+
err instanceof Error ? err.message : String(err)
|
|
967
|
+
}`
|
|
968
|
+
);
|
|
825
969
|
}
|
|
826
|
-
const dbClient = new DBClient(client,this.dc,this,this.logstore);
|
|
827
|
-
const recs = await dbClient.getRecordsFromPeer( req, serviceKey);
|
|
828
|
-
return recs;
|
|
829
|
-
} catch (err) {
|
|
830
|
-
console.error("getRecordsFromPeer error:", err);
|
|
831
|
-
throw err;
|
|
832
970
|
}
|
|
833
|
-
}
|
|
834
971
|
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
972
|
+
/**
|
|
973
|
+
* 从特定对等点获取记录
|
|
974
|
+
* 注意: 这是一个假设的实现,需要根据你的实际gRPC客户端实现来调整
|
|
975
|
+
*/
|
|
976
|
+
async getRecordsFromPeer(
|
|
977
|
+
peerId: PeerId,
|
|
978
|
+
req: any,
|
|
979
|
+
serviceKey: SymKey
|
|
980
|
+
): Promise<Record<string, PeerRecords>> {
|
|
981
|
+
try {
|
|
982
|
+
const [client, _] = await this.getClient(peerId);
|
|
983
|
+
if (!client) {
|
|
984
|
+
return {};
|
|
985
|
+
}
|
|
986
|
+
const dbClient = new DBClient(client, this.dc, this, this.logstore);
|
|
987
|
+
const recs = await dbClient.getRecordsFromPeer(req, serviceKey);
|
|
988
|
+
return recs;
|
|
989
|
+
} catch (err) {
|
|
990
|
+
console.error("getRecordsFromPeer error:", err);
|
|
991
|
+
throw err;
|
|
992
|
+
}
|
|
846
993
|
}
|
|
847
|
-
}
|
|
848
994
|
|
|
995
|
+
async getRecordsWithDbClient(
|
|
996
|
+
dbClient: DBClient,
|
|
997
|
+
req: any,
|
|
998
|
+
serviceKey: SymKey
|
|
999
|
+
): Promise<Record<string, PeerRecords>> {
|
|
1000
|
+
try {
|
|
1001
|
+
const recs = await dbClient.getRecordsFromPeer(req, serviceKey);
|
|
1002
|
+
return recs;
|
|
1003
|
+
} catch (err) {
|
|
1004
|
+
console.error("getRecordsFromPeer error:", err);
|
|
1005
|
+
throw err;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
849
1008
|
|
|
850
1009
|
/**
|
|
851
1010
|
* Add records to a thread
|
|
852
1011
|
*/
|
|
853
|
-
async putRecords(
|
|
854
|
-
|
|
855
|
-
|
|
1012
|
+
async putRecords(
|
|
1013
|
+
tid: ThreadID,
|
|
1014
|
+
lid: PeerId,
|
|
1015
|
+
recs: IRecord[],
|
|
1016
|
+
counter: number
|
|
1017
|
+
): Promise<void> {
|
|
1018
|
+
const [chain, head] = await this.loadRecords(tid, lid, recs, counter);
|
|
1019
|
+
|
|
856
1020
|
if (chain.length === 0) {
|
|
857
1021
|
return;
|
|
858
1022
|
}
|
|
@@ -863,11 +1027,11 @@ async getRecordsWithDbClient(
|
|
|
863
1027
|
const current = await this.currentHead(tid, lid);
|
|
864
1028
|
let headReached = true;
|
|
865
1029
|
let updatedHead = head;
|
|
866
|
-
if (current?.id?.toString()
|
|
1030
|
+
if (current?.id?.toString() != head?.id?.toString()) {
|
|
867
1031
|
// Fast-forward the chain up to the updated head
|
|
868
1032
|
headReached = false;
|
|
869
1033
|
updatedHead = current;
|
|
870
|
-
|
|
1034
|
+
|
|
871
1035
|
for (let i = 0; i < chain.length; i++) {
|
|
872
1036
|
if (chain[i]!.value().cid().equals(current.id)) {
|
|
873
1037
|
chain.splice(0, i + 1);
|
|
@@ -875,7 +1039,7 @@ async getRecordsWithDbClient(
|
|
|
875
1039
|
break;
|
|
876
1040
|
}
|
|
877
1041
|
}
|
|
878
|
-
|
|
1042
|
+
|
|
879
1043
|
if (!headReached) {
|
|
880
1044
|
// Entire chain already processed
|
|
881
1045
|
return;
|
|
@@ -883,17 +1047,17 @@ async getRecordsWithDbClient(
|
|
|
883
1047
|
}
|
|
884
1048
|
|
|
885
1049
|
const [connector, appConnected] = this.getConnector(tid);
|
|
886
|
-
let identity:Ed25519PubKey;
|
|
1050
|
+
let identity: Ed25519PubKey;
|
|
887
1051
|
let readKey: SymmetricKey | null = null;
|
|
888
1052
|
let validate = false;
|
|
889
1053
|
let updatedCounter = updatedHead.counter;
|
|
890
|
-
|
|
1054
|
+
|
|
891
1055
|
if (appConnected) {
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
1056
|
+
const symKey = await this.logstore.keyBook.readKey(tid);
|
|
1057
|
+
if (symKey) {
|
|
1058
|
+
readKey = SymmetricKey.fromSymKey(symKey);
|
|
1059
|
+
}
|
|
1060
|
+
if (readKey) {
|
|
897
1061
|
validate = true;
|
|
898
1062
|
}
|
|
899
1063
|
}
|
|
@@ -901,23 +1065,28 @@ async getRecordsWithDbClient(
|
|
|
901
1065
|
if (validate) {
|
|
902
1066
|
// Validate the record
|
|
903
1067
|
const block = await record.value().getBlock(this.bstore);
|
|
904
|
-
|
|
1068
|
+
|
|
905
1069
|
let event: Event;
|
|
906
1070
|
if (block instanceof Event) {
|
|
907
1071
|
event = block;
|
|
908
1072
|
} else {
|
|
909
|
-
event = await EventFromNode(block as Node) as Event;
|
|
1073
|
+
event = (await EventFromNode(block as Node)) as Event;
|
|
910
1074
|
}
|
|
911
|
-
|
|
912
|
-
const dbody = await event.getBody(
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
1075
|
+
|
|
1076
|
+
const dbody = await event.getBody(
|
|
1077
|
+
this.bstore,
|
|
1078
|
+
readKey ? readKey : undefined
|
|
1079
|
+
);
|
|
1080
|
+
|
|
1081
|
+
identity = await KeyConverter.publicFromBytes<Ed25519PubKey>(
|
|
1082
|
+
record.value().pubKey()
|
|
1083
|
+
);
|
|
1084
|
+
|
|
916
1085
|
try {
|
|
917
1086
|
await connector!.validateNetRecordBody(dbody, identity);
|
|
918
1087
|
} catch (err) {
|
|
919
1088
|
// If validation fails, clean up blocks
|
|
920
|
-
const header = await event.getHeader(
|
|
1089
|
+
const header = await event.getHeader(this.bstore);
|
|
921
1090
|
const body = await event.getBody(this.bstore);
|
|
922
1091
|
this.bstore.deleteMany([event.cid(), header.cid(), body.cid()]);
|
|
923
1092
|
throw err;
|
|
@@ -926,20 +1095,21 @@ async getRecordsWithDbClient(
|
|
|
926
1095
|
|
|
927
1096
|
// Update head counter
|
|
928
1097
|
updatedCounter++;
|
|
929
|
-
|
|
1098
|
+
|
|
930
1099
|
// Set new head for the log
|
|
931
|
-
await this.logstore.headBook.setHead(
|
|
1100
|
+
await this.logstore.headBook.setHead(tid, lid, {
|
|
1101
|
+
id: record.value().cid(),
|
|
1102
|
+
counter: updatedCounter,
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
// Set checkpoint for log
|
|
1106
|
+
await this.setThreadLogPoint(
|
|
932
1107
|
tid,
|
|
933
1108
|
lid,
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
counter: updatedCounter
|
|
937
|
-
}
|
|
1109
|
+
updatedCounter,
|
|
1110
|
+
record.value().cid()
|
|
938
1111
|
);
|
|
939
|
-
|
|
940
|
-
// Set checkpoint for log
|
|
941
|
-
await this.setThreadLogPoint(tid, lid, updatedCounter, record.value().cid());
|
|
942
|
-
|
|
1112
|
+
|
|
943
1113
|
// Handle record in app connector
|
|
944
1114
|
if (appConnected) {
|
|
945
1115
|
try {
|
|
@@ -952,7 +1122,6 @@ async getRecordsWithDbClient(
|
|
|
952
1122
|
|
|
953
1123
|
// Add record to blockstore
|
|
954
1124
|
await this.bstore.put(record.value().cid(), record.value().data());
|
|
955
|
-
|
|
956
1125
|
}
|
|
957
1126
|
} finally {
|
|
958
1127
|
mutex.release();
|
|
@@ -971,12 +1140,12 @@ async getRecordsWithDbClient(
|
|
|
971
1140
|
if (recs.length === 0) {
|
|
972
1141
|
throw new Error("Cannot load empty record chain");
|
|
973
1142
|
}
|
|
974
|
-
|
|
1143
|
+
|
|
975
1144
|
const head = await this.currentHead(tid, lid);
|
|
976
|
-
|
|
1145
|
+
|
|
977
1146
|
// Check if the last record was already loaded and processed
|
|
978
1147
|
const last = recs[recs.length - 1]!;
|
|
979
|
-
|
|
1148
|
+
|
|
980
1149
|
// If we don't have the counter, check if record exists
|
|
981
1150
|
if (counter === undefined) {
|
|
982
1151
|
const exist = await this.isKnown(last.cid());
|
|
@@ -986,28 +1155,27 @@ async getRecordsWithDbClient(
|
|
|
986
1155
|
} else if (counter <= head.counter) {
|
|
987
1156
|
return [[], head];
|
|
988
1157
|
}
|
|
989
|
-
|
|
990
1158
|
let chain: IRecord[] = [];
|
|
991
1159
|
let complete = false;
|
|
992
1160
|
// Check which records we already have
|
|
993
1161
|
for (let i = recs.length - 1; i >= 0; i--) {
|
|
994
1162
|
const next = recs[i]!;
|
|
995
|
-
if (
|
|
1163
|
+
if (next.cid().toString() == "" || next.cid().equals(head.id)) {
|
|
996
1164
|
complete = true;
|
|
997
1165
|
break;
|
|
998
1166
|
}
|
|
999
1167
|
chain.push(next);
|
|
1000
1168
|
}
|
|
1001
|
-
|
|
1002
1169
|
|
|
1003
1170
|
// Bridge the gap between the last provided record and current head
|
|
1004
1171
|
if (!complete && chain.length > 0) {
|
|
1172
|
+
|
|
1005
1173
|
let c = chain[chain.length - 1]!.prevID();
|
|
1006
1174
|
while (c && !(last.cid().toString() == "")) {
|
|
1007
1175
|
if (c.equals(head.id)) {
|
|
1008
1176
|
break;
|
|
1009
1177
|
}
|
|
1010
|
-
const r = await this.getRecord(
|
|
1178
|
+
const r = await this.getRecord(tid, c);
|
|
1011
1179
|
chain.push(r);
|
|
1012
1180
|
c = r.prevID();
|
|
1013
1181
|
}
|
|
@@ -1019,131 +1187,131 @@ async getRecordsWithDbClient(
|
|
|
1019
1187
|
|
|
1020
1188
|
// Prepare thread records in the right order
|
|
1021
1189
|
const tRecords: ThreadRecord[] = [];
|
|
1022
|
-
|
|
1190
|
+
|
|
1023
1191
|
for (let i = chain.length - 1; i >= 0; i--) {
|
|
1024
1192
|
const r = chain[i]!;
|
|
1025
|
-
|
|
1193
|
+
|
|
1026
1194
|
// Get and cache blocks
|
|
1027
1195
|
const block = await r.getBlock(this.bstore);
|
|
1028
|
-
|
|
1196
|
+
let event: IThreadEvent;
|
|
1029
1197
|
if (block instanceof Event) {
|
|
1030
1198
|
event = block;
|
|
1031
1199
|
} else {
|
|
1032
|
-
event = await EventFromNode(block as Node)
|
|
1200
|
+
event = await EventFromNode(block as Node);
|
|
1033
1201
|
}
|
|
1034
1202
|
|
|
1035
1203
|
const header = await event.getHeader(this.bstore);
|
|
1036
1204
|
const body = await event.getBody(this.bstore);
|
|
1037
|
-
|
|
1205
|
+
|
|
1038
1206
|
// Store internal blocks
|
|
1039
1207
|
await this.addMany([event, header, body]);
|
|
1040
1208
|
tRecords.push(newRecord(r, tid, lid) as ThreadRecord);
|
|
1041
|
-
}
|
|
1209
|
+
}
|
|
1042
1210
|
return [tRecords, head];
|
|
1043
1211
|
}
|
|
1044
1212
|
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
1213
|
/**
|
|
1048
1214
|
* Check if a record exists
|
|
1049
1215
|
*/
|
|
1050
|
-
async isKnown(
|
|
1051
|
-
return await this.bstore.has(
|
|
1216
|
+
async isKnown(rec: CID): Promise<boolean> {
|
|
1217
|
+
return await this.bstore.has(rec);
|
|
1052
1218
|
}
|
|
1053
1219
|
|
|
1220
|
+
/**
|
|
1221
|
+
* 获取记录
|
|
1222
|
+
* 从给定的threaddb 和CID获取记录
|
|
1223
|
+
*
|
|
1224
|
+
* @param ctx 上下文
|
|
1225
|
+
* @param id threaddb ID
|
|
1226
|
+
* @param rid 记录CID
|
|
1227
|
+
* @returns 检索到的记录
|
|
1228
|
+
* @throws 如果无法获取服务密钥或记录
|
|
1229
|
+
*/
|
|
1230
|
+
async getRecord(id: ThreadID, rid: CID): Promise<IRecord> {
|
|
1231
|
+
// 从存储中获取服务密钥
|
|
1232
|
+
const serviceKey = await this.logstore.keyBook.serviceKey(id);
|
|
1054
1233
|
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
* @throws 如果无法获取服务密钥或记录
|
|
1064
|
-
*/
|
|
1065
|
-
async getRecord( id: ThreadID, rid: CID): Promise<IRecord> {
|
|
1066
|
-
// 从存储中获取服务密钥
|
|
1067
|
-
const serviceKey = await this.logstore.keyBook.serviceKey(id);
|
|
1068
|
-
|
|
1069
|
-
// 检查服务密钥是否存在
|
|
1070
|
-
if (!serviceKey) {
|
|
1071
|
-
throw new Error("A service-key is required to get records");
|
|
1234
|
+
// 检查服务密钥是否存在
|
|
1235
|
+
if (!serviceKey) {
|
|
1236
|
+
throw new Error("A service-key is required to get records");
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
const sk = SymmetricKey.fromSymKey(serviceKey);
|
|
1240
|
+
// 使用CBOR获取记录
|
|
1241
|
+
return await GetRecord(this.dagService, rid, sk);
|
|
1072
1242
|
}
|
|
1073
|
-
|
|
1074
|
-
const sk = SymmetricKey.fromSymKey(serviceKey)
|
|
1075
|
-
// 使用CBOR获取记录
|
|
1076
|
-
return await GetRecord(this.dagService, rid, sk);
|
|
1077
|
-
}
|
|
1078
|
-
/**
|
|
1243
|
+
/**
|
|
1079
1244
|
* Set a thread log checkpoint
|
|
1080
1245
|
*/
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
await this.logstore.metadata.putBytes(tid, key, rcid.bytes);
|
|
1246
|
+
async setThreadLogPoint(
|
|
1247
|
+
tid: ThreadID,
|
|
1248
|
+
lid: PeerId,
|
|
1249
|
+
counter: number,
|
|
1250
|
+
rcid: CID
|
|
1251
|
+
): Promise<void> {
|
|
1252
|
+
// Only store checkpoints at multiples of 10000
|
|
1253
|
+
if (counter % 10000 !== 0) {
|
|
1254
|
+
return;
|
|
1091
1255
|
}
|
|
1256
|
+
// Create key for checkpoint
|
|
1257
|
+
const key = `${lid.toString()}/${counter}`;
|
|
1258
|
+
|
|
1259
|
+
// Store checkpoint
|
|
1260
|
+
await this.logstore.metadata.putBytes(tid, key, rcid.bytes);
|
|
1261
|
+
}
|
|
1092
1262
|
|
|
1093
1263
|
/**
|
|
1094
1264
|
* Get the current head of a log
|
|
1095
1265
|
*/
|
|
1096
1266
|
async currentHead(tid: ThreadID, lid: PeerId): Promise<Head> {
|
|
1097
1267
|
const heads = await this.logstore.headBook.heads(tid, lid);
|
|
1098
|
-
|
|
1268
|
+
|
|
1099
1269
|
if (heads.length > 0) {
|
|
1100
1270
|
return heads[0]!;
|
|
1101
1271
|
} else {
|
|
1102
1272
|
return await getHeadUndef();
|
|
1103
1273
|
}
|
|
1104
1274
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
peerMap.set(pid.toString(), pid);
|
|
1120
|
-
} catch (err) {
|
|
1121
|
-
// Skip addresses that can't be parsed
|
|
1275
|
+
/**
|
|
1276
|
+
* Extract unique peer IDs from addresses
|
|
1277
|
+
*/
|
|
1278
|
+
async uniquePeers(addrs: TMultiaddr[]): Promise<PeerId[]> {
|
|
1279
|
+
const peerMap = new Map<string, PeerId>();
|
|
1280
|
+
|
|
1281
|
+
for (const addr of addrs) {
|
|
1282
|
+
try {
|
|
1283
|
+
const [pid, callable] = await this.callablePeer(addr);
|
|
1284
|
+
|
|
1285
|
+
if (!callable) {
|
|
1286
|
+
continue;
|
|
1122
1287
|
}
|
|
1288
|
+
|
|
1289
|
+
peerMap.set(pid.toString(), pid);
|
|
1290
|
+
} catch (err) {
|
|
1291
|
+
// Skip addresses that can't be parsed
|
|
1123
1292
|
}
|
|
1124
|
-
|
|
1125
|
-
return Array.from(peerMap.values());
|
|
1126
1293
|
}
|
|
1127
|
-
|
|
1128
1294
|
|
|
1295
|
+
return Array.from(peerMap.values());
|
|
1296
|
+
}
|
|
1129
1297
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1298
|
+
/**
|
|
1299
|
+
* Extract peer ID from multiaddress and check if callable
|
|
1300
|
+
*/
|
|
1301
|
+
async callablePeer(addr: TMultiaddr): Promise<[PeerId, boolean]> {
|
|
1302
|
+
const p = addr.getPeerId();
|
|
1303
|
+
if (!p) {
|
|
1304
|
+
throw new Error("Address does not contain peer ID");
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
const pid = peerIdFromString(p);
|
|
1308
|
+
|
|
1309
|
+
// Don't call ourselves
|
|
1310
|
+
if (pid.toString() === this.hostID) {
|
|
1311
|
+
return [pid, false];
|
|
1312
|
+
}
|
|
1313
|
+
return [pid, true];
|
|
1314
|
+
}
|
|
1147
1315
|
|
|
1148
1316
|
/**
|
|
1149
1317
|
* 从指定对等点获取记录
|
|
@@ -1162,156 +1330,113 @@ async getRecord( id: ThreadID, rid: CID): Promise<IRecord> {
|
|
|
1162
1330
|
const recordCollector = new RecordCollector();
|
|
1163
1331
|
|
|
1164
1332
|
// 确定需要获取的对等点数量
|
|
1165
|
-
const needFetched = Math.min(
|
|
1166
|
-
let
|
|
1167
|
-
|
|
1168
|
-
const fetchState = {
|
|
1169
|
-
fetchedPeers: 0
|
|
1170
|
-
};
|
|
1171
|
-
let mainTimeoutId: NodeJS.Timeout | undefined;
|
|
1172
|
-
// 设置主超时
|
|
1173
|
-
const timeoutPromise = new Promise<void>((resolve) => {
|
|
1174
|
-
mainTimeoutId = setTimeout(() => {
|
|
1175
|
-
resolve(); // 超时时总是resolve,在外部判断是否有足够的数据
|
|
1176
|
-
}, 30000);
|
|
1177
|
-
});
|
|
1178
|
-
let dealedCount = 0;
|
|
1333
|
+
const needFetched = Math.min(2, peers.length);
|
|
1334
|
+
let fetchedPeers = 0;
|
|
1335
|
+
|
|
1179
1336
|
// 创建一个 Promise 在足够的对等点响应时解析
|
|
1180
1337
|
const fetchPromise = new Promise<void>((resolve) => {
|
|
1181
|
-
//
|
|
1338
|
+
// 当所有查询完成时,解析 Promise
|
|
1182
1339
|
const checkComplete = () => {
|
|
1183
|
-
if (
|
|
1184
|
-
resolved = true;
|
|
1340
|
+
if (fetchedPeers >= needFetched) {
|
|
1185
1341
|
resolve();
|
|
1186
1342
|
}
|
|
1187
1343
|
};
|
|
1188
1344
|
|
|
1189
1345
|
// 从每个对等点查询记录
|
|
1190
1346
|
const fetchPromises = peers.map(async (peerId) => {
|
|
1191
|
-
|
|
1192
|
-
let timeoutId: NodeJS.Timeout | undefined;
|
|
1193
|
-
// 为每个peer设置独立的超时
|
|
1194
|
-
const peerTimeout = new Promise<never>((_, reject) => {
|
|
1195
|
-
timeoutId = setTimeout(() => reject(new Error(`Peer ${peerId} timeout after 30s`)), 30000);
|
|
1196
|
-
});
|
|
1197
|
-
|
|
1198
1347
|
try {
|
|
1199
|
-
|
|
1200
|
-
await
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1348
|
+
//连接到指定peerId,返回一个Client
|
|
1349
|
+
const [client,err] = await this.getClient(peerId);
|
|
1350
|
+
if (!client) {
|
|
1351
|
+
console.error(`Error getting records from peer ${peerId},no client,errinfo:`, err);
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
const dbClient = new DBClient(client,this.dc,this,this.logstore);
|
|
1355
|
+
|
|
1356
|
+
// 这里使用一个队列来控制并发,类似于 Go 代码中的 queueGetRecords
|
|
1357
|
+
const records = await dbClient.getRecordsFromPeer( req, serviceKey);
|
|
1358
|
+
|
|
1359
|
+
// 更新收集器
|
|
1360
|
+
Object.entries(records).forEach(([logId, rs]) => {
|
|
1361
|
+
recordCollector.updateHeadCounter(logId, rs.counter);
|
|
1362
|
+
rs.records.forEach(record => {
|
|
1363
|
+
recordCollector.store(logId, record);
|
|
1364
|
+
});
|
|
1365
|
+
});
|
|
1208
1366
|
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
recordCollector.store(logId, record);
|
|
1216
|
-
});
|
|
1217
|
-
});
|
|
1218
|
-
|
|
1219
|
-
fetchState.fetchedPeers++;
|
|
1220
|
-
|
|
1221
|
-
// 每次成功获取记录后立即检查是否满足条件
|
|
1222
|
-
checkComplete();
|
|
1223
|
-
})(),
|
|
1224
|
-
peerTimeout
|
|
1225
|
-
]);
|
|
1367
|
+
fetchedPeers++;
|
|
1368
|
+
|
|
1369
|
+
// 如果获取了记录并且达到了所需的对等点数量,则解析 Promise
|
|
1370
|
+
if (Object.keys(records).length > 0 && fetchedPeers >= needFetched) {
|
|
1371
|
+
resolve();
|
|
1372
|
+
}
|
|
1226
1373
|
} catch (err) {
|
|
1227
1374
|
console.error(`Error getting records from peer ${peerId}:`, err);
|
|
1228
|
-
// 错误不影响其他peer的执行
|
|
1229
|
-
} finally {
|
|
1230
|
-
// 清除定时器,防止内存泄漏
|
|
1231
|
-
if (timeoutId) {
|
|
1232
|
-
clearTimeout(timeoutId);
|
|
1233
|
-
}
|
|
1234
|
-
dealedCount ++;
|
|
1235
|
-
}
|
|
1236
|
-
});
|
|
1237
|
-
|
|
1238
|
-
// 使用 Promise.allSettled 替代 Promise.all,避免被单个失败阻塞
|
|
1239
|
-
// 同时设置额外的兜底检查机制
|
|
1240
|
-
Promise.allSettled(fetchPromises).then(() => {
|
|
1241
|
-
if (!resolved) {
|
|
1242
|
-
checkComplete();
|
|
1243
1375
|
}
|
|
1244
1376
|
});
|
|
1245
1377
|
|
|
1246
|
-
//
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
clearInterval(checkInterval);
|
|
1250
|
-
} else {
|
|
1251
|
-
checkComplete();
|
|
1252
|
-
}
|
|
1253
|
-
}, 5000); // 每5秒检查一次
|
|
1254
|
-
|
|
1255
|
-
// 确保interval最终被清理
|
|
1256
|
-
setTimeout(() => clearInterval(checkInterval), 35000);
|
|
1378
|
+
// 当所有查询完成或失败时,检查是否需要解析 Promise
|
|
1379
|
+
Promise.all(fetchPromises.map(p => p.catch(e => e)))
|
|
1380
|
+
.then(() => checkComplete());
|
|
1257
1381
|
});
|
|
1258
1382
|
|
|
1259
|
-
|
|
1383
|
+
// 设置超时
|
|
1384
|
+
const timeoutPromise = new Promise<void>((resolve) => {
|
|
1385
|
+
setTimeout(() => {
|
|
1386
|
+
resolve(); // 超时时总是resolve,在外部判断是否有足够的数据
|
|
1387
|
+
}, 60000);
|
|
1388
|
+
});
|
|
1260
1389
|
|
|
1261
1390
|
// 等待获取足够的记录或超时
|
|
1262
1391
|
await Promise.race([fetchPromise, timeoutPromise]);
|
|
1263
|
-
//
|
|
1264
|
-
if (
|
|
1265
|
-
clearTimeout(mainTimeoutId);
|
|
1266
|
-
}
|
|
1267
|
-
// 检查是否获取到足够的数据
|
|
1268
|
-
if (fetchState.fetchedPeers === 0 && dealedCount < peers.length) {
|
|
1392
|
+
// 检查是否获取到足够的数据
|
|
1393
|
+
if (fetchedPeers === 0) {
|
|
1269
1394
|
throw new Error("Fetch records timeout: no peers responded");
|
|
1270
1395
|
}
|
|
1271
|
-
|
|
1272
|
-
// 如果有数据就返回(不管是1个还是2个节点)
|
|
1273
|
-
console.log(`获取记录完成: ${fetchState.fetchedPeers}/${needFetched} 个节点响应`);
|
|
1396
|
+
// 返回收集到的记录
|
|
1274
1397
|
return recordCollector.list();
|
|
1275
1398
|
} catch (err) {
|
|
1276
1399
|
console.error("getRecords error:", err);
|
|
1277
1400
|
throw err;
|
|
1278
1401
|
}
|
|
1279
1402
|
}
|
|
1280
|
-
|
|
1403
|
+
|
|
1281
1404
|
/**
|
|
1282
1405
|
* 构建获取记录的请求
|
|
1283
1406
|
*/
|
|
1284
1407
|
async buildGetRecordsRequest(
|
|
1285
1408
|
tid: ThreadID,
|
|
1286
|
-
offsets: Record<string,Head>,
|
|
1409
|
+
offsets: Record<string, Head>,
|
|
1287
1410
|
limit: number
|
|
1288
|
-
): Promise<{ req: net_pb.pb.IGetRecordsRequest
|
|
1411
|
+
): Promise<{ req: net_pb.pb.IGetRecordsRequest; serviceKey: SymKey }> {
|
|
1289
1412
|
try {
|
|
1290
1413
|
// 从存储中获取服务密钥
|
|
1291
1414
|
const serviceKey = await this.logstore.keyBook.serviceKey(tid);
|
|
1292
|
-
|
|
1415
|
+
|
|
1293
1416
|
if (!serviceKey) {
|
|
1294
1417
|
throw new Error("A service-key is required to request records");
|
|
1295
1418
|
}
|
|
1296
|
-
|
|
1419
|
+
|
|
1297
1420
|
// 创建日志条目
|
|
1298
1421
|
const logs = Object.entries(offsets).map(([logId, offset]) => {
|
|
1299
1422
|
const pbLog: net_pb.pb.GetRecordsRequest.Body.ILogEntry = {
|
|
1300
1423
|
logID: PeerIDConverter.toBytes(logId),
|
|
1301
1424
|
limit: limit,
|
|
1302
1425
|
counter: offset.counter,
|
|
1303
|
-
offset: offset.id
|
|
1304
|
-
|
|
1426
|
+
offset: offset.id
|
|
1427
|
+
? CidConverter.toBytes(offset.id)
|
|
1428
|
+
: new Uint8Array(),
|
|
1429
|
+
};
|
|
1305
1430
|
return pbLog;
|
|
1306
|
-
|
|
1307
|
-
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1308
1433
|
// 构建请求体
|
|
1309
1434
|
const body: net_pb.pb.GetRecordsRequest.IBody = {
|
|
1310
1435
|
threadID: ThreadIDConverter.toBytes(tid.toString()),
|
|
1311
1436
|
serviceKey: serviceKey.raw,
|
|
1312
|
-
logs: logs
|
|
1437
|
+
logs: logs,
|
|
1313
1438
|
};
|
|
1314
|
-
|
|
1439
|
+
|
|
1315
1440
|
// 创建请求
|
|
1316
1441
|
const req = new net_pb.pb.GetRecordsRequest();
|
|
1317
1442
|
req.body = body;
|
|
@@ -1322,9 +1447,8 @@ async getRecord( id: ThreadID, rid: CID): Promise<IRecord> {
|
|
|
1322
1447
|
}
|
|
1323
1448
|
}
|
|
1324
1449
|
|
|
1325
|
-
|
|
1326
1450
|
/**
|
|
1327
|
-
* 连接应用到指定threaddb
|
|
1451
|
+
* 连接应用到指定threaddb
|
|
1328
1452
|
* @param app 应用程序对象
|
|
1329
1453
|
* @param id threaddb ID
|
|
1330
1454
|
* @returns 返回一个应用连接器
|
|
@@ -1332,461 +1456,497 @@ async getRecord( id: ThreadID, rid: CID): Promise<IRecord> {
|
|
|
1332
1456
|
*/
|
|
1333
1457
|
async connectApp(app: App, id: ThreadID): Promise<Connector> {
|
|
1334
1458
|
// 验证threaddb ID
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1459
|
+
if (!id.isDefined()) {
|
|
1460
|
+
throw new Error("Invalid thread ID");
|
|
1461
|
+
}
|
|
1338
1462
|
// 获取threaddb信息
|
|
1339
1463
|
let info;
|
|
1340
1464
|
try {
|
|
1341
1465
|
info = await this.getThreadWithAddrs(id);
|
|
1342
1466
|
} catch (err) {
|
|
1343
|
-
throw new Error(
|
|
1467
|
+
throw new Error(
|
|
1468
|
+
`Error getting thread ${id.toString()}: ${
|
|
1469
|
+
err instanceof Error ? err.message : String(err)
|
|
1470
|
+
}`
|
|
1471
|
+
);
|
|
1344
1472
|
}
|
|
1345
|
-
|
|
1473
|
+
|
|
1346
1474
|
// 创建应用连接器
|
|
1347
1475
|
let con;
|
|
1348
1476
|
try {
|
|
1349
|
-
con =
|
|
1477
|
+
con = new Connector(this, app, info);
|
|
1350
1478
|
} catch (err) {
|
|
1351
|
-
throw new Error(
|
|
1479
|
+
throw new Error(
|
|
1480
|
+
`Error making connector ${id.toString()}: ${
|
|
1481
|
+
err instanceof Error ? err.message : String(err)
|
|
1482
|
+
}`
|
|
1483
|
+
);
|
|
1352
1484
|
}
|
|
1353
|
-
|
|
1485
|
+
|
|
1354
1486
|
// 添加连接器到网络
|
|
1355
1487
|
this.addConnector(id, con);
|
|
1356
|
-
|
|
1488
|
+
|
|
1357
1489
|
return con;
|
|
1358
1490
|
}
|
|
1359
1491
|
|
|
1492
|
+
/**
|
|
1493
|
+
* 获取线程的所有日志
|
|
1494
|
+
* 返回线程的日志列表和线程信息
|
|
1495
|
+
*
|
|
1496
|
+
* @param tid 线程ID
|
|
1497
|
+
* @returns 日志数组和线程信息
|
|
1498
|
+
*/
|
|
1499
|
+
async getPbLogs(tid: ThreadID): Promise<[net_pb.pb.ILog[], IThreadInfo]> {
|
|
1500
|
+
try {
|
|
1501
|
+
// 从存储中获取线程信息
|
|
1502
|
+
const info = await this.logstore.getThread(tid);
|
|
1360
1503
|
|
|
1504
|
+
// 创建日志数组
|
|
1505
|
+
const logs: net_pb.pb.ILog[] = [];
|
|
1361
1506
|
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
const info = await this.logstore.getThread(tid);
|
|
1374
|
-
|
|
1375
|
-
// 创建日志数组
|
|
1376
|
-
const logs: net_pb.pb.ILog[] = [];
|
|
1377
|
-
|
|
1378
|
-
// 将每个日志信息转换为protobuf格式
|
|
1379
|
-
for (const logInfo of info.logs) {
|
|
1380
|
-
logs.push(await logToProto(logInfo));
|
|
1507
|
+
// 将每个日志信息转换为protobuf格式
|
|
1508
|
+
for (const logInfo of info.logs) {
|
|
1509
|
+
logs.push(await logToProto(logInfo));
|
|
1510
|
+
}
|
|
1511
|
+
return [logs, info];
|
|
1512
|
+
} catch (err) {
|
|
1513
|
+
throw new Error(
|
|
1514
|
+
`Failed to get logs for thread ${tid}: ${
|
|
1515
|
+
err instanceof Error ? err.message : String(err)
|
|
1516
|
+
}`
|
|
1517
|
+
);
|
|
1381
1518
|
}
|
|
1382
|
-
return [logs, info];
|
|
1383
|
-
} catch (err) {
|
|
1384
|
-
throw new Error(`Failed to get logs for thread ${tid}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1385
1519
|
}
|
|
1386
|
-
}
|
|
1387
1520
|
|
|
1388
|
-
/**
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
async preLoadLogs(tid: ThreadID, logs: net_pb.pb.Log[]): Promise<void> {
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1521
|
+
/**
|
|
1522
|
+
* 预加载日志到线程
|
|
1523
|
+
* 浏览器兼容版本
|
|
1524
|
+
*
|
|
1525
|
+
* @param tid 线程ID
|
|
1526
|
+
* @param logs 协议缓冲区日志数组
|
|
1527
|
+
*/
|
|
1528
|
+
async preLoadLogs(tid: ThreadID, logs: net_pb.pb.Log[]): Promise<void> {
|
|
1529
|
+
try {
|
|
1530
|
+
// 创建一个与日志数组相同长度的ThreadLogInfo数组
|
|
1531
|
+
const lgs: IThreadLogInfo[] = [];
|
|
1532
|
+
|
|
1533
|
+
// 遍历并转换每个日志
|
|
1534
|
+
for (let i = 0; i < logs.length; i++) {
|
|
1535
|
+
lgs.push(await logFromProto(logs[i]!));
|
|
1536
|
+
}
|
|
1537
|
+
// 调用创建外部日志的方法
|
|
1538
|
+
await this.createExternalLogsIfNotExistForPreload(tid, lgs);
|
|
1539
|
+
} catch (err) {
|
|
1540
|
+
throw new Error(
|
|
1541
|
+
`Failed to preload logs: ${
|
|
1542
|
+
err instanceof Error ? err.message : String(err)
|
|
1543
|
+
}`
|
|
1544
|
+
);
|
|
1403
1545
|
}
|
|
1404
|
-
// 调用创建外部日志的方法
|
|
1405
|
-
await this.createExternalLogsIfNotExistForPreload(tid, lgs);
|
|
1406
|
-
} catch (err) {
|
|
1407
|
-
throw new Error(`Failed to preload logs: ${err instanceof Error ? err.message : String(err)}`);
|
|
1408
1546
|
}
|
|
1409
|
-
}
|
|
1410
1547
|
|
|
1548
|
+
/**
|
|
1549
|
+
* 创建外部日志(如果不存在)
|
|
1550
|
+
* 创建的日志将使用未定义CID作为当前头部
|
|
1551
|
+
* 此方法是线程安全的
|
|
1552
|
+
*
|
|
1553
|
+
* @param tid 线程ID
|
|
1554
|
+
* @param logs 日志信息数组
|
|
1555
|
+
*/
|
|
1556
|
+
async createExternalLogsIfNotExist(
|
|
1557
|
+
tid: ThreadID,
|
|
1558
|
+
logs: IThreadLogInfo[]
|
|
1559
|
+
): Promise<void> {
|
|
1560
|
+
const mutex = this.getMutexForThread(tid.toString());
|
|
1561
|
+
await mutex.acquire();
|
|
1562
|
+
try {
|
|
1563
|
+
for (const log of logs) {
|
|
1564
|
+
try {
|
|
1565
|
+
const heads = await this.logstore.headBook.heads(tid, log.id);
|
|
1411
1566
|
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
try {
|
|
1426
|
-
const heads = await this.logstore.headBook.heads(tid, log.id);
|
|
1427
|
-
|
|
1428
|
-
if (heads.length === 0) {
|
|
1429
|
-
log.head = await getHeadUndef();
|
|
1430
|
-
await this.logstore.addLog(tid, log);
|
|
1431
|
-
} else {
|
|
1432
|
-
await this.logstore.addrBook.addAddrs(
|
|
1433
|
-
tid,
|
|
1434
|
-
log.id,
|
|
1435
|
-
log.addrs,
|
|
1436
|
-
PermanentAddrTTL
|
|
1437
|
-
);
|
|
1567
|
+
if (heads.length === 0) {
|
|
1568
|
+
log.head = await getHeadUndef();
|
|
1569
|
+
await this.logstore.addLog(tid, log);
|
|
1570
|
+
} else {
|
|
1571
|
+
await this.logstore.addrBook.addAddrs(
|
|
1572
|
+
tid,
|
|
1573
|
+
log.id,
|
|
1574
|
+
log.addrs,
|
|
1575
|
+
PermanentAddrTTL
|
|
1576
|
+
);
|
|
1577
|
+
}
|
|
1578
|
+
} catch (err) {
|
|
1579
|
+
continue;
|
|
1438
1580
|
}
|
|
1439
|
-
} catch (err) {
|
|
1440
|
-
continue
|
|
1441
1581
|
}
|
|
1582
|
+
} finally {
|
|
1583
|
+
mutex.release();
|
|
1442
1584
|
}
|
|
1443
|
-
} finally {
|
|
1444
|
-
mutex.release();
|
|
1445
1585
|
}
|
|
1446
|
-
}
|
|
1447
1586
|
|
|
1448
|
-
/**
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
async createExternalLogsIfNotExistForPreload(
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
tid,
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1587
|
+
/**
|
|
1588
|
+
* 预加载时创建外部日志(如果不存在)
|
|
1589
|
+
* 浏览器兼容版本
|
|
1590
|
+
*
|
|
1591
|
+
* @param tid 线程ID
|
|
1592
|
+
* @param logs 日志信息数组
|
|
1593
|
+
*/
|
|
1594
|
+
async createExternalLogsIfNotExistForPreload(
|
|
1595
|
+
tid: ThreadID,
|
|
1596
|
+
logs: IThreadLogInfo[]
|
|
1597
|
+
): Promise<void> {
|
|
1598
|
+
const mutex = this.getMutexForThread(tid.toString());
|
|
1599
|
+
await mutex.acquire();
|
|
1600
|
+
try {
|
|
1601
|
+
for (const log of logs) {
|
|
1602
|
+
try {
|
|
1603
|
+
const heads = await this.logstore.headBook.heads(tid, log.id);
|
|
1604
|
+
if (heads.length === 0) {
|
|
1605
|
+
await this.logstore.addLog(tid, log);
|
|
1606
|
+
} else {
|
|
1607
|
+
await this.logstore.addrBook.addAddrs(
|
|
1608
|
+
tid,
|
|
1609
|
+
log.id,
|
|
1610
|
+
log.addrs,
|
|
1611
|
+
PermanentAddrTTL
|
|
1612
|
+
);
|
|
1613
|
+
}
|
|
1614
|
+
} catch (err) {
|
|
1615
|
+
continue;
|
|
1471
1616
|
}
|
|
1472
|
-
} catch (err) {
|
|
1473
|
-
continue;
|
|
1474
1617
|
}
|
|
1618
|
+
} finally {
|
|
1619
|
+
mutex.release();
|
|
1475
1620
|
}
|
|
1476
|
-
} finally {
|
|
1477
|
-
mutex.release();
|
|
1478
1621
|
}
|
|
1479
|
-
}
|
|
1480
1622
|
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
async createRecord(
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
): Promise<IThreadRecord> {
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
// 获取或创建日志
|
|
1510
|
-
const lg = await this.getOrCreateLog(id, identity);
|
|
1511
|
-
|
|
1512
|
-
// 创建新记录
|
|
1513
|
-
const r = await this.newRecord(id, lg, body, identity);
|
|
1514
|
-
|
|
1515
|
-
// 创建threaddb 记录
|
|
1516
|
-
const tr = newRecord(r, id, lg.id);
|
|
1517
|
-
if (!lg.head){
|
|
1518
|
-
lg.head = {
|
|
1519
|
-
counter: 0
|
|
1623
|
+
/**
|
|
1624
|
+
* 创建新的记录
|
|
1625
|
+
*
|
|
1626
|
+
* @param id threadID
|
|
1627
|
+
* @param body 节点内容
|
|
1628
|
+
* @param options 选项
|
|
1629
|
+
* @returns 创建的threaddb记录
|
|
1630
|
+
*/
|
|
1631
|
+
async createRecord(
|
|
1632
|
+
id: ThreadID,
|
|
1633
|
+
body: IPLDNode,
|
|
1634
|
+
options: { token?: ThreadToken; apiToken?: Token } = {}
|
|
1635
|
+
): Promise<IThreadRecord> {
|
|
1636
|
+
try {
|
|
1637
|
+
if (this.context.publicKey === undefined) {
|
|
1638
|
+
throw new Error("No identity provided for creating record");
|
|
1639
|
+
}
|
|
1640
|
+
// 验证身份,用节点的pubkey
|
|
1641
|
+
const identity = this.context.publicKey;
|
|
1642
|
+
// 获取并验证连接器
|
|
1643
|
+
const [con, ok] = this.getConnectorProtected(id, options.apiToken);
|
|
1644
|
+
|
|
1645
|
+
if (!ok) {
|
|
1646
|
+
throw new Error("Cannot create record: thread in use");
|
|
1647
|
+
} else if (con) {
|
|
1648
|
+
await con.validateNetRecordBody(body, identity as Ed25519PubKey);
|
|
1520
1649
|
}
|
|
1521
|
-
}
|
|
1522
|
-
|
|
1523
|
-
// 更新头部信息
|
|
1524
|
-
const head: Head = {
|
|
1525
|
-
id: tr.value().cid(),
|
|
1526
|
-
counter: lg.head.counter + 1
|
|
1527
|
-
};
|
|
1528
|
-
|
|
1529
|
-
await this.logstore.headBook.setHead(id, lg.id, head);
|
|
1530
|
-
|
|
1531
|
-
// 设置记录点(每10000个记录设置一个检查点)
|
|
1532
|
-
await this.setThreadLogPoint(id, lg.id, lg.head?.counter + 1, tr.value().cid());
|
|
1533
|
-
|
|
1534
|
-
console.debug(`Created record ${tr.value().cid()} (thread=${id}, log=${lg.id})`);
|
|
1535
|
-
|
|
1536
|
-
// 推送记录到节点
|
|
1537
|
-
if (this.server) {
|
|
1538
|
-
this.pushRecord(id, lg.id, tr.value(), lg.head.counter + 1);
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
|
-
return tr;
|
|
1542
|
-
} catch (error) {
|
|
1543
|
-
throw error;
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
1650
|
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
*/
|
|
1550
|
-
async getOrCreateLog(id: ThreadID, identity: PublicKey): Promise<IThreadLogInfo> {
|
|
1551
|
-
// 默认使用当前主机身份,如果未提供
|
|
1552
|
-
if (!identity) {
|
|
1553
|
-
throw new Error("No identity provided");
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
// 尝试获取此身份的现有日志ID
|
|
1557
|
-
const lidb = await this.logstore.metadata.getBytes(id, identity.toString());
|
|
1558
|
-
|
|
1559
|
-
// 检查旧式"自有"日志
|
|
1560
|
-
if (!lidb && identity.equals(this.context.publicKey)) {
|
|
1561
|
-
const thrd = await this.logstore.getThread(id);
|
|
1562
|
-
const ownLog = thrd.getFirstPrivKeyLog();
|
|
1563
|
-
if (ownLog) {
|
|
1564
|
-
return ownLog;
|
|
1565
|
-
}
|
|
1566
|
-
} else if (lidb) {
|
|
1567
|
-
// 获取现有日志
|
|
1568
|
-
const lidbstr = new TextDecoder().decode(lidb);
|
|
1569
|
-
const lid = peerIdFromString(lidbstr);
|
|
1570
|
-
return this.logstore.getLog(id, lid);
|
|
1571
|
-
}
|
|
1572
|
-
|
|
1573
|
-
// 创建新日志,如果不存在
|
|
1574
|
-
return this.createLog(id, undefined, identity);
|
|
1575
|
-
}
|
|
1651
|
+
// 获取或创建日志
|
|
1652
|
+
const lg = await this.getOrCreateLog(id, identity);
|
|
1576
1653
|
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
throw new Error("No identity provided for creating record");
|
|
1588
|
-
}
|
|
1589
|
-
const heads = await this.logstore.headBook.heads(id, lg.id)
|
|
1590
|
-
// 创建事件
|
|
1591
|
-
const sk = SymmetricKey.fromSymKey(serviceKey);
|
|
1592
|
-
const readKey = await this.logstore.keyBook.readKey(id);
|
|
1593
|
-
const rk = readKey ? SymmetricKey.fromSymKey(readKey) : null;
|
|
1594
|
-
if (!rk) {
|
|
1595
|
-
throw new Error("No read key for thread");
|
|
1596
|
-
}
|
|
1597
|
-
const event = await CreateEvent(
|
|
1598
|
-
this.bstore,
|
|
1599
|
-
body as Node,
|
|
1600
|
-
rk
|
|
1601
|
-
);
|
|
1602
|
-
// 将事件保存到存储
|
|
1603
|
-
await this.bstore.put(event.cid(), event.data());
|
|
1604
|
-
let prev:CID|undefined = undefined;
|
|
1605
|
-
if (heads && heads.length > 0 ){
|
|
1606
|
-
prev = heads[0]!.id;
|
|
1607
|
-
}
|
|
1608
|
-
const rec = await CreateRecord(
|
|
1609
|
-
this.bstore,
|
|
1610
|
-
{
|
|
1611
|
-
block: event,
|
|
1612
|
-
prev: prev,
|
|
1613
|
-
key: lg.privKey as Ed25519PrivKey,
|
|
1614
|
-
pubKey: this.context.publicKey,
|
|
1615
|
-
serviceKey: sk
|
|
1616
|
-
} as CreateRecordConfig)
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
return rec
|
|
1620
|
-
}
|
|
1654
|
+
// 创建新记录
|
|
1655
|
+
const r = await this.newRecord(id, lg, body, identity);
|
|
1656
|
+
|
|
1657
|
+
// 创建threaddb 记录
|
|
1658
|
+
const tr = newRecord(r, id, lg.id);
|
|
1659
|
+
if (!lg.head) {
|
|
1660
|
+
lg.head = {
|
|
1661
|
+
counter: 0,
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1621
1664
|
|
|
1665
|
+
// 更新头部信息
|
|
1666
|
+
const head: Head = {
|
|
1667
|
+
id: tr.value().cid(),
|
|
1668
|
+
counter: lg.head.counter + 1,
|
|
1669
|
+
};
|
|
1622
1670
|
|
|
1671
|
+
await this.logstore.headBook.setHead(id, lg.id, head);
|
|
1623
1672
|
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
if (l.addrs && l.addrs.length > 0) {
|
|
1640
|
-
addrs.push(...l.addrs);
|
|
1673
|
+
// 设置记录点(每10000个记录设置一个检查点)
|
|
1674
|
+
await this.setThreadLogPoint(
|
|
1675
|
+
id,
|
|
1676
|
+
lg.id,
|
|
1677
|
+
lg.head?.counter + 1,
|
|
1678
|
+
tr.value().cid()
|
|
1679
|
+
);
|
|
1680
|
+
|
|
1681
|
+
console.debug(
|
|
1682
|
+
`Created record ${tr.value().cid()} (thread=${id}, log=${lg.id})`
|
|
1683
|
+
);
|
|
1684
|
+
|
|
1685
|
+
// 推送记录到节点
|
|
1686
|
+
if (this.server) {
|
|
1687
|
+
this.pushRecord(id, lg.id, tr.value(), lg.head.counter + 1);
|
|
1641
1688
|
}
|
|
1689
|
+
|
|
1690
|
+
return tr;
|
|
1691
|
+
} catch (error) {
|
|
1692
|
+
throw error;
|
|
1642
1693
|
}
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
/**
|
|
1697
|
+
* 获取或创建当前身份的日志
|
|
1698
|
+
*/
|
|
1699
|
+
async getOrCreateLog(
|
|
1700
|
+
id: ThreadID,
|
|
1701
|
+
identity: PublicKey
|
|
1702
|
+
): Promise<IThreadLogInfo> {
|
|
1703
|
+
// 默认使用当前主机身份,如果未提供
|
|
1704
|
+
if (!identity) {
|
|
1705
|
+
throw new Error("No identity provided");
|
|
1646
1706
|
}
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
}
|
|
1658
|
-
const dbClient = new DBClient(client, this.dc,this,this.logstore);
|
|
1659
|
-
// 启动异步推送(不等待完成)
|
|
1660
|
-
dbClient.pushRecordToPeer(tid, lid, rec, counter);
|
|
1661
|
-
}catch (err) {
|
|
1662
|
-
continue; // 继续处理其他对等点
|
|
1707
|
+
|
|
1708
|
+
// 尝试获取此身份的现有日志ID
|
|
1709
|
+
const lidb = await this.logstore.metadata.getBytes(id, identity.toString());
|
|
1710
|
+
|
|
1711
|
+
// 检查旧式"自有"日志
|
|
1712
|
+
if (!lidb && identity.equals(this.context.publicKey)) {
|
|
1713
|
+
const thrd = await this.logstore.getThread(id);
|
|
1714
|
+
const ownLog = thrd.getFirstPrivKeyLog();
|
|
1715
|
+
if (ownLog) {
|
|
1716
|
+
return ownLog;
|
|
1663
1717
|
}
|
|
1664
|
-
|
|
1718
|
+
} else if (lidb) {
|
|
1719
|
+
// 获取现有日志
|
|
1720
|
+
const lidbstr = new TextDecoder().decode(lidb);
|
|
1721
|
+
const lid = peerIdFromString(lidbstr);
|
|
1722
|
+
return this.logstore.getLog(id, lid);
|
|
1665
1723
|
}
|
|
1666
|
-
|
|
1667
|
-
|
|
1724
|
+
|
|
1725
|
+
// 创建新日志,如果不存在
|
|
1726
|
+
return this.createLog(id, undefined, identity);
|
|
1668
1727
|
}
|
|
1669
|
-
}
|
|
1670
1728
|
|
|
1671
|
-
/**
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
//
|
|
1681
|
-
const
|
|
1682
|
-
|
|
1683
|
-
|
|
1729
|
+
/**
|
|
1730
|
+
* 创建新记录
|
|
1731
|
+
*/
|
|
1732
|
+
async newRecord(
|
|
1733
|
+
id: ThreadID,
|
|
1734
|
+
lg: IThreadLogInfo,
|
|
1735
|
+
body: IPLDNode,
|
|
1736
|
+
identity: PublicKey
|
|
1737
|
+
): Promise<IRecord> {
|
|
1738
|
+
// 获取threaddb 服务密钥
|
|
1739
|
+
const serviceKey = await this.logstore.keyBook.serviceKey(id);
|
|
1740
|
+
if (!serviceKey) {
|
|
1741
|
+
throw new Error("No service key for thread");
|
|
1684
1742
|
}
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
let peers = await this.uniquePeers(addrs);
|
|
1688
|
-
|
|
1689
|
-
// 尝试从外部对象获取对等点
|
|
1690
|
-
const extPeers = await this.getPeers(tid);
|
|
1691
|
-
if (extPeers && extPeers.length > 0) {
|
|
1692
|
-
peers = extPeers;
|
|
1743
|
+
if (!this.context.publicKey) {
|
|
1744
|
+
throw new Error("No identity provided for creating record");
|
|
1693
1745
|
}
|
|
1694
|
-
|
|
1695
|
-
//
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1746
|
+
const heads = await this.logstore.headBook.heads(id, lg.id);
|
|
1747
|
+
// 创建事件
|
|
1748
|
+
const sk = SymmetricKey.fromSymKey(serviceKey);
|
|
1749
|
+
const readKey = await this.logstore.keyBook.readKey(id);
|
|
1750
|
+
const rk = readKey ? SymmetricKey.fromSymKey(readKey) : null;
|
|
1751
|
+
if (!rk) {
|
|
1752
|
+
throw new Error("No read key for thread");
|
|
1701
1753
|
}
|
|
1702
|
-
|
|
1703
|
-
|
|
1754
|
+
const event = await CreateEvent(this.bstore, body as Node, rk);
|
|
1755
|
+
// 将事件保存到存储
|
|
1756
|
+
await this.bstore.put(event.cid(), event.data());
|
|
1757
|
+
let prev: CID | undefined = undefined;
|
|
1758
|
+
if (heads && heads.length > 0) {
|
|
1759
|
+
prev = heads[0]!.id;
|
|
1760
|
+
}
|
|
1761
|
+
const rec = await CreateRecord(this.bstore, {
|
|
1762
|
+
block: event,
|
|
1763
|
+
prev: prev,
|
|
1764
|
+
key: lg.privKey as Ed25519PrivKey,
|
|
1765
|
+
pubKey: this.context.publicKey,
|
|
1766
|
+
serviceKey: sk,
|
|
1767
|
+
} as CreateRecordConfig);
|
|
1768
|
+
|
|
1769
|
+
return rec;
|
|
1704
1770
|
}
|
|
1705
|
-
}
|
|
1706
1771
|
|
|
1707
|
-
/**
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1772
|
+
/**
|
|
1773
|
+
* 推送记录到日志地址和threaddb 主题
|
|
1774
|
+
* @param tid threaddb ID
|
|
1775
|
+
* @param lid 对等点ID
|
|
1776
|
+
* @param rec 记录对象
|
|
1777
|
+
* @param counter 计数器
|
|
1778
|
+
*/
|
|
1779
|
+
async pushRecord(
|
|
1780
|
+
tid: ThreadID,
|
|
1781
|
+
lid: PeerId,
|
|
1782
|
+
rec: IRecord,
|
|
1783
|
+
counter: number
|
|
1784
|
+
): Promise<void> {
|
|
1785
|
+
try {
|
|
1786
|
+
// 收集已知的写入器地址
|
|
1787
|
+
const addrs: TMultiaddr[] = [];
|
|
1788
|
+
const info = await this.logstore.getThread(tid);
|
|
1789
|
+
|
|
1790
|
+
// 收集所有日志的地址
|
|
1791
|
+
for (const l of info.logs) {
|
|
1792
|
+
if (l.addrs && l.addrs.length > 0) {
|
|
1793
|
+
addrs.push(...l.addrs);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
const peers = await this.getPeers(tid);
|
|
1797
|
+
if (!peers) {
|
|
1798
|
+
throw new Error(`No peers for thread ${tid}`);
|
|
1799
|
+
}
|
|
1800
|
+
// 向每个对等点推送
|
|
1801
|
+
for (const p of peers) {
|
|
1802
|
+
// 跳过无效对等点
|
|
1803
|
+
if (!p || p.toString() === "") {
|
|
1804
|
+
continue;
|
|
1805
|
+
}
|
|
1806
|
+
try {
|
|
1807
|
+
const [client, _] = await this.getClient(p);
|
|
1808
|
+
if (!client) {
|
|
1809
|
+
continue;
|
|
1810
|
+
}
|
|
1811
|
+
const dbClient = new DBClient(client, this.dc, this, this.logstore);
|
|
1812
|
+
// 启动异步推送(不等待完成)
|
|
1813
|
+
dbClient.pushRecordToPeer(tid, lid, rec, counter);
|
|
1814
|
+
} catch (err) {
|
|
1815
|
+
continue; // 继续处理其他对等点
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
} catch (err) {
|
|
1819
|
+
throw new Error(
|
|
1820
|
+
`Failed to push record: ${
|
|
1821
|
+
err instanceof Error ? err.message : String(err)
|
|
1822
|
+
}`
|
|
1823
|
+
);
|
|
1718
1824
|
}
|
|
1719
|
-
|
|
1720
|
-
// 创建数据库客户端
|
|
1721
|
-
const dbClient = new DBClient(client, this.dc,this,this.logstore);
|
|
1722
|
-
|
|
1723
|
-
// 交换边缘
|
|
1724
|
-
await dbClient.exchangeEdges([tid]);
|
|
1725
|
-
} catch (err) {
|
|
1726
|
-
throw err;
|
|
1727
1825
|
}
|
|
1728
|
-
}
|
|
1729
1826
|
|
|
1827
|
+
/**
|
|
1828
|
+
* 与拥有该threaddb 的对等节点交换threaddb 记录
|
|
1829
|
+
* @param tid threaddb ID
|
|
1830
|
+
*/
|
|
1831
|
+
async exchange(tid: ThreadID): Promise<void> {
|
|
1832
|
+
try {
|
|
1833
|
+
// 获取threaddb 信息
|
|
1834
|
+
const info = await this.logstore.getThread(tid);
|
|
1730
1835
|
|
|
1836
|
+
// 收集所有日志的地址
|
|
1837
|
+
const addrs: TMultiaddr[] = [];
|
|
1838
|
+
for (const lg of info.logs) {
|
|
1839
|
+
addrs.push(...lg.addrs);
|
|
1840
|
+
}
|
|
1731
1841
|
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1842
|
+
// 获取唯一对等点
|
|
1843
|
+
let peers = await this.uniquePeers(addrs);
|
|
1844
|
+
|
|
1845
|
+
// 尝试从外部对象获取对等点
|
|
1846
|
+
const extPeers = await this.getPeers(tid);
|
|
1847
|
+
if (extPeers && extPeers.length > 0) {
|
|
1848
|
+
peers = extPeers;
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
// 与每个对等点交换
|
|
1852
|
+
for (const pid of peers) {
|
|
1853
|
+
// 使用异步方式处理交换,不等待完成
|
|
1854
|
+
this.exchangeWithPeer(pid, tid).catch((err) => {
|
|
1855
|
+
console.error(
|
|
1856
|
+
`Error exchanging with peer ${pid}: ${
|
|
1857
|
+
err instanceof Error ? err.message : String(err)
|
|
1858
|
+
}`
|
|
1859
|
+
);
|
|
1860
|
+
});
|
|
1861
|
+
}
|
|
1862
|
+
} catch (err) {
|
|
1863
|
+
throw new Error(
|
|
1864
|
+
`Exchange failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1865
|
+
);
|
|
1866
|
+
}
|
|
1744
1867
|
}
|
|
1745
|
-
|
|
1746
|
-
return [conn, true];
|
|
1747
|
-
}
|
|
1748
1868
|
|
|
1869
|
+
/**
|
|
1870
|
+
* 与单个对等点交换threaddb 边缘
|
|
1871
|
+
* @param pid 对等点ID
|
|
1872
|
+
* @param tid threaddb ID
|
|
1873
|
+
*/
|
|
1874
|
+
private async exchangeWithPeer(pid: PeerId, tid: ThreadID): Promise<void> {
|
|
1875
|
+
try {
|
|
1876
|
+
// 获取客户端连接
|
|
1877
|
+
const [client, _] = await this.getClient(pid);
|
|
1878
|
+
if (!client) {
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1749
1881
|
|
|
1882
|
+
// 创建数据库客户端
|
|
1883
|
+
const dbClient = new DBClient(client, this.dc, this, this.logstore);
|
|
1750
1884
|
|
|
1885
|
+
// 交换边缘
|
|
1886
|
+
await dbClient.exchangeEdges([tid]);
|
|
1887
|
+
} catch (err) {
|
|
1888
|
+
throw err;
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1751
1891
|
|
|
1892
|
+
/**
|
|
1893
|
+
* 获取受保护的threaddb 连接器
|
|
1894
|
+
*/
|
|
1895
|
+
getConnectorProtected(
|
|
1896
|
+
id: ThreadID,
|
|
1897
|
+
token?: Token
|
|
1898
|
+
): [Connector | undefined, boolean] {
|
|
1899
|
+
const [conn, exists] = this.getConnector(id);
|
|
1900
|
+
|
|
1901
|
+
if (!exists) {
|
|
1902
|
+
return [undefined, true]; // threaddb 未被连接器使用
|
|
1903
|
+
}
|
|
1752
1904
|
|
|
1905
|
+
if (!token || !conn?.token || !this.bytesEqual(token, conn.token)) {
|
|
1906
|
+
return [undefined, false]; // 无效令牌
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
return [conn, true];
|
|
1910
|
+
}
|
|
1753
1911
|
|
|
1754
|
-
|
|
1755
1912
|
/**
|
|
1756
1913
|
* 检查两个字节数组是否相等
|
|
1757
1914
|
*/
|
|
1758
|
-
|
|
1915
|
+
bytesEqual(a: Uint8Array, b: Uint8Array): boolean {
|
|
1759
1916
|
if (a.length !== b.length) return false;
|
|
1760
|
-
|
|
1917
|
+
|
|
1761
1918
|
for (let i = 0; i < a.length; i++) {
|
|
1762
1919
|
if (a[i] !== b[i]) return false;
|
|
1763
1920
|
}
|
|
1764
|
-
|
|
1921
|
+
|
|
1765
1922
|
return true;
|
|
1766
1923
|
}
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
1924
|
}
|
|
1772
1925
|
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
/**
|
|
1926
|
+
/**
|
|
1776
1927
|
* 记录收集器,用于合并多个对等点的记录
|
|
1928
|
+
* 线程安全版本
|
|
1777
1929
|
*/
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1930
|
+
class RecordCollector {
|
|
1931
|
+
private records: Map<string, Map<string, any>> = new Map();
|
|
1932
|
+
private counters: Map<string, number> = new Map();
|
|
1933
|
+
private mutex: AsyncMutex = new AsyncMutex();
|
|
1934
|
+
|
|
1935
|
+
async updateHeadCounter(logId: string, counter: number): Promise<void> {
|
|
1936
|
+
await this.mutex.acquire();
|
|
1937
|
+
try {
|
|
1783
1938
|
const current = this.counters.get(logId) || 0;
|
|
1784
1939
|
if (counter > current) {
|
|
1785
1940
|
this.counters.set(logId, counter);
|
|
1786
1941
|
}
|
|
1942
|
+
} finally {
|
|
1943
|
+
this.mutex.release();
|
|
1787
1944
|
}
|
|
1788
|
-
|
|
1789
|
-
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
async store(logId: string, record: any): Promise<void> {
|
|
1948
|
+
await this.mutex.acquire();
|
|
1949
|
+
try {
|
|
1790
1950
|
let logRecords = this.records.get(logId);
|
|
1791
1951
|
if (!logRecords) {
|
|
1792
1952
|
logRecords = new Map();
|
|
@@ -1795,23 +1955,47 @@ getConnectorProtected(id: ThreadID, token?: Token): [Connector | undefined, bool
|
|
|
1795
1955
|
// 使用记录的CID作为键,避免重复
|
|
1796
1956
|
const key = record.cid().toString();
|
|
1797
1957
|
logRecords.set(key, record);
|
|
1958
|
+
} finally {
|
|
1959
|
+
this.mutex.release();
|
|
1798
1960
|
}
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
async batchUpdate(logId: string, rs: PeerRecords): Promise<void> {
|
|
1964
|
+
await this.mutex.acquire();
|
|
1965
|
+
try {
|
|
1966
|
+
// 更新计数器
|
|
1967
|
+
const current = this.counters.get(logId) || 0;
|
|
1968
|
+
if (rs.counter > current) {
|
|
1969
|
+
this.counters.set(logId, rs.counter);
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
// 存储记录
|
|
1973
|
+
let logRecords = this.records.get(logId);
|
|
1974
|
+
if (!logRecords) {
|
|
1975
|
+
logRecords = new Map();
|
|
1976
|
+
this.records.set(logId, logRecords);
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
rs.records.forEach(record => {
|
|
1980
|
+
const key = record.cid().toString();
|
|
1981
|
+
logRecords!.set(key, record);
|
|
1809
1982
|
});
|
|
1810
|
-
|
|
1811
|
-
|
|
1983
|
+
} finally {
|
|
1984
|
+
this.mutex.release();
|
|
1812
1985
|
}
|
|
1813
1986
|
}
|
|
1814
1987
|
|
|
1988
|
+
list(): Record<string, PeerRecords> {
|
|
1989
|
+
const result: Record<string, PeerRecords> = {};
|
|
1815
1990
|
|
|
1991
|
+
this.records.forEach((logRecords, logId) => {
|
|
1992
|
+
const records = Array.from(logRecords.values());
|
|
1993
|
+
result[logId] = {
|
|
1994
|
+
records,
|
|
1995
|
+
counter: this.counters.get(logId) || 0,
|
|
1996
|
+
};
|
|
1997
|
+
});
|
|
1816
1998
|
|
|
1817
|
-
|
|
1999
|
+
return result;
|
|
2000
|
+
}
|
|
2001
|
+
}
|