@spirobel/monero-wallet-api 0.5.3 → 0.5.4

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @spirobel/monero-wallet-api
2
2
 
3
+ ## install
4
+
5
+ ```bash
6
+ bun add @spirobel/monero-wallet-api
7
+ ```
8
+
3
9
  ## quick start
4
10
 
5
11
  ```ts
package/dist/api.d.ts CHANGED
@@ -14,7 +14,7 @@ export * from "./multisig/multisig";
14
14
  export * from "./multisig/DistributedKeyGenerator";
15
15
  export * from "./multisig/escrow";
16
16
  export * from "./multisig/MultiSigTxSigner";
17
- export { startWebworker, createWebworker, makeWebworkerScript, } from "./scanning-syncing/backgroundWorker";
17
+ export { createWebworker, makeWebworkerScript, } from "./scanning-syncing/backgroundWorker";
18
18
  export { spendable } from "./scanning-syncing/scanresult/scanResult";
19
19
  export { openWallets, openWallet } from "./scanning-syncing/openWallet";
20
20
  export * from "./scanning-syncing/blocksbuffer/blocksBufferFetchLoop";
package/dist/api.js CHANGED
@@ -13,7 +13,7 @@ export * from "./multisig/multisig";
13
13
  export * from "./multisig/DistributedKeyGenerator";
14
14
  export * from "./multisig/escrow";
15
15
  export * from "./multisig/MultiSigTxSigner";
16
- export { startWebworker, createWebworker, makeWebworkerScript, } from "./scanning-syncing/backgroundWorker";
16
+ export { createWebworker, makeWebworkerScript, } from "./scanning-syncing/backgroundWorker";
17
17
  export { spendable } from "./scanning-syncing/scanresult/scanResult";
18
18
  export { openWallets, openWallet } from "./scanning-syncing/openWallet";
19
19
  export * from "./scanning-syncing/blocksbuffer/blocksBufferFetchLoop";
@@ -13,7 +13,7 @@
13
13
  * "logs_include": ["blocksBufferFetchLoop", "makeWorkItemsFromBlocksBuffer", "logBufStatus"]
14
14
  * -> block fetching, reconciliation, buffer state.
15
15
  *
16
- * "logs_include": ["processScanResult", "processWorkItem", "processScanResultForWorkItem"]
16
+ * "logs_include": ["processScanResult", "processWorkItem"]
17
17
  * -> result processing, cache updates, reorg detection.
18
18
  *
19
19
  * "logs_include": ["handleConnectionStatusChanges", "handleScanError"]
@@ -23,7 +23,7 @@
23
23
  * "logs": "file" writes to `<coordinator|cpubound|mainthread>-<id>-<timestamp>.log`.
24
24
  * "logs": "console-and-file" writes to file and console.
25
25
  */
26
- export declare const LOGGING_FUNCTIONS: readonly ["findWorkToBeDone", "workToBeDoneForBatch", "makeWorkItemsFromBlocksBuffer", "reconcileWorkItemDone", "processScanResultForWorkItem", "processWorkItem", "logBufStatus", "coordinatorMain", "coordinatorMainMultithreaded", "scheduleWorkOnCpuPorts", "processScanResult", "handleScanError", "createWebworker", "startWebworker", "blocksBufferFetchLoop", "reduceStartHeightToTip", "handleConnectionStatusChanges", "handleCpuboundScan", "coordinatorMainWorker", "CPU_PORT_HANDLER", "atomicWrite", "multisigMainWorkerCall"];
26
+ export declare const LOGGING_FUNCTIONS: readonly ["findWorkToBeDone", "workToBeDoneForBatch", "makeWorkItemsFromBlocksBuffer", "reconcileWorkItemDone", "processWorkItem", "logBufStatus", "coordinatorMain", "coordinatorMainMultithreaded", "scheduleWorkOnCpuPorts", "processScanResult", "handleScanError", "createWebworker", "startWebworker", "blocksBufferFetchLoop", "reduceStartHeightToTip", "handleConnectionStatusChanges", "handleCpuboundScan", "coordinatorMainWorker", "CPU_PORT_HANDLER", "atomicWrite", "multisigMainWorkerCall"];
27
27
  export type PossibleLogs = (typeof LOGGING_FUNCTIONS)[number];
28
28
  export type LogSetting = "console" | "file" | "console-and-file" | "off";
29
29
  export type FileLogMessage = {
@@ -31,10 +31,10 @@ export type FileLogMessage = {
31
31
  message: any;
32
32
  };
33
33
  export declare function setupLoggingPath(scan_settings_path: string, path_prefix: string, role: "coordinator" | "cpubound" | "mainthread" | "multisig", cpu_worker_id?: number): Promise<{
34
- logs: LogSetting | undefined;
35
- logs_include: ("findWorkToBeDone" | "workToBeDoneForBatch" | "makeWorkItemsFromBlocksBuffer" | "reconcileWorkItemDone" | "processScanResultForWorkItem" | "processWorkItem" | "logBufStatus" | "coordinatorMain" | "coordinatorMainMultithreaded" | "scheduleWorkOnCpuPorts" | "processScanResult" | "handleScanError" | "createWebworker" | "startWebworker" | "blocksBufferFetchLoop" | "reduceStartHeightToTip" | "handleConnectionStatusChanges" | "handleCpuboundScan" | "coordinatorMainWorker" | "CPU_PORT_HANDLER" | "atomicWrite" | "multisigMainWorkerCall")[] | undefined;
36
- logs_exclude: ("findWorkToBeDone" | "workToBeDoneForBatch" | "makeWorkItemsFromBlocksBuffer" | "reconcileWorkItemDone" | "processScanResultForWorkItem" | "processWorkItem" | "logBufStatus" | "coordinatorMain" | "coordinatorMainMultithreaded" | "scheduleWorkOnCpuPorts" | "processScanResult" | "handleScanError" | "createWebworker" | "startWebworker" | "blocksBufferFetchLoop" | "reduceStartHeightToTip" | "handleConnectionStatusChanges" | "handleCpuboundScan" | "coordinatorMainWorker" | "CPU_PORT_HANDLER" | "atomicWrite" | "multisigMainWorkerCall")[] | undefined;
34
+ logs: "console" | "file" | "console-and-file";
35
+ logs_include: ("findWorkToBeDone" | "workToBeDoneForBatch" | "makeWorkItemsFromBlocksBuffer" | "reconcileWorkItemDone" | "processWorkItem" | "logBufStatus" | "coordinatorMain" | "coordinatorMainMultithreaded" | "scheduleWorkOnCpuPorts" | "processScanResult" | "handleScanError" | "createWebworker" | "startWebworker" | "blocksBufferFetchLoop" | "reduceStartHeightToTip" | "handleConnectionStatusChanges" | "handleCpuboundScan" | "coordinatorMainWorker" | "CPU_PORT_HANDLER" | "atomicWrite" | "multisigMainWorkerCall")[] | undefined;
36
+ logs_exclude: ("findWorkToBeDone" | "workToBeDoneForBatch" | "makeWorkItemsFromBlocksBuffer" | "reconcileWorkItemDone" | "processWorkItem" | "logBufStatus" | "coordinatorMain" | "coordinatorMainMultithreaded" | "scheduleWorkOnCpuPorts" | "processScanResult" | "handleScanError" | "createWebworker" | "startWebworker" | "blocksBufferFetchLoop" | "reduceStartHeightToTip" | "handleConnectionStatusChanges" | "handleCpuboundScan" | "coordinatorMainWorker" | "CPU_PORT_HANDLER" | "atomicWrite" | "multisigMainWorkerCall")[] | undefined;
37
37
  file_logbuffer: FileLogMessage[];
38
38
  global_logging_path: string;
39
- }>;
39
+ } | undefined>;
40
40
  export declare function log(fnname: string, message: any): void;
@@ -15,7 +15,7 @@ import { atomicWrite } from "./atomicWrite";
15
15
  * "logs_include": ["blocksBufferFetchLoop", "makeWorkItemsFromBlocksBuffer", "logBufStatus"]
16
16
  * -> block fetching, reconciliation, buffer state.
17
17
  *
18
- * "logs_include": ["processScanResult", "processWorkItem", "processScanResultForWorkItem"]
18
+ * "logs_include": ["processScanResult", "processWorkItem"]
19
19
  * -> result processing, cache updates, reorg detection.
20
20
  *
21
21
  * "logs_include": ["handleConnectionStatusChanges", "handleScanError"]
@@ -31,7 +31,6 @@ export const LOGGING_FUNCTIONS = [
31
31
  "workToBeDoneForBatch",
32
32
  "makeWorkItemsFromBlocksBuffer",
33
33
  "reconcileWorkItemDone",
34
- "processScanResultForWorkItem",
35
34
  "processWorkItem",
36
35
  "logBufStatus",
37
36
  "coordinatorMain",
@@ -63,6 +62,8 @@ export const LOGGING_FUNCTIONS = [
63
62
  export async function setupLoggingPath(scan_settings_path, path_prefix, role, cpu_worker_id) {
64
63
  const scanSettings = await openScanSettingsFile(scan_settings_path);
65
64
  const logs = scanSettings?.logs;
65
+ if (logs === "off" || typeof logs === "undefined")
66
+ return;
66
67
  const logs_include = scanSettings?.logs_include;
67
68
  const logs_exclude = scanSettings?.logs_exclude;
68
69
  const file_logbuffer = [];
@@ -147,7 +148,7 @@ export function log(fnname, message) {
147
148
  if (!setup)
148
149
  return;
149
150
  const { logs, logs_include, logs_exclude, file_logbuffer } = setup;
150
- if (logs === "off")
151
+ if (logs === "off" || typeof logs === "undefined")
151
152
  return;
152
153
  // Apply include/exclude filters
153
154
  if (logs_include && logs_include.length > 0 && !logs_include.includes(fnname))
@@ -1,5 +1,4 @@
1
1
  import { ed25519, x25519 } from "@noble/curves/ed25519.js";
2
- import { numberToBytesLE } from "@noble/curves/utils.js";
3
2
  import { blake3 } from "@noble/hashes/blake3.js";
4
3
  import { vk_from_entropy } from "../api";
5
4
  export function makeEscrowContext(context_index) {
@@ -7,5 +7,4 @@ export type WorkerSet = {
7
7
  };
8
8
  export declare function createWebworker(handle_result?: (result: CacheChangedCallbackParameters) => void, scan_settings_path?: string, pathPrefix?: string, handle_error?: (error: unknown) => void): Promise<WorkerSet | undefined>;
9
9
  export declare function makeWebworkerScript(): string;
10
- export declare function startWebworker(handle_result?: (result: unknown) => void, handle_error?: (error: unknown) => void): Worker;
11
10
  export declare function startWebworkerReady(): Promise<Worker>;
@@ -1,6 +1,6 @@
1
1
  import { LOCAL_NODE_DEFAULT_URL } from "../node-interaction/nodeUrl";
2
2
  import {} from "./scanresult/scanCache";
3
- import { openScanSettingsFile, SCAN_SETTINGS_STORE_NAME_DEFAULT } from "../api";
3
+ import { openScanSettingsFile, SCAN_SETTINGS_STORE_NAME_DEFAULT, sleep, } from "../api";
4
4
  import { log, setupLoggingPath } from "../io/logging";
5
5
  import { workerMainCode } from "./worker-entrypoints/worker";
6
6
  export const CPU_POOL_SIZE = 4;
@@ -8,10 +8,11 @@ export async function createWebworker(handle_result, scan_settings_path, pathPre
8
8
  try {
9
9
  const resolvedPath = scan_settings_path || SCAN_SETTINGS_STORE_NAME_DEFAULT;
10
10
  const scanSettings = await openScanSettingsFile(resolvedPath);
11
- await setupLoggingPath(resolvedPath, pathPrefix ?? "", "mainthread");
11
+ if (scanSettings?.logs !== "off" && scanSettings?.logs)
12
+ await setupLoggingPath(resolvedPath, pathPrefix ?? "", "mainthread");
12
13
  const node_url = scanSettings?.node_url || LOCAL_NODE_DEFAULT_URL;
13
14
  const cpu_worker_count = typeof scanSettings?.cpu_worker_count !== "undefined"
14
- ? scanSettings?.cpu_worker_count
15
+ ? scanSettings?.cpu_worker_count || 1
15
16
  : CPU_POOL_SIZE;
16
17
  // spawn CPU workers
17
18
  const cpuWorkers = [];
@@ -32,6 +33,7 @@ export async function createWebworker(handle_result, scan_settings_path, pathPre
32
33
  };
33
34
  cpuWorkers.push(cpuWorker);
34
35
  cpuPorts.push(channel.port2);
36
+ await sleep(50);
35
37
  }
36
38
  const coordinationWorker = await startWebworkerReady();
37
39
  coordinationWorker.postMessage({
@@ -39,9 +41,7 @@ export async function createWebworker(handle_result, scan_settings_path, pathPre
39
41
  scan_settings_path: resolvedPath,
40
42
  pathPrefix,
41
43
  role: "coordinator",
42
- node_url,
43
- start_height: 0,
44
- }, cpuPorts); // transfer CPU port2s to coordination worker
44
+ }, cpuPorts); // transfer CPU ports to coordination worker
45
45
  log("createWebworker", [
46
46
  "coordinator worker started, node_url=" + node_url,
47
47
  ", cpuPorts=" + cpuPorts.length,
@@ -60,9 +60,17 @@ export async function createWebworker(handle_result, scan_settings_path, pathPre
60
60
  fetchWorker: coordinationWorker,
61
61
  cpuWorkers,
62
62
  terminate: () => {
63
- coordinationWorker.terminate();
64
- for (const w of cpuWorkers)
63
+ for (const p of cpuPorts) {
64
+ p.close();
65
+ }
66
+ coordinationWorker.onmessage = null;
67
+ coordinationWorker.onerror = null;
68
+ for (const w of cpuWorkers) {
69
+ w.onerror = null;
70
+ w.onmessage = null;
65
71
  w.terminate();
72
+ }
73
+ coordinationWorker.terminate();
66
74
  },
67
75
  };
68
76
  }
@@ -73,36 +81,16 @@ export async function createWebworker(handle_result, scan_settings_path, pathPre
73
81
  export function makeWebworkerScript() {
74
82
  return workerMainCode;
75
83
  }
76
- export function startWebworker(handle_result, handle_error) {
77
- const blob = new Blob([workerMainCode], {
78
- type: "text/javascript",
79
- });
80
- const url = URL.createObjectURL(blob);
81
- const worker = new Worker(url, { type: "module" });
82
- worker.onmessage = (event) => {
83
- switch (event.data.type) {
84
- case "RESULT":
85
- if (handle_result)
86
- handle_result(event.data.payload);
87
- break;
88
- case "ERROR":
89
- case "scan_error":
90
- if (handle_error)
91
- handle_error(event.data.payload ?? event.data.error);
92
- break;
93
- case "DEBUG":
94
- log("startWebworker", ["worker debug:", event.data.payload]);
95
- break;
96
- }
97
- };
98
- return worker;
99
- }
84
+ const blob = new Blob([workerMainCode], {
85
+ type: "text/javascript",
86
+ });
87
+ const url = URL.createObjectURL(blob);
100
88
  // creates a worker via startWebworker, then waits for it to signal
101
89
  // WORKER_READY (meaning self.onmessage = handleMessage has executed).
102
90
  // after that, postMessage is safe, no race between worker startup
103
91
  // and message delivery.
104
92
  export function startWebworkerReady() {
105
- const worker = startWebworker();
93
+ const worker = new Worker(url, { type: "module" });
106
94
  return new Promise((resolve) => {
107
95
  const onReady = (e) => {
108
96
  if (e.data?.type === "WORKER_READY") {
@@ -6,7 +6,7 @@ export declare class ConnectionStatusOpened {
6
6
  private _prevConnected;
7
7
  private _path;
8
8
  constructor(scan_settings_path: string, _onChange?: ((status: ConnectionStatus | null) => void) | null | undefined);
9
- watch(intervalMs?: number): void;
9
+ watch(intervalMs?: number): Promise<void>;
10
10
  unwatch(): void;
11
11
  get connectionStatus(): ConnectionStatus | null;
12
12
  get isConnected(): boolean;
@@ -9,10 +9,10 @@ export class ConnectionStatusOpened {
9
9
  this._onChange = _onChange;
10
10
  this._path = connectionStatusFilePath(scan_settings_path);
11
11
  }
12
- watch(intervalMs = 2500) {
12
+ async watch(intervalMs = 2500) {
13
13
  if (this._timer)
14
14
  return;
15
- this._poll();
15
+ await this._poll();
16
16
  this._timer = setInterval(() => this._poll(), intervalMs);
17
17
  }
18
18
  unwatch() {
@@ -131,6 +131,7 @@ export declare class ScanCacheOpened {
131
131
  pause(): Promise<void>;
132
132
  stopWorker(): void;
133
133
  unpause(): Promise<void>;
134
+ private _onWorkerError;
134
135
  /**
135
136
  * selectInputs returns array of inputs, whose sum is larger than amount
136
137
  * adds approximate fee for 10kb transaction to amount if feePerByte is supplied
@@ -202,6 +203,7 @@ export declare class ManyScanCachesOpened {
202
203
  setCpuWorkerCount(cpu_worker_count: number | undefined): Promise<void>;
203
204
  setLogSettings(logs?: LogSetting | null, logs_include?: PossibleLogs[] | null, logs_exclude?: PossibleLogs[] | null): Promise<void>;
204
205
  setWalletName(primary_address: string, name?: string): Promise<void>;
206
+ setWalletSlot(primary_address: string, slot?: number): Promise<void>;
205
207
  changeStartHeight(start_height: number | null): Promise<void>;
206
208
  private static _buildWallets;
207
209
  static create(options: ManyScanCachesOpenedCreateOptions): Promise<ManyScanCachesOpened | undefined>;
@@ -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";
@@ -92,10 +92,7 @@ export class ScanCacheOpened {
92
92
  return this._stats.height;
93
93
  }
94
94
  async changeStartHeight(start_height) {
95
- if (this.worker) {
96
- this.worker.terminate();
97
- delete this.worker;
98
- }
95
+ this.stopWorker();
99
96
  await this._scanSettings.setStartHeight(start_height);
100
97
  this._start_height = start_height;
101
98
  await this.unpause();
@@ -170,10 +167,7 @@ export class ScanCacheOpened {
170
167
  return this._scanSettings.logs_exclude;
171
168
  }
172
169
  async changeNodeUrlAndStartHeight(node_url, start_height) {
173
- if (this.worker) {
174
- this.worker.terminate();
175
- delete this.worker;
176
- }
170
+ this.stopWorker();
177
171
  if (node_url !== undefined) {
178
172
  await this._scanSettings.setNodeUrl(node_url);
179
173
  this.node_url = node_url;
@@ -185,18 +179,13 @@ export class ScanCacheOpened {
185
179
  await this.unpause();
186
180
  }
187
181
  async changeNodeUrl(node_url) {
188
- if (this.worker) {
189
- this.worker.terminate();
190
- delete this.worker;
191
- }
182
+ this.stopWorker();
192
183
  await this._scanSettings.setNodeUrl(node_url);
193
184
  this.node_url = node_url;
194
185
  await this.unpause();
195
186
  }
196
187
  async setMerchantConfirmations(merchant_confirmations) {
197
- this.stopWorker();
198
188
  await this._scanSettings.setMerchantConfirmations(merchant_confirmations);
199
- await this.unpause();
200
189
  }
201
190
  async setCpuWorkerCount(cpu_worker_count) {
202
191
  this.stopWorker();
@@ -204,7 +193,7 @@ export class ScanCacheOpened {
204
193
  await this.unpause();
205
194
  }
206
195
  async setLogSettings(logs, logs_include, logs_exclude) {
207
- this.stopWorker();
196
+ await this.stopWorker();
208
197
  await this._scanSettings.setLogSettings(logs, logs_include, logs_exclude);
209
198
  await this.unpause();
210
199
  }
@@ -212,10 +201,7 @@ export class ScanCacheOpened {
212
201
  await this._scanSettings.setWalletName(this.view_pair.primary_address, name);
213
202
  }
214
203
  async retry() {
215
- if (this.worker) {
216
- this.worker.terminate();
217
- delete this.worker;
218
- }
204
+ this.stopWorker();
219
205
  // scansettings so external changes (e.g. from a sidebar frontend instance) are picked up
220
206
  await this._scanSettings.reload();
221
207
  const walletStillExists = this._scanSettings.walletExists(this.primary_address);
@@ -247,7 +233,7 @@ export class ScanCacheOpened {
247
233
  const feeFor10kb = feePerByte * 10000n;
248
234
  //2. check if fee is too high
249
235
  if (feeFor10kb > max_plausible_fee) {
250
- throw new Error(`fee too high:
236
+ throw new Error(`fee too high:
251
237
  ${feeFor10kb} (fee for 10kb tx size) > ${max_plausible_fee} (0.001 XMR)
252
238
  most likely your node is faulty. connect to another node.
253
239
  preferably run one yourself locally.`);
@@ -266,7 +252,7 @@ export class ScanCacheOpened {
266
252
  const feeFor10kb = feePerByte * 10000n;
267
253
  //2. check if fee is too high
268
254
  if (feeFor10kb > max_plausible_fee) {
269
- throw new Error(`fee too high:
255
+ throw new Error(`fee too high:
270
256
  ${feeFor10kb} (fee for 10kb tx size) > ${max_plausible_fee} (0.001 XMR)
271
257
  most likely your node is faulty. connect to another node.
272
258
  preferably run one yourself locally.`);
@@ -410,10 +396,7 @@ export class ScanCacheOpened {
410
396
  if (sendResult.status !== "OK")
411
397
  throw new Error("send raw transaction rpc returned error");
412
398
  // before writing the scan cache, we stop the worker to avoid a race
413
- if (this.worker) {
414
- this.worker.terminate();
415
- delete this.worker;
416
- }
399
+ this.stopWorker();
417
400
  // write txlog to cache + update pending_spent_utxos (affects stats + spendability)
418
401
  await writeCacheFileDefaultLocationThrows({
419
402
  primary_address: this.primary_address,
@@ -457,10 +440,7 @@ export class ScanCacheOpened {
457
440
  }
458
441
  catch (e) {
459
442
  // before writing the scan cache, we stop the worker to avoid a race
460
- if (this.worker) {
461
- this.worker.terminate();
462
- delete this.worker;
463
- }
443
+ this.stopWorker();
464
444
  // write txlog error to cache
465
445
  await writeCacheFileDefaultLocationThrows({
466
446
  primary_address: this.primary_address,
@@ -570,43 +550,45 @@ export class ScanCacheOpened {
570
550
  };
571
551
  }
572
552
  async pause() {
573
- if (this.worker)
574
- this.worker.terminate();
553
+ this.stopWorker();
575
554
  return await this._scanSettings.haltWallet(this.view_pair.primary_address);
576
555
  }
577
556
  stopWorker() {
578
557
  if (this.worker) {
579
558
  this.worker.terminate();
559
+ this.worker.cpuWorkers = [];
560
+ this.worker.fetchWorker = undefined;
580
561
  delete this.worker;
581
562
  }
582
563
  }
583
564
  async unpause() {
584
565
  // if worker does not exist yet, start it (if we are not slave / no_worker)
585
566
  if (!this.worker && !this.no_worker) {
586
- this.worker = await createWebworker(async (params) => await this.feed(params), this.scan_settings_path, this.pathPrefix, (error) => {
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
- });
567
+ this.worker = await createWebworker(async (params) => await this.feed(params), this.scan_settings_path, this.pathPrefix, (params) => this._onWorkerError(params));
607
568
  }
608
569
  return await this._scanSettings.unhaltWallet(this.view_pair.primary_address);
609
570
  }
571
+ _onWorkerError = (error) => {
572
+ const workerErrCB = this.workerError;
573
+ this.stopWorker();
574
+ readWriteConnectionStatusFile((cs) => {
575
+ if (cs?.last_packet.status === "catastrophic_reorg")
576
+ return;
577
+ const connectionStatus = {
578
+ ...cs,
579
+ last_packet: {
580
+ status: "connection_failed",
581
+ bytes_read: 0,
582
+ node_url: this.node_url,
583
+ timestamp: new Date().toISOString(),
584
+ },
585
+ };
586
+ return connectionStatus;
587
+ }, this.scan_settings_path).then(() => {
588
+ if (workerErrCB)
589
+ workerErrCB(error);
590
+ });
591
+ };
610
592
  /**
611
593
  * selectInputs returns array of inputs, whose sum is larger than amount
612
594
  * adds approximate fee for 10kb transaction to amount if feePerByte is supplied
@@ -804,6 +786,9 @@ export class ManyScanCachesOpened {
804
786
  async setWalletName(primary_address, name) {
805
787
  await this._scanSettings.setWalletName(primary_address, name);
806
788
  }
789
+ async setWalletSlot(primary_address, slot) {
790
+ await this._scanSettings.setWalletSlot(primary_address, slot);
791
+ }
807
792
  async changeStartHeight(start_height) {
808
793
  if (this.wallets.length === 0)
809
794
  throw new Error("no wallets");
@@ -872,31 +857,66 @@ export class ManyScanCachesOpened {
872
857
  // wrap workerError with auto-retry
873
858
  let retryTimer;
874
859
  let instance = null;
860
+ const retryFn = async () => {
861
+ await instance?.buildWallets();
862
+ const node_url = instance?.node_url;
863
+ if (!node_url)
864
+ throw new Error("No nodeurl set, can't retry connection");
865
+ const getinfo_result = await get_info(node_url)
866
+ .then((r) => {
867
+ r?.status === "OK";
868
+ })
869
+ .catch(() => false);
870
+ if (getinfo_result)
871
+ await instance?.retry();
872
+ clearTimeout(retryTimer);
873
+ retryTimer = undefined;
874
+ };
875
875
  const newOptions = { ...options };
876
+ let connectionFailedShown = false;
876
877
  if (autoRetry) {
877
878
  const originalError = options.workerError;
878
879
  newOptions.workerError = (err) => {
879
880
  originalError?.(err);
881
+ const msg = err instanceof Error ? err.message : String(err);
882
+ if (csOpened.connectionStatus?.last_packet?.status ===
883
+ "catastrophic_reorg") {
884
+ clearTimeout(retryTimer);
885
+ instance?.stopWorker();
886
+ throw new Error("catastrophic reorg, aborting ...");
887
+ }
888
+ if (msg.includes("connect") ||
889
+ msg.includes("fetch") ||
890
+ msg.includes("NetworkError")) {
891
+ if (!connectionFailedShown) {
892
+ connectionFailedShown = true;
893
+ console.error("unable to connect to node ... retrying ...");
894
+ }
895
+ }
880
896
  if (!retryTimer) {
881
- retryTimer = setTimeout(() => {
882
- retryTimer = undefined;
883
- instance?.retry();
884
- }, retryDelayMs ?? 1000);
897
+ retryTimer = setTimeout(retryFn, retryDelayMs ?? 5000);
885
898
  }
886
899
  };
887
900
  }
888
901
  const wallets = await this._buildWallets(scanSettingsOpened, newOptions);
889
902
  if (!wallets)
890
903
  return undefined;
891
- const csOpened = new ConnectionStatusOpened(scan_settings_path || SCAN_SETTINGS_STORE_NAME_DEFAULT, onConnectionStatusChange ?? null);
904
+ const csOpened = new ConnectionStatusOpened(scan_settings_path || SCAN_SETTINGS_STORE_NAME_DEFAULT, autoRetry
905
+ ? (status) => {
906
+ if (status?.last_packet?.status === "OK" && connectionFailedShown) {
907
+ connectionFailedShown = false;
908
+ console.log("connection to node established");
909
+ }
910
+ if (onConnectionStatusChange)
911
+ onConnectionStatusChange(status);
912
+ }
913
+ : (onConnectionStatusChange ?? null));
892
914
  csOpened.watch(connectionStatusIntervalMs);
893
915
  instance = new ManyScanCachesOpened(wallets, csOpened, scanSettingsOpened, newOptions);
894
916
  return instance;
895
917
  }
896
918
  async buildWallets() {
897
- for (const w of this._wallets) {
898
- w.stopWorker();
899
- }
919
+ this.stopWorker();
900
920
  await this._scanSettings.reload();
901
921
  const newWallets = await ManyScanCachesOpened._buildWallets(this._scanSettings, this._options);
902
922
  if (!newWallets)
@@ -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.
@@ -90,14 +76,6 @@ export type CoordinatorEvent = {
90
76
  type: "error";
91
77
  error: Error;
92
78
  };
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
79
  export declare function setupCoordinator(scanSettingsPath?: string, pathPrefix?: string): Promise<false | {
102
80
  blocksGenerator: AsyncGenerator<BlocksBufferLoopResult, any, any>;
103
81
  workBuffer: WorkItem[];