@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.
- package/dist/api.d.ts +5 -2
- package/dist/api.js +5 -1
- package/dist/io/atomicWrite.js +2 -1
- package/dist/io/indexedDB.js +7 -2
- package/dist/keypairs-seeds/keypairs.d.ts +21 -0
- package/dist/keypairs-seeds/keypairs.js +21 -0
- package/dist/node-interaction/binaryEndpoints.d.ts +31 -2
- package/dist/node-interaction/binaryEndpoints.js +61 -43
- package/dist/node-interaction/jsonEndpoints.d.ts +257 -130
- package/dist/node-interaction/jsonEndpoints.js +159 -119
- package/dist/node-interaction/nodeUrl.d.ts +21 -6
- package/dist/node-interaction/nodeUrl.js +50 -8
- package/dist/scanning-syncing/backgroundWorker.d.ts +9 -6
- package/dist/scanning-syncing/backgroundWorker.js +76 -39
- package/dist/scanning-syncing/blocksbuffer/blocksBufferFetchLoop.d.ts +44 -0
- package/dist/scanning-syncing/blocksbuffer/blocksBufferFetchLoop.js +353 -0
- package/dist/scanning-syncing/blocksbuffer/blocksbufferCoordination.d.ts +15 -0
- package/dist/scanning-syncing/blocksbuffer/blocksbufferCoordination.js +27 -0
- package/dist/scanning-syncing/{scanresult → blocksbuffer}/getBlocksbinBuffer.d.ts +5 -5
- package/dist/scanning-syncing/{scanresult → blocksbuffer}/getBlocksbinBuffer.js +7 -5
- package/dist/scanning-syncing/connectionStatus.d.ts +33 -9
- package/dist/scanning-syncing/connectionStatus.js +89 -19
- package/dist/scanning-syncing/scanSettings.d.ts +45 -0
- package/dist/scanning-syncing/scanSettings.js +63 -0
- package/dist/scanning-syncing/scanresult/reorg.d.ts +0 -11
- package/dist/scanning-syncing/scanresult/reorg.js +39 -16
- package/dist/scanning-syncing/scanresult/scanCache.d.ts +16 -2
- package/dist/scanning-syncing/scanresult/scanCache.js +58 -2
- package/dist/scanning-syncing/scanresult/scanCacheOpened.d.ts +18 -0
- package/dist/scanning-syncing/scanresult/scanCacheOpened.js +56 -11
- package/dist/scanning-syncing/scanresult/scanCoordination.d.ts +127 -0
- package/dist/scanning-syncing/scanresult/scanCoordination.js +611 -0
- package/dist/scanning-syncing/scanresult/scanLoop.d.ts +20 -0
- package/dist/scanning-syncing/scanresult/scanLoop.js +116 -0
- package/dist/scanning-syncing/scanresult/scanResult.d.ts +24 -3
- package/dist/scanning-syncing/scanresult/scanResult.js +151 -98
- 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 +23 -0
- package/dist/scanning-syncing/worker-mains/cpubound-main.d.ts +5 -0
- package/dist/scanning-syncing/worker-mains/cpubound-main.js +112 -0
- package/dist/scanning-syncing/worker-mains/worker.d.ts +1 -1
- package/dist/scanning-syncing/worker-mains/worker.js +58 -6
- package/dist/viewpair/ViewPair.d.ts +30 -7
- package/dist/viewpair/ViewPair.js +56 -118
- package/dist/wasm-processing/wasmFile.js +1 -1
- package/package.json +22 -24
- package/dist/io/extension.d.ts +0 -18
- 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 {
|
|
2
|
-
import {
|
|
3
|
-
|
|
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
|
-
|
|
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 |
|
|
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<
|
|
61
|
+
getBlocksBinScanResponse(getBlocksBinResponseBuffer: Uint8Array, metaCallBack?: GetBlocksBinMetaCallback): Promise<any>;
|
|
63
62
|
/**
|
|
64
|
-
*
|
|
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
|
-
|
|
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 {
|
|
1
|
+
import {} from "../scanning-syncing/scanresult/scanResult";
|
|
2
2
|
export {};
|
|
3
3
|
export { NodeUrl } from "../node-interaction/nodeUrl";
|
|
4
|
-
import { getBlocksBinScan, getBlocksBinExecuteRequest,
|
|
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/
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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.
|