@spirobel/monero-wallet-api 0.5.1 → 0.5.2
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/actionlog/actionLog.d.ts +4 -0
- package/dist/actionlog/actionLog.js +9 -0
- package/dist/actionlog/actionLogOpened.d.ts +1 -0
- package/dist/actionlog/actionLogOpened.js +1 -0
- package/dist/api.d.ts +3 -2
- package/dist/api.js +3 -2
- package/dist/scanning-syncing/backgroundWorker.js +1 -1
- package/dist/scanning-syncing/blocksbuffer/blocksBufferFetchLoop.js +29 -3
- package/dist/scanning-syncing/blocksbuffer/blocksbufferCoordination.js +6 -3
- package/dist/scanning-syncing/connectionStatus.d.ts +1 -4
- package/dist/scanning-syncing/connectionStatus.js +1 -31
- package/dist/scanning-syncing/connectionStatusOpened.d.ts +15 -0
- package/dist/scanning-syncing/connectionStatusOpened.js +51 -0
- package/dist/scanning-syncing/openWallet.js +1 -1
- package/dist/scanning-syncing/scanresult/scanCacheOpened.d.ts +53 -7
- package/dist/scanning-syncing/scanresult/scanCacheOpened.js +185 -50
- package/dist/scanning-syncing/scanresult/scanCoordination.d.ts +1 -1
- package/dist/scanning-syncing/scanresult/scanCoordination.js +64 -2
- package/dist/scanning-syncing/scansettings/scanSettings.d.ts +165 -0
- package/dist/scanning-syncing/scansettings/scanSettings.js +338 -0
- package/dist/scanning-syncing/worker-entrypoints/worker.js +1 -1
- package/dist/scanning-syncing/worker-mains/worker.js +1 -2
- package/dist/scansettings/scanSettings.d.ts +165 -0
- package/dist/scansettings/scanSettings.js +338 -0
- package/dist/scansettings/scanSettingsOpened.d.ts +102 -0
- package/dist/scansettings/scanSettingsOpened.js +299 -0
- package/dist/viewpair/ViewPair.js +1 -1
- package/package.json +1 -1
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { 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";
|
|
5
|
-
import { getPathPrefix,
|
|
5
|
+
import { getPathPrefix, readPrivateSpendKeyFromEnv, SCAN_SETTINGS_STORE_NAME_DEFAULT, SUB_ADDRESS_INDEX_DEFAULT_VALUE, walletSettingsPlusKeys, } from "../../api";
|
|
6
|
+
import { ScanSettingsOpened } from "../../scansettings/scanSettingsOpened";
|
|
7
|
+
import { ConnectionStatusOpened } from "../connectionStatusOpened";
|
|
6
8
|
import { findRange, lastRange, readCacheFileDefaultLocation, writeCacheFileDefaultLocationThrows, } from "./scanCache";
|
|
7
9
|
import { alignScanStatsWithCache, isSelfSpent, processTxlogInputs, processTxlogPayments, writeStatsFileDefaultLocation, } from "./scanStats";
|
|
8
|
-
import {
|
|
10
|
+
import { readWriteConnectionStatusFile, } from "../connectionStatus";
|
|
9
11
|
export class ScanCacheOpened {
|
|
12
|
+
_scanSettings;
|
|
10
13
|
view_pair;
|
|
11
14
|
wallet_route;
|
|
12
15
|
wallet_name;
|
|
@@ -35,17 +38,24 @@ export class ScanCacheOpened {
|
|
|
35
38
|
decoyRetrySizes = [20, 50, 100, 200, 500];
|
|
36
39
|
static async create(params) {
|
|
37
40
|
const theCatchToBeOpened = await readCacheFileDefaultLocation(params.primary_address, params.pathPrefix);
|
|
38
|
-
|
|
41
|
+
if (!params.primary_address)
|
|
42
|
+
throw new Error(`primary_address is required, potentially half filled out wallet setting in: ${params.scan_settings_path || SCAN_SETTINGS_STORE_NAME_DEFAULT}`);
|
|
43
|
+
// use ScanSettingsOpened instead of direct scanSettings calls
|
|
44
|
+
const scanSettings = await ScanSettingsOpened.create(params.scan_settings_path, params.pathPrefix);
|
|
45
|
+
const walletSettings = scanSettings.getWallet(params.primary_address);
|
|
39
46
|
if (!walletSettings)
|
|
40
47
|
throw new Error(`wallet not found in settings. did you call openwallet with the right params?
|
|
41
48
|
Either wrong file name supplied to params.scan_settings_path: ${params.scan_settings_path}
|
|
42
49
|
Or wrong primary_address supplied params.primary_address: ${params.primary_address}`);
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
const walletWithSettings = {
|
|
51
|
+
...walletSettings,
|
|
52
|
+
node_url: scanSettings.node_url,
|
|
53
|
+
start_height: scanSettings.start_height,
|
|
54
|
+
};
|
|
45
55
|
// read secret_view_key and secret_spend_key from env
|
|
46
|
-
const walletSettingsWithKeys = await walletSettingsPlusKeys(
|
|
56
|
+
const walletSettingsWithKeys = await walletSettingsPlusKeys(walletWithSettings);
|
|
47
57
|
// create viewpair + ScanCacheOpened instance
|
|
48
|
-
const scanCacheOpen = new ScanCacheOpened(await ViewPair.create(params.primary_address, walletSettingsWithKeys.secret_view_key, walletSettings.subaddress_index,
|
|
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, params.pathPrefix, params.workerError);
|
|
49
59
|
if (theCatchToBeOpened)
|
|
50
60
|
scanCacheOpen._cache = theCatchToBeOpened;
|
|
51
61
|
if (!walletSettings.halted) {
|
|
@@ -86,7 +96,7 @@ export class ScanCacheOpened {
|
|
|
86
96
|
this.worker.terminate();
|
|
87
97
|
delete this.worker;
|
|
88
98
|
}
|
|
89
|
-
await
|
|
99
|
+
await this._scanSettings.setStartHeight(start_height);
|
|
90
100
|
this._start_height = start_height;
|
|
91
101
|
await this.unpause();
|
|
92
102
|
}
|
|
@@ -144,17 +154,32 @@ export class ScanCacheOpened {
|
|
|
144
154
|
set node_url(nu) {
|
|
145
155
|
this.view_pair.node_url = nu;
|
|
146
156
|
}
|
|
157
|
+
get merchant_confirmations() {
|
|
158
|
+
return this._scanSettings.merchant_confirmations;
|
|
159
|
+
}
|
|
160
|
+
get cpu_worker_count() {
|
|
161
|
+
return this._scanSettings.cpu_worker_count;
|
|
162
|
+
}
|
|
163
|
+
get logs() {
|
|
164
|
+
return this._scanSettings.logs;
|
|
165
|
+
}
|
|
166
|
+
get logs_include() {
|
|
167
|
+
return this._scanSettings.logs_include;
|
|
168
|
+
}
|
|
169
|
+
get logs_exclude() {
|
|
170
|
+
return this._scanSettings.logs_exclude;
|
|
171
|
+
}
|
|
147
172
|
async changeNodeUrlAndStartHeight(node_url, start_height) {
|
|
148
173
|
if (this.worker) {
|
|
149
174
|
this.worker.terminate();
|
|
150
175
|
delete this.worker;
|
|
151
176
|
}
|
|
152
177
|
if (node_url !== undefined) {
|
|
153
|
-
await
|
|
178
|
+
await this._scanSettings.setNodeUrl(node_url);
|
|
154
179
|
this.node_url = node_url;
|
|
155
180
|
}
|
|
156
181
|
if (start_height !== undefined) {
|
|
157
|
-
await
|
|
182
|
+
await this._scanSettings.setStartHeight(start_height);
|
|
158
183
|
this._start_height = start_height;
|
|
159
184
|
}
|
|
160
185
|
await this.unpause();
|
|
@@ -164,17 +189,34 @@ export class ScanCacheOpened {
|
|
|
164
189
|
this.worker.terminate();
|
|
165
190
|
delete this.worker;
|
|
166
191
|
}
|
|
167
|
-
await
|
|
192
|
+
await this._scanSettings.setNodeUrl(node_url);
|
|
168
193
|
this.node_url = node_url;
|
|
169
194
|
await this.unpause();
|
|
170
195
|
}
|
|
196
|
+
async setMerchantConfirmations(merchant_confirmations) {
|
|
197
|
+
this.stopWorker();
|
|
198
|
+
await this._scanSettings.setMerchantConfirmations(merchant_confirmations);
|
|
199
|
+
await this.unpause();
|
|
200
|
+
}
|
|
201
|
+
async setCpuWorkerCount(cpu_worker_count) {
|
|
202
|
+
this.stopWorker();
|
|
203
|
+
await this._scanSettings.setCpuWorkerCount(cpu_worker_count);
|
|
204
|
+
await this.unpause();
|
|
205
|
+
}
|
|
206
|
+
async setLogSettings(logs, logs_include, logs_exclude) {
|
|
207
|
+
this.stopWorker();
|
|
208
|
+
await this._scanSettings.setLogSettings(logs, logs_include, logs_exclude);
|
|
209
|
+
await this.unpause();
|
|
210
|
+
}
|
|
171
211
|
async retry() {
|
|
172
212
|
if (this.worker) {
|
|
173
213
|
this.worker.terminate();
|
|
174
214
|
delete this.worker;
|
|
175
215
|
}
|
|
176
|
-
|
|
177
|
-
|
|
216
|
+
// scansettings so external changes (e.g. from a sidebar frontend instance) are picked up
|
|
217
|
+
await this._scanSettings.reload();
|
|
218
|
+
const walletStillExists = this._scanSettings.walletExists(this.primary_address);
|
|
219
|
+
if (walletStillExists) {
|
|
178
220
|
// if there is no scan settings file,
|
|
179
221
|
// the retry loop is stopped.
|
|
180
222
|
// the wallet reset happens through deleting all the scan setting + cache files
|
|
@@ -182,7 +224,7 @@ export class ScanCacheOpened {
|
|
|
182
224
|
//TODO ? write connection status retry
|
|
183
225
|
await this.unpause();
|
|
184
226
|
}
|
|
185
|
-
return
|
|
227
|
+
return walletStillExists;
|
|
186
228
|
}
|
|
187
229
|
async sendTransaction(signedTx) {
|
|
188
230
|
const node = await NodeUrl.create(this.node_url);
|
|
@@ -480,7 +522,9 @@ export class ScanCacheOpened {
|
|
|
480
522
|
* @returns Adressstring
|
|
481
523
|
*/
|
|
482
524
|
async makeSubaddress() {
|
|
483
|
-
|
|
525
|
+
// reload scansettings so external changes (e.g. from a sidebar frontend instance) are picked up
|
|
526
|
+
await this._scanSettings.reload();
|
|
527
|
+
const walletSettings = this._scanSettings.getWallet(this.view_pair.primary_address);
|
|
484
528
|
if (!walletSettings)
|
|
485
529
|
throw new Error(`wallet not found in settings. did you call openwallet with the right params?
|
|
486
530
|
Either wrong file name supplied to params.scan_settings_path: ${this.scan_settings_path}
|
|
@@ -489,11 +533,7 @@ export class ScanCacheOpened {
|
|
|
489
533
|
const minor = last_subaddress_index + 1;
|
|
490
534
|
const subaddress = this.view_pair.makeSubaddress(minor);
|
|
491
535
|
this._highest_subaddress_index = minor;
|
|
492
|
-
await
|
|
493
|
-
primary_address: this.view_pair.primary_address,
|
|
494
|
-
subaddress_index: minor,
|
|
495
|
-
scan_settings_path: this.scan_settings_path,
|
|
496
|
-
});
|
|
536
|
+
await this._scanSettings.setSubaddressIndex(this.view_pair.primary_address, minor);
|
|
497
537
|
const created_at_height = lastRange(this._cache.scanned_ranges)?.end || 0;
|
|
498
538
|
const created_at_timestamp = new Date().getTime();
|
|
499
539
|
const new_subaddress = {
|
|
@@ -529,11 +569,7 @@ export class ScanCacheOpened {
|
|
|
529
569
|
async pause() {
|
|
530
570
|
if (this.worker)
|
|
531
571
|
this.worker.terminate();
|
|
532
|
-
return await
|
|
533
|
-
primary_address: this.view_pair.primary_address,
|
|
534
|
-
halted: true,
|
|
535
|
-
scan_settings_path: this.scan_settings_path,
|
|
536
|
-
});
|
|
572
|
+
return await this._scanSettings.haltWallet(this.view_pair.primary_address);
|
|
537
573
|
}
|
|
538
574
|
stopWorker() {
|
|
539
575
|
if (this.worker) {
|
|
@@ -566,11 +602,7 @@ export class ScanCacheOpened {
|
|
|
566
602
|
});
|
|
567
603
|
});
|
|
568
604
|
}
|
|
569
|
-
return await
|
|
570
|
-
primary_address: this.view_pair.primary_address,
|
|
571
|
-
halted: false,
|
|
572
|
-
scan_settings_path: this.scan_settings_path,
|
|
573
|
-
});
|
|
605
|
+
return await this._scanSettings.unhaltWallet(this.view_pair.primary_address);
|
|
574
606
|
}
|
|
575
607
|
/**
|
|
576
608
|
* selectInputs returns array of inputs, whose sum is larger than amount
|
|
@@ -635,11 +667,6 @@ export class ScanCacheOpened {
|
|
|
635
667
|
this._cache = params.newCache;
|
|
636
668
|
if (!this._no_stats)
|
|
637
669
|
this._stats = await alignScanStatsWithCache(this._cache, this.view_pair, this.primary_address, getPathPrefix(this.scan_settings_path, this.pathPrefix), this.subaddress_index, lastRange(this._cache.scanned_ranges)?.end);
|
|
638
|
-
if (!this.no_worker) {
|
|
639
|
-
const etaResult = await updateSyncETA(this._cache.daemon_height, this.current_height || 0, this.last_eta_height, this.last_eta_timestamp, this.scan_settings_path);
|
|
640
|
-
this.last_eta_height = etaResult.last_height;
|
|
641
|
-
this.last_eta_timestamp = etaResult.last_timestamp;
|
|
642
|
-
}
|
|
643
670
|
for (const listener of this.notifyListeners) {
|
|
644
671
|
if (listener)
|
|
645
672
|
listener(params);
|
|
@@ -660,7 +687,8 @@ export class ScanCacheOpened {
|
|
|
660
687
|
worker = undefined;
|
|
661
688
|
last_eta_height = null;
|
|
662
689
|
last_eta_timestamp = null;
|
|
663
|
-
constructor(view_pair, wallet_route, wallet_name, wallet_slot, no_worker, masterCacheChanged, _start_height, scan_settings_path, pathPrefix, workerError) {
|
|
690
|
+
constructor(_scanSettings, view_pair, wallet_route, wallet_name, wallet_slot, no_worker, masterCacheChanged, _start_height, scan_settings_path, pathPrefix, workerError) {
|
|
691
|
+
this._scanSettings = _scanSettings;
|
|
664
692
|
this.view_pair = view_pair;
|
|
665
693
|
this.wallet_route = wallet_route;
|
|
666
694
|
this.wallet_name = wallet_name;
|
|
@@ -676,7 +704,9 @@ export class ScanCacheOpened {
|
|
|
676
704
|
notifyListeners = [];
|
|
677
705
|
}
|
|
678
706
|
export class ManyScanCachesOpened {
|
|
679
|
-
|
|
707
|
+
connectionStatusOpened;
|
|
708
|
+
_scanSettings;
|
|
709
|
+
_options;
|
|
680
710
|
get start_height() {
|
|
681
711
|
if (this.wallets.length === 0)
|
|
682
712
|
return null;
|
|
@@ -716,18 +746,67 @@ export class ManyScanCachesOpened {
|
|
|
716
746
|
const masterWallet = this.wallets[0];
|
|
717
747
|
return await masterWallet.changeNodeUrl(node_url);
|
|
718
748
|
}
|
|
749
|
+
get merchant_confirmations() {
|
|
750
|
+
if (this.wallets.length === 0)
|
|
751
|
+
return undefined;
|
|
752
|
+
return this.wallets[0]?.merchant_confirmations;
|
|
753
|
+
}
|
|
754
|
+
get cpu_worker_count() {
|
|
755
|
+
if (this.wallets.length === 0)
|
|
756
|
+
return undefined;
|
|
757
|
+
return this.wallets[0]?.cpu_worker_count;
|
|
758
|
+
}
|
|
759
|
+
get logs() {
|
|
760
|
+
if (this.wallets.length === 0)
|
|
761
|
+
return undefined;
|
|
762
|
+
return this.wallets[0]?.logs;
|
|
763
|
+
}
|
|
764
|
+
get logs_include() {
|
|
765
|
+
if (this.wallets.length === 0)
|
|
766
|
+
return undefined;
|
|
767
|
+
return this.wallets[0]?.logs_include;
|
|
768
|
+
}
|
|
769
|
+
get logs_exclude() {
|
|
770
|
+
if (this.wallets.length === 0)
|
|
771
|
+
return undefined;
|
|
772
|
+
return this.wallets[0]?.logs_exclude;
|
|
773
|
+
}
|
|
774
|
+
get connectionStatus() {
|
|
775
|
+
return this.connectionStatusOpened.connectionStatus;
|
|
776
|
+
}
|
|
777
|
+
get daemonHeight() {
|
|
778
|
+
return this.connectionStatusOpened.daemonHeight;
|
|
779
|
+
}
|
|
780
|
+
watchConnectionStatus(intervalMs) {
|
|
781
|
+
this.connectionStatusOpened.watch(intervalMs);
|
|
782
|
+
}
|
|
783
|
+
unwatchConnectionStatus() {
|
|
784
|
+
this.connectionStatusOpened.unwatch();
|
|
785
|
+
}
|
|
786
|
+
async setMerchantConfirmations(merchant_confirmations) {
|
|
787
|
+
if (this.wallets.length === 0)
|
|
788
|
+
throw new Error("no wallets");
|
|
789
|
+
return await this.wallets[0].setMerchantConfirmations(merchant_confirmations);
|
|
790
|
+
}
|
|
791
|
+
async setCpuWorkerCount(cpu_worker_count) {
|
|
792
|
+
if (this.wallets.length === 0)
|
|
793
|
+
throw new Error("no wallets");
|
|
794
|
+
return await this.wallets[0].setCpuWorkerCount(cpu_worker_count);
|
|
795
|
+
}
|
|
796
|
+
async setLogSettings(logs, logs_include, logs_exclude) {
|
|
797
|
+
if (this.wallets.length === 0)
|
|
798
|
+
throw new Error("no wallets");
|
|
799
|
+
return await this.wallets[0].setLogSettings(logs, logs_include, logs_exclude);
|
|
800
|
+
}
|
|
719
801
|
async changeStartHeight(start_height) {
|
|
720
802
|
if (this.wallets.length === 0)
|
|
721
803
|
throw new Error("no wallets");
|
|
722
804
|
const masterWallet = this.wallets[0];
|
|
723
805
|
return await masterWallet.changeStartHeight(start_height);
|
|
724
806
|
}
|
|
725
|
-
static async
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
throw new Error(`no wallets in settings file. Did you supply the right path?
|
|
729
|
-
are there wallets in the default '${SCAN_SETTINGS_STORE_NAME_DEFAULT}' file?`);
|
|
730
|
-
const nonHaltedWallets = scan_settings.wallets.filter((wallet) => !wallet?.halted);
|
|
807
|
+
static async _buildWallets(scanSettingsOpened, options) {
|
|
808
|
+
const { scan_settings_path, pathPrefix, no_worker, no_stats, workerError, notifyMasterChanged, } = options;
|
|
809
|
+
const nonHaltedWallets = scanSettingsOpened.wallets.filter((wallet) => !wallet?.halted);
|
|
731
810
|
if (!nonHaltedWallets.length)
|
|
732
811
|
return undefined;
|
|
733
812
|
const openedWallets = [];
|
|
@@ -776,15 +855,71 @@ export class ManyScanCachesOpened {
|
|
|
776
855
|
});
|
|
777
856
|
openedWallets.push(onlyWallet);
|
|
778
857
|
}
|
|
779
|
-
return
|
|
858
|
+
return openedWallets;
|
|
859
|
+
}
|
|
860
|
+
static async create(options) {
|
|
861
|
+
const { scan_settings_path, pathPrefix, onConnectionStatusChange, connectionStatusIntervalMs, autoRetry, retryDelayMs, } = options;
|
|
862
|
+
const scanSettingsOpened = await ScanSettingsOpened.create(scan_settings_path, pathPrefix);
|
|
863
|
+
if (!scanSettingsOpened.wallets || scanSettingsOpened.wallets.length === 0)
|
|
864
|
+
throw new Error(`no wallets in settings file. Did you supply the right path?
|
|
865
|
+
are there wallets in the default '${SCAN_SETTINGS_STORE_NAME_DEFAULT}' file?`);
|
|
866
|
+
// wrap workerError with auto-retry
|
|
867
|
+
let retryTimer;
|
|
868
|
+
let instance = null;
|
|
869
|
+
const newOptions = { ...options };
|
|
870
|
+
if (autoRetry) {
|
|
871
|
+
const originalError = options.workerError;
|
|
872
|
+
newOptions.workerError = (err) => {
|
|
873
|
+
originalError?.(err);
|
|
874
|
+
if (!retryTimer) {
|
|
875
|
+
retryTimer = setTimeout(() => {
|
|
876
|
+
retryTimer = undefined;
|
|
877
|
+
instance?.retry();
|
|
878
|
+
}, retryDelayMs ?? 1000);
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
const wallets = await this._buildWallets(scanSettingsOpened, newOptions);
|
|
883
|
+
if (!wallets)
|
|
884
|
+
return undefined;
|
|
885
|
+
const csOpened = new ConnectionStatusOpened(scan_settings_path || SCAN_SETTINGS_STORE_NAME_DEFAULT, onConnectionStatusChange ?? null);
|
|
886
|
+
csOpened.watch(connectionStatusIntervalMs);
|
|
887
|
+
instance = new ManyScanCachesOpened(wallets, csOpened, scanSettingsOpened, newOptions);
|
|
888
|
+
return instance;
|
|
889
|
+
}
|
|
890
|
+
async buildWallets() {
|
|
891
|
+
for (const w of this._wallets) {
|
|
892
|
+
w.stopWorker();
|
|
893
|
+
}
|
|
894
|
+
await this._scanSettings.reload();
|
|
895
|
+
const newWallets = await ManyScanCachesOpened._buildWallets(this._scanSettings, this._options);
|
|
896
|
+
if (!newWallets)
|
|
897
|
+
throw new Error("no non-halted wallets left after rebuild, use addViewWallet or addSpendWallet first");
|
|
898
|
+
this._wallets = newWallets;
|
|
899
|
+
}
|
|
900
|
+
async addViewWallet(primary_address, view_key, fields) {
|
|
901
|
+
await this._scanSettings.addViewWallet(primary_address, view_key, fields);
|
|
902
|
+
await this.buildWallets();
|
|
903
|
+
}
|
|
904
|
+
async addSpendWallet(wallet_secret, fields) {
|
|
905
|
+
await this._scanSettings.addSpendWallet(wallet_secret, fields);
|
|
906
|
+
await this.buildWallets();
|
|
907
|
+
}
|
|
908
|
+
async removeWallet(primary_address) {
|
|
909
|
+
await this._scanSettings.removeWallet(primary_address);
|
|
910
|
+
await this.buildWallets();
|
|
780
911
|
}
|
|
781
|
-
/**
|
|
782
|
-
* feed the master wallet and therefore all wallets
|
|
783
|
-
*/
|
|
784
912
|
async feed(params) {
|
|
785
913
|
await this.wallets[0].feed(params);
|
|
786
914
|
}
|
|
787
|
-
|
|
788
|
-
|
|
915
|
+
_wallets = [];
|
|
916
|
+
get wallets() {
|
|
917
|
+
return this._wallets;
|
|
918
|
+
}
|
|
919
|
+
constructor(wallets, connectionStatusOpened, _scanSettings, _options) {
|
|
920
|
+
this.connectionStatusOpened = connectionStatusOpened;
|
|
921
|
+
this._scanSettings = _scanSettings;
|
|
922
|
+
this._options = _options;
|
|
923
|
+
this._wallets = wallets;
|
|
789
924
|
}
|
|
790
925
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type GetBlocksBinBufferItem, type BlocksBufferLoopResult, type BlocksBufferIteratorResult, type ProcessScanResult, type BlockInfo } from "../../api";
|
|
2
2
|
import { type ScanLoopYield } from "./scanLoop";
|
|
3
3
|
import { type WorkItem } from "./scanLoop";
|
|
4
|
-
import { type ScanSettings } from "
|
|
4
|
+
import { type ScanSettings } from "../../api";
|
|
5
5
|
import { type CacheRange, type ScanCache } from "./scanCache";
|
|
6
6
|
export type WalletConfig = {
|
|
7
7
|
primary_address: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { setupBlocksBufferGenerator, ViewPair, handleConnectionStatusChanges, processScanResult, writeCacheToFile, sleep, findTipIndex, } from "../../api";
|
|
1
|
+
import { setupBlocksBufferGenerator, ViewPair, handleConnectionStatusChanges, processScanResult, writeCacheToFile, sleep, findTipIndex, readWriteConnectionStatusFile, connectionStatusFilePath, } from "../../api";
|
|
2
2
|
import { markWorkItemAsDone, } from "./scanLoop";
|
|
3
3
|
import { makeWorkItem, scanLoop } from "./scanLoop";
|
|
4
|
-
import { cullTooLargeScanHeight, getNonHaltedWallets, getPathPrefix, openScanSettingsFile, SCAN_SETTINGS_STORE_NAME_DEFAULT, walletSettingsPlusKeys, } from "
|
|
4
|
+
import { cullTooLargeScanHeight, getNonHaltedWallets, getPathPrefix, openScanSettingsFile, SCAN_SETTINGS_STORE_NAME_DEFAULT, walletSettingsPlusKeys, } from "../../api";
|
|
5
5
|
import { findRange, initScanCacheFile, lastRange, makeCacheRangeForHeight, mergeRanges, } from "./scanCache";
|
|
6
6
|
import { sendToCpuWorker } from "../worker-mains/cpubound-main";
|
|
7
7
|
import { log } from "../../io/logging";
|
|
@@ -299,6 +299,30 @@ function logBufStatus(blocksBuffer, workBuffer, ports, label) {
|
|
|
299
299
|
`[buf] ${label} blocks=[${bb.join(",")}] work=[${wb.join(",")}]`,
|
|
300
300
|
]);
|
|
301
301
|
}
|
|
302
|
+
function msToHHMM(ms) {
|
|
303
|
+
if (!Number.isFinite(ms) || ms < 0)
|
|
304
|
+
return "00:00";
|
|
305
|
+
const totalSeconds = Math.floor(ms / 1000);
|
|
306
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
307
|
+
const hours = Math.floor(minutes / 60);
|
|
308
|
+
const remainingMinutes = minutes % 60;
|
|
309
|
+
return `${String(hours).padStart(2, "0")}:${String(remainingMinutes).padStart(2, "0")}`;
|
|
310
|
+
}
|
|
311
|
+
function computeETA(cache, totalBlocksScanned, scanStartTime) {
|
|
312
|
+
const scanProgress = lastRange(cache.scanned_ranges)?.end || 0;
|
|
313
|
+
const elapsed = Date.now() - scanStartTime;
|
|
314
|
+
const daemonHeight = cache.daemon_height;
|
|
315
|
+
if (totalBlocksScanned <= 0 || elapsed <= 0 || daemonHeight <= 0)
|
|
316
|
+
return undefined;
|
|
317
|
+
const remaining = daemonHeight - scanProgress;
|
|
318
|
+
if (remaining <= 0)
|
|
319
|
+
return "00:00";
|
|
320
|
+
const blocksPerMs = totalBlocksScanned / elapsed;
|
|
321
|
+
if (blocksPerMs <= 0)
|
|
322
|
+
return undefined;
|
|
323
|
+
const etaMs = remaining / blocksPerMs;
|
|
324
|
+
return msToHHMM(etaMs);
|
|
325
|
+
}
|
|
302
326
|
/**
|
|
303
327
|
* coordinator main: async generator that drives the full scan cycle.
|
|
304
328
|
* takes only scanSettingsPath, derives everything else internally.
|
|
@@ -314,6 +338,8 @@ export async function* coordinatorMain(scanSettingsPath, pathPrefix) {
|
|
|
314
338
|
const blocksBuffer = ctx.blocksBuffer;
|
|
315
339
|
const workBuffer = ctx.workBuffer;
|
|
316
340
|
const blocksGenerator = ctx.blocksGenerator;
|
|
341
|
+
let totalBlocksScanned = 0;
|
|
342
|
+
let scanStartTime = Date.now();
|
|
317
343
|
let blocksPromise = blocksGenerator.next();
|
|
318
344
|
const scanGens = new Map();
|
|
319
345
|
const scanPromises = new Map();
|
|
@@ -379,7 +405,25 @@ export async function* coordinatorMain(scanSettingsPath, pathPrefix) {
|
|
|
379
405
|
const wc = work_to_be_done.wallet_configs.find((x) => x.primary_address === addr);
|
|
380
406
|
if (!wc)
|
|
381
407
|
throw new Error("[coordinatorMain] wallet config not found on process result");
|
|
408
|
+
// count blocks scanned in this work item for ETA
|
|
409
|
+
const doneItem = workBuffer.find((x) => x.work_uuid === value.work_uuid);
|
|
410
|
+
const blockCount = doneItem ? doneItem.to - doneItem.from + 1 : 0;
|
|
382
411
|
await processScanResultForWorkItem(value, workBuffer, blocksBuffer, getPathPrefix(scanSettingsPath, pathPrefix), wc?.secret_spend_key);
|
|
412
|
+
totalBlocksScanned += blockCount;
|
|
413
|
+
const eta = computeETA(wc.cache, totalBlocksScanned, scanStartTime);
|
|
414
|
+
// write connection status sync (includes eta)
|
|
415
|
+
if (eta) {
|
|
416
|
+
await readWriteConnectionStatusFile((cs) => {
|
|
417
|
+
cs.sync = {
|
|
418
|
+
...cs.sync,
|
|
419
|
+
scanned_ranges: wc.cache.scanned_ranges,
|
|
420
|
+
daemon_height: wc.cache.daemon_height,
|
|
421
|
+
current_scan_height: lastRange(wc.cache.scanned_ranges)?.end || 0,
|
|
422
|
+
eta,
|
|
423
|
+
timestamp: new Date().toISOString(),
|
|
424
|
+
};
|
|
425
|
+
}, scanSettingsPath);
|
|
426
|
+
}
|
|
383
427
|
const nextItem = workBuffer.find((x) => x.status !== "process_result_done" &&
|
|
384
428
|
x.walletConfig.primary_address === addr);
|
|
385
429
|
if (nextItem) {
|
|
@@ -452,6 +496,8 @@ export async function* coordinatorMainMultithreaded(scanSettingsPath, pathPrefix
|
|
|
452
496
|
const blocksBuffer = ctx.blocksBuffer;
|
|
453
497
|
const workBuffer = ctx.workBuffer;
|
|
454
498
|
const blocksGenerator = ctx.blocksGenerator;
|
|
499
|
+
let totalBlocksScanned = 0;
|
|
500
|
+
let scanStartTime = Date.now();
|
|
455
501
|
let blocksPromise = blocksGenerator.next();
|
|
456
502
|
const freePorts = [];
|
|
457
503
|
for (const port of cpuPorts) {
|
|
@@ -510,6 +556,22 @@ export async function* coordinatorMainMultithreaded(scanSettingsPath, pathPrefix
|
|
|
510
556
|
const to_be_processed = processable[0];
|
|
511
557
|
if (!to_be_processed)
|
|
512
558
|
continue;
|
|
559
|
+
const blockCount = to_be_processed.to - to_be_processed.from + 1;
|
|
560
|
+
totalBlocksScanned += blockCount;
|
|
561
|
+
const eta = computeETA(wallet.cache, totalBlocksScanned, scanStartTime);
|
|
562
|
+
// write connection status sync (includes eta)
|
|
563
|
+
if (eta) {
|
|
564
|
+
await readWriteConnectionStatusFile((cs) => {
|
|
565
|
+
cs.sync = {
|
|
566
|
+
...cs.sync,
|
|
567
|
+
scanned_ranges: wallet.cache.scanned_ranges,
|
|
568
|
+
daemon_height: wallet.cache.daemon_height,
|
|
569
|
+
current_scan_height: lastRange(wallet.cache.scanned_ranges)?.end || 0,
|
|
570
|
+
eta,
|
|
571
|
+
timestamp: new Date().toISOString(),
|
|
572
|
+
};
|
|
573
|
+
}, scanSettingsPath);
|
|
574
|
+
}
|
|
513
575
|
const res = await processWorkItem(to_be_processed, workBuffer, blocksBuffer, getPathPrefix(scanSettingsPath, pathPrefix), wallet.secret_spend_key);
|
|
514
576
|
// log("coordinatorMainMultithreaded", "processWorkItem result");
|
|
515
577
|
yield {
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import type { LogSetting, PossibleLogs } from "../../io/logging";
|
|
2
|
+
export declare const SCAN_SETTINGS_STORE_NAME_DEFAULT = "ScanSettings.json";
|
|
3
|
+
export declare const SUB_ADDRESS_INDEX_DEFAULT_VALUE = 1;
|
|
4
|
+
export type ScanSetting = {
|
|
5
|
+
primary_address: string;
|
|
6
|
+
subaddress_index?: number;
|
|
7
|
+
halted?: boolean;
|
|
8
|
+
wallet_route?: string;
|
|
9
|
+
wallet_name?: string;
|
|
10
|
+
wallet_slot?: number;
|
|
11
|
+
};
|
|
12
|
+
export type WriteScanSettingParams = {
|
|
13
|
+
primary_address: string;
|
|
14
|
+
start_height?: number | null;
|
|
15
|
+
subaddress_index?: number;
|
|
16
|
+
halted?: boolean;
|
|
17
|
+
scan_settings_path?: string;
|
|
18
|
+
node_url?: string;
|
|
19
|
+
wallet_route?: string;
|
|
20
|
+
wallet_name?: string;
|
|
21
|
+
wallet_slot?: number;
|
|
22
|
+
logs?: LogSetting;
|
|
23
|
+
logs_include?: PossibleLogs[];
|
|
24
|
+
logs_exclude?: PossibleLogs[];
|
|
25
|
+
};
|
|
26
|
+
export type ScanSettingOpened = {
|
|
27
|
+
primary_address: string;
|
|
28
|
+
start_height: number | null;
|
|
29
|
+
node_url: string;
|
|
30
|
+
subaddress_index?: number;
|
|
31
|
+
secret_view_key?: string;
|
|
32
|
+
halted?: boolean;
|
|
33
|
+
secret_spend_key?: string;
|
|
34
|
+
wallet_route?: string;
|
|
35
|
+
wallet_name?: string;
|
|
36
|
+
wallet_slot?: number;
|
|
37
|
+
};
|
|
38
|
+
export type ScanSettings = {
|
|
39
|
+
wallets: ScanSetting[];
|
|
40
|
+
node_url: string;
|
|
41
|
+
start_height: number | null;
|
|
42
|
+
merchant_confirmations?: number | null;
|
|
43
|
+
cpu_worker_count?: number;
|
|
44
|
+
logs?: LogSetting;
|
|
45
|
+
logs_include?: PossibleLogs[];
|
|
46
|
+
logs_exclude?: PossibleLogs[];
|
|
47
|
+
};
|
|
48
|
+
export type ScanSettingsOpened = {
|
|
49
|
+
wallets: (ScanSettingOpened | undefined)[];
|
|
50
|
+
node_url: string;
|
|
51
|
+
start_height: number | null;
|
|
52
|
+
merchant_confirmations?: number | null;
|
|
53
|
+
cpu_worker_count?: number;
|
|
54
|
+
logs?: LogSetting;
|
|
55
|
+
logs_include?: PossibleLogs[];
|
|
56
|
+
logs_exclude?: PossibleLogs[];
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Writes scan settings to the default or specified storage file in json.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```
|
|
63
|
+
* const settings: ScanSettings = {
|
|
64
|
+
* wallets: [{
|
|
65
|
+
* primary_address: "5dsf...",
|
|
66
|
+
* start_height: 1741707,
|
|
67
|
+
* }],
|
|
68
|
+
* node_urls: ["https://monerooo.roooo"]
|
|
69
|
+
* };
|
|
70
|
+
* await writeScanSettings(settings);
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* @param scan_settings - The complete {@link ScanSettings} configuration to persist.
|
|
74
|
+
* @param scan_settings_path - Optional path for the settings file. Defaults to `SCAN_SETTINGS_STORE_NAME_DEFAULT`.
|
|
75
|
+
* @returns A promise that resolves when the file is successfully written.
|
|
76
|
+
* @throws Will throw if file writing fails (e.g., permissions, disk space).
|
|
77
|
+
*/
|
|
78
|
+
export declare function writeScanSettings(scan_settings: ScanSettings, scan_settings_path?: string): Promise<number>;
|
|
79
|
+
export type WriteScanSettingsFileParams = {
|
|
80
|
+
scan_settings_path?: string;
|
|
81
|
+
writeCallback: (settings: ScanSettings) => void | Promise<void>;
|
|
82
|
+
};
|
|
83
|
+
export declare function writeScanSettingsFileDefaultLocation(params: WriteScanSettingsFileParams): Promise<void>;
|
|
84
|
+
export declare function writeStartHeightToScanSettings(start_height: number | null, scan_settings_path?: string): Promise<number>;
|
|
85
|
+
export declare function writeDaemonHeightAsStartHeightToScanSettings(node_url: string, scan_settings_path?: string): Promise<number>;
|
|
86
|
+
export declare function cullTooLargeScanHeight(node_url: string, scan_settings_path?: string): Promise<number>;
|
|
87
|
+
export declare function writeNodeUrlToScanSettings(node_url: string, scan_settings_path?: string): Promise<number>;
|
|
88
|
+
export declare function readNodeUrlFromScanSettings(scan_settings_path?: string): Promise<string | undefined>;
|
|
89
|
+
/**
|
|
90
|
+
* Writes the merchant confirmation threshold to scan settings.
|
|
91
|
+
*
|
|
92
|
+
* This setting only has effect when the wallet is run as part of a checkout system,
|
|
93
|
+
* that accepts payments.
|
|
94
|
+
* It determines after how many confirmations a transaction is accepted
|
|
95
|
+
* for a payment. The actual enforcement of this policy is left to the
|
|
96
|
+
* custom payment-system code that consumes this library.
|
|
97
|
+
*
|
|
98
|
+
* @param merchant_confirmations - Number of confirmations required before
|
|
99
|
+
* accepting a payment, or `null` to leave it unset.
|
|
100
|
+
* @param scan_settings_path - Optional path for the settings file.
|
|
101
|
+
* Defaults to `SCAN_SETTINGS_STORE_NAME_DEFAULT`.
|
|
102
|
+
* @returns A promise that resolves when the file is successfully written.
|
|
103
|
+
*/
|
|
104
|
+
export declare function writeMerchantConfirmationsToScanSettings(merchant_confirmations: number | null, scan_settings_path?: string): Promise<number>;
|
|
105
|
+
/**
|
|
106
|
+
* Reads the merchant confirmation threshold from scan settings.
|
|
107
|
+
*
|
|
108
|
+
* This setting only has effect when the wallet is run as part of a checkout system,
|
|
109
|
+
* that accepts payments.
|
|
110
|
+
*
|
|
111
|
+
* It determines after how many confirmations a transaction is accepted
|
|
112
|
+
* for a payment. The actual enforcement of this policy is left to the
|
|
113
|
+
* custom payment-system code that consumes this library.
|
|
114
|
+
*
|
|
115
|
+
* @param scan_settings_path - Optional path for the settings file.
|
|
116
|
+
* Defaults to `SCAN_SETTINGS_STORE_NAME_DEFAULT`.
|
|
117
|
+
* @returns The configured number of confirmations, null, or `undefined`
|
|
118
|
+
* if the setting has not been persisted yet.
|
|
119
|
+
*/
|
|
120
|
+
export declare function readMerchantConfirmationsFromScanSettings(scan_settings_path?: string): Promise<number | null | undefined>;
|
|
121
|
+
/**
|
|
122
|
+
* Reads scan settings from the default or specified storage file.
|
|
123
|
+
* secret_view_key and spend_private_key are read from environment variables
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```
|
|
127
|
+
* const settings = await readScanSettings();
|
|
128
|
+
* if (settings) {
|
|
129
|
+
* console.log(settings.wallets?.primary_address);
|
|
130
|
+
* }
|
|
131
|
+
* ```
|
|
132
|
+
*
|
|
133
|
+
* @param scan_settings_path - Path to the settings file. Defaults to `SCAN_SETTINGS_STORE_NAME_DEFAULT`.
|
|
134
|
+
* @returns The parsed {@link ScanSettings} object if file exists and is valid JSON, otherwise `undefined`.
|
|
135
|
+
*/
|
|
136
|
+
export declare function readScanSettings(scan_settings_path?: string): Promise<ScanSettingsOpened | undefined>;
|
|
137
|
+
export declare function readPrivateSpendKeyFromEnv(primary_address: string): string | undefined;
|
|
138
|
+
export declare function readPrivateViewKeyFromEnv(primary_address: string): string | undefined;
|
|
139
|
+
export declare function readWalletFromScanSettings(primary_address: string, scan_settings_path?: string): Promise<ScanSettingOpened | undefined>;
|
|
140
|
+
export declare function readWalletsFromScanSettings(scan_settings_path?: string): Promise<ScanSettingOpened[]>;
|
|
141
|
+
export declare function walletSettingsPlusKeys(wallet_settings: ScanSettingOpened, secret_view_key?: string, secret_spend_key?: string): Promise<{
|
|
142
|
+
secret_view_key: string;
|
|
143
|
+
secret_spend_key: string | undefined;
|
|
144
|
+
primary_address: string;
|
|
145
|
+
start_height: number | null;
|
|
146
|
+
node_url: string;
|
|
147
|
+
subaddress_index?: number;
|
|
148
|
+
halted?: boolean;
|
|
149
|
+
wallet_route?: string;
|
|
150
|
+
wallet_name?: string;
|
|
151
|
+
wallet_slot?: number;
|
|
152
|
+
}>;
|
|
153
|
+
export declare function writeWalletToScanSettings(params: WriteScanSettingParams): Promise<number>;
|
|
154
|
+
export declare function openScanSettingsFile(scan_settings_path?: string): Promise<ScanSettings | undefined>;
|
|
155
|
+
export declare function openNonHaltedWallets(scan_settings_path?: string): Promise<ScanSettingOpened[]>;
|
|
156
|
+
export declare function getNonHaltedWallets(scan_settings?: ScanSettings): ScanSetting[];
|
|
157
|
+
export declare function doesScanSettingsFileExist(scan_settings_path?: string): Promise<boolean>;
|
|
158
|
+
/**
|
|
159
|
+
* Either 1. the consumer explicitly sets a pathprefix, we extract it from the scan_settings_path
|
|
160
|
+
* or we pick the default which is nothing
|
|
161
|
+
* @param scan_settings_path optional scan settings path
|
|
162
|
+
* @param pathPrefix optional path prefix
|
|
163
|
+
* @returns definite path prefix
|
|
164
|
+
*/
|
|
165
|
+
export declare function getPathPrefix(scan_settings_path?: string, pathPrefix?: string): string;
|