@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
package/README.md
CHANGED
package/dist/api.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export * from "./multisig/multisig";
|
|
|
14
14
|
export * from "./multisig/DistributedKeyGenerator";
|
|
15
15
|
export * from "./multisig/escrow";
|
|
16
16
|
export * from "./multisig/MultiSigTxSigner";
|
|
17
|
-
export {
|
|
17
|
+
export { createWebworker, makeWebworkerScript, } from "./scanning-syncing/backgroundWorker";
|
|
18
18
|
export { spendable } from "./scanning-syncing/scanresult/scanResult";
|
|
19
19
|
export { openWallets, openWallet } from "./scanning-syncing/openWallet";
|
|
20
20
|
export * from "./scanning-syncing/blocksbuffer/blocksBufferFetchLoop";
|
package/dist/api.js
CHANGED
|
@@ -13,7 +13,7 @@ export * from "./multisig/multisig";
|
|
|
13
13
|
export * from "./multisig/DistributedKeyGenerator";
|
|
14
14
|
export * from "./multisig/escrow";
|
|
15
15
|
export * from "./multisig/MultiSigTxSigner";
|
|
16
|
-
export {
|
|
16
|
+
export { createWebworker, makeWebworkerScript, } from "./scanning-syncing/backgroundWorker";
|
|
17
17
|
export { spendable } from "./scanning-syncing/scanresult/scanResult";
|
|
18
18
|
export { openWallets, openWallet } from "./scanning-syncing/openWallet";
|
|
19
19
|
export * from "./scanning-syncing/blocksbuffer/blocksBufferFetchLoop";
|
package/dist/io/logging.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* "logs_include": ["blocksBufferFetchLoop", "makeWorkItemsFromBlocksBuffer", "logBufStatus"]
|
|
14
14
|
* -> block fetching, reconciliation, buffer state.
|
|
15
15
|
*
|
|
16
|
-
* "logs_include": ["processScanResult", "processWorkItem"
|
|
16
|
+
* "logs_include": ["processScanResult", "processWorkItem"]
|
|
17
17
|
* -> result processing, cache updates, reorg detection.
|
|
18
18
|
*
|
|
19
19
|
* "logs_include": ["handleConnectionStatusChanges", "handleScanError"]
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* "logs": "file" writes to `<coordinator|cpubound|mainthread>-<id>-<timestamp>.log`.
|
|
24
24
|
* "logs": "console-and-file" writes to file and console.
|
|
25
25
|
*/
|
|
26
|
-
export declare const LOGGING_FUNCTIONS: readonly ["findWorkToBeDone", "workToBeDoneForBatch", "makeWorkItemsFromBlocksBuffer", "reconcileWorkItemDone", "
|
|
26
|
+
export declare const LOGGING_FUNCTIONS: readonly ["findWorkToBeDone", "workToBeDoneForBatch", "makeWorkItemsFromBlocksBuffer", "reconcileWorkItemDone", "processWorkItem", "logBufStatus", "coordinatorMain", "coordinatorMainMultithreaded", "scheduleWorkOnCpuPorts", "processScanResult", "handleScanError", "createWebworker", "startWebworker", "blocksBufferFetchLoop", "reduceStartHeightToTip", "handleConnectionStatusChanges", "handleCpuboundScan", "coordinatorMainWorker", "CPU_PORT_HANDLER", "atomicWrite", "multisigMainWorkerCall"];
|
|
27
27
|
export type PossibleLogs = (typeof LOGGING_FUNCTIONS)[number];
|
|
28
28
|
export type LogSetting = "console" | "file" | "console-and-file" | "off";
|
|
29
29
|
export type FileLogMessage = {
|
|
@@ -31,10 +31,10 @@ export type FileLogMessage = {
|
|
|
31
31
|
message: any;
|
|
32
32
|
};
|
|
33
33
|
export declare function setupLoggingPath(scan_settings_path: string, path_prefix: string, role: "coordinator" | "cpubound" | "mainthread" | "multisig", cpu_worker_id?: number): Promise<{
|
|
34
|
-
logs:
|
|
35
|
-
logs_include: ("findWorkToBeDone" | "workToBeDoneForBatch" | "makeWorkItemsFromBlocksBuffer" | "reconcileWorkItemDone" | "
|
|
36
|
-
logs_exclude: ("findWorkToBeDone" | "workToBeDoneForBatch" | "makeWorkItemsFromBlocksBuffer" | "reconcileWorkItemDone" | "
|
|
34
|
+
logs: "console" | "file" | "console-and-file";
|
|
35
|
+
logs_include: ("findWorkToBeDone" | "workToBeDoneForBatch" | "makeWorkItemsFromBlocksBuffer" | "reconcileWorkItemDone" | "processWorkItem" | "logBufStatus" | "coordinatorMain" | "coordinatorMainMultithreaded" | "scheduleWorkOnCpuPorts" | "processScanResult" | "handleScanError" | "createWebworker" | "startWebworker" | "blocksBufferFetchLoop" | "reduceStartHeightToTip" | "handleConnectionStatusChanges" | "handleCpuboundScan" | "coordinatorMainWorker" | "CPU_PORT_HANDLER" | "atomicWrite" | "multisigMainWorkerCall")[] | undefined;
|
|
36
|
+
logs_exclude: ("findWorkToBeDone" | "workToBeDoneForBatch" | "makeWorkItemsFromBlocksBuffer" | "reconcileWorkItemDone" | "processWorkItem" | "logBufStatus" | "coordinatorMain" | "coordinatorMainMultithreaded" | "scheduleWorkOnCpuPorts" | "processScanResult" | "handleScanError" | "createWebworker" | "startWebworker" | "blocksBufferFetchLoop" | "reduceStartHeightToTip" | "handleConnectionStatusChanges" | "handleCpuboundScan" | "coordinatorMainWorker" | "CPU_PORT_HANDLER" | "atomicWrite" | "multisigMainWorkerCall")[] | undefined;
|
|
37
37
|
file_logbuffer: FileLogMessage[];
|
|
38
38
|
global_logging_path: string;
|
|
39
|
-
}>;
|
|
39
|
+
} | undefined>;
|
|
40
40
|
export declare function log(fnname: string, message: any): void;
|
package/dist/io/logging.js
CHANGED
|
@@ -15,7 +15,7 @@ import { atomicWrite } from "./atomicWrite";
|
|
|
15
15
|
* "logs_include": ["blocksBufferFetchLoop", "makeWorkItemsFromBlocksBuffer", "logBufStatus"]
|
|
16
16
|
* -> block fetching, reconciliation, buffer state.
|
|
17
17
|
*
|
|
18
|
-
* "logs_include": ["processScanResult", "processWorkItem"
|
|
18
|
+
* "logs_include": ["processScanResult", "processWorkItem"]
|
|
19
19
|
* -> result processing, cache updates, reorg detection.
|
|
20
20
|
*
|
|
21
21
|
* "logs_include": ["handleConnectionStatusChanges", "handleScanError"]
|
|
@@ -31,7 +31,6 @@ export const LOGGING_FUNCTIONS = [
|
|
|
31
31
|
"workToBeDoneForBatch",
|
|
32
32
|
"makeWorkItemsFromBlocksBuffer",
|
|
33
33
|
"reconcileWorkItemDone",
|
|
34
|
-
"processScanResultForWorkItem",
|
|
35
34
|
"processWorkItem",
|
|
36
35
|
"logBufStatus",
|
|
37
36
|
"coordinatorMain",
|
|
@@ -63,6 +62,8 @@ export const LOGGING_FUNCTIONS = [
|
|
|
63
62
|
export async function setupLoggingPath(scan_settings_path, path_prefix, role, cpu_worker_id) {
|
|
64
63
|
const scanSettings = await openScanSettingsFile(scan_settings_path);
|
|
65
64
|
const logs = scanSettings?.logs;
|
|
65
|
+
if (logs === "off" || typeof logs === "undefined")
|
|
66
|
+
return;
|
|
66
67
|
const logs_include = scanSettings?.logs_include;
|
|
67
68
|
const logs_exclude = scanSettings?.logs_exclude;
|
|
68
69
|
const file_logbuffer = [];
|
|
@@ -147,7 +148,7 @@ export function log(fnname, message) {
|
|
|
147
148
|
if (!setup)
|
|
148
149
|
return;
|
|
149
150
|
const { logs, logs_include, logs_exclude, file_logbuffer } = setup;
|
|
150
|
-
if (logs === "off")
|
|
151
|
+
if (logs === "off" || typeof logs === "undefined")
|
|
151
152
|
return;
|
|
152
153
|
// Apply include/exclude filters
|
|
153
154
|
if (logs_include && logs_include.length > 0 && !logs_include.includes(fnname))
|
package/dist/io/sleep.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const sleep: (ms: number) => Promise<
|
|
1
|
+
export declare const sleep: (ms: number, signal?: AbortSignal) => Promise<void>;
|
package/dist/io/sleep.js
CHANGED
|
@@ -1 +1,16 @@
|
|
|
1
|
-
export const sleep = (ms) => new Promise((resolve) =>
|
|
1
|
+
export const sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
2
|
+
if (signal?.aborted) {
|
|
3
|
+
reject(signal.reason ?? new DOMException("aborted", "AbortError"));
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
const onAbort = () => {
|
|
7
|
+
clearTimeout(timer);
|
|
8
|
+
signal?.removeEventListener("abort", onAbort);
|
|
9
|
+
reject(signal?.reason ?? new DOMException("aborted", "AbortError"));
|
|
10
|
+
};
|
|
11
|
+
const timer = setTimeout(() => {
|
|
12
|
+
signal?.removeEventListener("abort", onAbort);
|
|
13
|
+
resolve();
|
|
14
|
+
}, ms);
|
|
15
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
16
|
+
});
|
package/dist/multisig/escrow.js
CHANGED
|
@@ -3,9 +3,10 @@ export declare const CPU_POOL_SIZE = 4;
|
|
|
3
3
|
export type WorkerSet = {
|
|
4
4
|
fetchWorker: Worker;
|
|
5
5
|
cpuWorkers: Worker[];
|
|
6
|
+
shutdown: (timeoutMs?: number) => Promise<void>;
|
|
6
7
|
terminate: () => void;
|
|
8
|
+
dumpHeaps: (dir?: string) => Promise<string[]>;
|
|
7
9
|
};
|
|
8
10
|
export declare function createWebworker(handle_result?: (result: CacheChangedCallbackParameters) => void, scan_settings_path?: string, pathPrefix?: string, handle_error?: (error: unknown) => void): Promise<WorkerSet | undefined>;
|
|
9
11
|
export declare function makeWebworkerScript(): string;
|
|
10
|
-
export declare function startWebworker(handle_result?: (result: unknown) => void, handle_error?: (error: unknown) => void): Worker;
|
|
11
12
|
export declare function startWebworkerReady(): Promise<Worker>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LOCAL_NODE_DEFAULT_URL } from "../node-interaction/nodeUrl";
|
|
2
2
|
import {} from "./scanresult/scanCache";
|
|
3
|
-
import { openScanSettingsFile, SCAN_SETTINGS_STORE_NAME_DEFAULT } from "../api";
|
|
3
|
+
import { openScanSettingsFile, SCAN_SETTINGS_STORE_NAME_DEFAULT, sleep, } from "../api";
|
|
4
4
|
import { log, setupLoggingPath } from "../io/logging";
|
|
5
5
|
import { workerMainCode } from "./worker-entrypoints/worker";
|
|
6
6
|
export const CPU_POOL_SIZE = 4;
|
|
@@ -8,10 +8,11 @@ export async function createWebworker(handle_result, scan_settings_path, pathPre
|
|
|
8
8
|
try {
|
|
9
9
|
const resolvedPath = scan_settings_path || SCAN_SETTINGS_STORE_NAME_DEFAULT;
|
|
10
10
|
const scanSettings = await openScanSettingsFile(resolvedPath);
|
|
11
|
-
|
|
11
|
+
if (scanSettings?.logs !== "off" && scanSettings?.logs)
|
|
12
|
+
await setupLoggingPath(resolvedPath, pathPrefix ?? "", "mainthread");
|
|
12
13
|
const node_url = scanSettings?.node_url || LOCAL_NODE_DEFAULT_URL;
|
|
13
14
|
const cpu_worker_count = typeof scanSettings?.cpu_worker_count !== "undefined"
|
|
14
|
-
? scanSettings?.cpu_worker_count
|
|
15
|
+
? scanSettings?.cpu_worker_count || 1
|
|
15
16
|
: CPU_POOL_SIZE;
|
|
16
17
|
// spawn CPU workers
|
|
17
18
|
const cpuWorkers = [];
|
|
@@ -32,6 +33,7 @@ export async function createWebworker(handle_result, scan_settings_path, pathPre
|
|
|
32
33
|
};
|
|
33
34
|
cpuWorkers.push(cpuWorker);
|
|
34
35
|
cpuPorts.push(channel.port2);
|
|
36
|
+
await sleep(50);
|
|
35
37
|
}
|
|
36
38
|
const coordinationWorker = await startWebworkerReady();
|
|
37
39
|
coordinationWorker.postMessage({
|
|
@@ -39,13 +41,41 @@ export async function createWebworker(handle_result, scan_settings_path, pathPre
|
|
|
39
41
|
scan_settings_path: resolvedPath,
|
|
40
42
|
pathPrefix,
|
|
41
43
|
role: "coordinator",
|
|
42
|
-
|
|
43
|
-
start_height: 0,
|
|
44
|
-
}, cpuPorts); // transfer CPU port2s to coordination worker
|
|
44
|
+
}, cpuPorts); // transfer CPU ports to coordination worker
|
|
45
45
|
log("createWebworker", [
|
|
46
46
|
"coordinator worker started, node_url=" + node_url,
|
|
47
47
|
", cpuPorts=" + cpuPorts.length,
|
|
48
48
|
]);
|
|
49
|
+
let shutdownResolve;
|
|
50
|
+
let shuttingDown = false;
|
|
51
|
+
const hardTerminate = () => {
|
|
52
|
+
for (const p of cpuPorts) {
|
|
53
|
+
try {
|
|
54
|
+
p.close();
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// port may already be closed
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
coordinationWorker.onmessage = null;
|
|
61
|
+
coordinationWorker.onerror = null;
|
|
62
|
+
for (const w of cpuWorkers) {
|
|
63
|
+
w.onerror = null;
|
|
64
|
+
w.onmessage = null;
|
|
65
|
+
try {
|
|
66
|
+
w.terminate();
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// already dead
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
coordinationWorker.terminate();
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// already dead
|
|
77
|
+
}
|
|
78
|
+
};
|
|
49
79
|
coordinationWorker.onmessage = (event) => {
|
|
50
80
|
if (event.data.type === "ERROR") {
|
|
51
81
|
if (handle_error)
|
|
@@ -55,14 +85,74 @@ export async function createWebworker(handle_result, scan_settings_path, pathPre
|
|
|
55
85
|
if (handle_result)
|
|
56
86
|
handle_result(event.data.payload);
|
|
57
87
|
}
|
|
88
|
+
else if (event.data.type === "SHUTDOWN_DONE") {
|
|
89
|
+
log("createWebworker", ["SHUTDOWN_DONE received"]);
|
|
90
|
+
shutdownResolve?.();
|
|
91
|
+
shutdownResolve = undefined;
|
|
92
|
+
}
|
|
58
93
|
};
|
|
94
|
+
// wait for one HEAP_SNAPSHOT_DONE / ERROR from a worker
|
|
95
|
+
const waitHeap = (w, path, timeoutMs = 15000) => new Promise((resolve, reject) => {
|
|
96
|
+
const t = setTimeout(() => {
|
|
97
|
+
w.removeEventListener("message", onMsg);
|
|
98
|
+
reject(new Error("heap snapshot timeout: " + path));
|
|
99
|
+
}, timeoutMs);
|
|
100
|
+
const onMsg = (event) => {
|
|
101
|
+
if (event.data?.type === "HEAP_SNAPSHOT_DONE") {
|
|
102
|
+
clearTimeout(t);
|
|
103
|
+
w.removeEventListener("message", onMsg);
|
|
104
|
+
resolve(event.data.path || path);
|
|
105
|
+
}
|
|
106
|
+
else if (event.data?.type === "HEAP_SNAPSHOT_ERROR") {
|
|
107
|
+
clearTimeout(t);
|
|
108
|
+
w.removeEventListener("message", onMsg);
|
|
109
|
+
reject(new Error(String(event.data.error || "heap snapshot failed")));
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
w.addEventListener("message", onMsg);
|
|
113
|
+
try {
|
|
114
|
+
w.postMessage({ type: "heap_snapshot", path });
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
clearTimeout(t);
|
|
118
|
+
w.removeEventListener("message", onMsg);
|
|
119
|
+
reject(err);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
59
122
|
return {
|
|
60
123
|
fetchWorker: coordinationWorker,
|
|
61
124
|
cpuWorkers,
|
|
125
|
+
// graceful only: abort fetch, cancel cpus, wait. does not terminate.
|
|
126
|
+
shutdown: async (timeoutMs = 5000) => {
|
|
127
|
+
if (shuttingDown)
|
|
128
|
+
return;
|
|
129
|
+
shuttingDown = true;
|
|
130
|
+
log("createWebworker", ["shutdown start, timeoutMs=", timeoutMs]);
|
|
131
|
+
const done = new Promise((resolve) => {
|
|
132
|
+
shutdownResolve = resolve;
|
|
133
|
+
});
|
|
134
|
+
try {
|
|
135
|
+
coordinationWorker.postMessage({ type: "shutdown" });
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
log("createWebworker", ["post shutdown failed", String(err)]);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
await Promise.race([done, sleep(timeoutMs)]);
|
|
142
|
+
},
|
|
62
143
|
terminate: () => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
144
|
+
hardTerminate();
|
|
145
|
+
},
|
|
146
|
+
dumpHeaps: async (dir = ".") => {
|
|
147
|
+
const stamp = Date.now();
|
|
148
|
+
const paths = [];
|
|
149
|
+
const coordPath = `${dir}/coordinator-${stamp}.heapsnapshot`;
|
|
150
|
+
paths.push(await waitHeap(coordinationWorker, coordPath));
|
|
151
|
+
for (let i = 0; i < cpuWorkers.length; i++) {
|
|
152
|
+
const p = `${dir}/cpubound-${i}-${stamp}.heapsnapshot`;
|
|
153
|
+
paths.push(await waitHeap(cpuWorkers[i], p));
|
|
154
|
+
}
|
|
155
|
+
return paths;
|
|
66
156
|
},
|
|
67
157
|
};
|
|
68
158
|
}
|
|
@@ -73,36 +163,16 @@ export async function createWebworker(handle_result, scan_settings_path, pathPre
|
|
|
73
163
|
export function makeWebworkerScript() {
|
|
74
164
|
return workerMainCode;
|
|
75
165
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const url = URL.createObjectURL(blob);
|
|
81
|
-
const worker = new Worker(url, { type: "module" });
|
|
82
|
-
worker.onmessage = (event) => {
|
|
83
|
-
switch (event.data.type) {
|
|
84
|
-
case "RESULT":
|
|
85
|
-
if (handle_result)
|
|
86
|
-
handle_result(event.data.payload);
|
|
87
|
-
break;
|
|
88
|
-
case "ERROR":
|
|
89
|
-
case "scan_error":
|
|
90
|
-
if (handle_error)
|
|
91
|
-
handle_error(event.data.payload ?? event.data.error);
|
|
92
|
-
break;
|
|
93
|
-
case "DEBUG":
|
|
94
|
-
log("startWebworker", ["worker debug:", event.data.payload]);
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
return worker;
|
|
99
|
-
}
|
|
166
|
+
const blob = new Blob([workerMainCode], {
|
|
167
|
+
type: "text/javascript",
|
|
168
|
+
});
|
|
169
|
+
const url = URL.createObjectURL(blob);
|
|
100
170
|
// creates a worker via startWebworker, then waits for it to signal
|
|
101
171
|
// WORKER_READY (meaning self.onmessage = handleMessage has executed).
|
|
102
172
|
// after that, postMessage is safe, no race between worker startup
|
|
103
173
|
// and message delivery.
|
|
104
174
|
export function startWebworkerReady() {
|
|
105
|
-
const worker =
|
|
175
|
+
const worker = new Worker(url, { type: "module" });
|
|
106
176
|
return new Promise((resolve) => {
|
|
107
177
|
const onReady = (e) => {
|
|
108
178
|
if (e.data?.type === "WORKER_READY") {
|
|
@@ -27,6 +27,10 @@ max_blocks_buffer_size = MAX_BLOCKS_BUFFER_SIZE, anchor_range, stopSync) {
|
|
|
27
27
|
};
|
|
28
28
|
yield connection_status.last_packet;
|
|
29
29
|
while (true) {
|
|
30
|
+
if (stopSync?.aborted) {
|
|
31
|
+
log("blocksBufferFetchLoop", ["aborted, exiting fetch loop"]);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
30
34
|
// avoid overwriting last_packet in case of catastrophic reorg
|
|
31
35
|
// this generator will block on this. coordinator should rethrow the CatastrophicReorgError
|
|
32
36
|
if (connection_status.last_packet.status === "catastrophic_reorg") {
|
|
@@ -39,9 +43,17 @@ max_blocks_buffer_size = MAX_BLOCKS_BUFFER_SIZE, anchor_range, stopSync) {
|
|
|
39
43
|
bytes_read: 0,
|
|
40
44
|
node_url: node_url,
|
|
41
45
|
timestamp: new Date().toISOString(),
|
|
46
|
+
daemon_height: connection_status.sync.daemon_height ||
|
|
47
|
+
connection_status.last_packet.daemon_height,
|
|
42
48
|
};
|
|
43
49
|
yield connection_status.last_packet;
|
|
44
|
-
|
|
50
|
+
try {
|
|
51
|
+
await sleep(1000, stopSync);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
log("blocksBufferFetchLoop", ["aborted during buffer-full sleep"]);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
45
57
|
continue;
|
|
46
58
|
}
|
|
47
59
|
log("blocksBufferFetchLoop", [
|
|
@@ -56,11 +68,18 @@ max_blocks_buffer_size = MAX_BLOCKS_BUFFER_SIZE, anchor_range, stopSync) {
|
|
|
56
68
|
get_blocks_bin = await doRPCrequest(nodeUrl, current_range, stopSync);
|
|
57
69
|
}
|
|
58
70
|
catch (error) {
|
|
71
|
+
// abort is intentional shutdown, not a connection failure
|
|
72
|
+
if (stopSync?.aborted || isAbortError(error)) {
|
|
73
|
+
log("blocksBufferFetchLoop", ["aborted during rpc request"]);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
59
76
|
connection_status.last_packet = {
|
|
60
77
|
status: "connection_failed",
|
|
61
78
|
bytes_read: 0,
|
|
62
79
|
node_url,
|
|
63
80
|
timestamp: new Date().toISOString(),
|
|
81
|
+
daemon_height: connection_status.sync.daemon_height ||
|
|
82
|
+
connection_status.last_packet.daemon_height,
|
|
64
83
|
};
|
|
65
84
|
yield connection_status.last_packet;
|
|
66
85
|
throw error;
|
|
@@ -78,7 +97,13 @@ max_blocks_buffer_size = MAX_BLOCKS_BUFFER_SIZE, anchor_range, stopSync) {
|
|
|
78
97
|
yield connection_status.last_packet;
|
|
79
98
|
// no new blocks: at tip, sleep and retry
|
|
80
99
|
if (!result_meta.block_infos.length) {
|
|
81
|
-
|
|
100
|
+
try {
|
|
101
|
+
await sleep(1000, stopSync);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
log("blocksBufferFetchLoop", ["aborted during tip sleep"]);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
82
107
|
continue;
|
|
83
108
|
}
|
|
84
109
|
const bufferItem = makeBlocksBufferItem(result_meta, get_blocks_bin);
|
|
@@ -379,3 +404,9 @@ export async function reduceStartHeightToTip(start_height, node_url) {
|
|
|
379
404
|
export class CatastrophicReorgError extends Error {
|
|
380
405
|
name = "CatastrophicReorgError";
|
|
381
406
|
}
|
|
407
|
+
function isAbortError(error) {
|
|
408
|
+
if (!error || typeof error !== "object")
|
|
409
|
+
return false;
|
|
410
|
+
const name = error.name;
|
|
411
|
+
return name === "AbortError" || name === "DOMException";
|
|
412
|
+
}
|
|
@@ -19,6 +19,12 @@ export type ConnectionStatus = {
|
|
|
19
19
|
last_packet: ConnectionSatusLastPacket;
|
|
20
20
|
sync: ConnectionStatusSync;
|
|
21
21
|
};
|
|
22
|
+
export declare function applyWalletScanProgress(cs: ConnectionStatus, progress: {
|
|
23
|
+
current_scan_height: number;
|
|
24
|
+
scanned_ranges?: CacheRange[];
|
|
25
|
+
daemon_height?: number;
|
|
26
|
+
eta?: string;
|
|
27
|
+
}): void;
|
|
22
28
|
export declare const DEFAULT_CONNECTION_STATUS_PREFIX = "ConnectionStatus-";
|
|
23
29
|
export declare function msToHHMM(ms: number): string;
|
|
24
30
|
export declare function emptyConnectionStatus(overrides?: Partial<ConnectionStatus>): ConnectionStatus;
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { atomicWrite, } from "../api";
|
|
2
2
|
import { SCAN_SETTINGS_STORE_NAME_DEFAULT } from "../api";
|
|
3
|
+
// wallet progress write: always set scan fields; only replace eta when a new one is provided
|
|
4
|
+
// so a missing eta does not wipe / flicker the previous value.
|
|
5
|
+
export function applyWalletScanProgress(cs, progress) {
|
|
6
|
+
cs.sync.current_scan_height = progress.current_scan_height;
|
|
7
|
+
if (progress.scanned_ranges) {
|
|
8
|
+
cs.sync.scanned_ranges = progress.scanned_ranges;
|
|
9
|
+
}
|
|
10
|
+
if (typeof progress.daemon_height === "number") {
|
|
11
|
+
cs.sync.daemon_height = progress.daemon_height;
|
|
12
|
+
}
|
|
13
|
+
if (progress.eta !== undefined) {
|
|
14
|
+
cs.sync.eta = progress.eta;
|
|
15
|
+
}
|
|
16
|
+
cs.sync.timestamp = new Date().toISOString();
|
|
17
|
+
}
|
|
3
18
|
export const DEFAULT_CONNECTION_STATUS_PREFIX = "ConnectionStatus-";
|
|
4
19
|
export function msToHHMM(ms) {
|
|
5
20
|
const totalSeconds = Math.floor(ms / 1000);
|
|
@@ -4,12 +4,14 @@ export declare class ConnectionStatusOpened {
|
|
|
4
4
|
private _timer;
|
|
5
5
|
private _cached;
|
|
6
6
|
private _prevConnected;
|
|
7
|
+
private _prevSyncKey;
|
|
7
8
|
private _path;
|
|
8
9
|
constructor(scan_settings_path: string, _onChange?: ((status: ConnectionStatus | null) => void) | null | undefined);
|
|
9
|
-
watch(intervalMs?: number): void
|
|
10
|
+
watch(intervalMs?: number): Promise<void>;
|
|
10
11
|
unwatch(): void;
|
|
11
12
|
get connectionStatus(): ConnectionStatus | null;
|
|
12
13
|
get isConnected(): boolean;
|
|
13
14
|
get daemonHeight(): number | undefined;
|
|
15
|
+
private _syncKey;
|
|
14
16
|
private _poll;
|
|
15
17
|
}
|
|
@@ -4,15 +4,17 @@ export class ConnectionStatusOpened {
|
|
|
4
4
|
_timer;
|
|
5
5
|
_cached = null;
|
|
6
6
|
_prevConnected = false;
|
|
7
|
+
// notify when sync progress fields change, not only connect flip
|
|
8
|
+
_prevSyncKey = "";
|
|
7
9
|
_path;
|
|
8
10
|
constructor(scan_settings_path, _onChange) {
|
|
9
11
|
this._onChange = _onChange;
|
|
10
12
|
this._path = connectionStatusFilePath(scan_settings_path);
|
|
11
13
|
}
|
|
12
|
-
watch(intervalMs = 2500) {
|
|
14
|
+
async watch(intervalMs = 2500) {
|
|
13
15
|
if (this._timer)
|
|
14
16
|
return;
|
|
15
|
-
this._poll();
|
|
17
|
+
await this._poll();
|
|
16
18
|
this._timer = setInterval(() => this._poll(), intervalMs);
|
|
17
19
|
}
|
|
18
20
|
unwatch() {
|
|
@@ -37,14 +39,29 @@ export class ConnectionStatusOpened {
|
|
|
37
39
|
return age >= 0 && age <= 10_000;
|
|
38
40
|
}
|
|
39
41
|
get daemonHeight() {
|
|
40
|
-
return this._cached?.
|
|
42
|
+
return this._cached?.sync?.daemon_height;
|
|
43
|
+
}
|
|
44
|
+
_syncKey(cs) {
|
|
45
|
+
const s = cs?.sync;
|
|
46
|
+
if (!s)
|
|
47
|
+
return "";
|
|
48
|
+
return [
|
|
49
|
+
s.daemon_height ?? "",
|
|
50
|
+
s.current_scan_height ?? "",
|
|
51
|
+
s.eta ?? "",
|
|
52
|
+
s.timestamp ?? "",
|
|
53
|
+
].join("|");
|
|
41
54
|
}
|
|
42
55
|
async _poll() {
|
|
43
56
|
this._cached =
|
|
44
57
|
(await readConnectionStatusFile(this._path).catch(() => null)) || null;
|
|
45
58
|
const now = this.isConnected;
|
|
46
|
-
|
|
47
|
-
|
|
59
|
+
const syncKey = this._syncKey(this._cached);
|
|
60
|
+
const connectedChanged = now !== this._prevConnected;
|
|
61
|
+
const syncChanged = syncKey !== this._prevSyncKey;
|
|
62
|
+
this._prevConnected = now;
|
|
63
|
+
this._prevSyncKey = syncKey;
|
|
64
|
+
if (connectedChanged || syncChanged) {
|
|
48
65
|
this._onChange?.(this._cached);
|
|
49
66
|
}
|
|
50
67
|
}
|
|
@@ -7,9 +7,12 @@ import { ManyScanCachesOpened, ScanCacheOpened, type ManyScanCachesOpenedCreateO
|
|
|
7
7
|
* @param options.no_worker to feed the ManyScanCachesOpened manually with .feed(params) from CacheChangedCallbackParams
|
|
8
8
|
* @param options.no_stats to disable creation of stats file with aggregated information e.g. amount per wallet / subaddress
|
|
9
9
|
* @param options.notifyMasterChanged pass the output of this to another (no_worker) instance to feed
|
|
10
|
+
* @param options.logs "console" | "file" | "console-and-file" | "off"
|
|
11
|
+
* @param options.logs_include function names to include (e.g. handleCpuboundScan, blocksBufferFetchLoop)
|
|
12
|
+
* @param options.logs_exclude function names to exclude
|
|
10
13
|
* @returns Promise<ManyScanCachesOpened>
|
|
11
14
|
*/
|
|
12
|
-
export declare function openWallets(options?: ManyScanCachesOpenedCreateOptions): Promise<ManyScanCachesOpened
|
|
15
|
+
export declare function openWallets(options?: ManyScanCachesOpenedCreateOptions): Promise<ManyScanCachesOpened>;
|
|
13
16
|
/**
|
|
14
17
|
* Opens a **single wallet** for scanning.
|
|
15
18
|
* **Touches ScanSettings.json**,
|
|
@@ -8,6 +8,9 @@ import { openScanSettingsFile, writeWalletToScanSettings } from "../api";
|
|
|
8
8
|
* @param options.no_worker to feed the ManyScanCachesOpened manually with .feed(params) from CacheChangedCallbackParams
|
|
9
9
|
* @param options.no_stats to disable creation of stats file with aggregated information e.g. amount per wallet / subaddress
|
|
10
10
|
* @param options.notifyMasterChanged pass the output of this to another (no_worker) instance to feed
|
|
11
|
+
* @param options.logs "console" | "file" | "console-and-file" | "off"
|
|
12
|
+
* @param options.logs_include function names to include (e.g. handleCpuboundScan, blocksBufferFetchLoop)
|
|
13
|
+
* @param options.logs_exclude function names to exclude
|
|
11
14
|
* @returns Promise<ManyScanCachesOpened>
|
|
12
15
|
*/
|
|
13
16
|
export async function openWallets(options) {
|
|
@@ -129,8 +129,10 @@ export declare class ScanCacheOpened {
|
|
|
129
129
|
remove: () => null;
|
|
130
130
|
};
|
|
131
131
|
pause(): Promise<void>;
|
|
132
|
-
|
|
132
|
+
dumpWorkerHeaps(dir?: string): Promise<string[]>;
|
|
133
|
+
stopWorker(): Promise<void>;
|
|
133
134
|
unpause(): Promise<void>;
|
|
135
|
+
private _onWorkerError;
|
|
134
136
|
/**
|
|
135
137
|
* selectInputs returns array of inputs, whose sum is larger than amount
|
|
136
138
|
* adds approximate fee for 10kb transaction to amount if feePerByte is supplied
|
|
@@ -177,6 +179,9 @@ export type ManyScanCachesOpenedCreateOptions = {
|
|
|
177
179
|
retryDelayMs?: number;
|
|
178
180
|
connectionStatusIntervalMs?: number;
|
|
179
181
|
onConnectionStatusChange?: ((status: ConnectionStatus | null) => void) | null;
|
|
182
|
+
logs?: LogSetting;
|
|
183
|
+
logs_include?: PossibleLogs[];
|
|
184
|
+
logs_exclude?: PossibleLogs[];
|
|
180
185
|
};
|
|
181
186
|
export declare class ManyScanCachesOpened {
|
|
182
187
|
readonly connectionStatusOpened: ConnectionStatusOpened;
|
|
@@ -186,8 +191,9 @@ export declare class ManyScanCachesOpened {
|
|
|
186
191
|
get current_height(): number | null;
|
|
187
192
|
get node_url(): string;
|
|
188
193
|
changeNodeUrlAndStartHeight(node_url?: string, start_height?: number | null): Promise<void>;
|
|
189
|
-
retry(): Promise<boolean>;
|
|
190
|
-
stopWorker(): void
|
|
194
|
+
retry(): Promise<boolean | undefined>;
|
|
195
|
+
stopWorker(): Promise<void>;
|
|
196
|
+
dumpWorkerHeaps(dir?: string): Promise<string[]>;
|
|
191
197
|
changeNodeUrl(node_url: string): Promise<void>;
|
|
192
198
|
get merchant_confirmations(): number | null | undefined;
|
|
193
199
|
get cpu_worker_count(): number | undefined;
|
|
@@ -202,10 +208,12 @@ export declare class ManyScanCachesOpened {
|
|
|
202
208
|
setCpuWorkerCount(cpu_worker_count: number | undefined): Promise<void>;
|
|
203
209
|
setLogSettings(logs?: LogSetting | null, logs_include?: PossibleLogs[] | null, logs_exclude?: PossibleLogs[] | null): Promise<void>;
|
|
204
210
|
setWalletName(primary_address: string, name?: string): Promise<void>;
|
|
211
|
+
setWalletSlot(primary_address: string, slot?: number): Promise<void>;
|
|
205
212
|
changeStartHeight(start_height: number | null): Promise<void>;
|
|
206
213
|
private static _buildWallets;
|
|
207
|
-
static create(options: ManyScanCachesOpenedCreateOptions): Promise<ManyScanCachesOpened
|
|
214
|
+
static create(options: ManyScanCachesOpenedCreateOptions): Promise<ManyScanCachesOpened>;
|
|
208
215
|
buildWallets(): Promise<void>;
|
|
216
|
+
private reloadWalletsAfterStop;
|
|
209
217
|
addViewWallet(primary_address: string, view_key: string, fields?: {
|
|
210
218
|
wallet_name?: string;
|
|
211
219
|
wallet_slot?: number;
|