@dignetwork/dig-sdk 0.0.1-alpha.125 → 0.0.1-alpha.127
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/DigNetwork/DigNetwork.d.ts +1 -0
- package/dist/DigNetwork/DigNetwork.d.ts.map +1 -1
- package/dist/DigNetwork/DigNetwork.js +3 -0
- package/dist/blockchain/DataStore.d.ts +5 -7
- package/dist/blockchain/DataStore.d.ts.map +1 -1
- package/dist/blockchain/DataStore.js +25 -135
- package/dist/blockchain/FullNodePeer.d.ts +93 -9
- package/dist/blockchain/FullNodePeer.d.ts.map +1 -1
- package/dist/blockchain/FullNodePeer.js +314 -137
- package/dist/blockchain/StoreMonitorRegistry.d.ts +85 -0
- package/dist/blockchain/StoreMonitorRegistry.d.ts.map +1 -0
- package/dist/blockchain/StoreMonitorRegistry.js +242 -0
- package/dist/blockchain/index.d.ts +1 -0
- package/dist/blockchain/index.d.ts.map +1 -1
- package/dist/blockchain/index.js +1 -0
- package/package.json +1 -1
- package/dist/blockchain/StoreInfoCacheUpdater.d.ts +0 -16
- package/dist/blockchain/StoreInfoCacheUpdater.d.ts.map +0 -1
- package/dist/blockchain/StoreInfoCacheUpdater.js +0 -242
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StoreMonitorRegistry = void 0;
|
|
4
|
+
const FullNodePeer_1 = require("./FullNodePeer");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
const DataStoreSerializer_1 = require("./DataStoreSerializer");
|
|
7
|
+
const DataStore_1 = require("./DataStore");
|
|
8
|
+
const datalayer_driver_1 = require("@dignetwork/datalayer-driver");
|
|
9
|
+
const utils_2 = require("../utils");
|
|
10
|
+
/**
|
|
11
|
+
* StoreMonitorRegistry manages monitoring of multiple storeIds.
|
|
12
|
+
* It encapsulates cache management and ensures only one monitor runs per storeId.
|
|
13
|
+
*/
|
|
14
|
+
class StoreMonitorRegistry {
|
|
15
|
+
/**
|
|
16
|
+
* Private constructor to enforce singleton pattern.
|
|
17
|
+
*/
|
|
18
|
+
constructor() {
|
|
19
|
+
this.activeMonitors = new Map();
|
|
20
|
+
this.storeCoinCache = new utils_1.FileCache("stores", utils_1.USER_DIR_PATH);
|
|
21
|
+
this.cachePopulationPromises = new Map();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Retrieves the singleton instance of the registry.
|
|
25
|
+
* @returns {StoreMonitorRegistry} The singleton instance.
|
|
26
|
+
*/
|
|
27
|
+
static getInstance() {
|
|
28
|
+
if (!StoreMonitorRegistry.instance) {
|
|
29
|
+
StoreMonitorRegistry.instance = new StoreMonitorRegistry();
|
|
30
|
+
}
|
|
31
|
+
return StoreMonitorRegistry.instance;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Registers a storeId with a callback to be invoked upon updates.
|
|
35
|
+
* Immediately updates the cache and invokes the callback before starting the monitor.
|
|
36
|
+
* @param {string} storeId - The store identifier to monitor.
|
|
37
|
+
* @param {StoreUpdateCallback} callback - The callback to execute when the store is updated.
|
|
38
|
+
*/
|
|
39
|
+
async registerStore(storeId, callback) {
|
|
40
|
+
if (this.activeMonitors.has(storeId)) {
|
|
41
|
+
console.log(`Registry: Monitor already running for storeId: ${storeId}`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
console.log(`Registry: Registering monitor for storeId: ${storeId}`);
|
|
45
|
+
this.activeMonitors.set(storeId, callback);
|
|
46
|
+
try {
|
|
47
|
+
// Immediately fetch and cache the latest store info
|
|
48
|
+
const initialStore = await this.fetchAndCacheStoreInfo(storeId);
|
|
49
|
+
// Invoke the callback with the initial store info
|
|
50
|
+
callback(storeId, initialStore);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.error(`Registry: Failed to perform initial cache update for storeId: ${storeId} - ${error.message}`);
|
|
54
|
+
// Decide whether to unregister the monitor or proceed
|
|
55
|
+
// For this example, we'll proceed to start the monitor
|
|
56
|
+
}
|
|
57
|
+
// No monitoring in cli mode
|
|
58
|
+
if (!utils_2.Environment.CLI_MODE) {
|
|
59
|
+
// Start monitoring asynchronously
|
|
60
|
+
this.startMonitor(storeId, callback).catch((error) => {
|
|
61
|
+
console.error(`Registry: Unexpected error in startMonitor for storeId: ${storeId} - ${error.message}`);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Retrieves the latest cached entry for a given storeId.
|
|
67
|
+
* If no cache exists, it fetches and caches the store info before returning.
|
|
68
|
+
* @param {string} storeId - The store identifier.
|
|
69
|
+
* @returns {Promise<StoreCacheEntry>} The latest cached entry.
|
|
70
|
+
*/
|
|
71
|
+
async getLatestCache(storeId) {
|
|
72
|
+
// if in CLI mode, always fetch the latest store info directly
|
|
73
|
+
if (!utils_2.Environment.CLI_MODE) {
|
|
74
|
+
let cachedInfo = this.storeCoinCache.get(storeId);
|
|
75
|
+
if (cachedInfo) {
|
|
76
|
+
return cachedInfo;
|
|
77
|
+
}
|
|
78
|
+
// If cache is missing, fetch and cache it
|
|
79
|
+
console.log(`getLatestCache: No cache found for storeId: ${storeId}. Fetching...`);
|
|
80
|
+
// Prevent duplicate fetches for the same storeId
|
|
81
|
+
if (this.cachePopulationPromises.has(storeId)) {
|
|
82
|
+
return this.cachePopulationPromises.get(storeId);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const fetchPromise = this.fetchAndCacheStoreInfo(storeId)
|
|
86
|
+
.then((store) => {
|
|
87
|
+
this.cachePopulationPromises.delete(storeId);
|
|
88
|
+
return store;
|
|
89
|
+
})
|
|
90
|
+
.catch((error) => {
|
|
91
|
+
this.cachePopulationPromises.delete(storeId);
|
|
92
|
+
throw error;
|
|
93
|
+
});
|
|
94
|
+
this.cachePopulationPromises.set(storeId, fetchPromise);
|
|
95
|
+
return fetchPromise;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Starts the monitor for a specific storeId.
|
|
99
|
+
* Ensures that the monitor restarts upon any error.
|
|
100
|
+
* Implements an exponential backoff strategy for retries.
|
|
101
|
+
* @param {string} storeId - The store identifier to monitor.
|
|
102
|
+
* @param {StoreUpdateCallback} callback - The callback to invoke upon updates.
|
|
103
|
+
*/
|
|
104
|
+
async startMonitor(storeId, callback) {
|
|
105
|
+
let retryCount = 0;
|
|
106
|
+
const maxRetryDelay = 60000; // 60 seconds
|
|
107
|
+
while (this.activeMonitors.has(storeId)) {
|
|
108
|
+
try {
|
|
109
|
+
await this.monitorStore(storeId, callback);
|
|
110
|
+
retryCount = 0; // Reset on success
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
console.error(`Registry: Monitor for storeId: ${storeId} encountered an error: ${error.message}. Restarting monitor...`);
|
|
114
|
+
const delayTime = maxRetryDelay;
|
|
115
|
+
console.log(`Registry: Waiting for ${delayTime / 1000} seconds before retrying...`);
|
|
116
|
+
await this.delay(delayTime);
|
|
117
|
+
retryCount++;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
console.log(`Registry: Monitor for storeId: ${storeId} has been stopped.`);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Monitors a single store indefinitely.
|
|
124
|
+
* Executes a single iteration of the monitoring logic.
|
|
125
|
+
* @param {string} storeId - The store identifier to monitor.
|
|
126
|
+
* @param {StoreUpdateCallback} callback - The callback to invoke upon updates.
|
|
127
|
+
*/
|
|
128
|
+
async monitorStore(storeId, callback) {
|
|
129
|
+
console.log(`Monitor: Starting monitor iteration for storeId: ${storeId}`);
|
|
130
|
+
try {
|
|
131
|
+
console.log(`Monitor: Connecting to peer for storeId: ${storeId}`);
|
|
132
|
+
const peer = await FullNodePeer_1.FullNodePeer.connect();
|
|
133
|
+
const cachedInfo = this.storeCoinCache.get(storeId);
|
|
134
|
+
if (cachedInfo) {
|
|
135
|
+
// Log cached store info retrieval
|
|
136
|
+
console.log(`Monitor: Cached store info found for storeId: ${storeId}, syncing...`);
|
|
137
|
+
// Deserialize cached info and wait for the coin to be spent
|
|
138
|
+
const previousStore = DataStoreSerializer_1.DataStoreSerializer.deserialize({
|
|
139
|
+
latestStore: cachedInfo.latestStore,
|
|
140
|
+
latestHeight: cachedInfo.latestHeight.toString(),
|
|
141
|
+
latestHash: cachedInfo.latestHash,
|
|
142
|
+
});
|
|
143
|
+
console.log(`Monitor: Waiting for coin to be spent for storeId: ${storeId}...`);
|
|
144
|
+
const dataStore = DataStore_1.DataStore.from(storeId);
|
|
145
|
+
const { createdAtHeight, createdAtHash } = await dataStore.getCreationHeight();
|
|
146
|
+
await peer.waitForCoinToBeSpent((0, datalayer_driver_1.getCoinId)(previousStore.latestStore.coin), createdAtHeight, createdAtHash);
|
|
147
|
+
// Sync store and get updated details
|
|
148
|
+
console.log(`Monitor: Syncing store for storeId: ${storeId}`);
|
|
149
|
+
const { latestStore, latestHeight } = await peer.syncStore(previousStore.latestStore, createdAtHeight, createdAtHash, false);
|
|
150
|
+
const latestHash = await peer.getHeaderHash(latestHeight);
|
|
151
|
+
console.log(`Monitor: Store synced for storeId: ${storeId}, coin id: ${(0, datalayer_driver_1.getCoinId)(latestStore.coin).toString("hex")}`);
|
|
152
|
+
// Serialize and cache the updated store info
|
|
153
|
+
const serializedLatestStore = new DataStoreSerializer_1.DataStoreSerializer(latestStore, latestHeight, latestHash).serialize();
|
|
154
|
+
console.log(`Monitor: Caching updated store info for storeId: ${storeId}`);
|
|
155
|
+
this.storeCoinCache.set(storeId, {
|
|
156
|
+
latestStore: serializedLatestStore,
|
|
157
|
+
latestHeight,
|
|
158
|
+
latestHash: latestHash.toString("hex"),
|
|
159
|
+
});
|
|
160
|
+
// Invoke the callback with updated store info
|
|
161
|
+
const updatedStore = this.storeCoinCache.get(storeId);
|
|
162
|
+
if (updatedStore) {
|
|
163
|
+
callback(storeId, updatedStore);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
console.warn(`Monitor: Updated store info for storeId: ${storeId} is missing after caching.`);
|
|
167
|
+
}
|
|
168
|
+
return; // Successful iteration
|
|
169
|
+
}
|
|
170
|
+
// If no cached info exists, fetch initial store info
|
|
171
|
+
console.log(`Monitor: No cached info found for storeId: ${storeId}. Fetching initial store info.`);
|
|
172
|
+
// Fetch and cache the latest store info
|
|
173
|
+
const newStore = await this.fetchAndCacheStoreInfo(storeId);
|
|
174
|
+
// Invoke the callback with the new store info
|
|
175
|
+
callback(storeId, newStore);
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
console.error(`Monitor: Error monitoring storeId: ${storeId} - ${error.message}`);
|
|
179
|
+
// Propagate the error to trigger a restart in startMonitor
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Fetches the latest store information and updates the cache.
|
|
185
|
+
* @param {string} storeId - The store identifier.
|
|
186
|
+
* @returns {Promise<StoreCacheEntry>} The latest store information.
|
|
187
|
+
*/
|
|
188
|
+
async fetchAndCacheStoreInfo(storeId) {
|
|
189
|
+
console.log(`Monitor: Fetching and caching latest store info for storeId: ${storeId}`);
|
|
190
|
+
const peer = await FullNodePeer_1.FullNodePeer.connect();
|
|
191
|
+
const dataStore = DataStore_1.DataStore.from(storeId);
|
|
192
|
+
const { createdAtHeight, createdAtHash } = await dataStore.getCreationHeight();
|
|
193
|
+
// Sync store from the peer using launcher ID
|
|
194
|
+
console.log(`Monitor: Syncing store from launcher ID for storeId: ${storeId}`);
|
|
195
|
+
const { latestStore, latestHeight } = await peer.syncStoreFromLauncherId(Buffer.from(storeId, "hex"), createdAtHeight, createdAtHash, false);
|
|
196
|
+
const latestHash = await peer.getHeaderHash(latestHeight);
|
|
197
|
+
// Serialize and cache the new store info
|
|
198
|
+
const serializedLatestStore = new DataStoreSerializer_1.DataStoreSerializer(latestStore, latestHeight, latestHash).serialize();
|
|
199
|
+
console.log(`Monitor: Caching new store info for storeId: ${storeId}`);
|
|
200
|
+
this.storeCoinCache.set(storeId, {
|
|
201
|
+
latestStore: serializedLatestStore,
|
|
202
|
+
latestHeight,
|
|
203
|
+
latestHash: latestHash.toString("hex"),
|
|
204
|
+
});
|
|
205
|
+
// Return the cached store info
|
|
206
|
+
const newStore = this.storeCoinCache.get(storeId);
|
|
207
|
+
if (!newStore) {
|
|
208
|
+
throw new Error(`Failed to cache store info for storeId: ${storeId} after fetching.`);
|
|
209
|
+
}
|
|
210
|
+
return newStore;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Utility method to introduce a delay.
|
|
214
|
+
* @param {number} ms - The delay duration in milliseconds.
|
|
215
|
+
* @returns {Promise<void>} Resolves after the specified delay.
|
|
216
|
+
*/
|
|
217
|
+
async delay(ms) {
|
|
218
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Unregisters a storeId, stopping its monitor.
|
|
222
|
+
* @param {string} storeId - The store identifier to stop monitoring.
|
|
223
|
+
*/
|
|
224
|
+
unregisterStore(storeId) {
|
|
225
|
+
if (this.activeMonitors.has(storeId)) {
|
|
226
|
+
this.activeMonitors.delete(storeId);
|
|
227
|
+
console.log(`Registry: Unregistered monitor for storeId: ${storeId}`);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
console.log(`Registry: No active monitor found for storeId: ${storeId}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Stops all active monitors and clears the registry.
|
|
235
|
+
* Useful for graceful shutdowns.
|
|
236
|
+
*/
|
|
237
|
+
stopAllMonitors() {
|
|
238
|
+
this.activeMonitors.clear();
|
|
239
|
+
console.log(`Registry: All monitors have been stopped.`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
exports.StoreMonitorRegistry = StoreMonitorRegistry;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/blockchain/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAa,gBAAgB,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/blockchain/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAa,gBAAgB,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,wBAAwB,CAAC"}
|
package/dist/blockchain/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export declare class StoreInfoCacheUpdater {
|
|
2
|
-
private static instance;
|
|
3
|
-
private storeCoinCache;
|
|
4
|
-
private monitors;
|
|
5
|
-
private lockFilePath;
|
|
6
|
-
private releaseLock;
|
|
7
|
-
private isMonitoring;
|
|
8
|
-
private lockRenewalInterval;
|
|
9
|
-
private constructor();
|
|
10
|
-
static initInstance(): StoreInfoCacheUpdater;
|
|
11
|
-
private startMonitors;
|
|
12
|
-
private renewLock;
|
|
13
|
-
private monitorStore;
|
|
14
|
-
private isUnrecoverableError;
|
|
15
|
-
}
|
|
16
|
-
//# sourceMappingURL=StoreInfoCacheUpdater.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"StoreInfoCacheUpdater.d.ts","sourceRoot":"","sources":["../../src/blockchain/StoreInfoCacheUpdater.ts"],"names":[],"mappings":"AAcA,qBAAa,qBAAqB;IAChC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAwB;IAC/C,OAAO,CAAC,cAAc,CAIsB;IAC5C,OAAO,CAAC,QAAQ,CAAyC;IACzD,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,WAAW,CAAsC;IACzD,OAAO,CAAC,YAAY,CAAiB;IACrC,OAAO,CAAC,mBAAmB,CAA+B;IAE1D,OAAO;WAiBO,YAAY,IAAI,qBAAqB;YAQrC,aAAa;IA6E3B,OAAO,CAAC,SAAS;YA0BH,YAAY;IAyI1B,OAAO,CAAC,oBAAoB;CAG7B"}
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
-
};
|
|
28
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.StoreInfoCacheUpdater = void 0;
|
|
30
|
-
const fs_1 = __importDefault(require("fs"));
|
|
31
|
-
const FullNodePeer_1 = require("./FullNodePeer");
|
|
32
|
-
const utils_1 = require("../utils");
|
|
33
|
-
const DataStoreSerializer_1 = require("./DataStoreSerializer");
|
|
34
|
-
const utils_2 = require("../utils");
|
|
35
|
-
const lockfile = __importStar(require("proper-lockfile"));
|
|
36
|
-
const path = __importStar(require("path"));
|
|
37
|
-
const datalayer_driver_1 = require("@dignetwork/datalayer-driver");
|
|
38
|
-
class StoreInfoCacheUpdater {
|
|
39
|
-
constructor() {
|
|
40
|
-
this.storeCoinCache = new utils_1.FileCache(`stores`, utils_1.USER_DIR_PATH);
|
|
41
|
-
this.monitors = new Map();
|
|
42
|
-
this.releaseLock = null;
|
|
43
|
-
this.isMonitoring = true;
|
|
44
|
-
this.lockRenewalInterval = null;
|
|
45
|
-
console.log("Constructor: Initializing StoreInfoCacheUpdater");
|
|
46
|
-
// Construct lock file path using the path module
|
|
47
|
-
this.lockFilePath = path.join(utils_1.USER_DIR_PATH, "store-info-cache.lock");
|
|
48
|
-
console.log("Lock file path:", this.lockFilePath);
|
|
49
|
-
const lockDir = path.dirname(this.lockFilePath);
|
|
50
|
-
if (!fs_1.default.existsSync(lockDir)) {
|
|
51
|
-
console.log(`Creating lock directory: ${lockDir}`);
|
|
52
|
-
fs_1.default.mkdirSync(lockDir, { recursive: true });
|
|
53
|
-
}
|
|
54
|
-
// Start monitors for existing storeIds
|
|
55
|
-
this.startMonitors();
|
|
56
|
-
}
|
|
57
|
-
static initInstance() {
|
|
58
|
-
if (!StoreInfoCacheUpdater.instance) {
|
|
59
|
-
console.log("Initializing DataStore Monitor");
|
|
60
|
-
StoreInfoCacheUpdater.instance = new StoreInfoCacheUpdater();
|
|
61
|
-
}
|
|
62
|
-
return StoreInfoCacheUpdater.instance;
|
|
63
|
-
}
|
|
64
|
-
async startMonitors() {
|
|
65
|
-
try {
|
|
66
|
-
console.log("Checking if lockfile exists...");
|
|
67
|
-
// Check if the lock file exists
|
|
68
|
-
if (!fs_1.default.existsSync(this.lockFilePath)) {
|
|
69
|
-
console.log("Lockfile does not exist. Proceeding without lock.");
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
// Check if the lockfile is already held
|
|
73
|
-
const isLocked = await lockfile.check(this.lockFilePath, {
|
|
74
|
-
realpath: false,
|
|
75
|
-
});
|
|
76
|
-
if (isLocked) {
|
|
77
|
-
console.log("Another process is already running the StoreInfoCacheUpdater.");
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
// Attempt to acquire the lock
|
|
82
|
-
console.log("Attempting to acquire lock...");
|
|
83
|
-
this.releaseLock = await lockfile.lock(this.lockFilePath, {
|
|
84
|
-
retries: {
|
|
85
|
-
retries: 0, // No retries since we only need one lock
|
|
86
|
-
},
|
|
87
|
-
stale: 60000, // Lock expires after 1 minute
|
|
88
|
-
realpath: false, // Ensure lockfile uses the exact path
|
|
89
|
-
});
|
|
90
|
-
console.log("Lock acquired, starting monitors...");
|
|
91
|
-
// Renew the lock every minute by reacquiring it
|
|
92
|
-
this.renewLock();
|
|
93
|
-
const storeIds = this.storeCoinCache.getCachedKeys();
|
|
94
|
-
console.log(`Found ${storeIds.length} store IDs in cache:`, storeIds);
|
|
95
|
-
for (const storeId of storeIds) {
|
|
96
|
-
// Check if a monitor is already running for this storeId
|
|
97
|
-
if (!this.monitors.has(storeId)) {
|
|
98
|
-
console.log(`Starting monitor for storeId: ${storeId}`);
|
|
99
|
-
// Start monitoring in the background
|
|
100
|
-
const monitorPromise = this.monitorStore(storeId);
|
|
101
|
-
this.monitors.set(storeId, monitorPromise);
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
console.log(`Monitor already exists for storeId: ${storeId}`);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
this.isMonitoring = true;
|
|
108
|
-
// Wait for all monitors to settle
|
|
109
|
-
const monitorPromises = Array.from(this.monitors.values());
|
|
110
|
-
console.log("Waiting for all monitor promises to settle...");
|
|
111
|
-
await Promise.all(monitorPromises);
|
|
112
|
-
}
|
|
113
|
-
catch (error) {
|
|
114
|
-
console.error("Monitor system encountered an error:", error);
|
|
115
|
-
}
|
|
116
|
-
finally {
|
|
117
|
-
// Release the lock
|
|
118
|
-
if (this.releaseLock) {
|
|
119
|
-
try {
|
|
120
|
-
console.log("Releasing lock...");
|
|
121
|
-
await this.releaseLock();
|
|
122
|
-
console.log("Lock released successfully.");
|
|
123
|
-
}
|
|
124
|
-
catch (releaseError) {
|
|
125
|
-
console.error("Error releasing the lock:", releaseError);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
// Clear the lock renewal interval
|
|
129
|
-
if (this.lockRenewalInterval) {
|
|
130
|
-
clearInterval(this.lockRenewalInterval);
|
|
131
|
-
this.lockRenewalInterval = null;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
renewLock() {
|
|
136
|
-
// Set up a renewal process that releases and reacquires the lock every minute
|
|
137
|
-
this.lockRenewalInterval = setInterval(async () => {
|
|
138
|
-
try {
|
|
139
|
-
if (this.releaseLock) {
|
|
140
|
-
console.log("Releasing the lock for renewal...");
|
|
141
|
-
await this.releaseLock();
|
|
142
|
-
console.log("Lock released for renewal.");
|
|
143
|
-
}
|
|
144
|
-
// Reacquire the lock
|
|
145
|
-
this.releaseLock = await lockfile.lock(this.lockFilePath, {
|
|
146
|
-
retries: {
|
|
147
|
-
retries: 0, // No retries since we only need one lock
|
|
148
|
-
},
|
|
149
|
-
stale: 60000, // Lock expires after 1 minute
|
|
150
|
-
realpath: false, // Ensure lockfile uses the exact path
|
|
151
|
-
});
|
|
152
|
-
console.log("Lock reacquired for renewal.");
|
|
153
|
-
}
|
|
154
|
-
catch (error) {
|
|
155
|
-
console.error("Error renewing the lock:", error);
|
|
156
|
-
}
|
|
157
|
-
}, 60000); // Renew the lock every 60 seconds
|
|
158
|
-
}
|
|
159
|
-
// Monitor a single store's coin
|
|
160
|
-
async monitorStore(storeId) {
|
|
161
|
-
while (this.isMonitoring) {
|
|
162
|
-
let peer = null;
|
|
163
|
-
try {
|
|
164
|
-
console.log(`Monitoring store ${storeId}`);
|
|
165
|
-
// Connect to a peer
|
|
166
|
-
peer = await (0, utils_2.withTimeout)(FullNodePeer_1.FullNodePeer.connect(), 60000, "Timeout connecting to FullNodePeer");
|
|
167
|
-
// Get the latest store info (from cache if available)
|
|
168
|
-
const cachedInfo = this.storeCoinCache.get(storeId);
|
|
169
|
-
if (!cachedInfo) {
|
|
170
|
-
// If no cached info, skip and wait before retrying
|
|
171
|
-
console.error(`No cached info for storeId ${storeId}`);
|
|
172
|
-
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
const { latestStore: serializedStore, latestHeight, latestHash, } = cachedInfo;
|
|
176
|
-
const { latestStore } = DataStoreSerializer_1.DataStoreSerializer.deserialize({
|
|
177
|
-
latestStore: serializedStore,
|
|
178
|
-
latestHeight: latestHeight.toString(),
|
|
179
|
-
latestHash: latestHash,
|
|
180
|
-
});
|
|
181
|
-
// Get the coinId associated with the store
|
|
182
|
-
const coinId = (0, datalayer_driver_1.getCoinId)(latestStore.coin);
|
|
183
|
-
console.log(`Waiting for coin to be spent: ${coinId.toString("hex")}`);
|
|
184
|
-
try {
|
|
185
|
-
// Wait for the coin to be spent
|
|
186
|
-
await peer.waitForCoinToBeSpent(coinId, latestHeight, Buffer.from(latestHash, "hex"));
|
|
187
|
-
}
|
|
188
|
-
catch {
|
|
189
|
-
const genesisChallenge = await (0, datalayer_driver_1.getMainnetGenesisChallenge)();
|
|
190
|
-
const storeInfo = await (0, utils_2.withTimeout)(peer.syncStore(latestStore, null, genesisChallenge, false), 60000, `Timeout syncing store for storeId ${storeId}`);
|
|
191
|
-
const headerHash = await peer.getHeaderHash(storeInfo.latestHeight);
|
|
192
|
-
await peer.waitForCoinToBeSpent((0, datalayer_driver_1.getCoinId)(storeInfo.latestStore.coin), storeInfo.latestHeight, headerHash);
|
|
193
|
-
}
|
|
194
|
-
console.log(`Detected Coin Spend: ${coinId.toString("hex")}`);
|
|
195
|
-
let updatedStore, newHeight;
|
|
196
|
-
try {
|
|
197
|
-
// When resolved, sync the store
|
|
198
|
-
const storeInfo = await (0, utils_2.withTimeout)(peer.syncStore(latestStore, latestHeight, Buffer.from(latestHash, "hex"), false // withHistory
|
|
199
|
-
), 60000, `Timeout syncing store for storeId ${storeId}`);
|
|
200
|
-
updatedStore = storeInfo.latestStore;
|
|
201
|
-
newHeight = storeInfo.latestHeight;
|
|
202
|
-
}
|
|
203
|
-
catch {
|
|
204
|
-
const genesisChallenge = await (0, datalayer_driver_1.getMainnetGenesisChallenge)();
|
|
205
|
-
const storeInfo = await (0, utils_2.withTimeout)(peer.syncStore(latestStore, null, genesisChallenge, false), 60000, `Timeout syncing store for storeId ${storeId}`);
|
|
206
|
-
updatedStore = storeInfo.latestStore;
|
|
207
|
-
newHeight = storeInfo.latestHeight;
|
|
208
|
-
}
|
|
209
|
-
// Get the latest header hash
|
|
210
|
-
const latestHashBuffer = await (0, utils_2.withTimeout)(peer.getHeaderHash(newHeight), 60000, `Timeout getting header hash for height ${newHeight}`);
|
|
211
|
-
// Serialize the updated store data for caching
|
|
212
|
-
const serializedLatestStore = new DataStoreSerializer_1.DataStoreSerializer(updatedStore, newHeight, latestHashBuffer).serialize();
|
|
213
|
-
// Update the cache
|
|
214
|
-
this.storeCoinCache.set(storeId, {
|
|
215
|
-
latestStore: serializedLatestStore,
|
|
216
|
-
latestHeight: newHeight,
|
|
217
|
-
latestHash: latestHashBuffer.toString("hex"),
|
|
218
|
-
});
|
|
219
|
-
peer = null;
|
|
220
|
-
// Continue monitoring
|
|
221
|
-
}
|
|
222
|
-
catch (error) {
|
|
223
|
-
console.error(`Error monitoring store ${storeId}:`, error);
|
|
224
|
-
// Close the peer connection if it's open
|
|
225
|
-
if (peer) {
|
|
226
|
-
peer = null;
|
|
227
|
-
}
|
|
228
|
-
// Determine if the error is unrecoverable
|
|
229
|
-
if (this.isUnrecoverableError(error)) {
|
|
230
|
-
this.isMonitoring = false; // Signal other monitors to stop
|
|
231
|
-
throw error; // Propagate error up to stop monitoring
|
|
232
|
-
}
|
|
233
|
-
// Wait before retrying
|
|
234
|
-
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
isUnrecoverableError(error) {
|
|
239
|
-
return true;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
exports.StoreInfoCacheUpdater = StoreInfoCacheUpdater;
|