@breeztech/breez-sdk-spark 0.27.0-dev2 → 0.27.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/breez-sdk-spark.tgz +0 -0
- package/bundler/breez_sdk_spark_wasm.d.ts +347 -95
- package/bundler/breez_sdk_spark_wasm_bg.js +210 -92
- package/bundler/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/bundler/breez_sdk_spark_wasm_bg.wasm.d.ts +28 -14
- package/bundler/storage/index.js +251 -20
- package/bundler/tree-store/index.js +39 -6
- package/deno/breez_sdk_spark_wasm.d.ts +347 -95
- package/deno/breez_sdk_spark_wasm.js +210 -92
- package/deno/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/deno/breez_sdk_spark_wasm_bg.wasm.d.ts +28 -14
- package/itest/database-drivers.test.js +45 -0
- package/nodejs/breez_sdk_spark_wasm.d.ts +347 -95
- package/nodejs/breez_sdk_spark_wasm.js +210 -92
- package/nodejs/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/nodejs/breez_sdk_spark_wasm_bg.wasm.d.ts +28 -14
- package/nodejs/index.js +6 -0
- package/nodejs/mysql-session-store/index.cjs +3 -1
- package/nodejs/mysql-storage/index.cjs +238 -20
- package/nodejs/mysql-storage/migrations.cjs +49 -1
- package/nodejs/mysql-token-store/index.cjs +3 -7
- package/nodejs/mysql-tree-store/index.cjs +26 -1
- package/nodejs/postgres-session-store/index.cjs +3 -1
- package/nodejs/postgres-storage/index.cjs +228 -19
- package/nodejs/postgres-storage/migrations.cjs +49 -0
- package/nodejs/postgres-token-store/index.cjs +3 -1
- package/nodejs/postgres-tree-store/index.cjs +25 -1
- package/nodejs/postgres-tree-store/migrations.cjs +10 -0
- package/nodejs/storage/index.cjs +210 -19
- package/nodejs/storage/migrations.cjs +38 -0
- package/nodejs/tree-store/index.cjs +35 -0
- package/package.json +1 -1
- package/web/breez_sdk_spark_wasm.d.ts +375 -109
- package/web/breez_sdk_spark_wasm.js +210 -92
- package/web/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/web/breez_sdk_spark_wasm_bg.wasm.d.ts +28 -14
- package/web/storage/index.js +251 -20
- package/web/tree-store/index.js +39 -6
package/nodejs/storage/index.cjs
CHANGED
|
@@ -27,6 +27,14 @@ try {
|
|
|
27
27
|
const { StorageError } = require("./errors.cjs");
|
|
28
28
|
const { MigrationManager } = require("./migrations.cjs");
|
|
29
29
|
|
|
30
|
+
// JSON has no BigInt, so u128 amounts are stored as decimal strings, which the
|
|
31
|
+
// SDK reads back as BigInts.
|
|
32
|
+
function toJson(value) {
|
|
33
|
+
return JSON.stringify(value, (_key, v) =>
|
|
34
|
+
typeof v === "bigint" ? v.toString() : v
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
30
38
|
/**
|
|
31
39
|
* Base query for payment lookups.
|
|
32
40
|
* All columns are accessed by name in _rowToPayment.
|
|
@@ -329,7 +337,7 @@ class SqliteStorage {
|
|
|
329
337
|
} catch (error) {
|
|
330
338
|
return Promise.reject(
|
|
331
339
|
new StorageError(
|
|
332
|
-
`Failed to list payments (request: ${
|
|
340
|
+
`Failed to list payments (request: ${toJson(request)}: ${
|
|
333
341
|
error.message
|
|
334
342
|
}`,
|
|
335
343
|
error
|
|
@@ -446,7 +454,7 @@ class SqliteStorage {
|
|
|
446
454
|
amount: payment.amount.toString(),
|
|
447
455
|
fees: payment.fees.toString(),
|
|
448
456
|
timestamp: payment.timestamp,
|
|
449
|
-
method: payment.method ?
|
|
457
|
+
method: payment.method ? toJson(payment.method) : null,
|
|
450
458
|
withdrawTxId:
|
|
451
459
|
payment.details?.type === "withdraw" ? payment.details.txId : null,
|
|
452
460
|
spark: payment.details?.type === "spark" ? 1 : null,
|
|
@@ -468,10 +476,10 @@ class SqliteStorage {
|
|
|
468
476
|
sparkInsert.run({
|
|
469
477
|
id: payment.id,
|
|
470
478
|
invoiceDetails: payment.details.invoiceDetails
|
|
471
|
-
?
|
|
479
|
+
? toJson(payment.details.invoiceDetails)
|
|
472
480
|
: null,
|
|
473
481
|
htlcDetails: payment.details.htlcDetails
|
|
474
|
-
?
|
|
482
|
+
? toJson(payment.details.htlcDetails)
|
|
475
483
|
: null,
|
|
476
484
|
});
|
|
477
485
|
}
|
|
@@ -492,11 +500,11 @@ class SqliteStorage {
|
|
|
492
500
|
if (payment.details?.type === "token") {
|
|
493
501
|
tokenInsert.run({
|
|
494
502
|
id: payment.id,
|
|
495
|
-
metadata:
|
|
503
|
+
metadata: toJson(payment.details.metadata),
|
|
496
504
|
txHash: payment.details.txHash,
|
|
497
505
|
txType: payment.details.txType,
|
|
498
506
|
invoiceDetails: payment.details.invoiceDetails
|
|
499
|
-
?
|
|
507
|
+
? toJson(payment.details.invoiceDetails)
|
|
500
508
|
: null,
|
|
501
509
|
});
|
|
502
510
|
}
|
|
@@ -560,7 +568,6 @@ class SqliteStorage {
|
|
|
560
568
|
return Promise.resolve(this._rowToPayment(row));
|
|
561
569
|
} catch (error) {
|
|
562
570
|
if (error instanceof StorageError) return Promise.reject(error);
|
|
563
|
-
const paymentId = id || "unknown";
|
|
564
571
|
return Promise.reject(
|
|
565
572
|
new StorageError(
|
|
566
573
|
`Failed to get payment by invoice '${invoice}': ${error.message}`,
|
|
@@ -636,13 +643,13 @@ class SqliteStorage {
|
|
|
636
643
|
stmt.run(
|
|
637
644
|
paymentId,
|
|
638
645
|
metadata.parentPaymentId,
|
|
639
|
-
metadata.lnurlPayInfo ?
|
|
646
|
+
metadata.lnurlPayInfo ? toJson(metadata.lnurlPayInfo) : null,
|
|
640
647
|
metadata.lnurlWithdrawInfo
|
|
641
|
-
?
|
|
648
|
+
? toJson(metadata.lnurlWithdrawInfo)
|
|
642
649
|
: null,
|
|
643
650
|
metadata.lnurlDescription,
|
|
644
651
|
metadata.conversionInfo
|
|
645
|
-
?
|
|
652
|
+
? toJson(metadata.conversionInfo)
|
|
646
653
|
: null,
|
|
647
654
|
metadata.conversionStatus ?? null
|
|
648
655
|
);
|
|
@@ -739,7 +746,7 @@ class SqliteStorage {
|
|
|
739
746
|
WHERE txid = ? AND vout = ?
|
|
740
747
|
`);
|
|
741
748
|
|
|
742
|
-
stmt.run(
|
|
749
|
+
stmt.run(toJson(payload.error), txid, vout);
|
|
743
750
|
} else if (payload.type === "refund") {
|
|
744
751
|
const stmt = this.db.prepare(`
|
|
745
752
|
UPDATE unclaimed_deposits
|
|
@@ -750,7 +757,7 @@ class SqliteStorage {
|
|
|
750
757
|
stmt.run(
|
|
751
758
|
payload.refundTx,
|
|
752
759
|
payload.refundTxid,
|
|
753
|
-
|
|
760
|
+
toJson(payload.state),
|
|
754
761
|
txid,
|
|
755
762
|
vout
|
|
756
763
|
);
|
|
@@ -761,7 +768,7 @@ class SqliteStorage {
|
|
|
761
768
|
WHERE txid = ? AND vout = ?
|
|
762
769
|
`);
|
|
763
770
|
|
|
764
|
-
stmt.run(
|
|
771
|
+
stmt.run(toJson(payload.status), txid, vout);
|
|
765
772
|
} else if (payload.type === "refundBroadcastState") {
|
|
766
773
|
const stmt = this.db.prepare(`
|
|
767
774
|
UPDATE unclaimed_deposits
|
|
@@ -769,7 +776,7 @@ class SqliteStorage {
|
|
|
769
776
|
WHERE txid = ? AND vout = ? AND refund_tx_id = ?
|
|
770
777
|
`);
|
|
771
778
|
|
|
772
|
-
stmt.run(
|
|
779
|
+
stmt.run(toJson(payload.state), txid, vout, payload.refundTxid);
|
|
773
780
|
} else if (payload.type === "maxClaimFee") {
|
|
774
781
|
const stmt = this.db.prepare(`
|
|
775
782
|
UPDATE unclaimed_deposits
|
|
@@ -778,7 +785,7 @@ class SqliteStorage {
|
|
|
778
785
|
`);
|
|
779
786
|
|
|
780
787
|
stmt.run(
|
|
781
|
-
payload.maxFee ?
|
|
788
|
+
payload.maxFee ? toJson(payload.maxFee) : null,
|
|
782
789
|
txid,
|
|
783
790
|
vout
|
|
784
791
|
);
|
|
@@ -864,6 +871,147 @@ class SqliteStorage {
|
|
|
864
871
|
}
|
|
865
872
|
}
|
|
866
873
|
|
|
874
|
+
listLeafRecoveries() {
|
|
875
|
+
try {
|
|
876
|
+
const rows = this.db
|
|
877
|
+
.prepare(
|
|
878
|
+
`SELECT l.leaf_id,
|
|
879
|
+
l.chain_checked_at,
|
|
880
|
+
o.txid AS output_txid,
|
|
881
|
+
o.vout AS output_vout,
|
|
882
|
+
o.amount_sats AS output_amount_sats,
|
|
883
|
+
o.script_pubkey AS output_script_pubkey,
|
|
884
|
+
ot.block_height AS output_block_height,
|
|
885
|
+
s.txid AS spend_txid,
|
|
886
|
+
st.block_height AS spend_block_height,
|
|
887
|
+
w.txid AS sweep_txid,
|
|
888
|
+
wt.block_height AS sweep_block_height,
|
|
889
|
+
r.txid AS recovery_txid,
|
|
890
|
+
r.transaction_hex AS recovery_transaction_hex,
|
|
891
|
+
r.output_amount_sats AS recovery_output_amount_sats
|
|
892
|
+
FROM leaf_recoveries l
|
|
893
|
+
LEFT JOIN watchtower_exit_outputs o ON o.leaf_id = l.leaf_id
|
|
894
|
+
LEFT JOIN chain_transactions ot ON ot.txid = o.txid
|
|
895
|
+
LEFT JOIN watchtower_exit_spends s ON s.leaf_id = l.leaf_id
|
|
896
|
+
LEFT JOIN chain_transactions st ON st.txid = s.txid
|
|
897
|
+
LEFT JOIN unilateral_exit_sweeps w ON w.leaf_id = l.leaf_id
|
|
898
|
+
LEFT JOIN chain_transactions wt ON wt.txid = w.txid
|
|
899
|
+
LEFT JOIN watchtower_exit_recoveries r ON r.leaf_id = l.leaf_id
|
|
900
|
+
ORDER BY l.leaf_id, r.txid`
|
|
901
|
+
)
|
|
902
|
+
.all();
|
|
903
|
+
return Promise.resolve(leafRecoveriesFromRows(rows));
|
|
904
|
+
} catch (error) {
|
|
905
|
+
return Promise.reject(
|
|
906
|
+
new StorageError(`Failed to list leaf recoveries: ${error.message}`, error)
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
updateLeafRecovery(update) {
|
|
912
|
+
try {
|
|
913
|
+
const leafId = update.leafId;
|
|
914
|
+
const setChainTransaction = this.db.prepare(
|
|
915
|
+
`INSERT INTO chain_transactions (txid, block_height) VALUES (?, ?)
|
|
916
|
+
ON CONFLICT(txid) DO UPDATE SET block_height = excluded.block_height`
|
|
917
|
+
);
|
|
918
|
+
const transaction = this.db.transaction(() => {
|
|
919
|
+
this.db
|
|
920
|
+
.prepare(
|
|
921
|
+
"INSERT INTO leaf_recoveries (leaf_id) VALUES (?) ON CONFLICT(leaf_id) DO NOTHING"
|
|
922
|
+
)
|
|
923
|
+
.run(leafId);
|
|
924
|
+
if (update.chainCheckedAt != null) {
|
|
925
|
+
this.db
|
|
926
|
+
.prepare(
|
|
927
|
+
`UPDATE leaf_recoveries
|
|
928
|
+
SET chain_checked_at = MAX(COALESCE(chain_checked_at, 0), ?)
|
|
929
|
+
WHERE leaf_id = ?`
|
|
930
|
+
)
|
|
931
|
+
.run(update.chainCheckedAt, leafId);
|
|
932
|
+
}
|
|
933
|
+
if (update.watchtowerExitOutput != null) {
|
|
934
|
+
const output = update.watchtowerExitOutput;
|
|
935
|
+
for (const table of [
|
|
936
|
+
"watchtower_exit_recoveries",
|
|
937
|
+
"watchtower_exit_spends",
|
|
938
|
+
]) {
|
|
939
|
+
this.db
|
|
940
|
+
.prepare(
|
|
941
|
+
`DELETE FROM ${table} WHERE leaf_id = @leafId AND EXISTS (
|
|
942
|
+
SELECT 1 FROM watchtower_exit_outputs
|
|
943
|
+
WHERE leaf_id = @leafId AND (txid <> @txid OR vout <> @vout))`
|
|
944
|
+
)
|
|
945
|
+
.run({ leafId, txid: output.txid, vout: output.vout });
|
|
946
|
+
}
|
|
947
|
+
setChainTransaction.run(output.txid, output.blockHeight);
|
|
948
|
+
this.db
|
|
949
|
+
.prepare(
|
|
950
|
+
`INSERT INTO watchtower_exit_outputs
|
|
951
|
+
(leaf_id, txid, vout, amount_sats, script_pubkey)
|
|
952
|
+
VALUES (?, ?, ?, ?, ?)
|
|
953
|
+
ON CONFLICT(leaf_id) DO UPDATE SET
|
|
954
|
+
txid = excluded.txid,
|
|
955
|
+
vout = excluded.vout,
|
|
956
|
+
amount_sats = excluded.amount_sats,
|
|
957
|
+
script_pubkey = excluded.script_pubkey`
|
|
958
|
+
)
|
|
959
|
+
.run(
|
|
960
|
+
leafId,
|
|
961
|
+
output.txid,
|
|
962
|
+
output.vout,
|
|
963
|
+
output.amountSats,
|
|
964
|
+
output.scriptPubkey
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
if (update.watchtowerExitRecovery != null) {
|
|
968
|
+
const recovery = update.watchtowerExitRecovery;
|
|
969
|
+
this.db
|
|
970
|
+
.prepare(
|
|
971
|
+
`INSERT INTO watchtower_exit_recoveries
|
|
972
|
+
(leaf_id, txid, transaction_hex, output_amount_sats)
|
|
973
|
+
VALUES (?, ?, ?, ?)
|
|
974
|
+
ON CONFLICT(leaf_id, txid) DO NOTHING`
|
|
975
|
+
)
|
|
976
|
+
.run(
|
|
977
|
+
leafId,
|
|
978
|
+
recovery.txid,
|
|
979
|
+
recovery.transactionHex,
|
|
980
|
+
recovery.outputAmountSats
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
for (const [table, chainTransaction] of [
|
|
984
|
+
["watchtower_exit_spends", update.watchtowerExitSpend],
|
|
985
|
+
["unilateral_exit_sweeps", update.unilateralExitSweep],
|
|
986
|
+
]) {
|
|
987
|
+
if (chainTransaction == null) {
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
990
|
+
setChainTransaction.run(
|
|
991
|
+
chainTransaction.txid,
|
|
992
|
+
chainTransaction.blockHeight
|
|
993
|
+
);
|
|
994
|
+
this.db
|
|
995
|
+
.prepare(
|
|
996
|
+
`INSERT INTO ${table} (leaf_id, txid) VALUES (?, ?)
|
|
997
|
+
ON CONFLICT(leaf_id) DO UPDATE SET txid = excluded.txid`
|
|
998
|
+
)
|
|
999
|
+
.run(leafId, chainTransaction.txid);
|
|
1000
|
+
}
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
transaction.immediate();
|
|
1004
|
+
return Promise.resolve();
|
|
1005
|
+
} catch (error) {
|
|
1006
|
+
return Promise.reject(
|
|
1007
|
+
new StorageError(
|
|
1008
|
+
`Failed to update leaf recovery '${update.leafId}': ${error.message}`,
|
|
1009
|
+
error
|
|
1010
|
+
)
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
867
1015
|
setLnurlMetadata(metadata) {
|
|
868
1016
|
try {
|
|
869
1017
|
const stmt = this.db.prepare(
|
|
@@ -1048,7 +1196,7 @@ class SqliteStorage {
|
|
|
1048
1196
|
record.id.dataId,
|
|
1049
1197
|
record.schemaVersion,
|
|
1050
1198
|
Math.floor(Date.now() / 1000),
|
|
1051
|
-
|
|
1199
|
+
toJson(record.updatedFields),
|
|
1052
1200
|
revision.toString()
|
|
1053
1201
|
);
|
|
1054
1202
|
|
|
@@ -1099,7 +1247,7 @@ class SqliteStorage {
|
|
|
1099
1247
|
record.revision.toString(),
|
|
1100
1248
|
record.schemaVersion,
|
|
1101
1249
|
Math.floor(Date.now() / 1000),
|
|
1102
|
-
|
|
1250
|
+
toJson(record.data)
|
|
1103
1251
|
);
|
|
1104
1252
|
|
|
1105
1253
|
// Update sync_revision to track the highest known revision
|
|
@@ -1225,7 +1373,7 @@ class SqliteStorage {
|
|
|
1225
1373
|
record.id.dataId,
|
|
1226
1374
|
record.schemaVersion,
|
|
1227
1375
|
Math.floor(Date.now() / 1000),
|
|
1228
|
-
|
|
1376
|
+
toJson(record.data),
|
|
1229
1377
|
record.revision.toString()
|
|
1230
1378
|
);
|
|
1231
1379
|
}
|
|
@@ -1419,7 +1567,7 @@ class SqliteStorage {
|
|
|
1419
1567
|
record.revision.toString(),
|
|
1420
1568
|
record.schemaVersion,
|
|
1421
1569
|
Math.floor(Date.now() / 1000),
|
|
1422
|
-
|
|
1570
|
+
toJson(record.data)
|
|
1423
1571
|
);
|
|
1424
1572
|
|
|
1425
1573
|
// Update sync_revision to track the highest known revision
|
|
@@ -1592,6 +1740,49 @@ function crossChainSwapFromRow(row) {
|
|
|
1592
1740
|
};
|
|
1593
1741
|
}
|
|
1594
1742
|
|
|
1743
|
+
/// A row holds a leaf recovery and one of its watchtower exit recoveries, so
|
|
1744
|
+
/// a leaf recovery with several of them spans several rows.
|
|
1745
|
+
function leafRecoveriesFromRows(rows) {
|
|
1746
|
+
const leafRecoveries = [];
|
|
1747
|
+
for (const row of rows) {
|
|
1748
|
+
let leafRecovery = leafRecoveries[leafRecoveries.length - 1];
|
|
1749
|
+
if (leafRecovery?.leafId !== row.leaf_id) {
|
|
1750
|
+
leafRecovery = {
|
|
1751
|
+
leafId: row.leaf_id,
|
|
1752
|
+
chainCheckedAt: row.chain_checked_at,
|
|
1753
|
+
watchtowerExitOutput:
|
|
1754
|
+
row.output_block_height != null
|
|
1755
|
+
? {
|
|
1756
|
+
txid: row.output_txid,
|
|
1757
|
+
vout: row.output_vout,
|
|
1758
|
+
amountSats: row.output_amount_sats,
|
|
1759
|
+
scriptPubkey: row.output_script_pubkey,
|
|
1760
|
+
blockHeight: row.output_block_height,
|
|
1761
|
+
}
|
|
1762
|
+
: null,
|
|
1763
|
+
watchtowerExitRecoveries: [],
|
|
1764
|
+
watchtowerExitSpend:
|
|
1765
|
+
row.spend_block_height != null
|
|
1766
|
+
? { txid: row.spend_txid, blockHeight: row.spend_block_height }
|
|
1767
|
+
: null,
|
|
1768
|
+
unilateralExitSweep:
|
|
1769
|
+
row.sweep_block_height != null
|
|
1770
|
+
? { txid: row.sweep_txid, blockHeight: row.sweep_block_height }
|
|
1771
|
+
: null,
|
|
1772
|
+
};
|
|
1773
|
+
leafRecoveries.push(leafRecovery);
|
|
1774
|
+
}
|
|
1775
|
+
if (row.recovery_txid != null) {
|
|
1776
|
+
leafRecovery.watchtowerExitRecoveries.push({
|
|
1777
|
+
txid: row.recovery_txid,
|
|
1778
|
+
transactionHex: row.recovery_transaction_hex,
|
|
1779
|
+
outputAmountSats: row.recovery_output_amount_sats,
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
return leafRecoveries;
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1595
1786
|
async function createDefaultStorage(dataDir, logger = null) {
|
|
1596
1787
|
const path = require("path");
|
|
1597
1788
|
const fs = require("fs").promises;
|
|
@@ -521,6 +521,44 @@ class MigrationManager {
|
|
|
521
521
|
`ALTER TABLE unclaimed_deposits ADD COLUMN max_claim_fee TEXT`,
|
|
522
522
|
],
|
|
523
523
|
},
|
|
524
|
+
{
|
|
525
|
+
// Leaf recoveries. What the chain service reported about leaves with
|
|
526
|
+
// funds to recover on-chain, and the recoveries the operators co-signed.
|
|
527
|
+
// Every block height is in chain_transactions, keyed by txid.
|
|
528
|
+
name: "Add leaf recovery tables",
|
|
529
|
+
sql: [
|
|
530
|
+
`CREATE TABLE IF NOT EXISTS leaf_recoveries (
|
|
531
|
+
leaf_id TEXT NOT NULL PRIMARY KEY,
|
|
532
|
+
chain_checked_at INTEGER
|
|
533
|
+
)`,
|
|
534
|
+
`CREATE TABLE IF NOT EXISTS chain_transactions (
|
|
535
|
+
txid TEXT NOT NULL PRIMARY KEY,
|
|
536
|
+
block_height INTEGER NOT NULL
|
|
537
|
+
)`,
|
|
538
|
+
`CREATE TABLE IF NOT EXISTS watchtower_exit_outputs (
|
|
539
|
+
leaf_id TEXT NOT NULL PRIMARY KEY,
|
|
540
|
+
txid TEXT NOT NULL,
|
|
541
|
+
vout INTEGER NOT NULL,
|
|
542
|
+
amount_sats INTEGER NOT NULL,
|
|
543
|
+
script_pubkey TEXT NOT NULL
|
|
544
|
+
)`,
|
|
545
|
+
`CREATE TABLE IF NOT EXISTS watchtower_exit_recoveries (
|
|
546
|
+
leaf_id TEXT NOT NULL,
|
|
547
|
+
txid TEXT NOT NULL,
|
|
548
|
+
transaction_hex TEXT NOT NULL,
|
|
549
|
+
output_amount_sats INTEGER NOT NULL,
|
|
550
|
+
PRIMARY KEY (leaf_id, txid)
|
|
551
|
+
)`,
|
|
552
|
+
`CREATE TABLE IF NOT EXISTS watchtower_exit_spends (
|
|
553
|
+
leaf_id TEXT NOT NULL PRIMARY KEY,
|
|
554
|
+
txid TEXT NOT NULL
|
|
555
|
+
)`,
|
|
556
|
+
`CREATE TABLE IF NOT EXISTS unilateral_exit_sweeps (
|
|
557
|
+
leaf_id TEXT NOT NULL PRIMARY KEY,
|
|
558
|
+
txid TEXT NOT NULL
|
|
559
|
+
)`,
|
|
560
|
+
],
|
|
561
|
+
},
|
|
524
562
|
];
|
|
525
563
|
}
|
|
526
564
|
}
|
|
@@ -94,6 +94,17 @@ const SLIM_LEAF_CANDIDATES_SQL = `
|
|
|
94
94
|
)
|
|
95
95
|
`;
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Takes one status, bound twice: given several through IN, SQLite reads every
|
|
99
|
+
* unreserved leaf instead of using the status index.
|
|
100
|
+
*/
|
|
101
|
+
const LEAVES_WITH_STATUS_SQL = `
|
|
102
|
+
SELECT data FROM brz_tree_leaves WHERE status = ? AND reservation_id IS NULL
|
|
103
|
+
UNION ALL
|
|
104
|
+
SELECT data FROM brz_tree_leaves
|
|
105
|
+
WHERE status = ? AND reservation_id IN (SELECT id FROM brz_tree_reservations)
|
|
106
|
+
`;
|
|
107
|
+
|
|
97
108
|
/**
|
|
98
109
|
* Pair a leaf with its ancestors (nearest first) by walking `parent_node_id`
|
|
99
110
|
* through `nodes`. Returns null if the leaf itself is absent; stops at a gap or
|
|
@@ -414,6 +425,30 @@ class NodeTreeStore {
|
|
|
414
425
|
}
|
|
415
426
|
}
|
|
416
427
|
|
|
428
|
+
/**
|
|
429
|
+
* Return the leaves, reserved or not, whose status is one of `statuses`.
|
|
430
|
+
* @param {Array<string>} statuses
|
|
431
|
+
* @returns {Promise<Array<object>>}
|
|
432
|
+
*/
|
|
433
|
+
async getLeavesWithStatus(statuses) {
|
|
434
|
+
try {
|
|
435
|
+
const stmt = this.db.prepare(LEAVES_WITH_STATUS_SQL);
|
|
436
|
+
// One snapshot across the statements, so a leaf whose status changes
|
|
437
|
+
// between them is not read twice or missed.
|
|
438
|
+
return this.db.transaction(() =>
|
|
439
|
+
[...new Set(statuses)].flatMap((status) =>
|
|
440
|
+
stmt.all(status, status).map((row) => JSON.parse(row.data))
|
|
441
|
+
)
|
|
442
|
+
)();
|
|
443
|
+
} catch (error) {
|
|
444
|
+
if (error instanceof TreeStoreError) throw error;
|
|
445
|
+
throw new TreeStoreError(
|
|
446
|
+
`Failed to get leaves with status: ${error.message}`,
|
|
447
|
+
error
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
417
452
|
/**
|
|
418
453
|
* Replace the pool from a refresh. Skipped while a swap is in flight or one
|
|
419
454
|
* completed during the refresh, so a swap's leaves are never clobbered.
|
package/package.json
CHANGED