@spirobel/monero-wallet-api 0.5.1 → 0.5.3
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 +55 -7
- package/dist/scanning-syncing/scanresult/scanCacheOpened.js +191 -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 +127 -0
- package/dist/scansettings/scanSettingsOpened.js +475 -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,37 @@ 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
|
+
}
|
|
211
|
+
async setWalletName(name) {
|
|
212
|
+
await this._scanSettings.setWalletName(this.view_pair.primary_address, name);
|
|
213
|
+
}
|
|
171
214
|
async retry() {
|
|
172
215
|
if (this.worker) {
|
|
173
216
|
this.worker.terminate();
|
|
174
217
|
delete this.worker;
|
|
175
218
|
}
|
|
176
|
-
|
|
177
|
-
|
|
219
|
+
// scansettings so external changes (e.g. from a sidebar frontend instance) are picked up
|
|
220
|
+
await this._scanSettings.reload();
|
|
221
|
+
const walletStillExists = this._scanSettings.walletExists(this.primary_address);
|
|
222
|
+
if (walletStillExists) {
|
|
178
223
|
// if there is no scan settings file,
|
|
179
224
|
// the retry loop is stopped.
|
|
180
225
|
// the wallet reset happens through deleting all the scan setting + cache files
|
|
@@ -182,7 +227,7 @@ export class ScanCacheOpened {
|
|
|
182
227
|
//TODO ? write connection status retry
|
|
183
228
|
await this.unpause();
|
|
184
229
|
}
|
|
185
|
-
return
|
|
230
|
+
return walletStillExists;
|
|
186
231
|
}
|
|
187
232
|
async sendTransaction(signedTx) {
|
|
188
233
|
const node = await NodeUrl.create(this.node_url);
|
|
@@ -480,7 +525,9 @@ export class ScanCacheOpened {
|
|
|
480
525
|
* @returns Adressstring
|
|
481
526
|
*/
|
|
482
527
|
async makeSubaddress() {
|
|
483
|
-
|
|
528
|
+
// reload scansettings so external changes (e.g. from a sidebar frontend instance) are picked up
|
|
529
|
+
await this._scanSettings.reload();
|
|
530
|
+
const walletSettings = this._scanSettings.getWallet(this.view_pair.primary_address);
|
|
484
531
|
if (!walletSettings)
|
|
485
532
|
throw new Error(`wallet not found in settings. did you call openwallet with the right params?
|
|
486
533
|
Either wrong file name supplied to params.scan_settings_path: ${this.scan_settings_path}
|
|
@@ -489,11 +536,7 @@ export class ScanCacheOpened {
|
|
|
489
536
|
const minor = last_subaddress_index + 1;
|
|
490
537
|
const subaddress = this.view_pair.makeSubaddress(minor);
|
|
491
538
|
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
|
-
});
|
|
539
|
+
await this._scanSettings.setSubaddressIndex(this.view_pair.primary_address, minor);
|
|
497
540
|
const created_at_height = lastRange(this._cache.scanned_ranges)?.end || 0;
|
|
498
541
|
const created_at_timestamp = new Date().getTime();
|
|
499
542
|
const new_subaddress = {
|
|
@@ -529,11 +572,7 @@ export class ScanCacheOpened {
|
|
|
529
572
|
async pause() {
|
|
530
573
|
if (this.worker)
|
|
531
574
|
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
|
-
});
|
|
575
|
+
return await this._scanSettings.haltWallet(this.view_pair.primary_address);
|
|
537
576
|
}
|
|
538
577
|
stopWorker() {
|
|
539
578
|
if (this.worker) {
|
|
@@ -566,11 +605,7 @@ export class ScanCacheOpened {
|
|
|
566
605
|
});
|
|
567
606
|
});
|
|
568
607
|
}
|
|
569
|
-
return await
|
|
570
|
-
primary_address: this.view_pair.primary_address,
|
|
571
|
-
halted: false,
|
|
572
|
-
scan_settings_path: this.scan_settings_path,
|
|
573
|
-
});
|
|
608
|
+
return await this._scanSettings.unhaltWallet(this.view_pair.primary_address);
|
|
574
609
|
}
|
|
575
610
|
/**
|
|
576
611
|
* selectInputs returns array of inputs, whose sum is larger than amount
|
|
@@ -635,11 +670,6 @@ export class ScanCacheOpened {
|
|
|
635
670
|
this._cache = params.newCache;
|
|
636
671
|
if (!this._no_stats)
|
|
637
672
|
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
673
|
for (const listener of this.notifyListeners) {
|
|
644
674
|
if (listener)
|
|
645
675
|
listener(params);
|
|
@@ -660,7 +690,8 @@ export class ScanCacheOpened {
|
|
|
660
690
|
worker = undefined;
|
|
661
691
|
last_eta_height = null;
|
|
662
692
|
last_eta_timestamp = null;
|
|
663
|
-
constructor(view_pair, wallet_route, wallet_name, wallet_slot, no_worker, masterCacheChanged, _start_height, scan_settings_path, pathPrefix, workerError) {
|
|
693
|
+
constructor(_scanSettings, view_pair, wallet_route, wallet_name, wallet_slot, no_worker, masterCacheChanged, _start_height, scan_settings_path, pathPrefix, workerError) {
|
|
694
|
+
this._scanSettings = _scanSettings;
|
|
664
695
|
this.view_pair = view_pair;
|
|
665
696
|
this.wallet_route = wallet_route;
|
|
666
697
|
this.wallet_name = wallet_name;
|
|
@@ -676,7 +707,9 @@ export class ScanCacheOpened {
|
|
|
676
707
|
notifyListeners = [];
|
|
677
708
|
}
|
|
678
709
|
export class ManyScanCachesOpened {
|
|
679
|
-
|
|
710
|
+
connectionStatusOpened;
|
|
711
|
+
_scanSettings;
|
|
712
|
+
_options;
|
|
680
713
|
get start_height() {
|
|
681
714
|
if (this.wallets.length === 0)
|
|
682
715
|
return null;
|
|
@@ -716,18 +749,70 @@ export class ManyScanCachesOpened {
|
|
|
716
749
|
const masterWallet = this.wallets[0];
|
|
717
750
|
return await masterWallet.changeNodeUrl(node_url);
|
|
718
751
|
}
|
|
752
|
+
get merchant_confirmations() {
|
|
753
|
+
if (this.wallets.length === 0)
|
|
754
|
+
return undefined;
|
|
755
|
+
return this.wallets[0]?.merchant_confirmations;
|
|
756
|
+
}
|
|
757
|
+
get cpu_worker_count() {
|
|
758
|
+
if (this.wallets.length === 0)
|
|
759
|
+
return undefined;
|
|
760
|
+
return this.wallets[0]?.cpu_worker_count;
|
|
761
|
+
}
|
|
762
|
+
get logs() {
|
|
763
|
+
if (this.wallets.length === 0)
|
|
764
|
+
return undefined;
|
|
765
|
+
return this.wallets[0]?.logs;
|
|
766
|
+
}
|
|
767
|
+
get logs_include() {
|
|
768
|
+
if (this.wallets.length === 0)
|
|
769
|
+
return undefined;
|
|
770
|
+
return this.wallets[0]?.logs_include;
|
|
771
|
+
}
|
|
772
|
+
get logs_exclude() {
|
|
773
|
+
if (this.wallets.length === 0)
|
|
774
|
+
return undefined;
|
|
775
|
+
return this.wallets[0]?.logs_exclude;
|
|
776
|
+
}
|
|
777
|
+
get connectionStatus() {
|
|
778
|
+
return this.connectionStatusOpened.connectionStatus;
|
|
779
|
+
}
|
|
780
|
+
get daemonHeight() {
|
|
781
|
+
return this.connectionStatusOpened.daemonHeight;
|
|
782
|
+
}
|
|
783
|
+
watchConnectionStatus(intervalMs) {
|
|
784
|
+
this.connectionStatusOpened.watch(intervalMs);
|
|
785
|
+
}
|
|
786
|
+
unwatchConnectionStatus() {
|
|
787
|
+
this.connectionStatusOpened.unwatch();
|
|
788
|
+
}
|
|
789
|
+
async setMerchantConfirmations(merchant_confirmations) {
|
|
790
|
+
if (this.wallets.length === 0)
|
|
791
|
+
throw new Error("no wallets");
|
|
792
|
+
return await this.wallets[0].setMerchantConfirmations(merchant_confirmations);
|
|
793
|
+
}
|
|
794
|
+
async setCpuWorkerCount(cpu_worker_count) {
|
|
795
|
+
if (this.wallets.length === 0)
|
|
796
|
+
throw new Error("no wallets");
|
|
797
|
+
return await this.wallets[0].setCpuWorkerCount(cpu_worker_count);
|
|
798
|
+
}
|
|
799
|
+
async setLogSettings(logs, logs_include, logs_exclude) {
|
|
800
|
+
if (this.wallets.length === 0)
|
|
801
|
+
throw new Error("no wallets");
|
|
802
|
+
return await this.wallets[0].setLogSettings(logs, logs_include, logs_exclude);
|
|
803
|
+
}
|
|
804
|
+
async setWalletName(primary_address, name) {
|
|
805
|
+
await this._scanSettings.setWalletName(primary_address, name);
|
|
806
|
+
}
|
|
719
807
|
async changeStartHeight(start_height) {
|
|
720
808
|
if (this.wallets.length === 0)
|
|
721
809
|
throw new Error("no wallets");
|
|
722
810
|
const masterWallet = this.wallets[0];
|
|
723
811
|
return await masterWallet.changeStartHeight(start_height);
|
|
724
812
|
}
|
|
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);
|
|
813
|
+
static async _buildWallets(scanSettingsOpened, options) {
|
|
814
|
+
const { scan_settings_path, pathPrefix, no_worker, no_stats, workerError, notifyMasterChanged, } = options;
|
|
815
|
+
const nonHaltedWallets = scanSettingsOpened.wallets.filter((wallet) => !wallet?.halted);
|
|
731
816
|
if (!nonHaltedWallets.length)
|
|
732
817
|
return undefined;
|
|
733
818
|
const openedWallets = [];
|
|
@@ -776,15 +861,71 @@ export class ManyScanCachesOpened {
|
|
|
776
861
|
});
|
|
777
862
|
openedWallets.push(onlyWallet);
|
|
778
863
|
}
|
|
779
|
-
return
|
|
864
|
+
return openedWallets;
|
|
865
|
+
}
|
|
866
|
+
static async create(options) {
|
|
867
|
+
const { scan_settings_path, pathPrefix, onConnectionStatusChange, connectionStatusIntervalMs, autoRetry, retryDelayMs, } = options;
|
|
868
|
+
const scanSettingsOpened = await ScanSettingsOpened.create(scan_settings_path, pathPrefix);
|
|
869
|
+
if (!scanSettingsOpened.wallets || scanSettingsOpened.wallets.length === 0)
|
|
870
|
+
throw new Error(`no wallets in settings file. Did you supply the right path?
|
|
871
|
+
are there wallets in the default '${SCAN_SETTINGS_STORE_NAME_DEFAULT}' file?`);
|
|
872
|
+
// wrap workerError with auto-retry
|
|
873
|
+
let retryTimer;
|
|
874
|
+
let instance = null;
|
|
875
|
+
const newOptions = { ...options };
|
|
876
|
+
if (autoRetry) {
|
|
877
|
+
const originalError = options.workerError;
|
|
878
|
+
newOptions.workerError = (err) => {
|
|
879
|
+
originalError?.(err);
|
|
880
|
+
if (!retryTimer) {
|
|
881
|
+
retryTimer = setTimeout(() => {
|
|
882
|
+
retryTimer = undefined;
|
|
883
|
+
instance?.retry();
|
|
884
|
+
}, retryDelayMs ?? 1000);
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
const wallets = await this._buildWallets(scanSettingsOpened, newOptions);
|
|
889
|
+
if (!wallets)
|
|
890
|
+
return undefined;
|
|
891
|
+
const csOpened = new ConnectionStatusOpened(scan_settings_path || SCAN_SETTINGS_STORE_NAME_DEFAULT, onConnectionStatusChange ?? null);
|
|
892
|
+
csOpened.watch(connectionStatusIntervalMs);
|
|
893
|
+
instance = new ManyScanCachesOpened(wallets, csOpened, scanSettingsOpened, newOptions);
|
|
894
|
+
return instance;
|
|
895
|
+
}
|
|
896
|
+
async buildWallets() {
|
|
897
|
+
for (const w of this._wallets) {
|
|
898
|
+
w.stopWorker();
|
|
899
|
+
}
|
|
900
|
+
await this._scanSettings.reload();
|
|
901
|
+
const newWallets = await ManyScanCachesOpened._buildWallets(this._scanSettings, this._options);
|
|
902
|
+
if (!newWallets)
|
|
903
|
+
throw new Error("no non-halted wallets left after rebuild, use addViewWallet or addSpendWallet first");
|
|
904
|
+
this._wallets = newWallets;
|
|
905
|
+
}
|
|
906
|
+
async addViewWallet(primary_address, view_key, fields) {
|
|
907
|
+
await this._scanSettings.addViewWallet(primary_address, view_key, fields);
|
|
908
|
+
await this.buildWallets();
|
|
909
|
+
}
|
|
910
|
+
async addSpendWallet(wallet_secret, fields) {
|
|
911
|
+
await this._scanSettings.addSpendWallet(wallet_secret, fields);
|
|
912
|
+
await this.buildWallets();
|
|
913
|
+
}
|
|
914
|
+
async removeWallet(primary_address) {
|
|
915
|
+
await this._scanSettings.removeWallet(primary_address);
|
|
916
|
+
await this.buildWallets();
|
|
780
917
|
}
|
|
781
|
-
/**
|
|
782
|
-
* feed the master wallet and therefore all wallets
|
|
783
|
-
*/
|
|
784
918
|
async feed(params) {
|
|
785
919
|
await this.wallets[0].feed(params);
|
|
786
920
|
}
|
|
787
|
-
|
|
788
|
-
|
|
921
|
+
_wallets = [];
|
|
922
|
+
get wallets() {
|
|
923
|
+
return this._wallets;
|
|
924
|
+
}
|
|
925
|
+
constructor(wallets, connectionStatusOpened, _scanSettings, _options) {
|
|
926
|
+
this.connectionStatusOpened = connectionStatusOpened;
|
|
927
|
+
this._scanSettings = _scanSettings;
|
|
928
|
+
this._options = _options;
|
|
929
|
+
this._wallets = wallets;
|
|
789
930
|
}
|
|
790
931
|
}
|
|
@@ -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;
|