@spirobel/monero-wallet-api 0.5.3 → 0.5.5
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/README.md +6 -0
- package/dist/api.d.ts +1 -1
- package/dist/api.js +1 -1
- package/dist/io/logging.d.ts +6 -6
- package/dist/io/logging.js +4 -3
- package/dist/io/sleep.d.ts +1 -1
- package/dist/io/sleep.js +16 -1
- package/dist/multisig/escrow.js +0 -1
- package/dist/scanning-syncing/backgroundWorker.d.ts +2 -1
- package/dist/scanning-syncing/backgroundWorker.js +104 -34
- package/dist/scanning-syncing/blocksbuffer/blocksBufferFetchLoop.js +33 -2
- package/dist/scanning-syncing/connectionStatus.d.ts +6 -0
- package/dist/scanning-syncing/connectionStatus.js +15 -0
- package/dist/scanning-syncing/connectionStatusOpened.d.ts +3 -1
- package/dist/scanning-syncing/connectionStatusOpened.js +22 -5
- package/dist/scanning-syncing/openWallet.d.ts +4 -1
- package/dist/scanning-syncing/openWallet.js +3 -0
- package/dist/scanning-syncing/scanresult/scanCacheOpened.d.ts +12 -4
- package/dist/scanning-syncing/scanresult/scanCacheOpened.js +188 -107
- package/dist/scanning-syncing/scanresult/scanCoordination.d.ts +6 -25
- package/dist/scanning-syncing/scanresult/scanCoordination.js +89 -209
- package/dist/scanning-syncing/scanresult/scanLoop.d.ts +1 -2
- package/dist/scanning-syncing/scanresult/scanLoop.js +0 -13
- package/dist/scanning-syncing/worker-entrypoints/worker.js +1 -1
- package/dist/scanning-syncing/worker-mains/coordinator-main.d.ts +1 -0
- package/dist/scanning-syncing/worker-mains/coordinator-main.js +16 -2
- package/dist/scanning-syncing/worker-mains/cpubound-main.d.ts +5 -2
- package/dist/scanning-syncing/worker-mains/cpubound-main.js +10 -8
- package/dist/scanning-syncing/worker-mains/worker.js +50 -2
- package/dist/scansettings/scanSettingsOpened.js +3 -6
- package/dist/tools/monero-tools.js +1 -1
- package/dist/viewpair/ViewPair.js +9 -4
- package/dist/wasm-processing/wasmFile.js +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NodeUrl, signTransaction, ViewPair, } from "../../api";
|
|
1
|
+
import { get_info, NodeUrl, signTransaction, ViewPair, } from "../../api";
|
|
2
2
|
import { prepareInput, sumPayments, } from "../../send-functionality/inputSelection";
|
|
3
3
|
import { createWebworker } from "../backgroundWorker";
|
|
4
4
|
import { spendable } from "./scanResult";
|
|
@@ -37,11 +37,13 @@ export class ScanCacheOpened {
|
|
|
37
37
|
/** sample sizes to try when decoyRetry is enabled, in order */
|
|
38
38
|
decoyRetrySizes = [20, 50, 100, 200, 500];
|
|
39
39
|
static async create(params) {
|
|
40
|
-
|
|
40
|
+
// same dir as workers / connection status: next to ScanSettings.json
|
|
41
|
+
const pathPrefix = getPathPrefix(params.scan_settings_path, params.pathPrefix);
|
|
42
|
+
const theCatchToBeOpened = await readCacheFileDefaultLocation(params.primary_address, pathPrefix);
|
|
41
43
|
if (!params.primary_address)
|
|
42
44
|
throw new Error(`primary_address is required, potentially half filled out wallet setting in: ${params.scan_settings_path || SCAN_SETTINGS_STORE_NAME_DEFAULT}`);
|
|
43
45
|
// use ScanSettingsOpened instead of direct scanSettings calls
|
|
44
|
-
const scanSettings = await ScanSettingsOpened.create(params.scan_settings_path,
|
|
46
|
+
const scanSettings = await ScanSettingsOpened.create(params.scan_settings_path, pathPrefix);
|
|
45
47
|
const walletSettings = scanSettings.getWallet(params.primary_address);
|
|
46
48
|
if (!walletSettings)
|
|
47
49
|
throw new Error(`wallet not found in settings. did you call openwallet with the right params?
|
|
@@ -55,7 +57,7 @@ export class ScanCacheOpened {
|
|
|
55
57
|
// read secret_view_key and secret_spend_key from env
|
|
56
58
|
const walletSettingsWithKeys = await walletSettingsPlusKeys(walletWithSettings);
|
|
57
59
|
// create viewpair + ScanCacheOpened instance
|
|
58
|
-
const scanCacheOpen = new ScanCacheOpened(scanSettings, await ViewPair.create(params.primary_address, walletSettingsWithKeys.secret_view_key, walletSettings.subaddress_index, scanSettings.node_url), walletSettings.wallet_route, walletSettings.wallet_name, walletSettings.wallet_slot, params.no_worker || false, params.masterCacheChanged || null, scanSettings.start_height, params.scan_settings_path,
|
|
60
|
+
const scanCacheOpen = new ScanCacheOpened(scanSettings, await ViewPair.create(params.primary_address, walletSettingsWithKeys.secret_view_key, walletSettings.subaddress_index, scanSettings.node_url), walletSettings.wallet_route, walletSettings.wallet_name, walletSettings.wallet_slot, params.no_worker || false, params.masterCacheChanged || null, scanSettings.start_height, params.scan_settings_path, pathPrefix, params.workerError);
|
|
59
61
|
if (theCatchToBeOpened)
|
|
60
62
|
scanCacheOpen._cache = theCatchToBeOpened;
|
|
61
63
|
if (!walletSettings.halted) {
|
|
@@ -66,7 +68,7 @@ export class ScanCacheOpened {
|
|
|
66
68
|
scanCacheOpen._highest_subaddress_index =
|
|
67
69
|
walletSettings.subaddress_index || SUB_ADDRESS_INDEX_DEFAULT_VALUE;
|
|
68
70
|
if (!params.no_stats) {
|
|
69
|
-
scanCacheOpen._stats = await alignScanStatsWithCache(scanCacheOpen._cache, scanCacheOpen.view_pair, params.primary_address,
|
|
71
|
+
scanCacheOpen._stats = await alignScanStatsWithCache(scanCacheOpen._cache, scanCacheOpen.view_pair, params.primary_address, pathPrefix, walletSettings.subaddress_index, lastRange(scanCacheOpen._cache.scanned_ranges)?.end);
|
|
70
72
|
}
|
|
71
73
|
else {
|
|
72
74
|
scanCacheOpen._no_stats = params.no_stats; // true
|
|
@@ -83,8 +85,10 @@ export class ScanCacheOpened {
|
|
|
83
85
|
return this._highest_subaddress_index;
|
|
84
86
|
}
|
|
85
87
|
get current_height() {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
+
// progress on the range that covers start_height (start can jump;
|
|
89
|
+
// lastRange would pick a different segment and be wrong)
|
|
90
|
+
const current_range = findRange(this._cache.scanned_ranges, this._start_height || 0);
|
|
91
|
+
return current_range == null ? null : current_range.end;
|
|
88
92
|
}
|
|
89
93
|
get current_top_range_height() {
|
|
90
94
|
if (typeof this._stats === "undefined" || this._stats === null)
|
|
@@ -92,10 +96,7 @@ export class ScanCacheOpened {
|
|
|
92
96
|
return this._stats.height;
|
|
93
97
|
}
|
|
94
98
|
async changeStartHeight(start_height) {
|
|
95
|
-
|
|
96
|
-
this.worker.terminate();
|
|
97
|
-
delete this.worker;
|
|
98
|
-
}
|
|
99
|
+
await this.stopWorker();
|
|
99
100
|
await this._scanSettings.setStartHeight(start_height);
|
|
100
101
|
this._start_height = start_height;
|
|
101
102
|
await this.unpause();
|
|
@@ -170,10 +171,7 @@ export class ScanCacheOpened {
|
|
|
170
171
|
return this._scanSettings.logs_exclude;
|
|
171
172
|
}
|
|
172
173
|
async changeNodeUrlAndStartHeight(node_url, start_height) {
|
|
173
|
-
|
|
174
|
-
this.worker.terminate();
|
|
175
|
-
delete this.worker;
|
|
176
|
-
}
|
|
174
|
+
await this.stopWorker();
|
|
177
175
|
if (node_url !== undefined) {
|
|
178
176
|
await this._scanSettings.setNodeUrl(node_url);
|
|
179
177
|
this.node_url = node_url;
|
|
@@ -185,26 +183,21 @@ export class ScanCacheOpened {
|
|
|
185
183
|
await this.unpause();
|
|
186
184
|
}
|
|
187
185
|
async changeNodeUrl(node_url) {
|
|
188
|
-
|
|
189
|
-
this.worker.terminate();
|
|
190
|
-
delete this.worker;
|
|
191
|
-
}
|
|
186
|
+
await this.stopWorker();
|
|
192
187
|
await this._scanSettings.setNodeUrl(node_url);
|
|
193
188
|
this.node_url = node_url;
|
|
194
189
|
await this.unpause();
|
|
195
190
|
}
|
|
196
191
|
async setMerchantConfirmations(merchant_confirmations) {
|
|
197
|
-
this.stopWorker();
|
|
198
192
|
await this._scanSettings.setMerchantConfirmations(merchant_confirmations);
|
|
199
|
-
await this.unpause();
|
|
200
193
|
}
|
|
201
194
|
async setCpuWorkerCount(cpu_worker_count) {
|
|
202
|
-
this.stopWorker();
|
|
195
|
+
await this.stopWorker();
|
|
203
196
|
await this._scanSettings.setCpuWorkerCount(cpu_worker_count);
|
|
204
197
|
await this.unpause();
|
|
205
198
|
}
|
|
206
199
|
async setLogSettings(logs, logs_include, logs_exclude) {
|
|
207
|
-
this.stopWorker();
|
|
200
|
+
await this.stopWorker();
|
|
208
201
|
await this._scanSettings.setLogSettings(logs, logs_include, logs_exclude);
|
|
209
202
|
await this.unpause();
|
|
210
203
|
}
|
|
@@ -212,10 +205,7 @@ export class ScanCacheOpened {
|
|
|
212
205
|
await this._scanSettings.setWalletName(this.view_pair.primary_address, name);
|
|
213
206
|
}
|
|
214
207
|
async retry() {
|
|
215
|
-
|
|
216
|
-
this.worker.terminate();
|
|
217
|
-
delete this.worker;
|
|
218
|
-
}
|
|
208
|
+
await this.stopWorker();
|
|
219
209
|
// scansettings so external changes (e.g. from a sidebar frontend instance) are picked up
|
|
220
210
|
await this._scanSettings.reload();
|
|
221
211
|
const walletStillExists = this._scanSettings.walletExists(this.primary_address);
|
|
@@ -247,7 +237,7 @@ export class ScanCacheOpened {
|
|
|
247
237
|
const feeFor10kb = feePerByte * 10000n;
|
|
248
238
|
//2. check if fee is too high
|
|
249
239
|
if (feeFor10kb > max_plausible_fee) {
|
|
250
|
-
throw new Error(`fee too high:
|
|
240
|
+
throw new Error(`fee too high:
|
|
251
241
|
${feeFor10kb} (fee for 10kb tx size) > ${max_plausible_fee} (0.001 XMR)
|
|
252
242
|
most likely your node is faulty. connect to another node.
|
|
253
243
|
preferably run one yourself locally.`);
|
|
@@ -266,7 +256,7 @@ export class ScanCacheOpened {
|
|
|
266
256
|
const feeFor10kb = feePerByte * 10000n;
|
|
267
257
|
//2. check if fee is too high
|
|
268
258
|
if (feeFor10kb > max_plausible_fee) {
|
|
269
|
-
throw new Error(`fee too high:
|
|
259
|
+
throw new Error(`fee too high:
|
|
270
260
|
${feeFor10kb} (fee for 10kb tx size) > ${max_plausible_fee} (0.001 XMR)
|
|
271
261
|
most likely your node is faulty. connect to another node.
|
|
272
262
|
preferably run one yourself locally.`);
|
|
@@ -410,10 +400,7 @@ export class ScanCacheOpened {
|
|
|
410
400
|
if (sendResult.status !== "OK")
|
|
411
401
|
throw new Error("send raw transaction rpc returned error");
|
|
412
402
|
// before writing the scan cache, we stop the worker to avoid a race
|
|
413
|
-
|
|
414
|
-
this.worker.terminate();
|
|
415
|
-
delete this.worker;
|
|
416
|
-
}
|
|
403
|
+
await this.stopWorker();
|
|
417
404
|
// write txlog to cache + update pending_spent_utxos (affects stats + spendability)
|
|
418
405
|
await writeCacheFileDefaultLocationThrows({
|
|
419
406
|
primary_address: this.primary_address,
|
|
@@ -457,10 +444,7 @@ export class ScanCacheOpened {
|
|
|
457
444
|
}
|
|
458
445
|
catch (e) {
|
|
459
446
|
// before writing the scan cache, we stop the worker to avoid a race
|
|
460
|
-
|
|
461
|
-
this.worker.terminate();
|
|
462
|
-
delete this.worker;
|
|
463
|
-
}
|
|
447
|
+
await this.stopWorker();
|
|
464
448
|
// write txlog error to cache
|
|
465
449
|
await writeCacheFileDefaultLocationThrows({
|
|
466
450
|
primary_address: this.primary_address,
|
|
@@ -570,43 +554,65 @@ export class ScanCacheOpened {
|
|
|
570
554
|
};
|
|
571
555
|
}
|
|
572
556
|
async pause() {
|
|
573
|
-
|
|
574
|
-
this.worker.terminate();
|
|
557
|
+
await this.stopWorker();
|
|
575
558
|
return await this._scanSettings.haltWallet(this.view_pair.primary_address);
|
|
576
559
|
}
|
|
577
|
-
|
|
560
|
+
// debug: dump coordinator + cpu worker heaps while they are live
|
|
561
|
+
// in browser: use inspector directly, this is only useful for bun
|
|
562
|
+
async dumpWorkerHeaps(dir) {
|
|
563
|
+
if (!this.worker?.dumpHeaps)
|
|
564
|
+
return [];
|
|
565
|
+
return await this.worker.dumpHeaps(dir);
|
|
566
|
+
}
|
|
567
|
+
async stopWorker() {
|
|
578
568
|
if (this.worker) {
|
|
579
|
-
this.worker
|
|
569
|
+
const worker = this.worker;
|
|
570
|
+
// clear ref first so concurrent stop/retry cannot double shutdown
|
|
571
|
+
this.worker.cpuWorkers = [];
|
|
572
|
+
this.worker.fetchWorker = undefined;
|
|
580
573
|
delete this.worker;
|
|
574
|
+
// drain coordinator first, then hard kill workers
|
|
575
|
+
try {
|
|
576
|
+
await worker.shutdown();
|
|
577
|
+
}
|
|
578
|
+
catch {
|
|
579
|
+
// still terminate below
|
|
580
|
+
}
|
|
581
|
+
try {
|
|
582
|
+
worker.terminate();
|
|
583
|
+
}
|
|
584
|
+
catch {
|
|
585
|
+
}
|
|
581
586
|
}
|
|
582
587
|
}
|
|
583
588
|
async unpause() {
|
|
584
589
|
// if worker does not exist yet, start it (if we are not slave / no_worker)
|
|
585
590
|
if (!this.worker && !this.no_worker) {
|
|
586
|
-
this.worker = await createWebworker(async (params) => await this.feed(params), this.scan_settings_path, this.pathPrefix, (
|
|
587
|
-
const workerErrCB = this.workerError;
|
|
588
|
-
this.stopWorker();
|
|
589
|
-
readWriteConnectionStatusFile((cs) => {
|
|
590
|
-
if (cs?.last_packet.status === "catastrophic_reorg")
|
|
591
|
-
return;
|
|
592
|
-
const connectionStatus = {
|
|
593
|
-
...cs,
|
|
594
|
-
last_packet: {
|
|
595
|
-
status: "connection_failed",
|
|
596
|
-
bytes_read: 0,
|
|
597
|
-
node_url: this.node_url,
|
|
598
|
-
timestamp: new Date().toISOString(),
|
|
599
|
-
},
|
|
600
|
-
};
|
|
601
|
-
return connectionStatus;
|
|
602
|
-
}, this.scan_settings_path).then(() => {
|
|
603
|
-
if (workerErrCB)
|
|
604
|
-
workerErrCB(error);
|
|
605
|
-
});
|
|
606
|
-
});
|
|
591
|
+
this.worker = await createWebworker(async (params) => await this.feed(params), this.scan_settings_path, this.pathPrefix, (params) => this._onWorkerError(params));
|
|
607
592
|
}
|
|
608
593
|
return await this._scanSettings.unhaltWallet(this.view_pair.primary_address);
|
|
609
594
|
}
|
|
595
|
+
_onWorkerError = (error) => {
|
|
596
|
+
const workerErrCB = this.workerError;
|
|
597
|
+
this.stopWorker();
|
|
598
|
+
readWriteConnectionStatusFile((cs) => {
|
|
599
|
+
if (cs?.last_packet.status === "catastrophic_reorg")
|
|
600
|
+
return;
|
|
601
|
+
const connectionStatus = {
|
|
602
|
+
...cs,
|
|
603
|
+
last_packet: {
|
|
604
|
+
status: "connection_failed",
|
|
605
|
+
bytes_read: 0,
|
|
606
|
+
node_url: this.node_url,
|
|
607
|
+
timestamp: new Date().toISOString(),
|
|
608
|
+
},
|
|
609
|
+
};
|
|
610
|
+
return connectionStatus;
|
|
611
|
+
}, this.scan_settings_path).then(() => {
|
|
612
|
+
if (workerErrCB)
|
|
613
|
+
workerErrCB(error);
|
|
614
|
+
});
|
|
615
|
+
};
|
|
610
616
|
/**
|
|
611
617
|
* selectInputs returns array of inputs, whose sum is larger than amount
|
|
612
618
|
* adds approximate fee for 10kb transaction to amount if feePerByte is supplied
|
|
@@ -715,10 +721,17 @@ export class ManyScanCachesOpened {
|
|
|
715
721
|
return null;
|
|
716
722
|
return this.wallets[0]?.start_height;
|
|
717
723
|
}
|
|
724
|
+
// overall scan tip = lagging non-halted wallet (this.wallets is already non-halted)
|
|
718
725
|
get current_height() {
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
726
|
+
let min = null;
|
|
727
|
+
for (const w of this.wallets) {
|
|
728
|
+
const h = w.current_height;
|
|
729
|
+
if (h == null)
|
|
730
|
+
continue;
|
|
731
|
+
if (min == null || h < min)
|
|
732
|
+
min = h;
|
|
733
|
+
}
|
|
734
|
+
return min;
|
|
722
735
|
}
|
|
723
736
|
get node_url() {
|
|
724
737
|
if (this.wallets.length === 0)
|
|
@@ -727,25 +740,32 @@ export class ManyScanCachesOpened {
|
|
|
727
740
|
}
|
|
728
741
|
async changeNodeUrlAndStartHeight(node_url, start_height) {
|
|
729
742
|
if (this.wallets.length === 0)
|
|
730
|
-
|
|
743
|
+
return;
|
|
731
744
|
const masterWallet = this.wallets[0];
|
|
732
745
|
return await masterWallet.changeNodeUrlAndStartHeight(node_url, start_height);
|
|
733
746
|
}
|
|
734
747
|
async retry() {
|
|
735
748
|
if (this.wallets.length === 0)
|
|
736
|
-
|
|
749
|
+
return;
|
|
737
750
|
const masterWallet = this.wallets[0];
|
|
738
751
|
return await masterWallet.retry();
|
|
739
752
|
}
|
|
740
|
-
stopWorker() {
|
|
753
|
+
async stopWorker() {
|
|
741
754
|
if (this.wallets.length === 0)
|
|
742
|
-
|
|
755
|
+
return;
|
|
743
756
|
const masterWallet = this.wallets[0];
|
|
744
|
-
return masterWallet.stopWorker();
|
|
757
|
+
return await masterWallet.stopWorker();
|
|
758
|
+
}
|
|
759
|
+
// debug: dump live coordinator + cpu worker heaps via master wallet workers
|
|
760
|
+
// in browser: use inspector directly, this is only useful for bun
|
|
761
|
+
async dumpWorkerHeaps(dir) {
|
|
762
|
+
if (this.wallets.length === 0)
|
|
763
|
+
return [];
|
|
764
|
+
return await this.wallets[0].dumpWorkerHeaps(dir);
|
|
745
765
|
}
|
|
746
766
|
async changeNodeUrl(node_url) {
|
|
747
767
|
if (this.wallets.length === 0)
|
|
748
|
-
|
|
768
|
+
return;
|
|
749
769
|
const masterWallet = this.wallets[0];
|
|
750
770
|
return await masterWallet.changeNodeUrl(node_url);
|
|
751
771
|
}
|
|
@@ -760,19 +780,13 @@ export class ManyScanCachesOpened {
|
|
|
760
780
|
return this.wallets[0]?.cpu_worker_count;
|
|
761
781
|
}
|
|
762
782
|
get logs() {
|
|
763
|
-
|
|
764
|
-
return undefined;
|
|
765
|
-
return this.wallets[0]?.logs;
|
|
783
|
+
return this._scanSettings.logs;
|
|
766
784
|
}
|
|
767
785
|
get logs_include() {
|
|
768
|
-
|
|
769
|
-
return undefined;
|
|
770
|
-
return this.wallets[0]?.logs_include;
|
|
786
|
+
return this._scanSettings.logs_include;
|
|
771
787
|
}
|
|
772
788
|
get logs_exclude() {
|
|
773
|
-
|
|
774
|
-
return undefined;
|
|
775
|
-
return this.wallets[0]?.logs_exclude;
|
|
789
|
+
return this._scanSettings.logs_exclude;
|
|
776
790
|
}
|
|
777
791
|
get connectionStatus() {
|
|
778
792
|
return this.connectionStatusOpened.connectionStatus;
|
|
@@ -788,25 +802,26 @@ export class ManyScanCachesOpened {
|
|
|
788
802
|
}
|
|
789
803
|
async setMerchantConfirmations(merchant_confirmations) {
|
|
790
804
|
if (this.wallets.length === 0)
|
|
791
|
-
|
|
805
|
+
return;
|
|
792
806
|
return await this.wallets[0].setMerchantConfirmations(merchant_confirmations);
|
|
793
807
|
}
|
|
794
808
|
async setCpuWorkerCount(cpu_worker_count) {
|
|
795
809
|
if (this.wallets.length === 0)
|
|
796
|
-
|
|
810
|
+
return;
|
|
797
811
|
return await this.wallets[0].setCpuWorkerCount(cpu_worker_count);
|
|
798
812
|
}
|
|
799
813
|
async setLogSettings(logs, logs_include, logs_exclude) {
|
|
800
|
-
|
|
801
|
-
throw new Error("no wallets");
|
|
802
|
-
return await this.wallets[0].setLogSettings(logs, logs_include, logs_exclude);
|
|
814
|
+
return await this._scanSettings.setLogSettings(logs, logs_include, logs_exclude);
|
|
803
815
|
}
|
|
804
816
|
async setWalletName(primary_address, name) {
|
|
805
817
|
await this._scanSettings.setWalletName(primary_address, name);
|
|
806
818
|
}
|
|
819
|
+
async setWalletSlot(primary_address, slot) {
|
|
820
|
+
await this._scanSettings.setWalletSlot(primary_address, slot);
|
|
821
|
+
}
|
|
807
822
|
async changeStartHeight(start_height) {
|
|
808
823
|
if (this.wallets.length === 0)
|
|
809
|
-
|
|
824
|
+
return;
|
|
810
825
|
const masterWallet = this.wallets[0];
|
|
811
826
|
return await masterWallet.changeStartHeight(start_height);
|
|
812
827
|
}
|
|
@@ -866,56 +881,122 @@ export class ManyScanCachesOpened {
|
|
|
866
881
|
static async create(options) {
|
|
867
882
|
const { scan_settings_path, pathPrefix, onConnectionStatusChange, connectionStatusIntervalMs, autoRetry, retryDelayMs, } = options;
|
|
868
883
|
const scanSettingsOpened = await ScanSettingsOpened.create(scan_settings_path, pathPrefix);
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
884
|
+
// persist log options into ScanSettings.json before workers start
|
|
885
|
+
if (options.logs !== undefined ||
|
|
886
|
+
options.logs_include !== undefined ||
|
|
887
|
+
options.logs_exclude !== undefined) {
|
|
888
|
+
await scanSettingsOpened.setLogSettings(options.logs !== undefined ? options.logs : scanSettingsOpened.logs, options.logs_include !== undefined
|
|
889
|
+
? options.logs_include
|
|
890
|
+
: scanSettingsOpened.logs_include, options.logs_exclude !== undefined
|
|
891
|
+
? options.logs_exclude
|
|
892
|
+
: scanSettingsOpened.logs_exclude);
|
|
893
|
+
}
|
|
872
894
|
// wrap workerError with auto-retry
|
|
873
895
|
let retryTimer;
|
|
874
896
|
let instance = null;
|
|
897
|
+
const retryFn = async () => {
|
|
898
|
+
// no wallets means idle, do not rebuild or restart workers
|
|
899
|
+
if (!instance || instance.wallets.length === 0) {
|
|
900
|
+
clearTimeout(retryTimer);
|
|
901
|
+
retryTimer = undefined;
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
await instance.buildWallets();
|
|
905
|
+
// still empty after rebuild, stay idle
|
|
906
|
+
if (instance.wallets.length === 0) {
|
|
907
|
+
clearTimeout(retryTimer);
|
|
908
|
+
retryTimer = undefined;
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
const node_url = instance.node_url;
|
|
912
|
+
if (!node_url)
|
|
913
|
+
throw new Error("No nodeurl set, can't retry connection");
|
|
914
|
+
const getinfo_result = await get_info(node_url)
|
|
915
|
+
.then((r) => {
|
|
916
|
+
r?.status === "OK";
|
|
917
|
+
})
|
|
918
|
+
.catch(() => false);
|
|
919
|
+
if (getinfo_result)
|
|
920
|
+
await instance?.retry();
|
|
921
|
+
clearTimeout(retryTimer);
|
|
922
|
+
retryTimer = undefined;
|
|
923
|
+
};
|
|
875
924
|
const newOptions = { ...options };
|
|
925
|
+
let connectionFailedShown = false;
|
|
876
926
|
if (autoRetry) {
|
|
877
927
|
const originalError = options.workerError;
|
|
878
928
|
newOptions.workerError = (err) => {
|
|
879
929
|
originalError?.(err);
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
930
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
931
|
+
if (csOpened.connectionStatus?.last_packet?.status ===
|
|
932
|
+
"catastrophic_reorg") {
|
|
933
|
+
clearTimeout(retryTimer);
|
|
934
|
+
instance?.stopWorker();
|
|
935
|
+
throw new Error("catastrophic reorg, aborting ...");
|
|
936
|
+
}
|
|
937
|
+
if (msg.includes("connect") ||
|
|
938
|
+
msg.includes("fetch") ||
|
|
939
|
+
msg.includes("NetworkError")) {
|
|
940
|
+
if (!connectionFailedShown) {
|
|
941
|
+
connectionFailedShown = true;
|
|
942
|
+
console.error("unable to connect to node ... retrying ...");
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
// only schedule retry when we still have wallets to scan
|
|
946
|
+
if (!retryTimer && instance && instance.wallets.length > 0) {
|
|
947
|
+
retryTimer = setTimeout(retryFn, retryDelayMs ?? 5000);
|
|
885
948
|
}
|
|
886
949
|
};
|
|
887
950
|
}
|
|
888
|
-
const wallets = await this._buildWallets(scanSettingsOpened, newOptions);
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
951
|
+
const wallets = (await this._buildWallets(scanSettingsOpened, newOptions)) ?? [];
|
|
952
|
+
const csOpened = new ConnectionStatusOpened(scan_settings_path || SCAN_SETTINGS_STORE_NAME_DEFAULT, autoRetry
|
|
953
|
+
? (status) => {
|
|
954
|
+
if (status?.last_packet?.status === "OK" && connectionFailedShown) {
|
|
955
|
+
connectionFailedShown = false;
|
|
956
|
+
console.log("connection to node established");
|
|
957
|
+
}
|
|
958
|
+
if (onConnectionStatusChange)
|
|
959
|
+
onConnectionStatusChange(status);
|
|
960
|
+
}
|
|
961
|
+
: (onConnectionStatusChange ?? null));
|
|
892
962
|
csOpened.watch(connectionStatusIntervalMs);
|
|
893
963
|
instance = new ManyScanCachesOpened(wallets, csOpened, scanSettingsOpened, newOptions);
|
|
894
964
|
return instance;
|
|
895
965
|
}
|
|
896
966
|
async buildWallets() {
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
967
|
+
await this.stopWorker();
|
|
968
|
+
await this.reloadWalletsAfterStop();
|
|
969
|
+
}
|
|
970
|
+
// assumes stopWorker already ran (graceful shutdown + terminate)
|
|
971
|
+
async reloadWalletsAfterStop() {
|
|
900
972
|
await this._scanSettings.reload();
|
|
973
|
+
// empty after reload: stay idle, do not open ScanCacheOpened or start workers
|
|
974
|
+
const remaining = this._scanSettings.wallets.filter((w) => !w?.halted);
|
|
975
|
+
if (remaining.length === 0) {
|
|
976
|
+
this._wallets = [];
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
901
979
|
const newWallets = await ManyScanCachesOpened._buildWallets(this._scanSettings, this._options);
|
|
902
|
-
|
|
903
|
-
throw new Error("no non-halted wallets left after rebuild, use addViewWallet or addSpendWallet first");
|
|
904
|
-
this._wallets = newWallets;
|
|
980
|
+
this._wallets = newWallets ?? [];
|
|
905
981
|
}
|
|
906
982
|
async addViewWallet(primary_address, view_key, fields) {
|
|
983
|
+
await this.stopWorker();
|
|
907
984
|
await this._scanSettings.addViewWallet(primary_address, view_key, fields);
|
|
908
|
-
await this.
|
|
985
|
+
await this.reloadWalletsAfterStop();
|
|
909
986
|
}
|
|
910
987
|
async addSpendWallet(wallet_secret, fields) {
|
|
988
|
+
await this.stopWorker();
|
|
911
989
|
await this._scanSettings.addSpendWallet(wallet_secret, fields);
|
|
912
|
-
await this.
|
|
990
|
+
await this.reloadWalletsAfterStop();
|
|
913
991
|
}
|
|
914
992
|
async removeWallet(primary_address) {
|
|
993
|
+
await this.stopWorker();
|
|
915
994
|
await this._scanSettings.removeWallet(primary_address);
|
|
916
|
-
await this.
|
|
995
|
+
await this.reloadWalletsAfterStop();
|
|
917
996
|
}
|
|
918
997
|
async feed(params) {
|
|
998
|
+
if (this.wallets.length === 0)
|
|
999
|
+
return;
|
|
919
1000
|
await this.wallets[0].feed(params);
|
|
920
1001
|
}
|
|
921
1002
|
_wallets = [];
|
|
@@ -45,26 +45,12 @@ export declare function makeWorkItemsForAllWallets(wallet_configs: WalletConfigP
|
|
|
45
45
|
* blocks buffer if no remaining work items reference it.
|
|
46
46
|
*/
|
|
47
47
|
export declare function reconcileWorkItemDone(blocksBuffer: GetBlocksBinBufferItem[], workItemBuffer: WorkItem[]): void;
|
|
48
|
-
/**
|
|
49
|
-
* one round of the select race. takes two primed promises (blocks, scan),
|
|
50
|
-
* races them, returns winner info. the loser stays in flight.
|
|
51
|
-
*/
|
|
52
|
-
export declare function raceStep(blocksPromise: Promise<IteratorResult<BlocksBufferLoopResult>>, scanPromise: Promise<IteratorResult<ScanLoopYield, void>>): Promise<{
|
|
53
|
-
src: "blocks" | "scan";
|
|
54
|
-
value: IteratorResult<BlocksBufferLoopResult> | IteratorResult<ScanLoopYield, void>;
|
|
55
|
-
done: boolean;
|
|
56
|
-
}>;
|
|
57
48
|
/**
|
|
58
49
|
* handle a yield from the blocks buffer fetch loop.
|
|
59
50
|
*/
|
|
60
51
|
export declare function handleBlocksYield(value: BlocksBufferLoopResult, scanSettingsPath?: string): Promise<{
|
|
61
52
|
isBlocksBufferChanged: boolean;
|
|
62
53
|
}>;
|
|
63
|
-
/**
|
|
64
|
-
* process a scan result for a completed work item.
|
|
65
|
-
* updates the cache, writes to disk, marks work item done, reconciles.
|
|
66
|
-
*/
|
|
67
|
-
export declare function processScanResultForWorkItem(value: ScanLoopYield, workBuffer: WorkItem[], blocksBuffer: GetBlocksBinBufferItem[], pathPrefix: string, secret_spend_key?: string): Promise<void>;
|
|
68
54
|
/**
|
|
69
55
|
* process a scan result for a completed work item.
|
|
70
56
|
* updates the cache, writes to disk, marks work item done, reconciles blockbuffer with this.
|
|
@@ -78,7 +64,6 @@ export type CoordinatorEvent = {
|
|
|
78
64
|
} | {
|
|
79
65
|
type: "scan_ready";
|
|
80
66
|
address: string;
|
|
81
|
-
result: ScanLoopYield;
|
|
82
67
|
newCache: ScanCache;
|
|
83
68
|
changed_outputs: {
|
|
84
69
|
output: any;
|
|
@@ -86,19 +71,13 @@ export type CoordinatorEvent = {
|
|
|
86
71
|
}[];
|
|
87
72
|
} | {
|
|
88
73
|
type: "all_idle";
|
|
74
|
+
} | {
|
|
75
|
+
type: "shutdown_done";
|
|
89
76
|
} | {
|
|
90
77
|
type: "error";
|
|
91
78
|
error: Error;
|
|
92
79
|
};
|
|
93
|
-
|
|
94
|
-
* coordinator main: async generator that drives the full scan cycle.
|
|
95
|
-
* takes only scanSettingsPath, derives everything else internally.
|
|
96
|
-
* yields events for some of the underlying generator events,
|
|
97
|
-
* look at CoordinatorEvent, processing not decoupled from cpu bound scan work,
|
|
98
|
-
* as this single threaded, there is no point
|
|
99
|
-
*/
|
|
100
|
-
export declare function coordinatorMain(scanSettingsPath?: string, pathPrefix?: string): AsyncGenerator<CoordinatorEvent>;
|
|
101
|
-
export declare function setupCoordinator(scanSettingsPath?: string, pathPrefix?: string): Promise<false | {
|
|
80
|
+
export declare function setupCoordinator(scanSettingsPath?: string, pathPrefix?: string, stopSync?: AbortSignal): Promise<false | {
|
|
102
81
|
blocksGenerator: AsyncGenerator<BlocksBufferLoopResult, any, any>;
|
|
103
82
|
workBuffer: WorkItem[];
|
|
104
83
|
blocksBuffer: GetBlocksBinBufferItem[];
|
|
@@ -118,10 +97,12 @@ export type BlocksBufferRacer = {
|
|
|
118
97
|
};
|
|
119
98
|
export type ScanLoopRacer = ScanLoopYield;
|
|
120
99
|
export type Racers = BlocksBufferRacer | ScanLoopRacer;
|
|
100
|
+
export declare function resetInProgressWorkItems(workBuffer: WorkItem[]): void;
|
|
101
|
+
export declare function cancelBusyCpuPorts(ports: PortStatus[], timeoutMs?: number): Promise<void>;
|
|
121
102
|
/**
|
|
122
103
|
* coordinator main (multithreaded): dispatches scan work to CPU workers
|
|
123
104
|
* via MessagePorts, processes results in order per wallet.
|
|
124
105
|
* falls back to single-threaded coordinator if no cpuPorts provided.
|
|
125
106
|
*/
|
|
126
|
-
export declare function coordinatorMainMultithreaded(scanSettingsPath?: string, pathPrefix?: string, cpuPorts?: MessagePort[]): AsyncGenerator<CoordinatorEvent>;
|
|
107
|
+
export declare function coordinatorMainMultithreaded(scanSettingsPath?: string, pathPrefix?: string, cpuPorts?: MessagePort[], stopSync?: AbortSignal): AsyncGenerator<CoordinatorEvent>;
|
|
127
108
|
export declare function scheduleWorkOnCpuPorts(ports: PortStatus[], work_buffer: WorkItem[]): Promise<void>;
|