@spirobel/monero-wallet-api 0.3.16 → 0.4.0

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 (49) hide show
  1. package/dist/api.d.ts +5 -2
  2. package/dist/api.js +5 -1
  3. package/dist/io/atomicWrite.js +2 -1
  4. package/dist/io/indexedDB.js +7 -2
  5. package/dist/keypairs-seeds/keypairs.d.ts +21 -0
  6. package/dist/keypairs-seeds/keypairs.js +21 -0
  7. package/dist/node-interaction/binaryEndpoints.d.ts +31 -2
  8. package/dist/node-interaction/binaryEndpoints.js +61 -43
  9. package/dist/node-interaction/jsonEndpoints.d.ts +257 -130
  10. package/dist/node-interaction/jsonEndpoints.js +159 -119
  11. package/dist/node-interaction/nodeUrl.d.ts +21 -6
  12. package/dist/node-interaction/nodeUrl.js +50 -8
  13. package/dist/scanning-syncing/backgroundWorker.d.ts +9 -6
  14. package/dist/scanning-syncing/backgroundWorker.js +76 -39
  15. package/dist/scanning-syncing/blocksbuffer/blocksBufferFetchLoop.d.ts +44 -0
  16. package/dist/scanning-syncing/blocksbuffer/blocksBufferFetchLoop.js +353 -0
  17. package/dist/scanning-syncing/blocksbuffer/blocksbufferCoordination.d.ts +15 -0
  18. package/dist/scanning-syncing/blocksbuffer/blocksbufferCoordination.js +27 -0
  19. package/dist/scanning-syncing/{scanresult → blocksbuffer}/getBlocksbinBuffer.d.ts +5 -5
  20. package/dist/scanning-syncing/{scanresult → blocksbuffer}/getBlocksbinBuffer.js +7 -5
  21. package/dist/scanning-syncing/connectionStatus.d.ts +33 -9
  22. package/dist/scanning-syncing/connectionStatus.js +89 -19
  23. package/dist/scanning-syncing/scanSettings.d.ts +45 -0
  24. package/dist/scanning-syncing/scanSettings.js +63 -0
  25. package/dist/scanning-syncing/scanresult/reorg.d.ts +0 -11
  26. package/dist/scanning-syncing/scanresult/reorg.js +39 -16
  27. package/dist/scanning-syncing/scanresult/scanCache.d.ts +16 -2
  28. package/dist/scanning-syncing/scanresult/scanCache.js +58 -2
  29. package/dist/scanning-syncing/scanresult/scanCacheOpened.d.ts +18 -0
  30. package/dist/scanning-syncing/scanresult/scanCacheOpened.js +56 -11
  31. package/dist/scanning-syncing/scanresult/scanCoordination.d.ts +127 -0
  32. package/dist/scanning-syncing/scanresult/scanCoordination.js +611 -0
  33. package/dist/scanning-syncing/scanresult/scanLoop.d.ts +20 -0
  34. package/dist/scanning-syncing/scanresult/scanLoop.js +116 -0
  35. package/dist/scanning-syncing/scanresult/scanResult.d.ts +24 -3
  36. package/dist/scanning-syncing/scanresult/scanResult.js +151 -98
  37. package/dist/scanning-syncing/worker-entrypoints/worker.js +1 -1
  38. package/dist/scanning-syncing/worker-mains/coordinator-main.d.ts +1 -0
  39. package/dist/scanning-syncing/worker-mains/coordinator-main.js +23 -0
  40. package/dist/scanning-syncing/worker-mains/cpubound-main.d.ts +5 -0
  41. package/dist/scanning-syncing/worker-mains/cpubound-main.js +112 -0
  42. package/dist/scanning-syncing/worker-mains/worker.d.ts +1 -1
  43. package/dist/scanning-syncing/worker-mains/worker.js +58 -6
  44. package/dist/viewpair/ViewPair.d.ts +30 -7
  45. package/dist/viewpair/ViewPair.js +56 -118
  46. package/dist/wasm-processing/wasmFile.js +1 -1
  47. package/package.json +22 -24
  48. package/dist/io/extension.d.ts +0 -18
  49. package/dist/io/extension.js +0 -11
@@ -5,7 +5,7 @@ import { spendable } from "./scanResult";
5
5
  import { openScanSettingsFile, readPrivateSpendKeyFromEnv, readWalletFromScanSettings, SCAN_SETTINGS_STORE_NAME_DEFAULT, walletSettingsPlusKeys, writeNodeUrlToScanSettings, writeStartHeightToScanSettings, writeWalletToScanSettings, } from "../scanSettings";
6
6
  import { findRange, lastRange, readCacheFileDefaultLocation, writeCacheFileDefaultLocationThrows, } from "./scanCache";
7
7
  import { alignScanStatsWithCache, isSelfSpent, processTxlogInputs, processTxlogPayments, writeStatsFileDefaultLocation, } from "./scanStats";
8
- import { readWriteConnectionStatusFile, } from "../connectionStatus";
8
+ import { updateSyncETA, readWriteConnectionStatusFile, } from "../connectionStatus";
9
9
  export class ScanCacheOpened {
10
10
  view_pair;
11
11
  wallet_route;
@@ -17,6 +17,22 @@ export class ScanCacheOpened {
17
17
  scan_settings_path;
18
18
  pathPrefix;
19
19
  workerError;
20
+ /** how many decoys to sample per input (default 20, ring size is 11) */
21
+ decoySampleCount = 20;
22
+ /**
23
+ * when true, retry makeInput with higher sample counts on failure.
24
+ *
25
+ * PRIVACY WARNING: retrying contacts the node multiple times for the same
26
+ * input, each time with a different set of candidate indices. this lets the
27
+ * node correlate which output is the real spend across the retries.
28
+ * only ever enable this on your own local trusted node, never on a remote
29
+ * public node.
30
+ *
31
+ * defaults to false, on failure the original error propagates.
32
+ */
33
+ decoyRetry = false;
34
+ /** sample sizes to try when decoyRetry is enabled, in order */
35
+ decoyRetrySizes = [20, 50, 100, 200, 500];
20
36
  static async create(params) {
21
37
  const theCatchToBeOpened = await readCacheFileDefaultLocation(params.primary_address, params.pathPrefix);
22
38
  const walletSettings = await readWalletFromScanSettings(params.primary_address, params.scan_settings_path);
@@ -193,16 +209,30 @@ export class ScanCacheOpened {
193
209
  // 4. get output distribution
194
210
  const node = await NodeUrl.create(this.node_url);
195
211
  const distibution = await node.getOutputDistribution();
196
- const preparedInputs = [];
212
+ const inputs = [];
197
213
  for (const input of selectedInputs) {
198
214
  // 5. sample decoys & get outs from node: here is where a privacy compromising event could happen
199
- preparedInputs.push(prepareInput(node, distibution, input));
200
- }
201
- const inputs = [];
202
- for (const preparedInput of preparedInputs) {
203
- // 6. make input: combine output with sampled and verified unlocked decoys
204
- const input = node.makeInput(preparedInput.input, preparedInput.sample.candidates, await preparedInput.outsResponse);
205
- inputs.push(input);
215
+ const sizesToTry = this.decoyRetry
216
+ ? this.decoyRetrySizes
217
+ : [this.decoySampleCount];
218
+ let madeInput;
219
+ for (const size of sizesToTry) {
220
+ if (madeInput)
221
+ break;
222
+ try {
223
+ const prepared = prepareInput(node, distibution, input, size);
224
+ const wasmInput = node.makeInput(prepared.input, prepared.sample.candidates, await prepared.outsResponse);
225
+ madeInput = wasmInput;
226
+ }
227
+ catch (e) {
228
+ if (size === sizesToTry[sizesToTry.length - 1])
229
+ throw e;
230
+ // fall through to next size
231
+ }
232
+ }
233
+ if (!madeInput)
234
+ throw new Error("failed to make input");
235
+ inputs.push(madeInput);
206
236
  }
207
237
  // 7. make transaction: combine inputs, payments + fee info
208
238
  const unsignedTx = this.view_pair.makeTransaction({
@@ -429,10 +459,12 @@ export class ScanCacheOpened {
429
459
  if (!this.worker && !this.no_worker) {
430
460
  this.worker = await createWebworker(async (params) => await this.feed(params), this.scan_settings_path, this.pathPrefix, (error) => {
431
461
  const workerErrCB = this.workerError;
462
+ this.stopWorker();
432
463
  readWriteConnectionStatusFile((cs) => {
433
464
  if (cs?.last_packet.status === "catastrophic_reorg")
434
465
  return;
435
466
  const connectionStatus = {
467
+ ...cs,
436
468
  last_packet: {
437
469
  status: "connection_failed",
438
470
  bytes_read: 0,
@@ -441,7 +473,7 @@ export class ScanCacheOpened {
441
473
  },
442
474
  };
443
475
  return connectionStatus;
444
- }).then(() => {
476
+ }, this.scan_settings_path).then(() => {
445
477
  if (workerErrCB)
446
478
  workerErrCB(error);
447
479
  });
@@ -471,7 +503,13 @@ export class ScanCacheOpened {
471
503
  selectOneInput(amount) {
472
504
  return this.spendableInputs()
473
505
  .filter((output) => output.amount >= amount)
474
- .sort((a, b) => b.amount > a.amount ? -1 : b.amount < a.amount ? 1 : 0)[0];
506
+ .sort((a, b) => {
507
+ if (b.amount > a.amount)
508
+ return -1;
509
+ if (b.amount < a.amount)
510
+ return 1;
511
+ return a.block_height - b.block_height;
512
+ })[0];
475
513
  }
476
514
  /**
477
515
  * selectMultipleInputs larger than amount, sorted from largest to smallest until total reaches amount
@@ -509,6 +547,11 @@ export class ScanCacheOpened {
509
547
  return;
510
548
  this._cache = params.newCache;
511
549
  this._stats = await alignScanStatsWithCache(this._cache, this.view_pair, this.primary_address, this.pathPrefix, undefined, lastRange(this._cache.scanned_ranges)?.end);
550
+ if (!this.no_worker) {
551
+ const etaResult = await updateSyncETA(this._cache.daemon_height, this.current_height || 0, this.last_eta_height, this.last_eta_timestamp, this.scan_settings_path);
552
+ this.last_eta_height = etaResult.last_height;
553
+ this.last_eta_timestamp = etaResult.last_timestamp;
554
+ }
512
555
  for (const listener of this.notifyListeners) {
513
556
  if (listener)
514
557
  listener(params);
@@ -522,6 +565,8 @@ export class ScanCacheOpened {
522
565
  primary_address: "",
523
566
  };
524
567
  worker = undefined;
568
+ last_eta_height = null;
569
+ last_eta_timestamp = null;
525
570
  constructor(view_pair, wallet_route, wallet_name, wallet_slot, no_worker, masterCacheChanged, _start_height, scan_settings_path, pathPrefix, workerError) {
526
571
  this.view_pair = view_pair;
527
572
  this.wallet_route = wallet_route;
@@ -0,0 +1,127 @@
1
+ import { type GetBlocksBinBufferItem, type BlocksBufferLoopResult, type BlocksBufferIteratorResult, type ProcessScanResult, type BlockInfo } from "../../api";
2
+ import { type ScanLoopYield } from "./scanLoop";
3
+ import { type WorkItem } from "./scanLoop";
4
+ import { type ScanSettings } from "../scanSettings";
5
+ import { type CacheRange, type ScanCache } from "./scanCache";
6
+ export type WalletConfig = {
7
+ primary_address: string;
8
+ secret_view_key: string;
9
+ secret_spend_key?: string;
10
+ subaddress_index: number;
11
+ };
12
+ export type WalletConfigPlusCache = {
13
+ primary_address: string;
14
+ secret_view_key: string;
15
+ secret_spend_key?: string;
16
+ subaddress_index: number;
17
+ cache: ScanCache;
18
+ };
19
+ export type WorkToBeDone = {
20
+ start_height: number;
21
+ wallet_configs: WalletConfigPlusCache[];
22
+ anchor_range: CacheRange;
23
+ scan_settings: ScanSettings;
24
+ };
25
+ /**
26
+ * this depends only on ScanSettings.json start_height and wallet caches scanned_ranges
27
+ * side effect: will init wallet cache file if it does not exist
28
+ * side effect: will merge scan ranges + add subaddreses to existing cache files
29
+ * @param scan_settings_path
30
+ */
31
+ export declare function findWorkToBeDone(scan_settings_path?: string, pathPrefix?: string): Promise<WorkToBeDone | false>;
32
+ export declare function workToBeDoneForBatch(cache: ScanCache, batch_meta_infos: BlockInfo[]): "skip" | {
33
+ from: number;
34
+ };
35
+ /**
36
+ * called when the blocks buffer generator yields "blocks_buffer_changed".
37
+ * adds new work items for blocks buffer items not yet referenced.
38
+ * per wallet
39
+ */
40
+ export declare function makeWorkItemsFromBlocksBuffer(blocksBuffer: GetBlocksBinBufferItem[], workItemBuffer: WorkItem[], walletConfig: WalletConfigPlusCache, from?: number, to?: number): void;
41
+ export declare function makeWorkItemsForAllWallets(wallet_configs: WalletConfigPlusCache[], blocksBuffer: GetBlocksBinBufferItem[], workBuffer: WorkItem[]): void;
42
+ /**
43
+ * called when a work item at the left end of the work buffer is done.
44
+ * shifts done items off the left, and removes their batch from the
45
+ * blocks buffer if no remaining work items reference it.
46
+ */
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
+ /**
58
+ * handle a yield from the blocks buffer fetch loop.
59
+ */
60
+ export declare function handleBlocksYield(value: BlocksBufferLoopResult, scanSettingsPath?: string): Promise<{
61
+ isBlocksBufferChanged: boolean;
62
+ }>;
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
+ /**
69
+ * process a scan result for a completed work item.
70
+ * updates the cache, writes to disk, marks work item done, reconciles blockbuffer with this.
71
+ */
72
+ export declare function processWorkItem(item: WorkItem, workBuffer: WorkItem[], blocksBuffer: GetBlocksBinBufferItem[], pathPrefix: string, secret_spend_key?: string): Promise<ProcessScanResult>;
73
+ export type CoordinatorEvent = {
74
+ type: "blocks_buffer_changed";
75
+ } | {
76
+ type: "connection_status";
77
+ status: any;
78
+ } | {
79
+ type: "scan_ready";
80
+ address: string;
81
+ result: ScanLoopYield;
82
+ newCache: ScanCache;
83
+ changed_outputs: {
84
+ output: any;
85
+ change_reason: string;
86
+ }[];
87
+ } | {
88
+ type: "all_idle";
89
+ } | {
90
+ type: "error";
91
+ error: Error;
92
+ };
93
+ /**
94
+ * coordinator main: async generator that drives the full scan cycle.
95
+ * takes only scanSettingsPath, derives everything else internally.
96
+ * yields events for some of the underlying generator events,
97
+ * look at CoordinatorEvent, processing not decoupled from cpu bound scan work,
98
+ * as this single threaded, there is no point
99
+ */
100
+ export declare function coordinatorMain(scanSettingsPath?: string, pathPrefix?: string): AsyncGenerator<CoordinatorEvent>;
101
+ export declare function setupCoordinator(scanSettingsPath?: string, pathPrefix?: string): Promise<false | {
102
+ blocksGenerator: AsyncGenerator<BlocksBufferLoopResult, any, any>;
103
+ workBuffer: WorkItem[];
104
+ blocksBuffer: GetBlocksBinBufferItem[];
105
+ work_to_be_done: WorkToBeDone;
106
+ }>;
107
+ export type WalletSyncInfo = {
108
+ walletConfig: WalletConfig;
109
+ work_buffer: WorkItem[];
110
+ };
111
+ export type PortStatus = {
112
+ port: MessagePort;
113
+ promise: Promise<ScanLoopYield> | null;
114
+ };
115
+ export type BlocksBufferRacer = {
116
+ src: "blocks";
117
+ value: BlocksBufferIteratorResult;
118
+ };
119
+ export type ScanLoopRacer = ScanLoopYield;
120
+ export type Racers = BlocksBufferRacer | ScanLoopRacer;
121
+ /**
122
+ * coordinator main (multithreaded): dispatches scan work to CPU workers
123
+ * via MessagePorts, processes results in order per wallet.
124
+ * falls back to single-threaded coordinator if no cpuPorts provided.
125
+ */
126
+ export declare function coordinatorMainMultithreaded(scanSettingsPath?: string, pathPrefix?: string, cpuPorts?: MessagePort[]): AsyncGenerator<CoordinatorEvent>;
127
+ export declare function scheduleWorkOnCpuPorts(ports: PortStatus[], work_buffer: WorkItem[]): Promise<void>;