@dignetwork/dig-sdk 0.0.1-alpha.13 → 0.0.1-alpha.130
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/DataIntegrityTree/DataIntegrityTree.d.ts +11 -2
- package/dist/DataIntegrityTree/DataIntegrityTree.d.ts.map +1 -1
- package/dist/DataIntegrityTree/DataIntegrityTree.js +106 -15
- package/dist/DigNetwork/ContentServer.d.ts +12 -5
- package/dist/DigNetwork/ContentServer.d.ts.map +1 -1
- package/dist/DigNetwork/ContentServer.js +172 -24
- package/dist/DigNetwork/DigNetwork.d.ts +5 -9
- package/dist/DigNetwork/DigNetwork.d.ts.map +1 -1
- package/dist/DigNetwork/DigNetwork.js +117 -189
- package/dist/DigNetwork/DigPeer.d.ts +8 -3
- package/dist/DigNetwork/DigPeer.d.ts.map +1 -1
- package/dist/DigNetwork/DigPeer.js +68 -115
- package/dist/DigNetwork/IncentiveServer.d.ts.map +1 -1
- package/dist/DigNetwork/IncentiveServer.js +6 -5
- package/dist/DigNetwork/PropagationServer.d.ts +83 -33
- package/dist/DigNetwork/PropagationServer.d.ts.map +1 -1
- package/dist/DigNetwork/PropagationServer.js +610 -356
- package/dist/blockchain/DataStore.d.ts +25 -7
- package/dist/blockchain/DataStore.d.ts.map +1 -1
- package/dist/blockchain/DataStore.js +112 -157
- package/dist/blockchain/DataStoreSerializer.d.ts +1 -1
- package/dist/blockchain/DataStoreSerializer.d.ts.map +1 -1
- package/dist/blockchain/FullNodePeer.d.ts +94 -9
- package/dist/blockchain/FullNodePeer.d.ts.map +1 -1
- package/dist/blockchain/FullNodePeer.js +323 -121
- package/dist/blockchain/ServerCoin.d.ts +13 -5
- package/dist/blockchain/ServerCoin.d.ts.map +1 -1
- package/dist/blockchain/ServerCoin.js +90 -28
- package/dist/blockchain/StoreMonitorRegistry.d.ts +85 -0
- package/dist/blockchain/StoreMonitorRegistry.d.ts.map +1 -0
- package/dist/blockchain/StoreMonitorRegistry.js +242 -0
- package/dist/blockchain/Wallet.d.ts +2 -2
- package/dist/blockchain/Wallet.d.ts.map +1 -1
- package/dist/blockchain/Wallet.js +50 -18
- package/dist/blockchain/coins.d.ts +1 -2
- package/dist/blockchain/coins.d.ts.map +1 -1
- package/dist/blockchain/coins.js +1 -55
- package/dist/blockchain/index.d.ts +1 -0
- package/dist/blockchain/index.d.ts.map +1 -1
- package/dist/blockchain/index.js +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/ContentScanner.d.ts +63 -0
- package/dist/utils/ContentScanner.d.ts.map +1 -0
- package/dist/utils/ContentScanner.js +175 -0
- package/dist/utils/Environment.d.ts +21 -0
- package/dist/utils/Environment.d.ts.map +1 -0
- package/dist/utils/Environment.js +92 -0
- package/dist/utils/FileCache.d.ts +1 -1
- package/dist/utils/FileCache.d.ts.map +1 -1
- package/dist/utils/FileCache.js +2 -2
- package/dist/utils/FileTransfer.d.ts +47 -0
- package/dist/utils/FileTransfer.d.ts.map +1 -0
- package/dist/utils/FileTransfer.js +209 -0
- package/dist/utils/NconfManager.js +2 -1
- package/dist/utils/StoreArchiveManager.d.ts +45 -0
- package/dist/utils/StoreArchiveManager.d.ts.map +1 -0
- package/dist/utils/StoreArchiveManager.js +153 -0
- package/dist/utils/config.d.ts +4 -3
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +12 -7
- package/dist/utils/directoryUtils.d.ts +6 -0
- package/dist/utils/directoryUtils.d.ts.map +1 -1
- package/dist/utils/directoryUtils.js +31 -9
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/merkle.d.ts +2 -0
- package/dist/utils/merkle.d.ts.map +1 -0
- package/dist/utils/merkle.js +28 -0
- package/dist/utils/network.d.ts +1 -0
- package/dist/utils/network.d.ts.map +1 -1
- package/dist/utils/network.js +38 -11
- package/dist/utils/promiseUtils.d.ts +17 -0
- package/dist/utils/promiseUtils.d.ts.map +1 -0
- package/dist/utils/promiseUtils.js +38 -0
- package/dist/utils/ssl.js +2 -2
- package/package.json +16 -5
- package/dist/utils/deltaUtils.d.ts +0 -2
- package/dist/utils/deltaUtils.d.ts.map +0 -1
- package/dist/utils/deltaUtils.js +0 -83
|
@@ -17,6 +17,7 @@ declare class DataIntegrityTree {
|
|
|
17
17
|
files: Map<string, {
|
|
18
18
|
hash: string;
|
|
19
19
|
sha256: string;
|
|
20
|
+
bytes: string;
|
|
20
21
|
}>;
|
|
21
22
|
private tree;
|
|
22
23
|
constructor(storeId: string, options?: DataIntegrityTreeOptions);
|
|
@@ -78,6 +79,9 @@ declare class DataIntegrityTree {
|
|
|
78
79
|
* @returns The serialized Merkle tree.
|
|
79
80
|
*/
|
|
80
81
|
serialize(rootHash?: string | null): object;
|
|
82
|
+
static getRootOfForeignTree(treeData: {
|
|
83
|
+
leaves: string[];
|
|
84
|
+
}): string;
|
|
81
85
|
/**
|
|
82
86
|
* Deserialize a JSON object to a Merkle tree.
|
|
83
87
|
* @param rootHash - The root hash of the tree.
|
|
@@ -106,7 +110,7 @@ declare class DataIntegrityTree {
|
|
|
106
110
|
* @param rootHash - The root hash of the tree. Defaults to the latest root hash.
|
|
107
111
|
* @returns The readable stream for the file.
|
|
108
112
|
*/
|
|
109
|
-
getValueStream(hexKey: string, rootHash?: string | null): Readable;
|
|
113
|
+
getValueStream(hexKey: string, rootHash?: string | null, byteOffset?: number | null, length?: number | null): Readable;
|
|
110
114
|
/**
|
|
111
115
|
* Delete all leaves from the Merkle tree.
|
|
112
116
|
*/
|
|
@@ -152,9 +156,14 @@ declare class DataIntegrityTree {
|
|
|
152
156
|
* @param sha256 - The SHA-256 hash of the file.
|
|
153
157
|
* @param serializedTree - The foreign serialized Merkle tree.
|
|
154
158
|
* @param expectedRootHash - The expected root hash of the Merkle tree.
|
|
159
|
+
* @param dataDir - The directory where the data is stored.
|
|
160
|
+
* @param verifiedSha256 - A boolean indicating if the SHA-256 hash has already been verified.
|
|
155
161
|
* @returns A boolean indicating if the SHA-256 is present in the foreign tree and the root hash matches.
|
|
156
162
|
*/
|
|
157
|
-
static validateKeyIntegrityWithForeignTree(
|
|
163
|
+
static validateKeyIntegrityWithForeignTree(hexkey: string, sha256: string, serializedTree: object, expectedRootHash: string, dataDir: string, verifiedSha256?: boolean): Promise<boolean>;
|
|
164
|
+
private static verifyFileHash;
|
|
165
|
+
private static deserializeAndVerifyTree;
|
|
166
|
+
private static checkHashInTree;
|
|
158
167
|
}
|
|
159
168
|
export { DataIntegrityTree };
|
|
160
169
|
//# sourceMappingURL=DataIntegrityTree.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataIntegrityTree.d.ts","sourceRoot":"","sources":["../../src/DataIntegrityTree/DataIntegrityTree.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAuClC,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;GAEG;AACH,cAAM,iBAAiB;IACrB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAS;IACjB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"DataIntegrityTree.d.ts","sourceRoot":"","sources":["../../src/DataIntegrityTree/DataIntegrityTree.ts"],"names":[],"mappings":"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"}
|
|
@@ -94,8 +94,7 @@ class DataIntegrityTree {
|
|
|
94
94
|
}
|
|
95
95
|
this.files = new Map();
|
|
96
96
|
if (options.rootHash) {
|
|
97
|
-
|
|
98
|
-
if (manifest.includes(options.rootHash)) {
|
|
97
|
+
if (fs.existsSync(path.join(this.storeDir, `${options.rootHash}.dat`))) {
|
|
99
98
|
this.tree = this.deserializeTree(options.rootHash);
|
|
100
99
|
}
|
|
101
100
|
else {
|
|
@@ -111,7 +110,10 @@ class DataIntegrityTree {
|
|
|
111
110
|
}
|
|
112
111
|
}
|
|
113
112
|
static from(storeId, options) {
|
|
114
|
-
return new DataIntegrityTree(storeId, {
|
|
113
|
+
return new DataIntegrityTree(storeId, {
|
|
114
|
+
...options,
|
|
115
|
+
disableInitialize: true,
|
|
116
|
+
});
|
|
115
117
|
}
|
|
116
118
|
/**
|
|
117
119
|
* Load the manifest file.
|
|
@@ -132,7 +134,10 @@ class DataIntegrityTree {
|
|
|
132
134
|
const manifest = this._loadManifest();
|
|
133
135
|
if (manifest.length > 0) {
|
|
134
136
|
const latestRootHash = manifest[manifest.length - 1];
|
|
135
|
-
|
|
137
|
+
if (latestRootHash && isHexString(latestRootHash)) {
|
|
138
|
+
return this.deserializeTree(latestRootHash);
|
|
139
|
+
}
|
|
140
|
+
return new merkletreejs_1.MerkleTree([], crypto_js_1.SHA256, { sortPairs: true });
|
|
136
141
|
}
|
|
137
142
|
else {
|
|
138
143
|
return new merkletreejs_1.MerkleTree([], crypto_js_1.SHA256, { sortPairs: true });
|
|
@@ -186,11 +191,16 @@ class DataIntegrityTree {
|
|
|
186
191
|
fs.mkdirSync(tempDir, { recursive: true });
|
|
187
192
|
}
|
|
188
193
|
const tempFilePath = path.join(tempDir, `${crypto.randomUUID()}.gz`);
|
|
194
|
+
let totalBytes = 0;
|
|
189
195
|
return new Promise((resolve, reject) => {
|
|
190
196
|
const tempWriteStream = fs.createWriteStream(tempFilePath);
|
|
191
197
|
readStream.on("data", (chunk) => {
|
|
192
198
|
uncompressedHash.update(chunk);
|
|
193
199
|
});
|
|
200
|
+
// Now listen to the gzip stream for compressed data size
|
|
201
|
+
gzip.on("data", (chunk) => {
|
|
202
|
+
totalBytes += chunk.length; // This counts compressed bytes
|
|
203
|
+
});
|
|
194
204
|
readStream.pipe(gzip).pipe(tempWriteStream);
|
|
195
205
|
tempWriteStream.on("finish", async () => {
|
|
196
206
|
sha256 = uncompressedHash.digest("hex");
|
|
@@ -219,15 +229,21 @@ class DataIntegrityTree {
|
|
|
219
229
|
this.files.set(key, {
|
|
220
230
|
hash: combinedHash,
|
|
221
231
|
sha256: sha256,
|
|
232
|
+
bytes: totalBytes.toString(),
|
|
222
233
|
});
|
|
234
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
223
235
|
this._rebuildTree();
|
|
236
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
224
237
|
resolve();
|
|
225
238
|
}
|
|
226
239
|
catch (err) {
|
|
227
240
|
reject(err);
|
|
228
241
|
}
|
|
242
|
+
tempWriteStream.end();
|
|
243
|
+
finalWriteStream.end();
|
|
229
244
|
});
|
|
230
245
|
tempWriteStream.on("error", (err) => {
|
|
246
|
+
tempWriteStream.end();
|
|
231
247
|
reject(err);
|
|
232
248
|
});
|
|
233
249
|
readStream.on("error", (err) => {
|
|
@@ -305,6 +321,11 @@ class DataIntegrityTree {
|
|
|
305
321
|
files: Object.fromEntries(this.files),
|
|
306
322
|
};
|
|
307
323
|
}
|
|
324
|
+
static getRootOfForeignTree(treeData) {
|
|
325
|
+
const leaves = treeData.leaves.map((leaf) => Buffer.from(leaf, "hex"));
|
|
326
|
+
const tree = new merkletreejs_1.MerkleTree(leaves, crypto_js_1.SHA256, { sortPairs: true });
|
|
327
|
+
return tree.getRoot().toString("hex");
|
|
328
|
+
}
|
|
308
329
|
/**
|
|
309
330
|
* Deserialize a JSON object to a Merkle tree.
|
|
310
331
|
* @param rootHash - The root hash of the tree.
|
|
@@ -337,7 +358,9 @@ class DataIntegrityTree {
|
|
|
337
358
|
? fs.readFileSync(manifestPath, "utf-8").trim().split("\n")
|
|
338
359
|
: [];
|
|
339
360
|
// Check if the last entry is the same as the rootHash to avoid duplicates
|
|
340
|
-
const latestRootHash = manifestContent.length > 0
|
|
361
|
+
const latestRootHash = manifestContent.length > 0
|
|
362
|
+
? manifestContent[manifestContent.length - 1]
|
|
363
|
+
: null;
|
|
341
364
|
if (latestRootHash !== rootHash) {
|
|
342
365
|
// Append the new rootHash if it is not the same as the last one
|
|
343
366
|
fs.appendFileSync(manifestPath, `${rootHash}\n`);
|
|
@@ -413,7 +436,7 @@ class DataIntegrityTree {
|
|
|
413
436
|
* @param rootHash - The root hash of the tree. Defaults to the latest root hash.
|
|
414
437
|
* @returns The readable stream for the file.
|
|
415
438
|
*/
|
|
416
|
-
getValueStream(hexKey, rootHash = null) {
|
|
439
|
+
getValueStream(hexKey, rootHash = null, byteOffset = null, length = null) {
|
|
417
440
|
if (!isHexString(hexKey)) {
|
|
418
441
|
throw new Error("key must be a valid hex string");
|
|
419
442
|
}
|
|
@@ -436,8 +459,22 @@ class DataIntegrityTree {
|
|
|
436
459
|
if (!fs.existsSync(filePath)) {
|
|
437
460
|
throw new Error(`File at path ${filePath} does not exist`);
|
|
438
461
|
}
|
|
439
|
-
|
|
440
|
-
|
|
462
|
+
const fileSize = fs.statSync(filePath).size;
|
|
463
|
+
// Validate offset and length
|
|
464
|
+
if (byteOffset !== null && length !== null) {
|
|
465
|
+
if (byteOffset + length > fileSize) {
|
|
466
|
+
throw new Error(`Offset (${byteOffset}) and length (${length}) exceed the file size (${fileSize}).`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
// Create the read stream with optional byte range
|
|
470
|
+
const options = {};
|
|
471
|
+
if (byteOffset !== null) {
|
|
472
|
+
options.start = byteOffset;
|
|
473
|
+
}
|
|
474
|
+
if (length !== null && byteOffset !== null) {
|
|
475
|
+
options.end = byteOffset + length - 1; // `end` is inclusive, hence `byteOffset + length - 1`
|
|
476
|
+
}
|
|
477
|
+
const readStream = fs.createReadStream(filePath, options);
|
|
441
478
|
const decompressStream = zlib.createGunzip();
|
|
442
479
|
// Return the combined stream as a generic Readable stream
|
|
443
480
|
return readStream.pipe(decompressStream);
|
|
@@ -579,14 +616,13 @@ class DataIntegrityTree {
|
|
|
579
616
|
decompressStream.on("end", () => {
|
|
580
617
|
const uncompressedSha256 = hash.digest("hex");
|
|
581
618
|
const isValid = uncompressedSha256 === sha256;
|
|
582
|
-
console.log(`SHA-256 of uncompressed file: ${uncompressedSha256}`);
|
|
583
619
|
if (!isValid) {
|
|
584
620
|
return resolve(false);
|
|
585
621
|
}
|
|
586
622
|
const tree = this.deserializeTree(rootHash);
|
|
587
623
|
const combinedHash = crypto
|
|
588
624
|
.createHash("sha256")
|
|
589
|
-
.update(
|
|
625
|
+
.update(sha256)
|
|
590
626
|
.digest("hex");
|
|
591
627
|
const leaf = Buffer.from(combinedHash, "hex");
|
|
592
628
|
const isInTree = tree.getLeafIndex(leaf) !== -1;
|
|
@@ -608,10 +644,12 @@ class DataIntegrityTree {
|
|
|
608
644
|
* @param sha256 - The SHA-256 hash of the file.
|
|
609
645
|
* @param serializedTree - The foreign serialized Merkle tree.
|
|
610
646
|
* @param expectedRootHash - The expected root hash of the Merkle tree.
|
|
647
|
+
* @param dataDir - The directory where the data is stored.
|
|
648
|
+
* @param verifiedSha256 - A boolean indicating if the SHA-256 hash has already been verified.
|
|
611
649
|
* @returns A boolean indicating if the SHA-256 is present in the foreign tree and the root hash matches.
|
|
612
650
|
*/
|
|
613
|
-
static validateKeyIntegrityWithForeignTree(
|
|
614
|
-
if (!isHexString(
|
|
651
|
+
static async validateKeyIntegrityWithForeignTree(hexkey, sha256, serializedTree, expectedRootHash, dataDir, verifiedSha256 = false) {
|
|
652
|
+
if (!isHexString(hexkey)) {
|
|
615
653
|
throw new Error("key must be a valid hex string");
|
|
616
654
|
}
|
|
617
655
|
if (!isHexString(sha256)) {
|
|
@@ -620,6 +658,56 @@ class DataIntegrityTree {
|
|
|
620
658
|
if (!isHexString(expectedRootHash)) {
|
|
621
659
|
throw new Error("expectedRootHash must be a valid hex string");
|
|
622
660
|
}
|
|
661
|
+
if (!verifiedSha256) {
|
|
662
|
+
// Verify the file's SHA-256 hash
|
|
663
|
+
const isFileValid = await this.verifyFileHash(sha256, dataDir);
|
|
664
|
+
if (!isFileValid) {
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
// Deserialize and verify the tree
|
|
669
|
+
const tree = this.deserializeAndVerifyTree(serializedTree, expectedRootHash);
|
|
670
|
+
if (!tree) {
|
|
671
|
+
return false;
|
|
672
|
+
}
|
|
673
|
+
// Check if the combined hash exists in the foreign tree's leaves
|
|
674
|
+
const isInTree = this.checkHashInTree(tree, hexkey, sha256);
|
|
675
|
+
return isInTree;
|
|
676
|
+
}
|
|
677
|
+
// Function to verify the file's SHA-256 hash
|
|
678
|
+
static async verifyFileHash(sha256, dataDir) {
|
|
679
|
+
const filePath = path.join(dataDir, sha256.match(/.{1,2}/g).join("/"));
|
|
680
|
+
// Check if the file exists
|
|
681
|
+
if (!fs.existsSync(filePath)) {
|
|
682
|
+
throw new Error(`File at path ${filePath} does not exist`);
|
|
683
|
+
}
|
|
684
|
+
const compressedReadStream = fs.createReadStream(filePath);
|
|
685
|
+
const decompressStream = zlib.createGunzip();
|
|
686
|
+
const hash = crypto.createHash("sha256");
|
|
687
|
+
// Process file decompression and hash comparison
|
|
688
|
+
return new Promise((resolve, reject) => {
|
|
689
|
+
compressedReadStream.pipe(decompressStream);
|
|
690
|
+
decompressStream.on("data", (chunk) => {
|
|
691
|
+
hash.update(chunk);
|
|
692
|
+
});
|
|
693
|
+
decompressStream.on("end", () => {
|
|
694
|
+
const uncompressedSha256 = hash.digest("hex");
|
|
695
|
+
if (uncompressedSha256 !== sha256) {
|
|
696
|
+
console.warn(`File hash mismatch. Expected: ${sha256}, got: ${uncompressedSha256}`);
|
|
697
|
+
return resolve(false);
|
|
698
|
+
}
|
|
699
|
+
resolve(true);
|
|
700
|
+
});
|
|
701
|
+
decompressStream.on("error", (err) => {
|
|
702
|
+
reject(err);
|
|
703
|
+
});
|
|
704
|
+
compressedReadStream.on("error", (err) => {
|
|
705
|
+
reject(err);
|
|
706
|
+
});
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
// Function to deserialize and verify the tree
|
|
710
|
+
static deserializeAndVerifyTree(serializedTree, expectedRootHash) {
|
|
623
711
|
// Deserialize the foreign tree
|
|
624
712
|
const leaves = serializedTree.leaves.map((leaf) => Buffer.from(leaf, "hex"));
|
|
625
713
|
const tree = new merkletreejs_1.MerkleTree(leaves, crypto_js_1.SHA256, { sortPairs: true });
|
|
@@ -627,7 +715,7 @@ class DataIntegrityTree {
|
|
|
627
715
|
const treeRootHash = tree.getRoot().toString("hex");
|
|
628
716
|
if (treeRootHash !== expectedRootHash) {
|
|
629
717
|
console.warn(`Expected root hash ${expectedRootHash}, but got ${treeRootHash}`);
|
|
630
|
-
return
|
|
718
|
+
return null;
|
|
631
719
|
}
|
|
632
720
|
// Rebuild the files map from the serialized tree
|
|
633
721
|
// @ts-ignore
|
|
@@ -635,10 +723,13 @@ class DataIntegrityTree {
|
|
|
635
723
|
key,
|
|
636
724
|
{ hash: value.hash, sha256: value.sha256 },
|
|
637
725
|
]));
|
|
638
|
-
|
|
726
|
+
return tree;
|
|
727
|
+
}
|
|
728
|
+
// Function to check if the combined hash exists in the tree
|
|
729
|
+
static checkHashInTree(tree, hexkey, sha256) {
|
|
639
730
|
const combinedHash = crypto
|
|
640
731
|
.createHash("sha256")
|
|
641
|
-
.update(`${
|
|
732
|
+
.update(`${hexkey}/${sha256}`)
|
|
642
733
|
.digest("hex");
|
|
643
734
|
const leaf = Buffer.from(combinedHash, "hex");
|
|
644
735
|
const isInTree = tree.getLeafIndex(leaf) !== -1;
|
|
@@ -3,26 +3,33 @@ import { Readable } from "stream";
|
|
|
3
3
|
export declare class ContentServer {
|
|
4
4
|
private ipAddress;
|
|
5
5
|
private storeId;
|
|
6
|
+
private static certPath;
|
|
7
|
+
private static keyPath;
|
|
6
8
|
private static readonly port;
|
|
7
9
|
constructor(ipAddress: string, storeId: string);
|
|
8
10
|
getKey(key: string, rootHash: string, challengeHex?: string): Promise<string>;
|
|
9
|
-
|
|
11
|
+
getKeyChunk(key: string, rootHash: string): Promise<Buffer>;
|
|
12
|
+
getPaymentAddress(): Promise<string | null>;
|
|
10
13
|
getWellKnown(): Promise<any>;
|
|
11
14
|
getKnownStores(): Promise<any>;
|
|
12
15
|
getStoresIndex(): Promise<any>;
|
|
13
|
-
getKeysIndex(): Promise<any>;
|
|
14
|
-
headKey(key: string): Promise<{
|
|
16
|
+
getKeysIndex(rootHash?: string): Promise<any>;
|
|
17
|
+
headKey(key: string, rootHash?: string): Promise<{
|
|
15
18
|
success: boolean;
|
|
16
19
|
headers?: http.IncomingHttpHeaders;
|
|
17
20
|
}>;
|
|
18
|
-
headStore(
|
|
21
|
+
headStore(options?: {
|
|
22
|
+
hasRootHash: string;
|
|
23
|
+
}): Promise<{
|
|
19
24
|
success: boolean;
|
|
20
25
|
headers?: http.IncomingHttpHeaders;
|
|
21
26
|
}>;
|
|
22
|
-
|
|
27
|
+
hasRootHash(rootHash: string): Promise<boolean>;
|
|
28
|
+
streamKey(key: string, rootHash?: string): Promise<Readable>;
|
|
23
29
|
private head;
|
|
24
30
|
private fetchJson;
|
|
25
31
|
private fetchWithRetries;
|
|
26
32
|
private fetch;
|
|
33
|
+
private fetchFirstChunk;
|
|
27
34
|
}
|
|
28
35
|
//# sourceMappingURL=ContentServer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContentServer.d.ts","sourceRoot":"","sources":["../../src/DigNetwork/ContentServer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ContentServer.d.ts","sourceRoot":"","sources":["../../src/DigNetwork/ContentServer.ts"],"names":[],"mappings":"AACA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAIlC,qBAAa,aAAa;IACxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAS;IAChC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAS;IAC/B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAQ;gBAExB,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IAYjC,MAAM,CACjB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,CAAC;IAaJ,WAAW,CACvB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC;IAOL,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAe3C,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC;IAM5B,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAM9B,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAM9B,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAY7C,OAAO,CAClB,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,IAAI,CAAC,mBAAmB,CAAA;KAAE,CAAC;IAYvD,SAAS,CAAC,OAAO,CAAC,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QACjE,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,CAAC,EAAE,IAAI,CAAC,mBAAmB,CAAC;KACpC,CAAC;IAUW,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUrD,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;YAgDrD,IAAI;YA0EJ,SAAS;YAMT,gBAAgB;YAiChB,KAAK;YA+GL,eAAe;CA2G9B"}
|
|
@@ -4,23 +4,37 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.ContentServer = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
8
|
const http_1 = __importDefault(require("http"));
|
|
8
9
|
const url_1 = require("url");
|
|
10
|
+
const ssl_1 = require("../utils/ssl");
|
|
11
|
+
const network_1 = require("../utils/network");
|
|
9
12
|
class ContentServer {
|
|
10
13
|
constructor(ipAddress, storeId) {
|
|
11
14
|
this.ipAddress = ipAddress;
|
|
12
15
|
this.storeId = storeId;
|
|
16
|
+
if (!ContentServer.certPath || !ContentServer.keyPath) {
|
|
17
|
+
const { certPath, keyPath } = (0, ssl_1.getOrCreateSSLCerts)();
|
|
18
|
+
ContentServer.certPath = certPath;
|
|
19
|
+
ContentServer.keyPath = keyPath;
|
|
20
|
+
}
|
|
13
21
|
}
|
|
14
22
|
// Method to get the content of a specified key from the peer, with optional challenge query
|
|
15
23
|
async getKey(key, rootHash, challengeHex) {
|
|
16
24
|
// Construct the base URL
|
|
17
|
-
let url = `
|
|
25
|
+
let url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/chia.${this.storeId}.${rootHash}/${key}`;
|
|
18
26
|
// If a challenge is provided, append it as a query parameter
|
|
19
27
|
if (challengeHex) {
|
|
20
28
|
url += `?challenge=${challengeHex}`;
|
|
21
29
|
}
|
|
22
30
|
return this.fetchWithRetries(url);
|
|
23
31
|
}
|
|
32
|
+
// New method to get only the first chunk of the content
|
|
33
|
+
async getKeyChunk(key, rootHash) {
|
|
34
|
+
// Construct the base URL
|
|
35
|
+
let url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/chia.${this.storeId}.${rootHash}/${key}`;
|
|
36
|
+
return this.fetchFirstChunk(url);
|
|
37
|
+
}
|
|
24
38
|
// Method to get the payment address from the peer
|
|
25
39
|
async getPaymentAddress() {
|
|
26
40
|
console.log(`Fetching payment address from peer ${this.ipAddress}...`);
|
|
@@ -30,43 +44,66 @@ class ContentServer {
|
|
|
30
44
|
}
|
|
31
45
|
catch (error) {
|
|
32
46
|
console.error(`Failed to fetch payment address from ${this.ipAddress}: ${error.message}`);
|
|
33
|
-
|
|
47
|
+
return null;
|
|
34
48
|
}
|
|
35
49
|
}
|
|
36
50
|
// Method to get the .well-known information
|
|
37
51
|
async getWellKnown() {
|
|
38
|
-
const url = `
|
|
52
|
+
const url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/.well-known`;
|
|
39
53
|
return this.fetchJson(url);
|
|
40
54
|
}
|
|
41
55
|
// Method to get the list of known stores
|
|
42
56
|
async getKnownStores() {
|
|
43
|
-
const url = `
|
|
57
|
+
const url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/.well-known/stores`;
|
|
44
58
|
return this.fetchJson(url);
|
|
45
59
|
}
|
|
46
60
|
// Method to get the index of all stores
|
|
47
61
|
async getStoresIndex() {
|
|
48
|
-
const url = `
|
|
62
|
+
const url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/`;
|
|
49
63
|
return this.fetchJson(url);
|
|
50
64
|
}
|
|
51
65
|
// Method to get the index of keys in a store
|
|
52
|
-
async getKeysIndex() {
|
|
53
|
-
|
|
66
|
+
async getKeysIndex(rootHash) {
|
|
67
|
+
let udi = `chia.${this.storeId}`;
|
|
68
|
+
if (rootHash) {
|
|
69
|
+
udi += `.${rootHash}`;
|
|
70
|
+
}
|
|
71
|
+
const url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/${udi}`;
|
|
54
72
|
return this.fetchJson(url);
|
|
55
73
|
}
|
|
56
74
|
// Method to check if a specific key exists (HEAD request)
|
|
57
|
-
async headKey(key) {
|
|
58
|
-
|
|
59
|
-
|
|
75
|
+
async headKey(key, rootHash) {
|
|
76
|
+
let udi = `chia.${this.storeId}`;
|
|
77
|
+
if (rootHash) {
|
|
78
|
+
udi += `.${rootHash}`;
|
|
79
|
+
}
|
|
80
|
+
const url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/${udi}/${key}`;
|
|
81
|
+
return this.head(url);
|
|
60
82
|
}
|
|
61
83
|
// Method to check if a specific store exists (HEAD request)
|
|
62
|
-
async headStore() {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
84
|
+
async headStore(options) {
|
|
85
|
+
let url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/chia.${this.storeId}`;
|
|
86
|
+
if (options?.hasRootHash) {
|
|
87
|
+
url += `?hasRootHash=${options.hasRootHash}`;
|
|
88
|
+
}
|
|
89
|
+
return this.head(url);
|
|
66
90
|
}
|
|
67
|
-
|
|
91
|
+
async hasRootHash(rootHash) {
|
|
92
|
+
const { success, headers } = await this.headStore({
|
|
93
|
+
hasRootHash: rootHash,
|
|
94
|
+
});
|
|
95
|
+
if (success) {
|
|
96
|
+
return headers?.["x-has-root-hash"] === "true";
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
streamKey(key, rootHash) {
|
|
101
|
+
let udi = `chia.${this.storeId}`;
|
|
102
|
+
if (rootHash) {
|
|
103
|
+
udi += `.${rootHash}`;
|
|
104
|
+
}
|
|
68
105
|
return new Promise((resolve, reject) => {
|
|
69
|
-
const url = `
|
|
106
|
+
const url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/${udi}/${key}`;
|
|
70
107
|
const urlObj = new url_1.URL(url);
|
|
71
108
|
const requestOptions = {
|
|
72
109
|
hostname: urlObj.hostname,
|
|
@@ -93,7 +130,7 @@ class ContentServer {
|
|
|
93
130
|
}
|
|
94
131
|
});
|
|
95
132
|
request.on("error", (error) => {
|
|
96
|
-
console.error(`Request error for ${url}:`, error);
|
|
133
|
+
console.error(`GET Request error for ${url}:`, error);
|
|
97
134
|
reject(error);
|
|
98
135
|
});
|
|
99
136
|
request.end();
|
|
@@ -111,6 +148,9 @@ class ContentServer {
|
|
|
111
148
|
(urlObj.protocol === "http:" ? 80 : ContentServer.port),
|
|
112
149
|
path: urlObj.pathname + urlObj.search,
|
|
113
150
|
method: "HEAD",
|
|
151
|
+
key: fs_1.default.readFileSync(ContentServer.keyPath),
|
|
152
|
+
cert: fs_1.default.readFileSync(ContentServer.certPath),
|
|
153
|
+
rejectUnauthorized: false,
|
|
114
154
|
};
|
|
115
155
|
const request = http_1.default.request(requestOptions, (response) => {
|
|
116
156
|
const { statusCode, headers } = response;
|
|
@@ -149,7 +189,7 @@ class ContentServer {
|
|
|
149
189
|
}
|
|
150
190
|
});
|
|
151
191
|
request.on("error", (error) => {
|
|
152
|
-
console.error(`
|
|
192
|
+
console.error(`HEAD ${url}:`, error.message);
|
|
153
193
|
reject({ success: false });
|
|
154
194
|
});
|
|
155
195
|
request.end();
|
|
@@ -168,7 +208,7 @@ class ContentServer {
|
|
|
168
208
|
// Helper method to fetch content with retries and redirection handling
|
|
169
209
|
async fetchWithRetries(url) {
|
|
170
210
|
let attempt = 0;
|
|
171
|
-
const maxRetries =
|
|
211
|
+
const maxRetries = 1;
|
|
172
212
|
const initialDelay = 2000; // 2 seconds
|
|
173
213
|
const maxDelay = 10000; // 10 seconds
|
|
174
214
|
const delayMultiplier = 1.5;
|
|
@@ -192,23 +232,48 @@ class ContentServer {
|
|
|
192
232
|
}
|
|
193
233
|
throw new Error(`Failed to retrieve data from ${url} after ${maxRetries} attempts.`);
|
|
194
234
|
}
|
|
195
|
-
// Core method to fetch content from a URL
|
|
235
|
+
// Core method to fetch content from a URL with a 5-second inactivity timeout
|
|
196
236
|
async fetch(url, maxRedirects = 5) {
|
|
197
237
|
return new Promise((resolve, reject) => {
|
|
198
238
|
const urlObj = new url_1.URL(url);
|
|
239
|
+
const timeoutDuration = 5000; // 5 seconds
|
|
240
|
+
let timeout = null; // Initialize timeout
|
|
199
241
|
const requestOptions = {
|
|
200
242
|
hostname: urlObj.hostname,
|
|
201
243
|
port: urlObj.port || ContentServer.port,
|
|
202
244
|
path: urlObj.pathname + urlObj.search, // Include query params
|
|
203
245
|
method: "GET",
|
|
246
|
+
key: fs_1.default.readFileSync(ContentServer.keyPath),
|
|
247
|
+
cert: fs_1.default.readFileSync(ContentServer.certPath),
|
|
248
|
+
rejectUnauthorized: false,
|
|
204
249
|
};
|
|
205
250
|
const request = http_1.default.request(requestOptions, (response) => {
|
|
206
251
|
let data = "";
|
|
252
|
+
// Set timeout for inactivity
|
|
253
|
+
timeout = setTimeout(() => {
|
|
254
|
+
console.error(`Request timeout: No data received for ${timeoutDuration / 1000} seconds.`);
|
|
255
|
+
request.destroy(); // Use destroy instead of abort
|
|
256
|
+
reject(new Error(`Request timed out after ${timeoutDuration / 1000} seconds of inactivity`));
|
|
257
|
+
}, timeoutDuration);
|
|
258
|
+
const resetTimeout = () => {
|
|
259
|
+
if (timeout) {
|
|
260
|
+
clearTimeout(timeout);
|
|
261
|
+
}
|
|
262
|
+
timeout = setTimeout(() => {
|
|
263
|
+
console.error(`Request timeout: No data received for ${timeoutDuration / 1000} seconds.`);
|
|
264
|
+
request.destroy(); // Use destroy instead of abort
|
|
265
|
+
reject(new Error(`Request timed out after ${timeoutDuration / 1000} seconds of inactivity`));
|
|
266
|
+
}, timeoutDuration);
|
|
267
|
+
};
|
|
207
268
|
if (response.statusCode === 200) {
|
|
208
269
|
response.on("data", (chunk) => {
|
|
209
270
|
data += chunk;
|
|
271
|
+
resetTimeout(); // Reset the timeout every time data is received
|
|
210
272
|
});
|
|
211
273
|
response.on("end", () => {
|
|
274
|
+
if (timeout) {
|
|
275
|
+
clearTimeout(timeout);
|
|
276
|
+
}
|
|
212
277
|
resolve(data);
|
|
213
278
|
});
|
|
214
279
|
}
|
|
@@ -217,8 +282,9 @@ class ContentServer {
|
|
|
217
282
|
// Handle redirects
|
|
218
283
|
if (maxRedirects > 0) {
|
|
219
284
|
const redirectUrl = new url_1.URL(response.headers.location, url); // Resolve relative URLs based on the original URL
|
|
220
|
-
|
|
221
|
-
|
|
285
|
+
if (timeout) {
|
|
286
|
+
clearTimeout(timeout);
|
|
287
|
+
}
|
|
222
288
|
this.fetch(redirectUrl.toString(), maxRedirects - 1)
|
|
223
289
|
.then(resolve)
|
|
224
290
|
.catch(reject);
|
|
@@ -228,11 +294,93 @@ class ContentServer {
|
|
|
228
294
|
}
|
|
229
295
|
}
|
|
230
296
|
else {
|
|
297
|
+
if (timeout) {
|
|
298
|
+
clearTimeout(timeout);
|
|
299
|
+
}
|
|
231
300
|
reject(new Error(`Failed to retrieve data from ${url}. Status code: ${response.statusCode}`));
|
|
232
301
|
}
|
|
233
302
|
});
|
|
234
303
|
request.on("error", (error) => {
|
|
235
|
-
|
|
304
|
+
if (timeout) {
|
|
305
|
+
clearTimeout(timeout);
|
|
306
|
+
}
|
|
307
|
+
console.error(`GET ${url}:`, error.message);
|
|
308
|
+
reject(error);
|
|
309
|
+
});
|
|
310
|
+
request.end();
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
// New core method to fetch only the first chunk without retries
|
|
314
|
+
async fetchFirstChunk(url) {
|
|
315
|
+
return new Promise((resolve, reject) => {
|
|
316
|
+
const urlObj = new url_1.URL(url);
|
|
317
|
+
const timeoutDuration = 5000; // 5 seconds
|
|
318
|
+
let timeout = null;
|
|
319
|
+
const requestOptions = {
|
|
320
|
+
hostname: urlObj.hostname,
|
|
321
|
+
port: urlObj.port || ContentServer.port,
|
|
322
|
+
path: urlObj.pathname + urlObj.search, // Include query params
|
|
323
|
+
method: "GET",
|
|
324
|
+
key: fs_1.default.readFileSync(ContentServer.keyPath),
|
|
325
|
+
cert: fs_1.default.readFileSync(ContentServer.certPath),
|
|
326
|
+
rejectUnauthorized: false,
|
|
327
|
+
};
|
|
328
|
+
const request = http_1.default.request(requestOptions, (response) => {
|
|
329
|
+
// Set timeout for inactivity
|
|
330
|
+
timeout = setTimeout(() => {
|
|
331
|
+
console.error(`Request timeout: No data received for ${timeoutDuration / 1000} seconds.`);
|
|
332
|
+
request.destroy(); // Use destroy instead of abort
|
|
333
|
+
reject(new Error(`Request timed out after ${timeoutDuration / 1000} seconds of inactivity`));
|
|
334
|
+
}, timeoutDuration);
|
|
335
|
+
const resetTimeout = () => {
|
|
336
|
+
if (timeout) {
|
|
337
|
+
clearTimeout(timeout);
|
|
338
|
+
}
|
|
339
|
+
timeout = setTimeout(() => {
|
|
340
|
+
console.error(`Request timeout: No data received for ${timeoutDuration / 1000} seconds.`);
|
|
341
|
+
request.destroy(); // Use destroy instead of abort
|
|
342
|
+
reject(new Error(`Request timed out after ${timeoutDuration / 1000} seconds of inactivity`));
|
|
343
|
+
}, timeoutDuration);
|
|
344
|
+
};
|
|
345
|
+
if (response.statusCode === 200) {
|
|
346
|
+
response.once("data", (chunk) => {
|
|
347
|
+
if (timeout) {
|
|
348
|
+
clearTimeout(timeout);
|
|
349
|
+
}
|
|
350
|
+
response.destroy(); // Close the connection after receiving the first chunk
|
|
351
|
+
resolve(chunk);
|
|
352
|
+
});
|
|
353
|
+
response.on("end", () => {
|
|
354
|
+
if (timeout) {
|
|
355
|
+
clearTimeout(timeout);
|
|
356
|
+
}
|
|
357
|
+
// In case the response ends before any data is received
|
|
358
|
+
reject(new Error("No data received"));
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
else if ((response.statusCode === 301 || response.statusCode === 302) &&
|
|
362
|
+
response.headers.location) {
|
|
363
|
+
// Handle redirects
|
|
364
|
+
const redirectUrl = new url_1.URL(response.headers.location, url).toString(); // Resolve relative URLs
|
|
365
|
+
if (timeout) {
|
|
366
|
+
clearTimeout(timeout);
|
|
367
|
+
}
|
|
368
|
+
this.fetchFirstChunk(redirectUrl)
|
|
369
|
+
.then(resolve)
|
|
370
|
+
.catch(reject);
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
if (timeout) {
|
|
374
|
+
clearTimeout(timeout);
|
|
375
|
+
}
|
|
376
|
+
reject(new Error(`Failed to retrieve data from ${url}. Status code: ${response.statusCode}`));
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
request.on("error", (error) => {
|
|
380
|
+
if (timeout) {
|
|
381
|
+
clearTimeout(timeout);
|
|
382
|
+
}
|
|
383
|
+
console.error(`GET ${url}:`, error.message);
|
|
236
384
|
reject(error);
|
|
237
385
|
});
|
|
238
386
|
request.end();
|
|
@@ -240,4 +388,4 @@ class ContentServer {
|
|
|
240
388
|
}
|
|
241
389
|
}
|
|
242
390
|
exports.ContentServer = ContentServer;
|
|
243
|
-
ContentServer.port =
|
|
391
|
+
ContentServer.port = 4161;
|