@breeztech/breez-sdk-spark 0.27.0-dev2 → 0.27.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/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
|
@@ -22,10 +22,12 @@ try {
|
|
|
22
22
|
try {
|
|
23
23
|
pg = require("pg");
|
|
24
24
|
} catch (fallbackError) {
|
|
25
|
-
|
|
25
|
+
const loadError = new Error(
|
|
26
26
|
`pg not found. Please install it in your project: npm install pg@^8.18.0\n` +
|
|
27
27
|
`Original error: ${error.message}\nFallback error: ${fallbackError.message}`
|
|
28
28
|
);
|
|
29
|
+
loadError.code = fallbackError.code;
|
|
30
|
+
throw loadError;
|
|
29
31
|
}
|
|
30
32
|
}
|
|
31
33
|
|
|
@@ -16,16 +16,26 @@ try {
|
|
|
16
16
|
try {
|
|
17
17
|
pg = require("pg");
|
|
18
18
|
} catch (fallbackError) {
|
|
19
|
-
|
|
19
|
+
const loadError = new Error(
|
|
20
20
|
`pg not found. Please install it in your project: npm install pg@^8.18.0\n` +
|
|
21
21
|
`Original error: ${error.message}\nFallback error: ${fallbackError.message}`
|
|
22
22
|
);
|
|
23
|
+
loadError.code = fallbackError.code;
|
|
24
|
+
throw loadError;
|
|
23
25
|
}
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
const { StorageError } = require("./errors.cjs");
|
|
27
29
|
const { PostgresMigrationManager } = require("./migrations.cjs");
|
|
28
30
|
|
|
31
|
+
// JSON has no BigInt, so u128 amounts are stored as decimal strings, which the
|
|
32
|
+
// SDK reads back as BigInts.
|
|
33
|
+
function toJson(value) {
|
|
34
|
+
return JSON.stringify(value, (_key, v) =>
|
|
35
|
+
typeof v === "bigint" ? v.toString() : v
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
29
39
|
/**
|
|
30
40
|
* Base query for payment lookups.
|
|
31
41
|
* All columns are accessed by name in _rowToPayment.
|
|
@@ -371,7 +381,7 @@ class PostgresStorage {
|
|
|
371
381
|
} catch (error) {
|
|
372
382
|
if (error instanceof StorageError) throw error;
|
|
373
383
|
throw new StorageError(
|
|
374
|
-
`Failed to list payments (request: ${
|
|
384
|
+
`Failed to list payments (request: ${toJson(request)}): ${error.message}`,
|
|
375
385
|
error
|
|
376
386
|
);
|
|
377
387
|
}
|
|
@@ -462,7 +472,7 @@ class PostgresStorage {
|
|
|
462
472
|
payment.amount.toString(),
|
|
463
473
|
payment.fees.toString(),
|
|
464
474
|
payment.timestamp,
|
|
465
|
-
payment.method ?
|
|
475
|
+
payment.method ? toJson(payment.method) : null,
|
|
466
476
|
withdrawTxId,
|
|
467
477
|
spark,
|
|
468
478
|
]
|
|
@@ -494,10 +504,10 @@ class PostgresStorage {
|
|
|
494
504
|
this.identity,
|
|
495
505
|
payment.id,
|
|
496
506
|
payment.details.invoiceDetails
|
|
497
|
-
?
|
|
507
|
+
? toJson(payment.details.invoiceDetails)
|
|
498
508
|
: null,
|
|
499
509
|
payment.details.htlcDetails
|
|
500
|
-
?
|
|
510
|
+
? toJson(payment.details.htlcDetails)
|
|
501
511
|
: null,
|
|
502
512
|
]
|
|
503
513
|
);
|
|
@@ -543,11 +553,11 @@ class PostgresStorage {
|
|
|
543
553
|
[
|
|
544
554
|
this.identity,
|
|
545
555
|
payment.id,
|
|
546
|
-
|
|
556
|
+
toJson(payment.details.metadata),
|
|
547
557
|
payment.details.txHash,
|
|
548
558
|
payment.details.txType,
|
|
549
559
|
payment.details.invoiceDetails
|
|
550
|
-
?
|
|
560
|
+
? toJson(payment.details.invoiceDetails)
|
|
551
561
|
: null,
|
|
552
562
|
]
|
|
553
563
|
);
|
|
@@ -675,14 +685,14 @@ class PostgresStorage {
|
|
|
675
685
|
paymentId,
|
|
676
686
|
metadata.parentPaymentId,
|
|
677
687
|
metadata.lnurlPayInfo
|
|
678
|
-
?
|
|
688
|
+
? toJson(metadata.lnurlPayInfo)
|
|
679
689
|
: null,
|
|
680
690
|
metadata.lnurlWithdrawInfo
|
|
681
|
-
?
|
|
691
|
+
? toJson(metadata.lnurlWithdrawInfo)
|
|
682
692
|
: null,
|
|
683
693
|
metadata.lnurlDescription,
|
|
684
694
|
metadata.conversionInfo
|
|
685
|
-
?
|
|
695
|
+
? toJson(metadata.conversionInfo)
|
|
686
696
|
: null,
|
|
687
697
|
metadata.conversionStatus ?? null,
|
|
688
698
|
]
|
|
@@ -761,7 +771,7 @@ class PostgresStorage {
|
|
|
761
771
|
`UPDATE brz_unclaimed_deposits
|
|
762
772
|
SET claim_error = $1
|
|
763
773
|
WHERE user_id = $2 AND txid = $3 AND vout = $4`,
|
|
764
|
-
[
|
|
774
|
+
[toJson(payload.error), this.identity, txid, vout]
|
|
765
775
|
);
|
|
766
776
|
} else if (payload.type === "refund") {
|
|
767
777
|
await this.pool.query(
|
|
@@ -771,7 +781,7 @@ class PostgresStorage {
|
|
|
771
781
|
[
|
|
772
782
|
payload.refundTx,
|
|
773
783
|
payload.refundTxid,
|
|
774
|
-
|
|
784
|
+
toJson(payload.state),
|
|
775
785
|
this.identity,
|
|
776
786
|
txid,
|
|
777
787
|
vout,
|
|
@@ -782,7 +792,7 @@ class PostgresStorage {
|
|
|
782
792
|
`UPDATE brz_unclaimed_deposits
|
|
783
793
|
SET instant_claim_status = $1
|
|
784
794
|
WHERE user_id = $2 AND txid = $3 AND vout = $4`,
|
|
785
|
-
[
|
|
795
|
+
[toJson(payload.status), this.identity, txid, vout]
|
|
786
796
|
);
|
|
787
797
|
} else if (payload.type === "refundBroadcastState") {
|
|
788
798
|
await this.pool.query(
|
|
@@ -790,7 +800,7 @@ class PostgresStorage {
|
|
|
790
800
|
SET refund_state = $1
|
|
791
801
|
WHERE user_id = $2 AND txid = $3 AND vout = $4 AND refund_tx_id = $5`,
|
|
792
802
|
[
|
|
793
|
-
|
|
803
|
+
toJson(payload.state),
|
|
794
804
|
this.identity,
|
|
795
805
|
txid,
|
|
796
806
|
vout,
|
|
@@ -803,7 +813,7 @@ class PostgresStorage {
|
|
|
803
813
|
SET max_claim_fee = $1
|
|
804
814
|
WHERE user_id = $2 AND txid = $3 AND vout = $4`,
|
|
805
815
|
[
|
|
806
|
-
payload.maxFee ?
|
|
816
|
+
payload.maxFee ? toJson(payload.maxFee) : null,
|
|
807
817
|
this.identity,
|
|
808
818
|
txid,
|
|
809
819
|
vout,
|
|
@@ -874,6 +884,153 @@ class PostgresStorage {
|
|
|
874
884
|
}
|
|
875
885
|
}
|
|
876
886
|
|
|
887
|
+
async listLeafRecoveries() {
|
|
888
|
+
try {
|
|
889
|
+
const result = await this.pool.query(
|
|
890
|
+
`SELECT l.leaf_id,
|
|
891
|
+
l.chain_checked_at,
|
|
892
|
+
o.txid AS output_txid,
|
|
893
|
+
o.vout AS output_vout,
|
|
894
|
+
o.amount_sats AS output_amount_sats,
|
|
895
|
+
o.script_pubkey AS output_script_pubkey,
|
|
896
|
+
ot.block_height AS output_block_height,
|
|
897
|
+
s.txid AS spend_txid,
|
|
898
|
+
st.block_height AS spend_block_height,
|
|
899
|
+
w.txid AS sweep_txid,
|
|
900
|
+
wt.block_height AS sweep_block_height,
|
|
901
|
+
r.txid AS recovery_txid,
|
|
902
|
+
r.transaction_hex AS recovery_transaction_hex,
|
|
903
|
+
r.output_amount_sats AS recovery_output_amount_sats
|
|
904
|
+
FROM brz_leaf_recoveries l
|
|
905
|
+
LEFT JOIN brz_watchtower_exit_outputs o
|
|
906
|
+
ON o.user_id = l.user_id AND o.leaf_id = l.leaf_id
|
|
907
|
+
LEFT JOIN brz_chain_transactions ot
|
|
908
|
+
ON ot.user_id = o.user_id AND ot.txid = o.txid
|
|
909
|
+
LEFT JOIN brz_watchtower_exit_spends s
|
|
910
|
+
ON s.user_id = l.user_id AND s.leaf_id = l.leaf_id
|
|
911
|
+
LEFT JOIN brz_chain_transactions st
|
|
912
|
+
ON st.user_id = s.user_id AND st.txid = s.txid
|
|
913
|
+
LEFT JOIN brz_unilateral_exit_sweeps w
|
|
914
|
+
ON w.user_id = l.user_id AND w.leaf_id = l.leaf_id
|
|
915
|
+
LEFT JOIN brz_chain_transactions wt
|
|
916
|
+
ON wt.user_id = w.user_id AND wt.txid = w.txid
|
|
917
|
+
LEFT JOIN brz_watchtower_exit_recoveries r
|
|
918
|
+
ON r.user_id = l.user_id AND r.leaf_id = l.leaf_id
|
|
919
|
+
WHERE l.user_id = $1
|
|
920
|
+
ORDER BY l.leaf_id, r.txid`,
|
|
921
|
+
[this.identity]
|
|
922
|
+
);
|
|
923
|
+
return leafRecoveriesFromRows(result.rows);
|
|
924
|
+
} catch (error) {
|
|
925
|
+
throw new StorageError(
|
|
926
|
+
`Failed to list leaf recoveries: ${error.message}`,
|
|
927
|
+
error
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
async updateLeafRecovery(update) {
|
|
933
|
+
try {
|
|
934
|
+
const leafId = update.leafId;
|
|
935
|
+
await this._withTransaction(async (client) => {
|
|
936
|
+
const setChainTransaction = (txid, blockHeight) =>
|
|
937
|
+
client.query(
|
|
938
|
+
`INSERT INTO brz_chain_transactions (user_id, txid, block_height)
|
|
939
|
+
VALUES ($1, $2, $3)
|
|
940
|
+
ON CONFLICT(user_id, txid) DO UPDATE SET block_height = EXCLUDED.block_height`,
|
|
941
|
+
[this.identity, txid, blockHeight]
|
|
942
|
+
);
|
|
943
|
+
|
|
944
|
+
// PostgreSQL locks the leaf's row, so writers of one leaf do not interleave.
|
|
945
|
+
await client.query(
|
|
946
|
+
`INSERT INTO brz_leaf_recoveries (user_id, leaf_id) VALUES ($1, $2)
|
|
947
|
+
ON CONFLICT(user_id, leaf_id) DO UPDATE SET leaf_id = EXCLUDED.leaf_id`,
|
|
948
|
+
[this.identity, leafId]
|
|
949
|
+
);
|
|
950
|
+
if (update.chainCheckedAt != null) {
|
|
951
|
+
await client.query(
|
|
952
|
+
`UPDATE brz_leaf_recoveries
|
|
953
|
+
SET chain_checked_at = GREATEST(COALESCE(chain_checked_at, 0), $3)
|
|
954
|
+
WHERE user_id = $1 AND leaf_id = $2`,
|
|
955
|
+
[this.identity, leafId, String(update.chainCheckedAt)]
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
if (update.watchtowerExitOutput != null) {
|
|
959
|
+
const output = update.watchtowerExitOutput;
|
|
960
|
+
for (const table of [
|
|
961
|
+
"brz_watchtower_exit_recoveries",
|
|
962
|
+
"brz_watchtower_exit_spends",
|
|
963
|
+
]) {
|
|
964
|
+
await client.query(
|
|
965
|
+
`DELETE FROM ${table}
|
|
966
|
+
WHERE user_id = $1 AND leaf_id = $2 AND EXISTS (
|
|
967
|
+
SELECT 1 FROM brz_watchtower_exit_outputs
|
|
968
|
+
WHERE user_id = $1 AND leaf_id = $2 AND (txid <> $3 OR vout <> $4))`,
|
|
969
|
+
[this.identity, leafId, output.txid, output.vout]
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
await setChainTransaction(output.txid, output.blockHeight);
|
|
973
|
+
await client.query(
|
|
974
|
+
`INSERT INTO brz_watchtower_exit_outputs
|
|
975
|
+
(user_id, leaf_id, txid, vout, amount_sats, script_pubkey)
|
|
976
|
+
VALUES ($1, $2, $3, $4, $5, $6)
|
|
977
|
+
ON CONFLICT(user_id, leaf_id) DO UPDATE SET
|
|
978
|
+
txid = EXCLUDED.txid,
|
|
979
|
+
vout = EXCLUDED.vout,
|
|
980
|
+
amount_sats = EXCLUDED.amount_sats,
|
|
981
|
+
script_pubkey = EXCLUDED.script_pubkey`,
|
|
982
|
+
[
|
|
983
|
+
this.identity,
|
|
984
|
+
leafId,
|
|
985
|
+
output.txid,
|
|
986
|
+
output.vout,
|
|
987
|
+
String(output.amountSats),
|
|
988
|
+
output.scriptPubkey,
|
|
989
|
+
]
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
if (update.watchtowerExitRecovery != null) {
|
|
993
|
+
const recovery = update.watchtowerExitRecovery;
|
|
994
|
+
await client.query(
|
|
995
|
+
`INSERT INTO brz_watchtower_exit_recoveries
|
|
996
|
+
(user_id, leaf_id, txid, transaction_hex, output_amount_sats)
|
|
997
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
998
|
+
ON CONFLICT(user_id, leaf_id, txid) DO NOTHING`,
|
|
999
|
+
[
|
|
1000
|
+
this.identity,
|
|
1001
|
+
leafId,
|
|
1002
|
+
recovery.txid,
|
|
1003
|
+
recovery.transactionHex,
|
|
1004
|
+
String(recovery.outputAmountSats),
|
|
1005
|
+
]
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
for (const [table, chainTransaction] of [
|
|
1009
|
+
["brz_watchtower_exit_spends", update.watchtowerExitSpend],
|
|
1010
|
+
["brz_unilateral_exit_sweeps", update.unilateralExitSweep],
|
|
1011
|
+
]) {
|
|
1012
|
+
if (chainTransaction == null) {
|
|
1013
|
+
continue;
|
|
1014
|
+
}
|
|
1015
|
+
await setChainTransaction(
|
|
1016
|
+
chainTransaction.txid,
|
|
1017
|
+
chainTransaction.blockHeight
|
|
1018
|
+
);
|
|
1019
|
+
await client.query(
|
|
1020
|
+
`INSERT INTO ${table} (user_id, leaf_id, txid) VALUES ($1, $2, $3)
|
|
1021
|
+
ON CONFLICT(user_id, leaf_id) DO UPDATE SET txid = EXCLUDED.txid`,
|
|
1022
|
+
[this.identity, leafId, chainTransaction.txid]
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
1026
|
+
} catch (error) {
|
|
1027
|
+
throw new StorageError(
|
|
1028
|
+
`Failed to update leaf recovery '${update.leafId}': ${error.message}`,
|
|
1029
|
+
error
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
877
1034
|
async setLnurlMetadata(metadata) {
|
|
878
1035
|
try {
|
|
879
1036
|
await this._withTransaction(async (client) => {
|
|
@@ -1235,7 +1392,7 @@ class PostgresStorage {
|
|
|
1235
1392
|
record.id.dataId,
|
|
1236
1393
|
record.schemaVersion,
|
|
1237
1394
|
Math.floor(Date.now() / 1000),
|
|
1238
|
-
|
|
1395
|
+
toJson(record.updatedFields),
|
|
1239
1396
|
revision.toString(),
|
|
1240
1397
|
]
|
|
1241
1398
|
);
|
|
@@ -1295,7 +1452,7 @@ class PostgresStorage {
|
|
|
1295
1452
|
record.revision.toString(),
|
|
1296
1453
|
record.schemaVersion,
|
|
1297
1454
|
Math.floor(Date.now() / 1000),
|
|
1298
|
-
|
|
1455
|
+
toJson(record.data),
|
|
1299
1456
|
]
|
|
1300
1457
|
);
|
|
1301
1458
|
|
|
@@ -1426,7 +1583,7 @@ class PostgresStorage {
|
|
|
1426
1583
|
record.id.dataId,
|
|
1427
1584
|
record.schemaVersion,
|
|
1428
1585
|
Math.floor(Date.now() / 1000),
|
|
1429
|
-
|
|
1586
|
+
toJson(record.data),
|
|
1430
1587
|
record.revision.toString(),
|
|
1431
1588
|
]
|
|
1432
1589
|
);
|
|
@@ -1613,7 +1770,7 @@ class PostgresStorage {
|
|
|
1613
1770
|
record.revision.toString(),
|
|
1614
1771
|
record.schemaVersion,
|
|
1615
1772
|
Math.floor(Date.now() / 1000),
|
|
1616
|
-
|
|
1773
|
+
toJson(record.data),
|
|
1617
1774
|
]
|
|
1618
1775
|
);
|
|
1619
1776
|
|
|
@@ -1648,6 +1805,58 @@ function crossChainSwapFromRow(row) {
|
|
|
1648
1805
|
};
|
|
1649
1806
|
}
|
|
1650
1807
|
|
|
1808
|
+
/**
|
|
1809
|
+
* A row holds a leaf recovery and one of its watchtower exit recoveries, so
|
|
1810
|
+
* a leaf recovery with several of them spans several rows.
|
|
1811
|
+
*/
|
|
1812
|
+
function leafRecoveriesFromRows(rows) {
|
|
1813
|
+
const leafRecoveries = [];
|
|
1814
|
+
for (const row of rows) {
|
|
1815
|
+
let leafRecovery = leafRecoveries[leafRecoveries.length - 1];
|
|
1816
|
+
if (leafRecovery?.leafId !== row.leaf_id) {
|
|
1817
|
+
leafRecovery = {
|
|
1818
|
+
leafId: row.leaf_id,
|
|
1819
|
+
chainCheckedAt:
|
|
1820
|
+
row.chain_checked_at != null ? BigInt(row.chain_checked_at) : null,
|
|
1821
|
+
watchtowerExitOutput:
|
|
1822
|
+
row.output_block_height != null
|
|
1823
|
+
? {
|
|
1824
|
+
txid: row.output_txid,
|
|
1825
|
+
vout: Number(row.output_vout),
|
|
1826
|
+
amountSats: BigInt(row.output_amount_sats),
|
|
1827
|
+
scriptPubkey: row.output_script_pubkey,
|
|
1828
|
+
blockHeight: Number(row.output_block_height),
|
|
1829
|
+
}
|
|
1830
|
+
: null,
|
|
1831
|
+
watchtowerExitRecoveries: [],
|
|
1832
|
+
watchtowerExitSpend:
|
|
1833
|
+
row.spend_block_height != null
|
|
1834
|
+
? {
|
|
1835
|
+
txid: row.spend_txid,
|
|
1836
|
+
blockHeight: Number(row.spend_block_height),
|
|
1837
|
+
}
|
|
1838
|
+
: null,
|
|
1839
|
+
unilateralExitSweep:
|
|
1840
|
+
row.sweep_block_height != null
|
|
1841
|
+
? {
|
|
1842
|
+
txid: row.sweep_txid,
|
|
1843
|
+
blockHeight: Number(row.sweep_block_height),
|
|
1844
|
+
}
|
|
1845
|
+
: null,
|
|
1846
|
+
};
|
|
1847
|
+
leafRecoveries.push(leafRecovery);
|
|
1848
|
+
}
|
|
1849
|
+
if (row.recovery_txid != null) {
|
|
1850
|
+
leafRecovery.watchtowerExitRecoveries.push({
|
|
1851
|
+
txid: row.recovery_txid,
|
|
1852
|
+
transactionHex: row.recovery_transaction_hex,
|
|
1853
|
+
outputAmountSats: BigInt(row.recovery_output_amount_sats),
|
|
1854
|
+
});
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
return leafRecoveries;
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1651
1860
|
/**
|
|
1652
1861
|
* Creates a PostgresStorageConfig with the given connection string and default pool settings.
|
|
1653
1862
|
*
|
|
@@ -563,6 +563,55 @@ class PostgresMigrationManager {
|
|
|
563
563
|
`ALTER TABLE brz_unclaimed_deposits ADD COLUMN max_claim_fee JSONB`,
|
|
564
564
|
],
|
|
565
565
|
},
|
|
566
|
+
{
|
|
567
|
+
// Leaf recoveries. What the chain service reported about leaves with
|
|
568
|
+
// funds to recover on-chain, and the recoveries the operators co-signed.
|
|
569
|
+
// Every block height is in brz_chain_transactions, keyed by txid.
|
|
570
|
+
name: "Add leaf recovery tables",
|
|
571
|
+
sql: [
|
|
572
|
+
`CREATE TABLE IF NOT EXISTS brz_leaf_recoveries (
|
|
573
|
+
user_id BYTEA NOT NULL,
|
|
574
|
+
leaf_id TEXT NOT NULL,
|
|
575
|
+
chain_checked_at BIGINT,
|
|
576
|
+
PRIMARY KEY (user_id, leaf_id)
|
|
577
|
+
)`,
|
|
578
|
+
`CREATE TABLE IF NOT EXISTS brz_chain_transactions (
|
|
579
|
+
user_id BYTEA NOT NULL,
|
|
580
|
+
txid TEXT NOT NULL,
|
|
581
|
+
block_height BIGINT NOT NULL,
|
|
582
|
+
PRIMARY KEY (user_id, txid)
|
|
583
|
+
)`,
|
|
584
|
+
`CREATE TABLE IF NOT EXISTS brz_watchtower_exit_outputs (
|
|
585
|
+
user_id BYTEA NOT NULL,
|
|
586
|
+
leaf_id TEXT NOT NULL,
|
|
587
|
+
txid TEXT NOT NULL,
|
|
588
|
+
vout BIGINT NOT NULL,
|
|
589
|
+
amount_sats BIGINT NOT NULL,
|
|
590
|
+
script_pubkey TEXT NOT NULL,
|
|
591
|
+
PRIMARY KEY (user_id, leaf_id)
|
|
592
|
+
)`,
|
|
593
|
+
`CREATE TABLE IF NOT EXISTS brz_watchtower_exit_recoveries (
|
|
594
|
+
user_id BYTEA NOT NULL,
|
|
595
|
+
leaf_id TEXT NOT NULL,
|
|
596
|
+
txid TEXT NOT NULL,
|
|
597
|
+
transaction_hex TEXT NOT NULL,
|
|
598
|
+
output_amount_sats BIGINT NOT NULL,
|
|
599
|
+
PRIMARY KEY (user_id, leaf_id, txid)
|
|
600
|
+
)`,
|
|
601
|
+
`CREATE TABLE IF NOT EXISTS brz_watchtower_exit_spends (
|
|
602
|
+
user_id BYTEA NOT NULL,
|
|
603
|
+
leaf_id TEXT NOT NULL,
|
|
604
|
+
txid TEXT NOT NULL,
|
|
605
|
+
PRIMARY KEY (user_id, leaf_id)
|
|
606
|
+
)`,
|
|
607
|
+
`CREATE TABLE IF NOT EXISTS brz_unilateral_exit_sweeps (
|
|
608
|
+
user_id BYTEA NOT NULL,
|
|
609
|
+
leaf_id TEXT NOT NULL,
|
|
610
|
+
txid TEXT NOT NULL,
|
|
611
|
+
PRIMARY KEY (user_id, leaf_id)
|
|
612
|
+
)`,
|
|
613
|
+
],
|
|
614
|
+
},
|
|
566
615
|
];
|
|
567
616
|
}
|
|
568
617
|
}
|
|
@@ -14,10 +14,12 @@ try {
|
|
|
14
14
|
try {
|
|
15
15
|
pg = require("pg");
|
|
16
16
|
} catch (fallbackError) {
|
|
17
|
-
|
|
17
|
+
const loadError = new Error(
|
|
18
18
|
`pg not found. Please install it in your project: npm install pg@^8.18.0\n` +
|
|
19
19
|
`Original error: ${error.message}\nFallback error: ${fallbackError.message}`
|
|
20
20
|
);
|
|
21
|
+
loadError.code = fallbackError.code;
|
|
22
|
+
throw loadError;
|
|
21
23
|
}
|
|
22
24
|
}
|
|
23
25
|
|
|
@@ -14,10 +14,12 @@ try {
|
|
|
14
14
|
try {
|
|
15
15
|
pg = require("pg");
|
|
16
16
|
} catch (fallbackError) {
|
|
17
|
-
|
|
17
|
+
const loadError = new Error(
|
|
18
18
|
`pg not found. Please install it in your project: npm install pg@^8.18.0\n` +
|
|
19
19
|
`Original error: ${error.message}\nFallback error: ${fallbackError.message}`
|
|
20
20
|
);
|
|
21
|
+
loadError.code = fallbackError.code;
|
|
22
|
+
throw loadError;
|
|
21
23
|
}
|
|
22
24
|
}
|
|
23
25
|
|
|
@@ -475,6 +477,28 @@ class PostgresTreeStore {
|
|
|
475
477
|
}
|
|
476
478
|
}
|
|
477
479
|
|
|
480
|
+
/**
|
|
481
|
+
* Return the leaves, reserved or not, whose status is one of `statuses`.
|
|
482
|
+
* @param {Array<string>} statuses
|
|
483
|
+
* @returns {Promise<Array<object>>}
|
|
484
|
+
*/
|
|
485
|
+
async getLeavesWithStatus(statuses) {
|
|
486
|
+
try {
|
|
487
|
+
if (!statuses || statuses.length === 0) return [];
|
|
488
|
+
const result = await this.pool.query(
|
|
489
|
+
"SELECT data FROM brz_tree_leaves WHERE user_id = $1 AND status = ANY($2)",
|
|
490
|
+
[this.identity, statuses]
|
|
491
|
+
);
|
|
492
|
+
return result.rows.map((row) => row.data);
|
|
493
|
+
} catch (error) {
|
|
494
|
+
if (error instanceof TreeStoreError) throw error;
|
|
495
|
+
throw new TreeStoreError(
|
|
496
|
+
`Failed to get leaves with status: ${error.message}`,
|
|
497
|
+
error
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
478
502
|
/**
|
|
479
503
|
* Set leaves from a refresh operation.
|
|
480
504
|
* @param {Array} leaves - Available leaves from operators
|
|
@@ -346,6 +346,16 @@ class TreeStoreMigrationManager {
|
|
|
346
346
|
signing_public_key = data->'signing_keyshare'->>'public_key'`,
|
|
347
347
|
],
|
|
348
348
|
},
|
|
349
|
+
{
|
|
350
|
+
// Mirrors Rust migration 6 in spark-postgres/src/tree_store.rs. Indexes
|
|
351
|
+
// the leaves outside the `Available` status, so reading them by status
|
|
352
|
+
// does not scan the spendable pool.
|
|
353
|
+
name: "Index leaves outside the Available status",
|
|
354
|
+
sql: [
|
|
355
|
+
`CREATE INDEX IF NOT EXISTS brz_idx_tree_leaves_user_unavailable
|
|
356
|
+
ON brz_tree_leaves(user_id, status) WHERE status <> 'Available'`,
|
|
357
|
+
],
|
|
358
|
+
},
|
|
349
359
|
];
|
|
350
360
|
}
|
|
351
361
|
}
|