@dignetwork/dig-sdk 0.0.1-alpha.46 → 0.0.1-alpha.48
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 +1 -1
- package/dist/DataIntegrityTree/DataIntegrityTree.d.ts.map +1 -1
- package/dist/DataIntegrityTree/DataIntegrityTree.js +54 -24
- package/dist/DigNetwork/ContentServer.js +11 -11
- package/dist/DigNetwork/DigNetwork.d.ts +1 -1
- package/dist/DigNetwork/DigNetwork.d.ts.map +1 -1
- package/dist/DigNetwork/DigNetwork.js +85 -37
- package/dist/DigNetwork/DigPeer.d.ts +1 -0
- package/dist/DigNetwork/DigPeer.d.ts.map +1 -1
- package/dist/DigNetwork/DigPeer.js +44 -1
- package/dist/DigNetwork/PropagationServer.js +4 -4
- package/dist/blockchain/FullNodePeer.d.ts +1 -0
- package/dist/blockchain/FullNodePeer.d.ts.map +1 -1
- package/dist/blockchain/FullNodePeer.js +20 -6
- package/dist/blockchain/ServerCoin.d.ts.map +1 -1
- package/dist/blockchain/ServerCoin.js +3 -0
- 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/package.json +1 -1
|
@@ -154,7 +154,7 @@ declare class DataIntegrityTree {
|
|
|
154
154
|
* @param expectedRootHash - The expected root hash of the Merkle tree.
|
|
155
155
|
* @returns A boolean indicating if the SHA-256 is present in the foreign tree and the root hash matches.
|
|
156
156
|
*/
|
|
157
|
-
static validateKeyIntegrityWithForeignTree(
|
|
157
|
+
static validateKeyIntegrityWithForeignTree(hexkey: string, sha256: string, serializedTree: object, expectedRootHash: string, dataDir: string): Promise<boolean>;
|
|
158
158
|
}
|
|
159
159
|
export { DataIntegrityTree };
|
|
160
160
|
//# 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;IAC5D,OAAO,CAAC,IAAI,CAAa;gBAEb,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,wBAA6B;WAiDrD,IAAI,CAChB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,wBAAwB,GAChC,iBAAiB;IAOpB;;;OAGG;IACH,OAAO,CAAC,aAAa;IAQrB;;;OAGG;IACH,OAAO,CAAC,eAAe;IAUvB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAa1B;;;;OAIG;YACW,WAAW;IAazB;;;;;OAKG;IACG,SAAS,CAAC,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqFjE;;;OAGG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAW5B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,GAAE,MAAM,GAAG,IAAW,GAAG,MAAM,EAAE;IAalD;;;OAGG;IACH,OAAO,CAAC,YAAY;IAOpB;;;;OAIG;IACH,OAAO,IAAI,MAAM;IAIjB;;;;OAIG;IACH,SAAS,CAAC,QAAQ,GAAE,MAAM,GAAG,IAAW,GAAG,MAAM;IAqBjD;;;;OAIG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU;IAwB7C,OAAO,CAAC,wBAAwB;IAuBhC;;OAEG;IACH,MAAM,IAAI,MAAM,GAAG,SAAS;IAoC5B;;OAEG;IACH,gBAAgB,IAAI,IAAI;IAIxB;;;;;OAKG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,GAAG,IAAW,GAAG,OAAO;IA+B/D;;;;;OAKG;IACH,cAAc,CACZ,MAAM,EAAE,MAAM,EACd,QAAQ,GAAE,MAAM,GAAG,IAAW,EAC9B,UAAU,GAAE,MAAM,GAAG,IAAW,EAChC,MAAM,GAAE,MAAM,GAAG,IAAW,GAC3B,QAAQ;IA4DX;;OAEG;IACH,eAAe,IAAI,IAAI;IAKvB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAUhE;;;;;;OAMG;IACH,QAAQ,CACN,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,QAAQ,GAAE,MAAM,GAAG,IAAW,GAC7B,MAAM;IAsCT;;;;;OAKG;IACH,WAAW,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO;IA4B5D;;;;;OAKG;IACH,WAAW,CACT,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB;QAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE;IA+B/D;;;;;OAKG;IACG,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA0D5E;;;;;;;;;OASG;
|
|
1
|
+
{"version":3,"file":"DataIntegrityTree.d.ts","sourceRoot":"","sources":["../../src/DataIntegrityTree/DataIntegrityTree.ts"],"names":[],"mappings":"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"}
|
|
@@ -634,8 +634,8 @@ class DataIntegrityTree {
|
|
|
634
634
|
* @param expectedRootHash - The expected root hash of the Merkle tree.
|
|
635
635
|
* @returns A boolean indicating if the SHA-256 is present in the foreign tree and the root hash matches.
|
|
636
636
|
*/
|
|
637
|
-
static validateKeyIntegrityWithForeignTree(
|
|
638
|
-
if (!isHexString(
|
|
637
|
+
static async validateKeyIntegrityWithForeignTree(hexkey, sha256, serializedTree, expectedRootHash, dataDir) {
|
|
638
|
+
if (!isHexString(hexkey)) {
|
|
639
639
|
throw new Error("key must be a valid hex string");
|
|
640
640
|
}
|
|
641
641
|
if (!isHexString(sha256)) {
|
|
@@ -644,29 +644,59 @@ class DataIntegrityTree {
|
|
|
644
644
|
if (!isHexString(expectedRootHash)) {
|
|
645
645
|
throw new Error("expectedRootHash must be a valid hex string");
|
|
646
646
|
}
|
|
647
|
-
//
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
if (treeRootHash !== expectedRootHash) {
|
|
653
|
-
console.warn(`Expected root hash ${expectedRootHash}, but got ${treeRootHash}`);
|
|
654
|
-
return false;
|
|
647
|
+
// File path based on sha256
|
|
648
|
+
const filePath = path.join(dataDir, sha256.match(/.{1,2}/g).join("/"));
|
|
649
|
+
// Check if the file exists
|
|
650
|
+
if (!fs.existsSync(filePath)) {
|
|
651
|
+
throw new Error(`File at path ${filePath} does not exist`);
|
|
655
652
|
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
.
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
653
|
+
const compressedReadStream = fs.createReadStream(filePath);
|
|
654
|
+
const decompressStream = zlib.createGunzip();
|
|
655
|
+
const hash = crypto.createHash("sha256");
|
|
656
|
+
// Process file decompression and hash comparison
|
|
657
|
+
return new Promise((resolve, reject) => {
|
|
658
|
+
compressedReadStream.pipe(decompressStream);
|
|
659
|
+
decompressStream.on("data", (chunk) => {
|
|
660
|
+
hash.update(chunk);
|
|
661
|
+
});
|
|
662
|
+
decompressStream.on("end", () => {
|
|
663
|
+
const uncompressedSha256 = hash.digest("hex");
|
|
664
|
+
console.log(`SHA-256 of uncompressed file: ${uncompressedSha256}`);
|
|
665
|
+
if (uncompressedSha256 !== sha256) {
|
|
666
|
+
console.warn(`File hash mismatch. Expected: ${sha256}, got: ${uncompressedSha256}`);
|
|
667
|
+
return resolve(false);
|
|
668
|
+
}
|
|
669
|
+
// Deserialize the foreign tree
|
|
670
|
+
const leaves = serializedTree.leaves.map((leaf) => Buffer.from(leaf, "hex"));
|
|
671
|
+
const tree = new merkletreejs_1.MerkleTree(leaves, crypto_js_1.SHA256, { sortPairs: true });
|
|
672
|
+
// Verify that the deserialized tree's root matches the expected root hash
|
|
673
|
+
const treeRootHash = tree.getRoot().toString("hex");
|
|
674
|
+
if (treeRootHash !== expectedRootHash) {
|
|
675
|
+
console.warn(`Expected root hash ${expectedRootHash}, but got ${treeRootHash}`);
|
|
676
|
+
return resolve(false);
|
|
677
|
+
}
|
|
678
|
+
// Rebuild the files map from the serialized tree
|
|
679
|
+
// @ts-ignore
|
|
680
|
+
tree.files = new Map(Object.entries(serializedTree.files).map(([key, value]) => [
|
|
681
|
+
key,
|
|
682
|
+
{ hash: value.hash, sha256: value.sha256 },
|
|
683
|
+
]));
|
|
684
|
+
// Check if the SHA-256 exists in the foreign tree's files
|
|
685
|
+
const combinedHash = crypto
|
|
686
|
+
.createHash("sha256")
|
|
687
|
+
.update(`${hexkey}/${sha256}`)
|
|
688
|
+
.digest("hex");
|
|
689
|
+
const leaf = Buffer.from(combinedHash, "hex");
|
|
690
|
+
const isInTree = tree.getLeafIndex(leaf) !== -1;
|
|
691
|
+
resolve(isInTree);
|
|
692
|
+
});
|
|
693
|
+
decompressStream.on("error", (err) => {
|
|
694
|
+
reject(err);
|
|
695
|
+
});
|
|
696
|
+
compressedReadStream.on("error", (err) => {
|
|
697
|
+
reject(err);
|
|
698
|
+
});
|
|
699
|
+
});
|
|
670
700
|
}
|
|
671
701
|
}
|
|
672
702
|
exports.DataIntegrityTree = DataIntegrityTree;
|
|
@@ -21,7 +21,7 @@ class ContentServer {
|
|
|
21
21
|
// Method to get the content of a specified key from the peer, with optional challenge query
|
|
22
22
|
async getKey(key, rootHash, challengeHex) {
|
|
23
23
|
// Construct the base URL
|
|
24
|
-
let url = `
|
|
24
|
+
let url = `https://${this.ipAddress}:${ContentServer.port}/chia.${this.storeId}.${rootHash}/${key}`;
|
|
25
25
|
// If a challenge is provided, append it as a query parameter
|
|
26
26
|
if (challengeHex) {
|
|
27
27
|
url += `?challenge=${challengeHex}`;
|
|
@@ -42,32 +42,32 @@ class ContentServer {
|
|
|
42
42
|
}
|
|
43
43
|
// Method to get the .well-known information
|
|
44
44
|
async getWellKnown() {
|
|
45
|
-
const url = `
|
|
45
|
+
const url = `https://${this.ipAddress}:${ContentServer.port}/.well-known`;
|
|
46
46
|
return this.fetchJson(url);
|
|
47
47
|
}
|
|
48
48
|
// Method to get the list of known stores
|
|
49
49
|
async getKnownStores() {
|
|
50
|
-
const url = `
|
|
50
|
+
const url = `https://${this.ipAddress}:${ContentServer.port}/.well-known/stores`;
|
|
51
51
|
return this.fetchJson(url);
|
|
52
52
|
}
|
|
53
53
|
// Method to get the index of all stores
|
|
54
54
|
async getStoresIndex() {
|
|
55
|
-
const url = `
|
|
55
|
+
const url = `https://${this.ipAddress}:${ContentServer.port}/`;
|
|
56
56
|
return this.fetchJson(url);
|
|
57
57
|
}
|
|
58
58
|
// Method to get the index of keys in a store
|
|
59
59
|
async getKeysIndex() {
|
|
60
|
-
const url = `
|
|
60
|
+
const url = `https://${this.ipAddress}:${ContentServer.port}/${this.storeId}`;
|
|
61
61
|
return this.fetchJson(url);
|
|
62
62
|
}
|
|
63
63
|
// Method to check if a specific key exists (HEAD request)
|
|
64
64
|
async headKey(key) {
|
|
65
|
-
const url = `
|
|
65
|
+
const url = `https://${this.ipAddress}:${ContentServer.port}/${this.storeId}/${key}`;
|
|
66
66
|
return this.head(url);
|
|
67
67
|
}
|
|
68
68
|
// Method to check if a specific store exists (HEAD request)
|
|
69
69
|
async headStore(options) {
|
|
70
|
-
let url = `
|
|
70
|
+
let url = `https://${this.ipAddress}:${ContentServer.port}/${this.storeId}`;
|
|
71
71
|
if (options?.hasRootHash) {
|
|
72
72
|
url += `?hasRootHash=${options.hasRootHash}`;
|
|
73
73
|
}
|
|
@@ -75,7 +75,7 @@ class ContentServer {
|
|
|
75
75
|
}
|
|
76
76
|
streamKey(key) {
|
|
77
77
|
return new Promise((resolve, reject) => {
|
|
78
|
-
const url = `
|
|
78
|
+
const url = `https://${this.ipAddress}:${ContentServer.port}/${this.storeId}/${key}`;
|
|
79
79
|
const urlObj = new url_1.URL(url);
|
|
80
80
|
const requestOptions = {
|
|
81
81
|
hostname: urlObj.hostname,
|
|
@@ -102,7 +102,7 @@ class ContentServer {
|
|
|
102
102
|
}
|
|
103
103
|
});
|
|
104
104
|
request.on("error", (error) => {
|
|
105
|
-
console.error(`Request error for ${url}:`, error);
|
|
105
|
+
console.error(`GET Request error for ${url}:`, error);
|
|
106
106
|
reject(error);
|
|
107
107
|
});
|
|
108
108
|
request.end();
|
|
@@ -161,7 +161,7 @@ class ContentServer {
|
|
|
161
161
|
}
|
|
162
162
|
});
|
|
163
163
|
request.on("error", (error) => {
|
|
164
|
-
console.error(`
|
|
164
|
+
console.error(`HEAD ${url}:`, error.message);
|
|
165
165
|
reject({ success: false });
|
|
166
166
|
});
|
|
167
167
|
request.end();
|
|
@@ -247,7 +247,7 @@ class ContentServer {
|
|
|
247
247
|
}
|
|
248
248
|
});
|
|
249
249
|
request.on("error", (error) => {
|
|
250
|
-
console.error(`
|
|
250
|
+
console.error(`GET ${url}:`, error.message);
|
|
251
251
|
reject(error);
|
|
252
252
|
});
|
|
253
253
|
request.end();
|
|
@@ -9,7 +9,7 @@ export declare class DigNetwork {
|
|
|
9
9
|
uploadStoreHead(digPeer: DigPeer): Promise<void>;
|
|
10
10
|
uploadStore(digPeer: DigPeer): Promise<void>;
|
|
11
11
|
static subscribeToStore(storeId: string): Promise<void>;
|
|
12
|
-
static findPeerWithStoreKey(storeId: string, rootHash: string, key?: string, intialBlackList?: string[]): Promise<
|
|
12
|
+
static findPeerWithStoreKey(storeId: string, rootHash: string, key?: string, intialBlackList?: string[]): Promise<DigPeer | null>;
|
|
13
13
|
static unsubscribeFromStore(storeId: string): void;
|
|
14
14
|
downloadFiles(forceDownload?: boolean, renderProgressBar?: boolean, skipData?: boolean): Promise<void>;
|
|
15
15
|
private fetchAvailablePeers;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DigNetwork.d.ts","sourceRoot":"","sources":["../../src/DigNetwork/DigNetwork.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"DigNetwork.d.ts","sourceRoot":"","sources":["../../src/DigNetwork/DigNetwork.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAYpC,qBAAa,UAAU;IACrB,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAA2B;gBAEpC,OAAO,EAAE,MAAM;YAOb,eAAe;IA8ChB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAsDhD,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;WA6BrC,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;WAQhD,oBAAoB,CACtC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,GAAG,CAAC,EAAE,MAAM,EACZ,eAAe,GAAE,MAAM,EAAO,GAC7B,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;WAyDZ,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAQ5C,aAAa,CACxB,aAAa,GAAE,OAAe,EAC9B,iBAAiB,GAAE,OAAc,EACjC,QAAQ,GAAE,OAAe,GACxB,OAAO,CAAC,IAAI,CAAC;YAkJF,mBAAmB;YAWnB,kBAAkB;YASlB,oBAAoB;YASpB,qBAAqB;YAsFrB,cAAc;CAoB7B"}
|
|
@@ -33,6 +33,7 @@ const hashUtils_1 = require("../utils/hashUtils");
|
|
|
33
33
|
const blockchain_1 = require("../blockchain");
|
|
34
34
|
const config_1 = require("../utils/config");
|
|
35
35
|
const util_1 = require("util");
|
|
36
|
+
const DataIntegrityTree_1 = require("../DataIntegrityTree");
|
|
36
37
|
const rename = (0, util_1.promisify)(fs.rename);
|
|
37
38
|
const unlink = (0, util_1.promisify)(fs.unlink);
|
|
38
39
|
class DigNetwork {
|
|
@@ -128,31 +129,47 @@ class DigNetwork {
|
|
|
128
129
|
static async findPeerWithStoreKey(storeId, rootHash, key, intialBlackList = []) {
|
|
129
130
|
const peerBlackList = intialBlackList;
|
|
130
131
|
const serverCoin = new blockchain_1.ServerCoin(storeId);
|
|
132
|
+
let peerIp = null;
|
|
133
|
+
// Keep sampling peers until an empty array is returned
|
|
131
134
|
while (true) {
|
|
132
135
|
try {
|
|
136
|
+
// Sample a peer from the current epoch
|
|
133
137
|
const digPeers = await serverCoin.sampleCurrentEpoch(1, peerBlackList);
|
|
134
|
-
|
|
138
|
+
// If no peers are returned, break out of the loop
|
|
139
|
+
if (digPeers.length === 0) {
|
|
140
|
+
console.log("No more peers found.");
|
|
135
141
|
break;
|
|
136
|
-
|
|
142
|
+
}
|
|
143
|
+
peerIp = digPeers[0];
|
|
137
144
|
const digPeer = new DigPeer_1.DigPeer(peerIp, storeId);
|
|
138
|
-
|
|
145
|
+
// Try to fetch the head store information
|
|
146
|
+
const storeResponse = await digPeer.contentServer.headStore({
|
|
139
147
|
hasRootHash: rootHash,
|
|
140
148
|
});
|
|
141
|
-
|
|
149
|
+
// If the peer has the correct root hash, check if key is required
|
|
142
150
|
if (storeResponse.headers?.["x-has-roothash"] === "true") {
|
|
143
151
|
console.log(`Found Peer at ${peerIp} for storeId: ${storeId}, root hash ${rootHash}`);
|
|
152
|
+
// If no key is provided, return the peer
|
|
144
153
|
if (!key) {
|
|
145
|
-
return
|
|
154
|
+
return digPeer;
|
|
146
155
|
}
|
|
156
|
+
// If key is provided, check if the peer has it
|
|
147
157
|
const keyResponse = await digPeer.contentServer.headKey(key);
|
|
148
158
|
if (keyResponse.headers?.["x-key-exists"] === "true") {
|
|
149
|
-
return
|
|
159
|
+
return digPeer;
|
|
150
160
|
}
|
|
151
161
|
}
|
|
162
|
+
// Add peer to blacklist if it doesn't meet criteria
|
|
152
163
|
peerBlackList.push(peerIp);
|
|
153
164
|
}
|
|
154
|
-
catch {
|
|
165
|
+
catch (error) {
|
|
166
|
+
console.error(`Error connecting to DIG Peer ${peerIp}. Resampling...`);
|
|
167
|
+
if (peerIp) {
|
|
168
|
+
peerBlackList.push(peerIp); // Add to blacklist if error occurs
|
|
169
|
+
}
|
|
170
|
+
}
|
|
155
171
|
}
|
|
172
|
+
// Return null if no valid peer was found
|
|
156
173
|
return null;
|
|
157
174
|
}
|
|
158
175
|
static unsubscribeFromStore(storeId) {
|
|
@@ -163,45 +180,76 @@ class DigNetwork {
|
|
|
163
180
|
}
|
|
164
181
|
// Downloads files from the network based on the manifest
|
|
165
182
|
async downloadFiles(forceDownload = false, renderProgressBar = true, skipData = false) {
|
|
183
|
+
console.log("Starting file download process...");
|
|
184
|
+
let peerBlackList = [];
|
|
185
|
+
let selectedPeer = null;
|
|
166
186
|
try {
|
|
167
187
|
const rootHistory = await this.dataStore.getRootHistory();
|
|
168
188
|
if (!rootHistory.length) {
|
|
169
189
|
throw new Error("No roots found in rootHistory. Cannot proceed with file download.");
|
|
170
190
|
}
|
|
171
191
|
await this.downloadHeightFile(true);
|
|
172
|
-
|
|
192
|
+
// Filter out rootInfo entries where the .dat file already exists
|
|
193
|
+
const rootHistoryFiltered = rootHistory
|
|
173
194
|
.filter((item) => item.timestamp !== undefined)
|
|
174
|
-
.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
195
|
+
.filter((item) => !fs.existsSync(`${this.storeDir}/${item.root_hash}.dat`))
|
|
196
|
+
// Reverse to download the latest first
|
|
197
|
+
.reverse();
|
|
198
|
+
if (!rootHistoryFiltered.length) {
|
|
199
|
+
console.log("All root hashes already exist locally. No need for download.");
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
// Process filtered rootHistory sequentially
|
|
203
|
+
for (const rootInfo of rootHistoryFiltered) {
|
|
204
|
+
while (true) {
|
|
205
|
+
try {
|
|
206
|
+
selectedPeer = await DigNetwork.findPeerWithStoreKey(this.dataStore.StoreId, rootInfo.root_hash, undefined, peerBlackList);
|
|
207
|
+
if (!selectedPeer) {
|
|
208
|
+
console.error(`No peer found with root hash ${rootInfo.root_hash}. Skipping download.`);
|
|
209
|
+
break; // Exit loop if no more peers are found
|
|
210
|
+
}
|
|
211
|
+
const rootResponse = await selectedPeer.propagationServer.getStoreData(`${rootInfo.root_hash}.dat`);
|
|
212
|
+
const root = JSON.parse(rootResponse);
|
|
213
|
+
if (!skipData) {
|
|
214
|
+
// Sequential file download
|
|
215
|
+
for (const [storeKey, file] of Object.entries(root.files)) {
|
|
216
|
+
const filePath = (0, hashUtils_1.getFilePathFromSha256)(file.sha256, `${this.storeDir}/data`);
|
|
217
|
+
console.log(`Downloading file with sha256: ${file.sha256}...`);
|
|
218
|
+
await selectedPeer.downloadData(this.dataStore.StoreId, `data/${file.sha256.match(/.{1,2}/g).join("/")}`);
|
|
219
|
+
const integrityCheck = await DataIntegrityTree_1.DataIntegrityTree.validateKeyIntegrityWithForeignTree(storeKey, file.sha256, root, rootInfo.root_hash, `${this.storeDir}/data`);
|
|
220
|
+
if (integrityCheck) {
|
|
221
|
+
console.log(`\x1b[32mIntegrity check passed for file with sha256: ${file.sha256}.\x1b[0m`);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
console.error(`\x1b[31mIntegrity check failed for file with sha256: ${file.sha256}.\x1b[0m`);
|
|
225
|
+
await unlink(filePath);
|
|
226
|
+
throw new Error(`Store Integrity check failed. Syncing file from another peer.`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
fs.writeFileSync(`${this.storeDir}/${rootInfo.root_hash}.dat`, rootResponse);
|
|
230
|
+
peerBlackList = []; // Clear the blacklist upon successful download
|
|
231
|
+
// Break out of the retry loop if the download succeeds
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
console.error(`Error downloading from peer. Retrying with another peer.`, error);
|
|
236
|
+
if (selectedPeer) {
|
|
237
|
+
peerBlackList.push(selectedPeer.IpAddress); // Add peer to blacklist and try again
|
|
197
238
|
}
|
|
198
239
|
}
|
|
199
240
|
}
|
|
241
|
+
// Only process the first root hash so other stores can sync the latest.
|
|
242
|
+
// This has an effect where the latest roothash will always be synced first, even if new ones come in.
|
|
243
|
+
// Then it will backfill historical roothashes
|
|
244
|
+
break;
|
|
200
245
|
}
|
|
201
246
|
await this.downloadManifestFile(true);
|
|
202
247
|
console.log("Syncing store complete.");
|
|
203
248
|
}
|
|
204
249
|
catch (error) {
|
|
250
|
+
if (selectedPeer) {
|
|
251
|
+
peerBlackList.push(selectedPeer.IpAddress);
|
|
252
|
+
}
|
|
205
253
|
console.trace(error);
|
|
206
254
|
throw error;
|
|
207
255
|
}
|
|
@@ -229,13 +277,13 @@ class DigNetwork {
|
|
|
229
277
|
return;
|
|
230
278
|
const blacklist = this.peerBlacklist.get(dataPath) || new Set();
|
|
231
279
|
for (const digPeer of digPeers) {
|
|
232
|
-
if (blacklist.has(digPeer.IpAddress))
|
|
233
|
-
continue;
|
|
234
|
-
const response = await digPeer.propagationServer.headStore();
|
|
235
|
-
if (!response.success) {
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
280
|
try {
|
|
281
|
+
if (blacklist.has(digPeer.IpAddress))
|
|
282
|
+
continue;
|
|
283
|
+
const response = await digPeer.propagationServer.headStore();
|
|
284
|
+
if (!response.success) {
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
239
287
|
// Create directory if it doesn't exist
|
|
240
288
|
const directory = path.dirname(tempFilePath);
|
|
241
289
|
if (!fs.existsSync(directory)) {
|
|
@@ -19,5 +19,6 @@ export declare class DigPeer {
|
|
|
19
19
|
static sendBulkPayments(walletName: string, outputs: Output[]): Promise<void>;
|
|
20
20
|
sendPayment(walletName: string, amount: bigint, memos?: Buffer[]): Promise<void>;
|
|
21
21
|
static createPaymentHint(storeId: Buffer): Buffer;
|
|
22
|
+
downloadData(storeId: string, dataPath: string): Promise<void>;
|
|
22
23
|
}
|
|
23
24
|
//# sourceMappingURL=DigPeer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DigPeer.d.ts","sourceRoot":"","sources":["../../src/DigNetwork/DigPeer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"DigPeer.d.ts","sourceRoot":"","sources":["../../src/DigNetwork/DigPeer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAKpD,OAAO,EAKL,MAAM,EACP,MAAM,8BAA8B,CAAC;AAUtC,qBAAa,OAAO;IAClB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,kBAAkB,CAAoB;IAC9C,OAAO,CAAC,gBAAgB,CAAkB;gBAE9B,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IAS9C,IAAW,aAAa,IAAI,aAAa,CAExC;IAGD,IAAW,iBAAiB,IAAI,iBAAiB,CAEhD;IAGD,IAAW,eAAe,IAAI,eAAe,CAE5C;IAED,IAAW,SAAS,IAAI,MAAM,CAE7B;IAEY,aAAa,CACxB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EAAE,GACb,OAAO,CAAC,OAAO,CAAC;IAiHN,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC;WA2B3B,qBAAqB,CACjC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EAAE,EACnB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAAC,IAAI,CAAC;WAuBI,gBAAgB,CAClC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC;IAwCH,WAAW,CACtB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAAM,EAAO,GACnB,OAAO,CAAC,IAAI,CAAC;WAYF,iBAAiB,CAAC,OAAO,EAAE,MAAM;IAqBlC,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CA4C5E"}
|
|
@@ -4,16 +4,23 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.DigPeer = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
7
8
|
const crypto_1 = __importDefault(require("crypto"));
|
|
8
9
|
const ContentServer_1 = require("./ContentServer");
|
|
9
10
|
const PropagationServer_1 = require("./PropagationServer");
|
|
10
11
|
const IncentiveServer_1 = require("./IncentiveServer");
|
|
11
12
|
const blockchain_1 = require("../blockchain");
|
|
12
13
|
const DataIntegrityTree_1 = require("../DataIntegrityTree");
|
|
14
|
+
const config_1 = require("../utils/config");
|
|
15
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
16
|
const datalayer_driver_1 = require("@dignetwork/datalayer-driver");
|
|
14
17
|
const blockchain_2 = require("../blockchain");
|
|
15
18
|
const blockchain_3 = require("../blockchain");
|
|
16
19
|
const coins_1 = require("../blockchain/coins");
|
|
20
|
+
const config_2 = require("../utils/config");
|
|
21
|
+
const util_1 = require("util");
|
|
22
|
+
const rename = (0, util_1.promisify)(fs_1.default.rename);
|
|
23
|
+
const unlink = (0, util_1.promisify)(fs_1.default.unlink);
|
|
17
24
|
class DigPeer {
|
|
18
25
|
constructor(ipAddress, storeId) {
|
|
19
26
|
this.ipAddress = ipAddress;
|
|
@@ -97,7 +104,7 @@ class DigPeer {
|
|
|
97
104
|
});
|
|
98
105
|
});
|
|
99
106
|
// Perform tree integrity validation using the datFileContent and the root hash
|
|
100
|
-
const treeCheck = DataIntegrityTree_1.DataIntegrityTree.validateKeyIntegrityWithForeignTree(key, fileData.sha256, datFileContent, rootHash);
|
|
107
|
+
const treeCheck = DataIntegrityTree_1.DataIntegrityTree.validateKeyIntegrityWithForeignTree(key, fileData.sha256, datFileContent, rootHash, path_1.default.resolve(config_1.DIG_FOLDER_PATH, "stores", this.storeId, 'data'));
|
|
101
108
|
if (!treeCheck) {
|
|
102
109
|
console.error(`Tree validation failed for file ${key}.`);
|
|
103
110
|
filesIntegrityIntact = false;
|
|
@@ -195,5 +202,41 @@ class DigPeer {
|
|
|
195
202
|
// Return the 32-byte hash as a hex string
|
|
196
203
|
return transformedBuffer;
|
|
197
204
|
}
|
|
205
|
+
async downloadData(storeId, dataPath) {
|
|
206
|
+
const filePath = path_1.default.join(config_2.STORE_PATH, storeId, dataPath);
|
|
207
|
+
const tempFilePath = `${filePath}.tmp`;
|
|
208
|
+
try {
|
|
209
|
+
const headStoreResponse = await this.propagationServer.headStore();
|
|
210
|
+
if (!headStoreResponse.success) {
|
|
211
|
+
throw new Error("Data not accessible from store.");
|
|
212
|
+
}
|
|
213
|
+
const directory = path_1.default.dirname(tempFilePath);
|
|
214
|
+
if (!fs_1.default.existsSync(directory)) {
|
|
215
|
+
fs_1.default.mkdirSync(directory, { recursive: true });
|
|
216
|
+
}
|
|
217
|
+
const fileStream = fs_1.default.createWriteStream(tempFilePath);
|
|
218
|
+
const dataStream = await this.propagationServer.streamStoreData(dataPath);
|
|
219
|
+
await new Promise((resolve, reject) => {
|
|
220
|
+
dataStream.pipe(fileStream);
|
|
221
|
+
dataStream.on("end", resolve);
|
|
222
|
+
dataStream.on("error", reject);
|
|
223
|
+
fileStream.on("error", reject);
|
|
224
|
+
});
|
|
225
|
+
await rename(tempFilePath, filePath);
|
|
226
|
+
console.log(`Downloaded ${dataPath} from ${this.IpAddress}`);
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
console.error(`Failed to download data: ${error.message}`);
|
|
230
|
+
if (fs_1.default.existsSync(tempFilePath)) {
|
|
231
|
+
await unlink(tempFilePath);
|
|
232
|
+
}
|
|
233
|
+
// Check if directory is empty and remove it if it is
|
|
234
|
+
const directory = path_1.default.dirname(tempFilePath);
|
|
235
|
+
if (fs_1.default.existsSync(directory) && fs_1.default.readdirSync(directory).length === 0) {
|
|
236
|
+
fs_1.default.rmdirSync(directory);
|
|
237
|
+
console.log(`Removed empty directory: ${directory}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
198
241
|
}
|
|
199
242
|
exports.DigPeer = DigPeer;
|
|
@@ -271,7 +271,7 @@ class PropagationServer {
|
|
|
271
271
|
}
|
|
272
272
|
});
|
|
273
273
|
request.on("error", (error) => {
|
|
274
|
-
console.error(`Request error for ${url}:`, error);
|
|
274
|
+
console.error(`GET Request error for ${url}:`, error);
|
|
275
275
|
reject(error);
|
|
276
276
|
});
|
|
277
277
|
request.end();
|
|
@@ -326,13 +326,13 @@ class PropagationServer {
|
|
|
326
326
|
}
|
|
327
327
|
});
|
|
328
328
|
request.on("error", (error) => {
|
|
329
|
-
console.error(`
|
|
329
|
+
console.error(`HEAD ${url}:`, error.message);
|
|
330
330
|
reject({ success: false });
|
|
331
331
|
});
|
|
332
332
|
request.end();
|
|
333
333
|
}
|
|
334
334
|
catch (err) {
|
|
335
|
-
console.error(`Invalid URL: ${url}`, err);
|
|
335
|
+
console.error(`Invalid URL: ${url}`, err.message);
|
|
336
336
|
reject({ success: false, message: "Invalid URL" });
|
|
337
337
|
}
|
|
338
338
|
});
|
|
@@ -432,7 +432,7 @@ class PropagationServer {
|
|
|
432
432
|
}
|
|
433
433
|
});
|
|
434
434
|
request.on("error", (error) => {
|
|
435
|
-
console.error(`
|
|
435
|
+
console.error(`GET ${url}:`, error.message);
|
|
436
436
|
reject(error);
|
|
437
437
|
});
|
|
438
438
|
request.end();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FullNodePeer.d.ts","sourceRoot":"","sources":["../../src/blockchain/FullNodePeer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"FullNodePeer.d.ts","sourceRoot":"","sources":["../../src/blockchain/FullNodePeer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAE,MAAM,8BAA8B,CAAC;AAoBpD,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,UAAU,CAAkD;IAC3E,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAA0B;IAChE,OAAO,CAAC,IAAI,CAAO;IACnB,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAA0B;IAMzD,OAAO;WAIa,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5C,OAAO,CAAC,MAAM,CAAC,eAAe;IAiB9B,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAM/B;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;mBASZ,eAAe;mBA2Df,UAAU;IA2B/B,OAAO,CAAC,MAAM,CAAC,eAAe;mBAkDT,WAAW;IA+FzB,OAAO,IAAI,IAAI;WAIF,mBAAmB,CACrC,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,OAAO,CAAC;CAwBpB"}
|
|
@@ -16,6 +16,7 @@ const nanospinner_1 = require("nanospinner");
|
|
|
16
16
|
const config_1 = require("../utils/config");
|
|
17
17
|
const FULLNODE_PORT = 8444;
|
|
18
18
|
const LOCALHOST = "127.0.0.1";
|
|
19
|
+
const CHIA_NODES_HOST = "chia-nodes";
|
|
19
20
|
const DNS_HOSTS = [
|
|
20
21
|
"dns-introducer.chia.net",
|
|
21
22
|
"chia.ctrlaltdel.ch",
|
|
@@ -64,15 +65,22 @@ class FullNodePeer {
|
|
|
64
65
|
static async fetchNewPeerIPs() {
|
|
65
66
|
const trustedNodeIp = FullNodePeer.getTrustedFullNode();
|
|
66
67
|
const priorityIps = [];
|
|
67
|
-
// Prioritize trustedNodeIp
|
|
68
|
+
// Prioritize trustedNodeIp unless it's deprioritized
|
|
68
69
|
if (trustedNodeIp &&
|
|
70
|
+
!FullNodePeer.deprioritizedIps.has(trustedNodeIp) &&
|
|
69
71
|
(await FullNodePeer.isPortReachable(trustedNodeIp, FULLNODE_PORT))) {
|
|
70
72
|
priorityIps.push(trustedNodeIp);
|
|
71
73
|
}
|
|
72
|
-
// Prioritize LOCALHOST
|
|
73
|
-
if (
|
|
74
|
+
// Prioritize LOCALHOST unless it's deprioritized
|
|
75
|
+
if (!FullNodePeer.deprioritizedIps.has(LOCALHOST) &&
|
|
76
|
+
(await FullNodePeer.isPortReachable(LOCALHOST, FULLNODE_PORT))) {
|
|
74
77
|
priorityIps.push(LOCALHOST);
|
|
75
78
|
}
|
|
79
|
+
// Prioritize CHIA_NODES_HOST unless it's deprioritized
|
|
80
|
+
if (!FullNodePeer.deprioritizedIps.has(CHIA_NODES_HOST) &&
|
|
81
|
+
(await FullNodePeer.isPortReachable(CHIA_NODES_HOST, FULLNODE_PORT))) {
|
|
82
|
+
priorityIps.push(CHIA_NODES_HOST);
|
|
83
|
+
}
|
|
76
84
|
if (priorityIps.length > 0) {
|
|
77
85
|
return priorityIps;
|
|
78
86
|
}
|
|
@@ -114,6 +122,8 @@ class FullNodePeer {
|
|
|
114
122
|
}
|
|
115
123
|
// @ts-ignore
|
|
116
124
|
if (FullNodePeer.memoizedFetchNewPeerIPs?.cache?.clear) {
|
|
125
|
+
// Clear cache and reset deprioritized IPs when cache is cleared
|
|
126
|
+
FullNodePeer.deprioritizedIps.clear();
|
|
117
127
|
// @ts-ignore
|
|
118
128
|
FullNodePeer.memoizedFetchNewPeerIPs.cache.clear();
|
|
119
129
|
}
|
|
@@ -151,6 +161,8 @@ class FullNodePeer {
|
|
|
151
161
|
FullNodePeer.cachedPeer = null;
|
|
152
162
|
// @ts-ignore
|
|
153
163
|
FullNodePeer.memoizedFetchNewPeerIPs.cache.clear();
|
|
164
|
+
FullNodePeer.deprioritizedIps.clear();
|
|
165
|
+
console.info(`Fullnode Peer error, reconnecting to a new peer...`);
|
|
154
166
|
const newPeer = await FullNodePeer.getBestPeer();
|
|
155
167
|
return newPeer[prop](...args);
|
|
156
168
|
}
|
|
@@ -206,10 +218,11 @@ class FullNodePeer {
|
|
|
206
218
|
throw new Error("No valid peak heights obtained from any peer.");
|
|
207
219
|
}
|
|
208
220
|
const highestPeak = Math.max(...validHeights);
|
|
209
|
-
// Prioritize LOCALHOST and
|
|
221
|
+
// Prioritize LOCALHOST, TRUSTED_NODE_IP, and CHIA_NODES_HOST if they have the highest peak height
|
|
210
222
|
let bestPeerIndex = validHeights.findIndex((height, index) => height === highestPeak &&
|
|
211
|
-
(peerIPs[index]
|
|
212
|
-
|
|
223
|
+
!FullNodePeer.deprioritizedIps.has(peerIPs[index]) && // Exclude deprioritized IPs
|
|
224
|
+
(peerIPs[index] === LOCALHOST || peerIPs[index] === trustedNodeIp || peerIPs[index] === CHIA_NODES_HOST));
|
|
225
|
+
// If LOCALHOST, TRUSTED_NODE_IP, or CHIA_NODES_HOST don't have the highest peak, select any peer with the highest peak
|
|
213
226
|
if (bestPeerIndex === -1) {
|
|
214
227
|
bestPeerIndex = validHeights.findIndex((height) => height === highestPeak);
|
|
215
228
|
}
|
|
@@ -242,6 +255,7 @@ class FullNodePeer {
|
|
|
242
255
|
}
|
|
243
256
|
exports.FullNodePeer = FullNodePeer;
|
|
244
257
|
FullNodePeer.cachedPeer = null;
|
|
258
|
+
FullNodePeer.deprioritizedIps = new Set(); // New set for deprioritized IPs
|
|
245
259
|
(() => {
|
|
246
260
|
FullNodePeer.memoizedFetchNewPeerIPs = (0, lodash_1.memoize)(FullNodePeer.fetchNewPeerIPs);
|
|
247
261
|
})();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ServerCoin.d.ts","sourceRoot":"","sources":["../../src/blockchain/ServerCoin.ts"],"names":[],"mappings":"AACA,OAAO,EAML,UAAU,IAAI,gBAAgB,EAE/B,MAAM,8BAA8B,CAAC;AAItC,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAMrD,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAS;IACxB,gBAAuB,iBAAiB,eAEtC;gBAEU,OAAO,EAAE,MAAM;IAKd,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA2DzD,kBAAkB,CAC7B,UAAU,EAAE,gBAAgB,EAC5B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC;IAqBH,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAoDjD,oBAAoB;IAerB,gBAAgB,CAC3B,KAAK,EAAE,MAAM,EACb,SAAS,GAAE,MAAM,EAAO,GACvB,OAAO,CAAC,MAAM,EAAE,CAAC;IAoCP,mBAAmB,CAC9B,SAAS,GAAE,MAAM,EAAO,GACvB,OAAO,CAAC,MAAM,EAAE,CAAC;IAMP,kBAAkB,CAC7B,UAAU,GAAE,MAAU,EACtB,SAAS,GAAE,MAAM,EAAO,GACvB,OAAO,CAAC,MAAM,EAAE,CAAC;IAMP,wBAAwB,CACnC,KAAK,EAAE,MAAM,EACb,UAAU,GAAE,MAAU,EACtB,SAAS,GAAE,MAAM,EAAO,GACvB,OAAO,CAAC,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"ServerCoin.d.ts","sourceRoot":"","sources":["../../src/blockchain/ServerCoin.ts"],"names":[],"mappings":"AACA,OAAO,EAML,UAAU,IAAI,gBAAgB,EAE/B,MAAM,8BAA8B,CAAC;AAItC,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAMrD,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAS;IACxB,gBAAuB,iBAAiB,eAEtC;gBAEU,OAAO,EAAE,MAAM;IAKd,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA2DzD,kBAAkB,CAC7B,UAAU,EAAE,gBAAgB,EAC5B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC;IAqBH,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAoDjD,oBAAoB;IAerB,gBAAgB,CAC3B,KAAK,EAAE,MAAM,EACb,SAAS,GAAE,MAAM,EAAO,GACvB,OAAO,CAAC,MAAM,EAAE,CAAC;IAoCP,mBAAmB,CAC9B,SAAS,GAAE,MAAM,EAAO,GACvB,OAAO,CAAC,MAAM,EAAE,CAAC;IAMP,kBAAkB,CAC7B,UAAU,GAAE,MAAU,EACtB,SAAS,GAAE,MAAM,EAAO,GACvB,OAAO,CAAC,MAAM,EAAE,CAAC;IAMP,wBAAwB,CACnC,KAAK,EAAE,MAAM,EACb,UAAU,GAAE,MAAU,EACtB,SAAS,GAAE,MAAM,EAAO,GACvB,OAAO,CAAC,MAAM,EAAE,CAAC;WASN,eAAe,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;IAKpD,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiDrD,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;WA+B1C,uBAAuB;YAkE7B,sBAAsB;IAWvB,uBAAuB,CAClC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC;WAuBL,sBAAsB,CAAC,mBAAmB,EAAE,IAAI,GAAG;QAC/D,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;KACf;CA2BF"}
|
|
@@ -126,6 +126,9 @@ class ServerCoin {
|
|
|
126
126
|
// Sample server coins by epoch
|
|
127
127
|
async sampleServerCoinsByEpoch(epoch, sampleSize = 5, blacklist = []) {
|
|
128
128
|
const serverCoinPeers = await this.getAllEpochPeers(epoch, blacklist);
|
|
129
|
+
if (process.env.DIG_DEBUG === "1") {
|
|
130
|
+
console.log("Server Coin Peers: ", serverCoinPeers);
|
|
131
|
+
}
|
|
129
132
|
return lodash_1.default.sampleSize(serverCoinPeers, sampleSize);
|
|
130
133
|
}
|
|
131
134
|
// Get the current epoch based on the current timestamp
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Readable } from "stream";
|
|
2
|
+
export declare class FileTransfer {
|
|
3
|
+
private static certPath;
|
|
4
|
+
private static keyPath;
|
|
5
|
+
constructor();
|
|
6
|
+
/** ------------------------ UPLOAD FUNCTIONALITY ------------------------ **/
|
|
7
|
+
/**
|
|
8
|
+
* Upload a file to the server using a stream.
|
|
9
|
+
* @param filePath - Local path of the file.
|
|
10
|
+
* @param uploadUrl - Server URL to upload the file.
|
|
11
|
+
* @param headers - Additional headers for the request.
|
|
12
|
+
* @returns Promise<void>
|
|
13
|
+
*/
|
|
14
|
+
uploadFile(filePath: string, uploadUrl: string, headers: Record<string, string>): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Perform a POST request.
|
|
17
|
+
* @param url - The URL to POST to.
|
|
18
|
+
* @param data - Data to send.
|
|
19
|
+
* @param headers - Headers for the POST request.
|
|
20
|
+
* @returns Promise<void>
|
|
21
|
+
*/
|
|
22
|
+
postRequest(url: string, data: string, headers: Record<string, string>): Promise<void>;
|
|
23
|
+
/** ------------------------ DOWNLOAD FUNCTIONALITY ------------------------ **/
|
|
24
|
+
/**
|
|
25
|
+
* Download data from a URL.
|
|
26
|
+
* @param url - The URL to download data from.
|
|
27
|
+
* @returns Promise<string>
|
|
28
|
+
*/
|
|
29
|
+
downloadData(url: string): Promise<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Stream data from a URL.
|
|
32
|
+
* @param url - The URL to stream data from.
|
|
33
|
+
* @returns Promise<Readable>
|
|
34
|
+
*/
|
|
35
|
+
streamData(url: string): Promise<Readable>;
|
|
36
|
+
/** ------------------------ HEAD REQUEST FUNCTIONALITY ------------------------ **/
|
|
37
|
+
/**
|
|
38
|
+
* Perform a HEAD request to check if a resource exists on the server.
|
|
39
|
+
* @param url - The URL to check.
|
|
40
|
+
* @returns Promise<{ success: boolean; headers?: Record<string, string> }>
|
|
41
|
+
*/
|
|
42
|
+
headRequest(url: string): Promise<{
|
|
43
|
+
success: boolean;
|
|
44
|
+
headers?: Record<string, string>;
|
|
45
|
+
}>;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=FileTransfer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileTransfer.d.ts","sourceRoot":"","sources":["../../src/utils/FileTransfer.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAElC,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAS;IAChC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAS;;IAU/B,8EAA8E;IAE9E;;;;;;OAMG;IACU,UAAU,CACrB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC;IA0ChB;;;;;;OAMG;IACU,WAAW,CACtB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC;IAsChB,gFAAgF;IAEhF;;;;OAIG;IACU,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA0CvD;;;;OAIG;IACU,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAuCvD,oFAAoF;IAEpF;;;;OAIG;IACU,WAAW,CACtB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;CA8BnE"}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.FileTransfer = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const https_1 = __importDefault(require("https"));
|
|
9
|
+
const url_1 = require("url");
|
|
10
|
+
const ssl_1 = require("../utils/ssl");
|
|
11
|
+
class FileTransfer {
|
|
12
|
+
constructor() {
|
|
13
|
+
if (!FileTransfer.certPath || !FileTransfer.keyPath) {
|
|
14
|
+
const { certPath, keyPath } = (0, ssl_1.getOrCreateSSLCerts)();
|
|
15
|
+
FileTransfer.certPath = certPath;
|
|
16
|
+
FileTransfer.keyPath = keyPath;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** ------------------------ UPLOAD FUNCTIONALITY ------------------------ **/
|
|
20
|
+
/**
|
|
21
|
+
* Upload a file to the server using a stream.
|
|
22
|
+
* @param filePath - Local path of the file.
|
|
23
|
+
* @param uploadUrl - Server URL to upload the file.
|
|
24
|
+
* @param headers - Additional headers for the request.
|
|
25
|
+
* @returns Promise<void>
|
|
26
|
+
*/
|
|
27
|
+
async uploadFile(filePath, uploadUrl, headers) {
|
|
28
|
+
const fileStream = fs_1.default.createReadStream(filePath);
|
|
29
|
+
const fileSize = fs_1.default.statSync(filePath).size;
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const url = new url_1.URL(uploadUrl);
|
|
32
|
+
const options = {
|
|
33
|
+
hostname: url.hostname,
|
|
34
|
+
port: url.port || 443,
|
|
35
|
+
path: url.pathname,
|
|
36
|
+
method: "PUT",
|
|
37
|
+
headers: {
|
|
38
|
+
...headers,
|
|
39
|
+
"Content-Type": "application/octet-stream",
|
|
40
|
+
"Content-Length": fileSize,
|
|
41
|
+
},
|
|
42
|
+
key: fs_1.default.readFileSync(FileTransfer.keyPath),
|
|
43
|
+
cert: fs_1.default.readFileSync(FileTransfer.certPath),
|
|
44
|
+
rejectUnauthorized: false,
|
|
45
|
+
};
|
|
46
|
+
const req = https_1.default.request(options, (res) => {
|
|
47
|
+
if (res.statusCode === 200) {
|
|
48
|
+
resolve();
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
reject(new Error(`Upload failed with status ${res.statusCode}: ${res.statusMessage}`));
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
req.on("error", (err) => reject(err));
|
|
55
|
+
fileStream.pipe(req);
|
|
56
|
+
fileStream.on("error", (err) => reject(err));
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Perform a POST request.
|
|
61
|
+
* @param url - The URL to POST to.
|
|
62
|
+
* @param data - Data to send.
|
|
63
|
+
* @param headers - Headers for the POST request.
|
|
64
|
+
* @returns Promise<void>
|
|
65
|
+
*/
|
|
66
|
+
async postRequest(url, data, headers) {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
const urlObj = new url_1.URL(url);
|
|
69
|
+
const options = {
|
|
70
|
+
hostname: urlObj.hostname,
|
|
71
|
+
port: urlObj.port || 443,
|
|
72
|
+
path: urlObj.pathname,
|
|
73
|
+
method: "POST",
|
|
74
|
+
headers: {
|
|
75
|
+
...headers,
|
|
76
|
+
"Content-Type": "application/json",
|
|
77
|
+
"Content-Length": Buffer.byteLength(data),
|
|
78
|
+
},
|
|
79
|
+
key: fs_1.default.readFileSync(FileTransfer.keyPath),
|
|
80
|
+
cert: fs_1.default.readFileSync(FileTransfer.certPath),
|
|
81
|
+
rejectUnauthorized: false,
|
|
82
|
+
};
|
|
83
|
+
const req = https_1.default.request(options, (res) => {
|
|
84
|
+
if (res.statusCode === 200) {
|
|
85
|
+
resolve();
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
reject(new Error(`POST request failed with status ${res.statusCode}: ${res.statusMessage}`));
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
req.on("error", (err) => reject(err));
|
|
92
|
+
req.write(data);
|
|
93
|
+
req.end();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/** ------------------------ DOWNLOAD FUNCTIONALITY ------------------------ **/
|
|
97
|
+
/**
|
|
98
|
+
* Download data from a URL.
|
|
99
|
+
* @param url - The URL to download data from.
|
|
100
|
+
* @returns Promise<string>
|
|
101
|
+
*/
|
|
102
|
+
async downloadData(url) {
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
const urlObj = new url_1.URL(url);
|
|
105
|
+
const options = {
|
|
106
|
+
hostname: urlObj.hostname,
|
|
107
|
+
port: urlObj.port || 443,
|
|
108
|
+
path: urlObj.pathname + urlObj.search,
|
|
109
|
+
method: "GET",
|
|
110
|
+
key: fs_1.default.readFileSync(FileTransfer.keyPath),
|
|
111
|
+
cert: fs_1.default.readFileSync(FileTransfer.certPath),
|
|
112
|
+
rejectUnauthorized: false,
|
|
113
|
+
};
|
|
114
|
+
const req = https_1.default.request(options, (res) => {
|
|
115
|
+
let data = "";
|
|
116
|
+
if (res.statusCode === 200) {
|
|
117
|
+
res.on("data", (chunk) => (data += chunk));
|
|
118
|
+
res.on("end", () => resolve(data));
|
|
119
|
+
}
|
|
120
|
+
else if (res.statusCode === 301 || res.statusCode === 302) {
|
|
121
|
+
const redirectUrl = res.headers.location;
|
|
122
|
+
if (redirectUrl) {
|
|
123
|
+
this.downloadData(redirectUrl).then(resolve).catch(reject);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
reject(new Error("Redirected without a location header"));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
reject(new Error(`Failed to retrieve data from ${url}. Status code: ${res.statusCode}`));
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
req.on("error", (error) => reject(error));
|
|
134
|
+
req.end();
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Stream data from a URL.
|
|
139
|
+
* @param url - The URL to stream data from.
|
|
140
|
+
* @returns Promise<Readable>
|
|
141
|
+
*/
|
|
142
|
+
async streamData(url) {
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
const urlObj = new url_1.URL(url);
|
|
145
|
+
const options = {
|
|
146
|
+
hostname: urlObj.hostname,
|
|
147
|
+
port: urlObj.port || 443,
|
|
148
|
+
path: urlObj.pathname + urlObj.search,
|
|
149
|
+
method: "GET",
|
|
150
|
+
key: fs_1.default.readFileSync(FileTransfer.keyPath),
|
|
151
|
+
cert: fs_1.default.readFileSync(FileTransfer.certPath),
|
|
152
|
+
rejectUnauthorized: false,
|
|
153
|
+
};
|
|
154
|
+
const req = https_1.default.request(options, (res) => {
|
|
155
|
+
if (res.statusCode === 200) {
|
|
156
|
+
resolve(res); // Return the readable stream
|
|
157
|
+
}
|
|
158
|
+
else if (res.statusCode === 301 || res.statusCode === 302) {
|
|
159
|
+
const redirectUrl = res.headers.location;
|
|
160
|
+
if (redirectUrl) {
|
|
161
|
+
this.streamData(redirectUrl).then(resolve).catch(reject);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
reject(new Error("Redirected without a location header"));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
reject(new Error(`Failed to retrieve stream from ${url}. Status code: ${res.statusCode}`));
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
req.on("error", (error) => reject(error));
|
|
172
|
+
req.end();
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/** ------------------------ HEAD REQUEST FUNCTIONALITY ------------------------ **/
|
|
176
|
+
/**
|
|
177
|
+
* Perform a HEAD request to check if a resource exists on the server.
|
|
178
|
+
* @param url - The URL to check.
|
|
179
|
+
* @returns Promise<{ success: boolean; headers?: Record<string, string> }>
|
|
180
|
+
*/
|
|
181
|
+
async headRequest(url) {
|
|
182
|
+
return new Promise((resolve, reject) => {
|
|
183
|
+
const urlObj = new url_1.URL(url);
|
|
184
|
+
const options = {
|
|
185
|
+
hostname: urlObj.hostname,
|
|
186
|
+
port: urlObj.port || 443,
|
|
187
|
+
path: urlObj.pathname,
|
|
188
|
+
method: "HEAD",
|
|
189
|
+
key: fs_1.default.readFileSync(FileTransfer.keyPath),
|
|
190
|
+
cert: fs_1.default.readFileSync(FileTransfer.certPath),
|
|
191
|
+
rejectUnauthorized: false,
|
|
192
|
+
};
|
|
193
|
+
const req = https_1.default.request(options, (res) => {
|
|
194
|
+
if (res.statusCode === 200) {
|
|
195
|
+
resolve({
|
|
196
|
+
success: true,
|
|
197
|
+
headers: res.headers,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
resolve({ success: false });
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
req.on("error", (err) => reject(err));
|
|
205
|
+
req.end();
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
exports.FileTransfer = FileTransfer;
|