@spirobel/monero-wallet-api 0.3.16 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/api.d.ts +5 -2
  2. package/dist/api.js +5 -1
  3. package/dist/io/atomicWrite.js +2 -1
  4. package/dist/io/indexedDB.js +7 -2
  5. package/dist/keypairs-seeds/keypairs.d.ts +21 -0
  6. package/dist/keypairs-seeds/keypairs.js +21 -0
  7. package/dist/node-interaction/binaryEndpoints.d.ts +31 -2
  8. package/dist/node-interaction/binaryEndpoints.js +61 -43
  9. package/dist/node-interaction/jsonEndpoints.d.ts +257 -130
  10. package/dist/node-interaction/jsonEndpoints.js +159 -119
  11. package/dist/node-interaction/nodeUrl.d.ts +21 -6
  12. package/dist/node-interaction/nodeUrl.js +50 -8
  13. package/dist/scanning-syncing/backgroundWorker.d.ts +9 -6
  14. package/dist/scanning-syncing/backgroundWorker.js +76 -39
  15. package/dist/scanning-syncing/blocksbuffer/blocksBufferFetchLoop.d.ts +44 -0
  16. package/dist/scanning-syncing/blocksbuffer/blocksBufferFetchLoop.js +353 -0
  17. package/dist/scanning-syncing/blocksbuffer/blocksbufferCoordination.d.ts +15 -0
  18. package/dist/scanning-syncing/blocksbuffer/blocksbufferCoordination.js +27 -0
  19. package/dist/scanning-syncing/{scanresult → blocksbuffer}/getBlocksbinBuffer.d.ts +5 -5
  20. package/dist/scanning-syncing/{scanresult → blocksbuffer}/getBlocksbinBuffer.js +7 -5
  21. package/dist/scanning-syncing/connectionStatus.d.ts +33 -9
  22. package/dist/scanning-syncing/connectionStatus.js +89 -19
  23. package/dist/scanning-syncing/scanSettings.d.ts +45 -0
  24. package/dist/scanning-syncing/scanSettings.js +63 -0
  25. package/dist/scanning-syncing/scanresult/reorg.d.ts +0 -11
  26. package/dist/scanning-syncing/scanresult/reorg.js +39 -16
  27. package/dist/scanning-syncing/scanresult/scanCache.d.ts +16 -2
  28. package/dist/scanning-syncing/scanresult/scanCache.js +58 -2
  29. package/dist/scanning-syncing/scanresult/scanCacheOpened.d.ts +18 -0
  30. package/dist/scanning-syncing/scanresult/scanCacheOpened.js +56 -11
  31. package/dist/scanning-syncing/scanresult/scanCoordination.d.ts +127 -0
  32. package/dist/scanning-syncing/scanresult/scanCoordination.js +611 -0
  33. package/dist/scanning-syncing/scanresult/scanLoop.d.ts +20 -0
  34. package/dist/scanning-syncing/scanresult/scanLoop.js +116 -0
  35. package/dist/scanning-syncing/scanresult/scanResult.d.ts +24 -3
  36. package/dist/scanning-syncing/scanresult/scanResult.js +151 -98
  37. package/dist/scanning-syncing/worker-entrypoints/worker.js +1 -1
  38. package/dist/scanning-syncing/worker-mains/coordinator-main.d.ts +1 -0
  39. package/dist/scanning-syncing/worker-mains/coordinator-main.js +23 -0
  40. package/dist/scanning-syncing/worker-mains/cpubound-main.d.ts +5 -0
  41. package/dist/scanning-syncing/worker-mains/cpubound-main.js +112 -0
  42. package/dist/scanning-syncing/worker-mains/worker.d.ts +1 -1
  43. package/dist/scanning-syncing/worker-mains/worker.js +58 -6
  44. package/dist/viewpair/ViewPair.d.ts +30 -7
  45. package/dist/viewpair/ViewPair.js +56 -118
  46. package/dist/wasm-processing/wasmFile.js +1 -1
  47. package/package.json +22 -24
  48. package/dist/io/extension.d.ts +0 -18
  49. package/dist/io/extension.js +0 -11
@@ -0,0 +1 @@
1
+ export declare function coordinatorMainWorker(scan_settings_path?: string, pathPrefix?: string, cpuPorts?: MessagePort[]): Promise<void>;
@@ -0,0 +1,23 @@
1
+ import { coordinatorMainMultithreaded } from "../scanresult/scanCoordination";
2
+ export async function coordinatorMainWorker(scan_settings_path, pathPrefix, cpuPorts) {
3
+ console.log("[coordinatorMainWorker] cpuPorts=" + cpuPorts?.length);
4
+ try {
5
+ const gen = coordinatorMainMultithreaded(scan_settings_path, pathPrefix, cpuPorts);
6
+ for await (const event of gen) {
7
+ // console.log("event:", event);
8
+ if (event.type === "scan_ready") {
9
+ self.postMessage({
10
+ type: "SCAN_RESULT",
11
+ payload: {
12
+ newCache: event.newCache,
13
+ changed_outputs: event.changed_outputs,
14
+ },
15
+ });
16
+ }
17
+ }
18
+ }
19
+ catch (error) {
20
+ console.error("[coordinatorMainWorker] error:", error);
21
+ self.postMessage({ type: "ERROR", payload: error });
22
+ }
23
+ }
@@ -0,0 +1,5 @@
1
+ import { type ScanLoopInput, type ScanLoopYield } from "../scanresult/scanLoop";
2
+ export declare function handleCpuboundScan(msg: ScanLoopInput, port?: MessagePort): Promise<ScanLoopYield>;
3
+ export declare function handleCpuboundScanTry(msg: ScanLoopInput, port?: MessagePort): Promise<ScanLoopYield>;
4
+ export declare function sendFromCpuWorker(port: MessagePort, msg: ScanLoopYield): void;
5
+ export declare function sendToCpuWorker(port: MessagePort, msg: ScanLoopInput): void;
@@ -0,0 +1,112 @@
1
+ import { sleep, ViewPair } from "../../api";
2
+ import {} from "../scanresult/scanLoop";
3
+ /**
4
+ * handle a single scan work item dispatched from the coordinator.
5
+ * logs progress every 10 blocks, then sends the final result.
6
+ */
7
+ let callcounter = 0;
8
+ export async function handleCpuboundScan(msg, port) {
9
+ callcounter++;
10
+ console.log("[cpubound] callcounter=" + callcounter);
11
+ if (!msg) {
12
+ console.log("[cpubound] no content msg! ( ScanLoopInput = undefined )");
13
+ throw new Error("[cpubound] no content msg! ( ScanLoopInput = undefined )");
14
+ }
15
+ if (msg === "cancel") {
16
+ console.log("[cpubound] cancel msg! ( ScanLoopInput = cancel )");
17
+ throw new Error("[cpubound] no content msg! ( ScanLoopInput = undefined )");
18
+ }
19
+ console.log("[cpubound] got scan msg, walletConfig=" +
20
+ msg.walletConfig?.primary_address?.slice(0, 6));
21
+ const walletConfig = msg.walletConfig;
22
+ const item = msg;
23
+ if (!port) {
24
+ console.log("[cpubound] no port!");
25
+ throw new Error("[cpubound] no port");
26
+ }
27
+ console.log("[cpubound] primed, feeding workItem uuid=" +
28
+ (item && typeof item === "object"
29
+ ? item.work_uuid?.slice(0, 8)
30
+ : "cancel"));
31
+ // feed work item, get first InProgress yield
32
+ let blockCount = 0;
33
+ const heighfrom = item.batch.get_blocks_result_meta.block_infos[item.from].block_height;
34
+ const heightto = item.batch.get_blocks_result_meta.block_infos[item.to].block_height;
35
+ const viewpair = await ViewPair.create(item.walletConfig.primary_address, item.walletConfig.secret_view_key, item.walletConfig.subaddress_index);
36
+ await viewpair.loadGetBlocksBinResponse(item.batch.data);
37
+ const first_block_meta = item.batch.get_blocks_result_meta.block_infos[0];
38
+ if (!first_block_meta)
39
+ throw new Error("no first block meta");
40
+ const scanResult = {
41
+ outputs: [],
42
+ all_key_images: [],
43
+ new_height: first_block_meta.block_height + item.from,
44
+ primary_address: walletConfig.primary_address,
45
+ block_infos: item.batch.get_blocks_result_meta.block_infos,
46
+ daemon_height: item.batch.get_blocks_result_meta.daemon_height,
47
+ };
48
+ if (!(item.to >= item.from))
49
+ throw new Error("to must be >= from");
50
+ for (let i = item.from; i <= item.to; i++) {
51
+ // 2.call getBlocksBinScanOneBlock
52
+ const blockResult = await viewpair.getBlocksBinScanOneBlock(i);
53
+ if ("error" in blockResult)
54
+ throw new Error(blockResult.error);
55
+ //3. accumulate the scanresult
56
+ scanResult.outputs.push(...blockResult.outputs);
57
+ scanResult.all_key_images.push(...blockResult.all_key_images);
58
+ scanResult.new_height = first_block_meta.block_height + i;
59
+ // // update item.scanCache in memory will be done by the consumer
60
+ // it is saved on the workitem, but the workitemBuffer is managed by the consumer
61
+ // the consumer will reconcile the workitemBuffer on blocksbuffer changed yields from the fetchloop
62
+ // it will also reconcile the blocksbuffer with the workitembuffer when work was done,
63
+ // processScanResult will be called by the consumer and the scanresult will
64
+ // be persisted to disk by the consumer.
65
+ // if the workitem is at the left end of the workitemBuffer it will be shifted (popped from the left)
66
+ // if it is removed like this from the workItemBuffer and eventually no items in the workItemBuffer
67
+ // refercence the getBlocksBinBufferItem in the blocksbuffer, the blocksbufferitem is removed from the blocksbuffer
68
+ // this is how the reconiliaton workitembuffer -> blocksbuffer happens.
69
+ //
70
+ // the reconciliation in blocksbuffer -> workitembuffer starts in the fetchloop before it sends a yield blocksbuffer changed
71
+ // it is finished by the consumer of this generator after the yield blocksbuffer changed
72
+ // this happens through checking all workitemBuffer items,
73
+ // if their GetBlocksBinBufferItem is in the blocksbuffer (via the local_uuid)
74
+ // if they are not in the blocksbuffer they are removed from the workitemBuffer
75
+ // then for all the blocksbuffer items that are not yet referenced by workbuffer items
76
+ // they need to be silced into workitems and added to the workitembuffer
77
+ //the reconciliation blocksbuffer -> workitembuffer is done on every blocks buffer changed event from the fetchloop
78
+ // the reconciliation workitembuffer -> blocksbuffer is done on every workitembuffer marked done at the end of the workbuffer (left end)
79
+ blockCount++;
80
+ if (blockCount % 10 === 0) {
81
+ console.log("[cpubound] scanned " +
82
+ blockCount +
83
+ " " +
84
+ walletConfig.primary_address.slice(0, 6), "@", heighfrom, "-", heightto);
85
+ }
86
+ await sleep(10); // make sure the loop is not tight
87
+ }
88
+ console.log("[cpubound] scan done " +
89
+ blockCount +
90
+ " " +
91
+ walletConfig.primary_address.slice(0, 6), "@", heighfrom, "-", heightto);
92
+ return {
93
+ type: "Ready",
94
+ work_uuid: item.work_uuid,
95
+ result: scanResult,
96
+ };
97
+ }
98
+ export function handleCpuboundScanTry(msg, port) {
99
+ try {
100
+ return handleCpuboundScan(msg, port);
101
+ }
102
+ catch (err) {
103
+ console.error("[cpubound] error", err);
104
+ throw err;
105
+ }
106
+ }
107
+ export function sendFromCpuWorker(port, msg) {
108
+ port.postMessage(msg);
109
+ }
110
+ export function sendToCpuWorker(port, msg) {
111
+ port.postMessage(msg);
112
+ }
@@ -1 +1 @@
1
- export {};
1
+ export declare function CPU_PORT_HANDLER(pe: MessageEvent): void;
@@ -1,12 +1,64 @@
1
- import { sleep } from "../../io/sleep";
2
- import { scanWallets } from "../backgroundWorker";
3
- while (true) {
1
+ import { coordinatorMainWorker } from "./coordinator-main";
2
+ import { handleCpuboundScan, handleCpuboundScanTry, sendFromCpuWorker, } from "./cpubound-main";
3
+ self.onerror = (e) => self.postMessage({ type: "ERROR", payload: e });
4
+ self.addEventListener("unhandledrejection", (e) => self.postMessage({ type: "ERROR", payload: e.reason }));
5
+ let SCAN_SETTINGS_PATH;
6
+ let PATH_PREFIX;
7
+ let cpuPort;
8
+ export function CPU_PORT_HANDLER(pe) {
9
+ if (!cpuPort)
10
+ throw new Error("[cpubound] cpuPort is undefined in port.onmessage");
11
+ console.log("[cpubound] new workitem msg received");
12
+ cpuPort.postMessage({
13
+ type: "WORKSTART",
14
+ work_uuid: pe.data.work_uuid,
15
+ });
16
+ handleCpuboundScanTry(pe.data, cpuPort).then((result) => {
17
+ console.log("[cpubound] work finished, sending result");
18
+ if (!cpuPort)
19
+ throw new Error("[cpubound] cpuPort is undefined in port.onmessage");
20
+ cpuPort.onmessage = CPU_PORT_HANDLER;
21
+ sendFromCpuWorker(cpuPort, result);
22
+ });
23
+ }
24
+ const handleMessage = async (e) => {
25
+ const msg = e.data;
26
+ console.log("[worker] msg received", msg);
27
+ if (msg.type === "setup") {
28
+ SCAN_SETTINGS_PATH = msg.scan_settings_path;
29
+ PATH_PREFIX = msg.pathPrefix;
30
+ if (msg.role === "cpubound") {
31
+ cpuPort = e.ports[0];
32
+ if (cpuPort) {
33
+ cpuPort.onmessage = CPU_PORT_HANDLER;
34
+ cpuPort.addEventListener("message", (e) => {
35
+ console.log("[cpubound] message received", e);
36
+ });
37
+ cpuPort.addEventListener("messageerror", (e) => {
38
+ console.log("[cpubound] messageerror received", e);
39
+ });
40
+ }
41
+ }
42
+ else if (msg.role === "coordinator") {
43
+ const cpuPorts = [...e.ports];
44
+ coordinatorMainWorker(SCAN_SETTINGS_PATH, PATH_PREFIX, cpuPorts).catch((err) => {
45
+ self.postMessage({
46
+ type: "ERROR",
47
+ payload: err?.message ?? String(err),
48
+ });
49
+ });
50
+ }
51
+ }
52
+ };
53
+ function handleMessageTry(e) {
4
54
  try {
5
- await scanWallets((x) => self.postMessage({ type: "RESULT", payload: x }), undefined, scan_settings_path, pathPrefix);
6
- console.log("no wallets found to scan in wallet settings, trying again in 1 second");
7
- await sleep(1000);
55
+ handleMessage(e);
8
56
  }
9
57
  catch (error) {
58
+ console.error("[worker] error:", error);
10
59
  self.postMessage({ type: "ERROR", payload: error });
11
60
  }
12
61
  }
62
+ self.onmessage = handleMessageTry;
63
+ // signal main thread that onmessage handler is installed
64
+ self.postMessage({ type: "WORKER_READY" });
@@ -1,13 +1,12 @@
1
1
  import { type ScanResult } from "../scanning-syncing/scanresult/scanResult";
2
2
  export { type ScanResult };
3
3
  export { NodeUrl } from "../node-interaction/nodeUrl";
4
- import { type GetBlocksBinMetaCallback, type GetBlocksBinRequest } from "../node-interaction/binaryEndpoints";
4
+ import { type GetBlocksBinMetaCallback, type GetBlocksBinRequest, type ErrorResponse } from "../node-interaction/binaryEndpoints";
5
5
  import { type ScanCache } from "../scanning-syncing/scanresult/scanCache";
6
6
  import { type MakeTransactionParams, type UnsignedTransaction } from "../send-functionality/transactionBuilding";
7
7
  import { WasmProcessor } from "../wasm-processing/wasmProcessor";
8
+ import { type NETWORKS } from "../node-interaction/nodeUrl";
8
9
  import { type GetBlockHeadersRangeParams } from "../api";
9
- import { type CacheChangedCallback } from "../scanning-syncing/scanresult/scanCache";
10
- export type NETWORKS = "mainnet" | "stagenet" | "testnet";
11
10
  /**
12
11
  * This class is useful to interact with Moneros DaemonRpc binary requests in a convenient way.
13
12
  * (similar to how you would interact with a REST api that gives you json back.)
@@ -36,7 +35,7 @@ export declare class ViewPair extends WasmProcessor {
36
35
  * @param stopSync optional AbortSignal to stop the syncing process
37
36
  * @returns The difference to the same method on NodeUrl is: It returns {@link ScanResult} (outputs that belong to viewpair) and not just the blocks as json.
38
37
  */
39
- getBlocksBin(params: GetBlocksBinRequest, metaCallBack?: GetBlocksBinMetaCallback, stopSync?: AbortSignal): Promise<ScanResult | import("../api").ErrorResponse | undefined>;
38
+ getBlocksBin(params: GetBlocksBinRequest, metaCallBack?: GetBlocksBinMetaCallback, stopSync?: AbortSignal): Promise<ScanResult | ErrorResponse | undefined>;
40
39
  addGenesisHashToBlockIds(params: GetBlocksBinRequest): Promise<GetBlocksBinRequest>;
41
40
  /**
42
41
  * This function helps with making requests to the get_blocks.bin endpoint of the Monerod nodes.
@@ -59,11 +58,35 @@ export declare class ViewPair extends WasmProcessor {
59
58
  * @param metaCallBack contains meta information about the getBlocksbin call (new sync height = start_height param + number of blocks)
60
59
  * @returns It returns {@link ScanResult} (outputs that belong to viewpair)
61
60
  */
62
- getBlocksBinScanResponse(getBlocksBinResponseBuffer: Uint8Array, metaCallBack?: GetBlocksBinMetaCallback): Promise<ScanResult | import("../api").ErrorResponse | undefined>;
61
+ getBlocksBinScanResponse(getBlocksBinResponseBuffer: Uint8Array, metaCallBack?: GetBlocksBinMetaCallback): Promise<any>;
63
62
  /**
64
- * scan
63
+ * Loads a getBlocks.bin response into the WASM module without scanning for outputs.
64
+ * The stored response can later be used to scan individual blocks. Subsequent calls
65
+ * overwrite the previously stored response.
66
+ * @param getBlocksBinResponseBuffer the raw binary response from the get_blocks.bin endpoint
67
+ * @returns metadata about the loaded blocks (new_height, daemon_height, status, block_infos)
65
68
  */
66
- scan(cacheChanged?: CacheChangedCallback, stopSync?: AbortSignal, scan_settings_path?: string, pathPrefix?: string): Promise<void>;
69
+ loadGetBlocksBinResponse(getBlocksBinResponseBuffer: Uint8Array): Promise<import("../api").GetBlocksResultMeta>;
70
+ /**
71
+ * scan one block from a getblocks.bin response that was loaded into wasm memory.
72
+ * call loadGetBlocksBinResponse first to populate the response in the wasm module.
73
+ * call this in a loop over blockIndex 0..meta.block_infos.length-1 to scan all blocks.
74
+ * @param blockIndex index of the block within the loaded response (0-based)
75
+ * @returns scan result with outputs and all key images for that one block.
76
+ * returns ErrorResponse (`{ error: string }`) if scanning fails.
77
+ * check `if ("error" in result)` before accessing outputs/key_images.
78
+ *
79
+ * error cases from the rust wasm (search these strings in rust/src/):
80
+ * - "Block index {} out of bounds (total blocks: {})"
81
+ * blockIndex >= number of blocks in the loaded response
82
+ * - "No getBlocks.bin response loaded. Call loadGetBlocksBinResponse first."
83
+ * forgot to call loadGetBlocksBinResponse before this method
84
+ * - "Error scanning miner transaction: {}"
85
+ * the miner tx of the block could not be scanned
86
+ * - "Error scanning block: {}"
87
+ * the block could not be scanned
88
+ */
89
+ getBlocksBinScanOneBlock(blockIndex: number): Promise<ScanResult | ErrorResponse>;
67
90
  /**
68
91
  * This method makes an integrated Address for the Address of the Viewpair it was opened with.
69
92
  * The network (mainnet, stagenet, testnet) is the same as the one of the Viewpairaddress.
@@ -1,13 +1,13 @@
1
- import { processScanResult, } from "../scanning-syncing/scanresult/scanResult";
1
+ import {} from "../scanning-syncing/scanresult/scanResult";
2
2
  export {};
3
3
  export { NodeUrl } from "../node-interaction/nodeUrl";
4
- import { getBlocksBinScan, getBlocksBinExecuteRequest, getBlocksBinScanResponse, MAINNET_GENESIS_BLOCK_HASH, STAGENET_GENESIS_BLOCK_HASH, } from "../node-interaction/binaryEndpoints";
4
+ import { getBlocksBinScan, getBlocksBinExecuteRequest, getBlocksBinScanOneBlock, loadGetBlocksBinResponse, MAINNET_GENESIS_BLOCK_HASH, STAGENET_GENESIS_BLOCK_HASH, } from "../node-interaction/binaryEndpoints";
5
5
  import { handleScanError, lastRange, writeCacheFileDefaultLocationThrows, } from "../scanning-syncing/scanresult/scanCache";
6
6
  import { makeTransaction, } from "../send-functionality/transactionBuilding";
7
7
  import { WasmProcessor } from "../wasm-processing/wasmProcessor";
8
- import { LOCAL_NODE_DEFAULT_URL } from "../node-interaction/nodeUrl";
8
+ import { LOCAL_NODE_DEFAULT_URL, } from "../node-interaction/nodeUrl";
9
9
  import { atomicWrite, get_block_headers_range, get_info, } from "../api";
10
- import { readGetblocksBinBuffer, trimGetBlocksBinBuffer, writeGetblocksBinBuffer, } from "../scanning-syncing/scanresult/getBlocksbinBuffer";
10
+ import { readGetblocksBinBuffer, trimGetBlocksBinBuffer, writeGetblocksBinBuffer, } from "../scanning-syncing/blocksbuffer/getBlocksbinBuffer";
11
11
  import { initScanCache, readCacheFileDefaultLocation, } from "../scanning-syncing/scanresult/scanCache";
12
12
  import { cullTooLargeScanHeight, openNonHaltedWallets, readWalletFromScanSettings, walletSettingsPlusKeys, } from "../scanning-syncing/scanSettings";
13
13
  import { sleep } from "../io/sleep";
@@ -120,123 +120,61 @@ export class ViewPair extends WasmProcessor {
120
120
  * @param metaCallBack contains meta information about the getBlocksbin call (new sync height = start_height param + number of blocks)
121
121
  * @returns It returns {@link ScanResult} (outputs that belong to viewpair)
122
122
  */
123
- getBlocksBinScanResponse(getBlocksBinResponseBuffer, metaCallBack) {
124
- return getBlocksBinScanResponse(this, getBlocksBinResponseBuffer, metaCallBack);
123
+ async getBlocksBinScanResponse(getBlocksBinResponseBuffer, metaCallBack) {
124
+ const meta = await this.loadGetBlocksBinResponse(getBlocksBinResponseBuffer);
125
+ if ("error" in meta)
126
+ return meta;
127
+ if (metaCallBack)
128
+ metaCallBack(meta);
129
+ const result = {
130
+ outputs: [],
131
+ all_key_images: [],
132
+ new_height: meta.new_height,
133
+ primary_address: this.primary_address,
134
+ block_infos: meta.block_infos,
135
+ daemon_height: meta.daemon_height,
136
+ };
137
+ for (let i = 0; i < meta.block_infos.length; i++) {
138
+ const blockResult = await getBlocksBinScanOneBlock(this, i);
139
+ if ("error" in blockResult)
140
+ return blockResult;
141
+ result.outputs.push(...blockResult.outputs);
142
+ result.all_key_images.push(...blockResult.all_key_images);
143
+ await sleep(10);
144
+ }
145
+ return result;
125
146
  }
126
147
  /**
127
- * scan
148
+ * Loads a getBlocks.bin response into the WASM module without scanning for outputs.
149
+ * The stored response can later be used to scan individual blocks. Subsequent calls
150
+ * overwrite the previously stored response.
151
+ * @param getBlocksBinResponseBuffer the raw binary response from the get_blocks.bin endpoint
152
+ * @returns metadata about the loaded blocks (new_height, daemon_height, status, block_infos)
128
153
  */
129
- async scan(cacheChanged = (params) => console.log(params), stopSync, scan_settings_path, pathPrefix) {
130
- try {
131
- const processor = this;
132
- const nonHaltedWallets = await openNonHaltedWallets(scan_settings_path);
133
- const masterWalletSettings = nonHaltedWallets[0];
134
- if (masterWalletSettings.primary_address !== this.primary_address)
135
- throw new Error("master wallet should be the first of the non halted wallets");
136
- let masterStartHeight = await cullTooLargeScanHeight(processor.node_url, scan_settings_path);
137
- let current_range = await initScanCache(processor, masterStartHeight, scan_settings_path, pathPrefix);
138
- const blockGenerator = (async function* () {
139
- while (true) {
140
- if (stopSync?.aborted)
141
- return;
142
- const firstResponse = await processor.getBlocksBinExecuteRequest({ block_ids: current_range.block_hashes.map((b) => b.block_hash) }, stopSync);
143
- await readWriteConnectionStatusFile((cs) => {
144
- if (cs?.last_packet.status === "catastrophic_reorg")
145
- return;
146
- const connectionStatus = {
147
- last_packet: {
148
- status: "OK",
149
- bytes_read: firstResponse.length,
150
- node_url: processor.node_url,
151
- timestamp: new Date().toISOString(),
152
- },
153
- };
154
- return connectionStatus;
155
- });
156
- yield firstResponse;
157
- }
158
- })();
159
- const masterWithKeys = await walletSettingsPlusKeys(masterWalletSettings);
160
- const slaveViewPairs = [];
161
- if (nonHaltedWallets.length > 1) {
162
- for (const slaveWallet of nonHaltedWallets.slice(1)) {
163
- const slaveWithKeys = await walletSettingsPlusKeys(slaveWallet);
164
- const viewpair = await ViewPair.create(slaveWallet.primary_address, slaveWithKeys.secret_view_key, slaveWallet.subaddress_index, masterWalletSettings.node_url);
165
- slaveViewPairs.push({
166
- viewpair,
167
- current_range: await initScanCache(viewpair, masterStartHeight, scan_settings_path, pathPrefix),
168
- secret_spend_key: slaveWithKeys.secret_spend_key,
169
- });
170
- }
171
- }
172
- const catastrophic_reorg_cb = async () => {
173
- writeConnectionStatusFile("catastrophic_reorg", processor.node_url, undefined, scan_settings_path);
174
- };
175
- for await (const firstResponse of blockGenerator) {
176
- if (!firstResponse)
177
- continue;
178
- await this.writeSubaddressesToScanCache(scan_settings_path, pathPrefix);
179
- const result = await processor.getBlocksBinScanResponse(firstResponse);
180
- const oldMasterCurrentRange = structuredClone(current_range);
181
- current_range = await processScanResult({
182
- current_range,
183
- result,
184
- cacheChanged,
185
- secret_spend_key: masterWithKeys.secret_spend_key,
186
- pathPrefix,
187
- catastrophic_reorg_cb,
188
- });
189
- if (slaveViewPairs.length > 0) {
190
- if (result && "block_infos" in result)
191
- await writeGetblocksBinBuffer(firstResponse, result.block_infos, pathPrefix); // feed the slaves
192
- for (const slave of slaveViewPairs) {
193
- let blocksBinItems = await readGetblocksBinBuffer(slave.current_range.end, pathPrefix);
194
- let use_master_current_range = false;
195
- if (!blocksBinItems.length) {
196
- blocksBinItems = await readGetblocksBinBuffer(current_range.end, // we use the new current range end to find the blocks
197
- pathPrefix);
198
- slave.current_range = structuredClone(oldMasterCurrentRange); // but we use the old master current range to scan with slaves
199
- use_master_current_range = true;
200
- }
201
- for (const blocksBinItem of blocksBinItems) {
202
- const blocksbin = new Uint8Array(await Bun.file(`${pathPrefix ?? ""}getblocksbinbuffer/${blocksBinItem.filename}`).arrayBuffer());
203
- await slave.viewpair.writeSubaddressesToScanCache(scan_settings_path, pathPrefix);
204
- const slaveResult = await slave.viewpair.getBlocksBinScanResponse(blocksbin);
205
- slave.current_range = await processScanResult({
206
- current_range: slave.current_range,
207
- result: slaveResult,
208
- cacheChanged,
209
- secret_spend_key: slave.secret_spend_key,
210
- pathPrefix,
211
- use_master_current_range,
212
- catastrophic_reorg_cb,
213
- });
214
- }
215
- } // scan the slaves
216
- await trimGetBlocksBinBuffer(nonHaltedWallets, pathPrefix);
217
- }
218
- if (!result ||
219
- (result && "block_infos" in result && result.block_infos.length === 0)) {
220
- // we are at the tip, and there are no new blocks
221
- // sleep for 1 second before sending another
222
- // getBlocks.bin request
223
- //
224
- await sleep(1000);
225
- }
226
- }
227
- }
228
- catch (error) {
229
- handleScanError(error);
230
- const processor = this;
231
- const cache = await readCacheFileDefaultLocation(processor.primary_address, pathPrefix);
232
- if (!cache)
233
- throw new Error(`${error} in scan() + cache not found for primary address: ${processor.primary_address} and path prefix: ${pathPrefix}`);
234
- await cacheChanged({
235
- newCache: cache,
236
- changed_outputs: [],
237
- });
238
- throw error;
239
- }
154
+ loadGetBlocksBinResponse(getBlocksBinResponseBuffer) {
155
+ return loadGetBlocksBinResponse(this, getBlocksBinResponseBuffer);
156
+ }
157
+ /**
158
+ * scan one block from a getblocks.bin response that was loaded into wasm memory.
159
+ * call loadGetBlocksBinResponse first to populate the response in the wasm module.
160
+ * call this in a loop over blockIndex 0..meta.block_infos.length-1 to scan all blocks.
161
+ * @param blockIndex index of the block within the loaded response (0-based)
162
+ * @returns scan result with outputs and all key images for that one block.
163
+ * returns ErrorResponse (`{ error: string }`) if scanning fails.
164
+ * check `if ("error" in result)` before accessing outputs/key_images.
165
+ *
166
+ * error cases from the rust wasm (search these strings in rust/src/):
167
+ * - "Block index {} out of bounds (total blocks: {})"
168
+ * blockIndex >= number of blocks in the loaded response
169
+ * - "No getBlocks.bin response loaded. Call loadGetBlocksBinResponse first."
170
+ * forgot to call loadGetBlocksBinResponse before this method
171
+ * - "Error scanning miner transaction: {}"
172
+ * the miner tx of the block could not be scanned
173
+ * - "Error scanning block: {}"
174
+ * the block could not be scanned
175
+ */
176
+ getBlocksBinScanOneBlock(blockIndex) {
177
+ return getBlocksBinScanOneBlock(this, blockIndex);
240
178
  }
241
179
  /**
242
180
  * This method makes an integrated Address for the Address of the Viewpair it was opened with.