@dignetwork/dig-sdk 0.0.1-alpha.5 → 0.0.1-alpha.52
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 +2 -2
- package/dist/DataIntegrityTree/DataIntegrityTree.d.ts.map +1 -1
- package/dist/DataIntegrityTree/DataIntegrityTree.js +83 -29
- package/dist/DigNetwork/ContentServer.d.ts +6 -1
- package/dist/DigNetwork/ContentServer.d.ts.map +1 -1
- package/dist/DigNetwork/ContentServer.js +40 -16
- package/dist/DigNetwork/DigNetwork.d.ts +2 -5
- package/dist/DigNetwork/DigNetwork.d.ts.map +1 -1
- package/dist/DigNetwork/DigNetwork.js +119 -155
- package/dist/DigNetwork/DigPeer.d.ts +8 -3
- package/dist/DigNetwork/DigPeer.d.ts.map +1 -1
- package/dist/DigNetwork/DigPeer.js +116 -114
- package/dist/DigNetwork/PropagationServer.d.ts +8 -2
- package/dist/DigNetwork/PropagationServer.d.ts.map +1 -1
- package/dist/DigNetwork/PropagationServer.js +110 -75
- package/dist/blockchain/DataStore.d.ts +1 -4
- package/dist/blockchain/DataStore.d.ts.map +1 -1
- package/dist/blockchain/DataStore.js +5 -83
- package/dist/blockchain/DataStoreSerializer.d.ts +1 -1
- package/dist/blockchain/DataStoreSerializer.d.ts.map +1 -1
- package/dist/blockchain/FullNodePeer.d.ts +9 -1
- package/dist/blockchain/FullNodePeer.d.ts.map +1 -1
- package/dist/blockchain/FullNodePeer.js +60 -14
- package/dist/blockchain/ServerCoin.d.ts +11 -3
- package/dist/blockchain/ServerCoin.d.ts.map +1 -1
- package/dist/blockchain/ServerCoin.js +45 -17
- package/dist/blockchain/Wallet.d.ts +1 -1
- package/dist/blockchain/Wallet.d.ts.map +1 -1
- package/dist/blockchain/Wallet.js +1 -1
- package/dist/blockchain/coins.d.ts +1 -1
- package/dist/blockchain/coins.d.ts.map +1 -1
- package/dist/blockchain/coins.js +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- 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/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 +0 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +5 -12
- package/dist/utils/directoryUtils.d.ts +0 -6
- package/dist/utils/directoryUtils.d.ts.map +1 -1
- package/dist/utils/directoryUtils.js +30 -11
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +0 -1
- package/package.json +9 -3
- package/dist/utils/deltaUtils.d.ts +0 -2
- package/dist/utils/deltaUtils.d.ts.map +0 -1
- package/dist/utils/deltaUtils.js +0 -83
|
@@ -28,10 +28,13 @@ const fs = __importStar(require("fs"));
|
|
|
28
28
|
const path = __importStar(require("path"));
|
|
29
29
|
const cli_progress_1 = require("cli-progress");
|
|
30
30
|
const DigPeer_1 = require("./DigPeer");
|
|
31
|
-
const deltaUtils_1 = require("../utils/deltaUtils");
|
|
32
31
|
const hashUtils_1 = require("../utils/hashUtils");
|
|
33
32
|
const blockchain_1 = require("../blockchain");
|
|
34
33
|
const config_1 = require("../utils/config");
|
|
34
|
+
const util_1 = require("util");
|
|
35
|
+
const DataIntegrityTree_1 = require("../DataIntegrityTree");
|
|
36
|
+
const rename = (0, util_1.promisify)(fs.rename);
|
|
37
|
+
const unlink = (0, util_1.promisify)(fs.unlink);
|
|
35
38
|
class DigNetwork {
|
|
36
39
|
constructor(storeId) {
|
|
37
40
|
this.dataStore = blockchain_1.DataStore.from(storeId);
|
|
@@ -39,83 +42,6 @@ class DigNetwork {
|
|
|
39
42
|
this.storeDir = path.resolve(config_1.DIG_FOLDER_PATH, "stores", storeId);
|
|
40
43
|
this.peerBlacklist = new Map(); // Initialize empty map for blacklists
|
|
41
44
|
}
|
|
42
|
-
async uploadPreflight(digPeer) {
|
|
43
|
-
// Preflight check is handled internally by PropagationServer if needed
|
|
44
|
-
const { lastUploadedHash, generationIndex } = await digPeer.propagationServer.getUploadDetails();
|
|
45
|
-
const rootHistory = await this.dataStore.getLocalRootHistory();
|
|
46
|
-
if (!rootHistory || rootHistory.length === 0) {
|
|
47
|
-
throw new Error("No root hashes found. Please commit your changes first.");
|
|
48
|
-
}
|
|
49
|
-
const lastLocalRootHash = rootHistory[rootHistory.length - 1].root_hash;
|
|
50
|
-
const localGenerationIndex = rootHistory.length - 1;
|
|
51
|
-
// Handle conditions based on the upload details
|
|
52
|
-
if (lastUploadedHash !== lastLocalRootHash &&
|
|
53
|
-
generationIndex === localGenerationIndex) {
|
|
54
|
-
throw new Error("The repository seems to be corrupted. Please pull the latest changes before pushing.");
|
|
55
|
-
}
|
|
56
|
-
if (lastUploadedHash === lastLocalRootHash &&
|
|
57
|
-
generationIndex === localGenerationIndex) {
|
|
58
|
-
throw new Error("No changes detected. Skipping push.");
|
|
59
|
-
}
|
|
60
|
-
if (lastUploadedHash !== lastLocalRootHash &&
|
|
61
|
-
generationIndex > localGenerationIndex) {
|
|
62
|
-
throw new Error("Remote repository is ahead of the local repository. Please pull the latest changes before pushing.");
|
|
63
|
-
}
|
|
64
|
-
return { generationIndex, lastLocalRootHash };
|
|
65
|
-
}
|
|
66
|
-
async uploadStoreHead(digPeer) {
|
|
67
|
-
// First make sure that the remote store is up to date.
|
|
68
|
-
const rootHistory = await this.dataStore.getRootHistory();
|
|
69
|
-
const localManifestHashes = await this.dataStore.getManifestHashes();
|
|
70
|
-
const remoteManifestFile = await digPeer.propagationServer.getStoreData("manifest.dat");
|
|
71
|
-
const remoteManifestHashes = remoteManifestFile.split("\n").filter(Boolean);
|
|
72
|
-
const onChainRootHashes = rootHistory.map((root) => root.root_hash);
|
|
73
|
-
// Check that remote manifest is one behind on-chain root hashes
|
|
74
|
-
if (remoteManifestHashes.length !== onChainRootHashes.length - 1) {
|
|
75
|
-
throw new Error("Remote manifest should be one behind the on-chain root. Cannot push head.");
|
|
76
|
-
}
|
|
77
|
-
// Compare each remote manifest hash with the corresponding on-chain root hash
|
|
78
|
-
for (let i = 0; i < remoteManifestHashes.length; i++) {
|
|
79
|
-
if (remoteManifestHashes[i] !== onChainRootHashes[i]) {
|
|
80
|
-
throw new Error(`Remote manifest does not match on-chain root at index ${i}. Cannot push head.`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
// Get the files for the latest local manifest hash
|
|
84
|
-
const filesToUpload = await this.dataStore.getFileSetForRootHash(localManifestHashes[localManifestHashes.length - 1]);
|
|
85
|
-
if (!filesToUpload.length) {
|
|
86
|
-
console.log("No files to upload.");
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
// Upload files to the remote peer with a progress bar
|
|
90
|
-
await this.runProgressBar(filesToUpload.length, "Store Data", async (progress) => {
|
|
91
|
-
for (const filePath of filesToUpload) {
|
|
92
|
-
const relativePath = path
|
|
93
|
-
.relative(this.storeDir, filePath)
|
|
94
|
-
.replace(/\\/g, "/");
|
|
95
|
-
await digPeer.propagationServer.pushFile(filePath, relativePath);
|
|
96
|
-
progress.increment();
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
// Uploads the store to a specific peer
|
|
101
|
-
async uploadStore(digPeer) {
|
|
102
|
-
const { generationIndex } = await this.uploadPreflight(digPeer);
|
|
103
|
-
const filesToUpload = await (0, deltaUtils_1.getDeltaFiles)(this.dataStore.StoreId, generationIndex, path.resolve(config_1.DIG_FOLDER_PATH, "stores"));
|
|
104
|
-
console.log(filesToUpload);
|
|
105
|
-
if (!filesToUpload.length) {
|
|
106
|
-
console.log("No files to upload.");
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
await this.runProgressBar(filesToUpload.length, "Store Data", async (progress) => {
|
|
110
|
-
for (const filePath of filesToUpload) {
|
|
111
|
-
const relativePath = path
|
|
112
|
-
.relative(this.storeDir, filePath)
|
|
113
|
-
.replace(/\\/g, "/");
|
|
114
|
-
await digPeer.propagationServer.pushFile(filePath, relativePath);
|
|
115
|
-
progress.increment();
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
45
|
static async subscribeToStore(storeId) {
|
|
120
46
|
fs.mkdirSync(path.join(config_1.DIG_FOLDER_PATH, "stores", storeId), {
|
|
121
47
|
recursive: true,
|
|
@@ -123,60 +49,128 @@ class DigNetwork {
|
|
|
123
49
|
const digNetwork = new DigNetwork(storeId);
|
|
124
50
|
await digNetwork.downloadFiles(true);
|
|
125
51
|
}
|
|
52
|
+
static async findPeerWithStoreKey(storeId, rootHash, key, intialBlackList = []) {
|
|
53
|
+
const peerBlackList = intialBlackList;
|
|
54
|
+
const serverCoin = new blockchain_1.ServerCoin(storeId);
|
|
55
|
+
let peerIp = null;
|
|
56
|
+
// Keep sampling peers until an empty array is returned
|
|
57
|
+
while (true) {
|
|
58
|
+
try {
|
|
59
|
+
// Sample a peer from the current epoch
|
|
60
|
+
const digPeers = await serverCoin.sampleCurrentEpoch(1, peerBlackList);
|
|
61
|
+
// If no peers are returned, break out of the loop
|
|
62
|
+
if (digPeers.length === 0) {
|
|
63
|
+
console.log("No more peers found.");
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
peerIp = digPeers[0];
|
|
67
|
+
const digPeer = new DigPeer_1.DigPeer(peerIp, storeId);
|
|
68
|
+
// Try to fetch the head store information
|
|
69
|
+
const storeResponse = await digPeer.contentServer.headStore({
|
|
70
|
+
hasRootHash: rootHash,
|
|
71
|
+
});
|
|
72
|
+
// If the peer has the correct root hash, check if key is required
|
|
73
|
+
if (storeResponse.headers?.["x-has-roothash"] === "true") {
|
|
74
|
+
console.log(`Found Peer at ${peerIp} for storeId: ${storeId}, root hash ${rootHash}`);
|
|
75
|
+
// If no key is provided, return the peer
|
|
76
|
+
if (!key) {
|
|
77
|
+
return digPeer;
|
|
78
|
+
}
|
|
79
|
+
// If key is provided, check if the peer has it
|
|
80
|
+
const keyResponse = await digPeer.contentServer.headKey(key);
|
|
81
|
+
if (keyResponse.headers?.["x-key-exists"] === "true") {
|
|
82
|
+
return digPeer;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Add peer to blacklist if it doesn't meet criteria
|
|
86
|
+
peerBlackList.push(peerIp);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
console.error(`Error connecting to DIG Peer ${peerIp}. Resampling...`);
|
|
90
|
+
if (peerIp) {
|
|
91
|
+
peerBlackList.push(peerIp); // Add to blacklist if error occurs
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Return null if no valid peer was found
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
126
98
|
static unsubscribeFromStore(storeId) {
|
|
127
99
|
fs.rmdirSync(path.join(config_1.DIG_FOLDER_PATH, "stores", storeId), {
|
|
128
100
|
recursive: true,
|
|
129
101
|
});
|
|
130
102
|
fs.unlinkSync(path.join(config_1.DIG_FOLDER_PATH, "stores", storeId + ".json"));
|
|
131
103
|
}
|
|
132
|
-
|
|
133
|
-
|
|
104
|
+
async downloadFiles(skipData = false) {
|
|
105
|
+
console.log("Starting file download process...");
|
|
106
|
+
let peerBlackList = [];
|
|
107
|
+
let selectedPeer = null;
|
|
134
108
|
try {
|
|
135
109
|
const rootHistory = await this.dataStore.getRootHistory();
|
|
136
|
-
if (!rootHistory.length)
|
|
110
|
+
if (!rootHistory.length) {
|
|
137
111
|
throw new Error("No roots found in rootHistory. Cannot proceed with file download.");
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
112
|
+
}
|
|
113
|
+
await this.downloadHeightFile(true);
|
|
114
|
+
// Filter out rootInfo entries where the .dat file already exists
|
|
115
|
+
const rootHistoryFiltered = rootHistory
|
|
116
|
+
.filter((item) => item.timestamp !== undefined)
|
|
117
|
+
.filter((item) => !fs.existsSync(`${this.storeDir}/${item.root_hash}.dat`))
|
|
118
|
+
// Reverse to download the latest first
|
|
119
|
+
.reverse();
|
|
120
|
+
if (!rootHistoryFiltered.length) {
|
|
121
|
+
console.log("All root hashes already exist locally. No need for download.");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// Process filtered rootHistory sequentially
|
|
125
|
+
for (const rootInfo of rootHistoryFiltered) {
|
|
126
|
+
while (true) {
|
|
127
|
+
try {
|
|
128
|
+
selectedPeer = await DigNetwork.findPeerWithStoreKey(this.dataStore.StoreId, rootInfo.root_hash, undefined, peerBlackList);
|
|
129
|
+
if (!selectedPeer) {
|
|
130
|
+
console.error(`No peer found with root hash ${rootInfo.root_hash}. Skipping download.`);
|
|
131
|
+
break; // Exit loop if no more peers are found
|
|
132
|
+
}
|
|
133
|
+
const rootResponse = await selectedPeer.propagationServer.getStoreData(`${rootInfo.root_hash}.dat`);
|
|
134
|
+
const root = JSON.parse(rootResponse);
|
|
135
|
+
if (!skipData) {
|
|
136
|
+
// Sequential file download
|
|
137
|
+
for (const [storeKey, file] of Object.entries(root.files)) {
|
|
138
|
+
const filePath = (0, hashUtils_1.getFilePathFromSha256)(file.sha256, `${this.storeDir}/data`);
|
|
139
|
+
console.log(`Downloading file with sha256: ${file.sha256}...`);
|
|
140
|
+
await selectedPeer.downloadData(this.dataStore.StoreId, `data/${file.sha256.match(/.{1,2}/g).join("/")}`);
|
|
141
|
+
const integrityCheck = await DataIntegrityTree_1.DataIntegrityTree.validateKeyIntegrityWithForeignTree(storeKey, file.sha256, root, rootInfo.root_hash, `${this.storeDir}/data`);
|
|
142
|
+
if (integrityCheck) {
|
|
143
|
+
console.log(`\x1b[32mIntegrity check passed for file with sha256: ${file.sha256}.\x1b[0m`);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
console.error(`\x1b[31mIntegrity check failed for file with sha256: ${file.sha256}.\x1b[0m`);
|
|
147
|
+
await unlink(filePath);
|
|
148
|
+
throw new Error(`Store Integrity check failed. Syncing file from another peer.`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
fs.writeFileSync(`${this.storeDir}/${rootInfo.root_hash}.dat`, rootResponse);
|
|
152
|
+
peerBlackList = []; // Clear the blacklist upon successful download
|
|
153
|
+
// Break out of the retry loop if the download succeeds
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
console.error(`Error downloading from peer. Retrying with another peer.`, error);
|
|
158
|
+
if (selectedPeer) {
|
|
159
|
+
peerBlackList.push(selectedPeer.IpAddress); // Add peer to blacklist and try again
|
|
160
|
+
}
|
|
167
161
|
}
|
|
168
162
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
163
|
+
// Only process the first root hash so other stores can sync the latest.
|
|
164
|
+
// This has an effect where the latest roothash will always be synced first, even if new ones come in.
|
|
165
|
+
// Then it will backfill historical roothashes
|
|
166
|
+
break;
|
|
172
167
|
}
|
|
173
|
-
if (newRootHashes.length)
|
|
174
|
-
fs.appendFileSync(localManifestPath, newRootHashes.join("\n") + "\n");
|
|
175
|
-
await this.downloadManifestFile(forceDownload);
|
|
176
|
-
progressBar?.stop();
|
|
177
168
|
console.log("Syncing store complete.");
|
|
178
169
|
}
|
|
179
170
|
catch (error) {
|
|
171
|
+
if (selectedPeer) {
|
|
172
|
+
peerBlackList.push(selectedPeer.IpAddress);
|
|
173
|
+
}
|
|
180
174
|
console.trace(error);
|
|
181
175
|
throw error;
|
|
182
176
|
}
|
|
@@ -192,62 +186,33 @@ class DigNetwork {
|
|
|
192
186
|
const heightFilePath = path.join(this.storeDir, "height.json");
|
|
193
187
|
await this.downloadFileFromPeers("height.json", heightFilePath, forceDownload);
|
|
194
188
|
}
|
|
195
|
-
async downloadManifestFile(forceDownload) {
|
|
196
|
-
const heightFilePath = path.join(this.storeDir, "manifest.dat");
|
|
197
|
-
await this.downloadFileFromPeers("manifest.dat", heightFilePath, forceDownload);
|
|
198
|
-
}
|
|
199
189
|
async downloadFileFromPeers(dataPath, filePath, overwrite) {
|
|
200
190
|
let digPeers = await this.fetchAvailablePeers();
|
|
191
|
+
const tempFilePath = `${filePath}.tmp`;
|
|
201
192
|
while (true) {
|
|
202
193
|
if (!overwrite && fs.existsSync(filePath))
|
|
203
194
|
return;
|
|
204
195
|
const blacklist = this.peerBlacklist.get(dataPath) || new Set();
|
|
205
196
|
for (const digPeer of digPeers) {
|
|
206
|
-
if (blacklist.has(digPeer.IpAddress))
|
|
207
|
-
continue;
|
|
208
197
|
try {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
fs.mkdirSync(directory, { recursive: true });
|
|
213
|
-
}
|
|
214
|
-
// Stream the file data directly to the file system
|
|
215
|
-
const fileStream = fs.createWriteStream(filePath);
|
|
216
|
-
// Start streaming the data from the peer
|
|
217
|
-
const peerStream = await digPeer.propagationServer.streamStoreData(dataPath);
|
|
218
|
-
// Pipe the peer stream directly to the file system
|
|
219
|
-
await new Promise((resolve, reject) => {
|
|
220
|
-
peerStream.pipe(fileStream);
|
|
221
|
-
peerStream.on("end", resolve);
|
|
222
|
-
peerStream.on("error", reject);
|
|
223
|
-
fileStream.on("error", reject);
|
|
224
|
-
});
|
|
225
|
-
if (process.env.DIG_DEBUG === "1") {
|
|
226
|
-
console.log(`Downloaded ${dataPath} from ${digPeer.IpAddress}`);
|
|
227
|
-
}
|
|
198
|
+
if (blacklist.has(digPeer.IpAddress))
|
|
199
|
+
continue;
|
|
200
|
+
await digPeer.downloadData(this.dataStore.StoreId, dataPath);
|
|
228
201
|
return; // Exit the method if download succeeds
|
|
229
202
|
}
|
|
230
203
|
catch (error) {
|
|
231
204
|
console.warn(`Failed to download ${dataPath} from ${digPeer.IpAddress}, blacklisting peer and trying next...`);
|
|
232
205
|
blacklist.add(digPeer.IpAddress);
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if (process.env.DIG_DEBUG === "1") {
|
|
238
|
-
console.warn(`All peers blacklisted for ${dataPath}. Refreshing peers...`);
|
|
239
|
-
}
|
|
240
|
-
digPeers = await this.fetchAvailablePeers();
|
|
241
|
-
if (!digPeers.length) {
|
|
242
|
-
throw new Error(`Failed to download ${dataPath}: no peers available.`);
|
|
206
|
+
// Clean up the temp file in case of failure
|
|
207
|
+
if (fs.existsSync(tempFilePath)) {
|
|
208
|
+
await unlink(tempFilePath);
|
|
209
|
+
}
|
|
243
210
|
}
|
|
244
211
|
}
|
|
245
212
|
}
|
|
246
213
|
}
|
|
247
214
|
async runProgressBar(total, name, task) {
|
|
248
215
|
// Using 'any' to work around TypeScript issues
|
|
249
|
-
const oldConsoleLog = console.log;
|
|
250
|
-
console.log = () => { }; // Suppress console.log output
|
|
251
216
|
const multiBar = new cli_progress_1.MultiBar({
|
|
252
217
|
clearOnComplete: false,
|
|
253
218
|
hideCursor: true,
|
|
@@ -257,7 +222,6 @@ class DigNetwork {
|
|
|
257
222
|
const progress = multiBar.create(total, 0, { name });
|
|
258
223
|
await task(progress).finally(() => {
|
|
259
224
|
multiBar.stop();
|
|
260
|
-
console.log = oldConsoleLog; // Restore console.log
|
|
261
225
|
});
|
|
262
226
|
}
|
|
263
227
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ContentServer } from "./ContentServer";
|
|
2
2
|
import { PropagationServer } from "./PropagationServer";
|
|
3
3
|
import { IncentiveServer } from "./IncentiveServer";
|
|
4
|
+
import { Output } from "@dignetwork/datalayer-driver";
|
|
4
5
|
export declare class DigPeer {
|
|
5
6
|
private ipAddress;
|
|
6
7
|
private storeId;
|
|
@@ -12,8 +13,12 @@ export declare class DigPeer {
|
|
|
12
13
|
get propagationServer(): PropagationServer;
|
|
13
14
|
get incentiveServer(): IncentiveServer;
|
|
14
15
|
get IpAddress(): string;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
sendPayment(walletName: string, amount: bigint): Promise<void>;
|
|
16
|
+
static sendEqualBulkPayments(walletName: string, addresses: string[], totalAmount: bigint, memos: Buffer[]): Promise<void>;
|
|
17
|
+
static sendBulkPayments(walletName: string, outputs: Output[]): Promise<void>;
|
|
18
|
+
sendPayment(walletName: string, amount: bigint, memos?: Buffer[]): Promise<void>;
|
|
19
|
+
static createPaymentHint(storeId: Buffer): Buffer;
|
|
20
|
+
syncStore(): Promise<void>;
|
|
21
|
+
pushStoreRoot(storeId: string, rootHash: string): Promise<void>;
|
|
22
|
+
downloadData(storeId: string, dataPath: string): Promise<void>;
|
|
18
23
|
}
|
|
19
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;WAEa,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,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAU1B,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAyB/D,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CA4C5E"}
|
|
@@ -4,16 +4,21 @@ 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
|
-
const
|
|
13
|
-
const datalayer_driver_1 = require("datalayer-driver");
|
|
13
|
+
const fs_1 = __importDefault(require("fs"));
|
|
14
|
+
const datalayer_driver_1 = require("@dignetwork/datalayer-driver");
|
|
14
15
|
const blockchain_2 = require("../blockchain");
|
|
15
16
|
const blockchain_3 = require("../blockchain");
|
|
16
17
|
const coins_1 = require("../blockchain/coins");
|
|
18
|
+
const config_1 = require("../utils/config");
|
|
19
|
+
const util_1 = require("util");
|
|
20
|
+
const rename = (0, util_1.promisify)(fs_1.default.rename);
|
|
21
|
+
const unlink = (0, util_1.promisify)(fs_1.default.unlink);
|
|
17
22
|
class DigPeer {
|
|
18
23
|
constructor(ipAddress, storeId) {
|
|
19
24
|
this.ipAddress = ipAddress;
|
|
@@ -37,122 +42,31 @@ class DigPeer {
|
|
|
37
42
|
get IpAddress() {
|
|
38
43
|
return this.ipAddress;
|
|
39
44
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
// Ensure all hashes in root history are present in the manifest in the same order
|
|
56
|
-
for (let i = 0; i < rootHistory.length; i++) {
|
|
57
|
-
if (rootHistory[i].root_hash !== manifestHashes[i]) {
|
|
58
|
-
console.error(`Hash mismatch at index ${i}: manifest hash ${manifestHashes[i]} does not match root history hash ${rootHistory[i].root_hash}`);
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
console.log("Manifest file validated.");
|
|
63
|
-
// Fetch the .dat file content for the specified root hash from the content server
|
|
64
|
-
const datFileContent = JSON.parse(await this.propagationServer.getStoreData(`${rootHash}.dat`));
|
|
65
|
-
if (datFileContent.root !== rootHash) {
|
|
66
|
-
console.error(`Root hash in .dat file does not match: ${datFileContent.root} !== ${rootHash}`);
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
let filesIntegrityIntact = true;
|
|
70
|
-
// Validate SHA256 hashes of the specified keys using streamStoreKey
|
|
71
|
-
for (const key of keys) {
|
|
72
|
-
const fileData = datFileContent.files[key];
|
|
73
|
-
if (!fileData) {
|
|
74
|
-
console.error(`File key ${key} not found in .dat file.`);
|
|
75
|
-
filesIntegrityIntact = false;
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
// Stream the file from the propagation server and calculate the SHA256 hash on the fly
|
|
79
|
-
const hash = crypto_1.default.createHash("sha256");
|
|
80
|
-
const fileStream = await this.contentServer.streamKey(Buffer.from(key, "hex").toString("utf-8"));
|
|
81
|
-
await new Promise((resolve, reject) => {
|
|
82
|
-
fileStream.on("data", (chunk) => {
|
|
83
|
-
hash.update(chunk); // Update the hash with each chunk of data
|
|
84
|
-
});
|
|
85
|
-
fileStream.on("end", () => {
|
|
86
|
-
const calculatedHash = hash.digest("hex");
|
|
87
|
-
// Compare the calculated hash with the expected hash
|
|
88
|
-
if (calculatedHash !== fileData.sha256) {
|
|
89
|
-
console.error(`File ${key} failed SHA256 validation.`);
|
|
90
|
-
filesIntegrityIntact = false;
|
|
91
|
-
}
|
|
92
|
-
resolve();
|
|
93
|
-
});
|
|
94
|
-
fileStream.on("error", (err) => {
|
|
95
|
-
console.error(`Failed to stream file ${key}: ${err.message}`);
|
|
96
|
-
reject(err);
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
// Perform tree integrity validation using the datFileContent and the root hash
|
|
100
|
-
const treeCheck = DataIntegrityTree_1.DataIntegrityTree.validateKeyIntegrityWithForeignTree(key, fileData.sha256, datFileContent, rootHash);
|
|
101
|
-
if (!treeCheck) {
|
|
102
|
-
console.error(`Tree validation failed for file ${key}.`);
|
|
103
|
-
filesIntegrityIntact = false;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
if (!filesIntegrityIntact) {
|
|
107
|
-
console.error("Store Corrupted: Data failed SHA256 validation.");
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
console.log("Store validation successful.");
|
|
111
|
-
return true;
|
|
112
|
-
}
|
|
113
|
-
catch (error) {
|
|
114
|
-
console.error(`Failed to validate store: ${error.message}`);
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
45
|
+
static sendEqualBulkPayments(walletName, addresses, totalAmount, memos) {
|
|
46
|
+
// Use a Set to ensure unique addresses
|
|
47
|
+
const uniqueAddresses = Array.from(new Set(addresses));
|
|
48
|
+
// Convert unique addresses to puzzle hashes
|
|
49
|
+
const puzzleHashes = uniqueAddresses.map((address) => (0, datalayer_driver_1.addressToPuzzleHash)(address));
|
|
50
|
+
// Calculate amount per puzzle hash
|
|
51
|
+
const amountPerPuzzleHash = totalAmount / BigInt(puzzleHashes.length);
|
|
52
|
+
// Create outputs array
|
|
53
|
+
const outputs = puzzleHashes.map((puzzleHash) => ({
|
|
54
|
+
puzzleHash,
|
|
55
|
+
amount: amountPerPuzzleHash,
|
|
56
|
+
memos,
|
|
57
|
+
}));
|
|
58
|
+
// Call the sendBulkPayments function with the generated outputs
|
|
59
|
+
return DigPeer.sendBulkPayments(walletName, outputs);
|
|
117
60
|
}
|
|
118
|
-
async
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const dataStore = blockchain_1.DataStore.from(this.storeId);
|
|
122
|
-
const rootHistory = await dataStore.getRootHistory();
|
|
123
|
-
if (rootHistory.length === 0) {
|
|
124
|
-
console.error("No root history found for the store.");
|
|
125
|
-
return false;
|
|
126
|
-
}
|
|
127
|
-
// Fetch the manifest.dat file content from the content server
|
|
128
|
-
const manifestContent = await this.propagationServer.getStoreData("manifest.dat");
|
|
129
|
-
const manifestHashes = manifestContent
|
|
130
|
-
.split("\n")
|
|
131
|
-
.filter(Boolean);
|
|
132
|
-
// Compare lengths of root history and manifest
|
|
133
|
-
return rootHistory.length === manifestHashes.length;
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
console.error(`Failed to check sync status: ${error.message}`);
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
async sendPayment(walletName, amount) {
|
|
141
|
-
const paymentAddress = await this.contentServer.getPaymentAddress();
|
|
142
|
-
console.log(`Sending ${amount} Mojos to ${paymentAddress}...`);
|
|
143
|
-
const fee = BigInt(1000);
|
|
61
|
+
static async sendBulkPayments(walletName, outputs) {
|
|
62
|
+
const feePerCondition = BigInt(1000);
|
|
63
|
+
const totalFee = feePerCondition * BigInt(outputs.length);
|
|
144
64
|
const wallet = await blockchain_3.Wallet.load(walletName);
|
|
145
65
|
const publicSyntheticKey = await wallet.getPublicSyntheticKey();
|
|
146
66
|
const peer = await blockchain_2.FullNodePeer.connect();
|
|
147
|
-
const
|
|
148
|
-
const
|
|
149
|
-
const
|
|
150
|
-
{
|
|
151
|
-
puzzleHash: paymentAddressPuzzleHash,
|
|
152
|
-
amount: amount,
|
|
153
|
-
},
|
|
154
|
-
];
|
|
155
|
-
const coinSpends = await (0, datalayer_driver_1.sendXch)(publicSyntheticKey, coins, outputs, fee);
|
|
67
|
+
const totalAmount = outputs.reduce((acc, output) => acc + output.amount, BigInt(0));
|
|
68
|
+
const coins = await (0, coins_1.selectUnspentCoins)(peer, totalAmount, totalFee, [], walletName);
|
|
69
|
+
const coinSpends = await (0, datalayer_driver_1.sendXch)(publicSyntheticKey, coins, outputs, totalFee);
|
|
156
70
|
const sig = (0, datalayer_driver_1.signCoinSpends)(coinSpends, [await wallet.getPrivateSyntheticKey()], false);
|
|
157
71
|
const err = await peer.broadcastSpend(coinSpends, [sig]);
|
|
158
72
|
if (err) {
|
|
@@ -160,5 +74,93 @@ class DigPeer {
|
|
|
160
74
|
}
|
|
161
75
|
await blockchain_2.FullNodePeer.waitForConfirmation((0, datalayer_driver_1.getCoinId)(coins[0]));
|
|
162
76
|
}
|
|
77
|
+
async sendPayment(walletName, amount, memos = []) {
|
|
78
|
+
const paymentAddress = await this.contentServer.getPaymentAddress();
|
|
79
|
+
const paymentAddressPuzzleHash = (0, datalayer_driver_1.addressToPuzzleHash)(paymentAddress);
|
|
80
|
+
const output = {
|
|
81
|
+
puzzleHash: paymentAddressPuzzleHash,
|
|
82
|
+
amount,
|
|
83
|
+
memos,
|
|
84
|
+
};
|
|
85
|
+
return DigPeer.sendBulkPayments(walletName, [output]);
|
|
86
|
+
}
|
|
87
|
+
static createPaymentHint(storeId) {
|
|
88
|
+
// Ensure the input is a 32-byte buffer
|
|
89
|
+
if (!Buffer.isBuffer(storeId) || storeId.length !== 32) {
|
|
90
|
+
throw new Error("Invalid input. Must be a 32-byte buffer.");
|
|
91
|
+
}
|
|
92
|
+
// Define the seed
|
|
93
|
+
const seed = "digpayment";
|
|
94
|
+
// Combine the seed and the original buffer
|
|
95
|
+
const combinedBuffer = Buffer.concat([Buffer.from(seed), storeId]);
|
|
96
|
+
// Apply SHA-256 hash to the combined buffer
|
|
97
|
+
const hash = crypto_1.default.createHash("sha256");
|
|
98
|
+
hash.update(combinedBuffer);
|
|
99
|
+
const transformedBuffer = hash.digest();
|
|
100
|
+
// Return the 32-byte hash as a hex string
|
|
101
|
+
return transformedBuffer;
|
|
102
|
+
}
|
|
103
|
+
async syncStore() {
|
|
104
|
+
const dataStore = blockchain_1.DataStore.from(this.storeId);
|
|
105
|
+
const rootHistory = await dataStore.getRootHistory();
|
|
106
|
+
rootHistory
|
|
107
|
+
.filter((root) => root.synced)
|
|
108
|
+
.forEach((item) => {
|
|
109
|
+
this.pushStoreRoot(this.storeId, item.root_hash);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async pushStoreRoot(storeId, rootHash) {
|
|
113
|
+
const dataStore = blockchain_1.DataStore.from(storeId);
|
|
114
|
+
const alreadySynced = await this.contentServer.hasRootHash(rootHash);
|
|
115
|
+
if (alreadySynced) {
|
|
116
|
+
console.log(`Root hash ${rootHash} already synced.`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const tree = await dataStore.Tree.serialize(rootHash);
|
|
120
|
+
// @ts-ignore
|
|
121
|
+
tree.files.forEach(async (file) => {
|
|
122
|
+
await dataStore.Tree.verifyKeyIntegrity(file.sha256, rootHash);
|
|
123
|
+
console.log(`Pushing file ${file.key} to ${this.IpAddress}`);
|
|
124
|
+
const dataPath = path_1.default.join("data", file.sha256.match(/.{1,2}/g).join("/"));
|
|
125
|
+
const fileLocation = path_1.default.join(config_1.STORE_PATH, storeId, dataPath);
|
|
126
|
+
await this.propagationServer.pushFile(fileLocation, dataPath);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
async downloadData(storeId, dataPath) {
|
|
130
|
+
const filePath = path_1.default.join(config_1.STORE_PATH, storeId, dataPath);
|
|
131
|
+
const tempFilePath = `${filePath}.tmp`;
|
|
132
|
+
try {
|
|
133
|
+
const headStoreResponse = await this.propagationServer.headStore();
|
|
134
|
+
if (!headStoreResponse.success) {
|
|
135
|
+
throw new Error("Data not accessible from store.");
|
|
136
|
+
}
|
|
137
|
+
const directory = path_1.default.dirname(tempFilePath);
|
|
138
|
+
if (!fs_1.default.existsSync(directory)) {
|
|
139
|
+
fs_1.default.mkdirSync(directory, { recursive: true });
|
|
140
|
+
}
|
|
141
|
+
const fileStream = fs_1.default.createWriteStream(tempFilePath);
|
|
142
|
+
const dataStream = await this.propagationServer.streamStoreData(dataPath);
|
|
143
|
+
await new Promise((resolve, reject) => {
|
|
144
|
+
dataStream.pipe(fileStream);
|
|
145
|
+
dataStream.on("end", resolve);
|
|
146
|
+
dataStream.on("error", reject);
|
|
147
|
+
fileStream.on("error", reject);
|
|
148
|
+
});
|
|
149
|
+
await rename(tempFilePath, filePath);
|
|
150
|
+
console.log(`Downloaded ${dataPath} from ${this.IpAddress}`);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
console.error(`Failed to download data: ${error.message}`);
|
|
154
|
+
if (fs_1.default.existsSync(tempFilePath)) {
|
|
155
|
+
await unlink(tempFilePath);
|
|
156
|
+
}
|
|
157
|
+
// Check if directory is empty and remove it if it is
|
|
158
|
+
const directory = path_1.default.dirname(tempFilePath);
|
|
159
|
+
if (fs_1.default.existsSync(directory) && fs_1.default.readdirSync(directory).length === 0) {
|
|
160
|
+
fs_1.default.rmdirSync(directory);
|
|
161
|
+
console.log(`Removed empty directory: ${directory}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
163
165
|
}
|
|
164
166
|
exports.DigPeer = DigPeer;
|