@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.
- package/README.md +6 -0
- package/dist/api.d.ts +1 -1
- package/dist/api.js +1 -1
- package/dist/io/logging.d.ts +6 -6
- package/dist/io/logging.js +4 -3
- package/dist/io/sleep.d.ts +1 -1
- package/dist/io/sleep.js +16 -1
- package/dist/multisig/escrow.js +0 -1
- package/dist/scanning-syncing/backgroundWorker.d.ts +2 -1
- package/dist/scanning-syncing/backgroundWorker.js +104 -34
- package/dist/scanning-syncing/blocksbuffer/blocksBufferFetchLoop.js +33 -2
- package/dist/scanning-syncing/connectionStatus.d.ts +6 -0
- package/dist/scanning-syncing/connectionStatus.js +15 -0
- package/dist/scanning-syncing/connectionStatusOpened.d.ts +3 -1
- package/dist/scanning-syncing/connectionStatusOpened.js +22 -5
- package/dist/scanning-syncing/openWallet.d.ts +4 -1
- package/dist/scanning-syncing/openWallet.js +3 -0
- package/dist/scanning-syncing/scanresult/scanCacheOpened.d.ts +12 -4
- package/dist/scanning-syncing/scanresult/scanCacheOpened.js +188 -107
- package/dist/scanning-syncing/scanresult/scanCoordination.d.ts +6 -25
- package/dist/scanning-syncing/scanresult/scanCoordination.js +89 -209
- package/dist/scanning-syncing/scanresult/scanLoop.d.ts +1 -2
- package/dist/scanning-syncing/scanresult/scanLoop.js +0 -13
- package/dist/scanning-syncing/worker-entrypoints/worker.js +1 -1
- package/dist/scanning-syncing/worker-mains/coordinator-main.d.ts +1 -0
- package/dist/scanning-syncing/worker-mains/coordinator-main.js +16 -2
- package/dist/scanning-syncing/worker-mains/cpubound-main.d.ts +5 -2
- package/dist/scanning-syncing/worker-mains/cpubound-main.js +10 -8
- package/dist/scanning-syncing/worker-mains/worker.js +50 -2
- package/dist/scansettings/scanSettingsOpened.js +3 -6
- package/dist/tools/monero-tools.js +1 -1
- package/dist/viewpair/ViewPair.js +9 -4
- package/dist/wasm-processing/wasmFile.js +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { setupBlocksBufferGenerator, ViewPair, handleConnectionStatusChanges, processScanResult, writeCacheToFile, sleep, findTipIndex, readWriteConnectionStatusFile,
|
|
2
|
-
import {
|
|
3
|
-
import { makeWorkItem
|
|
1
|
+
import { setupBlocksBufferGenerator, ViewPair, handleConnectionStatusChanges, processScanResult, writeCacheToFile, sleep, findTipIndex, readWriteConnectionStatusFile, applyWalletScanProgress, } from "../../api";
|
|
2
|
+
import {} 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.
|
|
@@ -272,6 +220,9 @@ export async function processWorkItem(item, workBuffer, blocksBuffer, pathPrefix
|
|
|
272
220
|
throw error;
|
|
273
221
|
}
|
|
274
222
|
await writeCacheToFile(cache, pathPrefix);
|
|
223
|
+
// raw cpu ScanResult is fully merged into cache now. drop it so workBuffer
|
|
224
|
+
// does not pin all_key_images / hash strings until left-edge free.
|
|
225
|
+
item.result = undefined;
|
|
275
226
|
item.status = "process_result_done";
|
|
276
227
|
log("processWorkItem", `process_result_done work_uuid=${item?.work_uuid}`);
|
|
277
228
|
// remove from blocksbuffer if no more work items reference it
|
|
@@ -323,132 +274,7 @@ function computeETA(cache, totalBlocksScanned, scanStartTime) {
|
|
|
323
274
|
const etaMs = remaining / blocksPerMs;
|
|
324
275
|
return msToHHMM(etaMs);
|
|
325
276
|
}
|
|
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
|
-
export async function setupCoordinator(scanSettingsPath, pathPrefix) {
|
|
277
|
+
export async function setupCoordinator(scanSettingsPath, pathPrefix, stopSync) {
|
|
452
278
|
const work_to_be_done = await findWorkToBeDone(scanSettingsPath, pathPrefix);
|
|
453
279
|
if (!work_to_be_done)
|
|
454
280
|
return false;
|
|
@@ -457,6 +283,7 @@ export async function setupCoordinator(scanSettingsPath, pathPrefix) {
|
|
|
457
283
|
startHeight: work_to_be_done.start_height,
|
|
458
284
|
anchor_range: work_to_be_done.anchor_range,
|
|
459
285
|
scanSettingsPath,
|
|
286
|
+
stopSync,
|
|
460
287
|
});
|
|
461
288
|
const workBuffer = [];
|
|
462
289
|
//TODO: refactor to per wallet buffer
|
|
@@ -475,21 +302,46 @@ export async function setupCoordinator(scanSettingsPath, pathPrefix) {
|
|
|
475
302
|
work_to_be_done,
|
|
476
303
|
};
|
|
477
304
|
}
|
|
305
|
+
// flip inflight work back to fresh so a restart can reschedule it
|
|
306
|
+
export function resetInProgressWorkItems(workBuffer) {
|
|
307
|
+
for (const item of workBuffer) {
|
|
308
|
+
if (item.status === "scanwork_in_progress") {
|
|
309
|
+
item.status = "fresh";
|
|
310
|
+
item.result = undefined;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
// tell busy cpu ports to cancel and wait for their promises (with timeout)
|
|
315
|
+
export async function cancelBusyCpuPorts(ports, timeoutMs = 3000) {
|
|
316
|
+
const busy = ports.filter((ps) => ps.promise);
|
|
317
|
+
for (const ps of busy) {
|
|
318
|
+
try {
|
|
319
|
+
sendToCpuWorker(ps.port, "cancel");
|
|
320
|
+
}
|
|
321
|
+
catch (err) {
|
|
322
|
+
log("cancelBusyCpuPorts", ["send cancel failed", String(err)]);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (busy.length === 0)
|
|
326
|
+
return;
|
|
327
|
+
await Promise.race([
|
|
328
|
+
Promise.allSettled(busy.map((ps) => ps.promise)),
|
|
329
|
+
sleep(timeoutMs),
|
|
330
|
+
]);
|
|
331
|
+
for (const ps of ports) {
|
|
332
|
+
ps.promise = null;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
478
335
|
/**
|
|
479
336
|
* coordinator main (multithreaded): dispatches scan work to CPU workers
|
|
480
337
|
* via MessagePorts, processes results in order per wallet.
|
|
481
338
|
* falls back to single-threaded coordinator if no cpuPorts provided.
|
|
482
339
|
*/
|
|
483
|
-
export async function* coordinatorMainMultithreaded(scanSettingsPath, pathPrefix, cpuPorts) {
|
|
340
|
+
export async function* coordinatorMainMultithreaded(scanSettingsPath, pathPrefix, cpuPorts, stopSync) {
|
|
484
341
|
if (!cpuPorts || cpuPorts.length === 0) {
|
|
485
|
-
|
|
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;
|
|
342
|
+
throw new Error("[coordinatorMain Multithreaded] cpuPorts empty, there must be at least cpu worker");
|
|
491
343
|
}
|
|
492
|
-
const ctx = await setupCoordinator(scanSettingsPath, pathPrefix);
|
|
344
|
+
const ctx = await setupCoordinator(scanSettingsPath, pathPrefix, stopSync);
|
|
493
345
|
if (!ctx)
|
|
494
346
|
throw new Error("[coordinatorMain Multithreaded] findWorkToBeDone returned false");
|
|
495
347
|
const work_to_be_done = ctx.work_to_be_done;
|
|
@@ -509,6 +361,13 @@ export async function* coordinatorMainMultithreaded(scanSettingsPath, pathPrefix
|
|
|
509
361
|
}
|
|
510
362
|
let race_count = 0;
|
|
511
363
|
while (true) {
|
|
364
|
+
if (stopSync?.aborted) {
|
|
365
|
+
log("coordinatorMainMultithreaded", ["shutdown signal, draining"]);
|
|
366
|
+
await cancelBusyCpuPorts(freePorts);
|
|
367
|
+
resetInProgressWorkItems(workBuffer);
|
|
368
|
+
yield { type: "shutdown_done" };
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
512
371
|
race_count++;
|
|
513
372
|
log("coordinatorMainMultithreaded", ["race_count", race_count]);
|
|
514
373
|
await scheduleWorkOnCpuPorts(freePorts, workBuffer);
|
|
@@ -526,6 +385,16 @@ export async function* coordinatorMainMultithreaded(scanSettingsPath, pathPrefix
|
|
|
526
385
|
const winner = await Promise.race(races);
|
|
527
386
|
log("coordinatorMainMultithreaded", "cpu ports status");
|
|
528
387
|
if ("src" in winner && winner.src === "blocks") {
|
|
388
|
+
// fetch loop returned (done) on abort, treat as shutdown
|
|
389
|
+
if (winner.value.done) {
|
|
390
|
+
log("coordinatorMainMultithreaded", [
|
|
391
|
+
"blocks generator done, draining",
|
|
392
|
+
]);
|
|
393
|
+
await cancelBusyCpuPorts(freePorts);
|
|
394
|
+
resetInProgressWorkItems(workBuffer);
|
|
395
|
+
yield { type: "shutdown_done" };
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
529
398
|
const result = winner.value.value;
|
|
530
399
|
const { isBlocksBufferChanged } = await handleBlocksYield(result, scanSettingsPath);
|
|
531
400
|
if (isBlocksBufferChanged) {
|
|
@@ -558,30 +427,22 @@ export async function* coordinatorMainMultithreaded(scanSettingsPath, pathPrefix
|
|
|
558
427
|
continue;
|
|
559
428
|
const blockCount = to_be_processed.to - to_be_processed.from + 1;
|
|
560
429
|
totalBlocksScanned += blockCount;
|
|
561
|
-
const eta = computeETA(wallet.cache, totalBlocksScanned, scanStartTime);
|
|
562
|
-
// write connection status sync (includes eta)
|
|
563
|
-
if (eta) {
|
|
564
|
-
await readWriteConnectionStatusFile((cs) => {
|
|
565
|
-
cs.sync = {
|
|
566
|
-
...cs.sync,
|
|
567
|
-
scanned_ranges: wallet.cache.scanned_ranges,
|
|
568
|
-
daemon_height: wallet.cache.daemon_height,
|
|
569
|
-
current_scan_height: lastRange(wallet.cache.scanned_ranges)?.end || 0,
|
|
570
|
-
eta,
|
|
571
|
-
timestamp: new Date().toISOString(),
|
|
572
|
-
};
|
|
573
|
-
}, scanSettingsPath);
|
|
574
|
-
}
|
|
575
430
|
const res = await processWorkItem(to_be_processed, workBuffer, blocksBuffer, getPathPrefix(scanSettingsPath, pathPrefix), wallet.secret_spend_key);
|
|
431
|
+
// always persist wallet progress after process; eta only if we have a new one
|
|
432
|
+
// so missing eta does not wipe the previous value (no flicker)
|
|
433
|
+
const eta = computeETA(wallet.cache, totalBlocksScanned, scanStartTime);
|
|
434
|
+
await readWriteConnectionStatusFile((cs) => {
|
|
435
|
+
applyWalletScanProgress(cs, {
|
|
436
|
+
current_scan_height: lastRange(wallet.cache.scanned_ranges)?.end || 0,
|
|
437
|
+
scanned_ranges: wallet.cache.scanned_ranges,
|
|
438
|
+
daemon_height: wallet.cache.daemon_height,
|
|
439
|
+
eta,
|
|
440
|
+
});
|
|
441
|
+
}, scanSettingsPath);
|
|
576
442
|
// log("coordinatorMainMultithreaded", "processWorkItem result");
|
|
577
443
|
yield {
|
|
578
444
|
type: "scan_ready",
|
|
579
445
|
address: wallet.primary_address,
|
|
580
|
-
result: {
|
|
581
|
-
type: "Ready",
|
|
582
|
-
work_uuid: to_be_processed.work_uuid,
|
|
583
|
-
result: to_be_processed.result,
|
|
584
|
-
},
|
|
585
446
|
newCache: wallet.cache,
|
|
586
447
|
changed_outputs: res.changed_outputs,
|
|
587
448
|
};
|
|
@@ -606,6 +467,17 @@ export async function scheduleWorkOnCpuPorts(ports, work_buffer) {
|
|
|
606
467
|
const msg = event.data;
|
|
607
468
|
// handle the result here:
|
|
608
469
|
log("scheduleWorkOnCpuPorts", ["onmessage result", msg]);
|
|
470
|
+
// cancel ack may omit work_uuid when idle, but inflight cancel carries it
|
|
471
|
+
if (msg.type === "Canceled") {
|
|
472
|
+
if (item.status === "scanwork_in_progress") {
|
|
473
|
+
item.status = "fresh";
|
|
474
|
+
item.result = undefined;
|
|
475
|
+
}
|
|
476
|
+
port_status.promise = null;
|
|
477
|
+
// thin resolve: dont put full msg/result on the port promise on cancel
|
|
478
|
+
resolve_port({ type: "Canceled", work_uuid: item.work_uuid });
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
609
481
|
if (msg.work_uuid !== item.work_uuid) {
|
|
610
482
|
log("scheduleWorkOnCpuPorts", [
|
|
611
483
|
"wrong work_uuid in msg msg.work_uuid=",
|
|
@@ -623,10 +495,18 @@ export async function scheduleWorkOnCpuPorts(ports, work_buffer) {
|
|
|
623
495
|
log("scheduleWorkOnCpuPorts", "wrong status in msg");
|
|
624
496
|
throw new Error("[scheduleWorkOnCpuPorts] wrong status in msg");
|
|
625
497
|
}
|
|
498
|
+
// fat ScanResult lives only on the work item until process clears it
|
|
626
499
|
item.result = msg.result;
|
|
627
500
|
item.status = "scanwork_done";
|
|
501
|
+
// cpu already has its own buffer copy; process only needs meta.
|
|
502
|
+
// drop work-item bytes now; blocksBuffer still owns the batch until left-edge free.
|
|
503
|
+
item.batch = {
|
|
504
|
+
...item.batch,
|
|
505
|
+
data: new Uint8Array(0),
|
|
506
|
+
};
|
|
628
507
|
port_status.promise = null;
|
|
629
|
-
|
|
508
|
+
// thin resolve: race/await must not retain all_key_images via promise value
|
|
509
|
+
resolve_port({ type: msg.type, work_uuid: msg.work_uuid });
|
|
630
510
|
};
|
|
631
511
|
port_status.port.onmessage = onmessage;
|
|
632
512
|
item.status = "scanwork_in_progress";
|
|
@@ -12,9 +12,8 @@ export declare function makeWorkItem(walletConfig: WalletConfigPlusCache, batch:
|
|
|
12
12
|
export type ScanLoopIteratorResult = IteratorYieldResult<ScanLoopYield>;
|
|
13
13
|
export type ScanLoopInput = WorkItem | "cancel" | undefined;
|
|
14
14
|
export type ScanLoopYield = {
|
|
15
|
-
type: "Ready" | "InProgress";
|
|
15
|
+
type: "Ready" | "InProgress" | "Canceled";
|
|
16
16
|
work_uuid?: string;
|
|
17
17
|
result?: ScanResult;
|
|
18
18
|
};
|
|
19
19
|
export declare function scanLoop(wallet: WalletConfig): AsyncGenerator<ScanLoopYield, void, ScanLoopInput>;
|
|
20
|
-
export declare function markWorkItemAsDone(loop_event: ScanLoopYield, work_buffer: WorkItem[]): Promise<WorkItem | undefined>;
|
|
@@ -101,16 +101,3 @@ export async function* scanLoop(wallet) {
|
|
|
101
101
|
// will be done by processScanResult in the consumer
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
-
//theoretically markWorkItemAsDone could be done in the generator as WorkItem is passed in by reference
|
|
105
|
-
// but: eventually this has to be done accross CPU worker boundaries
|
|
106
|
-
// so we act on the yielded scan result
|
|
107
|
-
export async function markWorkItemAsDone(loop_event, work_buffer) {
|
|
108
|
-
if (loop_event.type === "Ready" && loop_event.work_uuid) {
|
|
109
|
-
const work_item = work_buffer.find((w) => w.work_uuid === loop_event.work_uuid);
|
|
110
|
-
if (work_item) {
|
|
111
|
-
work_item.result = loop_event.result;
|
|
112
|
-
work_item.status = "scanwork_done";
|
|
113
|
-
return work_item;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|