@dignetwork/dig-sdk 0.0.1-alpha.17 → 0.0.1-alpha.170
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 +10 -1
- package/dist/DataIntegrityTree/DataIntegrityTree.d.ts.map +1 -1
- package/dist/DataIntegrityTree/DataIntegrityTree.js +77 -10
- package/dist/DigNetwork/ContentServer.d.ts +24 -5
- package/dist/DigNetwork/ContentServer.d.ts.map +1 -1
- package/dist/DigNetwork/ContentServer.js +239 -27
- package/dist/DigNetwork/DigNetwork.d.ts +15 -9
- package/dist/DigNetwork/DigNetwork.d.ts.map +1 -1
- package/dist/DigNetwork/DigNetwork.js +129 -189
- package/dist/DigNetwork/DigPeer.d.ts +8 -3
- package/dist/DigNetwork/DigPeer.d.ts.map +1 -1
- package/dist/DigNetwork/DigPeer.js +72 -115
- package/dist/DigNetwork/IncentiveServer.d.ts.map +1 -1
- package/dist/DigNetwork/IncentiveServer.js +6 -5
- package/dist/DigNetwork/PropagationServer.d.ts +88 -33
- package/dist/DigNetwork/PropagationServer.d.ts.map +1 -1
- package/dist/DigNetwork/PropagationServer.js +636 -357
- package/dist/blockchain/DataStore.d.ts +25 -7
- package/dist/blockchain/DataStore.d.ts.map +1 -1
- package/dist/blockchain/DataStore.js +115 -159
- package/dist/blockchain/DataStoreSerializer.d.ts +1 -1
- package/dist/blockchain/DataStoreSerializer.d.ts.map +1 -1
- package/dist/blockchain/FullNodePeer.d.ts +95 -10
- package/dist/blockchain/FullNodePeer.d.ts.map +1 -1
- package/dist/blockchain/FullNodePeer.js +324 -115
- 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 +238 -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 +93 -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/PeerRanker.d.ts +55 -0
- package/dist/utils/PeerRanker.d.ts.map +1 -0
- package/dist/utils/PeerRanker.js +186 -0
- 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 +13 -8
- package/dist/utils/directoryUtils.d.ts +16 -0
- package/dist/utils/directoryUtils.d.ts.map +1 -1
- package/dist/utils/directoryUtils.js +66 -0
- package/dist/utils/index.d.ts +3 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -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 +2 -1
- package/dist/utils/network.d.ts.map +1 -1
- package/dist/utils/network.js +40 -15
- package/dist/utils/promiseUtils.d.ts +19 -0
- package/dist/utils/promiseUtils.d.ts.map +1 -0
- package/dist/utils/promiseUtils.js +43 -0
- package/dist/utils/ssl.js +2 -2
- package/package.json +18 -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.
|
|
@@ -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 {
|
|
@@ -135,7 +134,10 @@ class DataIntegrityTree {
|
|
|
135
134
|
const manifest = this._loadManifest();
|
|
136
135
|
if (manifest.length > 0) {
|
|
137
136
|
const latestRootHash = manifest[manifest.length - 1];
|
|
138
|
-
|
|
137
|
+
if (latestRootHash && isHexString(latestRootHash)) {
|
|
138
|
+
return this.deserializeTree(latestRootHash);
|
|
139
|
+
}
|
|
140
|
+
return new merkletreejs_1.MerkleTree([], crypto_js_1.SHA256, { sortPairs: true });
|
|
139
141
|
}
|
|
140
142
|
else {
|
|
141
143
|
return new merkletreejs_1.MerkleTree([], crypto_js_1.SHA256, { sortPairs: true });
|
|
@@ -189,11 +191,16 @@ class DataIntegrityTree {
|
|
|
189
191
|
fs.mkdirSync(tempDir, { recursive: true });
|
|
190
192
|
}
|
|
191
193
|
const tempFilePath = path.join(tempDir, `${crypto.randomUUID()}.gz`);
|
|
194
|
+
let totalBytes = 0;
|
|
192
195
|
return new Promise((resolve, reject) => {
|
|
193
196
|
const tempWriteStream = fs.createWriteStream(tempFilePath);
|
|
194
197
|
readStream.on("data", (chunk) => {
|
|
195
198
|
uncompressedHash.update(chunk);
|
|
196
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
|
+
});
|
|
197
204
|
readStream.pipe(gzip).pipe(tempWriteStream);
|
|
198
205
|
tempWriteStream.on("finish", async () => {
|
|
199
206
|
sha256 = uncompressedHash.digest("hex");
|
|
@@ -222,6 +229,7 @@ class DataIntegrityTree {
|
|
|
222
229
|
this.files.set(key, {
|
|
223
230
|
hash: combinedHash,
|
|
224
231
|
sha256: sha256,
|
|
232
|
+
bytes: totalBytes.toString(),
|
|
225
233
|
});
|
|
226
234
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
227
235
|
this._rebuildTree();
|
|
@@ -313,6 +321,11 @@ class DataIntegrityTree {
|
|
|
313
321
|
files: Object.fromEntries(this.files),
|
|
314
322
|
};
|
|
315
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
|
+
}
|
|
316
329
|
/**
|
|
317
330
|
* Deserialize a JSON object to a Merkle tree.
|
|
318
331
|
* @param rootHash - The root hash of the tree.
|
|
@@ -603,14 +616,13 @@ class DataIntegrityTree {
|
|
|
603
616
|
decompressStream.on("end", () => {
|
|
604
617
|
const uncompressedSha256 = hash.digest("hex");
|
|
605
618
|
const isValid = uncompressedSha256 === sha256;
|
|
606
|
-
console.log(`SHA-256 of uncompressed file: ${uncompressedSha256}`);
|
|
607
619
|
if (!isValid) {
|
|
608
620
|
return resolve(false);
|
|
609
621
|
}
|
|
610
622
|
const tree = this.deserializeTree(rootHash);
|
|
611
623
|
const combinedHash = crypto
|
|
612
624
|
.createHash("sha256")
|
|
613
|
-
.update(
|
|
625
|
+
.update(sha256)
|
|
614
626
|
.digest("hex");
|
|
615
627
|
const leaf = Buffer.from(combinedHash, "hex");
|
|
616
628
|
const isInTree = tree.getLeafIndex(leaf) !== -1;
|
|
@@ -632,10 +644,12 @@ class DataIntegrityTree {
|
|
|
632
644
|
* @param sha256 - The SHA-256 hash of the file.
|
|
633
645
|
* @param serializedTree - The foreign serialized Merkle tree.
|
|
634
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.
|
|
635
649
|
* @returns A boolean indicating if the SHA-256 is present in the foreign tree and the root hash matches.
|
|
636
650
|
*/
|
|
637
|
-
static validateKeyIntegrityWithForeignTree(
|
|
638
|
-
if (!isHexString(
|
|
651
|
+
static async validateKeyIntegrityWithForeignTree(hexkey, sha256, serializedTree, expectedRootHash, dataDir, verifiedSha256 = false) {
|
|
652
|
+
if (!isHexString(hexkey)) {
|
|
639
653
|
throw new Error("key must be a valid hex string");
|
|
640
654
|
}
|
|
641
655
|
if (!isHexString(sha256)) {
|
|
@@ -644,6 +658,56 @@ class DataIntegrityTree {
|
|
|
644
658
|
if (!isHexString(expectedRootHash)) {
|
|
645
659
|
throw new Error("expectedRootHash must be a valid hex string");
|
|
646
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) {
|
|
647
711
|
// Deserialize the foreign tree
|
|
648
712
|
const leaves = serializedTree.leaves.map((leaf) => Buffer.from(leaf, "hex"));
|
|
649
713
|
const tree = new merkletreejs_1.MerkleTree(leaves, crypto_js_1.SHA256, { sortPairs: true });
|
|
@@ -651,7 +715,7 @@ class DataIntegrityTree {
|
|
|
651
715
|
const treeRootHash = tree.getRoot().toString("hex");
|
|
652
716
|
if (treeRootHash !== expectedRootHash) {
|
|
653
717
|
console.warn(`Expected root hash ${expectedRootHash}, but got ${treeRootHash}`);
|
|
654
|
-
return
|
|
718
|
+
return null;
|
|
655
719
|
}
|
|
656
720
|
// Rebuild the files map from the serialized tree
|
|
657
721
|
// @ts-ignore
|
|
@@ -659,10 +723,13 @@ class DataIntegrityTree {
|
|
|
659
723
|
key,
|
|
660
724
|
{ hash: value.hash, sha256: value.sha256 },
|
|
661
725
|
]));
|
|
662
|
-
|
|
726
|
+
return tree;
|
|
727
|
+
}
|
|
728
|
+
// Function to check if the combined hash exists in the tree
|
|
729
|
+
static checkHashInTree(tree, hexkey, sha256) {
|
|
663
730
|
const combinedHash = crypto
|
|
664
731
|
.createHash("sha256")
|
|
665
|
-
.update(`${
|
|
732
|
+
.update(`${hexkey}/${sha256}`)
|
|
666
733
|
.digest("hex");
|
|
667
734
|
const leaf = Buffer.from(combinedHash, "hex");
|
|
668
735
|
const isInTree = tree.getLeafIndex(leaf) !== -1;
|
|
@@ -3,26 +3,45 @@ 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>;
|
|
13
|
+
/**
|
|
14
|
+
* Fetches and caches the .well-known information for the store's IP address.
|
|
15
|
+
*
|
|
16
|
+
* @returns A promise that resolves to the .well-known JSON data.
|
|
17
|
+
*/
|
|
10
18
|
getWellKnown(): Promise<any>;
|
|
11
19
|
getKnownStores(): Promise<any>;
|
|
12
20
|
getStoresIndex(): Promise<any>;
|
|
13
|
-
getKeysIndex(): Promise<any>;
|
|
14
|
-
headKey(key: string): Promise<{
|
|
21
|
+
getKeysIndex(rootHash?: string): Promise<any>;
|
|
22
|
+
headKey(key: string, rootHash?: string): Promise<{
|
|
15
23
|
success: boolean;
|
|
16
24
|
headers?: http.IncomingHttpHeaders;
|
|
17
25
|
}>;
|
|
18
|
-
headStore(
|
|
26
|
+
headStore(options?: {
|
|
27
|
+
hasRootHash: string;
|
|
28
|
+
}): Promise<{
|
|
19
29
|
success: boolean;
|
|
20
30
|
headers?: http.IncomingHttpHeaders;
|
|
21
31
|
}>;
|
|
22
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Checks if the store has the specified rootHash.
|
|
34
|
+
* Utilizes caching to improve performance.
|
|
35
|
+
*
|
|
36
|
+
* @param rootHash - The root hash to check.
|
|
37
|
+
* @returns A promise that resolves to true if the root hash exists, otherwise false.
|
|
38
|
+
*/
|
|
39
|
+
hasRootHash(rootHash: string): Promise<boolean>;
|
|
40
|
+
streamKey(key: string, rootHash?: string): Promise<Readable>;
|
|
23
41
|
private head;
|
|
24
42
|
private fetchJson;
|
|
25
43
|
private fetchWithRetries;
|
|
26
44
|
private fetch;
|
|
45
|
+
private fetchFirstChunk;
|
|
27
46
|
}
|
|
28
47
|
//# 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;AAQlC,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;IAeL,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAS3D,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAcxD;;;;OAIG;IACU,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC;IA6B5B,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAQ9B,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAM9B,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAqB7C,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;IAqBvD,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;IAmBF;;;;;;OAMG;IACU,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA0BrD,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;YAmDrD,IAAI;YA0EJ,SAAS;YAMT,gBAAgB;YAiChB,KAAK;YA+GL,eAAe;CA4G9B"}
|
|
@@ -4,23 +4,40 @@ 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");
|
|
12
|
+
const node_cache_1 = __importDefault(require("node-cache"));
|
|
13
|
+
const hasRootHashCache = new node_cache_1.default({ stdTTL: 86400 });
|
|
14
|
+
const wellKnownCache = new node_cache_1.default({ stdTTL: 86400 });
|
|
9
15
|
class ContentServer {
|
|
10
16
|
constructor(ipAddress, storeId) {
|
|
11
17
|
this.ipAddress = ipAddress;
|
|
12
18
|
this.storeId = storeId;
|
|
19
|
+
if (!ContentServer.certPath || !ContentServer.keyPath) {
|
|
20
|
+
const { certPath, keyPath } = (0, ssl_1.getOrCreateSSLCerts)();
|
|
21
|
+
ContentServer.certPath = certPath;
|
|
22
|
+
ContentServer.keyPath = keyPath;
|
|
23
|
+
}
|
|
13
24
|
}
|
|
14
25
|
// Method to get the content of a specified key from the peer, with optional challenge query
|
|
15
26
|
async getKey(key, rootHash, challengeHex) {
|
|
16
27
|
// Construct the base URL
|
|
17
|
-
let url = `
|
|
28
|
+
let url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/chia.${this.storeId}.${rootHash}/${key}`;
|
|
18
29
|
// If a challenge is provided, append it as a query parameter
|
|
19
30
|
if (challengeHex) {
|
|
20
31
|
url += `?challenge=${challengeHex}`;
|
|
21
32
|
}
|
|
22
33
|
return this.fetchWithRetries(url);
|
|
23
34
|
}
|
|
35
|
+
// New method to get only the first chunk of the content
|
|
36
|
+
async getKeyChunk(key, rootHash) {
|
|
37
|
+
// Construct the base URL
|
|
38
|
+
let url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/chia.${this.storeId}.${rootHash}/${key}`;
|
|
39
|
+
return this.fetchFirstChunk(url);
|
|
40
|
+
}
|
|
24
41
|
// Method to get the payment address from the peer
|
|
25
42
|
async getPaymentAddress() {
|
|
26
43
|
console.log(`Fetching payment address from peer ${this.ipAddress}...`);
|
|
@@ -30,43 +47,128 @@ class ContentServer {
|
|
|
30
47
|
}
|
|
31
48
|
catch (error) {
|
|
32
49
|
console.error(`Failed to fetch payment address from ${this.ipAddress}: ${error.message}`);
|
|
33
|
-
|
|
50
|
+
return null;
|
|
34
51
|
}
|
|
35
52
|
}
|
|
36
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Fetches and caches the .well-known information for the store's IP address.
|
|
55
|
+
*
|
|
56
|
+
* @returns A promise that resolves to the .well-known JSON data.
|
|
57
|
+
*/
|
|
37
58
|
async getWellKnown() {
|
|
38
|
-
|
|
39
|
-
|
|
59
|
+
// Construct the cache key based on ipAddress
|
|
60
|
+
const cacheKey = `${this.ipAddress}-wellknown`;
|
|
61
|
+
// Check if the result is already cached
|
|
62
|
+
const cachedResult = wellKnownCache.get(cacheKey);
|
|
63
|
+
if (cachedResult !== undefined) {
|
|
64
|
+
return cachedResult;
|
|
65
|
+
}
|
|
66
|
+
// If not cached, proceed to fetch the .well-known information
|
|
67
|
+
const url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/.well-known`;
|
|
68
|
+
try {
|
|
69
|
+
const data = await this.fetchJson(url);
|
|
70
|
+
wellKnownCache.set(cacheKey, data);
|
|
71
|
+
return data;
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
console.error(`Error fetching .well-known information for ${this.ipAddress}:`, error.message);
|
|
75
|
+
throw error; // Propagate the error after logging
|
|
76
|
+
}
|
|
40
77
|
}
|
|
41
78
|
// Method to get the list of known stores
|
|
42
79
|
async getKnownStores() {
|
|
43
|
-
const url = `
|
|
80
|
+
const url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/.well-known/stores`;
|
|
44
81
|
return this.fetchJson(url);
|
|
45
82
|
}
|
|
46
83
|
// Method to get the index of all stores
|
|
47
84
|
async getStoresIndex() {
|
|
48
|
-
const url = `
|
|
85
|
+
const url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/`;
|
|
49
86
|
return this.fetchJson(url);
|
|
50
87
|
}
|
|
51
88
|
// Method to get the index of keys in a store
|
|
52
|
-
async getKeysIndex() {
|
|
53
|
-
|
|
54
|
-
|
|
89
|
+
async getKeysIndex(rootHash) {
|
|
90
|
+
try {
|
|
91
|
+
let udi = `chia.${this.storeId}`;
|
|
92
|
+
if (rootHash) {
|
|
93
|
+
udi += `.${rootHash}`;
|
|
94
|
+
}
|
|
95
|
+
const url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/${udi}`;
|
|
96
|
+
return this.fetchJson(url);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (rootHash) {
|
|
100
|
+
hasRootHashCache.del(`${this.storeId}-${rootHash}`);
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
55
104
|
}
|
|
56
105
|
// Method to check if a specific key exists (HEAD request)
|
|
57
|
-
async headKey(key) {
|
|
58
|
-
|
|
59
|
-
|
|
106
|
+
async headKey(key, rootHash) {
|
|
107
|
+
try {
|
|
108
|
+
let udi = `chia.${this.storeId}`;
|
|
109
|
+
if (rootHash) {
|
|
110
|
+
udi += `.${rootHash}`;
|
|
111
|
+
}
|
|
112
|
+
const url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/${udi}/${key}`;
|
|
113
|
+
return this.head(url);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
if (rootHash) {
|
|
117
|
+
hasRootHashCache.del(`${this.storeId}-${rootHash}`);
|
|
118
|
+
}
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
60
121
|
}
|
|
61
122
|
// Method to check if a specific store exists (HEAD request)
|
|
62
|
-
async headStore() {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
123
|
+
async headStore(options) {
|
|
124
|
+
try {
|
|
125
|
+
let url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/chia.${this.storeId}`;
|
|
126
|
+
if (options?.hasRootHash) {
|
|
127
|
+
url += `?hasRootHash=${options.hasRootHash}`;
|
|
128
|
+
}
|
|
129
|
+
return this.head(url);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (options?.hasRootHash) {
|
|
133
|
+
hasRootHashCache.del(`${this.storeId}-${options.hasRootHash}`);
|
|
134
|
+
}
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
66
137
|
}
|
|
67
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Checks if the store has the specified rootHash.
|
|
140
|
+
* Utilizes caching to improve performance.
|
|
141
|
+
*
|
|
142
|
+
* @param rootHash - The root hash to check.
|
|
143
|
+
* @returns A promise that resolves to true if the root hash exists, otherwise false.
|
|
144
|
+
*/
|
|
145
|
+
async hasRootHash(rootHash) {
|
|
146
|
+
// Construct the cache key using storeId and rootHash
|
|
147
|
+
const cacheKey = `${this.storeId}-${rootHash}`;
|
|
148
|
+
// Check if the result is already cached
|
|
149
|
+
const cachedResult = hasRootHashCache.get(cacheKey);
|
|
150
|
+
if (cachedResult !== undefined) {
|
|
151
|
+
return cachedResult;
|
|
152
|
+
}
|
|
153
|
+
// If not cached, perform the headStore request
|
|
154
|
+
const { success, headers } = await this.headStore({
|
|
155
|
+
hasRootHash: rootHash,
|
|
156
|
+
});
|
|
157
|
+
// Determine if the store has the root hash
|
|
158
|
+
const hasHash = success && headers?.["x-has-root-hash"] === "true";
|
|
159
|
+
// Only cache the result if the store has the root hash
|
|
160
|
+
if (hasHash) {
|
|
161
|
+
hasRootHashCache.set(cacheKey, true);
|
|
162
|
+
}
|
|
163
|
+
return hasHash;
|
|
164
|
+
}
|
|
165
|
+
streamKey(key, rootHash) {
|
|
166
|
+
let udi = `chia.${this.storeId}`;
|
|
167
|
+
if (rootHash) {
|
|
168
|
+
udi += `.${rootHash}`;
|
|
169
|
+
}
|
|
68
170
|
return new Promise((resolve, reject) => {
|
|
69
|
-
const url = `
|
|
171
|
+
const url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/${udi}/${key}`;
|
|
70
172
|
const urlObj = new url_1.URL(url);
|
|
71
173
|
const requestOptions = {
|
|
72
174
|
hostname: urlObj.hostname,
|
|
@@ -89,11 +191,12 @@ class ContentServer {
|
|
|
89
191
|
}
|
|
90
192
|
}
|
|
91
193
|
else {
|
|
194
|
+
hasRootHashCache.del(`${this.storeId}-${rootHash}`);
|
|
92
195
|
reject(new Error(`Failed to retrieve data from ${url}. Status code: ${response.statusCode}`));
|
|
93
196
|
}
|
|
94
197
|
});
|
|
95
198
|
request.on("error", (error) => {
|
|
96
|
-
console.error(`Request error for ${url}:`, error);
|
|
199
|
+
console.error(`GET Request error for ${url}:`, error);
|
|
97
200
|
reject(error);
|
|
98
201
|
});
|
|
99
202
|
request.end();
|
|
@@ -111,6 +214,9 @@ class ContentServer {
|
|
|
111
214
|
(urlObj.protocol === "http:" ? 80 : ContentServer.port),
|
|
112
215
|
path: urlObj.pathname + urlObj.search,
|
|
113
216
|
method: "HEAD",
|
|
217
|
+
key: fs_1.default.readFileSync(ContentServer.keyPath),
|
|
218
|
+
cert: fs_1.default.readFileSync(ContentServer.certPath),
|
|
219
|
+
rejectUnauthorized: false,
|
|
114
220
|
};
|
|
115
221
|
const request = http_1.default.request(requestOptions, (response) => {
|
|
116
222
|
const { statusCode, headers } = response;
|
|
@@ -149,7 +255,7 @@ class ContentServer {
|
|
|
149
255
|
}
|
|
150
256
|
});
|
|
151
257
|
request.on("error", (error) => {
|
|
152
|
-
console.error(`
|
|
258
|
+
console.error(`HEAD ${url}:`, error.message);
|
|
153
259
|
reject({ success: false });
|
|
154
260
|
});
|
|
155
261
|
request.end();
|
|
@@ -168,7 +274,7 @@ class ContentServer {
|
|
|
168
274
|
// Helper method to fetch content with retries and redirection handling
|
|
169
275
|
async fetchWithRetries(url) {
|
|
170
276
|
let attempt = 0;
|
|
171
|
-
const maxRetries =
|
|
277
|
+
const maxRetries = 1;
|
|
172
278
|
const initialDelay = 2000; // 2 seconds
|
|
173
279
|
const maxDelay = 10000; // 10 seconds
|
|
174
280
|
const delayMultiplier = 1.5;
|
|
@@ -192,23 +298,48 @@ class ContentServer {
|
|
|
192
298
|
}
|
|
193
299
|
throw new Error(`Failed to retrieve data from ${url} after ${maxRetries} attempts.`);
|
|
194
300
|
}
|
|
195
|
-
// Core method to fetch content from a URL
|
|
301
|
+
// Core method to fetch content from a URL with a 5-second inactivity timeout
|
|
196
302
|
async fetch(url, maxRedirects = 5) {
|
|
197
303
|
return new Promise((resolve, reject) => {
|
|
198
304
|
const urlObj = new url_1.URL(url);
|
|
305
|
+
const timeoutDuration = 5000; // 5 seconds
|
|
306
|
+
let timeout = null; // Initialize timeout
|
|
199
307
|
const requestOptions = {
|
|
200
308
|
hostname: urlObj.hostname,
|
|
201
309
|
port: urlObj.port || ContentServer.port,
|
|
202
310
|
path: urlObj.pathname + urlObj.search, // Include query params
|
|
203
311
|
method: "GET",
|
|
312
|
+
key: fs_1.default.readFileSync(ContentServer.keyPath),
|
|
313
|
+
cert: fs_1.default.readFileSync(ContentServer.certPath),
|
|
314
|
+
rejectUnauthorized: false,
|
|
204
315
|
};
|
|
205
316
|
const request = http_1.default.request(requestOptions, (response) => {
|
|
206
317
|
let data = "";
|
|
318
|
+
// Set timeout for inactivity
|
|
319
|
+
timeout = setTimeout(() => {
|
|
320
|
+
console.error(`Request timeout: No data received for ${timeoutDuration / 1000} seconds.`);
|
|
321
|
+
request.destroy(); // Use destroy instead of abort
|
|
322
|
+
reject(new Error(`Request timed out after ${timeoutDuration / 1000} seconds of inactivity`));
|
|
323
|
+
}, timeoutDuration);
|
|
324
|
+
const resetTimeout = () => {
|
|
325
|
+
if (timeout) {
|
|
326
|
+
clearTimeout(timeout);
|
|
327
|
+
}
|
|
328
|
+
timeout = setTimeout(() => {
|
|
329
|
+
console.error(`Request timeout: No data received for ${timeoutDuration / 1000} seconds.`);
|
|
330
|
+
request.destroy(); // Use destroy instead of abort
|
|
331
|
+
reject(new Error(`Request timed out after ${timeoutDuration / 1000} seconds of inactivity`));
|
|
332
|
+
}, timeoutDuration);
|
|
333
|
+
};
|
|
207
334
|
if (response.statusCode === 200) {
|
|
208
335
|
response.on("data", (chunk) => {
|
|
209
336
|
data += chunk;
|
|
337
|
+
resetTimeout(); // Reset the timeout every time data is received
|
|
210
338
|
});
|
|
211
339
|
response.on("end", () => {
|
|
340
|
+
if (timeout) {
|
|
341
|
+
clearTimeout(timeout);
|
|
342
|
+
}
|
|
212
343
|
resolve(data);
|
|
213
344
|
});
|
|
214
345
|
}
|
|
@@ -217,8 +348,9 @@ class ContentServer {
|
|
|
217
348
|
// Handle redirects
|
|
218
349
|
if (maxRedirects > 0) {
|
|
219
350
|
const redirectUrl = new url_1.URL(response.headers.location, url); // Resolve relative URLs based on the original URL
|
|
220
|
-
|
|
221
|
-
|
|
351
|
+
if (timeout) {
|
|
352
|
+
clearTimeout(timeout);
|
|
353
|
+
}
|
|
222
354
|
this.fetch(redirectUrl.toString(), maxRedirects - 1)
|
|
223
355
|
.then(resolve)
|
|
224
356
|
.catch(reject);
|
|
@@ -228,11 +360,91 @@ class ContentServer {
|
|
|
228
360
|
}
|
|
229
361
|
}
|
|
230
362
|
else {
|
|
363
|
+
if (timeout) {
|
|
364
|
+
clearTimeout(timeout);
|
|
365
|
+
}
|
|
231
366
|
reject(new Error(`Failed to retrieve data from ${url}. Status code: ${response.statusCode}`));
|
|
232
367
|
}
|
|
233
368
|
});
|
|
234
369
|
request.on("error", (error) => {
|
|
235
|
-
|
|
370
|
+
if (timeout) {
|
|
371
|
+
clearTimeout(timeout);
|
|
372
|
+
}
|
|
373
|
+
console.error(`GET ${url}:`, error.message);
|
|
374
|
+
reject(error);
|
|
375
|
+
});
|
|
376
|
+
request.end();
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
// New core method to fetch only the first chunk without retries
|
|
380
|
+
async fetchFirstChunk(url) {
|
|
381
|
+
return new Promise((resolve, reject) => {
|
|
382
|
+
const urlObj = new url_1.URL(url);
|
|
383
|
+
const timeoutDuration = 5000; // 5 seconds
|
|
384
|
+
let timeout = null;
|
|
385
|
+
const requestOptions = {
|
|
386
|
+
hostname: urlObj.hostname,
|
|
387
|
+
port: urlObj.port || ContentServer.port,
|
|
388
|
+
path: urlObj.pathname + urlObj.search, // Include query params
|
|
389
|
+
method: "GET",
|
|
390
|
+
key: fs_1.default.readFileSync(ContentServer.keyPath),
|
|
391
|
+
cert: fs_1.default.readFileSync(ContentServer.certPath),
|
|
392
|
+
rejectUnauthorized: false,
|
|
393
|
+
};
|
|
394
|
+
const request = http_1.default.request(requestOptions, (response) => {
|
|
395
|
+
// Set timeout for inactivity
|
|
396
|
+
timeout = setTimeout(() => {
|
|
397
|
+
console.error(`Request timeout: No data received for ${timeoutDuration / 1000} seconds.`);
|
|
398
|
+
request.destroy(); // Use destroy instead of abort
|
|
399
|
+
reject(new Error(`Request timed out after ${timeoutDuration / 1000} seconds of inactivity`));
|
|
400
|
+
}, timeoutDuration);
|
|
401
|
+
const resetTimeout = () => {
|
|
402
|
+
if (timeout) {
|
|
403
|
+
clearTimeout(timeout);
|
|
404
|
+
}
|
|
405
|
+
timeout = setTimeout(() => {
|
|
406
|
+
console.error(`Request timeout: No data received for ${timeoutDuration / 1000} seconds.`);
|
|
407
|
+
request.destroy(); // Use destroy instead of abort
|
|
408
|
+
reject(new Error(`Request timed out after ${timeoutDuration / 1000} seconds of inactivity`));
|
|
409
|
+
}, timeoutDuration);
|
|
410
|
+
};
|
|
411
|
+
if (response.statusCode === 200) {
|
|
412
|
+
response.once("data", (chunk) => {
|
|
413
|
+
if (timeout) {
|
|
414
|
+
clearTimeout(timeout);
|
|
415
|
+
}
|
|
416
|
+
response.destroy(); // Close the connection after receiving the first chunk
|
|
417
|
+
resolve(chunk);
|
|
418
|
+
});
|
|
419
|
+
response.on("end", () => {
|
|
420
|
+
if (timeout) {
|
|
421
|
+
clearTimeout(timeout);
|
|
422
|
+
}
|
|
423
|
+
// In case the response ends before any data is received
|
|
424
|
+
reject(new Error("No data received"));
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
else if ((response.statusCode === 301 || response.statusCode === 302) &&
|
|
428
|
+
response.headers.location) {
|
|
429
|
+
// Handle redirects
|
|
430
|
+
const redirectUrl = new url_1.URL(response.headers.location, url).toString(); // Resolve relative URLs
|
|
431
|
+
if (timeout) {
|
|
432
|
+
clearTimeout(timeout);
|
|
433
|
+
}
|
|
434
|
+
this.fetchFirstChunk(redirectUrl).then(resolve).catch(reject);
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
if (timeout) {
|
|
438
|
+
clearTimeout(timeout);
|
|
439
|
+
}
|
|
440
|
+
reject(new Error(`Failed to retrieve data from ${url}. Status code: ${response.statusCode}`));
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
request.on("error", (error) => {
|
|
444
|
+
if (timeout) {
|
|
445
|
+
clearTimeout(timeout);
|
|
446
|
+
}
|
|
447
|
+
console.error(`GET ${url}:`, error.message);
|
|
236
448
|
reject(error);
|
|
237
449
|
});
|
|
238
450
|
request.end();
|
|
@@ -240,4 +452,4 @@ class ContentServer {
|
|
|
240
452
|
}
|
|
241
453
|
}
|
|
242
454
|
exports.ContentServer = ContentServer;
|
|
243
|
-
ContentServer.port =
|
|
455
|
+
ContentServer.port = 4161;
|