@dignetwork/dig-sdk 0.0.1-alpha.12 → 0.0.1-alpha.121
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 +11 -2
- package/dist/DataIntegrityTree/DataIntegrityTree.d.ts.map +1 -1
- package/dist/DataIntegrityTree/DataIntegrityTree.js +106 -15
- package/dist/DigNetwork/ContentServer.d.ts +12 -5
- package/dist/DigNetwork/ContentServer.d.ts.map +1 -1
- package/dist/DigNetwork/ContentServer.js +172 -24
- package/dist/DigNetwork/DigNetwork.d.ts +3 -9
- package/dist/DigNetwork/DigNetwork.d.ts.map +1 -1
- package/dist/DigNetwork/DigNetwork.js +98 -189
- package/dist/DigNetwork/DigPeer.d.ts +8 -3
- package/dist/DigNetwork/DigPeer.d.ts.map +1 -1
- package/dist/DigNetwork/DigPeer.js +68 -115
- package/dist/DigNetwork/IncentiveServer.d.ts.map +1 -1
- package/dist/DigNetwork/IncentiveServer.js +6 -5
- package/dist/DigNetwork/PropagationServer.d.ts +73 -33
- package/dist/DigNetwork/PropagationServer.d.ts.map +1 -1
- package/dist/DigNetwork/PropagationServer.js +545 -360
- package/dist/blockchain/DataStore.d.ts +27 -7
- package/dist/blockchain/DataStore.d.ts.map +1 -1
- package/dist/blockchain/DataStore.js +163 -101
- package/dist/blockchain/DataStoreSerializer.d.ts +1 -1
- package/dist/blockchain/DataStoreSerializer.d.ts.map +1 -1
- package/dist/blockchain/FullNodePeer.d.ts +2 -1
- package/dist/blockchain/FullNodePeer.d.ts.map +1 -1
- package/dist/blockchain/FullNodePeer.js +39 -14
- package/dist/blockchain/ServerCoin.d.ts +13 -5
- package/dist/blockchain/ServerCoin.d.ts.map +1 -1
- package/dist/blockchain/ServerCoin.js +90 -28
- package/dist/blockchain/StoreInfoCacheUpdater.d.ts +16 -0
- package/dist/blockchain/StoreInfoCacheUpdater.d.ts.map +1 -0
- package/dist/blockchain/StoreInfoCacheUpdater.js +242 -0
- package/dist/blockchain/Wallet.d.ts +2 -2
- package/dist/blockchain/Wallet.d.ts.map +1 -1
- package/dist/blockchain/Wallet.js +50 -18
- package/dist/blockchain/coins.d.ts +1 -2
- package/dist/blockchain/coins.d.ts.map +1 -1
- package/dist/blockchain/coins.js +1 -55
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/ContentScanner.d.ts +63 -0
- package/dist/utils/ContentScanner.d.ts.map +1 -0
- package/dist/utils/ContentScanner.js +175 -0
- package/dist/utils/Environment.d.ts +21 -0
- package/dist/utils/Environment.d.ts.map +1 -0
- package/dist/utils/Environment.js +92 -0
- package/dist/utils/FileCache.d.ts +1 -1
- package/dist/utils/FileCache.d.ts.map +1 -1
- package/dist/utils/FileCache.js +2 -2
- 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/NconfManager.js +2 -1
- 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 +4 -3
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +12 -7
- package/dist/utils/directoryUtils.d.ts.map +1 -1
- package/dist/utils/directoryUtils.js +32 -14
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/merkle.d.ts +2 -0
- package/dist/utils/merkle.d.ts.map +1 -0
- package/dist/utils/merkle.js +28 -0
- package/dist/utils/network.d.ts +1 -0
- package/dist/utils/network.d.ts.map +1 -1
- package/dist/utils/network.js +38 -11
- package/dist/utils/promiseUtils.d.ts +17 -0
- package/dist/utils/promiseUtils.d.ts.map +1 -0
- package/dist/utils/promiseUtils.js +38 -0
- package/dist/utils/ssl.js +2 -2
- package/package.json +15 -5
- package/dist/utils/deltaUtils.d.ts +0 -2
- package/dist/utils/deltaUtils.d.ts.map +0 -1
- package/dist/utils/deltaUtils.js +0 -83
|
@@ -1,21 +1,29 @@
|
|
|
1
|
-
import { ServerCoin as ServerCoinDriver } from "datalayer-driver";
|
|
1
|
+
import { ServerCoin as ServerCoinDriver } from "@dignetwork/datalayer-driver";
|
|
2
2
|
import { NconfManager } from "../utils/NconfManager";
|
|
3
3
|
export declare class ServerCoin {
|
|
4
4
|
private storeId;
|
|
5
5
|
static readonly serverCoinManager: NconfManager;
|
|
6
6
|
constructor(storeId: string);
|
|
7
|
-
createForEpoch(peerIp: string): Promise<ServerCoinDriver>;
|
|
8
|
-
saveServerCoinData(serverCoin: ServerCoinDriver, epoch: number, peerIp: string): Promise<void>;
|
|
7
|
+
createForEpoch(peerIp: string, rootHash: string): Promise<ServerCoinDriver>;
|
|
8
|
+
saveServerCoinData(serverCoin: ServerCoinDriver, epoch: number, peerIp: string, rootHash: string): Promise<void>;
|
|
9
9
|
melt(epoch: number, peerIp: string): Promise<void>;
|
|
10
10
|
private removeServerCoinData;
|
|
11
|
+
getAllEpochPeers(epoch: number, blacklist?: string[]): Promise<string[]>;
|
|
12
|
+
getActiveEpochPeers(blacklist?: string[]): Promise<string[]>;
|
|
11
13
|
sampleCurrentEpoch(sampleSize?: number, blacklist?: string[]): Promise<string[]>;
|
|
12
14
|
sampleServerCoinsByEpoch(epoch: number, sampleSize?: number, blacklist?: string[]): Promise<string[]>;
|
|
13
|
-
static getCurrentEpoch():
|
|
15
|
+
static getCurrentEpoch(): {
|
|
16
|
+
epoch: number;
|
|
17
|
+
round: number;
|
|
18
|
+
};
|
|
14
19
|
ensureServerCoinExists(peerIp: string): Promise<void>;
|
|
15
20
|
meltOutdatedEpochs(peerIp: string): Promise<void>;
|
|
16
21
|
static meltUntrackedStoreCoins(): Promise<void>;
|
|
17
22
|
private getServerCoinsForStore;
|
|
18
23
|
hasEpochCoinBeenCreated(currentEpoch: number, peerIp: string): Promise<boolean>;
|
|
19
|
-
static
|
|
24
|
+
static calculateEpochAndRound(currentTimestampUTC: Date): {
|
|
25
|
+
epoch: number;
|
|
26
|
+
round: number;
|
|
27
|
+
};
|
|
20
28
|
}
|
|
21
29
|
//# sourceMappingURL=ServerCoin.d.ts.map
|
|
@@ -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,
|
|
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;AAGtC,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAYrD,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAS;IACxB,gBAAuB,iBAAiB,eAEtC;gBAEU,OAAO,EAAE,MAAM;IAKd,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA4D3E,kBAAkB,CAC7B,UAAU,EAAE,gBAAgB,EAC5B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAsBH,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;IAoDP,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;WAeN,eAAe,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;IAKpD,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiFrD,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"}
|
|
@@ -5,27 +5,31 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.ServerCoin = void 0;
|
|
7
7
|
const lodash_1 = __importDefault(require("lodash"));
|
|
8
|
-
const datalayer_driver_1 = require("datalayer-driver");
|
|
8
|
+
const datalayer_driver_1 = require("@dignetwork/datalayer-driver");
|
|
9
9
|
const FullNodePeer_1 = require("./FullNodePeer");
|
|
10
|
-
const coins_1 = require("./coins");
|
|
11
10
|
const Wallet_1 = require("./Wallet");
|
|
12
11
|
const NconfManager_1 = require("../utils/NconfManager");
|
|
13
12
|
const DataStore_1 = require("./DataStore");
|
|
13
|
+
const node_cache_1 = __importDefault(require("node-cache"));
|
|
14
|
+
const network_1 = require("../utils/network");
|
|
15
|
+
const Environment_1 = require("../utils/Environment");
|
|
14
16
|
const serverCoinCollateral = 300000000;
|
|
17
|
+
// Initialize the cache with a TTL of 300 seconds (5 minutes)
|
|
18
|
+
const serverCoinPeersCache = new node_cache_1.default({ stdTTL: 300 });
|
|
15
19
|
class ServerCoin {
|
|
16
20
|
constructor(storeId) {
|
|
17
21
|
this.storeId = storeId;
|
|
18
22
|
}
|
|
19
23
|
// Create a new server coin for the current epoch
|
|
20
|
-
async createForEpoch(peerIp) {
|
|
24
|
+
async createForEpoch(peerIp, rootHash) {
|
|
21
25
|
try {
|
|
22
26
|
const peer = await FullNodePeer_1.FullNodePeer.connect();
|
|
23
27
|
const wallet = await Wallet_1.Wallet.load("default");
|
|
24
28
|
const publicSyntheticKey = await wallet.getPublicSyntheticKey();
|
|
25
|
-
const serverCoinCreationCoins = await
|
|
26
|
-
const currentEpoch = ServerCoin.getCurrentEpoch();
|
|
29
|
+
const serverCoinCreationCoins = await wallet.selectUnspentCoins(peer, BigInt(serverCoinCollateral), BigInt(1000000));
|
|
30
|
+
const { epoch: currentEpoch } = ServerCoin.getCurrentEpoch();
|
|
27
31
|
const epochBasedHint = (0, datalayer_driver_1.morphLauncherId)(Buffer.from(this.storeId, "hex"), BigInt(currentEpoch));
|
|
28
|
-
const newServerCoin = (0, datalayer_driver_1.createServerCoin)(publicSyntheticKey, serverCoinCreationCoins, epochBasedHint, [peerIp], BigInt(serverCoinCollateral), BigInt(1000000));
|
|
32
|
+
const newServerCoin = (0, datalayer_driver_1.createServerCoin)(publicSyntheticKey, serverCoinCreationCoins, epochBasedHint, [peerIp, publicSyntheticKey.toString("hex")], BigInt(serverCoinCollateral), BigInt(1000000));
|
|
29
33
|
const combinedCoinSpends = [...newServerCoin.coinSpends];
|
|
30
34
|
const sig = (0, datalayer_driver_1.signCoinSpends)(combinedCoinSpends, [await wallet.getPrivateSyntheticKey()], false);
|
|
31
35
|
const err = await peer.broadcastSpend(combinedCoinSpends, [sig]);
|
|
@@ -33,12 +37,12 @@ class ServerCoin {
|
|
|
33
37
|
if (err.includes("no spendable coins")) {
|
|
34
38
|
console.log("No coins available. Will try again in 5 seconds...");
|
|
35
39
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
36
|
-
return this.createForEpoch(peerIp);
|
|
40
|
+
return this.createForEpoch(peerIp, rootHash);
|
|
37
41
|
}
|
|
38
42
|
throw new Error(err);
|
|
39
43
|
}
|
|
40
44
|
// Cache the new server coin in the NconfManager
|
|
41
|
-
await this.saveServerCoinData(newServerCoin.serverCoin, currentEpoch, peerIp);
|
|
45
|
+
await this.saveServerCoinData(newServerCoin.serverCoin, currentEpoch, peerIp, rootHash);
|
|
42
46
|
return newServerCoin.serverCoin;
|
|
43
47
|
}
|
|
44
48
|
catch (error) {
|
|
@@ -46,7 +50,7 @@ class ServerCoin {
|
|
|
46
50
|
}
|
|
47
51
|
}
|
|
48
52
|
// Save the server coin data to NconfManager
|
|
49
|
-
async saveServerCoinData(serverCoin, epoch, peerIp) {
|
|
53
|
+
async saveServerCoinData(serverCoin, epoch, peerIp, rootHash) {
|
|
50
54
|
const newServerCoinData = {
|
|
51
55
|
coin: {
|
|
52
56
|
amount: serverCoin.coin.amount.toString(),
|
|
@@ -54,7 +58,8 @@ class ServerCoin {
|
|
|
54
58
|
parentCoinInfo: serverCoin.coin.parentCoinInfo.toString("hex"),
|
|
55
59
|
},
|
|
56
60
|
createdAt: new Date().toISOString(),
|
|
57
|
-
epoch
|
|
61
|
+
epoch,
|
|
62
|
+
rootHash,
|
|
58
63
|
};
|
|
59
64
|
const serverCoins = await this.getServerCoinsForStore(peerIp);
|
|
60
65
|
serverCoins.push(newServerCoinData);
|
|
@@ -70,7 +75,7 @@ class ServerCoin {
|
|
|
70
75
|
if (!serverCoin) {
|
|
71
76
|
throw new Error(`No server coin found for epoch ${epoch} in store ${this.storeId}.`);
|
|
72
77
|
}
|
|
73
|
-
const feeCoins = await
|
|
78
|
+
const feeCoins = await wallet.selectUnspentCoins(peer, BigInt(0), BigInt(1000000));
|
|
74
79
|
const coin = {
|
|
75
80
|
amount: BigInt(serverCoin.coin.amount),
|
|
76
81
|
puzzleHash: Buffer.from(serverCoin.coin.puzzleHash, "hex"),
|
|
@@ -94,13 +99,13 @@ class ServerCoin {
|
|
|
94
99
|
serverCoins = serverCoins.filter((coin) => coin.coin.parentCoinInfo !== serverCoin.parentCoinInfo);
|
|
95
100
|
await ServerCoin.serverCoinManager.setConfigValue(`${this.storeId}:${peerIp}`, serverCoins);
|
|
96
101
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
async getAllEpochPeers(epoch, blacklist = []) {
|
|
103
|
+
const cacheKey = `serverCoinPeers-${this.storeId}-${epoch}`;
|
|
104
|
+
// Check if the result is already cached
|
|
105
|
+
const cachedPeers = serverCoinPeersCache.get(cacheKey);
|
|
106
|
+
if (cachedPeers) {
|
|
107
|
+
return cachedPeers;
|
|
108
|
+
}
|
|
104
109
|
const epochBasedHint = (0, datalayer_driver_1.morphLauncherId)(Buffer.from(this.storeId, "hex"), BigInt(epoch));
|
|
105
110
|
const peer = await FullNodePeer_1.FullNodePeer.connect();
|
|
106
111
|
const maxClvmCost = BigInt(11000000000);
|
|
@@ -111,33 +116,83 @@ class ServerCoin {
|
|
|
111
116
|
for (const coinState of filteredCoinStates) {
|
|
112
117
|
const serverCoin = await peer.fetchServerCoin(coinState, maxClvmCost);
|
|
113
118
|
const peerUrl = serverCoin.memoUrls[0];
|
|
119
|
+
// The second memo URL is the public key
|
|
120
|
+
// We will utilize this for future features
|
|
121
|
+
const publicKey = serverCoin.memoUrls[1];
|
|
114
122
|
if (!blacklist.includes(peerUrl)) {
|
|
115
123
|
serverCoinPeers.add(peerUrl);
|
|
116
124
|
}
|
|
117
125
|
}
|
|
118
|
-
if (
|
|
126
|
+
if (Environment_1.Environment.DEBUG) {
|
|
127
|
+
console.log("Server Coin Peers: ", serverCoinPeers);
|
|
128
|
+
}
|
|
129
|
+
const peerList = Array.from(serverCoinPeers);
|
|
130
|
+
// Cache the result
|
|
131
|
+
serverCoinPeersCache.set(cacheKey, peerList);
|
|
132
|
+
return peerList;
|
|
133
|
+
}
|
|
134
|
+
async getActiveEpochPeers(blacklist = []) {
|
|
135
|
+
const { epoch } = ServerCoin.getCurrentEpoch();
|
|
136
|
+
return this.getAllEpochPeers(epoch, blacklist);
|
|
137
|
+
}
|
|
138
|
+
// Sample server coins for the current epoch
|
|
139
|
+
async sampleCurrentEpoch(sampleSize = 5, blacklist = []) {
|
|
140
|
+
const { epoch } = ServerCoin.getCurrentEpoch();
|
|
141
|
+
return this.sampleServerCoinsByEpoch(epoch, sampleSize, blacklist);
|
|
142
|
+
}
|
|
143
|
+
// Sample server coins by epoch
|
|
144
|
+
async sampleServerCoinsByEpoch(epoch, sampleSize = 5, blacklist = []) {
|
|
145
|
+
// We dont want our own IP to be included
|
|
146
|
+
const myIp = await (0, network_1.getPublicIpAddress)();
|
|
147
|
+
if (myIp) {
|
|
148
|
+
blacklist.push(myIp);
|
|
149
|
+
}
|
|
150
|
+
const serverCoinPeers = await this.getAllEpochPeers(epoch, blacklist);
|
|
151
|
+
if (Environment_1.Environment.DEBUG) {
|
|
119
152
|
console.log("Server Coin Peers: ", serverCoinPeers);
|
|
120
153
|
}
|
|
121
|
-
|
|
122
|
-
return lodash_1.default.sampleSize(Array.from(serverCoinPeers), sampleSize);
|
|
154
|
+
return lodash_1.default.sampleSize(serverCoinPeers, sampleSize);
|
|
123
155
|
}
|
|
124
156
|
// Get the current epoch based on the current timestamp
|
|
125
157
|
static getCurrentEpoch() {
|
|
126
|
-
return ServerCoin.
|
|
158
|
+
return ServerCoin.calculateEpochAndRound(new Date());
|
|
127
159
|
}
|
|
128
160
|
// Ensure server coin exists for the current epoch
|
|
129
161
|
async ensureServerCoinExists(peerIp) {
|
|
130
162
|
try {
|
|
131
163
|
console.log(`Ensuring server coin exists for store ${this.storeId}...`);
|
|
132
|
-
const currentEpoch = ServerCoin.getCurrentEpoch();
|
|
164
|
+
const { epoch: currentEpoch } = ServerCoin.getCurrentEpoch();
|
|
133
165
|
const serverCoins = await this.getServerCoinsForStore(peerIp);
|
|
134
166
|
// Check if a server coin already exists for the current epoch
|
|
135
167
|
const existingCoin = serverCoins.find((coin) => coin.epoch === currentEpoch);
|
|
168
|
+
const dataStore = DataStore_1.DataStore.from(this.storeId);
|
|
169
|
+
const rootHistory = await dataStore.getRootHistory(true);
|
|
170
|
+
const lastRoot = lodash_1.default.last(rootHistory);
|
|
171
|
+
if (!lastRoot) {
|
|
172
|
+
throw new Error('Cant get the last root on chain, something is wrong.');
|
|
173
|
+
}
|
|
136
174
|
if (existingCoin) {
|
|
175
|
+
// nothing to do
|
|
176
|
+
if (lastRoot?.synced && lastRoot?.root_hash === existingCoin.rootHash) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
// everything is fine, lets just update the roothash for tracking
|
|
180
|
+
if (lastRoot?.synced && lastRoot?.root_hash !== existingCoin.rootHash) {
|
|
181
|
+
existingCoin.rootHash = lastRoot.root_hash;
|
|
182
|
+
// Update the conf with the modified server coin
|
|
183
|
+
await ServerCoin.serverCoinManager.setConfigValue(`${this.storeId}:${peerIp}`, serverCoins);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// If not synced, melt the coin, a new one will be created when synced up
|
|
187
|
+
// this helps prevent penalties for not having a valid peer registered
|
|
188
|
+
await this.melt(currentEpoch, peerIp);
|
|
189
|
+
}
|
|
190
|
+
// Don't create a server coin until you're synced up
|
|
191
|
+
if (!lodash_1.default.last(rootHistory)?.synced) {
|
|
137
192
|
return;
|
|
138
193
|
}
|
|
139
194
|
console.log(`No server coin found for epoch ${currentEpoch}. Creating new server coin...`);
|
|
140
|
-
const serverCoin = await this.createForEpoch(peerIp);
|
|
195
|
+
const serverCoin = await this.createForEpoch(peerIp, lastRoot?.root_hash);
|
|
141
196
|
const newServerCoinData = {
|
|
142
197
|
coin: {
|
|
143
198
|
amount: serverCoin.coin.amount.toString(),
|
|
@@ -146,6 +201,7 @@ class ServerCoin {
|
|
|
146
201
|
},
|
|
147
202
|
createdAt: new Date().toISOString(),
|
|
148
203
|
epoch: currentEpoch,
|
|
204
|
+
rootHash: lodash_1.default.last(rootHistory)?.root_hash || "",
|
|
149
205
|
};
|
|
150
206
|
await FullNodePeer_1.FullNodePeer.waitForConfirmation(serverCoin.coin.parentCoinInfo);
|
|
151
207
|
serverCoins.push(newServerCoinData);
|
|
@@ -160,7 +216,7 @@ class ServerCoin {
|
|
|
160
216
|
// Melt outdated server coins
|
|
161
217
|
async meltOutdatedEpochs(peerIp) {
|
|
162
218
|
try {
|
|
163
|
-
const currentEpoch = ServerCoin.getCurrentEpoch();
|
|
219
|
+
const { epoch: currentEpoch } = ServerCoin.getCurrentEpoch();
|
|
164
220
|
let serverCoins = await this.getServerCoinsForStore(peerIp);
|
|
165
221
|
// Filter out coins that are not in the current epoch
|
|
166
222
|
const outdatedCoins = serverCoins.filter((coin) => coin.epoch < currentEpoch);
|
|
@@ -244,17 +300,23 @@ class ServerCoin {
|
|
|
244
300
|
}
|
|
245
301
|
}
|
|
246
302
|
// Static method to calculate the current epoch
|
|
247
|
-
static
|
|
303
|
+
static calculateEpochAndRound(currentTimestampUTC) {
|
|
248
304
|
const firstEpochStart = new Date(Date.UTC(2024, 8, 3, 0, 0)); // Sept 3, 2024, 00:00 UTC
|
|
249
305
|
// Convert the current timestamp to milliseconds
|
|
250
306
|
const currentTimestampMillis = currentTimestampUTC.getTime();
|
|
251
307
|
// Calculate the number of milliseconds in one epoch (7 days)
|
|
252
|
-
const millisecondsInEpoch = 7 * 24 * 60 * 60 * 1000;
|
|
308
|
+
const millisecondsInEpoch = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds
|
|
253
309
|
// Calculate the difference in milliseconds between the current timestamp and the first epoch start
|
|
254
310
|
const differenceMillis = currentTimestampMillis - firstEpochStart.getTime();
|
|
255
311
|
// Calculate the current epoch number
|
|
256
312
|
const epochNumber = Math.floor(differenceMillis / millisecondsInEpoch) + 1;
|
|
257
|
-
|
|
313
|
+
// Calculate the milliseconds elapsed since the start of the current epoch
|
|
314
|
+
const elapsedMillisInCurrentEpoch = differenceMillis % millisecondsInEpoch;
|
|
315
|
+
// Calculate the number of milliseconds in a round (10 minutes)
|
|
316
|
+
const millisecondsInRound = 10 * 60 * 1000; // 10 minutes in milliseconds
|
|
317
|
+
// Calculate the current round number
|
|
318
|
+
const roundNumber = Math.floor(elapsedMillisInCurrentEpoch / millisecondsInRound) + 1;
|
|
319
|
+
return { epoch: epochNumber, round: roundNumber };
|
|
258
320
|
}
|
|
259
321
|
}
|
|
260
322
|
exports.ServerCoin = ServerCoin;
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,242 @@
|
|
|
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;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Buffer } from "buffer";
|
|
2
|
-
import { Peer, Coin, CoinSpend } from "datalayer-driver";
|
|
2
|
+
import { Peer, Coin, CoinSpend } from "@dignetwork/datalayer-driver";
|
|
3
3
|
export declare const DEFAULT_FEE_COIN_COST = 64000000;
|
|
4
4
|
export declare class Wallet {
|
|
5
5
|
private mnemonic;
|
|
@@ -20,7 +20,7 @@ export declare class Wallet {
|
|
|
20
20
|
private static getWalletFromKeyring;
|
|
21
21
|
private static saveWalletToKeyring;
|
|
22
22
|
createKeyOwnershipSignature(nonce: string): Promise<string>;
|
|
23
|
-
verifyKeyOwnershipSignature(nonce: string, signature: string, publicKey: string): Promise<boolean>;
|
|
23
|
+
static verifyKeyOwnershipSignature(nonce: string, signature: string, publicKey: string): Promise<boolean>;
|
|
24
24
|
selectUnspentCoins(peer: Peer, coinAmount: bigint, feeBigInt: bigint, omitCoins?: Coin[]): Promise<Coin[]>;
|
|
25
25
|
static calculateFeeForCoinSpends(peer: Peer, coinSpends: CoinSpend[] | null): Promise<bigint>;
|
|
26
26
|
static isCoinSpendable(peer: Peer, coinId: Buffer): Promise<boolean>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Wallet.d.ts","sourceRoot":"","sources":["../../src/blockchain/Wallet.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EASL,IAAI,EACJ,IAAI,EAEJ,SAAS,EAEV,MAAM,
|
|
1
|
+
{"version":3,"file":"Wallet.d.ts","sourceRoot":"","sources":["../../src/blockchain/Wallet.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EASL,IAAI,EACJ,IAAI,EAEJ,SAAS,EAEV,MAAM,8BAA8B,CAAC;AAQtC,eAAO,MAAM,qBAAqB,WAAa,CAAC;AAOhD,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,UAAU,CAAS;IAE3B,OAAO;WAKa,IAAI,CACtB,UAAU,GAAE,MAAkB,EAC9B,iBAAiB,GAAE,OAAc,GAChC,OAAO,CAAC,MAAM,CAAC;IAsBX,WAAW,IAAI,MAAM;WAOR,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;WAMpD,YAAY,CAC9B,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC;WASE,oBAAoB,CACtC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC;IA6BL,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC;IAKrC,qBAAqB,IAAI,OAAO,CAAC,MAAM,CAAC;IAMxC,sBAAsB,IAAI,OAAO,CAAC,MAAM,CAAC;IAKzC,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC;IAMrC,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;WAK7B,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;WASlD,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;mBAU/B,oBAAoB;mBAcpB,mBAAmB;IAS3B,2BAA2B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;WAUpD,2BAA2B,CAC7C,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC;IASN,kBAAkB,CAC7B,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,SAAS,GAAE,IAAI,EAAO,GACrB,OAAO,CAAC,IAAI,EAAE,CAAC;WAiFE,yBAAyB,CAC3C,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,SAAS,EAAE,GAAG,IAAI,GAC7B,OAAO,CAAC,MAAM,CAAC;WAIE,eAAe,CACjC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC;CAWpB"}
|