@tradelayerprotocol/tradelayer 1.9.1

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.
Files changed (249) hide show
  1. package/.claude/settings.local.json +13 -0
  2. package/.claude/skills/tl-algo/SKILL.md +255 -0
  3. package/.gitattributes +2 -0
  4. package/.github/workflows/publish.yaml +26 -0
  5. package/4mm.js +163 -0
  6. package/LICENSE +21 -0
  7. package/NPMSwapRefactor.zip +0 -0
  8. package/README.md +217 -0
  9. package/address.sh +26 -0
  10. package/algoAPI.js +581 -0
  11. package/analyzepsbt.js +92 -0
  12. package/apiEx.js +99 -0
  13. package/bb_hyperscalper.js +290 -0
  14. package/bbo_demo.js +111 -0
  15. package/buyer.js +622 -0
  16. package/client.js +50 -0
  17. package/createTxTest.js +26 -0
  18. package/createWallet.js +75 -0
  19. package/daytrader.js +531 -0
  20. package/decodeTest.js +69 -0
  21. package/fundingManager.js +144 -0
  22. package/index.js +4 -0
  23. package/listener.js +27 -0
  24. package/litecoreTxBuilder.js +1128 -0
  25. package/mmEx.js +356 -0
  26. package/networks.js +51 -0
  27. package/orderbook.js +200 -0
  28. package/package.json +34 -0
  29. package/perTradeQueue.js +36 -0
  30. package/projectsTLNPMTLNPM/package-lock.json +162 -0
  31. package/projectsTLNPMTLNPM/package.json +5 -0
  32. package/quick.js +32 -0
  33. package/quickFut.js +37 -0
  34. package/quickSell.js +37 -0
  35. package/relayerClient.js +117 -0
  36. package/run4mm.js +80 -0
  37. package/run_bbo_tracker.js +241 -0
  38. package/seller.js +443 -0
  39. package/session.js +45 -0
  40. package/setup-lin-ltc.sh +139 -0
  41. package/setup-lin.sh +203 -0
  42. package/setup-win-ltc.bat +108 -0
  43. package/setup-win.bat +167 -0
  44. package/spam_screamer_futures.js +222 -0
  45. package/tradelayer.js/.gitattributes +2 -0
  46. package/tradelayer.js/README.md +2 -0
  47. package/tradelayer.js/oldTests/activationTest.js +6 -0
  48. package/tradelayer.js/oldTests/base58.test.js +23 -0
  49. package/tradelayer.js/oldTests/base64Decode.test.js +16 -0
  50. package/tradelayer.js/oldTests/blocksRefactor.js +140 -0
  51. package/tradelayer.js/oldTests/checkVestBalance.js +25 -0
  52. package/tradelayer.js/oldTests/consensusHashProto.js +151 -0
  53. package/tradelayer.js/oldTests/contractOrderbook.js +243 -0
  54. package/tradelayer.js/oldTests/createPayload.js +0 -0
  55. package/tradelayer.js/oldTests/createTestnetAddr.js +43 -0
  56. package/tradelayer.js/oldTests/decode.js +205 -0
  57. package/tradelayer.js/oldTests/decodeTest.js +50 -0
  58. package/tradelayer.js/oldTests/displayTallyMap.js +19 -0
  59. package/tradelayer.js/oldTests/encodeDecode.js +340 -0
  60. package/tradelayer.js/oldTests/expressTest.js +29 -0
  61. package/tradelayer.js/oldTests/extractBlocksVanilla.js +214 -0
  62. package/tradelayer.js/oldTests/extractBlocksVanillaa.js +179 -0
  63. package/tradelayer.js/oldTests/extractPubkeyTest.js +60 -0
  64. package/tradelayer.js/oldTests/fillInputCacheProto.js +111 -0
  65. package/tradelayer.js/oldTests/getRawTxTest.js +22 -0
  66. package/tradelayer.js/oldTests/indexTest.js +26 -0
  67. package/tradelayer.js/oldTests/initTokensTest.js +32 -0
  68. package/tradelayer.js/oldTests/interfaceChild.js +129 -0
  69. package/tradelayer.js/oldTests/listenerChild.js +112 -0
  70. package/tradelayer.js/oldTests/opdecode.js +26 -0
  71. package/tradelayer.js/oldTests/options.js +79 -0
  72. package/tradelayer.js/oldTests/optxtest.js +116 -0
  73. package/tradelayer.js/oldTests/optxtest1.js +64 -0
  74. package/tradelayer.js/oldTests/oracle.test.js +32 -0
  75. package/tradelayer.js/oldTests/orderbook.test.js +36 -0
  76. package/tradelayer.js/oldTests/parsing.js +93 -0
  77. package/tradelayer.js/oldTests/payload.js +13 -0
  78. package/tradelayer.js/oldTests/persistenceUnitTest.js +23 -0
  79. package/tradelayer.js/oldTests/property.test.js +53 -0
  80. package/tradelayer.js/oldTests/propertyLevel.js +75 -0
  81. package/tradelayer.js/oldTests/propertyTest.js +32 -0
  82. package/tradelayer.js/oldTests/queryAddressTest.js +17 -0
  83. package/tradelayer.js/oldTests/salter.js +14 -0
  84. package/tradelayer.js/oldTests/tally.js +81 -0
  85. package/tradelayer.js/oldTests/tally.test.js +48 -0
  86. package/tradelayer.js/oldTests/tally2.js +124 -0
  87. package/tradelayer.js/oldTests/tally3.js +142 -0
  88. package/tradelayer.js/oldTests/tallyDiag.js +38 -0
  89. package/tradelayer.js/oldTests/testGetRaw.js +40 -0
  90. package/tradelayer.js/oldTests/testHexConvert.js +47 -0
  91. package/tradelayer.js/oldTests/testNewEncoding.js +96 -0
  92. package/tradelayer.js/oldTests/testNewEncoding2.js +113 -0
  93. package/tradelayer.js/oldTests/testNewEncoding3 +112 -0
  94. package/tradelayer.js/oldTests/testNewEncoding3.js +168 -0
  95. package/tradelayer.js/oldTests/testOPReturn.js +102 -0
  96. package/tradelayer.js/oldTests/testPayload.js +23 -0
  97. package/tradelayer.js/oldTests/testRaw.js +50 -0
  98. package/tradelayer.js/oldTests/testSendTooMuch.js +20 -0
  99. package/tradelayer.js/oldTests/testTxBuild +28 -0
  100. package/tradelayer.js/oldTests/testTxBuild.js +42 -0
  101. package/tradelayer.js/oldTests/tokenOrderbook.js +243 -0
  102. package/tradelayer.js/oldTests/txUtilsA.js +515 -0
  103. package/tradelayer.js/oldTests/validityUnitTest.js +53 -0
  104. package/tradelayer.js/oldTests/vaults.js +72 -0
  105. package/tradelayer.js/oldTests/volumeIndex.js +117 -0
  106. package/tradelayer.js/oldTests/volumeIndex2.js +88 -0
  107. package/tradelayer.js/output_base64.txt +1 -0
  108. package/tradelayer.js/package-lock.json +9967 -0
  109. package/tradelayer.js/package.json +61 -0
  110. package/tradelayer.js/server/index.js +88 -0
  111. package/tradelayer.js/server/litecoind.exe +0 -0
  112. package/tradelayer.js/src/activation.js +303 -0
  113. package/tradelayer.js/src/adjuster.js +77 -0
  114. package/tradelayer.js/src/amm.js +400 -0
  115. package/tradelayer.js/src/base256.js +55 -0
  116. package/tradelayer.js/src/base94.js +79 -0
  117. package/tradelayer.js/src/channels.js +1163 -0
  118. package/tradelayer.js/src/clearing.js +3109 -0
  119. package/tradelayer.js/src/clearlist.js +364 -0
  120. package/tradelayer.js/src/client.js +295 -0
  121. package/tradelayer.js/src/consensus.js +613 -0
  122. package/tradelayer.js/src/contractRegistry.js +964 -0
  123. package/tradelayer.js/src/db.js +89 -0
  124. package/tradelayer.js/src/init.js +24 -0
  125. package/tradelayer.js/src/insurance.js +347 -0
  126. package/tradelayer.js/src/interface.js +218 -0
  127. package/tradelayer.js/src/interfaceExpress.js +178 -0
  128. package/tradelayer.js/src/iou.js +509 -0
  129. package/tradelayer.js/src/listener.js +226 -0
  130. package/tradelayer.js/src/logic.js +1702 -0
  131. package/tradelayer.js/src/main.js +927 -0
  132. package/tradelayer.js/src/marginMap.js +2165 -0
  133. package/tradelayer.js/src/options.js +126 -0
  134. package/tradelayer.js/src/oracle.js +394 -0
  135. package/tradelayer.js/src/orderbook.js +4123 -0
  136. package/tradelayer.js/src/persistence.js +554 -0
  137. package/tradelayer.js/src/property.js +411 -0
  138. package/tradelayer.js/src/reOrg.js +41 -0
  139. package/tradelayer.js/src/scaling.js +145 -0
  140. package/tradelayer.js/src/tally.js +1275 -0
  141. package/tradelayer.js/src/tradeHistoryManager.js +552 -0
  142. package/tradelayer.js/src/txDecoder.js +584 -0
  143. package/tradelayer.js/src/txEncoder.js +610 -0
  144. package/tradelayer.js/src/txIndex.js +502 -0
  145. package/tradelayer.js/src/txUtils.js +1392 -0
  146. package/tradelayer.js/src/types.js +429 -0
  147. package/tradelayer.js/src/validity.js +3077 -0
  148. package/tradelayer.js/src/vaults.js +430 -0
  149. package/tradelayer.js/src/vesting.js +491 -0
  150. package/tradelayer.js/src/volumeIndex.js +618 -0
  151. package/tradelayer.js/src/walletInterface.js +220 -0
  152. package/tradelayer.js/src/walletListener.js +665 -0
  153. package/tradelayer.js/tests/256decode.js +82 -0
  154. package/tradelayer.js/tests/UTXOracle.js +205 -0
  155. package/tradelayer.js/tests/base94test.js +23 -0
  156. package/tradelayer.js/tests/cancelTxTest.js +62 -0
  157. package/tradelayer.js/tests/contractInterfaceTest.js +48 -0
  158. package/tradelayer.js/tests/decimalTest.js +65 -0
  159. package/tradelayer.js/tests/decoderTest.js +100 -0
  160. package/tradelayer.js/tests/deltaCount.js +47 -0
  161. package/tradelayer.js/tests/deltaCount2.js +60 -0
  162. package/tradelayer.js/tests/interfaceTest.js +37 -0
  163. package/tradelayer.js/tests/mainTest.js +53 -0
  164. package/tradelayer.js/tests/makeActivationTest.js +24 -0
  165. package/tradelayer.js/tests/maxHeightTest.js +49 -0
  166. package/tradelayer.js/tests/reverseHash.js +72 -0
  167. package/tradelayer.js/tests/sensitiveConsoleOutput.txt +267 -0
  168. package/tradelayer.js/tests/tallyTest.js +40 -0
  169. package/tradelayer.js/tests/testBuybacks.js +46 -0
  170. package/tradelayer.js/tests/testCodeHash.js +49 -0
  171. package/tradelayer.js/tests/testConsensusHash.js +91 -0
  172. package/tradelayer.js/tests/testDecode.js +30 -0
  173. package/tradelayer.js/tests/testEncodingLengths.js +129 -0
  174. package/tradelayer.js/tests/testGetTx +32 -0
  175. package/tradelayer.js/tests/testGetTx.js +32 -0
  176. package/tradelayer.js/tests/testHexHash.js +32 -0
  177. package/tradelayer.js/tests/testIndexHash.js +35 -0
  178. package/tradelayer.js/tests/testInitContracts.js +38 -0
  179. package/tradelayer.js/tests/testMaxConsensus.js +12 -0
  180. package/tradelayer.js/tests/testMaxSynth.js +44 -0
  181. package/tradelayer.js/tests/testMint.js +21 -0
  182. package/tradelayer.js/tests/testNetwork.js +33 -0
  183. package/tradelayer.js/tests/testOrderbookLoad.js +62 -0
  184. package/tradelayer.js/tests/testRebates.js +32 -0
  185. package/tradelayer.js/tests/testRedeem.js +22 -0
  186. package/tradelayer.js/tests/testTokenTrade.js +39 -0
  187. package/tradelayer.js/tests/testTxBuild.js +42 -0
  188. package/tradelayer.js/tests/testUTXOTrade.js +27 -0
  189. package/tradelayer.js/tests/tokenTradeHistory.js +27 -0
  190. package/tradelayer.js/tests/tradeFutures.js +40 -0
  191. package/tradelayer.js/tests/tradeHistoryExample.js +35 -0
  192. package/tradelayer.js/tests/tradeHistoryLoad.js +15 -0
  193. package/tradelayer.js/tests/txScanTest.js +134 -0
  194. package/tradelayer.js/tests/validateTest.js +136 -0
  195. package/tradelayer.js/tests/vestingTest.js +37 -0
  196. package/tradelayer.js/utils/activateMainnet.js +59 -0
  197. package/tradelayer.js/utils/activateMainnetDoge.js +63 -0
  198. package/tradelayer.js/utils/autocompactdb.js +23 -0
  199. package/tradelayer.js/utils/base64toHex.js +32 -0
  200. package/tradelayer.js/utils/broadcastDoge.js +38 -0
  201. package/tradelayer.js/utils/calcRedeem.js +19 -0
  202. package/tradelayer.js/utils/checkNetwork.js +27 -0
  203. package/tradelayer.js/utils/createAddress.js +48 -0
  204. package/tradelayer.js/utils/createAttestation.js +133 -0
  205. package/tradelayer.js/utils/createContract.js +118 -0
  206. package/tradelayer.js/utils/createOracle.js +94 -0
  207. package/tradelayer.js/utils/createwallet.js +20 -0
  208. package/tradelayer.js/utils/crossFuturesTrades.js +57 -0
  209. package/tradelayer.js/utils/crossTokenTrades.js +62 -0
  210. package/tradelayer.js/utils/dumpPriv.js +29 -0
  211. package/tradelayer.js/utils/generateChannel.js +34 -0
  212. package/tradelayer.js/utils/getInfo.js +21 -0
  213. package/tradelayer.js/utils/hardWipe.js +20 -0
  214. package/tradelayer.js/utils/hexTo64.js +16 -0
  215. package/tradelayer.js/utils/importAddress.js +28 -0
  216. package/tradelayer.js/utils/importpriv.js +20 -0
  217. package/tradelayer.js/utils/issueOracleContract.js +67 -0
  218. package/tradelayer.js/utils/issueTokens.js +41 -0
  219. package/tradelayer.js/utils/listunspent.js +66 -0
  220. package/tradelayer.js/utils/litecoinClient.js +30 -0
  221. package/tradelayer.js/utils/loadwallet.js +20 -0
  222. package/tradelayer.js/utils/publishOracle.js +113 -0
  223. package/tradelayer.js/utils/sendActivation.js +21 -0
  224. package/tradelayer.js/utils/sendChannelContractTrade.js +34 -0
  225. package/tradelayer.js/utils/sendChannelTokenTrade.js +34 -0
  226. package/tradelayer.js/utils/sendCommit.js +24 -0
  227. package/tradelayer.js/utils/sendDoge.js +62 -0
  228. package/tradelayer.js/utils/sendDogeMain.js +67 -0
  229. package/tradelayer.js/utils/sendDogeTx.js +46 -0
  230. package/tradelayer.js/utils/sendLTC.js +63 -0
  231. package/tradelayer.js/utils/sendMainnet.js +62 -0
  232. package/tradelayer.js/utils/sendTransfer.js +19 -0
  233. package/tradelayer.js/utils/sendVestTest.js +88 -0
  234. package/tradelayer.js/utils/sendWithdrawal.js +26 -0
  235. package/tradelayer.js/utils/simpleStart.js +8 -0
  236. package/tradelayer.js/utils/startStop.js +27 -0
  237. package/tradelayer.js/utils/structuredTrades.js +136 -0
  238. package/tradelayer.js/utils/verifySignature.js +90 -0
  239. package/tradelayer.js/utils/verifyWitnessAndScriptPubkey.js +41 -0
  240. package/tradelayer.js/utils/walletCache.js +172 -0
  241. package/tradelayer.js/utils/walletContractInterface.js +48 -0
  242. package/tradelayer.js/utils/walletFetchTxs.js +66 -0
  243. package/tradelayer.js/utils/walletUtils.js +97 -0
  244. package/tradelayer.js/utils/wipeDB.js +55 -0
  245. package/tradelayer.js/utils/wipeDBNotTx.js +50 -0
  246. package/txEncoder.js +529 -0
  247. package/utility.js +28 -0
  248. package/verifymessage.js +38 -0
  249. package/ws-transport.js +311 -0
@@ -0,0 +1,554 @@
1
+ // persistence.js — Final Production-Ready Version
2
+ // ---------------------------------------------------------------
3
+ // • API identical to your previous version (getInstance + auto-init)
4
+ // • Option 1 network handling ("ltc" + test→"ltc-test")
5
+ // • Safe snapshots, safe restores, Windows-compatible
6
+ // • No nested backup folders inside dbDir
7
+ //---------------------------------------------------------------
8
+
9
+ const fs = require("fs");
10
+ const path = require("path");
11
+ const db = require("./db.js");
12
+ const ClientWrapper = require("./client.js");
13
+ const ConsensusDatabase = require("./consensus.js");
14
+
15
+ // small async sleep helper
16
+ const delay = ms => new Promise(res => setTimeout(res, ms));
17
+
18
+ class Persistence {
19
+ static instance = null;
20
+
21
+ //-------------------------------------------------------
22
+ // STATIC GET INSTANCE (Main calls this!)
23
+ //-------------------------------------------------------
24
+ static async getInstance(options = {}) {
25
+ if (!Persistence.instance) {
26
+ Persistence.instance = new Persistence(options);
27
+ await Persistence.instance.init(options);
28
+ }
29
+ return Persistence.instance;
30
+ }
31
+
32
+ //-------------------------------------------------------
33
+ // CONSTRUCTOR (does NOT auto-init)
34
+ //-------------------------------------------------------
35
+ constructor(options = {}) {
36
+ if (Persistence.instance) return Persistence.instance;
37
+
38
+ // store raw fields (network/test may not be valid yet)
39
+ this.rawNetwork = options.network || null;
40
+ this.rawTestFlag = typeof options.test === "boolean" ? options.test : null;
41
+ this.snapshotInterval =
42
+ typeof options.snapshotInterval === "number"
43
+ ? options.snapshotInterval
44
+ : 1000;
45
+
46
+ // directories will be set after network detection
47
+ this.networkFull = null;
48
+ this.dbDir = null;
49
+ this.snapshotsDir = null;
50
+ this.backupsDir = null;
51
+
52
+ this.client = null;
53
+
54
+ Persistence.instance = this;
55
+ }
56
+
57
+ //-------------------------------------------------------
58
+ // INIT — auto-called by getInstance
59
+ //-------------------------------------------------------
60
+ async init(options = {}) {
61
+ // we need RPC client before determining network if missing
62
+ if (!this.client) {
63
+ this.client = await ClientWrapper.getInstance(true);
64
+ }
65
+
66
+ // compute network name
67
+ let chain = this.rawNetwork;
68
+ let isTest = this.rawTestFlag;
69
+
70
+ if (!chain) {
71
+ chain = await this.client.getChain(); // "ltc" or "btc"
72
+ }
73
+ if (isTest === null) {
74
+ isTest = await this.client.getTest(); // boolean
75
+ }
76
+
77
+ chain = chain.toLowerCase().trim();
78
+ this.networkFull = `${chain}-${isTest ? "test" : "main"}`;
79
+
80
+ const baseDir = path.join(__dirname, "..", "nedb-data");
81
+
82
+ this.dbDir = path.join(baseDir, this.networkFull);
83
+ this.snapshotsDir = path.join(baseDir, `${this.networkFull}-snapshots`);
84
+ this.backupsDir = path.join(baseDir, `${this.networkFull}-backups`);
85
+
86
+ await fs.promises.mkdir(this.dbDir, { recursive: true });
87
+ await fs.promises.mkdir(this.snapshotsDir, { recursive: true });
88
+ await fs.promises.mkdir(this.backupsDir, { recursive: true });
89
+
90
+ return this;
91
+ }
92
+
93
+ //-------------------------------------------------------
94
+ // RECORD & FETCH BLOCK HEADERS
95
+ //-------------------------------------------------------
96
+ async recordBlockHeader(height, hash, prevHash) {
97
+ const base = await db.getDatabase("persistence");
98
+ await base.updateAsync(
99
+ { _id: `block-${height}` },
100
+ {
101
+ $set: {
102
+ height,
103
+ hash,
104
+ prevHash,
105
+ createdAt: Date.now(),
106
+ },
107
+ },
108
+ { upsert: true }
109
+ );
110
+ }
111
+
112
+ async getBlockHeader(height) {
113
+ const base = await db.getDatabase("persistence");
114
+ return base.findOneAsync({ _id: `block-${height}` });
115
+ }
116
+
117
+ async getLastKnownBlock() {
118
+ const base = await db.getDatabase("persistence");
119
+ const docs = await base.findAsync({ height: { $exists: true } });
120
+ if (!docs || docs.length === 0) return null;
121
+ docs.sort((a, b) => a.height - b.height);
122
+ return docs[docs.length - 1];
123
+ }
124
+
125
+ /**
126
+ * Hybrid reorg detection:
127
+ * 1. Fast path: compare incoming prevhash to our stored blockhash for (h-1)
128
+ * 2. If mismatch → confirm via RPC (canonical)
129
+ * 3. Only if both disagree → actual reorg
130
+ *
131
+ * Returns:
132
+ * false → no reorg
133
+ * true → confirmed reorg
134
+ */
135
+ async checkForReorgForNewBlock(blockHeight, incomingPrevHash) {
136
+ try {
137
+ const prevHeight = blockHeight - 1;
138
+ if (prevHeight <= 0) return false; // genesis cannot reorg
139
+
140
+ const persistenceDB = await db.getDatabase("persistence");
141
+
142
+ // -----------------------------------------
143
+ // FAST PATH: LOCAL HASH CHAIN CHECK
144
+ // -----------------------------------------
145
+ const prevDoc = await persistenceDB.findOneAsync({ height: prevHeight });
146
+ let localPrevHash = null;
147
+
148
+ if (prevDoc && prevDoc.hash) {
149
+ localPrevHash = prevDoc.hash;
150
+
151
+ // If hashes match → definitely no reorg
152
+ if (localPrevHash === incomingPrevHash) {
153
+ return false;
154
+ }
155
+
156
+ // If mismatch → MAYBE reorg, confirm via RPC
157
+ console.log(
158
+ `[reorg?] Local mismatch at height=${blockHeight}. ` +
159
+ `localPrev=${localPrevHash}, incomingPrev=${incomingPrevHash}. Confirming via RPC...`
160
+ );
161
+ } else {
162
+ // No local previous hash — could be startup or partial DB
163
+ // Must confirm with RPC
164
+ console.log(
165
+ `[reorg?] No local metadata at height=${prevHeight}. Checking RPC for canonical chain...`
166
+ );
167
+ }
168
+
169
+ let nodePrevHash;
170
+ try {
171
+ const rpcPrevHash = await this.client.getBlockHash(prevHeight);
172
+ const rpcPrevBlock = await this.client.getBlock(rpcPrevHash);
173
+ nodePrevHash = rpcPrevBlock.hash;
174
+ } catch (e) {
175
+ logger.warn("[reorg] RPC failure during reorg check", e);
176
+ return true //{ reorg: true, depth: 1, uncertain: true };
177
+ }
178
+
179
+ if (nodePrevHash !== localPrevHash) {
180
+ return {
181
+ reorg: true,
182
+ depth: 1,
183
+ expected: nodePrevHash,
184
+ received: localPrevHash
185
+ };
186
+ }
187
+
188
+ // Now compare canonical vs incoming
189
+ if (nodePrevHash !== incomingPrevHash) {
190
+ console.log(
191
+ `[reorg CONFIRMED] height=${blockHeight}. ` +
192
+ `nodePrev=${nodePrevHash}, incomingPrev=${incomingPrevHash}`
193
+ );
194
+ return true;
195
+ }
196
+
197
+ // RPC confirms no reorg → local mismatch was stale or snapshot-induced
198
+ console.log(
199
+ `[reorg false-positive resolved] Local mismatch at height=${blockHeight} ` +
200
+ `but RPC chain matches incoming block. No reorg.`
201
+ );
202
+ return false;
203
+
204
+ } catch (err) {
205
+ console.error('[persistence] Error in hybrid reorg detection:', err);
206
+ // safer to assume no reorg in case of RPC failure — do not reorg-loop
207
+ return false;
208
+ }
209
+ }
210
+
211
+
212
+ //-------------------------------------------------------
213
+ // REORG CHECK FOR NEW BLOCK (top-of-chain equality removed)
214
+ //-------------------------------------------------------
215
+ async detectAndHandleReorg() {
216
+ const last = await this.getLastKnownBlock();
217
+ if (!last) return null;
218
+
219
+ const nodeHash = await this.client.getBlockHash(last.height);
220
+
221
+ if (nodeHash === last.hash) return null;
222
+
223
+ console.warn(
224
+ `Reorg suspected: local(${last.height})=${last.hash}, node=${nodeHash}`
225
+ );
226
+
227
+ //---------------------------------------------------
228
+ // Find common ancestor (still useful for debugging)
229
+ //---------------------------------------------------
230
+ let h = last.height;
231
+ let ancestor = 0;
232
+
233
+ while (h > 0) {
234
+ const local = await this.getBlockHeader(h);
235
+ const node = await this.client.getBlockHash(h);
236
+ if (local && local.hash === node) {
237
+ ancestor = h;
238
+ break;
239
+ }
240
+ h--;
241
+ }
242
+
243
+ //---------------------------------------------------
244
+ // Choose a snapshot — prefer the one closest BELOW last.height
245
+ //---------------------------------------------------
246
+ const snaps = await this.listSnapshots();
247
+ if (!snaps.length) {
248
+ console.warn("No snapshots available — full replay needed");
249
+ return {
250
+ restoredFrom: 0,
251
+ commonAncestor: ancestor,
252
+ lastLocal: last.height,
253
+ };
254
+ }
255
+
256
+ // pick the highest snapshot <= last.height
257
+ let best = null;
258
+ for (const s of snaps) {
259
+ if (s.blockHeight <= last.height) {
260
+ if (!best || s.blockHeight > best.blockHeight) best = s;
261
+ }
262
+ }
263
+ if (!best) {
264
+ best = snaps[0];
265
+ }
266
+
267
+ await this.restoreSnapshot(best);
268
+
269
+ return {
270
+ restoredFrom: best.blockHeight,
271
+ commonAncestor: ancestor,
272
+ lastLocal: last.height,
273
+ };
274
+ }
275
+
276
+ //-------------------------------------------------------
277
+ // CHECKPOINT (called by Main)
278
+ //-------------------------------------------------------
279
+ async maybeCheckpoint(blockHeight) {
280
+ if (this.snapshotInterval <= 0) return;
281
+ if (blockHeight % this.snapshotInterval !== 0) return;
282
+
283
+ // Compute consensus state hash
284
+ const consensusHash = await ConsensusDatabase.stateConsensusHash();
285
+
286
+ // Store hash keyed by height
287
+ await ConsensusDatabase.storeConsensusHash(blockHeight, consensusHash);
288
+
289
+ // Create snapshot (EBUSY-safe)
290
+ await this.createSnapshot(blockHeight, consensusHash);
291
+ }
292
+
293
+
294
+ //-------------------------------------------------------
295
+ // SNAPSHOT CREATION (EBUSY-safe)
296
+ //-------------------------------------------------------
297
+ async createSnapshot(height, consensusHash) {
298
+ if (this.snapshotInterval <= 0) return;
299
+
300
+ const short = (consensusHash || "nohash").slice(0, 12);
301
+ const dirName = `${height}-${short}`;
302
+
303
+ const snapPath = path.join(this.snapshotsDir, dirName);
304
+ await fs.promises.mkdir(snapPath, { recursive: true });
305
+
306
+ const files = await fs.promises.readdir(this.dbDir);
307
+ const copied = [];
308
+
309
+ for (const file of files) {
310
+ if (!file.endsWith(".db")) continue;
311
+ const src = path.join(this.dbDir, file);
312
+ const dst = path.join(snapPath, file);
313
+ await this.safeCopy(src, dst);
314
+ copied.push(file);
315
+ }
316
+
317
+ await fs.promises.writeFile(
318
+ path.join(snapPath, "meta.json"),
319
+ JSON.stringify(
320
+ {
321
+ blockHeight: height,
322
+ consensusHash,
323
+ createdAt: new Date().toISOString(),
324
+ files: copied,
325
+ },
326
+ null,
327
+ 2
328
+ )
329
+ );
330
+
331
+ await this.pruneOldSnapshots(2);
332
+ await this.updateMaxProcessed(height)
333
+ await this.cleanupPersistence(height)
334
+ console.log(`Snapshot created at ${height} (${dirName})`);
335
+ }
336
+
337
+ /**
338
+ * Delete all persistence entries older than the checkpointHeight.
339
+ */
340
+ async cleanupPersistence(checkpointHeight) {
341
+ const persistenceDB = await db.getDatabase("persistence");
342
+ let height = checkpointHeight-1000
343
+ // delete everything older than the new checkpoint
344
+ await persistenceDB.removeAsync(
345
+ { height: { $lt: height } },
346
+ { multi: true }
347
+ );
348
+ console.log(`[persistence] Cleaned entries < ${checkpointHeight}`);
349
+ }
350
+
351
+ async updateMaxProcessed(height) {
352
+ const consensusDB = await db.getDatabase('consensus'); // Access the consensus sub-database
353
+
354
+ try {
355
+ // Upsert the document with _id = 'MaxProcessedHeight'
356
+ await consensusDB.updateAsync(
357
+ { _id: 'MaxProcessedHeight' },
358
+ { $set: { value: height } },
359
+ { upsert: true }
360
+ );
361
+
362
+ //console.log('MaxProcessedHeight updated:', height);
363
+ return height;
364
+ } catch (error) {
365
+ console.error('Error updating MaxProcessedHeight:', error);
366
+ throw error;
367
+ }
368
+ }
369
+
370
+
371
+ //-------------------------------------------------------
372
+ // RESTORE SNAPSHOT (EBUSY-SAFE)
373
+ //-------------------------------------------------------
374
+ async restoreSnapshot(meta) {
375
+ const dir = path.join(this.snapshotsDir, meta.dir || `${meta.blockHeight}`);
376
+
377
+ // -----------------------------------------------------------------
378
+ // PRUNE OLD BACKUPS: keep only the most recent backup-* directory
379
+ // -----------------------------------------------------------------
380
+ try {
381
+ const backupEntries = await fs.promises.readdir(this.backupsDir);
382
+ const backups = [];
383
+
384
+ for (const name of backupEntries) {
385
+ const fullPath = path.join(this.backupsDir, name);
386
+ const stat = await fs.promises.stat(fullPath);
387
+
388
+ // only consider directories that look like "backup-<something>"
389
+ if (!stat.isDirectory()) continue;
390
+ if (!name.startsWith('backup-')) continue;
391
+
392
+ backups.push({
393
+ name,
394
+ fullPath,
395
+ mtime: stat.mtimeMs,
396
+ });
397
+ }
398
+
399
+ if (backups.length > 1) {
400
+ // sort by newest first
401
+ backups.sort((a, b) => b.mtime - a.mtime);
402
+
403
+ // keep the newest one, delete all others
404
+ for (let i = 1; i < backups.length; i++) {
405
+ try {
406
+ await fs.promises.rm(backups[i].fullPath, {
407
+ recursive: true,
408
+ force: true,
409
+ });
410
+ } catch (e) {
411
+ console.error(
412
+ '[persistence] Error removing old backup dir:',
413
+ backups[i].fullPath,
414
+ e
415
+ );
416
+ }
417
+ }
418
+ }
419
+ } catch (e) {
420
+ console.error('[persistence] Error pruning backup dirs:', e);
421
+ // non-fatal: continue restoring snapshot
422
+ }
423
+
424
+ // -------------------------------------------------
425
+ // CREATE NEW BACKUP OF CURRENT DB STATE
426
+ // -------------------------------------------------
427
+ const backupDir = path.join(
428
+ this.backupsDir,
429
+ `backup-${Date.now()}`
430
+ );
431
+ await fs.promises.mkdir(backupDir, { recursive: true });
432
+
433
+ const curr = await fs.promises.readdir(this.dbDir);
434
+ for (const f of curr) {
435
+ const src = path.join(this.dbDir, f);
436
+ const dst = path.join(backupDir, f);
437
+ await this.safeCopy(src, dst);
438
+ }
439
+
440
+ // clear current dbDir of ONLY .db files
441
+ for (const f of curr) {
442
+ if (!f.endsWith(".db")) continue;
443
+ const p = path.join(this.dbDir, f);
444
+ await this.safeDelete(p);
445
+ }
446
+
447
+ // restore
448
+ for (const f of meta.files) {
449
+ const src = path.join(dir, f);
450
+ const dst = path.join(this.dbDir, f);
451
+ await this.safeCopy(src, dst);
452
+ }
453
+
454
+ this.saveTrackHeight(meta.blockHeight)
455
+
456
+ console.log(
457
+ `Restored DB from snapshot ${meta.dir} (block=${meta.blockHeight})`
458
+ );
459
+
460
+ }
461
+
462
+ async saveTrackHeight(saveHeight){
463
+ const base = await db.getDatabase('consensus')
464
+ await base.updateAsync(
465
+ { _id: 'TrackHeight' },
466
+ { $set: { value: saveHeight } },
467
+ { upsert: true }
468
+ )
469
+ }
470
+
471
+
472
+
473
+ //-------------------------------------------------------
474
+ // LIST + PRUNE SNAPSHOTS
475
+ //-------------------------------------------------------
476
+ async listSnapshots() {
477
+ try {
478
+ const entries = await fs.promises.readdir(this.snapshotsDir, {
479
+ withFileTypes: true,
480
+ });
481
+ const dirs = entries.filter(e => e.isDirectory()).map(d => d.name);
482
+
483
+ const snaps = [];
484
+ for (const dir of dirs) {
485
+ try {
486
+ const meta = JSON.parse(
487
+ await fs.promises.readFile(
488
+ path.join(this.snapshotsDir, dir, "meta.json"),
489
+ "utf8"
490
+ )
491
+ );
492
+ meta.dir = dir;
493
+ snaps.push(meta);
494
+ } catch (e) {
495
+ console.warn(`Invalid snapshot meta in ${dir}`);
496
+ }
497
+ }
498
+
499
+ snaps.sort((a, b) => a.blockHeight - b.blockHeight);
500
+ return snaps;
501
+ } catch (e) {
502
+ return [];
503
+ }
504
+ }
505
+
506
+ async pruneOldSnapshots(keep = 2) {
507
+ const snaps = await this.listSnapshots();
508
+ if (snaps.length <= keep) return;
509
+
510
+ const toDelete = snaps.slice(0, snaps.length - keep);
511
+ for (const s of toDelete) {
512
+ const p = path.join(this.snapshotsDir, s.dir);
513
+ await fs.promises.rm(p, { recursive: true, force: true });
514
+ }
515
+ }
516
+
517
+ //-------------------------------------------------------
518
+ // EBUSY-SAFE COPY + DELETE
519
+ //-------------------------------------------------------
520
+ async safeCopy(src, dst) {
521
+ for (let i = 0; i < 5; i++) {
522
+ try {
523
+ await fs.promises.copyFile(src, dst);
524
+ return;
525
+ } catch (e) {
526
+ if (e.code === "EBUSY" || e.code === "EPERM") {
527
+ await delay(50);
528
+ continue;
529
+ }
530
+ throw e;
531
+ }
532
+ }
533
+ throw new Error(`safeCopy failed: ${src} → ${dst}`);
534
+ }
535
+
536
+ async safeDelete(p) {
537
+ for (let i = 0; i < 5; i++) {
538
+ try {
539
+ await fs.promises.unlink(p);
540
+ return;
541
+ } catch (e) {
542
+ if (e.code === "EBUSY" || e.code === "EPERM") {
543
+ await delay(50);
544
+ continue;
545
+ }
546
+ if (e.code === "ENOENT") return;
547
+ throw e;
548
+ }
549
+ }
550
+ throw new Error(`safeDelete failed: ${p}`);
551
+ }
552
+ }
553
+
554
+ module.exports = Persistence;