@spirobel/monero-wallet-api 0.5.4 → 0.6.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.
- package/dist/actionlog/actionLogOpened.d.ts +96 -1
- package/dist/actionlog/actionLogOpened.js +386 -1
- package/dist/actionlog/actionLogTypes.d.ts +114 -0
- package/dist/actionlog/actionLogTypes.js +198 -0
- package/dist/actionlog/backend/idbBackend.d.ts +13 -0
- package/dist/actionlog/backend/idbBackend.js +109 -0
- package/dist/actionlog/backend/jsonlBackend.d.ts +19 -0
- package/dist/actionlog/backend/jsonlBackend.js +110 -0
- package/dist/actionlog/backend/memoryBackend.d.ts +11 -0
- package/dist/actionlog/backend/memoryBackend.js +31 -0
- package/dist/actionlog/backend/sqliteBackend.d.ts +14 -0
- package/dist/actionlog/backend/sqliteBackend.js +96 -0
- package/dist/actionlog/backend.d.ts +12 -0
- package/dist/actionlog/backend.js +1 -0
- package/dist/actionlog/idbBackend.d.ts +19 -0
- package/dist/actionlog/idbBackend.js +98 -0
- package/dist/actionlog/jsonlBackend.d.ts +19 -0
- package/dist/actionlog/jsonlBackend.js +75 -0
- package/dist/actionlog/memoryBackend.d.ts +14 -0
- package/dist/actionlog/memoryBackend.js +38 -0
- package/dist/actionlog/openMaps.d.ts +11 -0
- package/dist/actionlog/openMaps.js +32 -0
- package/dist/actionlog/sqliteBackend.d.ts +18 -0
- package/dist/actionlog/sqliteBackend.js +105 -0
- package/dist/actionlog/toolContext.d.ts +36 -0
- package/dist/actionlog/toolContext.js +1 -0
- package/dist/actionlog/types.d.ts +62 -0
- package/dist/actionlog/types.js +62 -0
- package/dist/api.d.ts +4 -0
- package/dist/api.js +3 -0
- package/dist/io/sleep.d.ts +1 -1
- package/dist/io/sleep.js +16 -1
- package/dist/scanning-syncing/backgroundWorker.d.ts +2 -0
- package/dist/scanning-syncing/backgroundWorker.js +92 -10
- package/dist/scanning-syncing/blocksbuffer/blocksBufferFetchLoop.js +33 -2
- package/dist/scanning-syncing/blocksbuffer/blocksbufferCoordination.js +2 -2
- package/dist/scanning-syncing/connectionStatus.d.ts +8 -0
- package/dist/scanning-syncing/connectionStatus.js +50 -0
- package/dist/scanning-syncing/connectionStatusOpened.d.ts +3 -1
- package/dist/scanning-syncing/connectionStatusOpened.js +22 -14
- package/dist/scanning-syncing/openWallet.d.ts +4 -1
- package/dist/scanning-syncing/openWallet.js +3 -0
- package/dist/scanning-syncing/scanresult/scanCache.d.ts +5 -1
- package/dist/scanning-syncing/scanresult/scanCache.js +11 -2
- package/dist/scanning-syncing/scanresult/scanCacheOpened.d.ts +56 -5
- package/dist/scanning-syncing/scanresult/scanCacheOpened.js +422 -83
- package/dist/scanning-syncing/scanresult/scanCoordination.d.ts +7 -3
- package/dist/scanning-syncing/scanresult/scanCoordination.js +126 -28
- package/dist/scanning-syncing/scanresult/scanLoop.d.ts +1 -2
- package/dist/scanning-syncing/scanresult/scanLoop.js +0 -13
- package/dist/scanning-syncing/scanresult/scanResult.js +17 -12
- package/dist/scanning-syncing/scanresult/scanStats.js +8 -5
- 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 -1
- package/dist/scanning-syncing/worker-mains/cpubound-main.js +3 -2
- package/dist/scanning-syncing/worker-mains/worker.js +45 -2
- package/dist/tools/calls/001.d.ts +20 -0
- package/dist/tools/calls/001.js +236 -0
- package/dist/tools/calls/002-content.d.ts +46 -0
- package/dist/tools/calls/002-content.js +135 -0
- package/dist/tools/calls/002-fail-cleanup.d.ts +27 -0
- package/dist/tools/calls/002-fail-cleanup.js +60 -0
- package/dist/tools/calls/002.d.ts +64 -0
- package/dist/tools/calls/002.js +353 -0
- package/dist/tools/content.d.ts +22 -0
- package/dist/tools/content.js +41 -0
- package/dist/tools/counterparty.d.ts +3 -0
- package/dist/tools/counterparty.js +1 -0
- package/dist/tools/download-public-suffix.d.ts +1 -0
- package/dist/tools/download-public-suffix.js +20 -0
- package/dist/tools/globals.d.ts +196 -0
- package/dist/tools/globals.js +14 -0
- package/dist/tools/monero-tools-content.d.ts +29 -0
- package/dist/tools/monero-tools-content.js +80 -0
- package/dist/tools/monero-tools.d.ts +27 -52
- package/dist/tools/monero-tools.js +127 -222
- package/dist/tools/public-suffix-list.d.ts +1 -0
- package/dist/tools/public-suffix-list.js +10336 -0
- package/dist/tools/runtimeMessages.d.ts +4 -0
- package/dist/tools/runtimeMessages.js +18 -0
- package/dist/tools/toolActionLogContext.d.ts +33 -0
- package/dist/tools/toolActionLogContext.js +1 -0
- package/dist/tools/toolArm.d.ts +47 -0
- package/dist/tools/toolArm.js +1 -0
- package/dist/tools/ui.d.ts +2 -0
- package/dist/tools/ui.js +1 -0
- package/dist/tools/worker.d.ts +1 -0
- package/dist/tools/worker.js +1 -0
- package/dist/wasm-processing/wasmFile.js +2 -2
- package/package.json +8 -1
|
@@ -29,6 +29,7 @@ export type WorkToBeDone = {
|
|
|
29
29
|
* @param scan_settings_path
|
|
30
30
|
*/
|
|
31
31
|
export declare function findWorkToBeDone(scan_settings_path?: string, pathPrefix?: string): Promise<WorkToBeDone | false>;
|
|
32
|
+
export declare function fastForwardFetch(wallet_configs: WalletConfigPlusCache[], start_height: number, batchEnd: number): CacheRange | null;
|
|
32
33
|
export declare function workToBeDoneForBatch(cache: ScanCache, batch_meta_infos: BlockInfo[]): "skip" | {
|
|
33
34
|
from: number;
|
|
34
35
|
};
|
|
@@ -64,7 +65,6 @@ export type CoordinatorEvent = {
|
|
|
64
65
|
} | {
|
|
65
66
|
type: "scan_ready";
|
|
66
67
|
address: string;
|
|
67
|
-
result: ScanLoopYield;
|
|
68
68
|
newCache: ScanCache;
|
|
69
69
|
changed_outputs: {
|
|
70
70
|
output: any;
|
|
@@ -72,11 +72,13 @@ export type CoordinatorEvent = {
|
|
|
72
72
|
}[];
|
|
73
73
|
} | {
|
|
74
74
|
type: "all_idle";
|
|
75
|
+
} | {
|
|
76
|
+
type: "shutdown_done";
|
|
75
77
|
} | {
|
|
76
78
|
type: "error";
|
|
77
79
|
error: Error;
|
|
78
80
|
};
|
|
79
|
-
export declare function setupCoordinator(scanSettingsPath?: string, pathPrefix?: string): Promise<false | {
|
|
81
|
+
export declare function setupCoordinator(scanSettingsPath?: string, pathPrefix?: string, stopSync?: AbortSignal): Promise<false | {
|
|
80
82
|
blocksGenerator: AsyncGenerator<BlocksBufferLoopResult, any, any>;
|
|
81
83
|
workBuffer: WorkItem[];
|
|
82
84
|
blocksBuffer: GetBlocksBinBufferItem[];
|
|
@@ -96,10 +98,12 @@ export type BlocksBufferRacer = {
|
|
|
96
98
|
};
|
|
97
99
|
export type ScanLoopRacer = ScanLoopYield;
|
|
98
100
|
export type Racers = BlocksBufferRacer | ScanLoopRacer;
|
|
101
|
+
export declare function resetInProgressWorkItems(workBuffer: WorkItem[]): void;
|
|
102
|
+
export declare function cancelBusyCpuPorts(ports: PortStatus[], timeoutMs?: number): Promise<void>;
|
|
99
103
|
/**
|
|
100
104
|
* coordinator main (multithreaded): dispatches scan work to CPU workers
|
|
101
105
|
* via MessagePorts, processes results in order per wallet.
|
|
102
106
|
* falls back to single-threaded coordinator if no cpuPorts provided.
|
|
103
107
|
*/
|
|
104
|
-
export declare function coordinatorMainMultithreaded(scanSettingsPath?: string, pathPrefix?: string, cpuPorts?: MessagePort[]): AsyncGenerator<CoordinatorEvent>;
|
|
108
|
+
export declare function coordinatorMainMultithreaded(scanSettingsPath?: string, pathPrefix?: string, cpuPorts?: MessagePort[], stopSync?: AbortSignal): AsyncGenerator<CoordinatorEvent>;
|
|
105
109
|
export declare function scheduleWorkOnCpuPorts(ports: PortStatus[], work_buffer: WorkItem[]): Promise<void>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { setupBlocksBufferGenerator, ViewPair, handleConnectionStatusChanges, processScanResult, writeCacheToFile, sleep, findTipIndex, readWriteConnectionStatusFile, } from "../../api";
|
|
2
|
-
import {
|
|
1
|
+
import { setupBlocksBufferGenerator, ViewPair, handleConnectionStatusChanges, processScanResult, writeCacheToFile, sleep, findTipIndex, readWriteConnectionStatusFile, applyWalletScanProgress, } from "../../api";
|
|
2
|
+
import {} from "./scanLoop";
|
|
3
3
|
import { makeWorkItem } from "./scanLoop";
|
|
4
4
|
import { cullTooLargeScanHeight, getNonHaltedWallets, getPathPrefix, openScanSettingsFile, SCAN_SETTINGS_STORE_NAME_DEFAULT, walletSettingsPlusKeys, } from "../../api";
|
|
5
|
-
import { findRange, initScanCacheFile, lastRange, makeCacheRangeForHeight, mergeRanges, } from "./scanCache";
|
|
5
|
+
import { currentScanHeightFromRanges, findRange, initScanCacheFile, lastRange, makeCacheRangeForHeight, mergeRanges, } from "./scanCache";
|
|
6
6
|
import { sendToCpuWorker } from "../worker-mains/cpubound-main";
|
|
7
7
|
import { log } from "../../io/logging";
|
|
8
8
|
/**
|
|
@@ -79,6 +79,19 @@ export async function findWorkToBeDone(scan_settings_path = SCAN_SETTINGS_STORE_
|
|
|
79
79
|
scan_settings,
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
|
+
// slowest wallet past this batch: return its range so fetch can restart at that tip.
|
|
83
|
+
export function fastForwardFetch(wallet_configs, start_height, batchEnd) {
|
|
84
|
+
if (!wallet_configs.length)
|
|
85
|
+
return null;
|
|
86
|
+
const slowest = Math.min(...wallet_configs.map((w) => currentScanHeightFromRanges(w.cache.scanned_ranges, start_height)));
|
|
87
|
+
if (slowest <= batchEnd)
|
|
88
|
+
return null;
|
|
89
|
+
const owner = wallet_configs.find((w) => currentScanHeightFromRanges(w.cache.scanned_ranges, start_height) ===
|
|
90
|
+
slowest);
|
|
91
|
+
return owner
|
|
92
|
+
? findRange(owner.cache.scanned_ranges, start_height)
|
|
93
|
+
: null;
|
|
94
|
+
}
|
|
82
95
|
export function workToBeDoneForBatch(cache, batch_meta_infos) {
|
|
83
96
|
const begin_height = batch_meta_infos[0].block_height;
|
|
84
97
|
const end_height = batch_meta_infos[batch_meta_infos.length - 1].block_height;
|
|
@@ -220,6 +233,9 @@ export async function processWorkItem(item, workBuffer, blocksBuffer, pathPrefix
|
|
|
220
233
|
throw error;
|
|
221
234
|
}
|
|
222
235
|
await writeCacheToFile(cache, pathPrefix);
|
|
236
|
+
// raw cpu ScanResult is fully merged into cache now. drop it so workBuffer
|
|
237
|
+
// does not pin all_key_images / hash strings until left-edge free.
|
|
238
|
+
item.result = undefined;
|
|
223
239
|
item.status = "process_result_done";
|
|
224
240
|
log("processWorkItem", `process_result_done work_uuid=${item?.work_uuid}`);
|
|
225
241
|
// remove from blocksbuffer if no more work items reference it
|
|
@@ -271,15 +287,22 @@ function computeETA(cache, totalBlocksScanned, scanStartTime) {
|
|
|
271
287
|
const etaMs = remaining / blocksPerMs;
|
|
272
288
|
return msToHHMM(etaMs);
|
|
273
289
|
}
|
|
274
|
-
export async function setupCoordinator(scanSettingsPath, pathPrefix) {
|
|
290
|
+
export async function setupCoordinator(scanSettingsPath, pathPrefix, stopSync) {
|
|
275
291
|
const work_to_be_done = await findWorkToBeDone(scanSettingsPath, pathPrefix);
|
|
276
292
|
if (!work_to_be_done)
|
|
277
293
|
return false;
|
|
294
|
+
// bring connection status file scan height in line with / cache file / in memory state
|
|
295
|
+
await readWriteConnectionStatusFile((cs) => {
|
|
296
|
+
applyWalletScanProgress(cs, {
|
|
297
|
+
current_scan_height: currentScanHeightFromRanges(work_to_be_done.wallet_configs[0]?.cache.scanned_ranges ?? [], work_to_be_done.scan_settings.start_height || 0),
|
|
298
|
+
});
|
|
299
|
+
}, scanSettingsPath);
|
|
278
300
|
const { generator: blocksGenerator, blocksBuffer } = await setupBlocksBufferGenerator({
|
|
279
301
|
nodeUrl: work_to_be_done.scan_settings.node_url,
|
|
280
302
|
startHeight: work_to_be_done.start_height,
|
|
281
303
|
anchor_range: work_to_be_done.anchor_range,
|
|
282
304
|
scanSettingsPath,
|
|
305
|
+
stopSync,
|
|
283
306
|
});
|
|
284
307
|
const workBuffer = [];
|
|
285
308
|
//TODO: refactor to per wallet buffer
|
|
@@ -298,22 +321,53 @@ export async function setupCoordinator(scanSettingsPath, pathPrefix) {
|
|
|
298
321
|
work_to_be_done,
|
|
299
322
|
};
|
|
300
323
|
}
|
|
324
|
+
// flip inflight work back to fresh so a restart can reschedule it
|
|
325
|
+
export function resetInProgressWorkItems(workBuffer) {
|
|
326
|
+
for (const item of workBuffer) {
|
|
327
|
+
if (item.status === "scanwork_in_progress") {
|
|
328
|
+
item.status = "fresh";
|
|
329
|
+
item.result = undefined;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
// tell busy cpu ports to cancel and wait for their promises (with timeout)
|
|
334
|
+
export async function cancelBusyCpuPorts(ports, timeoutMs = 3000) {
|
|
335
|
+
const busy = ports.filter((ps) => ps.promise);
|
|
336
|
+
for (const ps of busy) {
|
|
337
|
+
try {
|
|
338
|
+
sendToCpuWorker(ps.port, "cancel");
|
|
339
|
+
}
|
|
340
|
+
catch (err) {
|
|
341
|
+
log("cancelBusyCpuPorts", ["send cancel failed", String(err)]);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (busy.length === 0)
|
|
345
|
+
return;
|
|
346
|
+
await Promise.race([
|
|
347
|
+
Promise.allSettled(busy.map((ps) => ps.promise)),
|
|
348
|
+
sleep(timeoutMs),
|
|
349
|
+
]);
|
|
350
|
+
for (const ps of ports) {
|
|
351
|
+
ps.promise = null;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
301
354
|
/**
|
|
302
355
|
* coordinator main (multithreaded): dispatches scan work to CPU workers
|
|
303
356
|
* via MessagePorts, processes results in order per wallet.
|
|
304
357
|
* falls back to single-threaded coordinator if no cpuPorts provided.
|
|
305
358
|
*/
|
|
306
|
-
export async function* coordinatorMainMultithreaded(scanSettingsPath, pathPrefix, cpuPorts) {
|
|
359
|
+
export async function* coordinatorMainMultithreaded(scanSettingsPath, pathPrefix, cpuPorts, stopSync) {
|
|
307
360
|
if (!cpuPorts || cpuPorts.length === 0) {
|
|
308
361
|
throw new Error("[coordinatorMain Multithreaded] cpuPorts empty, there must be at least cpu worker");
|
|
309
362
|
}
|
|
310
|
-
const ctx = await setupCoordinator(scanSettingsPath, pathPrefix);
|
|
363
|
+
const ctx = await setupCoordinator(scanSettingsPath, pathPrefix, stopSync);
|
|
311
364
|
if (!ctx)
|
|
312
365
|
throw new Error("[coordinatorMain Multithreaded] findWorkToBeDone returned false");
|
|
313
366
|
const work_to_be_done = ctx.work_to_be_done;
|
|
314
|
-
|
|
367
|
+
let blocksBuffer = ctx.blocksBuffer;
|
|
315
368
|
const workBuffer = ctx.workBuffer;
|
|
316
|
-
|
|
369
|
+
let blocksGenerator = ctx.blocksGenerator;
|
|
370
|
+
let fetchAt = work_to_be_done.start_height;
|
|
317
371
|
let totalBlocksScanned = 0;
|
|
318
372
|
let scanStartTime = Date.now();
|
|
319
373
|
let blocksPromise = blocksGenerator.next();
|
|
@@ -327,6 +381,13 @@ export async function* coordinatorMainMultithreaded(scanSettingsPath, pathPrefix
|
|
|
327
381
|
}
|
|
328
382
|
let race_count = 0;
|
|
329
383
|
while (true) {
|
|
384
|
+
if (stopSync?.aborted) {
|
|
385
|
+
log("coordinatorMainMultithreaded", ["shutdown signal, draining"]);
|
|
386
|
+
await cancelBusyCpuPorts(freePorts);
|
|
387
|
+
resetInProgressWorkItems(workBuffer);
|
|
388
|
+
yield { type: "shutdown_done" };
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
330
391
|
race_count++;
|
|
331
392
|
log("coordinatorMainMultithreaded", ["race_count", race_count]);
|
|
332
393
|
await scheduleWorkOnCpuPorts(freePorts, workBuffer);
|
|
@@ -344,6 +405,16 @@ export async function* coordinatorMainMultithreaded(scanSettingsPath, pathPrefix
|
|
|
344
405
|
const winner = await Promise.race(races);
|
|
345
406
|
log("coordinatorMainMultithreaded", "cpu ports status");
|
|
346
407
|
if ("src" in winner && winner.src === "blocks") {
|
|
408
|
+
// fetch loop returned (done) on abort, treat as shutdown
|
|
409
|
+
if (winner.value.done) {
|
|
410
|
+
log("coordinatorMainMultithreaded", [
|
|
411
|
+
"blocks generator done, draining",
|
|
412
|
+
]);
|
|
413
|
+
await cancelBusyCpuPorts(freePorts);
|
|
414
|
+
resetInProgressWorkItems(workBuffer);
|
|
415
|
+
yield { type: "shutdown_done" };
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
347
418
|
const result = winner.value.value;
|
|
348
419
|
const { isBlocksBufferChanged } = await handleBlocksYield(result, scanSettingsPath);
|
|
349
420
|
if (isBlocksBufferChanged) {
|
|
@@ -376,30 +447,38 @@ export async function* coordinatorMainMultithreaded(scanSettingsPath, pathPrefix
|
|
|
376
447
|
continue;
|
|
377
448
|
const blockCount = to_be_processed.to - to_be_processed.from + 1;
|
|
378
449
|
totalBlocksScanned += blockCount;
|
|
379
|
-
const eta = computeETA(wallet.cache, totalBlocksScanned, scanStartTime);
|
|
380
|
-
// write connection status sync (includes eta)
|
|
381
|
-
if (eta) {
|
|
382
|
-
await readWriteConnectionStatusFile((cs) => {
|
|
383
|
-
cs.sync = {
|
|
384
|
-
...cs.sync,
|
|
385
|
-
scanned_ranges: wallet.cache.scanned_ranges,
|
|
386
|
-
daemon_height: wallet.cache.daemon_height,
|
|
387
|
-
current_scan_height: lastRange(wallet.cache.scanned_ranges)?.end || 0,
|
|
388
|
-
eta,
|
|
389
|
-
timestamp: new Date().toISOString(),
|
|
390
|
-
};
|
|
391
|
-
}, scanSettingsPath);
|
|
392
|
-
}
|
|
393
450
|
const res = await processWorkItem(to_be_processed, workBuffer, blocksBuffer, getPathPrefix(scanSettingsPath, pathPrefix), wallet.secret_spend_key);
|
|
451
|
+
const jumped = fastForwardFetch(work_to_be_done.wallet_configs, work_to_be_done.scan_settings.start_height || 0, to_be_processed.batch.get_blocks_result_meta.block_infos[to_be_processed.to].block_height);
|
|
452
|
+
if (jumped && jumped.end > fetchAt) {
|
|
453
|
+
log("coordinatorMainMultithreaded", ["jump", fetchAt, "->", jumped.end]);
|
|
454
|
+
await blocksGenerator.return(undefined);
|
|
455
|
+
const next = await setupBlocksBufferGenerator({
|
|
456
|
+
nodeUrl: work_to_be_done.scan_settings.node_url,
|
|
457
|
+
startHeight: jumped.end,
|
|
458
|
+
anchor_range: jumped,
|
|
459
|
+
scanSettingsPath,
|
|
460
|
+
stopSync,
|
|
461
|
+
});
|
|
462
|
+
blocksBuffer = next.blocksBuffer;
|
|
463
|
+
blocksGenerator = next.generator;
|
|
464
|
+
blocksPromise = blocksGenerator.next();
|
|
465
|
+
fetchAt = jumped.end;
|
|
466
|
+
}
|
|
467
|
+
// always persist wallet progress after process; eta only if we have a new one
|
|
468
|
+
// so missing eta does not wipe the previous value (no flicker)
|
|
469
|
+
const eta = computeETA(wallet.cache, totalBlocksScanned, scanStartTime);
|
|
470
|
+
await readWriteConnectionStatusFile((cs) => {
|
|
471
|
+
applyWalletScanProgress(cs, {
|
|
472
|
+
current_scan_height: currentScanHeightFromRanges(wallet.cache.scanned_ranges, work_to_be_done.scan_settings.start_height || 0),
|
|
473
|
+
scanned_ranges: wallet.cache.scanned_ranges,
|
|
474
|
+
daemon_height: wallet.cache.daemon_height,
|
|
475
|
+
eta,
|
|
476
|
+
});
|
|
477
|
+
}, scanSettingsPath);
|
|
394
478
|
// log("coordinatorMainMultithreaded", "processWorkItem result");
|
|
395
479
|
yield {
|
|
396
480
|
type: "scan_ready",
|
|
397
481
|
address: wallet.primary_address,
|
|
398
|
-
result: {
|
|
399
|
-
type: "Ready",
|
|
400
|
-
work_uuid: to_be_processed.work_uuid,
|
|
401
|
-
result: to_be_processed.result,
|
|
402
|
-
},
|
|
403
482
|
newCache: wallet.cache,
|
|
404
483
|
changed_outputs: res.changed_outputs,
|
|
405
484
|
};
|
|
@@ -424,6 +503,17 @@ export async function scheduleWorkOnCpuPorts(ports, work_buffer) {
|
|
|
424
503
|
const msg = event.data;
|
|
425
504
|
// handle the result here:
|
|
426
505
|
log("scheduleWorkOnCpuPorts", ["onmessage result", msg]);
|
|
506
|
+
// cancel ack may omit work_uuid when idle, but inflight cancel carries it
|
|
507
|
+
if (msg.type === "Canceled") {
|
|
508
|
+
if (item.status === "scanwork_in_progress") {
|
|
509
|
+
item.status = "fresh";
|
|
510
|
+
item.result = undefined;
|
|
511
|
+
}
|
|
512
|
+
port_status.promise = null;
|
|
513
|
+
// thin resolve: dont put full msg/result on the port promise on cancel
|
|
514
|
+
resolve_port({ type: "Canceled", work_uuid: item.work_uuid });
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
427
517
|
if (msg.work_uuid !== item.work_uuid) {
|
|
428
518
|
log("scheduleWorkOnCpuPorts", [
|
|
429
519
|
"wrong work_uuid in msg msg.work_uuid=",
|
|
@@ -441,10 +531,18 @@ export async function scheduleWorkOnCpuPorts(ports, work_buffer) {
|
|
|
441
531
|
log("scheduleWorkOnCpuPorts", "wrong status in msg");
|
|
442
532
|
throw new Error("[scheduleWorkOnCpuPorts] wrong status in msg");
|
|
443
533
|
}
|
|
534
|
+
// fat ScanResult lives only on the work item until process clears it
|
|
444
535
|
item.result = msg.result;
|
|
445
536
|
item.status = "scanwork_done";
|
|
537
|
+
// cpu already has its own buffer copy; process only needs meta.
|
|
538
|
+
// drop work-item bytes now; blocksBuffer still owns the batch until left-edge free.
|
|
539
|
+
item.batch = {
|
|
540
|
+
...item.batch,
|
|
541
|
+
data: new Uint8Array(0),
|
|
542
|
+
};
|
|
446
543
|
port_status.promise = null;
|
|
447
|
-
|
|
544
|
+
// thin resolve: race/await must not retain all_key_images via promise value
|
|
545
|
+
resolve_port({ type: msg.type, work_uuid: msg.work_uuid });
|
|
448
546
|
};
|
|
449
547
|
port_status.port.onmessage = onmessage;
|
|
450
548
|
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
|
-
}
|
|
@@ -39,7 +39,7 @@ export async function processScanResult(params) {
|
|
|
39
39
|
reverted_spends: [],
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
|
-
else {
|
|
42
|
+
else if (!cache.reorg_info.split_heights.some((s) => s.block_hash === split_height.block_hash)) {
|
|
43
43
|
cache.reorg_info.split_heights.push(split_height);
|
|
44
44
|
}
|
|
45
45
|
// First, collect reverted spends from all outputs (before any removals)
|
|
@@ -56,11 +56,14 @@ export async function processScanResult(params) {
|
|
|
56
56
|
for (const [id, old_output_state] of removed_outputs) {
|
|
57
57
|
// 1. find key_image of output to be removed (as it was reorged)
|
|
58
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
|
|
59
|
-
cache.reorg_info.removed_outputs.
|
|
60
|
-
old_output_state
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
if (!cache.reorg_info.removed_outputs.some((r) => r.old_output_state.index_on_blockchain ===
|
|
60
|
+
old_output_state.index_on_blockchain)) {
|
|
61
|
+
cache.reorg_info.removed_outputs.push({
|
|
62
|
+
old_output_state,
|
|
63
|
+
key_image,
|
|
64
|
+
split_height,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
64
67
|
// 2. remove from outputs and own_key_images
|
|
65
68
|
delete cache.outputs[id];
|
|
66
69
|
delete cache.own_key_images[key_image];
|
|
@@ -72,11 +75,14 @@ export async function processScanResult(params) {
|
|
|
72
75
|
for (const [id, old_output_state_pointer] of reverted_outputs) {
|
|
73
76
|
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
|
|
74
77
|
const old_output_state = Object.assign({}, old_output_state_pointer);
|
|
75
|
-
cache.reorg_info.reverted_spends.
|
|
76
|
-
old_output_state
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
if (!cache.reorg_info.reverted_spends.some((r) => r.old_output_state.index_on_blockchain ===
|
|
79
|
+
old_output_state.index_on_blockchain)) {
|
|
80
|
+
cache.reorg_info.reverted_spends.push({
|
|
81
|
+
old_output_state,
|
|
82
|
+
key_image, // in this case key_image only used here, does not get removed
|
|
83
|
+
split_height,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
80
86
|
// remove spend info from original cache (if output still exists
|
|
81
87
|
// it may have been deleted above in the removed_outputs loop)
|
|
82
88
|
if (cache.outputs[id]) {
|
|
@@ -226,7 +232,6 @@ export function outputStatus(output, cache, current_height) {
|
|
|
226
232
|
if (unlock_height > current_height) {
|
|
227
233
|
return { status: "pending", unlock_height };
|
|
228
234
|
}
|
|
229
|
-
// TODO: check that that output is not txlog input indeces
|
|
230
235
|
return { status: "spendable" };
|
|
231
236
|
}
|
|
232
237
|
export function spendable(output, cache, current_height) {
|
|
@@ -120,16 +120,19 @@ export function processFoundTransactions(cache, stats, current_height) {
|
|
|
120
120
|
}
|
|
121
121
|
// handle spent case
|
|
122
122
|
if (output.spent_in_tx_hash) {
|
|
123
|
-
const
|
|
124
|
-
? cache.pending_spent_utxos[output.index_on_blockchain]
|
|
125
|
-
:
|
|
126
|
-
|
|
127
|
-
|
|
123
|
+
const spentIndex = cache.pending_spent_utxos
|
|
124
|
+
? cache.pending_spent_utxos[String(output.index_on_blockchain)]
|
|
125
|
+
: undefined;
|
|
126
|
+
// index 0 is a real log. do not treat it as missing.
|
|
127
|
+
const txlog = cache.tx_logs && spentIndex !== undefined
|
|
128
|
+
? cache.tx_logs[spentIndex]
|
|
128
129
|
: undefined;
|
|
129
130
|
const spentTx = stats.found_transactions[output.spent_in_tx_hash];
|
|
130
131
|
if (spentTx) {
|
|
131
132
|
spentTx.amount -= output.amount;
|
|
132
133
|
spentTx.inputs.push(output);
|
|
134
|
+
if (!spentTx.txlog && txlog)
|
|
135
|
+
spentTx.txlog = txlog;
|
|
133
136
|
}
|
|
134
137
|
else {
|
|
135
138
|
stats.found_transactions[output.spent_in_tx_hash] = {
|