@spirobel/monero-wallet-api 0.6.2 → 0.6.7
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.js +7 -2
- package/dist/scanning-syncing/scanresult/scanCacheOpened.d.ts +3 -1
- package/dist/scanning-syncing/scanresult/scanCacheOpened.js +161 -118
- package/dist/scanning-syncing/scanresult/scanResult.js +3 -1
- package/dist/scanning-syncing/scanresult/scanStats.d.ts +1 -0
- package/dist/scanning-syncing/scanresult/scanStats.js +20 -4
- package/dist/scansettings/scanSettingsOpened.d.ts +1 -0
- package/dist/scansettings/scanSettingsOpened.js +15 -8
- package/package.json +1 -1
|
@@ -245,8 +245,13 @@ export class ActionLogOpened {
|
|
|
245
245
|
}
|
|
246
246
|
case "worker.changeNodeUrlAndStartHeight": {
|
|
247
247
|
const p = env.payload;
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
try {
|
|
249
|
+
await this.mco?.changeNodeUrlAndStartHeight(p?.node_url, p?.start_height);
|
|
250
|
+
return {};
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
254
|
+
}
|
|
250
255
|
}
|
|
251
256
|
case "worker.buildWallets":
|
|
252
257
|
await this.mco?.buildWallets();
|
|
@@ -102,6 +102,9 @@ export declare class ScanCacheOpened {
|
|
|
102
102
|
get pending_amount(): bigint;
|
|
103
103
|
get subaddresses(): Subaddress[];
|
|
104
104
|
get tx_logs(): TxLog[];
|
|
105
|
+
private writeTxCache;
|
|
106
|
+
private beginInFlightSend;
|
|
107
|
+
private finishInFlightSend;
|
|
105
108
|
makeSignSendTransaction(params: CreateTransactionParams): Promise<SendRawTransactionResult>;
|
|
106
109
|
private outputsForIndexes;
|
|
107
110
|
makeSignSend(params: {
|
|
@@ -118,7 +121,6 @@ export declare class ScanCacheOpened {
|
|
|
118
121
|
txlog: TxLog;
|
|
119
122
|
inputs: Output[];
|
|
120
123
|
}[];
|
|
121
|
-
private recordSend;
|
|
122
124
|
dismissSendPlate(): Promise<void>;
|
|
123
125
|
/**
|
|
124
126
|
* makeStandardTransaction
|
|
@@ -8,7 +8,7 @@ import { ConnectionStatusOpened } from "../connectionStatusOpened";
|
|
|
8
8
|
import { ActionLogOpened, } from "../../actionlog/actionLogOpened";
|
|
9
9
|
import { sendToBackground } from "../../tools/globals";
|
|
10
10
|
import { findRange, lastRange, readCacheFileDefaultLocation, writeCacheFileDefaultLocationThrows, } from "./scanCache";
|
|
11
|
-
import { alignScanStatsWithCache, isSelfSpent, processTxlogInputs, processTxlogPayments, writeStatsFileDefaultLocation, } from "./scanStats";
|
|
11
|
+
import { alignScanStatsWithCache, isSelfSpent, processTxlogInputs, processTxlogPayments, txLogHoldsFunds, writeStatsFileDefaultLocation, } from "./scanStats";
|
|
12
12
|
import { readWriteConnectionStatusFile, } from "../connectionStatus";
|
|
13
13
|
export class ScanCacheOpened {
|
|
14
14
|
_scanSettings;
|
|
@@ -109,9 +109,7 @@ export class ScanCacheOpened {
|
|
|
109
109
|
get prepending_txs() {
|
|
110
110
|
const txs = [];
|
|
111
111
|
for (const txlog of this._cache.tx_logs || []) {
|
|
112
|
-
if (!txlog ||
|
|
113
|
-
!txlog.sendResult ||
|
|
114
|
-
(txlog.sendResult && txlog.sendResult.status !== "OK"))
|
|
112
|
+
if (!txlog || !txLogHoldsFunds(txlog))
|
|
115
113
|
continue;
|
|
116
114
|
const { inputSum, alreadyRecognizedAsSpend } = processTxlogInputs(txlog, this._cache);
|
|
117
115
|
if (alreadyRecognizedAsSpend)
|
|
@@ -173,14 +171,19 @@ export class ScanCacheOpened {
|
|
|
173
171
|
return this._scanSettings.logs_exclude;
|
|
174
172
|
}
|
|
175
173
|
async changeNodeUrlAndStartHeight(node_url, start_height) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
await this.
|
|
179
|
-
this.node_url = node_url;
|
|
174
|
+
// stop first so a live worker cannot write the old file over the new url.
|
|
175
|
+
try {
|
|
176
|
+
await this.stopWorker();
|
|
180
177
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
178
|
+
catch {
|
|
179
|
+
// stop can fail. still write the new url.
|
|
180
|
+
}
|
|
181
|
+
if (node_url !== undefined || start_height !== undefined) {
|
|
182
|
+
await this._scanSettings.setNodeUrlAndStartHeight(node_url, start_height);
|
|
183
|
+
if (node_url !== undefined)
|
|
184
|
+
this.node_url = node_url;
|
|
185
|
+
if (start_height !== undefined)
|
|
186
|
+
this._start_height = start_height;
|
|
184
187
|
}
|
|
185
188
|
await this.unpause();
|
|
186
189
|
}
|
|
@@ -387,15 +390,91 @@ export class ScanCacheOpened {
|
|
|
387
390
|
get tx_logs() {
|
|
388
391
|
return this._cache.tx_logs || [];
|
|
389
392
|
}
|
|
393
|
+
async writeTxCache(writeCallback, changed_outputs) {
|
|
394
|
+
await writeCacheFileDefaultLocationThrows({
|
|
395
|
+
primary_address: this.primary_address,
|
|
396
|
+
pathPrefix: this.pathPrefix,
|
|
397
|
+
writeCallback,
|
|
398
|
+
});
|
|
399
|
+
const newCache = await readCacheFileDefaultLocation(this.primary_address, this.pathPrefix);
|
|
400
|
+
if (!newCache)
|
|
401
|
+
throw new Error(`cache not found for primary address: ${this.primary_address}, and path prefix: ${this.pathPrefix}`);
|
|
402
|
+
await this.feed({ newCache, changed_outputs });
|
|
403
|
+
}
|
|
404
|
+
// stop the worker, lock the inputs, then let stats show pending.
|
|
405
|
+
async beginInFlightSend(args) {
|
|
406
|
+
await this.stopWorker();
|
|
407
|
+
const inputs_index = args.inputs.map((input) => String(input.index_on_blockchain));
|
|
408
|
+
let txLogIndex = 0;
|
|
409
|
+
try {
|
|
410
|
+
await this.writeTxCache(async (cache) => {
|
|
411
|
+
if (!cache.tx_logs)
|
|
412
|
+
cache.tx_logs = [];
|
|
413
|
+
if (!cache.pending_spent_utxos)
|
|
414
|
+
cache.pending_spent_utxos = {};
|
|
415
|
+
const txLog = {
|
|
416
|
+
feeEstimate: args.feeEstimate,
|
|
417
|
+
payments: args.payments,
|
|
418
|
+
node_url: this.node_url,
|
|
419
|
+
inputs_index,
|
|
420
|
+
height: this.current_height,
|
|
421
|
+
timestamp: Date.now(),
|
|
422
|
+
invocationId: args.invocationId,
|
|
423
|
+
};
|
|
424
|
+
txLogIndex = cache.tx_logs.push(txLog) - 1;
|
|
425
|
+
for (const inputId of inputs_index)
|
|
426
|
+
cache.pending_spent_utxos[inputId] = txLogIndex;
|
|
427
|
+
}, args.inputs.map((output) => ({ change_reason: "spent", output })));
|
|
428
|
+
}
|
|
429
|
+
catch (e) {
|
|
430
|
+
await this.unpause();
|
|
431
|
+
throw e;
|
|
432
|
+
}
|
|
433
|
+
return txLogIndex;
|
|
434
|
+
}
|
|
435
|
+
// update the same row with the node answer. unlock the inputs on failure.
|
|
436
|
+
async finishInFlightSend(args) {
|
|
437
|
+
const ok = args.sendResult?.status === "OK" && args.error === undefined;
|
|
438
|
+
try {
|
|
439
|
+
await this.writeTxCache(async (cache) => {
|
|
440
|
+
const row = cache.tx_logs?.[args.txLogIndex];
|
|
441
|
+
if (!row)
|
|
442
|
+
throw new Error("tx log missing");
|
|
443
|
+
if (args.invocationId)
|
|
444
|
+
row.invocationId = args.invocationId;
|
|
445
|
+
if (args.signedTx)
|
|
446
|
+
row.signed_tx = args.signedTx;
|
|
447
|
+
if (args.sendResult)
|
|
448
|
+
row.sendResult = args.sendResult;
|
|
449
|
+
if (!ok) {
|
|
450
|
+
row.error = args.error || args.sendResult?.reason || "send failed";
|
|
451
|
+
for (const inputId of row.inputs_index) {
|
|
452
|
+
if (cache.pending_spent_utxos?.[inputId] === args.txLogIndex)
|
|
453
|
+
delete cache.pending_spent_utxos[inputId];
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}, []);
|
|
457
|
+
}
|
|
458
|
+
finally {
|
|
459
|
+
await this.unpause();
|
|
460
|
+
}
|
|
461
|
+
}
|
|
390
462
|
async makeSignSendTransaction(params) {
|
|
391
463
|
let maybeInputs = [];
|
|
392
464
|
let maybeFeeEstimate;
|
|
393
465
|
let maybeSendResult;
|
|
394
466
|
let maybeSignedTx;
|
|
467
|
+
let txLogIndex;
|
|
395
468
|
try {
|
|
396
469
|
const { selectedInputs, feeEstimate } = await this.calculateFeeAndSelectInputs(params);
|
|
397
470
|
maybeInputs = selectedInputs;
|
|
398
471
|
maybeFeeEstimate = feeEstimate;
|
|
472
|
+
txLogIndex = await this.beginInFlightSend({
|
|
473
|
+
payments: params.payments,
|
|
474
|
+
inputs: selectedInputs,
|
|
475
|
+
feeEstimate,
|
|
476
|
+
invocationId: params.invocationId,
|
|
477
|
+
});
|
|
399
478
|
const unsignedTx = await this.makeTransactionFromSelectedInputs(params.payments, selectedInputs, feeEstimate);
|
|
400
479
|
const signedTx = await this.signTransaction(unsignedTx);
|
|
401
480
|
maybeSignedTx = signedTx;
|
|
@@ -403,52 +482,29 @@ export class ScanCacheOpened {
|
|
|
403
482
|
maybeSendResult = sendResult;
|
|
404
483
|
if (sendResult.status !== "OK")
|
|
405
484
|
throw new Error("send raw transaction rpc returned error");
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
pathPrefix: this.pathPrefix,
|
|
412
|
-
writeCallback: async (cache) => {
|
|
413
|
-
if (!cache.tx_logs)
|
|
414
|
-
cache.tx_logs = [];
|
|
415
|
-
if (!cache.pending_spent_utxos)
|
|
416
|
-
cache.pending_spent_utxos = {};
|
|
417
|
-
const inputs_index = selectedInputs.map((input) => String(input.index_on_blockchain));
|
|
418
|
-
const txLog = {
|
|
419
|
-
sendResult,
|
|
420
|
-
feeEstimate,
|
|
421
|
-
payments: params.payments,
|
|
422
|
-
node_url: this.node_url,
|
|
423
|
-
inputs_index,
|
|
424
|
-
height: this.current_height,
|
|
425
|
-
timestamp: Date.now(),
|
|
426
|
-
invocationId: params.invocationId,
|
|
427
|
-
signed_tx: signedTx,
|
|
428
|
-
};
|
|
429
|
-
const newLen = cache.tx_logs.push(txLog);
|
|
430
|
-
const txLogIndex = newLen - 1;
|
|
431
|
-
for (const inputId of inputs_index) {
|
|
432
|
-
cache.pending_spent_utxos[inputId] = txLogIndex;
|
|
433
|
-
}
|
|
434
|
-
},
|
|
435
|
-
});
|
|
436
|
-
const newCache = await readCacheFileDefaultLocation(this.primary_address, this.pathPrefix);
|
|
437
|
-
if (!newCache)
|
|
438
|
-
throw new Error(`cache not found for primary address: ${this.primary_address}, and path prefix: ${this.pathPrefix}`);
|
|
439
|
-
const changed_outputs = selectedInputs.map((input) => ({
|
|
440
|
-
change_reason: "spent",
|
|
441
|
-
output: input,
|
|
442
|
-
}));
|
|
443
|
-
await this.feed({
|
|
444
|
-
newCache,
|
|
445
|
-
changed_outputs,
|
|
485
|
+
await this.finishInFlightSend({
|
|
486
|
+
txLogIndex,
|
|
487
|
+
sendResult,
|
|
488
|
+
signedTx,
|
|
489
|
+
invocationId: params.invocationId,
|
|
446
490
|
});
|
|
447
|
-
// restart the worker
|
|
448
|
-
await this.unpause();
|
|
449
491
|
return sendResult;
|
|
450
492
|
}
|
|
451
493
|
catch (e) {
|
|
494
|
+
if (txLogIndex !== undefined && maybeSendResult?.status !== "OK") {
|
|
495
|
+
await this.finishInFlightSend({
|
|
496
|
+
txLogIndex,
|
|
497
|
+
sendResult: maybeSendResult,
|
|
498
|
+
signedTx: maybeSignedTx,
|
|
499
|
+
error: String(e || "unknown error"),
|
|
500
|
+
invocationId: params.invocationId,
|
|
501
|
+
});
|
|
502
|
+
throw e;
|
|
503
|
+
}
|
|
504
|
+
if (txLogIndex !== undefined) {
|
|
505
|
+
await this.unpause();
|
|
506
|
+
throw e;
|
|
507
|
+
}
|
|
452
508
|
// before writing the scan cache, we stop the worker to avoid a race
|
|
453
509
|
await this.stopWorker();
|
|
454
510
|
// write txlog error to cache
|
|
@@ -524,31 +580,41 @@ export class ScanCacheOpened {
|
|
|
524
580
|
if (!inputs.length)
|
|
525
581
|
throw new Error("no inputs selected");
|
|
526
582
|
const feeEstimate = await this.getFeeEstimate();
|
|
527
|
-
const unsignedTx = await this.makeSweepTransactionFromSelectedInputs(params.address, inputs, feeEstimate);
|
|
528
|
-
const signedTx = await this.signTransaction(unsignedTx);
|
|
529
583
|
const payments = [{ address: params.address, amount: "0" }];
|
|
530
|
-
const
|
|
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({
|
|
584
|
+
const txLogIndex = await this.beginInFlightSend({
|
|
544
585
|
payments,
|
|
545
586
|
inputs,
|
|
546
|
-
signedTx,
|
|
547
587
|
feeEstimate,
|
|
548
|
-
sendResult,
|
|
549
|
-
lockInputs: true,
|
|
550
588
|
});
|
|
551
|
-
|
|
589
|
+
let sendResult;
|
|
590
|
+
let signedTx;
|
|
591
|
+
try {
|
|
592
|
+
const unsignedTx = await this.makeSweepTransactionFromSelectedInputs(params.address, inputs, feeEstimate);
|
|
593
|
+
signedTx = await this.signTransaction(unsignedTx);
|
|
594
|
+
sendResult = await this.sendTransaction(signedTx);
|
|
595
|
+
if (sendResult.status !== "OK")
|
|
596
|
+
throw new Error("send raw transaction rpc returned error");
|
|
597
|
+
await this.finishInFlightSend({
|
|
598
|
+
txLogIndex,
|
|
599
|
+
sendResult,
|
|
600
|
+
signedTx,
|
|
601
|
+
});
|
|
602
|
+
return sendResult;
|
|
603
|
+
}
|
|
604
|
+
catch (e) {
|
|
605
|
+
if (sendResult?.status !== "OK") {
|
|
606
|
+
await this.finishInFlightSend({
|
|
607
|
+
txLogIndex,
|
|
608
|
+
sendResult,
|
|
609
|
+
signedTx,
|
|
610
|
+
error: e instanceof Error ? e.message : String(e),
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
await this.unpause();
|
|
615
|
+
}
|
|
616
|
+
throw e;
|
|
617
|
+
}
|
|
552
618
|
}
|
|
553
619
|
async rebroadcastTx(txLogIndex) {
|
|
554
620
|
const txlog = this._cache.tx_logs?.[txLogIndex];
|
|
@@ -598,6 +664,9 @@ export class ScanCacheOpened {
|
|
|
598
664
|
continue;
|
|
599
665
|
if (txlog.sendResult?.status === "OK")
|
|
600
666
|
continue;
|
|
667
|
+
// no node answer yet. this row is still in flight.
|
|
668
|
+
if (!txlog.sendResult && !txlog.error)
|
|
669
|
+
continue;
|
|
601
670
|
const inputs = txlog.inputs_index
|
|
602
671
|
.map((id) => this._cache.outputs[id])
|
|
603
672
|
.filter((output) => output);
|
|
@@ -608,44 +677,6 @@ export class ScanCacheOpened {
|
|
|
608
677
|
}
|
|
609
678
|
return rows;
|
|
610
679
|
}
|
|
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
680
|
async dismissSendPlate() {
|
|
650
681
|
await this.stopWorker();
|
|
651
682
|
await writeCacheFileDefaultLocationThrows({
|
|
@@ -751,7 +782,11 @@ export class ScanCacheOpened {
|
|
|
751
782
|
delete this.worker;
|
|
752
783
|
// drain coordinator first, then hard kill workers
|
|
753
784
|
try {
|
|
754
|
-
|
|
785
|
+
// shutdown already races 5s. this race still returns if that promise never settles.
|
|
786
|
+
await Promise.race([
|
|
787
|
+
worker.shutdown(),
|
|
788
|
+
new Promise((resolve) => setTimeout(resolve, 5000)),
|
|
789
|
+
]);
|
|
755
790
|
}
|
|
756
791
|
catch {
|
|
757
792
|
// still terminate below
|
|
@@ -925,17 +960,25 @@ export class ManyScanCachesOpened {
|
|
|
925
960
|
}
|
|
926
961
|
async changeNodeUrlAndStartHeight(node_url, start_height) {
|
|
927
962
|
if (this.actionLogOpened.extensionMessageBus === "ui") {
|
|
928
|
-
|
|
963
|
+
// the background owns the worker. it stops, then writes. do not write here first.
|
|
964
|
+
const result = await sendToBackground("worker.changeNodeUrlAndStartHeight", {
|
|
929
965
|
node_url,
|
|
930
966
|
start_height,
|
|
931
|
-
})
|
|
967
|
+
});
|
|
968
|
+
if (result &&
|
|
969
|
+
typeof result === "object" &&
|
|
970
|
+
"error" in result &&
|
|
971
|
+
result.error) {
|
|
972
|
+
throw new Error(String(result.error));
|
|
973
|
+
}
|
|
974
|
+
if (result == null)
|
|
975
|
+
throw new Error("background did not save the node url");
|
|
932
976
|
return;
|
|
933
977
|
}
|
|
934
978
|
if (this.wallets.length === 0) {
|
|
935
|
-
if (node_url !== undefined)
|
|
936
|
-
await this._scanSettings.
|
|
937
|
-
|
|
938
|
-
await this._scanSettings.setStartHeight(start_height);
|
|
979
|
+
if (node_url !== undefined || start_height !== undefined) {
|
|
980
|
+
await this._scanSettings.setNodeUrlAndStartHeight(node_url, start_height);
|
|
981
|
+
}
|
|
939
982
|
return;
|
|
940
983
|
}
|
|
941
984
|
await this.wallets[0].changeNodeUrlAndStartHeight(node_url, start_height);
|
|
@@ -218,8 +218,10 @@ export function unlockedAtHeight(output) {
|
|
|
218
218
|
export function outputStatus(output, cache, current_height) {
|
|
219
219
|
if (
|
|
220
220
|
// order matters, check this before "spent" check + "pending" check
|
|
221
|
+
// tx log index 0 is a real lock. 0 must not count as missing.
|
|
221
222
|
cache.pending_spent_utxos &&
|
|
222
|
-
cache.pending_spent_utxos[output.index_on_blockchain.toString()]
|
|
223
|
+
cache.pending_spent_utxos[output.index_on_blockchain.toString()] !==
|
|
224
|
+
undefined) {
|
|
223
225
|
return { status: "prepending" };
|
|
224
226
|
}
|
|
225
227
|
if (typeof output.burned === "number") {
|
|
@@ -58,6 +58,7 @@ export declare function processTxlogInputs(txlog: TxLog, cache: ScanCache): {
|
|
|
58
58
|
inputSum: bigint;
|
|
59
59
|
alreadyRecognizedAsSpend: boolean;
|
|
60
60
|
};
|
|
61
|
+
export declare function txLogHoldsFunds(txlog: TxLog): boolean;
|
|
61
62
|
export declare function processTxlogs(cache: ScanCache, stats: ScanStats): void;
|
|
62
63
|
export declare function alignScanStatsWithCache(cache: ScanCache, view_pair: ViewPair, primary_address: string, pathPrefix?: string, highestSubaddressMinor?: number, current_scan_tip_height?: number): Promise<ScanStats>;
|
|
63
64
|
export declare function resetStats(stats: ScanStats): void;
|
|
@@ -241,17 +241,33 @@ export function processTxlogInputs(txlog, cache) {
|
|
|
241
241
|
}
|
|
242
242
|
return { inputSum, alreadyRecognizedAsSpend };
|
|
243
243
|
}
|
|
244
|
+
// an in-flight row has no node answer yet. it still locks the inputs.
|
|
245
|
+
export function txLogHoldsFunds(txlog) {
|
|
246
|
+
if (txlog.error)
|
|
247
|
+
return false;
|
|
248
|
+
if (!txlog.sendResult)
|
|
249
|
+
return true;
|
|
250
|
+
return txlog.sendResult.status === "OK";
|
|
251
|
+
}
|
|
244
252
|
export function processTxlogs(cache, stats) {
|
|
245
253
|
for (const txlog of cache.tx_logs || []) {
|
|
246
|
-
if (!txlog ||
|
|
247
|
-
!txlog.sendResult ||
|
|
248
|
-
(txlog.sendResult && txlog.sendResult.status !== "OK"))
|
|
254
|
+
if (!txlog || !txLogHoldsFunds(txlog))
|
|
249
255
|
continue;
|
|
250
256
|
const { inputSum, alreadyRecognizedAsSpend } = processTxlogInputs(txlog, cache);
|
|
251
257
|
if (alreadyRecognizedAsSpend)
|
|
252
258
|
continue;
|
|
253
259
|
const outWardPaymentSum = processTxlogPayments(txlog, cache);
|
|
254
|
-
|
|
260
|
+
// locked inputs are already out of the spendable sum. do not subtract them again.
|
|
261
|
+
let alreadyExcluded = 0n;
|
|
262
|
+
for (const inputId of txlog.inputs_index) {
|
|
263
|
+
if (cache.pending_spent_utxos?.[inputId] === undefined)
|
|
264
|
+
continue;
|
|
265
|
+
const input = cache.outputs[inputId];
|
|
266
|
+
if (!input || typeof input.spent_in_tx_hash === "string")
|
|
267
|
+
continue;
|
|
268
|
+
alreadyExcluded += input.amount;
|
|
269
|
+
}
|
|
270
|
+
stats.total_spendable_amount -= inputSum - alreadyExcluded;
|
|
255
271
|
const newPending = inputSum - outWardPaymentSum;
|
|
256
272
|
stats.total_pending_amount += newPending;
|
|
257
273
|
}
|
|
@@ -20,6 +20,7 @@ export declare class ScanSettingsOpened {
|
|
|
20
20
|
get pathPrefix(): string;
|
|
21
21
|
setNodeUrl(node_url: string): Promise<void>;
|
|
22
22
|
setStartHeight(start_height: number | null): Promise<void>;
|
|
23
|
+
setNodeUrlAndStartHeight(node_url?: string, start_height?: number | null): Promise<void>;
|
|
23
24
|
setMerchantConfirmations(merchant_confirmations: number | undefined | null): Promise<void>;
|
|
24
25
|
setCpuWorkerCount(cpu_worker_count: number | undefined | null): Promise<void>;
|
|
25
26
|
setLogSettings(logs?: LogSetting | null, logs_include?: PossibleLogs[] | null, logs_exclude?: PossibleLogs[] | null): Promise<void>;
|
|
@@ -54,14 +54,21 @@ export class ScanSettingsOpened {
|
|
|
54
54
|
return this._pathPrefix;
|
|
55
55
|
}
|
|
56
56
|
async setNodeUrl(node_url) {
|
|
57
|
-
await this.
|
|
58
|
-
this._settings.node_url = ScanSettingsOpened._validateNodeUrl(node_url);
|
|
59
|
-
await this._persist();
|
|
57
|
+
await this.setNodeUrlAndStartHeight(node_url, undefined);
|
|
60
58
|
}
|
|
61
59
|
async setStartHeight(start_height) {
|
|
60
|
+
await this.setNodeUrlAndStartHeight(undefined, start_height);
|
|
61
|
+
}
|
|
62
|
+
// one read and one write. a second reload must not put the old url back.
|
|
63
|
+
async setNodeUrlAndStartHeight(node_url, start_height) {
|
|
62
64
|
await this.reload();
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
if (node_url !== undefined) {
|
|
66
|
+
this._settings.node_url = ScanSettingsOpened._validateNodeUrl(node_url);
|
|
67
|
+
}
|
|
68
|
+
if (start_height !== undefined) {
|
|
69
|
+
ScanSettingsOpened._validateStartHeight(start_height);
|
|
70
|
+
this._settings.start_height = start_height;
|
|
71
|
+
}
|
|
65
72
|
await this._persist();
|
|
66
73
|
}
|
|
67
74
|
async setMerchantConfirmations(merchant_confirmations) {
|
|
@@ -163,7 +170,7 @@ export class ScanSettingsOpened {
|
|
|
163
170
|
async addViewWallet(primary_address, view_key, fields) {
|
|
164
171
|
primary_address = primary_address.trim();
|
|
165
172
|
view_key = view_key.trim();
|
|
166
|
-
ScanSettingsOpened._validateAddress(primary_address);
|
|
173
|
+
await ScanSettingsOpened._validateAddress(primary_address);
|
|
167
174
|
ScanSettingsOpened._validateViewKey(view_key);
|
|
168
175
|
ScanSettingsOpened._validateWalletName(fields?.wallet_name);
|
|
169
176
|
ScanSettingsOpened._validateWalletSlot(fields?.wallet_slot);
|
|
@@ -308,8 +315,8 @@ export class ScanSettingsOpened {
|
|
|
308
315
|
`valid options are: ${[...valid].join(", ")}`);
|
|
309
316
|
}
|
|
310
317
|
}
|
|
311
|
-
static _validateAddress(address) {
|
|
312
|
-
const parsed = parseAddress(address);
|
|
318
|
+
static async _validateAddress(address) {
|
|
319
|
+
const parsed = await parseAddress(address);
|
|
313
320
|
if ("error" in parsed) {
|
|
314
321
|
throw new Error(`invalid monero address: ${address}`);
|
|
315
322
|
}
|