@spirobel/monero-wallet-api 0.5.5 → 0.6.1
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/io/logging.js +1 -1
- package/dist/multisig/escrow.js +1 -1
- package/dist/scanning-syncing/backgroundWorker.js +1 -1
- package/dist/scanning-syncing/blocksbuffer/blocksBufferFetchLoop.d.ts +1 -1
- package/dist/scanning-syncing/blocksbuffer/blocksBufferFetchLoop.js +1 -1
- package/dist/scanning-syncing/blocksbuffer/blocksbufferCoordination.d.ts +1 -1
- package/dist/scanning-syncing/blocksbuffer/blocksbufferCoordination.js +2 -2
- package/dist/scanning-syncing/connectionStatus.d.ts +3 -1
- package/dist/scanning-syncing/connectionStatus.js +38 -3
- package/dist/scanning-syncing/connectionStatusOpened.d.ts +1 -1
- package/dist/scanning-syncing/connectionStatusOpened.js +2 -11
- package/dist/scanning-syncing/openWallet.js +1 -1
- package/dist/scanning-syncing/scanresult/computeKeyImage.d.ts +1 -1
- package/dist/scanning-syncing/scanresult/scanCache.d.ts +6 -2
- package/dist/scanning-syncing/scanresult/scanCache.js +12 -3
- package/dist/scanning-syncing/scanresult/scanCacheOpened.d.ts +47 -2
- package/dist/scanning-syncing/scanresult/scanCacheOpened.js +328 -47
- package/dist/scanning-syncing/scanresult/scanCoordination.d.ts +3 -2
- package/dist/scanning-syncing/scanresult/scanCoordination.js +42 -6
- package/dist/scanning-syncing/scanresult/scanLoop.d.ts +1 -1
- package/dist/scanning-syncing/scanresult/scanLoop.js +1 -1
- package/dist/scanning-syncing/scanresult/scanResult.d.ts +1 -1
- package/dist/scanning-syncing/scanresult/scanResult.js +18 -13
- package/dist/scanning-syncing/scanresult/scanStats.d.ts +1 -1
- package/dist/scanning-syncing/scanresult/scanStats.js +9 -6
- package/dist/scanning-syncing/worker-entrypoints/worker.js +1 -1
- package/dist/scanning-syncing/worker-mains/cpubound-main.js +1 -1
- package/dist/scanning-syncing/worker-mains/multisig-main.d.ts +1 -1
- package/dist/scanning-syncing/worker-mains/multisig-main.js +1 -1
- package/dist/scanning-syncing/worker-mains/worker.js +1 -1
- package/dist/scansettings/scanSettings.js +1 -1
- package/dist/scansettings/scanSettingsOpened.d.ts +1 -1
- package/dist/scansettings/scanSettingsOpened.js +1 -1
- package/dist/send-functionality/inputSelection.d.ts +1 -1
- package/dist/send-functionality/transactionBuilding.d.ts +1 -1
- 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/viewpair/ViewPair.d.ts +3 -3
- package/dist/viewpair/ViewPair.js +2 -2
- package/dist/wasm-processing/wasmFile.js +2 -2
- package/dist/{api.d.ts → xmr3.d.ts} +4 -0
- package/dist/{api.js → xmr3.js} +3 -0
- package/package.json +13 -6
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { get_info, NodeUrl, signTransaction, ViewPair, } from "../../
|
|
1
|
+
import { get_info, NodeUrl, signTransaction, ViewPair, } from "../../xmr3";
|
|
2
2
|
import { prepareInput, sumPayments, } from "../../send-functionality/inputSelection";
|
|
3
3
|
import { createWebworker } from "../backgroundWorker";
|
|
4
4
|
import { spendable } from "./scanResult";
|
|
5
|
-
import { getPathPrefix, readPrivateSpendKeyFromEnv, SCAN_SETTINGS_STORE_NAME_DEFAULT, SUB_ADDRESS_INDEX_DEFAULT_VALUE, walletSettingsPlusKeys, } from "../../
|
|
5
|
+
import { getPathPrefix, readPrivateSpendKeyFromEnv, SCAN_SETTINGS_STORE_NAME_DEFAULT, SUB_ADDRESS_INDEX_DEFAULT_VALUE, walletSettingsPlusKeys, } from "../../xmr3";
|
|
6
6
|
import { ScanSettingsOpened } from "../../scansettings/scanSettingsOpened";
|
|
7
7
|
import { ConnectionStatusOpened } from "../connectionStatusOpened";
|
|
8
|
+
import { ActionLogOpened, } from "../../actionlog/actionLogOpened";
|
|
9
|
+
import { sendToBackground } from "../../tools/globals";
|
|
8
10
|
import { findRange, lastRange, readCacheFileDefaultLocation, writeCacheFileDefaultLocationThrows, } from "./scanCache";
|
|
9
11
|
import { alignScanStatsWithCache, isSelfSpent, processTxlogInputs, processTxlogPayments, writeStatsFileDefaultLocation, } from "./scanStats";
|
|
10
12
|
import { readWriteConnectionStatusFile, } from "../connectionStatus";
|
|
@@ -389,12 +391,14 @@ export class ScanCacheOpened {
|
|
|
389
391
|
let maybeInputs = [];
|
|
390
392
|
let maybeFeeEstimate;
|
|
391
393
|
let maybeSendResult;
|
|
394
|
+
let maybeSignedTx;
|
|
392
395
|
try {
|
|
393
396
|
const { selectedInputs, feeEstimate } = await this.calculateFeeAndSelectInputs(params);
|
|
394
397
|
maybeInputs = selectedInputs;
|
|
395
398
|
maybeFeeEstimate = feeEstimate;
|
|
396
399
|
const unsignedTx = await this.makeTransactionFromSelectedInputs(params.payments, selectedInputs, feeEstimate);
|
|
397
400
|
const signedTx = await this.signTransaction(unsignedTx);
|
|
401
|
+
maybeSignedTx = signedTx;
|
|
398
402
|
const sendResult = await this.sendTransaction(signedTx);
|
|
399
403
|
maybeSendResult = sendResult;
|
|
400
404
|
if (sendResult.status !== "OK")
|
|
@@ -419,6 +423,8 @@ export class ScanCacheOpened {
|
|
|
419
423
|
inputs_index,
|
|
420
424
|
height: this.current_height,
|
|
421
425
|
timestamp: Date.now(),
|
|
426
|
+
invocationId: params.invocationId,
|
|
427
|
+
signed_tx: signedTx,
|
|
422
428
|
};
|
|
423
429
|
const newLen = cache.tx_logs.push(txLog);
|
|
424
430
|
const txLogIndex = newLen - 1;
|
|
@@ -464,17 +470,16 @@ export class ScanCacheOpened {
|
|
|
464
470
|
inputs_index,
|
|
465
471
|
height: this.current_height,
|
|
466
472
|
timestamp: Date.now(),
|
|
473
|
+
invocationId: params.invocationId,
|
|
474
|
+
signed_tx: maybeSignedTx,
|
|
467
475
|
};
|
|
468
|
-
|
|
476
|
+
cache.tx_logs.push(txLog);
|
|
469
477
|
},
|
|
470
478
|
});
|
|
471
479
|
const newCache = await readCacheFileDefaultLocation(this.primary_address, this.pathPrefix);
|
|
472
480
|
if (!newCache)
|
|
473
481
|
throw new Error(`cache not found for primary address: ${this.primary_address}, and path prefix: ${this.pathPrefix}`);
|
|
474
|
-
const changed_outputs =
|
|
475
|
-
change_reason: "spent",
|
|
476
|
-
output: input,
|
|
477
|
-
}));
|
|
482
|
+
const changed_outputs = [];
|
|
478
483
|
await this.feed({
|
|
479
484
|
newCache,
|
|
480
485
|
changed_outputs,
|
|
@@ -484,6 +489,179 @@ export class ScanCacheOpened {
|
|
|
484
489
|
throw e;
|
|
485
490
|
}
|
|
486
491
|
}
|
|
492
|
+
outputsForIndexes(indexes) {
|
|
493
|
+
const byId = new Map(this.spendableInputs().map((output) => [
|
|
494
|
+
String(output.index_on_blockchain),
|
|
495
|
+
output,
|
|
496
|
+
]));
|
|
497
|
+
const selected = [];
|
|
498
|
+
for (const id of indexes) {
|
|
499
|
+
const output = byId.get(id);
|
|
500
|
+
if (!output)
|
|
501
|
+
throw new Error(`input not spendable: ${id}`);
|
|
502
|
+
selected.push(output);
|
|
503
|
+
}
|
|
504
|
+
if (!selected.length)
|
|
505
|
+
throw new Error("no inputs selected");
|
|
506
|
+
return selected;
|
|
507
|
+
}
|
|
508
|
+
async makeSignSend(params) {
|
|
509
|
+
// an empty list means the wallet selects the inputs
|
|
510
|
+
if (!params.input_indexes.length) {
|
|
511
|
+
return this.makeSignSendTransaction({
|
|
512
|
+
payments: params.payments,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
return this.makeSignSendTransaction({
|
|
516
|
+
payments: params.payments,
|
|
517
|
+
inputs: this.outputsForIndexes(params.input_indexes),
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
async sweepSignSend(params) {
|
|
521
|
+
const inputs = params.input_indexes.length
|
|
522
|
+
? this.outputsForIndexes(params.input_indexes)
|
|
523
|
+
: this.spendableInputs();
|
|
524
|
+
if (!inputs.length)
|
|
525
|
+
throw new Error("no inputs selected");
|
|
526
|
+
const feeEstimate = await this.getFeeEstimate();
|
|
527
|
+
const unsignedTx = await this.makeSweepTransactionFromSelectedInputs(params.address, inputs, feeEstimate);
|
|
528
|
+
const signedTx = await this.signTransaction(unsignedTx);
|
|
529
|
+
const payments = [{ address: params.address, amount: "0" }];
|
|
530
|
+
const sendResult = await this.sendTransaction(signedTx);
|
|
531
|
+
if (sendResult.status !== "OK") {
|
|
532
|
+
await this.recordSend({
|
|
533
|
+
payments,
|
|
534
|
+
inputs,
|
|
535
|
+
signedTx,
|
|
536
|
+
feeEstimate,
|
|
537
|
+
sendResult,
|
|
538
|
+
error: "send raw transaction rpc returned error",
|
|
539
|
+
lockInputs: false,
|
|
540
|
+
});
|
|
541
|
+
throw new Error("send raw transaction rpc returned error");
|
|
542
|
+
}
|
|
543
|
+
await this.recordSend({
|
|
544
|
+
payments,
|
|
545
|
+
inputs,
|
|
546
|
+
signedTx,
|
|
547
|
+
feeEstimate,
|
|
548
|
+
sendResult,
|
|
549
|
+
lockInputs: true,
|
|
550
|
+
});
|
|
551
|
+
return sendResult;
|
|
552
|
+
}
|
|
553
|
+
async rebroadcastTx(txLogIndex) {
|
|
554
|
+
const txlog = this._cache.tx_logs?.[txLogIndex];
|
|
555
|
+
if (!txlog?.signed_tx)
|
|
556
|
+
throw new Error("no signed tx to rebroadcast");
|
|
557
|
+
const inputs = txlog.inputs_index
|
|
558
|
+
.map((id) => this._cache.outputs[id])
|
|
559
|
+
.filter((output) => output);
|
|
560
|
+
if (inputs.length &&
|
|
561
|
+
inputs.every((output) => typeof output.spent_in_tx_hash === "string"))
|
|
562
|
+
throw new Error("inputs already spent on chain");
|
|
563
|
+
const sendResult = await this.sendTransaction(txlog.signed_tx);
|
|
564
|
+
await this.stopWorker();
|
|
565
|
+
await writeCacheFileDefaultLocationThrows({
|
|
566
|
+
primary_address: this.primary_address,
|
|
567
|
+
pathPrefix: this.pathPrefix,
|
|
568
|
+
writeCallback: async (cache) => {
|
|
569
|
+
const row = cache.tx_logs?.[txLogIndex];
|
|
570
|
+
if (!row)
|
|
571
|
+
throw new Error("tx log missing");
|
|
572
|
+
row.sendResult = sendResult;
|
|
573
|
+
if (sendResult.status === "OK") {
|
|
574
|
+
row.error = undefined;
|
|
575
|
+
if (!cache.pending_spent_utxos)
|
|
576
|
+
cache.pending_spent_utxos = {};
|
|
577
|
+
for (const inputId of row.inputs_index)
|
|
578
|
+
cache.pending_spent_utxos[inputId] = txLogIndex;
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
row.error = sendResult.reason || "rebroadcast failed";
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
});
|
|
585
|
+
const newCache = await readCacheFileDefaultLocation(this.primary_address, this.pathPrefix);
|
|
586
|
+
if (!newCache)
|
|
587
|
+
throw new Error("cache not found after rebroadcast");
|
|
588
|
+
await this.feed({ newCache, changed_outputs: [] });
|
|
589
|
+
await this.unpause();
|
|
590
|
+
return sendResult;
|
|
591
|
+
}
|
|
592
|
+
get failed_txs() {
|
|
593
|
+
const logs = this._cache.tx_logs || [];
|
|
594
|
+
const rows = [];
|
|
595
|
+
for (let index = 0; index < logs.length; index++) {
|
|
596
|
+
const txlog = logs[index];
|
|
597
|
+
if (!txlog || txlog.hidden_on_send_plate)
|
|
598
|
+
continue;
|
|
599
|
+
if (txlog.sendResult?.status === "OK")
|
|
600
|
+
continue;
|
|
601
|
+
const inputs = txlog.inputs_index
|
|
602
|
+
.map((id) => this._cache.outputs[id])
|
|
603
|
+
.filter((output) => output);
|
|
604
|
+
if (inputs.length &&
|
|
605
|
+
inputs.every((output) => typeof output.spent_in_tx_hash === "string"))
|
|
606
|
+
continue;
|
|
607
|
+
rows.push({ index, txlog, inputs });
|
|
608
|
+
}
|
|
609
|
+
return rows;
|
|
610
|
+
}
|
|
611
|
+
async recordSend(args) {
|
|
612
|
+
await this.stopWorker();
|
|
613
|
+
await writeCacheFileDefaultLocationThrows({
|
|
614
|
+
primary_address: this.primary_address,
|
|
615
|
+
pathPrefix: this.pathPrefix,
|
|
616
|
+
writeCallback: async (cache) => {
|
|
617
|
+
if (!cache.tx_logs)
|
|
618
|
+
cache.tx_logs = [];
|
|
619
|
+
if (!cache.pending_spent_utxos)
|
|
620
|
+
cache.pending_spent_utxos = {};
|
|
621
|
+
const inputs_index = args.inputs.map((input) => String(input.index_on_blockchain));
|
|
622
|
+
const txLog = {
|
|
623
|
+
sendResult: args.sendResult,
|
|
624
|
+
error: args.error,
|
|
625
|
+
feeEstimate: args.feeEstimate,
|
|
626
|
+
payments: args.payments,
|
|
627
|
+
node_url: this.node_url,
|
|
628
|
+
inputs_index,
|
|
629
|
+
height: this.current_height,
|
|
630
|
+
timestamp: Date.now(),
|
|
631
|
+
signed_tx: args.signedTx,
|
|
632
|
+
};
|
|
633
|
+
const txLogIndex = cache.tx_logs.push(txLog) - 1;
|
|
634
|
+
if (!args.lockInputs)
|
|
635
|
+
return;
|
|
636
|
+
for (const inputId of inputs_index)
|
|
637
|
+
cache.pending_spent_utxos[inputId] = txLogIndex;
|
|
638
|
+
},
|
|
639
|
+
});
|
|
640
|
+
const newCache = await readCacheFileDefaultLocation(this.primary_address, this.pathPrefix);
|
|
641
|
+
if (!newCache)
|
|
642
|
+
throw new Error("cache not found after send");
|
|
643
|
+
const changed_outputs = args.lockInputs
|
|
644
|
+
? args.inputs.map((output) => ({ change_reason: "spent", output }))
|
|
645
|
+
: [];
|
|
646
|
+
await this.feed({ newCache, changed_outputs });
|
|
647
|
+
await this.unpause();
|
|
648
|
+
}
|
|
649
|
+
async dismissSendPlate() {
|
|
650
|
+
await this.stopWorker();
|
|
651
|
+
await writeCacheFileDefaultLocationThrows({
|
|
652
|
+
primary_address: this.primary_address,
|
|
653
|
+
pathPrefix: this.pathPrefix,
|
|
654
|
+
writeCallback: async (cache) => {
|
|
655
|
+
for (const row of cache.tx_logs || [])
|
|
656
|
+
row.hidden_on_send_plate = true;
|
|
657
|
+
},
|
|
658
|
+
});
|
|
659
|
+
const newCache = await readCacheFileDefaultLocation(this.primary_address, this.pathPrefix);
|
|
660
|
+
if (!newCache)
|
|
661
|
+
throw new Error("cache not found after dismiss");
|
|
662
|
+
await this.feed({ newCache, changed_outputs: [] });
|
|
663
|
+
await this.unpause();
|
|
664
|
+
}
|
|
487
665
|
/**
|
|
488
666
|
* makeStandardTransaction
|
|
489
667
|
*/
|
|
@@ -668,14 +846,17 @@ export class ScanCacheOpened {
|
|
|
668
846
|
* for all primary addresses
|
|
669
847
|
*/
|
|
670
848
|
async feed(params) {
|
|
671
|
-
//
|
|
849
|
+
// same wallet must store the new cache before a listener reads transactions
|
|
850
|
+
const sameWallet = this.view_pair.primary_address === params.newCache.primary_address;
|
|
851
|
+
if (sameWallet) {
|
|
852
|
+
this._cache = params.newCache;
|
|
853
|
+
if (!this._no_stats)
|
|
854
|
+
this._stats = await alignScanStatsWithCache(this._cache, this.view_pair, this.primary_address, getPathPrefix(this.scan_settings_path, this.pathPrefix), this.subaddress_index, lastRange(this._cache.scanned_ranges)?.end);
|
|
855
|
+
}
|
|
672
856
|
if (this.masterCacheChanged)
|
|
673
|
-
this.masterCacheChanged(params);
|
|
674
|
-
if (
|
|
857
|
+
await this.masterCacheChanged(params);
|
|
858
|
+
if (!sameWallet)
|
|
675
859
|
return;
|
|
676
|
-
this._cache = params.newCache;
|
|
677
|
-
if (!this._no_stats)
|
|
678
|
-
this._stats = await alignScanStatsWithCache(this._cache, this.view_pair, this.primary_address, getPathPrefix(this.scan_settings_path, this.pathPrefix), this.subaddress_index, lastRange(this._cache.scanned_ranges)?.end);
|
|
679
860
|
for (const listener of this.notifyListeners) {
|
|
680
861
|
if (listener)
|
|
681
862
|
listener(params);
|
|
@@ -712,13 +893,17 @@ export class ScanCacheOpened {
|
|
|
712
893
|
_stats = null;
|
|
713
894
|
notifyListeners = [];
|
|
714
895
|
}
|
|
896
|
+
function defaultNotifyMasterChanged(params) {
|
|
897
|
+
void sendToBackground("walletCacheChanged", JSON.stringify(params, (_k, v) => typeof v === "bigint" ? v.toString() : v)).catch(() => { });
|
|
898
|
+
}
|
|
715
899
|
export class ManyScanCachesOpened {
|
|
716
900
|
connectionStatusOpened;
|
|
901
|
+
actionLogOpened;
|
|
717
902
|
_scanSettings;
|
|
718
903
|
_options;
|
|
719
904
|
get start_height() {
|
|
720
905
|
if (this.wallets.length === 0)
|
|
721
|
-
return
|
|
906
|
+
return this._scanSettings.start_height;
|
|
722
907
|
return this.wallets[0]?.start_height;
|
|
723
908
|
}
|
|
724
909
|
// overall scan tip = lagging non-halted wallet (this.wallets is already non-halted)
|
|
@@ -735,14 +920,25 @@ export class ManyScanCachesOpened {
|
|
|
735
920
|
}
|
|
736
921
|
get node_url() {
|
|
737
922
|
if (this.wallets.length === 0)
|
|
738
|
-
return "";
|
|
923
|
+
return this._scanSettings.node_url ?? "";
|
|
739
924
|
return this.wallets[0]?.node_url;
|
|
740
925
|
}
|
|
741
926
|
async changeNodeUrlAndStartHeight(node_url, start_height) {
|
|
742
|
-
if (this.
|
|
927
|
+
if (this.actionLogOpened.extensionMessageBus === "ui") {
|
|
928
|
+
await sendToBackground("worker.changeNodeUrlAndStartHeight", {
|
|
929
|
+
node_url,
|
|
930
|
+
start_height,
|
|
931
|
+
}).catch(() => { });
|
|
743
932
|
return;
|
|
744
|
-
|
|
745
|
-
|
|
933
|
+
}
|
|
934
|
+
if (this.wallets.length === 0) {
|
|
935
|
+
if (node_url !== undefined)
|
|
936
|
+
await this._scanSettings.setNodeUrl(node_url);
|
|
937
|
+
if (start_height !== undefined)
|
|
938
|
+
await this._scanSettings.setStartHeight(start_height);
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
await this.wallets[0].changeNodeUrlAndStartHeight(node_url, start_height);
|
|
746
942
|
}
|
|
747
943
|
async retry() {
|
|
748
944
|
if (this.wallets.length === 0)
|
|
@@ -751,6 +947,10 @@ export class ManyScanCachesOpened {
|
|
|
751
947
|
return await masterWallet.retry();
|
|
752
948
|
}
|
|
753
949
|
async stopWorker() {
|
|
950
|
+
if (this.actionLogOpened.extensionMessageBus === "ui") {
|
|
951
|
+
await sendToBackground("worker.stopWorker", null).catch(() => { });
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
754
954
|
if (this.wallets.length === 0)
|
|
755
955
|
return;
|
|
756
956
|
const masterWallet = this.wallets[0];
|
|
@@ -764,10 +964,49 @@ export class ManyScanCachesOpened {
|
|
|
764
964
|
return await this.wallets[0].dumpWorkerHeaps(dir);
|
|
765
965
|
}
|
|
766
966
|
async changeNodeUrl(node_url) {
|
|
767
|
-
if (this.
|
|
967
|
+
if (this.actionLogOpened.extensionMessageBus === "ui") {
|
|
968
|
+
await sendToBackground("worker.setNodeUrl", { node_url }).catch(() => { });
|
|
768
969
|
return;
|
|
769
|
-
|
|
770
|
-
|
|
970
|
+
}
|
|
971
|
+
if (this.wallets.length === 0) {
|
|
972
|
+
await this._scanSettings.setNodeUrl(node_url);
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
await this.wallets[0].changeNodeUrl(node_url);
|
|
976
|
+
}
|
|
977
|
+
walletByAddress(primary_address) {
|
|
978
|
+
const wallet = this.wallets.find((row) => row.primary_address === primary_address);
|
|
979
|
+
if (!wallet)
|
|
980
|
+
throw new Error("wallet not found");
|
|
981
|
+
return wallet;
|
|
982
|
+
}
|
|
983
|
+
async makeSignSend(params) {
|
|
984
|
+
if (this.actionLogOpened.extensionMessageBus === "ui") {
|
|
985
|
+
await sendToBackground("worker.makeSignSend", params).catch(() => { });
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
return this.walletByAddress(params.primary_address).makeSignSend(params);
|
|
989
|
+
}
|
|
990
|
+
async sweepSignSend(params) {
|
|
991
|
+
if (this.actionLogOpened.extensionMessageBus === "ui") {
|
|
992
|
+
await sendToBackground("worker.sweepSignSend", params).catch(() => { });
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
return this.walletByAddress(params.primary_address).sweepSignSend(params);
|
|
996
|
+
}
|
|
997
|
+
async rebroadcastTx(params) {
|
|
998
|
+
if (this.actionLogOpened.extensionMessageBus === "ui") {
|
|
999
|
+
await sendToBackground("worker.rebroadcastTx", params).catch(() => { });
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
return this.walletByAddress(params.primary_address).rebroadcastTx(params.tx_log_index);
|
|
1003
|
+
}
|
|
1004
|
+
async dismissSendPlate(params) {
|
|
1005
|
+
if (this.actionLogOpened.extensionMessageBus === "ui") {
|
|
1006
|
+
await sendToBackground("worker.dismissSendPlate", params).catch(() => { });
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
return this.walletByAddress(params.primary_address).dismissSendPlate();
|
|
771
1010
|
}
|
|
772
1011
|
get merchant_confirmations() {
|
|
773
1012
|
if (this.wallets.length === 0)
|
|
@@ -811,7 +1050,20 @@ export class ManyScanCachesOpened {
|
|
|
811
1050
|
return await this.wallets[0].setCpuWorkerCount(cpu_worker_count);
|
|
812
1051
|
}
|
|
813
1052
|
async setLogSettings(logs, logs_include, logs_exclude) {
|
|
814
|
-
|
|
1053
|
+
if (this.actionLogOpened.extensionMessageBus === "ui") {
|
|
1054
|
+
await sendToBackground("worker.setLogSettings", {
|
|
1055
|
+
logs,
|
|
1056
|
+
logs_include,
|
|
1057
|
+
logs_exclude,
|
|
1058
|
+
}).catch(() => { });
|
|
1059
|
+
}
|
|
1060
|
+
await this._scanSettings.setLogSettings(logs, logs_include, logs_exclude);
|
|
1061
|
+
if (this.actionLogOpened.extensionMessageBus === "ui")
|
|
1062
|
+
return;
|
|
1063
|
+
for (const wallet of this.wallets)
|
|
1064
|
+
await wallet.stopWorker();
|
|
1065
|
+
for (const wallet of this.wallets)
|
|
1066
|
+
await wallet.unpause();
|
|
815
1067
|
}
|
|
816
1068
|
async setWalletName(primary_address, name) {
|
|
817
1069
|
await this._scanSettings.setWalletName(primary_address, name);
|
|
@@ -820,10 +1072,17 @@ export class ManyScanCachesOpened {
|
|
|
820
1072
|
await this._scanSettings.setWalletSlot(primary_address, slot);
|
|
821
1073
|
}
|
|
822
1074
|
async changeStartHeight(start_height) {
|
|
823
|
-
if (this.
|
|
1075
|
+
if (this.actionLogOpened.extensionMessageBus === "ui") {
|
|
1076
|
+
await sendToBackground("worker.setStartHeight", {
|
|
1077
|
+
start_height,
|
|
1078
|
+
}).catch(() => { });
|
|
824
1079
|
return;
|
|
825
|
-
|
|
826
|
-
|
|
1080
|
+
}
|
|
1081
|
+
if (this.wallets.length === 0) {
|
|
1082
|
+
await this._scanSettings.setStartHeight(start_height);
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
await this.wallets[0].changeStartHeight(start_height);
|
|
827
1086
|
}
|
|
828
1087
|
static async _buildWallets(scanSettingsOpened, options) {
|
|
829
1088
|
const { scan_settings_path, pathPrefix, no_worker, no_stats, workerError, notifyMasterChanged, } = options;
|
|
@@ -894,34 +1153,36 @@ export class ManyScanCachesOpened {
|
|
|
894
1153
|
// wrap workerError with auto-retry
|
|
895
1154
|
let retryTimer;
|
|
896
1155
|
let instance = null;
|
|
1156
|
+
const clearRetryTimer = () => {
|
|
1157
|
+
clearTimeout(retryTimer);
|
|
1158
|
+
retryTimer = undefined;
|
|
1159
|
+
};
|
|
897
1160
|
const retryFn = async () => {
|
|
1161
|
+
clearRetryTimer();
|
|
898
1162
|
// no wallets means idle, do not rebuild or restart workers
|
|
899
|
-
if (!instance || instance.wallets.length === 0)
|
|
900
|
-
clearTimeout(retryTimer);
|
|
901
|
-
retryTimer = undefined;
|
|
902
|
-
return;
|
|
903
|
-
}
|
|
904
|
-
await instance.buildWallets();
|
|
905
|
-
// still empty after rebuild, stay idle
|
|
906
|
-
if (instance.wallets.length === 0) {
|
|
907
|
-
clearTimeout(retryTimer);
|
|
908
|
-
retryTimer = undefined;
|
|
1163
|
+
if (!instance || instance.wallets.length === 0)
|
|
909
1164
|
return;
|
|
910
|
-
}
|
|
911
1165
|
const node_url = instance.node_url;
|
|
912
1166
|
if (!node_url)
|
|
913
|
-
|
|
1167
|
+
return;
|
|
1168
|
+
// probe first; only restart workers when node is reachable
|
|
914
1169
|
const getinfo_result = await get_info(node_url)
|
|
915
|
-
.then((r) =>
|
|
916
|
-
r?.status === "OK";
|
|
917
|
-
})
|
|
1170
|
+
.then((r) => r?.status === "OK")
|
|
918
1171
|
.catch(() => false);
|
|
919
|
-
if (getinfo_result)
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1172
|
+
if (!getinfo_result) {
|
|
1173
|
+
// still down: try again later without buildWallets thrash
|
|
1174
|
+
if (!retryTimer && instance.wallets.length > 0) {
|
|
1175
|
+
retryTimer = setTimeout(retryFn, retryDelayMs ?? 5000);
|
|
1176
|
+
}
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
await instance.buildWallets();
|
|
923
1180
|
};
|
|
924
1181
|
const newOptions = { ...options };
|
|
1182
|
+
// browser worker (background.ts): broadcast cache to sidebar
|
|
1183
|
+
if (!newOptions.notifyMasterChanged && !newOptions.no_worker) {
|
|
1184
|
+
newOptions.notifyMasterChanged = defaultNotifyMasterChanged;
|
|
1185
|
+
}
|
|
925
1186
|
let connectionFailedShown = false;
|
|
926
1187
|
if (autoRetry) {
|
|
927
1188
|
const originalError = options.workerError;
|
|
@@ -930,7 +1191,7 @@ export class ManyScanCachesOpened {
|
|
|
930
1191
|
const msg = err instanceof Error ? err.message : String(err);
|
|
931
1192
|
if (csOpened.connectionStatus?.last_packet?.status ===
|
|
932
1193
|
"catastrophic_reorg") {
|
|
933
|
-
|
|
1194
|
+
clearRetryTimer();
|
|
934
1195
|
instance?.stopWorker();
|
|
935
1196
|
throw new Error("catastrophic reorg, aborting ...");
|
|
936
1197
|
}
|
|
@@ -960,10 +1221,22 @@ export class ManyScanCachesOpened {
|
|
|
960
1221
|
}
|
|
961
1222
|
: (onConnectionStatusChange ?? null));
|
|
962
1223
|
csOpened.watch(connectionStatusIntervalMs);
|
|
963
|
-
|
|
1224
|
+
const actionLogPath = newOptions.actionLogPath ??
|
|
1225
|
+
actionLogPathFromScanSettings(scan_settings_path || SCAN_SETTINGS_STORE_NAME_DEFAULT);
|
|
1226
|
+
const actionLogOpened = await ActionLogOpened.create({
|
|
1227
|
+
path: actionLogPath,
|
|
1228
|
+
backend: newOptions.actionLogBackend,
|
|
1229
|
+
onChange: newOptions.onActionLogChange ?? null,
|
|
1230
|
+
extensionMessageBus: newOptions.extensionMessageBus,
|
|
1231
|
+
});
|
|
1232
|
+
instance = new ManyScanCachesOpened(wallets, csOpened, actionLogOpened, scanSettingsOpened, newOptions);
|
|
964
1233
|
return instance;
|
|
965
1234
|
}
|
|
966
1235
|
async buildWallets() {
|
|
1236
|
+
if (this.actionLogOpened.extensionMessageBus === "ui") {
|
|
1237
|
+
await sendToBackground("worker.buildWallets", null).catch(() => { });
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
967
1240
|
await this.stopWorker();
|
|
968
1241
|
await this.reloadWalletsAfterStop();
|
|
969
1242
|
}
|
|
@@ -1003,10 +1276,18 @@ export class ManyScanCachesOpened {
|
|
|
1003
1276
|
get wallets() {
|
|
1004
1277
|
return this._wallets;
|
|
1005
1278
|
}
|
|
1006
|
-
constructor(wallets, connectionStatusOpened, _scanSettings, _options) {
|
|
1279
|
+
constructor(wallets, connectionStatusOpened, actionLogOpened, _scanSettings, _options) {
|
|
1007
1280
|
this.connectionStatusOpened = connectionStatusOpened;
|
|
1281
|
+
this.actionLogOpened = actionLogOpened;
|
|
1008
1282
|
this._scanSettings = _scanSettings;
|
|
1009
1283
|
this._options = _options;
|
|
1010
1284
|
this._wallets = wallets;
|
|
1285
|
+
this.actionLogOpened.bindMco(this);
|
|
1011
1286
|
}
|
|
1012
1287
|
}
|
|
1288
|
+
function actionLogPathFromScanSettings(scan_settings_path) {
|
|
1289
|
+
const base = scan_settings_path.replace(/\.json$/i, "");
|
|
1290
|
+
if (globalThis.areWeInTheBrowser === true)
|
|
1291
|
+
return "actionlog";
|
|
1292
|
+
return `${base}-actionlog.sqlite`;
|
|
1293
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type GetBlocksBinBufferItem, type BlocksBufferLoopResult, type BlocksBufferIteratorResult, type ProcessScanResult, type BlockInfo } from "../../
|
|
1
|
+
import { type GetBlocksBinBufferItem, type BlocksBufferLoopResult, type BlocksBufferIteratorResult, type ProcessScanResult, type BlockInfo } from "../../xmr3";
|
|
2
2
|
import { type ScanLoopYield } from "./scanLoop";
|
|
3
3
|
import { type WorkItem } from "./scanLoop";
|
|
4
|
-
import { type ScanSettings } from "../../
|
|
4
|
+
import { type ScanSettings } from "../../xmr3";
|
|
5
5
|
import { type CacheRange, type ScanCache } from "./scanCache";
|
|
6
6
|
export type WalletConfig = {
|
|
7
7
|
primary_address: string;
|
|
@@ -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
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { setupBlocksBufferGenerator, ViewPair, handleConnectionStatusChanges, processScanResult, writeCacheToFile, sleep, findTipIndex, readWriteConnectionStatusFile, applyWalletScanProgress, } from "../../
|
|
1
|
+
import { setupBlocksBufferGenerator, ViewPair, handleConnectionStatusChanges, processScanResult, writeCacheToFile, sleep, findTipIndex, readWriteConnectionStatusFile, applyWalletScanProgress, } from "../../xmr3";
|
|
2
2
|
import {} from "./scanLoop";
|
|
3
3
|
import { makeWorkItem } from "./scanLoop";
|
|
4
|
-
import { cullTooLargeScanHeight, getNonHaltedWallets, getPathPrefix, openScanSettingsFile, SCAN_SETTINGS_STORE_NAME_DEFAULT, walletSettingsPlusKeys, } from "../../
|
|
5
|
-
import { findRange, initScanCacheFile, lastRange, makeCacheRangeForHeight, mergeRanges, } from "./scanCache";
|
|
4
|
+
import { cullTooLargeScanHeight, getNonHaltedWallets, getPathPrefix, openScanSettingsFile, SCAN_SETTINGS_STORE_NAME_DEFAULT, walletSettingsPlusKeys, } from "../../xmr3";
|
|
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;
|
|
@@ -278,6 +291,12 @@ export async function setupCoordinator(scanSettingsPath, pathPrefix, stopSync) {
|
|
|
278
291
|
const work_to_be_done = await findWorkToBeDone(scanSettingsPath, pathPrefix);
|
|
279
292
|
if (!work_to_be_done)
|
|
280
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);
|
|
281
300
|
const { generator: blocksGenerator, blocksBuffer } = await setupBlocksBufferGenerator({
|
|
282
301
|
nodeUrl: work_to_be_done.scan_settings.node_url,
|
|
283
302
|
startHeight: work_to_be_done.start_height,
|
|
@@ -345,9 +364,10 @@ export async function* coordinatorMainMultithreaded(scanSettingsPath, pathPrefix
|
|
|
345
364
|
if (!ctx)
|
|
346
365
|
throw new Error("[coordinatorMain Multithreaded] findWorkToBeDone returned false");
|
|
347
366
|
const work_to_be_done = ctx.work_to_be_done;
|
|
348
|
-
|
|
367
|
+
let blocksBuffer = ctx.blocksBuffer;
|
|
349
368
|
const workBuffer = ctx.workBuffer;
|
|
350
|
-
|
|
369
|
+
let blocksGenerator = ctx.blocksGenerator;
|
|
370
|
+
let fetchAt = work_to_be_done.start_height;
|
|
351
371
|
let totalBlocksScanned = 0;
|
|
352
372
|
let scanStartTime = Date.now();
|
|
353
373
|
let blocksPromise = blocksGenerator.next();
|
|
@@ -428,12 +448,28 @@ export async function* coordinatorMainMultithreaded(scanSettingsPath, pathPrefix
|
|
|
428
448
|
const blockCount = to_be_processed.to - to_be_processed.from + 1;
|
|
429
449
|
totalBlocksScanned += blockCount;
|
|
430
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
|
+
}
|
|
431
467
|
// always persist wallet progress after process; eta only if we have a new one
|
|
432
468
|
// so missing eta does not wipe the previous value (no flicker)
|
|
433
469
|
const eta = computeETA(wallet.cache, totalBlocksScanned, scanStartTime);
|
|
434
470
|
await readWriteConnectionStatusFile((cs) => {
|
|
435
471
|
applyWalletScanProgress(cs, {
|
|
436
|
-
current_scan_height:
|
|
472
|
+
current_scan_height: currentScanHeightFromRanges(wallet.cache.scanned_ranges, work_to_be_done.scan_settings.start_height || 0),
|
|
437
473
|
scanned_ranges: wallet.cache.scanned_ranges,
|
|
438
474
|
daemon_height: wallet.cache.daemon_height,
|
|
439
475
|
eta,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type GetBlocksBinBufferItem, type ScanResult, type WalletConfig, type WalletConfigPlusCache } from "../../
|
|
1
|
+
import { type GetBlocksBinBufferItem, type ScanResult, type WalletConfig, type WalletConfigPlusCache } from "../../xmr3";
|
|
2
2
|
export type WorkItem = {
|
|
3
3
|
walletConfig: WalletConfigPlusCache;
|
|
4
4
|
batch: GetBlocksBinBufferItem;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sleep, ViewPair, } from "../../
|
|
1
|
+
import { sleep, ViewPair, } from "../../xmr3";
|
|
2
2
|
export function makeWorkItem(walletConfig, batch, from, to) {
|
|
3
3
|
if (to && to > batch.get_blocks_result_meta.block_infos.length - 1) {
|
|
4
4
|
// we could throw here but we dont. Off by one errors shouldnt break the application
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type BlockInfo, type Output } from "../../
|
|
1
|
+
import { type BlockInfo, type Output } from "../../xmr3";
|
|
2
2
|
import { type KeyImage } from "./computeKeyImage";
|
|
3
3
|
import { type ErrorResponse } from "../../node-interaction/binaryEndpoints";
|
|
4
4
|
import type { CacheChangedCallback, CacheRange, ChangedOutput, ScanCache } from "./scanCache";
|