@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.
@@ -1,6 +1,6 @@
1
- import { setupBlocksBufferGenerator, ViewPair, handleConnectionStatusChanges, processScanResult, writeCacheToFile, sleep, findTipIndex, readWriteConnectionStatusFile, connectionStatusFilePath, } from "../../api";
1
+ import { setupBlocksBufferGenerator, ViewPair, handleConnectionStatusChanges, processScanResult, writeCacheToFile, sleep, findTipIndex, readWriteConnectionStatusFile, } from "../../api";
2
2
  import { markWorkItemAsDone, } from "./scanLoop";
3
- import { makeWorkItem, scanLoop } from "./scanLoop";
3
+ import { makeWorkItem } from "./scanLoop";
4
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";
@@ -166,19 +166,6 @@ export function reconcileWorkItemDone(blocksBuffer, workItemBuffer) {
166
166
  }
167
167
  }
168
168
  }
169
- /**
170
- * one round of the select race. takes two primed promises (blocks, scan),
171
- * races them, returns winner info. the loser stays in flight.
172
- */
173
- export async function raceStep(blocksPromise, scanPromise) {
174
- const winner = await Promise.race([
175
- blocksPromise.then((v) => ({ src: "blocks", value: v })),
176
- scanPromise.then((v) => ({ src: "scan", value: v })),
177
- ]);
178
- const value = winner.value;
179
- const done = !!winner.value.done;
180
- return { src: winner.src, value, done };
181
- }
182
169
  /**
183
170
  * handle a yield from the blocks buffer fetch loop.
184
171
  */
@@ -189,45 +176,6 @@ export async function handleBlocksYield(value, scanSettingsPath) {
189
176
  await handleConnectionStatusChanges(value, scanSettingsPath);
190
177
  return { isBlocksBufferChanged: false };
191
178
  }
192
- /**
193
- * process a scan result for a completed work item.
194
- * updates the cache, writes to disk, marks work item done, reconciles.
195
- */
196
- export async function processScanResultForWorkItem(value, workBuffer, blocksBuffer, pathPrefix, secret_spend_key) {
197
- if (value.type !== "Ready" || !value.result || !value.work_uuid)
198
- return;
199
- const item = await markWorkItemAsDone(value, workBuffer);
200
- log("processScanResultForWorkItem", `item_work_uuid=${item?.work_uuid}`);
201
- if (!item || item.status !== "scanwork_done")
202
- throw new Error("[processScanResultForWorkItem] item not found or not scanwork not done. item_status=" +
203
- item?.status);
204
- const firstBlock = item.batch.get_blocks_result_meta.block_infos[item.from];
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
- ]);
211
- const lastBlock = item.batch.get_blocks_result_meta.block_infos[item.to];
212
- const cache = item.walletConfig.cache;
213
- await processScanResult({
214
- from_height: firstBlock.block_height,
215
- to_height: lastBlock.block_height,
216
- result: value.result,
217
- scanCache: cache,
218
- secret_spend_key,
219
- });
220
- await writeCacheToFile(cache, pathPrefix);
221
- item.status = "process_result_done";
222
- reconcileWorkItemDone(blocksBuffer, workBuffer);
223
- //because cache is tied to the workitems by reference,
224
- // the following workitems will have the most recent cache
225
- //TODO: if we do cpu workers we need to be sure to wait with the processing at the top of this functon
226
- // until all workitems for this wallet before it (to the left of it) are done
227
- // the cpu worker loop generator will have to ensure the order of this
228
- // currently as a sideeffect of the work scheduling function on fetch result aka blocks buffer changed,
229
- // this is already implicitly handled
230
- }
231
179
  /**
232
180
  * process a scan result for a completed work item.
233
181
  * updates the cache, writes to disk, marks work item done, reconciles blockbuffer with this.
@@ -323,131 +271,6 @@ function computeETA(cache, totalBlocksScanned, scanStartTime) {
323
271
  const etaMs = remaining / blocksPerMs;
324
272
  return msToHHMM(etaMs);
325
273
  }
326
- /**
327
- * coordinator main: async generator that drives the full scan cycle.
328
- * takes only scanSettingsPath, derives everything else internally.
329
- * yields events for some of the underlying generator events,
330
- * look at CoordinatorEvent, processing not decoupled from cpu bound scan work,
331
- * as this single threaded, there is no point
332
- */
333
- export async function* coordinatorMain(scanSettingsPath, pathPrefix) {
334
- const ctx = await setupCoordinator(scanSettingsPath, pathPrefix);
335
- if (!ctx)
336
- throw new Error("[coordinatorMain] findWorkToBeDone returned false");
337
- const work_to_be_done = ctx.work_to_be_done;
338
- const blocksBuffer = ctx.blocksBuffer;
339
- const workBuffer = ctx.workBuffer;
340
- const blocksGenerator = ctx.blocksGenerator;
341
- let totalBlocksScanned = 0;
342
- let scanStartTime = Date.now();
343
- let blocksPromise = blocksGenerator.next();
344
- const scanGens = new Map();
345
- const scanPromises = new Map();
346
- for (const wc of work_to_be_done.wallet_configs) {
347
- scanGens.set(wc.primary_address, scanLoop({
348
- primary_address: wc.primary_address,
349
- secret_view_key: wc.secret_view_key,
350
- secret_spend_key: wc.secret_spend_key,
351
- subaddress_index: wc.subaddress_index,
352
- }));
353
- }
354
- // prime all scan generators
355
- // ( technically no need to do this as loop + scan generator + workbuffer structure mean it will happen automatically)
356
- for (const [, gen] of scanGens) {
357
- await gen.next();
358
- }
359
- while (true) {
360
- const races = [blocksPromise.then((v) => ({ src: "blocks", value: v }))];
361
- for (const [addr, p] of scanPromises) {
362
- races.push(p.then((v) => ({ src: "scan", addr, value: v })));
363
- }
364
- const winner = await Promise.race(races);
365
- log("coordinatorMain", [
366
- "winner=" + winner.src + " addr=" + String(winner.addr ?? "").slice(0, 8),
367
- ]);
368
- if (winner.src === "blocks") {
369
- const result = winner.value.value;
370
- const { isBlocksBufferChanged } = await handleBlocksYield(result, scanSettingsPath);
371
- if (isBlocksBufferChanged) {
372
- makeWorkItemsForAllWallets(work_to_be_done.wallet_configs, blocksBuffer, workBuffer);
373
- // start scan promises for wallets that now have work
374
- for (const wc of work_to_be_done.wallet_configs) {
375
- const addr = wc.primary_address;
376
- if (scanPromises.has(addr))
377
- continue;
378
- const gen = scanGens.get(addr);
379
- log("coordinatorMain", ["scanGens.get(addr)", gen]);
380
- if (!gen)
381
- continue;
382
- const item = workBuffer.find((x) => x.status === "fresh" && x.walletConfig.primary_address === addr);
383
- if (item) {
384
- item.status = "scanwork_in_progress";
385
- scanPromises.set(addr, gen.next(item));
386
- }
387
- }
388
- logBufStatus(blocksBuffer, workBuffer, scanPromises, "after_blocks");
389
- yield { type: "blocks_buffer_changed" };
390
- }
391
- else {
392
- logBufStatus(blocksBuffer, workBuffer, scanPromises, "fetch_conn");
393
- yield { type: "connection_status", status: result };
394
- }
395
- blocksPromise = blocksGenerator.next();
396
- }
397
- else {
398
- const addr = winner.addr;
399
- const gen = scanGens.get(addr);
400
- const value = winner.value.value;
401
- if (value.type === "InProgress") {
402
- scanPromises.set(addr, gen.next());
403
- }
404
- else if (value.type === "Ready") {
405
- const wc = work_to_be_done.wallet_configs.find((x) => x.primary_address === addr);
406
- if (!wc)
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;
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
- }
427
- const nextItem = workBuffer.find((x) => x.status !== "process_result_done" &&
428
- x.walletConfig.primary_address === addr);
429
- if (nextItem) {
430
- scanPromises.set(addr, gen.next(nextItem));
431
- }
432
- else {
433
- scanPromises.delete(addr);
434
- }
435
- logBufStatus(blocksBuffer, workBuffer, scanPromises, "after_scan");
436
- yield {
437
- type: "scan_ready",
438
- address: addr,
439
- result: value,
440
- newCache: wc.cache,
441
- changed_outputs: [],
442
- };
443
- // signal idle when no scans active and nothing buffered
444
- if (scanPromises.size === 0 && blocksBuffer.length === 0) {
445
- yield { type: "all_idle" };
446
- }
447
- }
448
- }
449
- }
450
- }
451
274
  export async function setupCoordinator(scanSettingsPath, pathPrefix) {
452
275
  const work_to_be_done = await findWorkToBeDone(scanSettingsPath, pathPrefix);
453
276
  if (!work_to_be_done)
@@ -482,12 +305,7 @@ export async function setupCoordinator(scanSettingsPath, pathPrefix) {
482
305
  */
483
306
  export async function* coordinatorMainMultithreaded(scanSettingsPath, pathPrefix, cpuPorts) {
484
307
  if (!cpuPorts || cpuPorts.length === 0) {
485
- // throw new Error(
486
- // "[coordinatorMain Multithreaded] cpuPorts empty, there must be at least cpu worker",
487
- // );
488
- log("coordinatorMainMultithreaded", "fallback to single-threaded");
489
- yield* coordinatorMain(scanSettingsPath, pathPrefix);
490
- return;
308
+ throw new Error("[coordinatorMain Multithreaded] cpuPorts empty, there must be at least cpu worker");
491
309
  }
492
310
  const ctx = await setupCoordinator(scanSettingsPath, pathPrefix);
493
311
  if (!ctx)