@spirobel/monero-wallet-api 0.4.0 → 0.5.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 (48) hide show
  1. package/README.md +80 -8
  2. package/dist/api.d.ts +4 -0
  3. package/dist/api.js +4 -0
  4. package/dist/io/atomicWrite.js +2 -1
  5. package/dist/io/logging.d.ts +40 -0
  6. package/dist/io/logging.js +174 -0
  7. package/dist/keypairs-seeds/keypairs.d.ts +8 -0
  8. package/dist/keypairs-seeds/keypairs.js +31 -3
  9. package/dist/multisig/DistributedKeyGenerator.d.ts +125 -0
  10. package/dist/multisig/DistributedKeyGenerator.js +197 -0
  11. package/dist/multisig/MultiSigTxSigner.d.ts +68 -0
  12. package/dist/multisig/MultiSigTxSigner.js +123 -0
  13. package/dist/multisig/escrow.d.ts +58 -0
  14. package/dist/multisig/escrow.js +104 -0
  15. package/dist/multisig/multisig.d.ts +36 -0
  16. package/dist/multisig/multisig.js +140 -0
  17. package/dist/node-interaction/nodeUrl.js +2 -1
  18. package/dist/scanning-syncing/backgroundWorker.d.ts +2 -0
  19. package/dist/scanning-syncing/backgroundWorker.js +20 -8
  20. package/dist/scanning-syncing/blocksbuffer/blocksBufferFetchLoop.js +9 -7
  21. package/dist/scanning-syncing/blocksbuffer/blocksbufferCoordination.js +2 -1
  22. package/dist/scanning-syncing/scanSettings.d.ts +11 -0
  23. package/dist/scanning-syncing/scanSettings.js +18 -3
  24. package/dist/scanning-syncing/scanresult/computeKeyImage.d.ts +2 -1
  25. package/dist/scanning-syncing/scanresult/computeKeyImage.js +4 -2
  26. package/dist/scanning-syncing/scanresult/scanCache.js +7 -3
  27. package/dist/scanning-syncing/scanresult/scanCacheOpened.d.ts +11 -0
  28. package/dist/scanning-syncing/scanresult/scanCacheOpened.js +104 -11
  29. package/dist/scanning-syncing/scanresult/scanCoordination.js +63 -72
  30. package/dist/scanning-syncing/scanresult/scanResult.d.ts +1 -1
  31. package/dist/scanning-syncing/scanresult/scanResult.js +19 -6
  32. package/dist/scanning-syncing/worker-entrypoints/worker.js +1 -1
  33. package/dist/scanning-syncing/worker-mains/coordinator-main.js +3 -2
  34. package/dist/scanning-syncing/worker-mains/cpubound-main.js +24 -14
  35. package/dist/scanning-syncing/worker-mains/multisig-main.d.ts +13 -0
  36. package/dist/scanning-syncing/worker-mains/multisig-main.js +31 -0
  37. package/dist/scanning-syncing/worker-mains/worker.js +23 -7
  38. package/dist/send-functionality/multisig.d.ts +72 -0
  39. package/dist/send-functionality/multisig.js +97 -0
  40. package/dist/send-functionality/transactionBuilding.d.ts +1 -0
  41. package/dist/send-functionality/transactionBuilding.js +23 -2
  42. package/dist/viewpair/ViewPair.d.ts +8 -0
  43. package/dist/viewpair/ViewPair.js +16 -8
  44. package/dist/wasm-processing/wasmFile.d.ts +1 -0
  45. package/dist/wasm-processing/wasmFile.js +8 -3
  46. package/dist/wasm-processing/wasmProcessor.d.ts +2 -2
  47. package/dist/wasm-processing/wasmProcessor.js +4 -5
  48. package/package.json +6 -2
@@ -2,7 +2,7 @@ import { atomicWrite, 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 { openScanSettingsFile, readPrivateSpendKeyFromEnv, readWalletFromScanSettings, SCAN_SETTINGS_STORE_NAME_DEFAULT, walletSettingsPlusKeys, writeNodeUrlToScanSettings, writeStartHeightToScanSettings, writeWalletToScanSettings, } from "../scanSettings";
5
+ import { getPathPrefix, openScanSettingsFile, readPrivateSpendKeyFromEnv, readWalletFromScanSettings, SCAN_SETTINGS_STORE_NAME_DEFAULT, SUB_ADDRESS_INDEX_DEFAULT_VALUE, 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
8
  import { updateSyncETA, readWriteConnectionStatusFile, } from "../connectionStatus";
@@ -53,19 +53,32 @@ export class ScanCacheOpened {
53
53
  // unpause will start scanning from this.wallet_scan_settings.start_height
54
54
  await scanCacheOpen.unpause();
55
55
  }
56
- scanCacheOpen._stats = await alignScanStatsWithCache(scanCacheOpen._cache, scanCacheOpen.view_pair, params.primary_address, params.pathPrefix, walletSettings.subaddress_index, lastRange(scanCacheOpen._cache.scanned_ranges)?.end);
56
+ scanCacheOpen._highest_subaddress_index =
57
+ walletSettings.subaddress_index || SUB_ADDRESS_INDEX_DEFAULT_VALUE;
58
+ if (!params.no_stats) {
59
+ scanCacheOpen._stats = await alignScanStatsWithCache(scanCacheOpen._cache, scanCacheOpen.view_pair, params.primary_address, getPathPrefix(params.scan_settings_path, params.pathPrefix), walletSettings.subaddress_index, lastRange(scanCacheOpen._cache.scanned_ranges)?.end);
60
+ }
61
+ else {
62
+ scanCacheOpen._no_stats = params.no_stats; // true
63
+ }
57
64
  return scanCacheOpen;
58
65
  }
59
66
  get start_height() {
60
67
  return this._start_height;
61
68
  }
69
+ get subaddress_index() {
70
+ if (typeof this._highest_subaddress_index === "undefined" ||
71
+ this._highest_subaddress_index === null)
72
+ return SUB_ADDRESS_INDEX_DEFAULT_VALUE;
73
+ return this._highest_subaddress_index;
74
+ }
62
75
  get current_height() {
63
76
  let current_range = findRange(this._cache.scanned_ranges, this._start_height || 0);
64
77
  return current_range?.end || null;
65
78
  }
66
79
  get current_top_range_height() {
67
80
  if (typeof this._stats === "undefined" || this._stats === null)
68
- return null;
81
+ return this.current_height;
69
82
  return this._stats.height;
70
83
  }
71
84
  async changeStartHeight(start_height) {
@@ -181,6 +194,21 @@ export class ScanCacheOpened {
181
194
  throw new Error("privateSpendKey not found in env");
182
195
  return await signTransaction(unsignedTx, privateSpendKey);
183
196
  }
197
+ async getFeeEstimate() {
198
+ const node = await NodeUrl.create(this.node_url);
199
+ const feeEstimate = await node.getFeeEstimate();
200
+ const feePerByte = BigInt(feeEstimate.fees[0]);
201
+ const max_plausible_fee = 20000000000n; // 0.02 XMR
202
+ const feeFor10kb = feePerByte * 10000n;
203
+ //2. check if fee is too high
204
+ if (feeFor10kb > max_plausible_fee) {
205
+ throw new Error(`fee too high:
206
+ ${feeFor10kb} (fee for 10kb tx size) > ${max_plausible_fee} (0.001 XMR)
207
+ most likely your node is faulty. connect to another node.
208
+ preferably run one yourself locally.`);
209
+ }
210
+ return feeEstimate;
211
+ }
184
212
  async calculateFeeAndSelectInputs(params) {
185
213
  const sum = sumPayments(params.payments);
186
214
  const node = await NodeUrl.create(this.node_url);
@@ -243,6 +271,49 @@ export class ScanCacheOpened {
243
271
  });
244
272
  return unsignedTx;
245
273
  }
274
+ async makeSweepTransactionFromSelectedInputs(destination_address, selectedInputs, feeEstimate) {
275
+ // 4. get output distribution
276
+ const node = await NodeUrl.create(this.node_url);
277
+ const distibution = await node.getOutputDistribution();
278
+ const inputs = [];
279
+ for (const input of selectedInputs) {
280
+ // 5. sample decoys & get outs from node: here is where a privacy compromising event could happen
281
+ const sizesToTry = this.decoyRetry
282
+ ? this.decoyRetrySizes
283
+ : [this.decoySampleCount];
284
+ let madeInput;
285
+ for (const size of sizesToTry) {
286
+ if (madeInput)
287
+ break;
288
+ try {
289
+ const prepared = prepareInput(node, distibution, input, size);
290
+ const wasmInput = node.makeInput(prepared.input, prepared.sample.candidates, await prepared.outsResponse);
291
+ madeInput = wasmInput;
292
+ }
293
+ catch (e) {
294
+ if (size === sizesToTry[sizesToTry.length - 1])
295
+ throw e;
296
+ // fall through to next size
297
+ }
298
+ }
299
+ if (!madeInput)
300
+ throw new Error("failed to make input");
301
+ inputs.push(madeInput);
302
+ }
303
+ // 7. make transaction: combine inputs, payments + fee info
304
+ const unsignedTx = this.view_pair.makeSweepTransaction({
305
+ inputs,
306
+ payments: [
307
+ {
308
+ address: destination_address,
309
+ amount: "0",
310
+ },
311
+ ],
312
+ fee_response: feeEstimate,
313
+ fee_priority: "unimportant",
314
+ });
315
+ return unsignedTx;
316
+ }
246
317
  /**
247
318
  * this function returns the unsigned transaction, throws {@link SendError}
248
319
  */
@@ -250,16 +321,30 @@ export class ScanCacheOpened {
250
321
  const { selectedInputs, feeEstimate } = await this.calculateFeeAndSelectInputs(params);
251
322
  return await this.makeTransactionFromSelectedInputs(params.payments, selectedInputs, feeEstimate);
252
323
  }
324
+ /**
325
+ * sweep inputs to external wallet address (the wallet will receive input amount - fee)
326
+ * @param inputs use spendableInputs() to find inputs to put in
327
+ */
328
+ async sweepToExternalWallet(destination_address, inputs) {
329
+ const feeEstimate = await this.getFeeEstimate();
330
+ return await this.makeSweepTransactionFromSelectedInputs(destination_address, inputs, feeEstimate);
331
+ }
253
332
  get daemon_height() {
254
333
  return this._cache.daemon_height;
255
334
  }
256
335
  get amount() {
336
+ if (this.no_stats)
337
+ throw new Error("instance has no_stats option active");
257
338
  return this._stats?.total_spendable_amount || 0n;
258
339
  }
259
340
  get pending_amount() {
341
+ if (this.no_stats)
342
+ throw new Error("instance has no_stats option active");
260
343
  return this._stats?.total_pending_amount || 0n;
261
344
  }
262
345
  get subaddresses() {
346
+ if (this.no_stats)
347
+ throw new Error("instance has no_stats option active");
263
348
  return Object.values(this._stats?.subaddresses || {});
264
349
  }
265
350
  get tx_logs() {
@@ -403,6 +488,7 @@ export class ScanCacheOpened {
403
488
  const last_subaddress_index = walletSettings.subaddress_index || 0;
404
489
  const minor = last_subaddress_index + 1;
405
490
  const subaddress = this.view_pair.makeSubaddress(minor);
491
+ this._highest_subaddress_index = minor;
406
492
  await writeWalletToScanSettings({
407
493
  primary_address: this.view_pair.primary_address,
408
494
  subaddress_index: minor,
@@ -417,13 +503,14 @@ export class ScanCacheOpened {
417
503
  created_at_timestamp,
418
504
  not_yet_included: true,
419
505
  };
420
- this._stats = await writeStatsFileDefaultLocation({
421
- primary_address: this.primary_address,
422
- pathPrefix: this.pathPrefix,
423
- writeCallback: async (stats) => {
424
- stats.subaddresses[minor.toString()] = new_subaddress;
425
- },
426
- });
506
+ if (!this._no_stats)
507
+ this._stats = await writeStatsFileDefaultLocation({
508
+ primary_address: this.primary_address,
509
+ pathPrefix: getPathPrefix(this.scan_settings_path, this.pathPrefix),
510
+ writeCallback: async (stats) => {
511
+ stats.subaddresses[minor.toString()] = new_subaddress;
512
+ },
513
+ });
427
514
  return new_subaddress;
428
515
  }
429
516
  /**
@@ -546,7 +633,8 @@ export class ScanCacheOpened {
546
633
  if (this.view_pair.primary_address !== params.newCache.primary_address)
547
634
  return;
548
635
  this._cache = params.newCache;
549
- this._stats = await alignScanStatsWithCache(this._cache, this.view_pair, this.primary_address, this.pathPrefix, undefined, lastRange(this._cache.scanned_ranges)?.end);
636
+ if (!this._no_stats)
637
+ 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);
550
638
  if (!this.no_worker) {
551
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);
552
640
  this.last_eta_height = etaResult.last_height;
@@ -557,6 +645,11 @@ export class ScanCacheOpened {
557
645
  listener(params);
558
646
  }
559
647
  }
648
+ _highest_subaddress_index = null;
649
+ _no_stats = false;
650
+ get no_stats() {
651
+ return this._no_stats;
652
+ }
560
653
  _cache = {
561
654
  daemon_height: 0,
562
655
  outputs: {},
@@ -1,9 +1,10 @@
1
- import { setupBlocksBufferGenerator, ViewPair, handleConnectionStatusChanges, processScanResultWITHOUT_SIDE_EFFECTS, writeCacheToFile, sleep, findTipIndex, } from "../../api";
1
+ import { setupBlocksBufferGenerator, ViewPair, handleConnectionStatusChanges, processScanResult, writeCacheToFile, sleep, findTipIndex, } from "../../api";
2
2
  import { markWorkItemAsDone, } from "./scanLoop";
3
3
  import { makeWorkItem, scanLoop } from "./scanLoop";
4
4
  import { cullTooLargeScanHeight, getNonHaltedWallets, getPathPrefix, openScanSettingsFile, SCAN_SETTINGS_STORE_NAME_DEFAULT, walletSettingsPlusKeys, } from "../scanSettings";
5
5
  import { findRange, initScanCacheFile, lastRange, makeCacheRangeForHeight, mergeRanges, } from "./scanCache";
6
6
  import { sendToCpuWorker } from "../worker-mains/cpubound-main";
7
+ import { log } from "../../io/logging";
7
8
  /**
8
9
  * this depends only on ScanSettings.json start_height and wallet caches scanned_ranges
9
10
  * side effect: will init wallet cache file if it does not exist
@@ -59,7 +60,7 @@ export async function findWorkToBeDone(scan_settings_path = SCAN_SETTINGS_STORE_
59
60
  for (const wallet_cache of wallet_caches) {
60
61
  // only add the range to wallets that don't already have one
61
62
  if (!findRange(wallet_cache.scanned_ranges, total_start_height)) {
62
- // console.log("inserting range_at_start", range_at_start);
63
+ log("findWorkToBeDone", "inserting range_at_start");
63
64
  wallet_cache.scanned_ranges.push(range_at_start);
64
65
  wallet_cache.scanned_ranges = mergeRanges(wallet_cache.scanned_ranges);
65
66
  }
@@ -81,9 +82,9 @@ export async function findWorkToBeDone(scan_settings_path = SCAN_SETTINGS_STORE_
81
82
  export function workToBeDoneForBatch(cache, batch_meta_infos) {
82
83
  const begin_height = batch_meta_infos[0].block_height;
83
84
  const end_height = batch_meta_infos[batch_meta_infos.length - 1].block_height;
84
- console.log("[workToBeDoneForBatch]", begin_height, end_height);
85
+ log("workToBeDoneForBatch", [begin_height, end_height]);
85
86
  const foundRange = findRange(cache.scanned_ranges, begin_height);
86
- console.log("[workToBeDoneForBatch] foundRange", foundRange);
87
+ log("workToBeDoneForBatch", ["foundRange", foundRange]);
87
88
  if (foundRange) {
88
89
  const fullycovered = cache.scanned_ranges.find((r) => r.start <= begin_height && r.end > end_height);
89
90
  if (fullycovered) {
@@ -96,13 +97,13 @@ export function workToBeDoneForBatch(cache, batch_meta_infos) {
96
97
  throw new Error("[workToBeDoneForBatch] tip not found, malformed range that covers the work to be done for this batch");
97
98
  const tipindex = findTipIndex(batch_meta_infos, tip);
98
99
  if (tipindex === "reorg_found") {
99
- console.log("[workToBeDoneForBatch] reorg found");
100
+ log("workToBeDoneForBatch", "reorg found");
100
101
  return { from: 0 };
101
102
  }
102
103
  else if (tipindex === "empty_blocks_array") {
103
104
  return "skip";
104
105
  }
105
- console.log("[workToBeDoneForBatch] tipindex", tipindex);
106
+ log("workToBeDoneForBatch", ["tipindex", tipindex]);
106
107
  return { from: tipindex }; // NORMAL case of scheduling directly in front of the tip
107
108
  }
108
109
  }
@@ -123,36 +124,14 @@ export function makeWorkItemsFromBlocksBuffer(blocksBuffer, workItemBuffer, wall
123
124
  const alreadyReferenced = workItemBuffer.some((w) => w.batch.local_uuid === batch.local_uuid &&
124
125
  w.walletConfig.primary_address === walletConfig.primary_address);
125
126
  if (!alreadyReferenced) {
126
- // const begin_height =
127
- // batch.get_blocks_result_meta.block_infos[0].block_height;
128
- // const end_height =
129
- // batch.get_blocks_result_meta.block_infos[
130
- // batch.get_blocks_result_meta.block_infos.length - 1
131
- // ].block_height;
132
- // console.log(walletConfig.cache.scanned_ranges);
133
- // console.log(begin_height);
134
- // // skip batches fully covered by an existing scanned range
135
- // if (
136
- // walletConfig.cache.scanned_ranges.some(
137
- // (r) => r.start <= begin_height && r.end >= end_height,
138
- // )
139
- // ) {
140
- // throw new Error(
141
- // "skip" +
142
- // begin_height +
143
- // " end_height" +
144
- // end_height +
145
- // " " +
146
- // walletConfig.primary_address.slice(0, 6),
147
- // );
148
- // continue;
149
- // }
150
127
  const workToBeDone = workToBeDoneForBatch(walletConfig.cache, batch.get_blocks_result_meta.block_infos);
151
128
  if (workToBeDone === "skip") {
152
129
  continue;
153
130
  }
154
131
  const workItem = makeWorkItem(walletConfig, batch, from, to);
155
- console.log(`[reconcileBlocksBufferChanged] workItem: uuid=${workItem.work_uuid.slice()} to=${workItem.to} from=${workItem.from} batchbegin_height=${batch.get_blocks_result_meta.block_infos[0].block_height} batchend_height=${batch.get_blocks_result_meta.block_infos[batch.get_blocks_result_meta.block_infos.length - 1].block_height}`);
132
+ log("makeWorkItemsFromBlocksBuffer", [
133
+ `uuid=${workItem.work_uuid.slice()} to=${workItem.to} from=${workItem.from} batchbegin_height=${batch.get_blocks_result_meta.block_infos[0].block_height} batchend_height=${batch.get_blocks_result_meta.block_infos[batch.get_blocks_result_meta.block_infos.length - 1].block_height}`,
134
+ ]);
156
135
  workItemBuffer.push(workItem);
157
136
  }
158
137
  }
@@ -168,16 +147,16 @@ export function makeWorkItemsForAllWallets(wallet_configs, blocksBuffer, workBuf
168
147
  * blocks buffer if no remaining work items reference it.
169
148
  */
170
149
  export function reconcileWorkItemDone(blocksBuffer, workItemBuffer) {
171
- // console.log(
172
- // `[reconcileWorkItemDone] workItemBuffer.length=${workItemBuffer.length} , blocksBuffer.length=${blocksBuffer.length}`,
173
- // );
150
+ log("reconcileWorkItemDone", [
151
+ `workItemBuffer.length=${workItemBuffer.length} , blocksBuffer.length=${blocksBuffer.length}`,
152
+ ]);
174
153
  while (workItemBuffer.length > 0 &&
175
154
  workItemBuffer[0].status === "process_result_done") {
176
155
  const removed = workItemBuffer.shift();
177
156
  const stillReferenced = workItemBuffer.some((w) => w.batch.local_uuid === removed.batch.local_uuid);
178
- // console.log(
179
- // `[reconcileWorkItemDone] workItem: ${removed.work_uuid.slice()} removed. stillReferenced=${stillReferenced}`,
180
- // );
157
+ log("reconcileWorkItemDone", [
158
+ `workItem: ${removed.work_uuid.slice()} removed. stillReferenced=${stillReferenced}`,
159
+ ]);
181
160
  if (!stillReferenced) {
182
161
  const idx = blocksBuffer.findIndex((b) => b.local_uuid === removed.batch.local_uuid);
183
162
  // this really means we have to save work items scanCache to file
@@ -218,17 +197,20 @@ export async function processScanResultForWorkItem(value, workBuffer, blocksBuff
218
197
  if (value.type !== "Ready" || !value.result || !value.work_uuid)
219
198
  return;
220
199
  const item = await markWorkItemAsDone(value, workBuffer);
221
- //console.log(`[processScanResultForWorkItem] item=${JSON.stringify(item)}`);
200
+ log("processScanResultForWorkItem", `item_work_uuid=${item?.work_uuid}`);
222
201
  if (!item || item.status !== "scanwork_done")
223
202
  throw new Error("[processScanResultForWorkItem] item not found or not scanwork not done. item_status=" +
224
203
  item?.status);
225
204
  const firstBlock = item.batch.get_blocks_result_meta.block_infos[item.from];
226
- //console.log(`
227
- // [processScanResultForWorkItem] block_infos=${JSON.stringify(item.batch.get_blocks_result_meta.block_infos)} block_infos.length=${item.batch.get_blocks_result_meta.block_infos.length} firstBlock=${firstBlock} `);
228
- console.log(`[processScanResultForWorkItem] block_infos.length=${item.batch.get_blocks_result_meta.block_infos.length} from=${item.from} to=${item.to}`);
205
+ log("processScanResultForWorkItem", [
206
+ `block_infos.length=${item.batch.get_blocks_result_meta.block_infos.length} firstBlock=${firstBlock?.block_height} firstBlockHash=${firstBlock?.block_hash?.slice(0, 8)}`,
207
+ ]);
208
+ log("processScanResultForWorkItem", [
209
+ `block_infos.length=${item.batch.get_blocks_result_meta.block_infos.length} from=${item.from} to=${item.to}`,
210
+ ]);
229
211
  const lastBlock = item.batch.get_blocks_result_meta.block_infos[item.to];
230
212
  const cache = item.walletConfig.cache;
231
- await processScanResultWITHOUT_SIDE_EFFECTS({
213
+ await processScanResult({
232
214
  from_height: firstBlock.block_height,
233
215
  to_height: lastBlock.block_height,
234
216
  result: value.result,
@@ -263,28 +245,27 @@ export async function processWorkItem(item, workBuffer, blocksBuffer, pathPrefix
263
245
  }
264
246
  }
265
247
  const firstBlock = item.batch.get_blocks_result_meta.block_infos[item.from];
266
- // console.log(
267
- // `[processWorkItem] block_infos.length=${item.batch.get_blocks_result_meta.block_infos.length} from=${item.from} to=${item.to}`,
268
- // );
248
+ log("processWorkItem", [
249
+ `block_infos.length=${item.batch.get_blocks_result_meta.block_infos.length} from=${item.from} to=${item.to}`,
250
+ ]);
269
251
  const lastBlock = item.batch.get_blocks_result_meta.block_infos[item.to];
270
- // console.log(
271
- // "[processWorkItem] ",
272
- // item.walletConfig.primary_address.slice(0, 6),
273
- // "@",
274
- // firstBlock.block_height,
275
- // "-",
276
- // lastBlock.block_height,
277
- // );
252
+ log("processWorkItem", [
253
+ item.walletConfig.primary_address.slice(0, 6),
254
+ "@",
255
+ firstBlock.block_height,
256
+ "-",
257
+ lastBlock.block_height,
258
+ ]);
278
259
  let res;
279
260
  try {
280
- res = await processScanResultWITHOUT_SIDE_EFFECTS({
261
+ res = await processScanResult({
281
262
  from_height: firstBlock.block_height,
282
263
  to_height: lastBlock.block_height,
283
264
  result: item.result,
284
265
  scanCache: cache,
285
266
  secret_spend_key,
286
267
  });
287
- // console.log("[processWorkItem] res=", res);
268
+ log("processWorkItem", ["res=", res]);
288
269
  }
289
270
  catch (error) {
290
271
  console.error("[processWorkItem] error=", error);
@@ -292,7 +273,7 @@ export async function processWorkItem(item, workBuffer, blocksBuffer, pathPrefix
292
273
  }
293
274
  await writeCacheToFile(cache, pathPrefix);
294
275
  item.status = "process_result_done";
295
- //console.log(`[processWorkItem] process_result_done item=${JSON.stringify(item)}`);
276
+ log("processWorkItem", `process_result_done work_uuid=${item?.work_uuid}`);
296
277
  // remove from blocksbuffer if no more work items reference it
297
278
  reconcileWorkItemDone(blocksBuffer, workBuffer);
298
279
  //because cache is tied to the workitems by reference,
@@ -314,7 +295,9 @@ function logBufStatus(blocksBuffer, workBuffer, ports, label) {
314
295
  w.batch.get_blocks_result_meta.block_infos[w.from]?.block_height +
315
296
  "-" +
316
297
  w.batch.get_blocks_result_meta.block_infos[w.to]?.block_height);
317
- console.log(`[buf] ${label} blocks=[${bb.join(",")}] work=[${wb.join(",")}] `);
298
+ log("logBufStatus", [
299
+ `[buf] ${label} blocks=[${bb.join(",")}] work=[${wb.join(",")}]`,
300
+ ]);
318
301
  }
319
302
  /**
320
303
  * coordinator main: async generator that drives the full scan cycle.
@@ -353,10 +336,9 @@ export async function* coordinatorMain(scanSettingsPath, pathPrefix) {
353
336
  races.push(p.then((v) => ({ src: "scan", addr, value: v })));
354
337
  }
355
338
  const winner = await Promise.race(races);
356
- console.log("[coordinatorMain] winner=" +
357
- winner.src +
358
- " addr=" +
359
- String(winner.addr ?? "").slice(0, 8));
339
+ log("coordinatorMain", [
340
+ "winner=" + winner.src + " addr=" + String(winner.addr ?? "").slice(0, 8),
341
+ ]);
360
342
  if (winner.src === "blocks") {
361
343
  const result = winner.value.value;
362
344
  const { isBlocksBufferChanged } = await handleBlocksYield(result, scanSettingsPath);
@@ -368,7 +350,7 @@ export async function* coordinatorMain(scanSettingsPath, pathPrefix) {
368
350
  if (scanPromises.has(addr))
369
351
  continue;
370
352
  const gen = scanGens.get(addr);
371
- console.log("scanGens.get(addr)", gen);
353
+ log("coordinatorMain", ["scanGens.get(addr)", gen]);
372
354
  if (!gen)
373
355
  continue;
374
356
  const item = workBuffer.find((x) => x.status === "fresh" && x.walletConfig.primary_address === addr);
@@ -459,7 +441,7 @@ export async function* coordinatorMainMultithreaded(scanSettingsPath, pathPrefix
459
441
  // throw new Error(
460
442
  // "[coordinatorMain Multithreaded] cpuPorts empty, there must be at least cpu worker",
461
443
  // );
462
- console.log("[coordinatorMainMultithreaded] fallback to single-threaded");
444
+ log("coordinatorMainMultithreaded", "fallback to single-threaded");
463
445
  yield* coordinatorMain(scanSettingsPath, pathPrefix);
464
446
  return;
465
447
  }
@@ -482,7 +464,7 @@ export async function* coordinatorMainMultithreaded(scanSettingsPath, pathPrefix
482
464
  let race_count = 0;
483
465
  while (true) {
484
466
  race_count++;
485
- console.log("[coordinatorMainMultithreaded] race_count", race_count);
467
+ log("coordinatorMainMultithreaded", ["race_count", race_count]);
486
468
  await scheduleWorkOnCpuPorts(freePorts, workBuffer);
487
469
  const scan_promises = freePorts
488
470
  .filter((ps) => ps.promise)
@@ -494,9 +476,9 @@ export async function* coordinatorMainMultithreaded(scanSettingsPath, pathPrefix
494
476
  value: v,
495
477
  })),
496
478
  ];
497
- console.log("[coordinatorMainMultithreaded] races", races);
479
+ log("coordinatorMainMultithreaded", ["races", races]);
498
480
  const winner = await Promise.race(races);
499
- //console.log("[coordinatorMainMultithreaded] cpu ports status", freePorts);
481
+ log("coordinatorMainMultithreaded", "cpu ports status");
500
482
  if ("src" in winner && winner.src === "blocks") {
501
483
  const result = winner.value.value;
502
484
  const { isBlocksBufferChanged } = await handleBlocksYield(result, scanSettingsPath);
@@ -529,7 +511,7 @@ export async function* coordinatorMainMultithreaded(scanSettingsPath, pathPrefix
529
511
  if (!to_be_processed)
530
512
  continue;
531
513
  const res = await processWorkItem(to_be_processed, workBuffer, blocksBuffer, getPathPrefix(scanSettingsPath, pathPrefix), wallet.secret_spend_key);
532
- // console.log("[coordinatorMainMultithreaded] processWorkItem result", res);
514
+ // log("coordinatorMainMultithreaded", "processWorkItem result");
533
515
  yield {
534
516
  type: "scan_ready",
535
517
  address: wallet.primary_address,
@@ -561,9 +543,14 @@ export async function scheduleWorkOnCpuPorts(ports, work_buffer) {
561
543
  const onmessage = (event) => {
562
544
  const msg = event.data;
563
545
  // handle the result here:
564
- console.log("[scheduleWorkOnCpuPorts] onmessage result", msg);
546
+ log("scheduleWorkOnCpuPorts", ["onmessage result", msg]);
565
547
  if (msg.work_uuid !== item.work_uuid) {
566
- console.log("[scheduleWorkOnCpuPorts] wrong work_uuid in msg msg.work_uuid=", msg.work_uuid, "item.work_uuid=", item.work_uuid);
548
+ log("scheduleWorkOnCpuPorts", [
549
+ "wrong work_uuid in msg msg.work_uuid=",
550
+ msg.work_uuid,
551
+ "item.work_uuid=",
552
+ item.work_uuid,
553
+ ]);
567
554
  throw new Error("[scheduleWorkOnCpuPorts] wrong work_uuid in msg");
568
555
  }
569
556
  if (msg.type === "WORKSTART") {
@@ -571,7 +558,7 @@ export async function scheduleWorkOnCpuPorts(ports, work_buffer) {
571
558
  return;
572
559
  }
573
560
  if (item.status !== "scanwork_in_progress") {
574
- console.log("[scheduleWorkOnCpuPorts] wrong status in msg");
561
+ log("scheduleWorkOnCpuPorts", "wrong status in msg");
575
562
  throw new Error("[scheduleWorkOnCpuPorts] wrong status in msg");
576
563
  }
577
564
  item.result = msg.result;
@@ -581,7 +568,7 @@ export async function scheduleWorkOnCpuPorts(ports, work_buffer) {
581
568
  };
582
569
  port_status.port.onmessage = onmessage;
583
570
  item.status = "scanwork_in_progress";
584
- console.log("[scheduleWorkOnCpuPorts] scheduling work item", item.work_uuid);
571
+ log("scheduleWorkOnCpuPorts", ["scheduling work item", item.work_uuid]);
585
572
  port_status.promise = new Promise((resolve) => {
586
573
  resolve_port = resolve;
587
574
  });
@@ -604,7 +591,11 @@ export async function scheduleWorkOnCpuPorts(ports, work_buffer) {
604
591
  break;
605
592
  }
606
593
  catch {
607
- console.log("[scheduleWorkOnCpuPorts] resend attempt", attempt, item.work_uuid);
594
+ log("scheduleWorkOnCpuPorts", [
595
+ "resend attempt",
596
+ attempt,
597
+ item.work_uuid,
598
+ ]);
608
599
  }
609
600
  }
610
601
  }
@@ -22,7 +22,7 @@ export type ProcessScanResult = {
22
22
  current_range: CacheRange;
23
23
  changed_outputs: ChangedOutput[];
24
24
  };
25
- export declare function processScanResultWITHOUT_SIDE_EFFECTS(params: ProcessScanResultParamsWithoutSideEffects): Promise<ProcessScanResult>;
25
+ export declare function processScanResult(params: ProcessScanResultParamsWithoutSideEffects): Promise<ProcessScanResult>;
26
26
  export declare function processResultReturnValue(cache: ScanCache, params: ProcessScanResultParamsWithoutSideEffects, changed_outputs: ChangedOutput[]): {
27
27
  current_range: CacheRange;
28
28
  changed_outputs: ChangedOutput[];
@@ -1,8 +1,11 @@
1
- import { CatastrophicReorgError, } from "../../api";
1
+ import { CatastrophicReorgError } from "../../api";
2
2
  import { computeKeyImage } from "./computeKeyImage";
3
- import { mergeRanges, findRange, readCacheFileDefaultLocation, lastRange, writeCacheToFile, findRangeThrows, } from "./scanCache";
3
+ import { log } from "../../io/logging";
4
+ import { mergeRanges, findRange, findRangeThrows } from "./scanCache";
4
5
  import {} from "../../node-interaction/binaryEndpoints";
5
- export async function processScanResultWITHOUT_SIDE_EFFECTS(params) {
6
+ import { WasmProcessor } from "../../wasm-processing/wasmProcessor";
7
+ import { monero_wallet_api_wasm } from "../../wasm-processing/wasmFile";
8
+ export async function processScanResult(params) {
6
9
  const { result, scanCache: cache, secret_spend_key } = params;
7
10
  let changed_outputs = [];
8
11
  if (!(result && "primary_address" in result))
@@ -25,7 +28,10 @@ export async function processScanResultWITHOUT_SIDE_EFFECTS(params) {
25
28
  throw new CatastrophicReorgError("could not locate anchors, should not try to processResult on stale workItems");
26
29
  const split_height = result.block_infos[split_height_index];
27
30
  // we found the split height & do the reorg
28
- console.log("[handleReorg] SPLIT FOUND at height", split_height.block_height);
31
+ log("processScanResult", [
32
+ "SPLIT FOUND at height",
33
+ split_height.block_height,
34
+ ]);
29
35
  if (!cache.reorg_info) {
30
36
  cache.reorg_info = {
31
37
  split_heights: [split_height],
@@ -41,7 +47,12 @@ export async function processScanResultWITHOUT_SIDE_EFFECTS(params) {
41
47
  const reverted_outputs = Object.entries(cache.outputs).filter(([id, output]) => output.spent_block_height !== undefined &&
42
48
  output.spent_block_height >= split_height.block_height);
43
49
  const removed_outputs = Object.entries(cache.outputs).filter(([id, output]) => output.block_height >= split_height.block_height);
44
- console.log("[handleReorg] removed_outputs count:", removed_outputs.length, "reverted count:", reverted_outputs.length);
50
+ log("processScanResult", [
51
+ "removed_outputs count:",
52
+ removed_outputs.length,
53
+ "reverted count:",
54
+ reverted_outputs.length,
55
+ ]);
45
56
  for (const [id, old_output_state] of removed_outputs) {
46
57
  // 1. find key_image of output to be removed (as it was reorged)
47
58
  const [key_image] = Object.entries(cache.own_key_images).find(([own_key_image, globalid]) => globalid === id) || [""]; // if this is viewonly the key_image will be empty
@@ -128,6 +139,7 @@ export function makeNewRange(newRange, cache) {
128
139
  // Assumption: result is new, cache is still old. (this + detectOwnspends() turns the catch new, based on the scan result)
129
140
  export async function detectOutputs(result, cache, spend_private_key) {
130
141
  let changed_outputs = [];
142
+ const wasmProcessor = await WasmProcessor.init(monero_wallet_api_wasm);
131
143
  for (const output of result.outputs) {
132
144
  // 0. prevent burning bug and avoid overwriting earlier found outputs
133
145
  const duplicate = Object.values(cache.outputs).find((ex) => ex.stealth_address === output.stealth_address && !ex.burned);
@@ -158,7 +170,7 @@ export async function detectOutputs(result, cache, spend_private_key) {
158
170
  changed_outputs.push({ output, change_reason: "added" });
159
171
  // 3. if this is not view only, add the key image to the cache, to find transactions spent by this wallet
160
172
  if (spend_private_key) {
161
- let keyImage = await computeKeyImage(output, spend_private_key);
173
+ let keyImage = await computeKeyImage(output, spend_private_key, wasmProcessor);
162
174
  if (keyImage) {
163
175
  cache.own_key_images[keyImage] = globalId;
164
176
  }
@@ -214,6 +226,7 @@ export function outputStatus(output, cache, current_height) {
214
226
  if (unlock_height > current_height) {
215
227
  return { status: "pending", unlock_height };
216
228
  }
229
+ // TODO: check that that output is not txlog input indeces
217
230
  return { status: "spendable" };
218
231
  }
219
232
  export function spendable(output, cache, current_height) {