@@ -40,6 +40,7 @@ import NodeCache from "node-cache";
40
40
import { MAIN_NET_GENISES_CHALLENGE } from "../utils/config" ;
41
41
import { StoreInfoCacheUpdater } from "./StoreInfoCacheUpdater" ;
42
42
import { Environment } from "../utils" ;
43
+ import { get } from "lodash" ;
43
44
44
45
// Initialize the cache with a TTL of 180 seconds (3 minutes)
45
46
const rootHistoryCache = new NodeCache ( { stdTTL : 180 } ) ;
@@ -50,6 +51,7 @@ const readdir = promisify(fs.readdir);
50
51
export class DataStore {
51
52
private storeId : string ;
52
53
private tree : DataIntegrityTree ;
54
+ private static activeMonitors : Map < string , boolean > = new Map ( ) ;
53
55
54
56
constructor ( storeId : string , options ?: DataIntegrityTreeOptions ) {
55
57
this . storeId = storeId ;
@@ -66,6 +68,10 @@ export class DataStore {
66
68
}
67
69
68
70
this . tree = new DataIntegrityTree ( storeId , _options ) ;
71
+
72
+ if ( ! Environment . CLI_MODE ) {
73
+ DataStore . monitorStoreIndefinitely ( this . storeId ) ;
74
+ }
69
75
}
70
76
71
77
public get StoreId ( ) : string {
@@ -308,6 +314,131 @@ export class DataStore {
308
314
return storIds . map ( ( storeId ) => DataStore . from ( storeId ) ) ;
309
315
}
310
316
317
+ /**
318
+ * Monitors the store indefinitely by syncing it with a peer.
319
+ * Logs store retrieval and caching operations. If an error occurs, logs the error and restarts the process after a short delay.
320
+ * Only one monitor can be active per storeId.
321
+ * @param {string } storeId - The store identifier to monitor.
322
+ * @returns {Promise<void> } Never resolves; runs indefinitely.
323
+ */
324
+ public static async monitorStoreIndefinitely ( storeId : string ) : Promise < void > {
325
+ // Check if a monitor is already running for this storeId
326
+ if ( this . activeMonitors . get ( storeId ) ) {
327
+ console . log ( `Monitor already running for storeId: ${ storeId } ` ) ;
328
+ return ;
329
+ }
330
+
331
+ // Set the monitor as active
332
+ this . activeMonitors . set ( storeId , true ) ;
333
+
334
+ const storeCoinCache = new FileCache < {
335
+ latestStore : ReturnType < DataStoreSerializer [ "serialize" ] > ;
336
+ latestHeight : number ;
337
+ latestHash : string ;
338
+ } > ( `stores` ) ;
339
+
340
+ // Clear the cache at the start
341
+ console . log ( `Clearing cache for storeId: ${ storeId } ` ) ;
342
+ storeCoinCache . delete ( storeId ) ;
343
+
344
+ while ( true ) {
345
+ try {
346
+ console . log ( `Connecting to peer for storeId: ${ storeId } ` ) ;
347
+ const peer = await FullNodePeer . connect ( ) ;
348
+ const cachedInfo = storeCoinCache . get ( storeId ) ;
349
+
350
+ if ( cachedInfo ) {
351
+ // Log cached store info retrieval
352
+ console . log (
353
+ `Cached store info found for storeId: ${ storeId } , syncing...`
354
+ ) ;
355
+
356
+ // Deserialize cached info and wait for the coin to be spent
357
+ const previousStore = DataStoreSerializer . deserialize ( {
358
+ latestStore : cachedInfo . latestStore ,
359
+ latestHeight : cachedInfo . latestHeight . toString ( ) ,
360
+ latestHash : cachedInfo . latestHash ,
361
+ } ) ;
362
+
363
+ console . log (
364
+ `Waiting for coin to be spent for storeId: ${ storeId } ...`
365
+ ) ;
366
+ await peer . waitForCoinToBeSpent (
367
+ getCoinId ( previousStore . latestStore . coin ) ,
368
+ previousStore . latestHeight ,
369
+ previousStore . latestHash
370
+ ) ;
371
+
372
+ // Sync store and get updated details
373
+ console . log ( `Syncing store for storeId: ${ storeId } ` ) ;
374
+ const { latestStore, latestHeight } = await peer . syncStore (
375
+ previousStore . latestStore ,
376
+ previousStore . latestHeight ,
377
+ previousStore . latestHash ,
378
+ false
379
+ ) ;
380
+ const latestHash = await peer . getHeaderHash ( latestHeight ) ;
381
+
382
+ // Serialize and cache the updated store info
383
+ const serializedLatestStore = new DataStoreSerializer (
384
+ latestStore ,
385
+ latestHeight ,
386
+ latestHash
387
+ ) . serialize ( ) ;
388
+
389
+ console . log ( `Caching updated store info for storeId: ${ storeId } ` ) ;
390
+ storeCoinCache . set ( storeId , {
391
+ latestStore : serializedLatestStore ,
392
+ latestHeight,
393
+ latestHash : latestHash . toString ( "hex" ) ,
394
+ } ) ;
395
+
396
+ continue ; // Continue monitoring
397
+ }
398
+
399
+ // If no cached info exists, log and sync from the creation height
400
+ console . log (
401
+ `No cached info found for storeId: ${ storeId } . Retrieving creation height.`
402
+ ) ;
403
+
404
+ const dataStore = DataStore . from ( storeId ) ;
405
+ const { createdAtHeight, createdAtHash } = await dataStore . getCreationHeight ( ) ;
406
+
407
+ // Sync store from the peer using launcher ID
408
+ console . log ( `Syncing store from launcher ID for storeId: ${ storeId } ` ) ;
409
+ const { latestStore, latestHeight } = await peer . syncStoreFromLauncherId (
410
+ Buffer . from ( storeId , "hex" ) ,
411
+ createdAtHeight ,
412
+ createdAtHash ,
413
+ false
414
+ ) ;
415
+
416
+ const latestHash = await peer . getHeaderHash ( latestHeight ) ;
417
+
418
+ // Serialize and cache the new store info
419
+ const serializedLatestStore = new DataStoreSerializer (
420
+ latestStore ,
421
+ latestHeight ,
422
+ latestHash
423
+ ) . serialize ( ) ;
424
+
425
+ console . log ( `Caching new store info for storeId: ${ storeId } ` ) ;
426
+ storeCoinCache . set ( storeId , {
427
+ latestStore : serializedLatestStore ,
428
+ latestHeight,
429
+ latestHash : latestHash . toString ( "hex" ) ,
430
+ } ) ;
431
+ } catch ( error : any ) {
432
+ console . error (
433
+ `Error in monitorStoreIndefinitely for storeId: ${ storeId } - ${ error . message } `
434
+ ) ;
435
+
436
+ // Delay before restarting to avoid rapid retries
437
+ await new Promise ( ( resolve ) => setTimeout ( resolve , 5000 ) ) ;
438
+ }
439
+ }
440
+ }
441
+
311
442
public async fetchCoinInfo ( ) : Promise < {
312
443
latestStore : DataStoreDriver ;
313
444
latestHeight : number ;
0 commit comments