@spirobel/monero-wallet-api 0.5.5 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actionlog/actionLogOpened.d.ts +96 -1
- package/dist/actionlog/actionLogOpened.js +386 -1
- package/dist/actionlog/actionLogTypes.d.ts +114 -0
- package/dist/actionlog/actionLogTypes.js +198 -0
- package/dist/actionlog/backend/idbBackend.d.ts +13 -0
- package/dist/actionlog/backend/idbBackend.js +109 -0
- package/dist/actionlog/backend/jsonlBackend.d.ts +19 -0
- package/dist/actionlog/backend/jsonlBackend.js +110 -0
- package/dist/actionlog/backend/memoryBackend.d.ts +11 -0
- package/dist/actionlog/backend/memoryBackend.js +31 -0
- package/dist/actionlog/backend/sqliteBackend.d.ts +14 -0
- package/dist/actionlog/backend/sqliteBackend.js +96 -0
- package/dist/actionlog/backend.d.ts +12 -0
- package/dist/actionlog/backend.js +1 -0
- package/dist/actionlog/idbBackend.d.ts +19 -0
- package/dist/actionlog/idbBackend.js +98 -0
- package/dist/actionlog/jsonlBackend.d.ts +19 -0
- package/dist/actionlog/jsonlBackend.js +75 -0
- package/dist/actionlog/memoryBackend.d.ts +14 -0
- package/dist/actionlog/memoryBackend.js +38 -0
- package/dist/actionlog/openMaps.d.ts +11 -0
- package/dist/actionlog/openMaps.js +32 -0
- package/dist/actionlog/sqliteBackend.d.ts +18 -0
- package/dist/actionlog/sqliteBackend.js +105 -0
- package/dist/actionlog/toolContext.d.ts +36 -0
- package/dist/actionlog/toolContext.js +1 -0
- package/dist/actionlog/types.d.ts +62 -0
- package/dist/actionlog/types.js +62 -0
- package/dist/api.d.ts +4 -0
- package/dist/api.js +3 -0
- package/dist/scanning-syncing/blocksbuffer/blocksbufferCoordination.js +2 -2
- package/dist/scanning-syncing/connectionStatus.d.ts +2 -0
- package/dist/scanning-syncing/connectionStatus.js +36 -1
- package/dist/scanning-syncing/connectionStatusOpened.d.ts +1 -1
- package/dist/scanning-syncing/connectionStatusOpened.js +2 -11
- package/dist/scanning-syncing/scanresult/scanCache.d.ts +5 -1
- package/dist/scanning-syncing/scanresult/scanCache.js +11 -2
- package/dist/scanning-syncing/scanresult/scanCacheOpened.d.ts +46 -1
- package/dist/scanning-syncing/scanresult/scanCacheOpened.js +317 -39
- package/dist/scanning-syncing/scanresult/scanCoordination.d.ts +1 -0
- package/dist/scanning-syncing/scanresult/scanCoordination.js +40 -4
- package/dist/scanning-syncing/scanresult/scanResult.js +17 -12
- package/dist/scanning-syncing/scanresult/scanStats.js +8 -5
- package/dist/scanning-syncing/worker-entrypoints/worker.js +1 -1
- package/dist/tools/calls/001.d.ts +20 -0
- package/dist/tools/calls/001.js +236 -0
- package/dist/tools/calls/002-content.d.ts +46 -0
- package/dist/tools/calls/002-content.js +135 -0
- package/dist/tools/calls/002-fail-cleanup.d.ts +27 -0
- package/dist/tools/calls/002-fail-cleanup.js +60 -0
- package/dist/tools/calls/002.d.ts +64 -0
- package/dist/tools/calls/002.js +353 -0
- package/dist/tools/content.d.ts +22 -0
- package/dist/tools/content.js +41 -0
- package/dist/tools/counterparty.d.ts +3 -0
- package/dist/tools/counterparty.js +1 -0
- package/dist/tools/download-public-suffix.d.ts +1 -0
- package/dist/tools/download-public-suffix.js +20 -0
- package/dist/tools/globals.d.ts +196 -0
- package/dist/tools/globals.js +14 -0
- package/dist/tools/monero-tools-content.d.ts +29 -0
- package/dist/tools/monero-tools-content.js +80 -0
- package/dist/tools/monero-tools.d.ts +27 -52
- package/dist/tools/monero-tools.js +127 -222
- package/dist/tools/public-suffix-list.d.ts +1 -0
- package/dist/tools/public-suffix-list.js +10336 -0
- package/dist/tools/runtimeMessages.d.ts +4 -0
- package/dist/tools/runtimeMessages.js +18 -0
- package/dist/tools/toolActionLogContext.d.ts +33 -0
- package/dist/tools/toolActionLogContext.js +1 -0
- package/dist/tools/toolArm.d.ts +47 -0
- package/dist/tools/toolArm.js +1 -0
- package/dist/tools/ui.d.ts +2 -0
- package/dist/tools/ui.js +1 -0
- package/dist/tools/worker.d.ts +1 -0
- package/dist/tools/worker.js +1 -0
- package/dist/wasm-processing/wasmFile.js +2 -2
- package/package.json +8 -1
|
@@ -5,6 +5,8 @@ import { spendable } from "./scanResult";
|
|
|
5
5
|
import { getPathPrefix, readPrivateSpendKeyFromEnv, SCAN_SETTINGS_STORE_NAME_DEFAULT, SUB_ADDRESS_INDEX_DEFAULT_VALUE, walletSettingsPlusKeys, } from "../../api";
|
|
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
|
*/
|
|
@@ -712,13 +890,17 @@ export class ScanCacheOpened {
|
|
|
712
890
|
_stats = null;
|
|
713
891
|
notifyListeners = [];
|
|
714
892
|
}
|
|
893
|
+
function defaultNotifyMasterChanged(params) {
|
|
894
|
+
void sendToBackground("walletCacheChanged", JSON.stringify(params, (_k, v) => typeof v === "bigint" ? v.toString() : v)).catch(() => { });
|
|
895
|
+
}
|
|
715
896
|
export class ManyScanCachesOpened {
|
|
716
897
|
connectionStatusOpened;
|
|
898
|
+
actionLogOpened;
|
|
717
899
|
_scanSettings;
|
|
718
900
|
_options;
|
|
719
901
|
get start_height() {
|
|
720
902
|
if (this.wallets.length === 0)
|
|
721
|
-
return
|
|
903
|
+
return this._scanSettings.start_height;
|
|
722
904
|
return this.wallets[0]?.start_height;
|
|
723
905
|
}
|
|
724
906
|
// overall scan tip = lagging non-halted wallet (this.wallets is already non-halted)
|
|
@@ -735,14 +917,25 @@ export class ManyScanCachesOpened {
|
|
|
735
917
|
}
|
|
736
918
|
get node_url() {
|
|
737
919
|
if (this.wallets.length === 0)
|
|
738
|
-
return "";
|
|
920
|
+
return this._scanSettings.node_url ?? "";
|
|
739
921
|
return this.wallets[0]?.node_url;
|
|
740
922
|
}
|
|
741
923
|
async changeNodeUrlAndStartHeight(node_url, start_height) {
|
|
742
|
-
if (this.
|
|
924
|
+
if (this.actionLogOpened.extensionMessageBus === "ui") {
|
|
925
|
+
await sendToBackground("worker.changeNodeUrlAndStartHeight", {
|
|
926
|
+
node_url,
|
|
927
|
+
start_height,
|
|
928
|
+
}).catch(() => { });
|
|
743
929
|
return;
|
|
744
|
-
|
|
745
|
-
|
|
930
|
+
}
|
|
931
|
+
if (this.wallets.length === 0) {
|
|
932
|
+
if (node_url !== undefined)
|
|
933
|
+
await this._scanSettings.setNodeUrl(node_url);
|
|
934
|
+
if (start_height !== undefined)
|
|
935
|
+
await this._scanSettings.setStartHeight(start_height);
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
await this.wallets[0].changeNodeUrlAndStartHeight(node_url, start_height);
|
|
746
939
|
}
|
|
747
940
|
async retry() {
|
|
748
941
|
if (this.wallets.length === 0)
|
|
@@ -751,6 +944,10 @@ export class ManyScanCachesOpened {
|
|
|
751
944
|
return await masterWallet.retry();
|
|
752
945
|
}
|
|
753
946
|
async stopWorker() {
|
|
947
|
+
if (this.actionLogOpened.extensionMessageBus === "ui") {
|
|
948
|
+
await sendToBackground("worker.stopWorker", null).catch(() => { });
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
754
951
|
if (this.wallets.length === 0)
|
|
755
952
|
return;
|
|
756
953
|
const masterWallet = this.wallets[0];
|
|
@@ -764,10 +961,49 @@ export class ManyScanCachesOpened {
|
|
|
764
961
|
return await this.wallets[0].dumpWorkerHeaps(dir);
|
|
765
962
|
}
|
|
766
963
|
async changeNodeUrl(node_url) {
|
|
767
|
-
if (this.
|
|
964
|
+
if (this.actionLogOpened.extensionMessageBus === "ui") {
|
|
965
|
+
await sendToBackground("worker.setNodeUrl", { node_url }).catch(() => { });
|
|
768
966
|
return;
|
|
769
|
-
|
|
770
|
-
|
|
967
|
+
}
|
|
968
|
+
if (this.wallets.length === 0) {
|
|
969
|
+
await this._scanSettings.setNodeUrl(node_url);
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
await this.wallets[0].changeNodeUrl(node_url);
|
|
973
|
+
}
|
|
974
|
+
walletByAddress(primary_address) {
|
|
975
|
+
const wallet = this.wallets.find((row) => row.primary_address === primary_address);
|
|
976
|
+
if (!wallet)
|
|
977
|
+
throw new Error("wallet not found");
|
|
978
|
+
return wallet;
|
|
979
|
+
}
|
|
980
|
+
async makeSignSend(params) {
|
|
981
|
+
if (this.actionLogOpened.extensionMessageBus === "ui") {
|
|
982
|
+
await sendToBackground("worker.makeSignSend", params).catch(() => { });
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
return this.walletByAddress(params.primary_address).makeSignSend(params);
|
|
986
|
+
}
|
|
987
|
+
async sweepSignSend(params) {
|
|
988
|
+
if (this.actionLogOpened.extensionMessageBus === "ui") {
|
|
989
|
+
await sendToBackground("worker.sweepSignSend", params).catch(() => { });
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
return this.walletByAddress(params.primary_address).sweepSignSend(params);
|
|
993
|
+
}
|
|
994
|
+
async rebroadcastTx(params) {
|
|
995
|
+
if (this.actionLogOpened.extensionMessageBus === "ui") {
|
|
996
|
+
await sendToBackground("worker.rebroadcastTx", params).catch(() => { });
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
return this.walletByAddress(params.primary_address).rebroadcastTx(params.tx_log_index);
|
|
1000
|
+
}
|
|
1001
|
+
async dismissSendPlate(params) {
|
|
1002
|
+
if (this.actionLogOpened.extensionMessageBus === "ui") {
|
|
1003
|
+
await sendToBackground("worker.dismissSendPlate", params).catch(() => { });
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
return this.walletByAddress(params.primary_address).dismissSendPlate();
|
|
771
1007
|
}
|
|
772
1008
|
get merchant_confirmations() {
|
|
773
1009
|
if (this.wallets.length === 0)
|
|
@@ -811,7 +1047,20 @@ export class ManyScanCachesOpened {
|
|
|
811
1047
|
return await this.wallets[0].setCpuWorkerCount(cpu_worker_count);
|
|
812
1048
|
}
|
|
813
1049
|
async setLogSettings(logs, logs_include, logs_exclude) {
|
|
814
|
-
|
|
1050
|
+
if (this.actionLogOpened.extensionMessageBus === "ui") {
|
|
1051
|
+
await sendToBackground("worker.setLogSettings", {
|
|
1052
|
+
logs,
|
|
1053
|
+
logs_include,
|
|
1054
|
+
logs_exclude,
|
|
1055
|
+
}).catch(() => { });
|
|
1056
|
+
}
|
|
1057
|
+
await this._scanSettings.setLogSettings(logs, logs_include, logs_exclude);
|
|
1058
|
+
if (this.actionLogOpened.extensionMessageBus === "ui")
|
|
1059
|
+
return;
|
|
1060
|
+
for (const wallet of this.wallets)
|
|
1061
|
+
await wallet.stopWorker();
|
|
1062
|
+
for (const wallet of this.wallets)
|
|
1063
|
+
await wallet.unpause();
|
|
815
1064
|
}
|
|
816
1065
|
async setWalletName(primary_address, name) {
|
|
817
1066
|
await this._scanSettings.setWalletName(primary_address, name);
|
|
@@ -820,10 +1069,17 @@ export class ManyScanCachesOpened {
|
|
|
820
1069
|
await this._scanSettings.setWalletSlot(primary_address, slot);
|
|
821
1070
|
}
|
|
822
1071
|
async changeStartHeight(start_height) {
|
|
823
|
-
if (this.
|
|
1072
|
+
if (this.actionLogOpened.extensionMessageBus === "ui") {
|
|
1073
|
+
await sendToBackground("worker.setStartHeight", {
|
|
1074
|
+
start_height,
|
|
1075
|
+
}).catch(() => { });
|
|
824
1076
|
return;
|
|
825
|
-
|
|
826
|
-
|
|
1077
|
+
}
|
|
1078
|
+
if (this.wallets.length === 0) {
|
|
1079
|
+
await this._scanSettings.setStartHeight(start_height);
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
await this.wallets[0].changeStartHeight(start_height);
|
|
827
1083
|
}
|
|
828
1084
|
static async _buildWallets(scanSettingsOpened, options) {
|
|
829
1085
|
const { scan_settings_path, pathPrefix, no_worker, no_stats, workerError, notifyMasterChanged, } = options;
|
|
@@ -894,34 +1150,36 @@ export class ManyScanCachesOpened {
|
|
|
894
1150
|
// wrap workerError with auto-retry
|
|
895
1151
|
let retryTimer;
|
|
896
1152
|
let instance = null;
|
|
1153
|
+
const clearRetryTimer = () => {
|
|
1154
|
+
clearTimeout(retryTimer);
|
|
1155
|
+
retryTimer = undefined;
|
|
1156
|
+
};
|
|
897
1157
|
const retryFn = async () => {
|
|
1158
|
+
clearRetryTimer();
|
|
898
1159
|
// 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;
|
|
1160
|
+
if (!instance || instance.wallets.length === 0)
|
|
909
1161
|
return;
|
|
910
|
-
}
|
|
911
1162
|
const node_url = instance.node_url;
|
|
912
1163
|
if (!node_url)
|
|
913
|
-
|
|
1164
|
+
return;
|
|
1165
|
+
// probe first; only restart workers when node is reachable
|
|
914
1166
|
const getinfo_result = await get_info(node_url)
|
|
915
|
-
.then((r) =>
|
|
916
|
-
r?.status === "OK";
|
|
917
|
-
})
|
|
1167
|
+
.then((r) => r?.status === "OK")
|
|
918
1168
|
.catch(() => false);
|
|
919
|
-
if (getinfo_result)
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1169
|
+
if (!getinfo_result) {
|
|
1170
|
+
// still down: try again later without buildWallets thrash
|
|
1171
|
+
if (!retryTimer && instance.wallets.length > 0) {
|
|
1172
|
+
retryTimer = setTimeout(retryFn, retryDelayMs ?? 5000);
|
|
1173
|
+
}
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
await instance.buildWallets();
|
|
923
1177
|
};
|
|
924
1178
|
const newOptions = { ...options };
|
|
1179
|
+
// browser worker (background.ts): broadcast cache to sidebar
|
|
1180
|
+
if (!newOptions.notifyMasterChanged && !newOptions.no_worker) {
|
|
1181
|
+
newOptions.notifyMasterChanged = defaultNotifyMasterChanged;
|
|
1182
|
+
}
|
|
925
1183
|
let connectionFailedShown = false;
|
|
926
1184
|
if (autoRetry) {
|
|
927
1185
|
const originalError = options.workerError;
|
|
@@ -930,7 +1188,7 @@ export class ManyScanCachesOpened {
|
|
|
930
1188
|
const msg = err instanceof Error ? err.message : String(err);
|
|
931
1189
|
if (csOpened.connectionStatus?.last_packet?.status ===
|
|
932
1190
|
"catastrophic_reorg") {
|
|
933
|
-
|
|
1191
|
+
clearRetryTimer();
|
|
934
1192
|
instance?.stopWorker();
|
|
935
1193
|
throw new Error("catastrophic reorg, aborting ...");
|
|
936
1194
|
}
|
|
@@ -960,10 +1218,22 @@ export class ManyScanCachesOpened {
|
|
|
960
1218
|
}
|
|
961
1219
|
: (onConnectionStatusChange ?? null));
|
|
962
1220
|
csOpened.watch(connectionStatusIntervalMs);
|
|
963
|
-
|
|
1221
|
+
const actionLogPath = newOptions.actionLogPath ??
|
|
1222
|
+
actionLogPathFromScanSettings(scan_settings_path || SCAN_SETTINGS_STORE_NAME_DEFAULT);
|
|
1223
|
+
const actionLogOpened = await ActionLogOpened.create({
|
|
1224
|
+
path: actionLogPath,
|
|
1225
|
+
backend: newOptions.actionLogBackend,
|
|
1226
|
+
onChange: newOptions.onActionLogChange ?? null,
|
|
1227
|
+
extensionMessageBus: newOptions.extensionMessageBus,
|
|
1228
|
+
});
|
|
1229
|
+
instance = new ManyScanCachesOpened(wallets, csOpened, actionLogOpened, scanSettingsOpened, newOptions);
|
|
964
1230
|
return instance;
|
|
965
1231
|
}
|
|
966
1232
|
async buildWallets() {
|
|
1233
|
+
if (this.actionLogOpened.extensionMessageBus === "ui") {
|
|
1234
|
+
await sendToBackground("worker.buildWallets", null).catch(() => { });
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
967
1237
|
await this.stopWorker();
|
|
968
1238
|
await this.reloadWalletsAfterStop();
|
|
969
1239
|
}
|
|
@@ -1003,10 +1273,18 @@ export class ManyScanCachesOpened {
|
|
|
1003
1273
|
get wallets() {
|
|
1004
1274
|
return this._wallets;
|
|
1005
1275
|
}
|
|
1006
|
-
constructor(wallets, connectionStatusOpened, _scanSettings, _options) {
|
|
1276
|
+
constructor(wallets, connectionStatusOpened, actionLogOpened, _scanSettings, _options) {
|
|
1007
1277
|
this.connectionStatusOpened = connectionStatusOpened;
|
|
1278
|
+
this.actionLogOpened = actionLogOpened;
|
|
1008
1279
|
this._scanSettings = _scanSettings;
|
|
1009
1280
|
this._options = _options;
|
|
1010
1281
|
this._wallets = wallets;
|
|
1282
|
+
this.actionLogOpened.bindMco(this);
|
|
1011
1283
|
}
|
|
1012
1284
|
}
|
|
1285
|
+
function actionLogPathFromScanSettings(scan_settings_path) {
|
|
1286
|
+
const base = scan_settings_path.replace(/\.json$/i, "");
|
|
1287
|
+
if (globalThis.areWeInTheBrowser === true)
|
|
1288
|
+
return "actionlog";
|
|
1289
|
+
return `${base}-actionlog.sqlite`;
|
|
1290
|
+
}
|
|
@@ -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
|
};
|
|
@@ -2,7 +2,7 @@ import { setupBlocksBufferGenerator, ViewPair, handleConnectionStatusChanges, pr
|
|
|
2
2
|
import {} from "./scanLoop";
|
|
3
3
|
import { makeWorkItem } from "./scanLoop";
|
|
4
4
|
import { cullTooLargeScanHeight, getNonHaltedWallets, getPathPrefix, openScanSettingsFile, SCAN_SETTINGS_STORE_NAME_DEFAULT, walletSettingsPlusKeys, } from "../../api";
|
|
5
|
-
import { findRange, initScanCacheFile, lastRange, makeCacheRangeForHeight, mergeRanges, } from "./scanCache";
|
|
5
|
+
import { currentScanHeightFromRanges, findRange, initScanCacheFile, lastRange, makeCacheRangeForHeight, mergeRanges, } from "./scanCache";
|
|
6
6
|
import { sendToCpuWorker } from "../worker-mains/cpubound-main";
|
|
7
7
|
import { log } from "../../io/logging";
|
|
8
8
|
/**
|
|
@@ -79,6 +79,19 @@ export async function findWorkToBeDone(scan_settings_path = SCAN_SETTINGS_STORE_
|
|
|
79
79
|
scan_settings,
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
|
+
// slowest wallet past this batch: return its range so fetch can restart at that tip.
|
|
83
|
+
export function fastForwardFetch(wallet_configs, start_height, batchEnd) {
|
|
84
|
+
if (!wallet_configs.length)
|
|
85
|
+
return null;
|
|
86
|
+
const slowest = Math.min(...wallet_configs.map((w) => currentScanHeightFromRanges(w.cache.scanned_ranges, start_height)));
|
|
87
|
+
if (slowest <= batchEnd)
|
|
88
|
+
return null;
|
|
89
|
+
const owner = wallet_configs.find((w) => currentScanHeightFromRanges(w.cache.scanned_ranges, start_height) ===
|
|
90
|
+
slowest);
|
|
91
|
+
return owner
|
|
92
|
+
? findRange(owner.cache.scanned_ranges, start_height)
|
|
93
|
+
: null;
|
|
94
|
+
}
|
|
82
95
|
export function workToBeDoneForBatch(cache, batch_meta_infos) {
|
|
83
96
|
const begin_height = batch_meta_infos[0].block_height;
|
|
84
97
|
const end_height = batch_meta_infos[batch_meta_infos.length - 1].block_height;
|
|
@@ -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,
|
|
@@ -39,7 +39,7 @@ export async function processScanResult(params) {
|
|
|
39
39
|
reverted_spends: [],
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
|
-
else {
|
|
42
|
+
else if (!cache.reorg_info.split_heights.some((s) => s.block_hash === split_height.block_hash)) {
|
|
43
43
|
cache.reorg_info.split_heights.push(split_height);
|
|
44
44
|
}
|
|
45
45
|
// First, collect reverted spends from all outputs (before any removals)
|
|
@@ -56,11 +56,14 @@ export async function processScanResult(params) {
|
|
|
56
56
|
for (const [id, old_output_state] of removed_outputs) {
|
|
57
57
|
// 1. find key_image of output to be removed (as it was reorged)
|
|
58
58
|
const [key_image] = Object.entries(cache.own_key_images).find(([own_key_image, globalid]) => globalid === id) || [""]; // if this is viewonly the key_image will be empty
|
|
59
|
-
cache.reorg_info.removed_outputs.
|
|
60
|
-
old_output_state
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
if (!cache.reorg_info.removed_outputs.some((r) => r.old_output_state.index_on_blockchain ===
|
|
60
|
+
old_output_state.index_on_blockchain)) {
|
|
61
|
+
cache.reorg_info.removed_outputs.push({
|
|
62
|
+
old_output_state,
|
|
63
|
+
key_image,
|
|
64
|
+
split_height,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
64
67
|
// 2. remove from outputs and own_key_images
|
|
65
68
|
delete cache.outputs[id];
|
|
66
69
|
delete cache.own_key_images[key_image];
|
|
@@ -72,11 +75,14 @@ export async function processScanResult(params) {
|
|
|
72
75
|
for (const [id, old_output_state_pointer] of reverted_outputs) {
|
|
73
76
|
const [key_image] = Object.entries(cache.own_key_images).find(([own_key_image, globalid]) => globalid === id) || [""]; // if this is viewonly the key_image will be empty
|
|
74
77
|
const old_output_state = Object.assign({}, old_output_state_pointer);
|
|
75
|
-
cache.reorg_info.reverted_spends.
|
|
76
|
-
old_output_state
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
if (!cache.reorg_info.reverted_spends.some((r) => r.old_output_state.index_on_blockchain ===
|
|
79
|
+
old_output_state.index_on_blockchain)) {
|
|
80
|
+
cache.reorg_info.reverted_spends.push({
|
|
81
|
+
old_output_state,
|
|
82
|
+
key_image, // in this case key_image only used here, does not get removed
|
|
83
|
+
split_height,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
80
86
|
// remove spend info from original cache (if output still exists
|
|
81
87
|
// it may have been deleted above in the removed_outputs loop)
|
|
82
88
|
if (cache.outputs[id]) {
|
|
@@ -226,7 +232,6 @@ export function outputStatus(output, cache, current_height) {
|
|
|
226
232
|
if (unlock_height > current_height) {
|
|
227
233
|
return { status: "pending", unlock_height };
|
|
228
234
|
}
|
|
229
|
-
// TODO: check that that output is not txlog input indeces
|
|
230
235
|
return { status: "spendable" };
|
|
231
236
|
}
|
|
232
237
|
export function spendable(output, cache, current_height) {
|
|
@@ -120,16 +120,19 @@ export function processFoundTransactions(cache, stats, current_height) {
|
|
|
120
120
|
}
|
|
121
121
|
// handle spent case
|
|
122
122
|
if (output.spent_in_tx_hash) {
|
|
123
|
-
const
|
|
124
|
-
? cache.pending_spent_utxos[output.index_on_blockchain]
|
|
125
|
-
:
|
|
126
|
-
|
|
127
|
-
|
|
123
|
+
const spentIndex = cache.pending_spent_utxos
|
|
124
|
+
? cache.pending_spent_utxos[String(output.index_on_blockchain)]
|
|
125
|
+
: undefined;
|
|
126
|
+
// index 0 is a real log. do not treat it as missing.
|
|
127
|
+
const txlog = cache.tx_logs && spentIndex !== undefined
|
|
128
|
+
? cache.tx_logs[spentIndex]
|
|
128
129
|
: undefined;
|
|
129
130
|
const spentTx = stats.found_transactions[output.spent_in_tx_hash];
|
|
130
131
|
if (spentTx) {
|
|
131
132
|
spentTx.amount -= output.amount;
|
|
132
133
|
spentTx.inputs.push(output);
|
|
134
|
+
if (!spentTx.txlog && txlog)
|
|
135
|
+
spentTx.txlog = txlog;
|
|
133
136
|
}
|
|
134
137
|
else {
|
|
135
138
|
stats.found_transactions[output.spent_in_tx_hash] = {
|