@dignetwork/dig-sdk 0.0.1-alpha.17 → 0.0.1-alpha.171

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.
Files changed (87) hide show
  1. package/dist/DataIntegrityTree/DataIntegrityTree.d.ts +10 -1
  2. package/dist/DataIntegrityTree/DataIntegrityTree.d.ts.map +1 -1
  3. package/dist/DataIntegrityTree/DataIntegrityTree.js +77 -10
  4. package/dist/DigNetwork/ContentServer.d.ts +24 -5
  5. package/dist/DigNetwork/ContentServer.d.ts.map +1 -1
  6. package/dist/DigNetwork/ContentServer.js +237 -27
  7. package/dist/DigNetwork/DigNetwork.d.ts +15 -9
  8. package/dist/DigNetwork/DigNetwork.d.ts.map +1 -1
  9. package/dist/DigNetwork/DigNetwork.js +129 -189
  10. package/dist/DigNetwork/DigPeer.d.ts +8 -3
  11. package/dist/DigNetwork/DigPeer.d.ts.map +1 -1
  12. package/dist/DigNetwork/DigPeer.js +72 -115
  13. package/dist/DigNetwork/IncentiveServer.d.ts.map +1 -1
  14. package/dist/DigNetwork/IncentiveServer.js +6 -5
  15. package/dist/DigNetwork/PropagationServer.d.ts +88 -33
  16. package/dist/DigNetwork/PropagationServer.d.ts.map +1 -1
  17. package/dist/DigNetwork/PropagationServer.js +635 -358
  18. package/dist/blockchain/DataStore.d.ts +25 -7
  19. package/dist/blockchain/DataStore.d.ts.map +1 -1
  20. package/dist/blockchain/DataStore.js +114 -159
  21. package/dist/blockchain/DataStoreSerializer.d.ts +1 -1
  22. package/dist/blockchain/DataStoreSerializer.d.ts.map +1 -1
  23. package/dist/blockchain/FullNodePeer.d.ts +95 -10
  24. package/dist/blockchain/FullNodePeer.d.ts.map +1 -1
  25. package/dist/blockchain/FullNodePeer.js +325 -115
  26. package/dist/blockchain/ServerCoin.d.ts +13 -5
  27. package/dist/blockchain/ServerCoin.d.ts.map +1 -1
  28. package/dist/blockchain/ServerCoin.js +88 -28
  29. package/dist/blockchain/StoreMonitorRegistry.d.ts +85 -0
  30. package/dist/blockchain/StoreMonitorRegistry.d.ts.map +1 -0
  31. package/dist/blockchain/StoreMonitorRegistry.js +238 -0
  32. package/dist/blockchain/Wallet.d.ts +2 -2
  33. package/dist/blockchain/Wallet.d.ts.map +1 -1
  34. package/dist/blockchain/Wallet.js +50 -18
  35. package/dist/blockchain/coins.d.ts +1 -2
  36. package/dist/blockchain/coins.d.ts.map +1 -1
  37. package/dist/blockchain/coins.js +1 -55
  38. package/dist/blockchain/index.d.ts +1 -0
  39. package/dist/blockchain/index.d.ts.map +1 -1
  40. package/dist/blockchain/index.js +1 -0
  41. package/dist/types.d.ts +2 -0
  42. package/dist/types.d.ts.map +1 -1
  43. package/dist/utils/ContentScanner.d.ts +63 -0
  44. package/dist/utils/ContentScanner.d.ts.map +1 -0
  45. package/dist/utils/ContentScanner.js +175 -0
  46. package/dist/utils/DigCache.d.ts +38 -0
  47. package/dist/utils/DigCache.d.ts.map +1 -0
  48. package/dist/utils/DigCache.js +231 -0
  49. package/dist/utils/Environment.d.ts +21 -0
  50. package/dist/utils/Environment.d.ts.map +1 -0
  51. package/dist/utils/Environment.js +93 -0
  52. package/dist/utils/FileCache.d.ts +1 -1
  53. package/dist/utils/FileCache.d.ts.map +1 -1
  54. package/dist/utils/FileCache.js +2 -2
  55. package/dist/utils/FileTransfer.d.ts +47 -0
  56. package/dist/utils/FileTransfer.d.ts.map +1 -0
  57. package/dist/utils/FileTransfer.js +209 -0
  58. package/dist/utils/NconfManager.js +2 -1
  59. package/dist/utils/PeerRanker.d.ts +55 -0
  60. package/dist/utils/PeerRanker.d.ts.map +1 -0
  61. package/dist/utils/PeerRanker.js +186 -0
  62. package/dist/utils/StoreArchiveManager.d.ts +45 -0
  63. package/dist/utils/StoreArchiveManager.d.ts.map +1 -0
  64. package/dist/utils/StoreArchiveManager.js +153 -0
  65. package/dist/utils/config.d.ts +4 -3
  66. package/dist/utils/config.d.ts.map +1 -1
  67. package/dist/utils/config.js +13 -8
  68. package/dist/utils/directoryUtils.d.ts +16 -0
  69. package/dist/utils/directoryUtils.d.ts.map +1 -1
  70. package/dist/utils/directoryUtils.js +66 -0
  71. package/dist/utils/index.d.ts +4 -1
  72. package/dist/utils/index.d.ts.map +1 -1
  73. package/dist/utils/index.js +4 -1
  74. package/dist/utils/merkle.d.ts +2 -0
  75. package/dist/utils/merkle.d.ts.map +1 -0
  76. package/dist/utils/merkle.js +28 -0
  77. package/dist/utils/network.d.ts +2 -1
  78. package/dist/utils/network.d.ts.map +1 -1
  79. package/dist/utils/network.js +40 -15
  80. package/dist/utils/promiseUtils.d.ts +27 -0
  81. package/dist/utils/promiseUtils.d.ts.map +1 -0
  82. package/dist/utils/promiseUtils.js +69 -0
  83. package/dist/utils/ssl.js +2 -2
  84. package/package.json +19 -5
  85. package/dist/utils/deltaUtils.d.ts +0 -2
  86. package/dist/utils/deltaUtils.d.ts.map +0 -1
  87. 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(key: string, sha256: string, serializedTree: object, expectedRootHash: string): boolean;
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":"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"}
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
- const manifest = this._loadManifest();
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
- return this.deserializeTree(latestRootHash);
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(`${toHex(sha256)}/${sha256}`)
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(key, sha256, serializedTree, expectedRootHash) {
638
- if (!isHexString(key)) {
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 false;
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
- // Check if the SHA-256 exists in the foreign tree's files
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(`${toHex(key)}/${sha256}`)
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
- getPaymentAddress(): Promise<string>;
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(): Promise<{
26
+ headStore(options?: {
27
+ hasRootHash: string;
28
+ }): Promise<{
19
29
  success: boolean;
20
30
  headers?: http.IncomingHttpHeaders;
21
31
  }>;
22
- streamKey(key: string): Promise<Readable>;
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":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAGlC,qBAAa,aAAa;IACxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAM;gBAEtB,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IAMjC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAa7E,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;IAepC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC;IAM5B,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAM9B,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAM9B,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC;IAM5B,OAAO,CAClB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,IAAI,CAAC,mBAAmB,CAAA;KAAE,CAAC;IAMvD,SAAS,IAAI,OAAO,CAAC;QAChC,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,CAAC,EAAE,IAAI,CAAC,mBAAmB,CAAC;KACpC,CAAC;IAMK,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;YA0ClC,IAAI;YAuEJ,SAAS;YAMT,gBAAgB;YAiChB,KAAK;CAuDpB"}
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;AAMlC,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,38 @@ 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 utils_1 = require("../utils");
11
+ const hasRootHashCache = new utils_1.DigCache({ stdTTL: 86400 });
12
+ const wellKnownCache = new utils_1.DigCache({ stdTTL: 86400 });
9
13
  class ContentServer {
10
14
  constructor(ipAddress, storeId) {
11
15
  this.ipAddress = ipAddress;
12
16
  this.storeId = storeId;
17
+ if (!ContentServer.certPath || !ContentServer.keyPath) {
18
+ const { certPath, keyPath } = (0, utils_1.getOrCreateSSLCerts)();
19
+ ContentServer.certPath = certPath;
20
+ ContentServer.keyPath = keyPath;
21
+ }
13
22
  }
14
23
  // Method to get the content of a specified key from the peer, with optional challenge query
15
24
  async getKey(key, rootHash, challengeHex) {
16
25
  // Construct the base URL
17
- let url = `http://${this.ipAddress}/chia.${this.storeId}.${rootHash}/${key}`;
26
+ let url = `https://${(0, utils_1.formatHost)(this.ipAddress)}:${ContentServer.port}/chia.${this.storeId}.${rootHash}/${key}`;
18
27
  // If a challenge is provided, append it as a query parameter
19
28
  if (challengeHex) {
20
29
  url += `?challenge=${challengeHex}`;
21
30
  }
22
31
  return this.fetchWithRetries(url);
23
32
  }
33
+ // New method to get only the first chunk of the content
34
+ async getKeyChunk(key, rootHash) {
35
+ // Construct the base URL
36
+ let url = `https://${(0, utils_1.formatHost)(this.ipAddress)}:${ContentServer.port}/chia.${this.storeId}.${rootHash}/${key}`;
37
+ return this.fetchFirstChunk(url);
38
+ }
24
39
  // Method to get the payment address from the peer
25
40
  async getPaymentAddress() {
26
41
  console.log(`Fetching payment address from peer ${this.ipAddress}...`);
@@ -30,43 +45,128 @@ class ContentServer {
30
45
  }
31
46
  catch (error) {
32
47
  console.error(`Failed to fetch payment address from ${this.ipAddress}: ${error.message}`);
33
- throw new Error(`Failed to fetch payment address: ${error.message}`);
48
+ return null;
34
49
  }
35
50
  }
36
- // Method to get the .well-known information
51
+ /**
52
+ * Fetches and caches the .well-known information for the store's IP address.
53
+ *
54
+ * @returns A promise that resolves to the .well-known JSON data.
55
+ */
37
56
  async getWellKnown() {
38
- const url = `http://${this.ipAddress}/.well-known`;
39
- return this.fetchJson(url);
57
+ // Construct the cache key based on ipAddress
58
+ const cacheKey = `${this.ipAddress}-wellknown`;
59
+ // Check if the result is already cached
60
+ const cachedResult = wellKnownCache.get(cacheKey);
61
+ if (cachedResult !== undefined) {
62
+ return cachedResult;
63
+ }
64
+ // If not cached, proceed to fetch the .well-known information
65
+ const url = `https://${(0, utils_1.formatHost)(this.ipAddress)}:${ContentServer.port}/.well-known`;
66
+ try {
67
+ const data = await this.fetchJson(url);
68
+ wellKnownCache.set(cacheKey, data);
69
+ return data;
70
+ }
71
+ catch (error) {
72
+ console.error(`Error fetching .well-known information for ${this.ipAddress}:`, error.message);
73
+ throw error; // Propagate the error after logging
74
+ }
40
75
  }
41
76
  // Method to get the list of known stores
42
77
  async getKnownStores() {
43
- const url = `http://${this.ipAddress}/.well-known/stores`;
78
+ const url = `https://${(0, utils_1.formatHost)(this.ipAddress)}:${ContentServer.port}/.well-known/stores`;
44
79
  return this.fetchJson(url);
45
80
  }
46
81
  // Method to get the index of all stores
47
82
  async getStoresIndex() {
48
- const url = `http://${this.ipAddress}/`;
83
+ const url = `https://${(0, utils_1.formatHost)(this.ipAddress)}:${ContentServer.port}/`;
49
84
  return this.fetchJson(url);
50
85
  }
51
86
  // Method to get the index of keys in a store
52
- async getKeysIndex() {
53
- const url = `http://${this.ipAddress}/${this.storeId}`;
54
- return this.fetchJson(url);
87
+ async getKeysIndex(rootHash) {
88
+ try {
89
+ let udi = `chia.${this.storeId}`;
90
+ if (rootHash) {
91
+ udi += `.${rootHash}`;
92
+ }
93
+ const url = `https://${(0, utils_1.formatHost)(this.ipAddress)}:${ContentServer.port}/${udi}`;
94
+ return this.fetchJson(url);
95
+ }
96
+ catch (error) {
97
+ if (rootHash) {
98
+ hasRootHashCache.del(`${this.storeId}-${rootHash}`);
99
+ }
100
+ throw error;
101
+ }
55
102
  }
56
103
  // Method to check if a specific key exists (HEAD request)
57
- async headKey(key) {
58
- const url = `http://${this.ipAddress}/${this.storeId}/${key}`;
59
- return this.head(url); // Return the object from head method
104
+ async headKey(key, rootHash) {
105
+ try {
106
+ let udi = `chia.${this.storeId}`;
107
+ if (rootHash) {
108
+ udi += `.${rootHash}`;
109
+ }
110
+ const url = `https://${(0, utils_1.formatHost)(this.ipAddress)}:${ContentServer.port}/${udi}/${key}`;
111
+ return this.head(url);
112
+ }
113
+ catch (error) {
114
+ if (rootHash) {
115
+ hasRootHashCache.del(`${this.storeId}-${rootHash}`);
116
+ }
117
+ throw error;
118
+ }
60
119
  }
61
120
  // Method to check if a specific store exists (HEAD request)
62
- async headStore() {
63
- const url = `http://${this.ipAddress}/${this.storeId}`;
64
- console.log({ url });
65
- return this.head(url); // Return the object from head method
121
+ async headStore(options) {
122
+ try {
123
+ let url = `https://${(0, utils_1.formatHost)(this.ipAddress)}:${ContentServer.port}/chia.${this.storeId}`;
124
+ if (options?.hasRootHash) {
125
+ url += `?hasRootHash=${options.hasRootHash}`;
126
+ }
127
+ return this.head(url);
128
+ }
129
+ catch (error) {
130
+ if (options?.hasRootHash) {
131
+ hasRootHashCache.del(`${this.storeId}-${options.hasRootHash}`);
132
+ }
133
+ throw error;
134
+ }
66
135
  }
67
- streamKey(key) {
136
+ /**
137
+ * Checks if the store has the specified rootHash.
138
+ * Utilizes caching to improve performance.
139
+ *
140
+ * @param rootHash - The root hash to check.
141
+ * @returns A promise that resolves to true if the root hash exists, otherwise false.
142
+ */
143
+ async hasRootHash(rootHash) {
144
+ // Construct the cache key using storeId and rootHash
145
+ const cacheKey = `${this.storeId}-${rootHash}`;
146
+ // Check if the result is already cached
147
+ const cachedResult = await hasRootHashCache.get(cacheKey);
148
+ if (cachedResult !== undefined) {
149
+ return cachedResult;
150
+ }
151
+ // If not cached, perform the headStore request
152
+ const { success, headers } = await this.headStore({
153
+ hasRootHash: rootHash,
154
+ });
155
+ // Determine if the store has the root hash
156
+ const hasHash = success && headers?.["x-has-root-hash"] === "true";
157
+ // Only cache the result if the store has the root hash
158
+ if (hasHash) {
159
+ hasRootHashCache.set(cacheKey, true);
160
+ }
161
+ return hasHash;
162
+ }
163
+ streamKey(key, rootHash) {
164
+ let udi = `chia.${this.storeId}`;
165
+ if (rootHash) {
166
+ udi += `.${rootHash}`;
167
+ }
68
168
  return new Promise((resolve, reject) => {
69
- const url = `http://${this.ipAddress}/${this.storeId}/${key}`;
169
+ const url = `https://${(0, utils_1.formatHost)(this.ipAddress)}:${ContentServer.port}/${udi}/${key}`;
70
170
  const urlObj = new url_1.URL(url);
71
171
  const requestOptions = {
72
172
  hostname: urlObj.hostname,
@@ -89,11 +189,12 @@ class ContentServer {
89
189
  }
90
190
  }
91
191
  else {
192
+ hasRootHashCache.del(`${this.storeId}-${rootHash}`);
92
193
  reject(new Error(`Failed to retrieve data from ${url}. Status code: ${response.statusCode}`));
93
194
  }
94
195
  });
95
196
  request.on("error", (error) => {
96
- console.error(`Request error for ${url}:`, error);
197
+ console.error(`GET Request error for ${url}:`, error);
97
198
  reject(error);
98
199
  });
99
200
  request.end();
@@ -111,6 +212,9 @@ class ContentServer {
111
212
  (urlObj.protocol === "http:" ? 80 : ContentServer.port),
112
213
  path: urlObj.pathname + urlObj.search,
113
214
  method: "HEAD",
215
+ key: fs_1.default.readFileSync(ContentServer.keyPath),
216
+ cert: fs_1.default.readFileSync(ContentServer.certPath),
217
+ rejectUnauthorized: false,
114
218
  };
115
219
  const request = http_1.default.request(requestOptions, (response) => {
116
220
  const { statusCode, headers } = response;
@@ -149,7 +253,7 @@ class ContentServer {
149
253
  }
150
254
  });
151
255
  request.on("error", (error) => {
152
- console.error(`Request error for ${url}:`, error);
256
+ console.error(`HEAD ${url}:`, error.message);
153
257
  reject({ success: false });
154
258
  });
155
259
  request.end();
@@ -168,7 +272,7 @@ class ContentServer {
168
272
  // Helper method to fetch content with retries and redirection handling
169
273
  async fetchWithRetries(url) {
170
274
  let attempt = 0;
171
- const maxRetries = 5;
275
+ const maxRetries = 1;
172
276
  const initialDelay = 2000; // 2 seconds
173
277
  const maxDelay = 10000; // 10 seconds
174
278
  const delayMultiplier = 1.5;
@@ -192,23 +296,48 @@ class ContentServer {
192
296
  }
193
297
  throw new Error(`Failed to retrieve data from ${url} after ${maxRetries} attempts.`);
194
298
  }
195
- // Core method to fetch content from a URL
299
+ // Core method to fetch content from a URL with a 5-second inactivity timeout
196
300
  async fetch(url, maxRedirects = 5) {
197
301
  return new Promise((resolve, reject) => {
198
302
  const urlObj = new url_1.URL(url);
303
+ const timeoutDuration = 5000; // 5 seconds
304
+ let timeout = null; // Initialize timeout
199
305
  const requestOptions = {
200
306
  hostname: urlObj.hostname,
201
307
  port: urlObj.port || ContentServer.port,
202
308
  path: urlObj.pathname + urlObj.search, // Include query params
203
309
  method: "GET",
310
+ key: fs_1.default.readFileSync(ContentServer.keyPath),
311
+ cert: fs_1.default.readFileSync(ContentServer.certPath),
312
+ rejectUnauthorized: false,
204
313
  };
205
314
  const request = http_1.default.request(requestOptions, (response) => {
206
315
  let data = "";
316
+ // Set timeout for inactivity
317
+ timeout = setTimeout(() => {
318
+ console.error(`Request timeout: No data received for ${timeoutDuration / 1000} seconds.`);
319
+ request.destroy(); // Use destroy instead of abort
320
+ reject(new Error(`Request timed out after ${timeoutDuration / 1000} seconds of inactivity`));
321
+ }, timeoutDuration);
322
+ const resetTimeout = () => {
323
+ if (timeout) {
324
+ clearTimeout(timeout);
325
+ }
326
+ timeout = setTimeout(() => {
327
+ console.error(`Request timeout: No data received for ${timeoutDuration / 1000} seconds.`);
328
+ request.destroy(); // Use destroy instead of abort
329
+ reject(new Error(`Request timed out after ${timeoutDuration / 1000} seconds of inactivity`));
330
+ }, timeoutDuration);
331
+ };
207
332
  if (response.statusCode === 200) {
208
333
  response.on("data", (chunk) => {
209
334
  data += chunk;
335
+ resetTimeout(); // Reset the timeout every time data is received
210
336
  });
211
337
  response.on("end", () => {
338
+ if (timeout) {
339
+ clearTimeout(timeout);
340
+ }
212
341
  resolve(data);
213
342
  });
214
343
  }
@@ -217,8 +346,9 @@ class ContentServer {
217
346
  // Handle redirects
218
347
  if (maxRedirects > 0) {
219
348
  const redirectUrl = new url_1.URL(response.headers.location, url); // Resolve relative URLs based on the original URL
220
- // Recursively follow the redirect, passing the same query params
221
- // console.log(`Redirecting to: ${redirectUrl.toString()}`);
349
+ if (timeout) {
350
+ clearTimeout(timeout);
351
+ }
222
352
  this.fetch(redirectUrl.toString(), maxRedirects - 1)
223
353
  .then(resolve)
224
354
  .catch(reject);
@@ -228,11 +358,91 @@ class ContentServer {
228
358
  }
229
359
  }
230
360
  else {
361
+ if (timeout) {
362
+ clearTimeout(timeout);
363
+ }
231
364
  reject(new Error(`Failed to retrieve data from ${url}. Status code: ${response.statusCode}`));
232
365
  }
233
366
  });
234
367
  request.on("error", (error) => {
235
- console.error(`Request error for ${url}:`, error);
368
+ if (timeout) {
369
+ clearTimeout(timeout);
370
+ }
371
+ console.error(`GET ${url}:`, error.message);
372
+ reject(error);
373
+ });
374
+ request.end();
375
+ });
376
+ }
377
+ // New core method to fetch only the first chunk without retries
378
+ async fetchFirstChunk(url) {
379
+ return new Promise((resolve, reject) => {
380
+ const urlObj = new url_1.URL(url);
381
+ const timeoutDuration = 5000; // 5 seconds
382
+ let timeout = null;
383
+ const requestOptions = {
384
+ hostname: urlObj.hostname,
385
+ port: urlObj.port || ContentServer.port,
386
+ path: urlObj.pathname + urlObj.search, // Include query params
387
+ method: "GET",
388
+ key: fs_1.default.readFileSync(ContentServer.keyPath),
389
+ cert: fs_1.default.readFileSync(ContentServer.certPath),
390
+ rejectUnauthorized: false,
391
+ };
392
+ const request = http_1.default.request(requestOptions, (response) => {
393
+ // Set timeout for inactivity
394
+ timeout = setTimeout(() => {
395
+ console.error(`Request timeout: No data received for ${timeoutDuration / 1000} seconds.`);
396
+ request.destroy(); // Use destroy instead of abort
397
+ reject(new Error(`Request timed out after ${timeoutDuration / 1000} seconds of inactivity`));
398
+ }, timeoutDuration);
399
+ const resetTimeout = () => {
400
+ if (timeout) {
401
+ clearTimeout(timeout);
402
+ }
403
+ timeout = setTimeout(() => {
404
+ console.error(`Request timeout: No data received for ${timeoutDuration / 1000} seconds.`);
405
+ request.destroy(); // Use destroy instead of abort
406
+ reject(new Error(`Request timed out after ${timeoutDuration / 1000} seconds of inactivity`));
407
+ }, timeoutDuration);
408
+ };
409
+ if (response.statusCode === 200) {
410
+ response.once("data", (chunk) => {
411
+ if (timeout) {
412
+ clearTimeout(timeout);
413
+ }
414
+ response.destroy(); // Close the connection after receiving the first chunk
415
+ resolve(chunk);
416
+ });
417
+ response.on("end", () => {
418
+ if (timeout) {
419
+ clearTimeout(timeout);
420
+ }
421
+ // In case the response ends before any data is received
422
+ reject(new Error("No data received"));
423
+ });
424
+ }
425
+ else if ((response.statusCode === 301 || response.statusCode === 302) &&
426
+ response.headers.location) {
427
+ // Handle redirects
428
+ const redirectUrl = new url_1.URL(response.headers.location, url).toString(); // Resolve relative URLs
429
+ if (timeout) {
430
+ clearTimeout(timeout);
431
+ }
432
+ this.fetchFirstChunk(redirectUrl).then(resolve).catch(reject);
433
+ }
434
+ else {
435
+ if (timeout) {
436
+ clearTimeout(timeout);
437
+ }
438
+ reject(new Error(`Failed to retrieve data from ${url}. Status code: ${response.statusCode}`));
439
+ }
440
+ });
441
+ request.on("error", (error) => {
442
+ if (timeout) {
443
+ clearTimeout(timeout);
444
+ }
445
+ console.error(`GET ${url}:`, error.message);
236
446
  reject(error);
237
447
  });
238
448
  request.end();
@@ -240,4 +450,4 @@ class ContentServer {
240
450
  }
241
451
  }
242
452
  exports.ContentServer = ContentServer;
243
- ContentServer.port = 80;
453
+ ContentServer.port = 4161;