@breeztech/breez-sdk-spark 0.15.0 → 0.16.1-dev1
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 +511 -215
- package/bundler/breez_sdk_spark_wasm.js +1 -1
- package/bundler/breez_sdk_spark_wasm_bg.js +567 -414
- package/bundler/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/bundler/breez_sdk_spark_wasm_bg.wasm.d.ts +55 -46
- package/bundler/storage/index.js +205 -15
- package/deno/breez_sdk_spark_wasm.d.ts +511 -215
- package/deno/breez_sdk_spark_wasm.js +567 -414
- package/deno/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/deno/breez_sdk_spark_wasm_bg.wasm.d.ts +55 -46
- package/nodejs/breez_sdk_spark_wasm.d.ts +511 -215
- package/nodejs/breez_sdk_spark_wasm.js +578 -421
- package/nodejs/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/nodejs/breez_sdk_spark_wasm_bg.wasm.d.ts +55 -46
- package/nodejs/index.js +10 -10
- package/nodejs/index.mjs +12 -8
- package/nodejs/mysql-session-store/errors.cjs +13 -0
- package/nodejs/{mysql-session-manager → mysql-session-store}/index.cjs +24 -21
- package/nodejs/{mysql-session-manager → mysql-session-store}/migrations.cjs +17 -11
- package/nodejs/mysql-session-store/package.json +9 -0
- package/nodejs/mysql-storage/index.cjs +229 -111
- package/nodejs/mysql-storage/migrations.cjs +37 -2
- package/nodejs/mysql-token-store/index.cjs +99 -79
- package/nodejs/mysql-token-store/migrations.cjs +59 -2
- package/nodejs/mysql-tree-store/index.cjs +15 -9
- package/nodejs/mysql-tree-store/migrations.cjs +16 -2
- package/nodejs/package.json +2 -2
- package/nodejs/postgres-session-store/errors.cjs +13 -0
- package/nodejs/{postgres-session-manager → postgres-session-store}/index.cjs +23 -23
- package/nodejs/{postgres-session-manager → postgres-session-store}/migrations.cjs +14 -14
- package/nodejs/postgres-session-store/package.json +9 -0
- package/nodejs/postgres-storage/index.cjs +174 -107
- package/nodejs/postgres-storage/migrations.cjs +24 -0
- package/nodejs/postgres-token-store/index.cjs +89 -64
- package/nodejs/postgres-token-store/migrations.cjs +44 -0
- package/nodejs/storage/index.cjs +167 -113
- package/nodejs/storage/migrations.cjs +23 -0
- package/package.json +6 -1
- package/ssr/index.js +52 -28
- package/web/breez_sdk_spark_wasm.d.ts +566 -261
- package/web/breez_sdk_spark_wasm.js +567 -414
- package/web/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/web/breez_sdk_spark_wasm_bg.wasm.d.ts +55 -46
- package/web/passkey-prf-provider/index.d.ts +203 -0
- package/web/passkey-prf-provider/index.js +733 -0
- package/web/storage/index.js +205 -15
- package/nodejs/mysql-session-manager/errors.cjs +0 -13
- package/nodejs/mysql-session-manager/package.json +0 -9
- package/nodejs/postgres-session-manager/errors.cjs +0 -13
- package/nodejs/postgres-session-manager/package.json +0 -9
|
@@ -226,25 +226,27 @@ class MysqlTokenStore {
|
|
|
226
226
|
);
|
|
227
227
|
|
|
228
228
|
const [spentRows] = await conn.query(
|
|
229
|
-
"SELECT
|
|
229
|
+
"SELECT prev_tx_hash, prev_tx_vout FROM brz_token_spent_outputs WHERE user_id = ? AND spent_at >= ?",
|
|
230
230
|
[this.identity, refreshTimestamp]
|
|
231
231
|
);
|
|
232
|
-
const
|
|
232
|
+
const spentOutpoints = new Set(
|
|
233
|
+
spentRows.map((r) => `${r.prev_tx_hash}:${r.prev_tx_vout}`)
|
|
234
|
+
);
|
|
233
235
|
|
|
234
236
|
await conn.query(
|
|
235
237
|
"DELETE FROM brz_token_outputs WHERE user_id = ? AND reservation_id IS NULL AND added_at < ?",
|
|
236
238
|
[this.identity, refreshTimestamp]
|
|
237
239
|
);
|
|
238
240
|
|
|
239
|
-
const
|
|
241
|
+
const incomingOutpoints = new Set();
|
|
240
242
|
for (const to of tokenOutputs) {
|
|
241
243
|
for (const o of to.outputs) {
|
|
242
|
-
|
|
244
|
+
incomingOutpoints.add(`${o.prevTxHash}:${o.prevTxVout}`);
|
|
243
245
|
}
|
|
244
246
|
}
|
|
245
247
|
|
|
246
248
|
const [reservedRows] = await conn.query(
|
|
247
|
-
`SELECT r.id, o.
|
|
249
|
+
`SELECT r.id, o.prev_tx_hash, o.prev_tx_vout
|
|
248
250
|
FROM brz_token_reservations r
|
|
249
251
|
JOIN brz_token_outputs o
|
|
250
252
|
ON o.reservation_id = r.id AND o.user_id = r.user_id
|
|
@@ -257,21 +259,23 @@ class MysqlTokenStore {
|
|
|
257
259
|
if (!reservationOutputs.has(row.id)) {
|
|
258
260
|
reservationOutputs.set(row.id, []);
|
|
259
261
|
}
|
|
260
|
-
reservationOutputs.get(row.id).push(row.
|
|
262
|
+
reservationOutputs.get(row.id).push([row.prev_tx_hash, row.prev_tx_vout]);
|
|
261
263
|
}
|
|
262
264
|
|
|
263
265
|
const reservationsToDelete = [];
|
|
264
|
-
const
|
|
265
|
-
for (const [reservationId,
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
for (const
|
|
271
|
-
if (!
|
|
272
|
-
|
|
266
|
+
const outpointsToRemoveFromReservation = [];
|
|
267
|
+
for (const [reservationId, outpoints] of reservationOutputs) {
|
|
268
|
+
const hasValid = outpoints.some(([h, v]) =>
|
|
269
|
+
incomingOutpoints.has(`${h}:${v}`)
|
|
270
|
+
);
|
|
271
|
+
if (hasValid) {
|
|
272
|
+
for (const [h, v] of outpoints) {
|
|
273
|
+
if (!incomingOutpoints.has(`${h}:${v}`)) {
|
|
274
|
+
outpointsToRemoveFromReservation.push([h, v]);
|
|
273
275
|
}
|
|
274
276
|
}
|
|
277
|
+
} else {
|
|
278
|
+
reservationsToDelete.push(reservationId);
|
|
275
279
|
}
|
|
276
280
|
}
|
|
277
281
|
|
|
@@ -287,20 +291,25 @@ class MysqlTokenStore {
|
|
|
287
291
|
);
|
|
288
292
|
}
|
|
289
293
|
|
|
290
|
-
if (
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
+
if (outpointsToRemoveFromReservation.length > 0) {
|
|
295
|
+
const pairPlaceholders = outpointsToRemoveFromReservation
|
|
296
|
+
.map(() => "(?, ?)")
|
|
297
|
+
.join(", ");
|
|
298
|
+
const params = [this.identity];
|
|
299
|
+
for (const [h, v] of outpointsToRemoveFromReservation) {
|
|
300
|
+
params.push(h, v);
|
|
301
|
+
}
|
|
294
302
|
await conn.query(
|
|
295
|
-
`DELETE FROM brz_token_outputs WHERE user_id = ?
|
|
296
|
-
|
|
303
|
+
`DELETE FROM brz_token_outputs WHERE user_id = ?
|
|
304
|
+
AND (prev_tx_hash, prev_tx_vout) IN (${pairPlaceholders})`,
|
|
305
|
+
params
|
|
297
306
|
);
|
|
298
307
|
|
|
299
308
|
const [emptyRows] = await conn.query(
|
|
300
309
|
`SELECT r.id FROM brz_token_reservations r
|
|
301
310
|
LEFT JOIN brz_token_outputs o
|
|
302
311
|
ON o.reservation_id = r.id AND o.user_id = r.user_id
|
|
303
|
-
WHERE r.user_id = ? AND o.
|
|
312
|
+
WHERE r.user_id = ? AND o.prev_tx_hash IS NULL`,
|
|
304
313
|
[this.identity]
|
|
305
314
|
);
|
|
306
315
|
const emptyIds = emptyRows.map((r) => r.id);
|
|
@@ -314,10 +323,12 @@ class MysqlTokenStore {
|
|
|
314
323
|
}
|
|
315
324
|
|
|
316
325
|
const [reservedOutputRows] = await conn.query(
|
|
317
|
-
"SELECT
|
|
326
|
+
"SELECT prev_tx_hash, prev_tx_vout FROM brz_token_outputs WHERE user_id = ? AND reservation_id IS NOT NULL",
|
|
318
327
|
[this.identity]
|
|
319
328
|
);
|
|
320
|
-
const
|
|
329
|
+
const reservedOutpoints = new Set(
|
|
330
|
+
reservedOutputRows.map((r) => `${r.prev_tx_hash}:${r.prev_tx_vout}`)
|
|
331
|
+
);
|
|
321
332
|
|
|
322
333
|
await conn.query(
|
|
323
334
|
`DELETE FROM brz_token_metadata
|
|
@@ -332,10 +343,8 @@ class MysqlTokenStore {
|
|
|
332
343
|
await this._upsertMetadata(conn, to.metadata);
|
|
333
344
|
|
|
334
345
|
for (const output of to.outputs) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
spentIds.has(output.output.id)
|
|
338
|
-
) {
|
|
346
|
+
const outpoint = `${output.prevTxHash}:${output.prevTxVout}`;
|
|
347
|
+
if (reservedOutpoints.has(outpoint) || spentOutpoints.has(outpoint)) {
|
|
339
348
|
continue;
|
|
340
349
|
}
|
|
341
350
|
await this._insertSingleOutput(
|
|
@@ -410,7 +419,7 @@ class MysqlTokenStore {
|
|
|
410
419
|
const [rows] = await this.pool.query(
|
|
411
420
|
`SELECT m.identifier, m.issuer_public_key, m.name, m.ticker, m.decimals,
|
|
412
421
|
m.max_supply, m.is_freezable, m.creation_entity_public_key,
|
|
413
|
-
o.
|
|
422
|
+
o.owner_public_key, o.revocation_commitment,
|
|
414
423
|
o.withdraw_bond_sats, o.withdraw_relative_block_locktime,
|
|
415
424
|
o.token_public_key, o.token_amount, o.token_identifier,
|
|
416
425
|
o.prev_tx_hash, o.prev_tx_vout, o.reservation_id,
|
|
@@ -439,7 +448,7 @@ class MysqlTokenStore {
|
|
|
439
448
|
|
|
440
449
|
const entry = map.get(row.identifier);
|
|
441
450
|
|
|
442
|
-
if (!row.
|
|
451
|
+
if (!row.prev_tx_hash) {
|
|
443
452
|
continue;
|
|
444
453
|
}
|
|
445
454
|
|
|
@@ -482,7 +491,7 @@ class MysqlTokenStore {
|
|
|
482
491
|
const [rows] = await this.pool.query(
|
|
483
492
|
`SELECT m.identifier, m.issuer_public_key, m.name, m.ticker, m.decimals,
|
|
484
493
|
m.max_supply, m.is_freezable, m.creation_entity_public_key,
|
|
485
|
-
o.
|
|
494
|
+
o.owner_public_key, o.revocation_commitment,
|
|
486
495
|
o.withdraw_bond_sats, o.withdraw_relative_block_locktime,
|
|
487
496
|
o.token_public_key, o.token_amount, o.token_identifier,
|
|
488
497
|
o.prev_tx_hash, o.prev_tx_vout, o.reservation_id,
|
|
@@ -510,7 +519,7 @@ class MysqlTokenStore {
|
|
|
510
519
|
};
|
|
511
520
|
|
|
512
521
|
for (const row of rows) {
|
|
513
|
-
if (!row.
|
|
522
|
+
if (!row.prev_tx_hash) {
|
|
514
523
|
continue;
|
|
515
524
|
}
|
|
516
525
|
|
|
@@ -543,23 +552,20 @@ class MysqlTokenStore {
|
|
|
543
552
|
*/
|
|
544
553
|
async updateTokenOutputs(outputsToRemove, outputsToAdd) {
|
|
545
554
|
try {
|
|
546
|
-
|
|
555
|
+
// Serialize against the other token-store mutators (refresh, reservation,
|
|
556
|
+
// finalization), which take the same per-user advisory lock.
|
|
557
|
+
await this._withWriteTransaction(async (conn) => {
|
|
547
558
|
// 1. Remove spent outputs and mark as spent.
|
|
548
559
|
if (outputsToRemove && outputsToRemove.length > 0) {
|
|
549
560
|
for (const [txHash, vout] of outputsToRemove) {
|
|
550
|
-
const [
|
|
551
|
-
"
|
|
561
|
+
const [result] = await conn.query(
|
|
562
|
+
"DELETE FROM brz_token_outputs WHERE user_id = ? AND prev_tx_hash = ? AND prev_tx_vout = ?",
|
|
552
563
|
[this.identity, txHash, vout]
|
|
553
564
|
);
|
|
554
|
-
if (
|
|
555
|
-
const outputId = rows[0].id;
|
|
556
|
-
await conn.query(
|
|
557
|
-
"DELETE FROM brz_token_outputs WHERE user_id = ? AND id = ?",
|
|
558
|
-
[this.identity, outputId]
|
|
559
|
-
);
|
|
565
|
+
if (result.affectedRows > 0) {
|
|
560
566
|
await conn.query(
|
|
561
|
-
"INSERT IGNORE INTO brz_token_spent_outputs (user_id,
|
|
562
|
-
[this.identity,
|
|
567
|
+
"INSERT IGNORE INTO brz_token_spent_outputs (user_id, prev_tx_hash, prev_tx_vout, spent_at) VALUES (?, ?, ?, UTC_TIMESTAMP(6))",
|
|
568
|
+
[this.identity, txHash, vout]
|
|
563
569
|
);
|
|
564
570
|
}
|
|
565
571
|
}
|
|
@@ -569,12 +575,17 @@ class MysqlTokenStore {
|
|
|
569
575
|
if (outputsToAdd) {
|
|
570
576
|
await this._upsertMetadata(conn, outputsToAdd.metadata);
|
|
571
577
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
578
|
+
if (outputsToAdd.outputs.length > 0) {
|
|
579
|
+
const pairPlaceholders = outputsToAdd.outputs
|
|
580
|
+
.map(() => "(?, ?)")
|
|
581
|
+
.join(", ");
|
|
582
|
+
const params = [this.identity];
|
|
583
|
+
for (const o of outputsToAdd.outputs) {
|
|
584
|
+
params.push(o.prevTxHash, o.prevTxVout);
|
|
585
|
+
}
|
|
575
586
|
await conn.query(
|
|
576
|
-
`DELETE FROM brz_token_spent_outputs WHERE user_id = ? AND
|
|
577
|
-
|
|
587
|
+
`DELETE FROM brz_token_spent_outputs WHERE user_id = ? AND (prev_tx_hash, prev_tx_vout) IN (${pairPlaceholders})`,
|
|
588
|
+
params
|
|
578
589
|
);
|
|
579
590
|
}
|
|
580
591
|
|
|
@@ -636,7 +647,7 @@ class MysqlTokenStore {
|
|
|
636
647
|
const metadata = this._metadataFromRow(metadataRows[0]);
|
|
637
648
|
|
|
638
649
|
const [outputRows] = await conn.query(
|
|
639
|
-
`SELECT o.
|
|
650
|
+
`SELECT o.owner_public_key, o.revocation_commitment,
|
|
640
651
|
o.withdraw_bond_sats, o.withdraw_relative_block_locktime,
|
|
641
652
|
o.token_public_key, o.token_amount, o.token_identifier,
|
|
642
653
|
o.prev_tx_hash, o.prev_tx_vout
|
|
@@ -648,10 +659,12 @@ class MysqlTokenStore {
|
|
|
648
659
|
let outputs = outputRows.map((row) => this._outputFromRow(row));
|
|
649
660
|
|
|
650
661
|
if (preferredOutputs && preferredOutputs.length > 0) {
|
|
651
|
-
const
|
|
652
|
-
preferredOutputs.map((p) => p.
|
|
662
|
+
const preferredOutpoints = new Set(
|
|
663
|
+
preferredOutputs.map((p) => `${p.prevTxHash}:${p.prevTxVout}`)
|
|
664
|
+
);
|
|
665
|
+
outputs = outputs.filter((o) =>
|
|
666
|
+
preferredOutpoints.has(`${o.prevTxHash}:${o.prevTxVout}`)
|
|
653
667
|
);
|
|
654
|
-
outputs = outputs.filter((o) => preferredIds.has(o.output.id));
|
|
655
668
|
}
|
|
656
669
|
|
|
657
670
|
let selectedOutputs;
|
|
@@ -727,16 +740,22 @@ class MysqlTokenStore {
|
|
|
727
740
|
const reservationId = this._generateId();
|
|
728
741
|
|
|
729
742
|
await conn.query(
|
|
730
|
-
"INSERT INTO brz_token_reservations (user_id, id, purpose) VALUES (?, ?,
|
|
743
|
+
"INSERT INTO brz_token_reservations (user_id, id, purpose, created_at) VALUES (?, ?, ?, UTC_TIMESTAMP(6))",
|
|
731
744
|
[this.identity, reservationId, purpose]
|
|
732
745
|
);
|
|
733
746
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
747
|
+
if (selectedOutputs.length > 0) {
|
|
748
|
+
const pairPlaceholders = selectedOutputs
|
|
749
|
+
.map(() => "(?, ?)")
|
|
750
|
+
.join(", ");
|
|
751
|
+
const params = [reservationId, this.identity];
|
|
752
|
+
for (const o of selectedOutputs) {
|
|
753
|
+
params.push(o.prevTxHash, o.prevTxVout);
|
|
754
|
+
}
|
|
737
755
|
await conn.query(
|
|
738
|
-
`UPDATE brz_token_outputs SET reservation_id = ? WHERE user_id = ?
|
|
739
|
-
|
|
756
|
+
`UPDATE brz_token_outputs SET reservation_id = ? WHERE user_id = ?
|
|
757
|
+
AND (prev_tx_hash, prev_tx_vout) IN (${pairPlaceholders})`,
|
|
758
|
+
params
|
|
740
759
|
);
|
|
741
760
|
}
|
|
742
761
|
|
|
@@ -794,23 +813,22 @@ class MysqlTokenStore {
|
|
|
794
813
|
const isSwap = reservationRows[0].purpose === "Swap";
|
|
795
814
|
|
|
796
815
|
const [reservedRows] = await conn.query(
|
|
797
|
-
"SELECT
|
|
816
|
+
"SELECT prev_tx_hash, prev_tx_vout FROM brz_token_outputs WHERE user_id = ? AND reservation_id = ?",
|
|
798
817
|
[this.identity, id]
|
|
799
818
|
);
|
|
800
|
-
const reservedOutputIds = reservedRows.map((r) => r.id);
|
|
801
819
|
|
|
802
|
-
if (
|
|
803
|
-
const valueClauses = new Array(
|
|
804
|
-
.fill("(?,
|
|
820
|
+
if (reservedRows.length > 0) {
|
|
821
|
+
const valueClauses = new Array(reservedRows.length)
|
|
822
|
+
.fill("(?, ?, ?, UTC_TIMESTAMP(6))")
|
|
805
823
|
.join(", ");
|
|
806
824
|
const params = [];
|
|
807
|
-
for (const
|
|
808
|
-
params.push(this.identity,
|
|
825
|
+
for (const row of reservedRows) {
|
|
826
|
+
params.push(this.identity, row.prev_tx_hash, row.prev_tx_vout);
|
|
809
827
|
}
|
|
810
828
|
// Suppress duplicate-PK errors only.
|
|
811
829
|
await conn.query(
|
|
812
|
-
`INSERT INTO brz_token_spent_outputs (user_id,
|
|
813
|
-
ON DUPLICATE KEY UPDATE
|
|
830
|
+
`INSERT INTO brz_token_spent_outputs (user_id, prev_tx_hash, prev_tx_vout, spent_at) VALUES ${valueClauses}
|
|
831
|
+
ON DUPLICATE KEY UPDATE prev_tx_hash = prev_tx_hash`,
|
|
814
832
|
params
|
|
815
833
|
);
|
|
816
834
|
}
|
|
@@ -828,7 +846,7 @@ class MysqlTokenStore {
|
|
|
828
846
|
// (and thus has no row) gets one created lazily.
|
|
829
847
|
if (isSwap) {
|
|
830
848
|
await conn.query(
|
|
831
|
-
`INSERT INTO brz_token_swap_status (user_id, last_completed_at) VALUES (?,
|
|
849
|
+
`INSERT INTO brz_token_swap_status (user_id, last_completed_at) VALUES (?, UTC_TIMESTAMP(6))
|
|
832
850
|
ON DUPLICATE KEY UPDATE last_completed_at = VALUES(last_completed_at)`,
|
|
833
851
|
[this.identity]
|
|
834
852
|
);
|
|
@@ -854,7 +872,7 @@ class MysqlTokenStore {
|
|
|
854
872
|
|
|
855
873
|
async now() {
|
|
856
874
|
try {
|
|
857
|
-
const [rows] = await this.pool.query("SELECT
|
|
875
|
+
const [rows] = await this.pool.query("SELECT UTC_TIMESTAMP(6) AS now");
|
|
858
876
|
const value = rows[0].now;
|
|
859
877
|
if (value instanceof Date) return value.getTime();
|
|
860
878
|
return new Date(value).getTime();
|
|
@@ -892,14 +910,14 @@ class MysqlTokenStore {
|
|
|
892
910
|
SELECT id FROM (
|
|
893
911
|
SELECT id FROM brz_token_reservations
|
|
894
912
|
WHERE user_id = ?
|
|
895
|
-
AND created_at < DATE_SUB(
|
|
913
|
+
AND created_at < DATE_SUB(UTC_TIMESTAMP(6), INTERVAL ? SECOND)
|
|
896
914
|
) AS stale
|
|
897
915
|
)`,
|
|
898
916
|
[this.identity, this.identity, RESERVATION_TIMEOUT_SECS]
|
|
899
917
|
);
|
|
900
918
|
await conn.query(
|
|
901
919
|
`DELETE FROM brz_token_reservations
|
|
902
|
-
WHERE user_id = ? AND created_at < DATE_SUB(
|
|
920
|
+
WHERE user_id = ? AND created_at < DATE_SUB(UTC_TIMESTAMP(6), INTERVAL ? SECOND)`,
|
|
903
921
|
[this.identity, RESERVATION_TIMEOUT_SECS]
|
|
904
922
|
);
|
|
905
923
|
}
|
|
@@ -933,19 +951,18 @@ class MysqlTokenStore {
|
|
|
933
951
|
}
|
|
934
952
|
|
|
935
953
|
async _insertSingleOutput(conn, tokenIdentifier, output) {
|
|
936
|
-
// ON DUPLICATE KEY UPDATE
|
|
937
|
-
//
|
|
938
|
-
// still propagate.
|
|
954
|
+
// ON DUPLICATE KEY UPDATE prev_tx_hash = prev_tx_hash no-ops on the
|
|
955
|
+
// (user_id, prev_tx_hash, prev_tx_vout) primary key conflict only — unlike
|
|
956
|
+
// INSERT IGNORE, FK / NOT NULL / type errors still propagate.
|
|
939
957
|
await conn.query(
|
|
940
958
|
`INSERT INTO brz_token_outputs
|
|
941
|
-
(user_id,
|
|
959
|
+
(user_id, token_identifier, owner_public_key, revocation_commitment,
|
|
942
960
|
withdraw_bond_sats, withdraw_relative_block_locktime,
|
|
943
961
|
token_public_key, token_amount, prev_tx_hash, prev_tx_vout, added_at)
|
|
944
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
945
|
-
ON DUPLICATE KEY UPDATE
|
|
962
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, UTC_TIMESTAMP(6))
|
|
963
|
+
ON DUPLICATE KEY UPDATE prev_tx_hash = prev_tx_hash`,
|
|
946
964
|
[
|
|
947
965
|
this.identity,
|
|
948
|
-
output.output.id,
|
|
949
966
|
tokenIdentifier,
|
|
950
967
|
output.output.ownerPublicKey,
|
|
951
968
|
output.output.revocationCommitment,
|
|
@@ -975,7 +992,6 @@ class MysqlTokenStore {
|
|
|
975
992
|
_outputFromRow(row) {
|
|
976
993
|
return {
|
|
977
994
|
output: {
|
|
978
|
-
id: row.output_id,
|
|
979
995
|
ownerPublicKey: row.owner_public_key,
|
|
980
996
|
revocationCommitment: row.revocation_commitment,
|
|
981
997
|
withdrawBondSats: Number(row.withdraw_bond_sats),
|
|
@@ -999,6 +1015,10 @@ function createMysqlPool(config) {
|
|
|
999
1015
|
connectTimeout: (config.createTimeoutSecs || 0) * 1000 || 10000,
|
|
1000
1016
|
idleTimeout: (config.recycleTimeoutSecs || 0) * 1000 || 10000,
|
|
1001
1017
|
waitForConnections: true,
|
|
1018
|
+
// Serialize JS `Date` parameters as UTC strings rather than host-local
|
|
1019
|
+
// time. Paired with explicit `UTC_TIMESTAMP(6)` on the server side, this
|
|
1020
|
+
// keeps timestamp comparisons consistent regardless of the host TZ.
|
|
1021
|
+
timezone: "Z",
|
|
1002
1022
|
});
|
|
1003
1023
|
}
|
|
1004
1024
|
|
|
@@ -109,7 +109,7 @@ class MysqlTokenStoreMigrationManager {
|
|
|
109
109
|
await conn.query(`
|
|
110
110
|
CREATE TABLE IF NOT EXISTS \`${TOKEN_MIGRATIONS_TABLE}\` (
|
|
111
111
|
version INT PRIMARY KEY,
|
|
112
|
-
applied_at DATETIME(6) NOT NULL DEFAULT
|
|
112
|
+
applied_at DATETIME(6) NOT NULL DEFAULT (UTC_TIMESTAMP(6))
|
|
113
113
|
)
|
|
114
114
|
`);
|
|
115
115
|
|
|
@@ -132,7 +132,7 @@ class MysqlTokenStoreMigrationManager {
|
|
|
132
132
|
await runMigrationStep(conn, step);
|
|
133
133
|
}
|
|
134
134
|
await conn.query(
|
|
135
|
-
`INSERT INTO \`${TOKEN_MIGRATIONS_TABLE}\` (version) VALUES (
|
|
135
|
+
`INSERT INTO \`${TOKEN_MIGRATIONS_TABLE}\` (version, applied_at) VALUES (?, UTC_TIMESTAMP(6))`,
|
|
136
136
|
[version]
|
|
137
137
|
);
|
|
138
138
|
}
|
|
@@ -447,6 +447,63 @@ class MysqlTokenStoreMigrationManager {
|
|
|
447
447
|
`ALTER TABLE brz_token_swap_status ADD PRIMARY KEY (user_id)`,
|
|
448
448
|
],
|
|
449
449
|
},
|
|
450
|
+
{
|
|
451
|
+
// Pin DATETIME defaults to UTC. Server-side INSERTs already pass
|
|
452
|
+
// `UTC_TIMESTAMP(6)` explicitly; this migration makes the column
|
|
453
|
+
// default match, so any future callsite that omits the column also
|
|
454
|
+
// gets a UTC value rather than a session-TZ-dependent one.
|
|
455
|
+
name: "Pin DATETIME defaults to UTC",
|
|
456
|
+
sql: [
|
|
457
|
+
`ALTER TABLE brz_token_outputs MODIFY COLUMN added_at DATETIME(6) NOT NULL DEFAULT (UTC_TIMESTAMP(6))`,
|
|
458
|
+
`ALTER TABLE brz_token_reservations MODIFY COLUMN created_at DATETIME(6) NOT NULL DEFAULT (UTC_TIMESTAMP(6))`,
|
|
459
|
+
`ALTER TABLE brz_token_spent_outputs MODIFY COLUMN spent_at DATETIME(6) NOT NULL DEFAULT (UTC_TIMESTAMP(6))`,
|
|
460
|
+
`ALTER TABLE brz_token_schema_migrations MODIFY COLUMN applied_at DATETIME(6) NOT NULL DEFAULT (UTC_TIMESTAMP(6))`,
|
|
461
|
+
],
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
// Mirrors Rust migration 4 in spark-mysql/src/token_store.rs.
|
|
465
|
+
// Re-keys brz_token_spent_outputs by (prev_tx_hash, prev_tx_vout) instead
|
|
466
|
+
// of the operator-issued output id. v3 FinalTokenOutput carries no id
|
|
467
|
+
// field, so post-broadcast spent markers only have an outpoint to work
|
|
468
|
+
// with. Existing output_id-keyed rows can't be backfilled (no outpoint
|
|
469
|
+
// stored alongside them), so the table is wiped on upgrade — spent
|
|
470
|
+
// markers are short-lived (5 minute cleanup window) so wiping is
|
|
471
|
+
// equivalent to letting them age out.
|
|
472
|
+
name: "Re-key spent outputs by (prev_tx_hash, prev_tx_vout)",
|
|
473
|
+
sql: [
|
|
474
|
+
`DROP TABLE IF EXISTS brz_token_spent_outputs`,
|
|
475
|
+
`CREATE TABLE brz_token_spent_outputs (
|
|
476
|
+
user_id VARBINARY(33) NOT NULL,
|
|
477
|
+
prev_tx_hash VARCHAR(255) NOT NULL,
|
|
478
|
+
prev_tx_vout INT NOT NULL,
|
|
479
|
+
spent_at DATETIME(6) NOT NULL DEFAULT (UTC_TIMESTAMP(6)),
|
|
480
|
+
PRIMARY KEY (user_id, prev_tx_hash, prev_tx_vout)
|
|
481
|
+
)`,
|
|
482
|
+
],
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
// Mirrors Rust migration 5 in spark-mysql/src/token_store.rs.
|
|
486
|
+
// Re-key brz_token_outputs by (prev_tx_hash, prev_tx_vout) and drop the
|
|
487
|
+
// legacy id column. id already held "{prev_tx_hash}:{vout}", so the
|
|
488
|
+
// outpoint is the natural key. Dedup any duplicate-outpoint rows
|
|
489
|
+
// (possible from pre-outpoint code) before adding the composite PK,
|
|
490
|
+
// preferring rows that hold a reservation.
|
|
491
|
+
name: "Re-key token outputs by (prev_tx_hash, prev_tx_vout), drop legacy id",
|
|
492
|
+
sql: [
|
|
493
|
+
`DELETE a FROM brz_token_outputs a
|
|
494
|
+
JOIN brz_token_outputs b
|
|
495
|
+
ON a.user_id = b.user_id
|
|
496
|
+
AND a.prev_tx_hash = b.prev_tx_hash
|
|
497
|
+
AND a.prev_tx_vout = b.prev_tx_vout
|
|
498
|
+
AND ((b.reservation_id IS NOT NULL) > (a.reservation_id IS NOT NULL)
|
|
499
|
+
OR ((b.reservation_id IS NOT NULL) = (a.reservation_id IS NOT NULL)
|
|
500
|
+
AND b.id > a.id))`,
|
|
501
|
+
`ALTER TABLE brz_token_outputs
|
|
502
|
+
DROP PRIMARY KEY,
|
|
503
|
+
ADD PRIMARY KEY (user_id, prev_tx_hash, prev_tx_vout)`,
|
|
504
|
+
`ALTER TABLE brz_token_outputs DROP COLUMN id`,
|
|
505
|
+
],
|
|
506
|
+
},
|
|
450
507
|
];
|
|
451
508
|
}
|
|
452
509
|
}
|
|
@@ -430,7 +430,7 @@ class MysqlTreeStore {
|
|
|
430
430
|
// (and thus has no row) gets one created lazily.
|
|
431
431
|
if (isSwap && newLeaves && newLeaves.length > 0) {
|
|
432
432
|
await conn.query(
|
|
433
|
-
`INSERT INTO brz_tree_swap_status (user_id, last_completed_at) VALUES (?,
|
|
433
|
+
`INSERT INTO brz_tree_swap_status (user_id, last_completed_at) VALUES (?, UTC_TIMESTAMP(6))
|
|
434
434
|
ON DUPLICATE KEY UPDATE last_completed_at = VALUES(last_completed_at)`,
|
|
435
435
|
[this.identity]
|
|
436
436
|
);
|
|
@@ -581,7 +581,7 @@ class MysqlTreeStore {
|
|
|
581
581
|
|
|
582
582
|
async now() {
|
|
583
583
|
try {
|
|
584
|
-
const [rows] = await this.pool.query("SELECT
|
|
584
|
+
const [rows] = await this.pool.query("SELECT UTC_TIMESTAMP(6) AS now");
|
|
585
585
|
const value = rows[0].now;
|
|
586
586
|
// mysql2 typically returns DATETIME as a JS Date when dateStrings is false (default).
|
|
587
587
|
if (value instanceof Date) return value.getTime();
|
|
@@ -809,7 +809,7 @@ class MysqlTreeStore {
|
|
|
809
809
|
|
|
810
810
|
async _createReservation(conn, reservationId, leaves, purpose, pendingChange) {
|
|
811
811
|
await conn.query(
|
|
812
|
-
"INSERT INTO brz_tree_reservations (user_id, id, purpose, pending_change_amount) VALUES (?, ?, ?,
|
|
812
|
+
"INSERT INTO brz_tree_reservations (user_id, id, purpose, pending_change_amount, created_at) VALUES (?, ?, ?, ?, UTC_TIMESTAMP(6))",
|
|
813
813
|
[this.identity, reservationId, purpose, pendingChange]
|
|
814
814
|
);
|
|
815
815
|
|
|
@@ -827,7 +827,7 @@ class MysqlTreeStore {
|
|
|
827
827
|
if (filtered.length === 0) return;
|
|
828
828
|
|
|
829
829
|
const valueClauses = new Array(filtered.length)
|
|
830
|
-
.fill("(?, ?, ?, ?, ?, ?,
|
|
830
|
+
.fill("(?, ?, ?, ?, ?, ?, UTC_TIMESTAMP(6))")
|
|
831
831
|
.join(", ");
|
|
832
832
|
const params = [];
|
|
833
833
|
for (const leaf of filtered) {
|
|
@@ -849,7 +849,7 @@ class MysqlTreeStore {
|
|
|
849
849
|
is_missing_from_operators = VALUES(is_missing_from_operators),
|
|
850
850
|
data = VALUES(data),
|
|
851
851
|
value = VALUES(value),
|
|
852
|
-
added_at =
|
|
852
|
+
added_at = UTC_TIMESTAMP(6)`,
|
|
853
853
|
params
|
|
854
854
|
);
|
|
855
855
|
}
|
|
@@ -867,7 +867,9 @@ class MysqlTreeStore {
|
|
|
867
867
|
async _batchInsertSpentLeaves(conn, leafIds) {
|
|
868
868
|
if (leafIds.length === 0) return;
|
|
869
869
|
|
|
870
|
-
const valueClauses = new Array(leafIds.length)
|
|
870
|
+
const valueClauses = new Array(leafIds.length)
|
|
871
|
+
.fill("(?, ?, UTC_TIMESTAMP(6))")
|
|
872
|
+
.join(", ");
|
|
871
873
|
const params = [];
|
|
872
874
|
for (const id of leafIds) {
|
|
873
875
|
params.push(this.identity, id);
|
|
@@ -876,7 +878,7 @@ class MysqlTreeStore {
|
|
|
876
878
|
// problems (FK violations, NOT NULL violations, type errors) still
|
|
877
879
|
// propagate.
|
|
878
880
|
await conn.query(
|
|
879
|
-
`INSERT INTO brz_tree_spent_leaves (user_id, leaf_id) VALUES ${valueClauses}
|
|
881
|
+
`INSERT INTO brz_tree_spent_leaves (user_id, leaf_id, spent_at) VALUES ${valueClauses}
|
|
880
882
|
ON DUPLICATE KEY UPDATE leaf_id = leaf_id`,
|
|
881
883
|
params
|
|
882
884
|
);
|
|
@@ -904,14 +906,14 @@ class MysqlTreeStore {
|
|
|
904
906
|
SELECT id FROM (
|
|
905
907
|
SELECT id FROM brz_tree_reservations
|
|
906
908
|
WHERE user_id = ?
|
|
907
|
-
AND created_at < DATE_SUB(
|
|
909
|
+
AND created_at < DATE_SUB(UTC_TIMESTAMP(6), INTERVAL ? SECOND)
|
|
908
910
|
) AS stale
|
|
909
911
|
)`,
|
|
910
912
|
[this.identity, this.identity, RESERVATION_TIMEOUT_SECS]
|
|
911
913
|
);
|
|
912
914
|
await conn.query(
|
|
913
915
|
`DELETE FROM brz_tree_reservations
|
|
914
|
-
WHERE user_id = ? AND created_at < DATE_SUB(
|
|
916
|
+
WHERE user_id = ? AND created_at < DATE_SUB(UTC_TIMESTAMP(6), INTERVAL ? SECOND)`,
|
|
915
917
|
[this.identity, RESERVATION_TIMEOUT_SECS]
|
|
916
918
|
);
|
|
917
919
|
}
|
|
@@ -936,6 +938,10 @@ function createMysqlPool(config) {
|
|
|
936
938
|
connectTimeout: (config.createTimeoutSecs || 0) * 1000 || 10000,
|
|
937
939
|
idleTimeout: (config.recycleTimeoutSecs || 0) * 1000 || 10000,
|
|
938
940
|
waitForConnections: true,
|
|
941
|
+
// Serialize JS `Date` parameters as UTC strings rather than host-local
|
|
942
|
+
// time. Paired with explicit `UTC_TIMESTAMP(6)` on the server side, this
|
|
943
|
+
// keeps timestamp comparisons consistent regardless of the host TZ.
|
|
944
|
+
timezone: "Z",
|
|
939
945
|
});
|
|
940
946
|
}
|
|
941
947
|
|
|
@@ -98,7 +98,7 @@ class MysqlTreeStoreMigrationManager {
|
|
|
98
98
|
await conn.query(`
|
|
99
99
|
CREATE TABLE IF NOT EXISTS \`${TREE_MIGRATIONS_TABLE}\` (
|
|
100
100
|
version INT PRIMARY KEY,
|
|
101
|
-
applied_at DATETIME(6) NOT NULL DEFAULT
|
|
101
|
+
applied_at DATETIME(6) NOT NULL DEFAULT (UTC_TIMESTAMP(6))
|
|
102
102
|
)
|
|
103
103
|
`);
|
|
104
104
|
|
|
@@ -121,7 +121,7 @@ class MysqlTreeStoreMigrationManager {
|
|
|
121
121
|
await runMigrationStep(conn, step);
|
|
122
122
|
}
|
|
123
123
|
await conn.query(
|
|
124
|
-
`INSERT INTO \`${TREE_MIGRATIONS_TABLE}\` (version) VALUES (
|
|
124
|
+
`INSERT INTO \`${TREE_MIGRATIONS_TABLE}\` (version, applied_at) VALUES (?, UTC_TIMESTAMP(6))`,
|
|
125
125
|
[version]
|
|
126
126
|
);
|
|
127
127
|
}
|
|
@@ -367,6 +367,20 @@ class MysqlTreeStoreMigrationManager {
|
|
|
367
367
|
`ALTER TABLE brz_tree_swap_status ADD PRIMARY KEY (user_id)`,
|
|
368
368
|
],
|
|
369
369
|
},
|
|
370
|
+
{
|
|
371
|
+
// Pin DATETIME defaults to UTC. Server-side INSERTs already pass
|
|
372
|
+
// `UTC_TIMESTAMP(6)` explicitly; this migration makes the column
|
|
373
|
+
// default match, so any future callsite that omits the column also
|
|
374
|
+
// gets a UTC value rather than a session-TZ-dependent one.
|
|
375
|
+
name: "Pin DATETIME defaults to UTC",
|
|
376
|
+
sql: [
|
|
377
|
+
`ALTER TABLE brz_tree_reservations MODIFY COLUMN created_at DATETIME(6) NOT NULL DEFAULT (UTC_TIMESTAMP(6))`,
|
|
378
|
+
`ALTER TABLE brz_tree_leaves MODIFY COLUMN created_at DATETIME(6) NOT NULL DEFAULT (UTC_TIMESTAMP(6))`,
|
|
379
|
+
`ALTER TABLE brz_tree_leaves MODIFY COLUMN added_at DATETIME(6) NOT NULL DEFAULT (UTC_TIMESTAMP(6))`,
|
|
380
|
+
`ALTER TABLE brz_tree_spent_leaves MODIFY COLUMN spent_at DATETIME(6) NOT NULL DEFAULT (UTC_TIMESTAMP(6))`,
|
|
381
|
+
`ALTER TABLE brz_tree_schema_migrations MODIFY COLUMN applied_at DATETIME(6) NOT NULL DEFAULT (UTC_TIMESTAMP(6))`,
|
|
382
|
+
],
|
|
383
|
+
},
|
|
370
384
|
];
|
|
371
385
|
}
|
|
372
386
|
}
|
package/nodejs/package.json
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
"postgres-storage/",
|
|
8
8
|
"postgres-tree-store/",
|
|
9
9
|
"postgres-token-store/",
|
|
10
|
-
"postgres-session-
|
|
10
|
+
"postgres-session-store/",
|
|
11
11
|
"mysql-storage/",
|
|
12
12
|
"mysql-tree-store/",
|
|
13
13
|
"mysql-token-store/",
|
|
14
|
-
"mysql-session-
|
|
14
|
+
"mysql-session-store/",
|
|
15
15
|
"index.js",
|
|
16
16
|
"index.mjs"
|
|
17
17
|
],
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// errors.cjs - Session store error wrapper with cause chain support
|
|
2
|
+
class SessionStoreError extends Error {
|
|
3
|
+
constructor(message, cause = null) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = 'SessionStoreError';
|
|
6
|
+
this.cause = cause;
|
|
7
|
+
if (Error.captureStackTrace) {
|
|
8
|
+
Error.captureStackTrace(this, SessionStoreError);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = { SessionStoreError };
|