@spirobel/monero-wallet-api 0.5.3 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +6 -0
  2. package/dist/api.d.ts +1 -1
  3. package/dist/api.js +1 -1
  4. package/dist/io/logging.d.ts +6 -6
  5. package/dist/io/logging.js +4 -3
  6. package/dist/io/sleep.d.ts +1 -1
  7. package/dist/io/sleep.js +16 -1
  8. package/dist/multisig/escrow.js +0 -1
  9. package/dist/scanning-syncing/backgroundWorker.d.ts +2 -1
  10. package/dist/scanning-syncing/backgroundWorker.js +104 -34
  11. package/dist/scanning-syncing/blocksbuffer/blocksBufferFetchLoop.js +33 -2
  12. package/dist/scanning-syncing/connectionStatus.d.ts +6 -0
  13. package/dist/scanning-syncing/connectionStatus.js +15 -0
  14. package/dist/scanning-syncing/connectionStatusOpened.d.ts +3 -1
  15. package/dist/scanning-syncing/connectionStatusOpened.js +22 -5
  16. package/dist/scanning-syncing/openWallet.d.ts +4 -1
  17. package/dist/scanning-syncing/openWallet.js +3 -0
  18. package/dist/scanning-syncing/scanresult/scanCacheOpened.d.ts +12 -4
  19. package/dist/scanning-syncing/scanresult/scanCacheOpened.js +188 -107
  20. package/dist/scanning-syncing/scanresult/scanCoordination.d.ts +6 -25
  21. package/dist/scanning-syncing/scanresult/scanCoordination.js +89 -209
  22. package/dist/scanning-syncing/scanresult/scanLoop.d.ts +1 -2
  23. package/dist/scanning-syncing/scanresult/scanLoop.js +0 -13
  24. package/dist/scanning-syncing/worker-entrypoints/worker.js +1 -1
  25. package/dist/scanning-syncing/worker-mains/coordinator-main.d.ts +1 -0
  26. package/dist/scanning-syncing/worker-mains/coordinator-main.js +16 -2
  27. package/dist/scanning-syncing/worker-mains/cpubound-main.d.ts +5 -2
  28. package/dist/scanning-syncing/worker-mains/cpubound-main.js +10 -8
  29. package/dist/scanning-syncing/worker-mains/worker.js +50 -2
  30. package/dist/scansettings/scanSettingsOpened.js +3 -6
  31. package/dist/tools/monero-tools.js +1 -1
  32. package/dist/viewpair/ViewPair.js +9 -4
  33. package/dist/wasm-processing/wasmFile.js +1 -1
  34. package/package.json +1 -1
@@ -1 +1,2 @@
1
+ export declare function requestCoordinatorShutdown(): void;
1
2
  export declare function coordinatorMainWorker(scan_settings_path?: string, pathPrefix?: string, cpuPorts?: MessagePort[]): Promise<void>;
@@ -1,9 +1,16 @@
1
1
  import { coordinatorMainMultithreaded } from "../scanresult/scanCoordination";
2
2
  import { log } from "../../io/logging";
3
+ // one controller per coordinator worker; shutdown aborts fetch + race
4
+ let shutdownController;
5
+ export function requestCoordinatorShutdown() {
6
+ log("coordinatorMainWorker", ["shutdown requested"]);
7
+ shutdownController?.abort();
8
+ }
3
9
  export async function coordinatorMainWorker(scan_settings_path, pathPrefix, cpuPorts) {
4
10
  log("coordinatorMainWorker", "cpuPorts=" + cpuPorts?.length);
11
+ shutdownController = new AbortController();
5
12
  try {
6
- const gen = coordinatorMainMultithreaded(scan_settings_path, pathPrefix, cpuPorts);
13
+ const gen = coordinatorMainMultithreaded(scan_settings_path, pathPrefix, cpuPorts, shutdownController.signal);
7
14
  for await (const event of gen) {
8
15
  log("coordinatorMainWorker", ["event:", event]);
9
16
  if (event.type === "scan_ready") {
@@ -15,10 +22,17 @@ export async function coordinatorMainWorker(scan_settings_path, pathPrefix, cpuP
15
22
  },
16
23
  });
17
24
  }
25
+ else if (event.type === "shutdown_done") {
26
+ self.postMessage({ type: "SHUTDOWN_DONE" });
27
+ return;
28
+ }
18
29
  }
30
+ // generator returned without shutdown_done (e.g. empty)
31
+ self.postMessage({ type: "SHUTDOWN_DONE" });
19
32
  }
20
33
  catch (error) {
21
- console.error("[coordinatorMainWorker] error:", error);
34
+ // still tell main thread we stopped so terminate can proceed
35
+ self.postMessage({ type: "SHUTDOWN_DONE" });
22
36
  self.postMessage({ type: "ERROR", payload: error });
23
37
  }
24
38
  }
@@ -1,5 +1,8 @@
1
1
  import { type ScanLoopInput, type ScanLoopYield } from "../scanresult/scanLoop";
2
- export declare function handleCpuboundScan(msg: ScanLoopInput, port?: MessagePort): Promise<ScanLoopYield>;
3
- export declare function handleCpuboundScanTry(msg: ScanLoopInput, port?: MessagePort): Promise<ScanLoopYield>;
2
+ export declare function handleCpuboundScan(msg: ScanLoopInput, cpu_worker_status: CpuWorkerStatus): Promise<ScanLoopYield>;
3
+ export declare function handleCpuboundScanTry(msg: ScanLoopInput, cpu_worker_status: CpuWorkerStatus): Promise<ScanLoopYield>;
4
+ export type CpuWorkerStatus = {
5
+ cancel: boolean;
6
+ };
4
7
  export declare function sendFromCpuWorker(port: MessagePort, msg: ScanLoopYield): void;
5
8
  export declare function sendToCpuWorker(port: MessagePort, msg: ScanLoopInput): void;
@@ -6,7 +6,7 @@ import {} from "../scanresult/scanLoop";
6
6
  * logs progress every 10 blocks, then sends the final result.
7
7
  */
8
8
  let callcounter = 0;
9
- export async function handleCpuboundScan(msg, port) {
9
+ export async function handleCpuboundScan(msg, cpu_worker_status) {
10
10
  callcounter++;
11
11
  log("handleCpuboundScan", "callcounter=" + callcounter);
12
12
  if (!msg) {
@@ -14,17 +14,14 @@ export async function handleCpuboundScan(msg, port) {
14
14
  throw new Error("[cpubound] no content msg! ( ScanLoopInput = undefined )");
15
15
  }
16
16
  if (msg === "cancel") {
17
+ // idle cancel: nothing in flight, just ack
17
18
  log("handleCpuboundScan", "cancel msg! ( ScanLoopInput = cancel )");
18
- throw new Error("[cpubound] no content msg! ( ScanLoopInput = undefined )");
19
+ return { type: "Canceled" };
19
20
  }
20
21
  log("handleCpuboundScan", "got scan msg, walletConfig=" +
21
22
  msg.walletConfig?.primary_address?.slice(0, 6));
22
23
  const walletConfig = msg.walletConfig;
23
24
  const item = msg;
24
- if (!port) {
25
- log("handleCpuboundScan", "no port!");
26
- throw new Error("[cpubound] no port");
27
- }
28
25
  log("handleCpuboundScan", "primed, feeding workItem uuid=" +
29
26
  (item && typeof item === "object"
30
27
  ? item.work_uuid?.slice(0, 8)
@@ -90,6 +87,11 @@ export async function handleCpuboundScan(msg, port) {
90
87
  heightto,
91
88
  ]);
92
89
  }
90
+ if (cpu_worker_status.cancel) {
91
+ cpu_worker_status.cancel = false;
92
+ log("handleCpuboundScan", ["cpu work canceled"]);
93
+ return { type: "Canceled", work_uuid: item.work_uuid };
94
+ }
93
95
  await sleep(10); // make sure the loop is not tight
94
96
  }
95
97
  log("handleCpuboundScan", [
@@ -105,9 +107,9 @@ export async function handleCpuboundScan(msg, port) {
105
107
  result: scanResult,
106
108
  };
107
109
  }
108
- export function handleCpuboundScanTry(msg, port) {
110
+ export function handleCpuboundScanTry(msg, cpu_worker_status) {
109
111
  try {
110
- return handleCpuboundScan(msg, port);
112
+ return handleCpuboundScan(msg, cpu_worker_status);
111
113
  }
112
114
  catch (err) {
113
115
  console.error("[cpubound] error", err);
@@ -1,4 +1,4 @@
1
- import { coordinatorMainWorker } from "./coordinator-main";
1
+ import { coordinatorMainWorker, requestCoordinatorShutdown, } from "./coordinator-main";
2
2
  import { handleCpuboundScanTry, sendFromCpuWorker } from "./cpubound-main";
3
3
  import { log, setupLoggingPath } from "../../io/logging";
4
4
  import { multisigMainWorkerCall } from "./multisig-main";
@@ -8,17 +8,33 @@ self.addEventListener("unhandledrejection", (e) => self.postMessage({ type: "ERR
8
8
  let SCAN_SETTINGS_PATH;
9
9
  let PATH_PREFIX;
10
10
  let cpuPort;
11
+ const cpu_worker_status = { cancel: false };
12
+ // true while a scan is running so idle cancel can ack immediately
13
+ let cpu_work_in_flight = false;
11
14
  let multisig_dkg;
12
15
  export function CPU_PORT_HANDLER(pe) {
16
+ if (pe.data === "cancel") {
17
+ if (cpu_work_in_flight) {
18
+ cpu_worker_status.cancel = true;
19
+ return;
20
+ }
21
+ // idle cancel: ack right away so coordinator drain does not hang
22
+ if (cpuPort) {
23
+ sendFromCpuWorker(cpuPort, { type: "Canceled" });
24
+ }
25
+ return;
26
+ }
13
27
  if (!cpuPort)
14
28
  throw new Error("[cpubound] cpuPort is undefined in port.onmessage");
15
29
  log("CPU_PORT_HANDLER", "new workitem msg received");
30
+ cpu_work_in_flight = true;
16
31
  cpuPort.postMessage({
17
32
  type: "WORKSTART",
18
33
  work_uuid: pe.data.work_uuid,
19
34
  });
20
- handleCpuboundScanTry(pe.data, cpuPort).then((result) => {
35
+ handleCpuboundScanTry(pe.data, cpu_worker_status).then((result) => {
21
36
  log("CPU_PORT_HANDLER", "work finished, sending result");
37
+ cpu_work_in_flight = false;
22
38
  if (!cpuPort)
23
39
  throw new Error("[cpubound] cpuPort is undefined in port.onmessage");
24
40
  cpuPort.onmessage = CPU_PORT_HANDLER;
@@ -28,6 +44,38 @@ export function CPU_PORT_HANDLER(pe) {
28
44
  const handleMessage = async (e) => {
29
45
  const msg = e.data;
30
46
  log("handleMessage", ["msg received", msg]);
47
+ if (msg.type === "shutdown") {
48
+ // only coordinator listens for this; abort fetch + race loop
49
+ requestCoordinatorShutdown();
50
+ return;
51
+ }
52
+ if (msg.type === "heap_snapshot") {
53
+ try {
54
+ const path = typeof msg.path === "string" && msg.path.length > 0
55
+ ? msg.path
56
+ : `worker-heap-${Date.now()}.heapsnapshot`;
57
+ // @ts-ignore bun has generateHeapSnapshot; node has v8.writeHeapSnapshot
58
+ let out;
59
+ if (typeof Bun !== "undefined" && typeof Bun.generateHeapSnapshot === "function") {
60
+ const snap = Bun.generateHeapSnapshot("v8");
61
+ await Bun.write(path, typeof snap === "string" ? snap : JSON.stringify(snap));
62
+ out = path;
63
+ }
64
+ else {
65
+ throw Error("in the browser use the inspector directly to observe worker memory growth");
66
+ // const v8 = await import("node:v8");
67
+ // out = v8.writeHeapSnapshot(path);
68
+ }
69
+ self.postMessage({ type: "HEAP_SNAPSHOT_DONE", path: out ?? path });
70
+ }
71
+ catch (err) {
72
+ self.postMessage({
73
+ type: "HEAP_SNAPSHOT_ERROR",
74
+ error: err instanceof Error ? err.message : String(err),
75
+ });
76
+ }
77
+ return;
78
+ }
31
79
  if (msg.type === "setup") {
32
80
  const settingsPath = msg.scan_settings_path || SCAN_SETTINGS_STORE_NAME_DEFAULT;
33
81
  SCAN_SETTINGS_PATH = settingsPath;
@@ -1,6 +1,6 @@
1
1
  import { LOCAL_NODE_DEFAULT_URL } from "../node-interaction/nodeUrl";
2
2
  import { LOGGING_FUNCTIONS, } from "../io/logging";
3
- import { getPathPrefix, makeSpendKeyFromSeed, makeViewKey, openScanSettingsFile, SCAN_SETTINGS_STORE_NAME_DEFAULT, SUB_ADDRESS_INDEX_DEFAULT_VALUE, walletSettingsPlusKeys, writeEnvLineToDotEnvRefresh, writeScanSettings, writeViewKeyToDotEnv, } from "../api";
3
+ import { getPathPrefix, makeSpendKeyFromSeed, makeViewKey, openScanSettingsFile, parseAddress, SCAN_SETTINGS_STORE_NAME_DEFAULT, SUB_ADDRESS_INDEX_DEFAULT_VALUE, walletSettingsPlusKeys, writeEnvLineToDotEnvRefresh, writeScanSettings, writeViewKeyToDotEnv, } from "../api";
4
4
  export class ScanSettingsOpened {
5
5
  _scan_settings_path;
6
6
  _pathPrefix;
@@ -309,7 +309,8 @@ export class ScanSettingsOpened {
309
309
  }
310
310
  }
311
311
  static _validateAddress(address) {
312
- if (!isValidMoneroAddress(address)) {
312
+ const parsed = parseAddress(address);
313
+ if ("error" in parsed) {
313
314
  throw new Error(`invalid monero address: ${address}`);
314
315
  }
315
316
  }
@@ -404,10 +405,6 @@ export class ScanSettingsOpened {
404
405
  await writeScanSettings(this._settings, this._scan_settings_path);
405
406
  }
406
407
  }
407
- function isValidMoneroAddress(address) {
408
- // basic sanity check
409
- return (address.length >= 95 && address.length <= 106 && isAlphaNumeric(address));
410
- }
411
408
  function isValidMoneroPrivateKey(key) {
412
409
  return key.length === 64 && isAlphaNumeric(key);
413
410
  }
@@ -122,7 +122,7 @@ export function parseDestination(destination) {
122
122
  const url = new URL(destination);
123
123
  return getDomainWithTLD(url.hostname);
124
124
  }
125
- export const OPEN_DOMAINS = ["monerochan.city"];
125
+ export const OPEN_DOMAINS = ["monerochan.cash"];
126
126
  // this validity check should happen in the contentscript when the link is clicked,
127
127
  // not in the background script
128
128
  // -> tor circuit is separated & compartmentalized
@@ -46,18 +46,23 @@ export class ViewPair extends WasmProcessor {
46
46
  };
47
47
  };
48
48
  let init_viewpair_result = undefined;
49
+ let init_viewpair_error = undefined;
49
50
  viewPair.readFromWasmMemory = (ptr, len) => {
50
51
  init_viewpair_result = JSON.parse(viewPair.readString(ptr, len));
51
52
  };
53
+ viewPair.readErrorFromWasmMemory = (ptr, len) => {
54
+ init_viewpair_error = JSON.parse(viewPair.readString(ptr, len));
55
+ };
52
56
  //@ts-ignore
53
57
  tinywasi.instance.exports.init_viewpair(primary_address.length, secret_view_key.length, subaddress_index);
58
+ if (init_viewpair_error && "error" in init_viewpair_error) {
59
+ throw new Error(`primary-address-not-valid (${primary_address.slice(0, 8)}...${primary_address.slice(-8)})`);
60
+ }
54
61
  if (!init_viewpair_result) {
55
62
  throw new Error("Failed to init viewpair");
56
63
  }
57
- else {
58
- //@ts-ignore
59
- viewPair._network = init_viewpair_result.network;
60
- }
64
+ //@ts-ignore
65
+ viewPair._network = init_viewpair_result.network;
61
66
  return viewPair;
62
67
  }
63
68
  /**