@bsv/wallet-toolbox 2.1.25 → 3.0.0-alpha.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/docs/CREATEACTION_BLOCKERS.md +391 -0
- package/docs/CUTOVER_RUNBOOK.md +95 -0
- package/docs/REQUIREMENTS_COMPLIANCE.md +157 -0
- package/docs/ROLLOUT_PLAN.md +273 -0
- package/docs/SESSION_HANDOFF.md +298 -0
- package/docs/STORAGE_METHOD_WIRING.md +176 -0
- package/docs/client.md +3765 -1325
- package/docs/monitor.md +255 -33
- package/docs/services.md +304 -280
- package/docs/setup.md +24 -24
- package/docs/storage.md +2783 -251
- package/docs/v3-upgrade/index.html +911 -0
- package/docs/wallet.md +4956 -9455
- package/out/src/Wallet.d.ts.map +1 -1
- package/out/src/Wallet.js.map +1 -1
- package/out/src/WalletLogger.d.ts.map +1 -1
- package/out/src/WalletLogger.js.map +1 -1
- package/out/src/__tests/CWIStyleWalletManager.test.js.map +1 -1
- package/out/src/__tests/WalletPermissionsManager.fixtures.d.ts.map +1 -1
- package/out/src/__tests/WalletPermissionsManager.fixtures.js.map +1 -1
- package/out/src/entropy/EntropyCollector.d.ts.map +1 -1
- package/out/src/entropy/EntropyCollector.js.map +1 -1
- package/out/src/monitor/LeasedMonitorTask.d.ts +43 -0
- package/out/src/monitor/LeasedMonitorTask.d.ts.map +1 -0
- package/out/src/monitor/LeasedMonitorTask.js +89 -0
- package/out/src/monitor/LeasedMonitorTask.js.map +1 -0
- package/out/src/monitor/Monitor.d.ts +7 -0
- package/out/src/monitor/Monitor.d.ts.map +1 -1
- package/out/src/monitor/Monitor.js +7 -0
- package/out/src/monitor/Monitor.js.map +1 -1
- package/out/src/monitor/MonitorDaemon.d.ts.map +1 -1
- package/out/src/monitor/MonitorDaemon.js.map +1 -1
- package/out/src/monitor/V7LeasedTask.d.ts +43 -0
- package/out/src/monitor/V7LeasedTask.d.ts.map +1 -0
- package/out/src/monitor/V7LeasedTask.js +89 -0
- package/out/src/monitor/V7LeasedTask.js.map +1 -0
- package/out/src/monitor/index.all.d.ts +1 -0
- package/out/src/monitor/index.all.d.ts.map +1 -1
- package/out/src/monitor/index.all.js +1 -0
- package/out/src/monitor/index.all.js.map +1 -1
- package/out/src/monitor/tasks/TaskCheckForProofs.d.ts +2 -0
- package/out/src/monitor/tasks/TaskCheckForProofs.d.ts.map +1 -1
- package/out/src/monitor/tasks/TaskCheckForProofs.js +55 -0
- package/out/src/monitor/tasks/TaskCheckForProofs.js.map +1 -1
- package/out/src/monitor/tasks/TaskSendWaiting.d.ts.map +1 -1
- package/out/src/monitor/tasks/TaskSendWaiting.js.map +1 -1
- package/out/src/sdk/WalletStorage.interfaces.d.ts +59 -59
- package/out/src/sdk/WalletStorage.interfaces.d.ts.map +1 -1
- package/out/src/sdk/types.d.ts +32 -0
- package/out/src/sdk/types.d.ts.map +1 -1
- package/out/src/sdk/types.js +50 -1
- package/out/src/sdk/types.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/LiveIngestorWhatsOnChainPoll.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/LiveIngestorWhatsOnChainPoll.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageKnex.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageKnex.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/createDefaultIdbChaintracksOptions.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/createDefaultIdbChaintracksOptions.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/createDefaultKnexChaintracksOptions.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/createDefaultKnexChaintracksOptions.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/createDefaultNoDbChaintracksOptions.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/createDefaultNoDbChaintracksOptions.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/createIdbChaintracks.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/createIdbChaintracks.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/createKnexChaintracks.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/createKnexChaintracks.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/createNoDbChaintracks.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/createNoDbChaintracks.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/util/BulkFilesReader.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/util/BulkFilesReader.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/util/ChaintracksFs.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/util/ChaintracksFs.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/util/SingleWriterMultiReaderLock.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/util/SingleWriterMultiReaderLock.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/util/__tests/SingleWriterMultiReaderLock.test.js.map +1 -1
- package/out/src/storage/StorageIdb.d.ts.map +1 -1
- package/out/src/storage/StorageIdb.js +10 -5
- package/out/src/storage/StorageIdb.js.map +1 -1
- package/out/src/storage/StorageKnex.d.ts +133 -3
- package/out/src/storage/StorageKnex.d.ts.map +1 -1
- package/out/src/storage/StorageKnex.js +691 -94
- package/out/src/storage/StorageKnex.js.map +1 -1
- package/out/src/storage/StorageProvider.d.ts +114 -1
- package/out/src/storage/StorageProvider.d.ts.map +1 -1
- package/out/src/storage/StorageProvider.js +164 -4
- package/out/src/storage/StorageProvider.js.map +1 -1
- package/out/src/storage/WalletStorageManager.d.ts.map +1 -1
- package/out/src/storage/WalletStorageManager.js.map +1 -1
- package/out/src/storage/idbHelpers.d.ts +5 -0
- package/out/src/storage/idbHelpers.d.ts.map +1 -1
- package/out/src/storage/idbHelpers.js +42 -0
- package/out/src/storage/idbHelpers.js.map +1 -1
- package/out/src/storage/methods/attemptToPostReqsToNetwork.d.ts.map +1 -1
- package/out/src/storage/methods/attemptToPostReqsToNetwork.js +116 -4
- package/out/src/storage/methods/attemptToPostReqsToNetwork.js.map +1 -1
- package/out/src/storage/methods/createAction.d.ts.map +1 -1
- package/out/src/storage/methods/createAction.js +22 -5
- package/out/src/storage/methods/createAction.js.map +1 -1
- package/out/src/storage/methods/internalizeAction.d.ts.map +1 -1
- package/out/src/storage/methods/internalizeAction.js +172 -5
- package/out/src/storage/methods/internalizeAction.js.map +1 -1
- package/out/src/storage/methods/listActionsKnex.d.ts +49 -0
- package/out/src/storage/methods/listActionsKnex.d.ts.map +1 -1
- package/out/src/storage/methods/listActionsKnex.js +179 -69
- package/out/src/storage/methods/listActionsKnex.js.map +1 -1
- package/out/src/storage/methods/listOutputsKnex.d.ts.map +1 -1
- package/out/src/storage/methods/listOutputsKnex.js +51 -4
- package/out/src/storage/methods/listOutputsKnex.js.map +1 -1
- package/out/src/storage/methods/processAction.d.ts.map +1 -1
- package/out/src/storage/methods/processAction.js +82 -14
- package/out/src/storage/methods/processAction.js.map +1 -1
- package/out/src/storage/methods/purgeData.d.ts.map +1 -1
- package/out/src/storage/methods/purgeData.js +22 -14
- package/out/src/storage/methods/purgeData.js.map +1 -1
- package/out/src/storage/methods/reviewStatus.d.ts.map +1 -1
- package/out/src/storage/methods/reviewStatus.js +18 -9
- package/out/src/storage/methods/reviewStatus.js.map +1 -1
- package/out/src/storage/schema/KnexMigrations.d.ts.map +1 -1
- package/out/src/storage/schema/KnexMigrations.js +134 -0
- package/out/src/storage/schema/KnexMigrations.js.map +1 -1
- package/out/src/storage/schema/StorageIdbSchema.d.ts +43 -2
- package/out/src/storage/schema/StorageIdbSchema.d.ts.map +1 -1
- package/out/src/storage/schema/__tests/backfill.runner.test.d.ts +2 -0
- package/out/src/storage/schema/__tests/backfill.runner.test.d.ts.map +1 -0
- package/out/src/storage/schema/__tests/backfill.runner.test.js +148 -0
- package/out/src/storage/schema/__tests/backfill.runner.test.js.map +1 -0
- package/out/src/storage/schema/__tests/backfill.test.d.ts +2 -0
- package/out/src/storage/schema/__tests/backfill.test.d.ts.map +1 -0
- package/out/src/storage/schema/__tests/backfill.test.js +96 -0
- package/out/src/storage/schema/__tests/backfill.test.js.map +1 -0
- package/out/src/storage/schema/__tests/processingFsm.test.d.ts +2 -0
- package/out/src/storage/schema/__tests/processingFsm.test.d.ts.map +1 -0
- package/out/src/storage/schema/__tests/processingFsm.test.js +42 -0
- package/out/src/storage/schema/__tests/processingFsm.test.js.map +1 -0
- package/out/src/storage/schema/__tests/processingFsmLegacyMapping.test.d.ts +2 -0
- package/out/src/storage/schema/__tests/processingFsmLegacyMapping.test.d.ts.map +1 -0
- package/out/src/storage/schema/__tests/processingFsmLegacyMapping.test.js +82 -0
- package/out/src/storage/schema/__tests/processingFsmLegacyMapping.test.js.map +1 -0
- package/out/src/storage/schema/__tests/spendabilityRule.test.d.ts +2 -0
- package/out/src/storage/schema/__tests/spendabilityRule.test.d.ts.map +1 -0
- package/out/src/storage/schema/__tests/spendabilityRule.test.js +29 -0
- package/out/src/storage/schema/__tests/spendabilityRule.test.js.map +1 -0
- package/out/src/storage/schema/__tests/v7FsmLegacyMapping.test.d.ts +2 -0
- package/out/src/storage/schema/__tests/v7FsmLegacyMapping.test.d.ts.map +1 -0
- package/out/src/storage/schema/__tests/v7FsmLegacyMapping.test.js +77 -0
- package/out/src/storage/schema/__tests/v7FsmLegacyMapping.test.js.map +1 -0
- package/out/src/storage/schema/backfill.d.ts +35 -0
- package/out/src/storage/schema/backfill.d.ts.map +1 -0
- package/out/src/storage/schema/backfill.idb.d.ts +32 -0
- package/out/src/storage/schema/backfill.idb.d.ts.map +1 -0
- package/out/src/storage/schema/backfill.idb.js +95 -0
- package/out/src/storage/schema/backfill.idb.js.map +1 -0
- package/out/src/storage/schema/backfill.js +150 -0
- package/out/src/storage/schema/backfill.js.map +1 -0
- package/out/src/storage/schema/backfill.knex.d.ts +32 -0
- package/out/src/storage/schema/backfill.knex.d.ts.map +1 -0
- package/out/src/storage/schema/backfill.knex.js +240 -0
- package/out/src/storage/schema/backfill.knex.js.map +1 -0
- package/out/src/storage/schema/backfill.runner.d.ts +63 -0
- package/out/src/storage/schema/backfill.runner.d.ts.map +1 -0
- package/out/src/storage/schema/backfill.runner.js +64 -0
- package/out/src/storage/schema/backfill.runner.js.map +1 -0
- package/out/src/storage/schema/coinbaseMaturityBackfill.d.ts +25 -0
- package/out/src/storage/schema/coinbaseMaturityBackfill.d.ts.map +1 -0
- package/out/src/storage/schema/coinbaseMaturityBackfill.js +75 -0
- package/out/src/storage/schema/coinbaseMaturityBackfill.js.map +1 -0
- package/out/src/storage/schema/monitorLease.d.ts +57 -0
- package/out/src/storage/schema/monitorLease.d.ts.map +1 -0
- package/out/src/storage/schema/monitorLease.js +101 -0
- package/out/src/storage/schema/monitorLease.js.map +1 -0
- package/out/src/storage/schema/processingFsm.d.ts +27 -0
- package/out/src/storage/schema/processingFsm.d.ts.map +1 -0
- package/out/src/storage/schema/processingFsm.js +132 -0
- package/out/src/storage/schema/processingFsm.js.map +1 -0
- package/out/src/storage/schema/schemaCutover.d.ts +34 -0
- package/out/src/storage/schema/schemaCutover.d.ts.map +1 -0
- package/out/src/storage/schema/schemaCutover.js +230 -0
- package/out/src/storage/schema/schemaCutover.js.map +1 -0
- package/out/src/storage/schema/schemaCutoverIdb.d.ts +26 -0
- package/out/src/storage/schema/schemaCutoverIdb.d.ts.map +1 -0
- package/out/src/storage/schema/schemaCutoverIdb.js +90 -0
- package/out/src/storage/schema/schemaCutoverIdb.js.map +1 -0
- package/out/src/storage/schema/spendabilityRefresh.d.ts +49 -0
- package/out/src/storage/schema/spendabilityRefresh.d.ts.map +1 -0
- package/out/src/storage/schema/spendabilityRefresh.js +120 -0
- package/out/src/storage/schema/spendabilityRefresh.js.map +1 -0
- package/out/src/storage/schema/spendabilityRule.d.ts +45 -0
- package/out/src/storage/schema/spendabilityRule.d.ts.map +1 -0
- package/out/src/storage/schema/spendabilityRule.js +52 -0
- package/out/src/storage/schema/spendabilityRule.js.map +1 -0
- package/out/src/storage/schema/tables/TableAction.d.ts +3 -3
- package/out/src/storage/schema/tables/TableAction.d.ts.map +1 -1
- package/out/src/storage/schema/tables/TableChainTip.d.ts +1 -1
- package/out/src/storage/schema/tables/TableMonitorLease.d.ts +1 -1
- package/out/src/storage/schema/tables/TableOutput.d.ts +7 -0
- package/out/src/storage/schema/tables/TableOutput.d.ts.map +1 -1
- package/out/src/storage/schema/tables/TableOutput.js.map +1 -1
- package/out/src/storage/schema/tables/TableTransactionNew.d.ts +50 -0
- package/out/src/storage/schema/tables/TableTransactionNew.d.ts.map +1 -0
- package/out/src/storage/schema/tables/TableTransactionNew.js +3 -0
- package/out/src/storage/schema/tables/TableTransactionNew.js.map +1 -0
- package/out/src/storage/schema/tables/TableTxAudit.d.ts +1 -1
- package/out/src/storage/schema/tables/index.d.ts +5 -0
- package/out/src/storage/schema/tables/index.d.ts.map +1 -1
- package/out/src/storage/schema/tables/index.js +5 -0
- package/out/src/storage/schema/tables/index.js.map +1 -1
- package/out/src/storage/schema/transactionCrud.d.ts +41 -0
- package/out/src/storage/schema/transactionCrud.d.ts.map +1 -0
- package/out/src/storage/schema/transactionCrud.js +205 -0
- package/out/src/storage/schema/transactionCrud.js.map +1 -0
- package/out/src/storage/schema/transactionService.d.ts +315 -0
- package/out/src/storage/schema/transactionService.d.ts.map +1 -0
- package/out/src/storage/schema/transactionService.js +783 -0
- package/out/src/storage/schema/transactionService.js.map +1 -0
- package/out/src/storage/schema/txAudit.d.ts +33 -0
- package/out/src/storage/schema/txAudit.d.ts.map +1 -0
- package/out/src/storage/schema/txAudit.js +64 -0
- package/out/src/storage/schema/txAudit.js.map +1 -0
- package/out/src/storage/schema/v7Backfill.d.ts.map +1 -1
- package/out/src/storage/schema/v7Backfill.js +4 -1
- package/out/src/storage/schema/v7Backfill.js.map +1 -1
- package/out/src/storage/schema/v7Backfill.runner.d.ts.map +1 -1
- package/out/src/storage/schema/v7Backfill.runner.js +3 -1
- package/out/src/storage/schema/v7Backfill.runner.js.map +1 -1
- package/out/src/storage/schema/v7CoinbaseMaturityBackfill.d.ts +25 -0
- package/out/src/storage/schema/v7CoinbaseMaturityBackfill.d.ts.map +1 -0
- package/out/src/storage/schema/v7CoinbaseMaturityBackfill.js +75 -0
- package/out/src/storage/schema/v7CoinbaseMaturityBackfill.js.map +1 -0
- package/out/src/storage/schema/v7Crud.d.ts +5 -3
- package/out/src/storage/schema/v7Crud.d.ts.map +1 -1
- package/out/src/storage/schema/v7Crud.js +11 -9
- package/out/src/storage/schema/v7Crud.js.map +1 -1
- package/out/src/storage/schema/v7Cutover.d.ts +34 -0
- package/out/src/storage/schema/v7Cutover.d.ts.map +1 -0
- package/out/src/storage/schema/v7Cutover.js +223 -0
- package/out/src/storage/schema/v7Cutover.js.map +1 -0
- package/out/src/storage/schema/v7CutoverIdb.d.ts +26 -0
- package/out/src/storage/schema/v7CutoverIdb.d.ts.map +1 -0
- package/out/src/storage/schema/v7CutoverIdb.js +90 -0
- package/out/src/storage/schema/v7CutoverIdb.js.map +1 -0
- package/out/src/storage/schema/v7Fsm.d.ts.map +1 -1
- package/out/src/storage/schema/v7Fsm.js +22 -6
- package/out/src/storage/schema/v7Fsm.js.map +1 -1
- package/out/src/storage/schema/v7Service.d.ts +305 -0
- package/out/src/storage/schema/v7Service.d.ts.map +1 -0
- package/out/src/storage/schema/v7Service.js +757 -0
- package/out/src/storage/schema/v7Service.js.map +1 -0
- package/out/src/storage/schema/v7SpendabilityRefresh.d.ts +49 -0
- package/out/src/storage/schema/v7SpendabilityRefresh.d.ts.map +1 -0
- package/out/src/storage/schema/v7SpendabilityRefresh.js +111 -0
- package/out/src/storage/schema/v7SpendabilityRefresh.js.map +1 -0
- package/out/src/storage/storageProviderHelpers.js +1 -1
- package/out/src/storage/storageProviderHelpers.js.map +1 -1
- package/out/src/utility/Format.js.map +1 -1
- package/package.json +7 -4
|
@@ -10,17 +10,204 @@ const listOutputsKnex_1 = require("./methods/listOutputsKnex");
|
|
|
10
10
|
const reviewStatus_1 = require("./methods/reviewStatus");
|
|
11
11
|
const WERR_errors_1 = require("../sdk/WERR_errors");
|
|
12
12
|
const utilityHelpers_1 = require("../utility/utilityHelpers");
|
|
13
|
+
const types_1 = require("../sdk/types");
|
|
14
|
+
const transactionService_1 = require("./schema/transactionService");
|
|
13
15
|
class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
14
16
|
constructor(options) {
|
|
15
17
|
super(options);
|
|
18
|
+
this._postCutoverCache = undefined;
|
|
16
19
|
this._verifiedReadyForDatabaseAccess = false;
|
|
17
20
|
if (!options.knex)
|
|
18
21
|
throw new WERR_errors_1.WERR_INVALID_PARAMETER('options.knex', 'valid');
|
|
19
22
|
this.knex = options.knex;
|
|
20
23
|
}
|
|
24
|
+
getTransactionService() {
|
|
25
|
+
// The new-schema transaction service is only valid post-cutover. Pre-cutover,
|
|
26
|
+
// the `transactions` table is the legacy schema and queries via this service
|
|
27
|
+
// would return legacy ids that don't satisfy the `tx_audit.transactionId`
|
|
28
|
+
// FK (which targets `transactions_new`). Callers gate on `undefined` and
|
|
29
|
+
// fall back to the legacy storage path.
|
|
30
|
+
if (this._postCutoverCache !== true)
|
|
31
|
+
return undefined;
|
|
32
|
+
return new transactionService_1.TransactionService(this.knex);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Eagerly warm the `_postCutoverCache` so sync query builders such as
|
|
36
|
+
* `findOutputsQuery` can branch on cutover state without an extra await.
|
|
37
|
+
* Callers that have invoked `makeAvailable` are guaranteed the cache has
|
|
38
|
+
* been populated.
|
|
39
|
+
*/
|
|
40
|
+
async makeAvailable() {
|
|
41
|
+
const settings = await super.makeAvailable();
|
|
42
|
+
if (this._postCutoverCache === undefined) {
|
|
43
|
+
this._postCutoverCache = await this.knex.schema.hasTable('transactions_legacy');
|
|
44
|
+
}
|
|
45
|
+
return settings;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Returns true when the database has been through `runSchemaCutover` (i.e.
|
|
49
|
+
* `transactions_legacy` exists). Result is cached for the lifetime of this
|
|
50
|
+
* StorageKnex instance.
|
|
51
|
+
*/
|
|
52
|
+
async isPostCutover() {
|
|
53
|
+
if (this._postCutoverCache === undefined) {
|
|
54
|
+
this._postCutoverCache = await this.knex.schema.hasTable('transactions_legacy');
|
|
55
|
+
}
|
|
56
|
+
return this._postCutoverCache;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Maps legacy TransactionStatus values to their ProcessingStatus equivalents
|
|
60
|
+
* for use in `outputs JOIN transactions` WHERE clauses post-cutover.
|
|
61
|
+
*/
|
|
62
|
+
legacyStatiToProcessing(stati) {
|
|
63
|
+
const result = new Set();
|
|
64
|
+
for (const s of stati) {
|
|
65
|
+
result.add((0, types_1.transactionStatusToProcessing)(s));
|
|
66
|
+
if (s === 'unproven') {
|
|
67
|
+
// 'sent','seen','seen_multi','unconfirmed' all map to legacy 'unproven'
|
|
68
|
+
result.add('sent');
|
|
69
|
+
result.add('seen');
|
|
70
|
+
result.add('seen_multi');
|
|
71
|
+
result.add('unconfirmed');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return [...result];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Returns the canonical name of the `proven_txs` table — `proven_txs_legacy`
|
|
78
|
+
* post-cutover, `proven_txs` otherwise.
|
|
79
|
+
*
|
|
80
|
+
* Public so that helper modules (reviewStatus, purgeData) can resolve the
|
|
81
|
+
* correct table name without duplicating the post-cutover detection logic.
|
|
82
|
+
*/
|
|
83
|
+
async provenTxsTableName() {
|
|
84
|
+
return (await this.isPostCutover()) ? 'proven_txs_legacy' : 'proven_txs';
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Returns the canonical name of the `proven_tx_reqs` table —
|
|
88
|
+
* `proven_tx_reqs_legacy` post-cutover, `proven_tx_reqs` otherwise.
|
|
89
|
+
*
|
|
90
|
+
* Public so that helper modules (reviewStatus, purgeData) can resolve the
|
|
91
|
+
* correct table name without duplicating the post-cutover detection logic.
|
|
92
|
+
*/
|
|
93
|
+
async provenTxReqsTableName() {
|
|
94
|
+
return (await this.isPostCutover()) ? 'proven_tx_reqs_legacy' : 'proven_tx_reqs';
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Insert a legacy-shaped transaction row into the correct table:
|
|
98
|
+
* - Post-cutover: `transactions_legacy` (the renamed legacy schema table)
|
|
99
|
+
* - Pre-cutover: `transactions` (the standard table, same behaviour as `insertTransaction`)
|
|
100
|
+
*
|
|
101
|
+
* This allows `createAction` to store unsigned rows (txid unknown) without
|
|
102
|
+
* conflicting with the new `transactions` table's NOT NULL txid constraint.
|
|
103
|
+
* A new `transactions` row + `actions` row are created later by `processAction`
|
|
104
|
+
* once the real txid is known.
|
|
105
|
+
*
|
|
106
|
+
* On SQLite, foreign key enforcement is temporarily disabled while inserting
|
|
107
|
+
* into `transactions_legacy` because the referenced `proven_txs` table was
|
|
108
|
+
* renamed to `proven_txs_legacy` during the the schema cutover. Since `provenTxId`
|
|
109
|
+
* is always NULL for new unsigned transactions this is semantically safe.
|
|
110
|
+
*/
|
|
111
|
+
async insertLegacyTransaction(tx, trx) {
|
|
112
|
+
const e = await this.validateEntityForInsert(tx, trx);
|
|
113
|
+
if (e.transactionId === 0)
|
|
114
|
+
delete e.transactionId;
|
|
115
|
+
const hasLegacyTable = await this.knex.schema.hasTable('transactions_legacy');
|
|
116
|
+
const tableName = hasLegacyTable ? 'transactions_legacy' : 'transactions';
|
|
117
|
+
const isSqlite = this.dbtype === 'SQLite';
|
|
118
|
+
// On SQLite post-cutover the `transactions_legacy` FK to `proven_txs` is dangling
|
|
119
|
+
// (proven_txs was renamed to proven_txs_legacy). Temporarily disable FK checks for
|
|
120
|
+
// this insert since provenTxId is always NULL for a brand-new unsigned transaction.
|
|
121
|
+
if (isSqlite && hasLegacyTable)
|
|
122
|
+
await this.knex.raw('PRAGMA foreign_keys = OFF');
|
|
123
|
+
try {
|
|
124
|
+
const [id] = await this.toDb(trx)(tableName).insert(e);
|
|
125
|
+
tx.transactionId = id;
|
|
126
|
+
return tx.transactionId;
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
if (isSqlite && hasLegacyTable)
|
|
130
|
+
await this.knex.raw('PRAGMA foreign_keys = ON');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Insert a `tx_labels_map` row for a legacy transaction that does not yet
|
|
135
|
+
* have a `actions.actionId`. On post-cutover SQLite, temporarily disables
|
|
136
|
+
* FK checks so that the legacy transactionId (which is not yet an actionId)
|
|
137
|
+
* can be written. processAction will repoint it via repointLabelsToActionId.
|
|
138
|
+
*
|
|
139
|
+
* Note: bypasses `validateEntityForInsert` (and therefore `verifyReadyForDatabaseAccess`
|
|
140
|
+
* which would re-enable FK checks) to ensure the PRAGMA=OFF persists across
|
|
141
|
+
* the insert statement.
|
|
142
|
+
*/
|
|
143
|
+
async insertLegacyTxLabelMap(labelMap, trx) {
|
|
144
|
+
const isSqlite = this.dbtype === 'SQLite';
|
|
145
|
+
const hasLegacyTable = isSqlite ? await this.knex.schema.hasTable('transactions_legacy') : false;
|
|
146
|
+
if (!isSqlite || !hasLegacyTable) {
|
|
147
|
+
// Pre-cutover or non-SQLite: standard insert is safe (no dangling FK issue).
|
|
148
|
+
await this.insertTxLabelMap(labelMap, trx);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// Post-cutover SQLite: tx_labels_map.transactionId FK now points to actions.actionId.
|
|
152
|
+
// We are writing a legacyTransactionId (no corresponding actions row yet).
|
|
153
|
+
// Temporarily disable FK checks for this single insert.
|
|
154
|
+
const now = new Date();
|
|
155
|
+
const e = {
|
|
156
|
+
created_at: now,
|
|
157
|
+
updated_at: now,
|
|
158
|
+
txLabelId: labelMap.txLabelId,
|
|
159
|
+
transactionId: labelMap.transactionId,
|
|
160
|
+
isDeleted: labelMap.isDeleted ? 1 : 0
|
|
161
|
+
};
|
|
162
|
+
// Use the raw knex handle (not toDb(trx)) to send the PRAGMA on the same connection.
|
|
163
|
+
await this.knex.raw('PRAGMA foreign_keys = OFF');
|
|
164
|
+
try {
|
|
165
|
+
await this.knex('tx_labels_map').insert(e);
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
await this.knex.raw('PRAGMA foreign_keys = ON');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
21
171
|
async readSettings() {
|
|
22
172
|
return this.validateEntity((0, utilityHelpers_1.verifyOne)(await this.toDb(undefined)('settings')));
|
|
23
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Synthesise a `ProvenOrRawTx` from a `TableTransactionNew` row.
|
|
176
|
+
*
|
|
177
|
+
* - If the new schema row carries a `merklePath` (i.e. processing is `proven`), build
|
|
178
|
+
* a `TableProvenTx`-shaped object so the BEEF assembly code can extract the
|
|
179
|
+
* merkle path and merge it into the Beef without hitting the legacy tables.
|
|
180
|
+
* - If the new schema row only has `rawTx` (broadcast/queued state), return it as the
|
|
181
|
+
* raw-tx path.
|
|
182
|
+
* - Returns `null` when the new schema row has neither rawTx nor merklePath.
|
|
183
|
+
*/
|
|
184
|
+
newTxToProvenOrRawTx(row, txid) {
|
|
185
|
+
var _a, _b, _c, _d;
|
|
186
|
+
// Only use the new-schema row as the primary source when it carries a *non-empty*
|
|
187
|
+
// merklePath and a rawTx — both are required for `handleProvenTxBranch` to assemble
|
|
188
|
+
// a valid BEEF. An empty merklePath (e.g. a backfill race or a req whose proof was
|
|
189
|
+
// never stored) must fall through to the legacy `proven_txs_legacy` path.
|
|
190
|
+
// Raw-tx-only rows (no merklePath) are likewise forwarded to the legacy path so
|
|
191
|
+
// that the legacy `inputBEEF` column is available for recursive BEEF assembly.
|
|
192
|
+
if (row.merklePath != null && row.merklePath.length > 0 && row.rawTx != null && row.rawTx.length > 0) {
|
|
193
|
+
// Full proof available — synthesise TableProvenTx shape
|
|
194
|
+
const proven = {
|
|
195
|
+
created_at: row.created_at,
|
|
196
|
+
updated_at: row.updated_at,
|
|
197
|
+
provenTxId: row.transactionId,
|
|
198
|
+
txid,
|
|
199
|
+
height: (_a = row.height) !== null && _a !== void 0 ? _a : 0,
|
|
200
|
+
index: (_b = row.merkleIndex) !== null && _b !== void 0 ? _b : 0,
|
|
201
|
+
merklePath: row.merklePath,
|
|
202
|
+
rawTx: row.rawTx,
|
|
203
|
+
blockHash: (_c = row.blockHash) !== null && _c !== void 0 ? _c : '',
|
|
204
|
+
merkleRoot: (_d = row.merkleRoot) !== null && _d !== void 0 ? _d : ''
|
|
205
|
+
};
|
|
206
|
+
return { proven, rawTx: undefined, inputBEEF: undefined };
|
|
207
|
+
}
|
|
208
|
+
// No complete proof in new-schema row — let the caller fall through to legacy tables.
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
24
211
|
async getProvenOrRawTx(txid, trx) {
|
|
25
212
|
const k = this.toDb(trx);
|
|
26
213
|
const r = {
|
|
@@ -28,9 +215,29 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
28
215
|
rawTx: undefined,
|
|
29
216
|
inputBEEF: undefined
|
|
30
217
|
};
|
|
218
|
+
// Post-cutover: try new `transactions` table first. The new schema row is the single
|
|
219
|
+
// source of truth for rawTx and merklePath; legacy tables are read-only
|
|
220
|
+
// archives after cutover.
|
|
221
|
+
if (await this.isPostCutover()) {
|
|
222
|
+
try {
|
|
223
|
+
const txSvc = this.getTransactionService();
|
|
224
|
+
if (txSvc != null) {
|
|
225
|
+
const newTx = await txSvc.findByTxid(txid);
|
|
226
|
+
if (newTx != null) {
|
|
227
|
+
const newTxResult = this.newTxToProvenOrRawTx(newTx, txid);
|
|
228
|
+
if (newTxResult != null)
|
|
229
|
+
return newTxResult;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch (_a) {
|
|
234
|
+
// new table lookup failed (e.g. table not yet available) — fall through to legacy
|
|
235
|
+
}
|
|
236
|
+
}
|
|
31
237
|
r.proven = (0, utilityHelpers_1.verifyOneOrNone)(await this.findProvenTxs({ partial: { txid } }));
|
|
32
238
|
if (r.proven == null) {
|
|
33
|
-
const
|
|
239
|
+
const reqTable = await this.provenTxReqsTableName();
|
|
240
|
+
const reqRawTx = (0, utilityHelpers_1.verifyOneOrNone)(await k(reqTable)
|
|
34
241
|
.where('txid', txid)
|
|
35
242
|
.whereIn('status', ['unsent', 'unmined', 'unconfirmed', 'sending', 'nosend', 'completed'])
|
|
36
243
|
.select('rawTx', 'inputBEEF'));
|
|
@@ -52,12 +259,31 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
52
259
|
return rs;
|
|
53
260
|
}
|
|
54
261
|
async getRawTxSlice(txid, offset, length, trx) {
|
|
262
|
+
// Post-cutover: try new transactions table first — the new table is the canonical store
|
|
263
|
+
// for rawTx after the cutover. Apply the slice in JS to avoid raw SQL
|
|
264
|
+
// column naming differences between the two schemas.
|
|
265
|
+
if (await this.isPostCutover()) {
|
|
266
|
+
try {
|
|
267
|
+
const txSvc = this.getTransactionService();
|
|
268
|
+
if (txSvc != null) {
|
|
269
|
+
const newTx = await txSvc.findByTxid(txid);
|
|
270
|
+
if ((newTx === null || newTx === void 0 ? void 0 : newTx.rawTx) != null) {
|
|
271
|
+
return newTx.rawTx.slice(offset, offset + length);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch (_a) {
|
|
276
|
+
// new table lookup failed — fall through to legacy tables
|
|
277
|
+
}
|
|
278
|
+
}
|
|
55
279
|
const sub = this.dbTypeSubstring('rawTx', offset + 1, length);
|
|
56
|
-
|
|
280
|
+
const provenTable = await this.provenTxsTableName();
|
|
281
|
+
const reqTable = await this.provenTxReqsTableName();
|
|
282
|
+
let rs = await this.toDb(trx).raw(`select ${sub} as rawTx from ${provenTable} where txid = '${txid}'`);
|
|
57
283
|
const proven = (0, utilityHelpers_1.verifyOneOrNone)(this.normaliseKnexRawResult(rs));
|
|
58
284
|
if ((proven === null || proven === void 0 ? void 0 : proven.rawTx) != null)
|
|
59
285
|
return Array.from(proven.rawTx);
|
|
60
|
-
rs = await this.toDb(trx).raw(`select ${sub} as rawTx from
|
|
286
|
+
rs = await this.toDb(trx).raw(`select ${sub} as rawTx from ${reqTable} where txid = '${txid}' and status in ('unsent', 'nosend', 'sending', 'unmined', 'completed', 'unfail')`);
|
|
61
287
|
const req = (0, utilityHelpers_1.verifyOneOrNone)(this.normaliseKnexRawResult(rs));
|
|
62
288
|
return (req === null || req === void 0 ? void 0 : req.rawTx) != null ? Array.from(req.rawTx) : undefined;
|
|
63
289
|
}
|
|
@@ -67,7 +293,7 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
67
293
|
if (!this.isAvailable())
|
|
68
294
|
await this.makeAvailable();
|
|
69
295
|
if (Number.isInteger(offset) && Number.isInteger(length)) {
|
|
70
|
-
return this.getRawTxSlice(txid, offset, length, trx);
|
|
296
|
+
return await this.getRawTxSlice(txid, offset, length, trx);
|
|
71
297
|
}
|
|
72
298
|
const r = await this.getProvenOrRawTx(txid, trx);
|
|
73
299
|
return r.proven != null ? r.proven.rawTx : r.rawTx;
|
|
@@ -89,6 +315,25 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
89
315
|
return q;
|
|
90
316
|
}
|
|
91
317
|
async getProvenTxsForUser(args) {
|
|
318
|
+
const postCutover = await this.isPostCutover();
|
|
319
|
+
if (postCutover) {
|
|
320
|
+
// Post-cutover: proven_txs → proven_txs_legacy; transactions → transactions_legacy
|
|
321
|
+
const k = this.toDb(args.trx);
|
|
322
|
+
let q = k('proven_txs_legacy').where(function () {
|
|
323
|
+
this.whereExists(k
|
|
324
|
+
.select('*')
|
|
325
|
+
.from('transactions_legacy')
|
|
326
|
+
.whereRaw(`proven_txs_legacy.provenTxId = transactions_legacy.provenTxId and transactions_legacy.userId = ${args.userId}`));
|
|
327
|
+
});
|
|
328
|
+
if (args.paged != null) {
|
|
329
|
+
q = q.limit(args.paged.limit);
|
|
330
|
+
q = q.offset(args.paged.offset || 0);
|
|
331
|
+
}
|
|
332
|
+
if (args.since != null)
|
|
333
|
+
q = q.where('updated_at', '>=', this.validateDateForWhere(args.since));
|
|
334
|
+
const rs = await q;
|
|
335
|
+
return this.validateEntities(rs);
|
|
336
|
+
}
|
|
92
337
|
const q = this.getProvenTxsForUserQuery(args);
|
|
93
338
|
const rs = await q;
|
|
94
339
|
return this.validateEntities(rs);
|
|
@@ -110,6 +355,25 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
110
355
|
return q;
|
|
111
356
|
}
|
|
112
357
|
async getProvenTxReqsForUser(args) {
|
|
358
|
+
const postCutover = await this.isPostCutover();
|
|
359
|
+
if (postCutover) {
|
|
360
|
+
// Post-cutover: proven_tx_reqs → proven_tx_reqs_legacy; transactions → transactions_legacy
|
|
361
|
+
const k = this.toDb(args.trx);
|
|
362
|
+
let q = k('proven_tx_reqs_legacy').where(function () {
|
|
363
|
+
this.whereExists(k
|
|
364
|
+
.select('*')
|
|
365
|
+
.from('transactions_legacy')
|
|
366
|
+
.whereRaw(`proven_tx_reqs_legacy.txid = transactions_legacy.txid and transactions_legacy.userId = ${args.userId}`));
|
|
367
|
+
});
|
|
368
|
+
if (args.paged != null) {
|
|
369
|
+
q = q.limit(args.paged.limit);
|
|
370
|
+
q = q.offset(args.paged.offset || 0);
|
|
371
|
+
}
|
|
372
|
+
if (args.since != null)
|
|
373
|
+
q = q.where('updated_at', '>=', this.validateDateForWhere(args.since));
|
|
374
|
+
const rs = await q;
|
|
375
|
+
return this.validateEntities(rs, undefined, ['notified']);
|
|
376
|
+
}
|
|
113
377
|
const q = this.getProvenTxReqsForUserQuery(args);
|
|
114
378
|
const rs = await q;
|
|
115
379
|
return this.validateEntities(rs, undefined, ['notified']);
|
|
@@ -166,7 +430,8 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
166
430
|
const e = await this.validateEntityForInsert(tx, trx);
|
|
167
431
|
if (e.provenTxId === 0)
|
|
168
432
|
delete e.provenTxId;
|
|
169
|
-
const
|
|
433
|
+
const tableName = await this.provenTxsTableName();
|
|
434
|
+
const [id] = await this.toDb(trx)(tableName).insert(e);
|
|
170
435
|
tx.provenTxId = id;
|
|
171
436
|
return tx.provenTxId;
|
|
172
437
|
}
|
|
@@ -174,7 +439,10 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
174
439
|
const e = await this.validateEntityForInsert(tx, trx);
|
|
175
440
|
if (e.provenTxReqId === 0)
|
|
176
441
|
delete e.provenTxReqId;
|
|
177
|
-
|
|
442
|
+
// Post-cutover: route to proven_tx_reqs_legacy.
|
|
443
|
+
// FK bypass is handled by the caller (disableForeignKeys before any transaction).
|
|
444
|
+
const reqTable = await this.provenTxReqsTableName();
|
|
445
|
+
const [id] = await this.toDb(trx)(reqTable).insert(e);
|
|
178
446
|
tx.provenTxReqId = id;
|
|
179
447
|
return tx.provenTxReqId;
|
|
180
448
|
}
|
|
@@ -228,6 +496,28 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
228
496
|
const e = await this.validateEntityForInsert(tx, trx);
|
|
229
497
|
if (e.transactionId === 0)
|
|
230
498
|
delete e.transactionId;
|
|
499
|
+
// Post-cutover the canonical `transactions` table is the new schema which
|
|
500
|
+
// does not carry legacy columns (description, status, satoshis, version...).
|
|
501
|
+
// Legacy-shaped insertions belong in `transactions_legacy`. We route there
|
|
502
|
+
// automatically so existing callers that still speak the legacy shape keep
|
|
503
|
+
// working without a code change at every call site. FK enforcement is
|
|
504
|
+
// bypassed because the renamed legacy table still references the renamed
|
|
505
|
+
// `proven_txs_legacy` via its original `proven_txs` FK column name.
|
|
506
|
+
const isSqlite = this.dbtype === 'SQLite';
|
|
507
|
+
const postCutover = isSqlite ? await this.isPostCutover() : false;
|
|
508
|
+
if (postCutover) {
|
|
509
|
+
if (isSqlite)
|
|
510
|
+
await this.knex.raw('PRAGMA foreign_keys = OFF');
|
|
511
|
+
try {
|
|
512
|
+
const [id] = await this.toDb(trx)('transactions_legacy').insert(e);
|
|
513
|
+
tx.transactionId = id;
|
|
514
|
+
return tx.transactionId;
|
|
515
|
+
}
|
|
516
|
+
finally {
|
|
517
|
+
if (isSqlite)
|
|
518
|
+
await this.knex.raw('PRAGMA foreign_keys = ON');
|
|
519
|
+
}
|
|
520
|
+
}
|
|
231
521
|
const [id] = await this.toDb(trx)('transactions').insert(e);
|
|
232
522
|
tx.transactionId = id;
|
|
233
523
|
return tx.transactionId;
|
|
@@ -236,21 +526,42 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
236
526
|
const e = await this.validateEntityForInsert(commission, trx);
|
|
237
527
|
if (e.commissionId === 0)
|
|
238
528
|
delete e.commissionId;
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
529
|
+
// Post-cutover bridge-period: commissions.transactionId FKs new transactions.
|
|
530
|
+
// During createAction the transactionId references transactions_legacy, so bypass FK.
|
|
531
|
+
const isSqlite = this.dbtype === 'SQLite';
|
|
532
|
+
const postCutover = isSqlite ? await this.isPostCutover() : false;
|
|
533
|
+
if (postCutover)
|
|
534
|
+
await this.knex.raw('PRAGMA foreign_keys = OFF');
|
|
535
|
+
try {
|
|
536
|
+
const [id] = await this.toDb(trx)('commissions').insert(e);
|
|
537
|
+
commission.commissionId = id;
|
|
538
|
+
return commission.commissionId;
|
|
539
|
+
}
|
|
540
|
+
finally {
|
|
541
|
+
if (postCutover)
|
|
542
|
+
await this.knex.raw('PRAGMA foreign_keys = ON');
|
|
543
|
+
}
|
|
242
544
|
}
|
|
243
545
|
async insertOutput(output, trx) {
|
|
546
|
+
const e = await this.validateEntityForInsert(output, trx);
|
|
547
|
+
if (e.outputId === 0)
|
|
548
|
+
delete e.outputId;
|
|
549
|
+
// Post-cutover bridge-period: outputs.transactionId FKs new transactions,
|
|
550
|
+
// but new unsigned outputs reference transactions_legacy (bridge-period rows).
|
|
551
|
+
// Temporarily disable FK checks on SQLite. Safe because once processAction
|
|
552
|
+
// runs, spentBy and transactionId are remapped to real new-schema IDs.
|
|
553
|
+
const isSqlite = this.dbtype === 'SQLite';
|
|
554
|
+
const postCutover = isSqlite ? await this.isPostCutover() : false;
|
|
555
|
+
if (postCutover)
|
|
556
|
+
await this.knex.raw('PRAGMA foreign_keys = OFF');
|
|
244
557
|
try {
|
|
245
|
-
const e = await this.validateEntityForInsert(output, trx);
|
|
246
|
-
if (e.outputId === 0)
|
|
247
|
-
delete e.outputId;
|
|
248
558
|
const [id] = await this.toDb(trx)('outputs').insert(e);
|
|
249
559
|
output.outputId = id;
|
|
250
560
|
return output.outputId;
|
|
251
561
|
}
|
|
252
|
-
|
|
253
|
-
|
|
562
|
+
finally {
|
|
563
|
+
if (postCutover)
|
|
564
|
+
await this.knex.raw('PRAGMA foreign_keys = ON');
|
|
254
565
|
}
|
|
255
566
|
}
|
|
256
567
|
async insertOutputTag(tag, trx) {
|
|
@@ -275,6 +586,22 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
275
586
|
}
|
|
276
587
|
async insertTxLabelMap(labelMap, trx) {
|
|
277
588
|
const e = await this.validateEntityForInsert(labelMap, trx, undefined, ['isDeleted']);
|
|
589
|
+
// Post-cutover SQLite: tx_labels_map.transactionId references actions.actionId
|
|
590
|
+
// (rebuilt during schema cutover). Sync paths that import legacy rows still
|
|
591
|
+
// carry the legacy transactionId until repointLabelsToActionId remaps them.
|
|
592
|
+
// Temporarily disable FK checks so the import does not fail.
|
|
593
|
+
const isSqlite = this.dbtype === 'SQLite';
|
|
594
|
+
const postCutover = isSqlite ? await this.isPostCutover() : false;
|
|
595
|
+
if (postCutover) {
|
|
596
|
+
await this.knex.raw('PRAGMA foreign_keys = OFF');
|
|
597
|
+
try {
|
|
598
|
+
await this.toDb(trx)('tx_labels_map').insert(e);
|
|
599
|
+
}
|
|
600
|
+
finally {
|
|
601
|
+
await this.knex.raw('PRAGMA foreign_keys = ON');
|
|
602
|
+
}
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
278
605
|
await this.toDb(trx)('tx_labels_map').insert(e);
|
|
279
606
|
}
|
|
280
607
|
async insertMonitorEvent(event, trx) {
|
|
@@ -307,9 +634,22 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
307
634
|
}
|
|
308
635
|
async updateCommission(id, update, trx) {
|
|
309
636
|
await this.verifyReadyForDatabaseAccess(trx);
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
637
|
+
// Post-cutover SQLite: commissions.transactionId FK references the new
|
|
638
|
+
// schema `transactions` table. Legacy callers carry transactionId values
|
|
639
|
+
// that still point at transactions_legacy. Bypass FK so the update lands.
|
|
640
|
+
const isSqlite = this.dbtype === 'SQLite';
|
|
641
|
+
const postCutover = isSqlite ? await this.isPostCutover() : false;
|
|
642
|
+
if (postCutover)
|
|
643
|
+
await this.knex.raw('PRAGMA foreign_keys = OFF');
|
|
644
|
+
try {
|
|
645
|
+
return await this.toDb(trx)('commissions')
|
|
646
|
+
.where({ commissionId: id })
|
|
647
|
+
.update(this.validatePartialForUpdate(update));
|
|
648
|
+
}
|
|
649
|
+
finally {
|
|
650
|
+
if (postCutover)
|
|
651
|
+
await this.knex.raw('PRAGMA foreign_keys = ON');
|
|
652
|
+
}
|
|
313
653
|
}
|
|
314
654
|
async updateOutputBasket(id, update, trx) {
|
|
315
655
|
await this.verifyReadyForDatabaseAccess(trx);
|
|
@@ -337,14 +677,15 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
337
677
|
}
|
|
338
678
|
async updateProvenTxReq(id, update, trx) {
|
|
339
679
|
await this.verifyReadyForDatabaseAccess(trx);
|
|
680
|
+
const reqTable = await this.provenTxReqsTableName();
|
|
340
681
|
let r;
|
|
341
682
|
if (Array.isArray(id)) {
|
|
342
|
-
r = await this.toDb(trx)(
|
|
683
|
+
r = await this.toDb(trx)(reqTable)
|
|
343
684
|
.whereIn('provenTxReqId', id)
|
|
344
685
|
.update(this.validatePartialForUpdate(update));
|
|
345
686
|
}
|
|
346
687
|
else if (Number.isInteger(id)) {
|
|
347
|
-
r = await this.toDb(trx)(
|
|
688
|
+
r = await this.toDb(trx)(reqTable)
|
|
348
689
|
.where({ provenTxReqId: id })
|
|
349
690
|
.update(this.validatePartialForUpdate(update));
|
|
350
691
|
}
|
|
@@ -355,7 +696,8 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
355
696
|
}
|
|
356
697
|
async updateProvenTx(id, update, trx) {
|
|
357
698
|
await this.verifyReadyForDatabaseAccess(trx);
|
|
358
|
-
|
|
699
|
+
const tableName = await this.provenTxsTableName();
|
|
700
|
+
return await this.toDb(trx)(tableName)
|
|
359
701
|
.where({ provenTxId: id })
|
|
360
702
|
.update(this.validatePartialForUpdate(update));
|
|
361
703
|
}
|
|
@@ -367,21 +709,36 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
367
709
|
}
|
|
368
710
|
async updateTransaction(id, update, trx) {
|
|
369
711
|
await this.verifyReadyForDatabaseAccess(trx);
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
712
|
+
// Post-cutover the canonical `transactions` table is the new schema which
|
|
713
|
+
// lacks legacy columns (userId, status, description, satoshis, version, ...).
|
|
714
|
+
// Legacy-shaped updates belong in `transactions_legacy`, matching the
|
|
715
|
+
// routing performed by `insertTransaction`.
|
|
716
|
+
const isSqlite = this.dbtype === 'SQLite';
|
|
717
|
+
const postCutover = isSqlite ? await this.isPostCutover() : false;
|
|
718
|
+
const tableName = postCutover ? 'transactions_legacy' : 'transactions';
|
|
719
|
+
if (postCutover && isSqlite)
|
|
720
|
+
await this.knex.raw('PRAGMA foreign_keys = OFF');
|
|
721
|
+
try {
|
|
722
|
+
let r;
|
|
723
|
+
if (Array.isArray(id)) {
|
|
724
|
+
r = await this.toDb(trx)(tableName)
|
|
725
|
+
.whereIn('transactionId', id)
|
|
726
|
+
.update(this.validatePartialForUpdate(update));
|
|
727
|
+
}
|
|
728
|
+
else if (Number.isInteger(id)) {
|
|
729
|
+
r = await this.toDb(trx)(tableName)
|
|
730
|
+
.where({ transactionId: id })
|
|
731
|
+
.update(this.validatePartialForUpdate(update));
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
throw new WERR_errors_1.WERR_INVALID_PARAMETER('id', 'transactionId or array of transactionId');
|
|
735
|
+
}
|
|
736
|
+
return r;
|
|
380
737
|
}
|
|
381
|
-
|
|
382
|
-
|
|
738
|
+
finally {
|
|
739
|
+
if (postCutover && isSqlite)
|
|
740
|
+
await this.knex.raw('PRAGMA foreign_keys = ON');
|
|
383
741
|
}
|
|
384
|
-
return r;
|
|
385
742
|
}
|
|
386
743
|
async updateTxLabelMap(transactionId, txLabelId, update, trx) {
|
|
387
744
|
await this.verifyReadyForDatabaseAccess(trx);
|
|
@@ -430,15 +787,18 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
430
787
|
sortColumn = 'outputTagId';
|
|
431
788
|
break;
|
|
432
789
|
case 'proven_tx_reqs':
|
|
790
|
+
case 'proven_tx_reqs_legacy':
|
|
433
791
|
sortColumn = 'provenTxReqId';
|
|
434
792
|
break;
|
|
435
793
|
case 'proven_txs':
|
|
794
|
+
case 'proven_txs_legacy':
|
|
436
795
|
sortColumn = 'provenTxId';
|
|
437
796
|
break;
|
|
438
797
|
case 'sync_states':
|
|
439
798
|
sortColumn = 'syncStateId';
|
|
440
799
|
break;
|
|
441
800
|
case 'transactions':
|
|
801
|
+
case 'transactions_legacy':
|
|
442
802
|
sortColumn = 'transactionId';
|
|
443
803
|
break;
|
|
444
804
|
case 'tx_labels':
|
|
@@ -489,7 +849,19 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
489
849
|
}
|
|
490
850
|
const q = this.setupQuery('outputs', args);
|
|
491
851
|
if ((args.txStatus != null) && args.txStatus.length > 0) {
|
|
492
|
-
|
|
852
|
+
// Post-cutover the `transactions` table carries `processing`
|
|
853
|
+
// (ProcessingStatus), not the legacy `status` column. We translate
|
|
854
|
+
// the requested legacy status set to its ProcessingStatus equivalents
|
|
855
|
+
// when the cutover has been applied; pre-cutover the original `status`
|
|
856
|
+
// column lookup remains correct.
|
|
857
|
+
if (this._postCutoverCache === true) {
|
|
858
|
+
const processingFilter = this.legacyStatiToProcessing(args.txStatus);
|
|
859
|
+
const filterList = processingFilter.map(s => "'" + s + "'").join(',');
|
|
860
|
+
q.whereRaw(`(select processing from transactions where transactions.transactionId = outputs.transactionId) in (${filterList})`);
|
|
861
|
+
}
|
|
862
|
+
else {
|
|
863
|
+
q.whereRaw(`(select status from transactions where transactions.transactionId = outputs.transactionId) in (${args.txStatus.map(s => "'" + s + "'").join(',')})`);
|
|
864
|
+
}
|
|
493
865
|
}
|
|
494
866
|
if (args.noScript && !count) {
|
|
495
867
|
const columns = tables_1.outputColumnsWithoutLockingScript.map(c => `outputs.${c}`);
|
|
@@ -506,14 +878,14 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
506
878
|
findOutputTagsQuery(args) {
|
|
507
879
|
return this.setupQuery('output_tags', args);
|
|
508
880
|
}
|
|
509
|
-
findProvenTxReqsQuery(args) {
|
|
881
|
+
findProvenTxReqsQuery(args, tableName = 'proven_tx_reqs') {
|
|
510
882
|
if (args.partial.rawTx != null) {
|
|
511
883
|
throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.rawTx', 'undefined. ProvenTxReqs may not be found by rawTx value.');
|
|
512
884
|
}
|
|
513
885
|
if (args.partial.inputBEEF != null) {
|
|
514
886
|
throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.inputBEEF', 'undefined. ProvenTxReqs may not be found by inputBEEF value.');
|
|
515
887
|
}
|
|
516
|
-
const q = this.setupQuery(
|
|
888
|
+
const q = this.setupQuery(tableName, args);
|
|
517
889
|
if ((args.status != null) && args.status.length > 0)
|
|
518
890
|
q.whereIn('status', args.status);
|
|
519
891
|
if (args.txids != null) {
|
|
@@ -523,17 +895,17 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
523
895
|
}
|
|
524
896
|
return q;
|
|
525
897
|
}
|
|
526
|
-
findProvenTxsQuery(args) {
|
|
898
|
+
findProvenTxsQuery(args, tableName = 'proven_txs') {
|
|
527
899
|
if (args.partial.rawTx != null) {
|
|
528
900
|
throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.rawTx', 'undefined. ProvenTxs may not be found by rawTx value.');
|
|
529
901
|
}
|
|
530
902
|
if (args.partial.merklePath != null) {
|
|
531
903
|
throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.merklePath', 'undefined. ProvenTxs may not be found by merklePath value.');
|
|
532
904
|
}
|
|
533
|
-
return this.setupQuery(
|
|
905
|
+
return this.setupQuery(tableName, args);
|
|
534
906
|
}
|
|
535
|
-
findStaleMerkleRootsQuery(args) {
|
|
536
|
-
const q = this.toDb(args.trx)(
|
|
907
|
+
findStaleMerkleRootsQuery(args, tableName = 'proven_txs') {
|
|
908
|
+
const q = this.toDb(args.trx)(tableName);
|
|
537
909
|
q.where('height', '=', args.height);
|
|
538
910
|
q.where('merkleRoot', '!=', args.merkleRoot);
|
|
539
911
|
q.select('merkleRoot');
|
|
@@ -550,7 +922,16 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
550
922
|
if (args.partial.inputBEEF != null) {
|
|
551
923
|
throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.inputBEEF', 'undefined. Transactions may not be found by inputBEEF value.');
|
|
552
924
|
}
|
|
553
|
-
|
|
925
|
+
// Post-cutover the original per-user `transactions` table has been
|
|
926
|
+
// renamed to `transactions_legacy`. `findTransactions` returns the
|
|
927
|
+
// legacy shape (status / userId / satoshis / description / etc.) so it
|
|
928
|
+
// must route at the renamed table to preserve the legacy read surface
|
|
929
|
+
// through the bridge period. New-canonical per-txid reads go through
|
|
930
|
+
// TransactionService.
|
|
931
|
+
const tableName = this._postCutoverCache === true
|
|
932
|
+
? 'transactions_legacy'
|
|
933
|
+
: 'transactions';
|
|
934
|
+
const q = this.setupQuery(tableName, args);
|
|
554
935
|
if ((args.status != null) && args.status.length > 0)
|
|
555
936
|
q.whereIn('status', args.status);
|
|
556
937
|
if (args.from != null)
|
|
@@ -558,7 +939,7 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
558
939
|
if (args.to != null)
|
|
559
940
|
q.where('created_at', '<', this.validateDateForWhere(args.to));
|
|
560
941
|
if (args.noRawTx && !count) {
|
|
561
|
-
const columns = tables_1.transactionColumnsWithoutRawTx.map(c =>
|
|
942
|
+
const columns = tables_1.transactionColumnsWithoutRawTx.map(c => `${tableName}.${c}`);
|
|
562
943
|
q.select(columns);
|
|
563
944
|
}
|
|
564
945
|
return q;
|
|
@@ -644,17 +1025,20 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
644
1025
|
return this.validateEntities(r, undefined, ['isDeleted']);
|
|
645
1026
|
}
|
|
646
1027
|
async findProvenTxReqs(args) {
|
|
647
|
-
const
|
|
1028
|
+
const reqTable = await this.provenTxReqsTableName();
|
|
1029
|
+
const q = this.findProvenTxReqsQuery(args, reqTable);
|
|
648
1030
|
const r = await q;
|
|
649
1031
|
return this.validateEntities(r, undefined, ['notified', 'wasBroadcast']);
|
|
650
1032
|
}
|
|
651
1033
|
async findProvenTxs(args) {
|
|
652
|
-
const
|
|
1034
|
+
const tableName = await this.provenTxsTableName();
|
|
1035
|
+
const q = this.findProvenTxsQuery(args, tableName);
|
|
653
1036
|
const r = await q;
|
|
654
1037
|
return this.validateEntities(r);
|
|
655
1038
|
}
|
|
656
1039
|
async findStaleMerkleRoots(args) {
|
|
657
|
-
const
|
|
1040
|
+
const tableName = await this.provenTxsTableName();
|
|
1041
|
+
const q = this.findStaleMerkleRootsQuery(args, tableName);
|
|
658
1042
|
const r = await q;
|
|
659
1043
|
return r.map((row) => row.merkleRoot);
|
|
660
1044
|
}
|
|
@@ -673,6 +1057,172 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
673
1057
|
}
|
|
674
1058
|
return this.validateEntities(r, undefined, ['isOutgoing']);
|
|
675
1059
|
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Post-cutover: queries `transactions_legacy` for unsigned/pending rows
|
|
1062
|
+
* created by `createAction` (which have no real txid yet and therefore have no
|
|
1063
|
+
* new-schema counterpart). Falls back to the standard `findTransactions` pre-cutover.
|
|
1064
|
+
*
|
|
1065
|
+
* Used by `processAction.validateCommitNewTxToStorageArgs` to locate the
|
|
1066
|
+
* unsigned transaction row by `{userId, reference}`.
|
|
1067
|
+
*
|
|
1068
|
+
* Mapping §2: legacy `unsigned` / `unprocessed` → these rows only exist in
|
|
1069
|
+
* `transactions_legacy` post-cutover; new `transactions` has `processing` not
|
|
1070
|
+
* `status` and no unsigned-state rows.
|
|
1071
|
+
*/
|
|
1072
|
+
async findLegacyTransactions(args) {
|
|
1073
|
+
const postCutover = await this.isPostCutover();
|
|
1074
|
+
if (!postCutover) {
|
|
1075
|
+
// Pre-cutover: standard transactions table
|
|
1076
|
+
return await this.findTransactions(args);
|
|
1077
|
+
}
|
|
1078
|
+
// Post-cutover: unsigned/unprocessed rows live in transactions_legacy
|
|
1079
|
+
const q = this.setupQuery('transactions_legacy', args);
|
|
1080
|
+
if ((args.status != null) && args.status.length > 0)
|
|
1081
|
+
q.whereIn('status', args.status);
|
|
1082
|
+
if (args.from != null)
|
|
1083
|
+
q.where('created_at', '>=', this.validateDateForWhere(args.from));
|
|
1084
|
+
if (args.to != null)
|
|
1085
|
+
q.where('created_at', '<', this.validateDateForWhere(args.to));
|
|
1086
|
+
if (args.noRawTx) {
|
|
1087
|
+
const columns = tables_1.transactionColumnsWithoutRawTx.map(c => `transactions_legacy.${c}`);
|
|
1088
|
+
q.select(columns);
|
|
1089
|
+
}
|
|
1090
|
+
const r = await q;
|
|
1091
|
+
if (!args.noRawTx) {
|
|
1092
|
+
for (const t of r) {
|
|
1093
|
+
await this.validateRawTransaction(t, args.trx);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
return this.validateEntities(r, undefined, ['isOutgoing']);
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Post-cutover: updates `transactions_legacy` (where unsigned rows live).
|
|
1100
|
+
* Pre-cutover: delegates to `updateTransaction`.
|
|
1101
|
+
*
|
|
1102
|
+
* Used by `processAction.commitNewTxToStorage` to write back the real txid
|
|
1103
|
+
* and status to the legacy row that was created by `createAction`.
|
|
1104
|
+
*
|
|
1105
|
+
* Mapping §2: legacy `unsigned` → `unprocessed` transition and txid write-back
|
|
1106
|
+
* must go to `transactions_legacy` post-cutover, not new `transactions`.
|
|
1107
|
+
*/
|
|
1108
|
+
async updateLegacyTransaction(id, update, trx) {
|
|
1109
|
+
const postCutover = await this.isPostCutover();
|
|
1110
|
+
if (!postCutover) {
|
|
1111
|
+
return await this.updateTransaction(id, update, trx);
|
|
1112
|
+
}
|
|
1113
|
+
// Post-cutover: unsigned rows are in transactions_legacy
|
|
1114
|
+
await this.verifyReadyForDatabaseAccess(trx);
|
|
1115
|
+
let r;
|
|
1116
|
+
if (Array.isArray(id)) {
|
|
1117
|
+
r = await this.toDb(trx)('transactions_legacy')
|
|
1118
|
+
.whereIn('transactionId', id)
|
|
1119
|
+
.update(this.validatePartialForUpdate(update));
|
|
1120
|
+
}
|
|
1121
|
+
else if (Number.isInteger(id)) {
|
|
1122
|
+
r = await this.toDb(trx)('transactions_legacy')
|
|
1123
|
+
.where({ transactionId: id })
|
|
1124
|
+
.update(this.validatePartialForUpdate(update));
|
|
1125
|
+
}
|
|
1126
|
+
else {
|
|
1127
|
+
throw new WERR_errors_1.WERR_INVALID_PARAMETER('id', 'transactionId or array of transactionId');
|
|
1128
|
+
}
|
|
1129
|
+
return r;
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Post-cutover SQLite: temporarily disable FK enforcement around the
|
|
1133
|
+
* `outputs.spentBy = legacyTransactionId` UPDATE. The `outputs.spentBy` FK
|
|
1134
|
+
* references `transactions.transactionId` after cutover, but unsigned
|
|
1135
|
+
* transactions from `createAction` live in `transactions_legacy` (their new-schema
|
|
1136
|
+
* counterpart is created later by `processAction`).
|
|
1137
|
+
*
|
|
1138
|
+
* IMPORTANT: We bypass `updateOutput` (and therefore `verifyReadyForDatabaseAccess`)
|
|
1139
|
+
* because `verifyReadyForDatabaseAccess` always re-enables FK with
|
|
1140
|
+
* `PRAGMA foreign_keys = ON`, which would undo our bypass. Instead we
|
|
1141
|
+
* directly run the UPDATE via the raw knex handle.
|
|
1142
|
+
*
|
|
1143
|
+
* Pre-cutover or MySQL: delegates to `updateOutput` unchanged.
|
|
1144
|
+
*
|
|
1145
|
+
* Mapping §2: bridge-period spentBy references transactions_legacy during
|
|
1146
|
+
* createAction; FK bypass covers this until processAction wires the new-schema.
|
|
1147
|
+
*/
|
|
1148
|
+
async markOutputAsSpentBy(outputId, update, trx) {
|
|
1149
|
+
const postCutover = await this.isPostCutover();
|
|
1150
|
+
const isSqlite = this.dbtype === 'SQLite';
|
|
1151
|
+
if (postCutover && isSqlite) {
|
|
1152
|
+
// Build the UPDATE payload the same way validatePartialForUpdate does,
|
|
1153
|
+
// but WITHOUT calling verifyReadyForDatabaseAccess (which always issues
|
|
1154
|
+
// PRAGMA foreign_keys = ON, undoing our bypass).
|
|
1155
|
+
const now = new Date().toISOString();
|
|
1156
|
+
const payload = { updated_at: now };
|
|
1157
|
+
for (const [k, v] of Object.entries(update)) {
|
|
1158
|
+
if (v === undefined) {
|
|
1159
|
+
payload[k] = null;
|
|
1160
|
+
}
|
|
1161
|
+
else if (Array.isArray(v) && (v.length === 0 || typeof v[0] === 'number')) {
|
|
1162
|
+
payload[k] = Buffer.from(v);
|
|
1163
|
+
}
|
|
1164
|
+
else {
|
|
1165
|
+
payload[k] = v;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
// Strategy: FK is a connection-level setting in SQLite. PRAGMA changes
|
|
1169
|
+
// inside a Knex transaction are no-ops (SQLite restriction), but PRAGMA
|
|
1170
|
+
// changes OUTSIDE a transaction persist.
|
|
1171
|
+
//
|
|
1172
|
+
// When called with trx (inside a transaction): the caller is expected to have
|
|
1173
|
+
// already disabled FK before opening the transaction. We run the UPDATE via
|
|
1174
|
+
// the transaction handle (no PRAGMA calls needed here — they'd be no-ops or
|
|
1175
|
+
// pool-exhausting anyway).
|
|
1176
|
+
//
|
|
1177
|
+
// When called without trx (outside a transaction): disable FK, run UPDATE,
|
|
1178
|
+
// re-enable FK.
|
|
1179
|
+
if (trx != null) {
|
|
1180
|
+
// Inside caller's transaction — FK was disabled before the transaction.
|
|
1181
|
+
// Just run the UPDATE; do NOT try to PRAGMA here (no-op or timeout).
|
|
1182
|
+
this.whenLastAccess = new Date();
|
|
1183
|
+
await (this.toDb(trx))('outputs')
|
|
1184
|
+
.where({ outputId })
|
|
1185
|
+
.update(payload);
|
|
1186
|
+
}
|
|
1187
|
+
else {
|
|
1188
|
+
// Outside any transaction — we can safely toggle FK on the bare connection.
|
|
1189
|
+
await this.knex.raw('PRAGMA foreign_keys = OFF');
|
|
1190
|
+
try {
|
|
1191
|
+
this.whenLastAccess = new Date();
|
|
1192
|
+
await this.knex('outputs').where({ outputId }).update(payload);
|
|
1193
|
+
}
|
|
1194
|
+
finally {
|
|
1195
|
+
await this.knex.raw('PRAGMA foreign_keys = ON');
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
else {
|
|
1200
|
+
await this.updateOutput(outputId, update, trx);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* Post-cutover SQLite: disable FK on the bare connection BEFORE opening a
|
|
1205
|
+
* transaction. PRAGMA foreign_keys changes inside SQLite transactions are no-ops,
|
|
1206
|
+
* so this MUST be called before `this.knex.transaction()`.
|
|
1207
|
+
*
|
|
1208
|
+
* Only acts when post-cutover AND SQLite. Pre-cutover or MySQL: no-op.
|
|
1209
|
+
*/
|
|
1210
|
+
async disableForeignKeys() {
|
|
1211
|
+
const postCutover = await this.isPostCutover();
|
|
1212
|
+
if (postCutover && this.dbtype === 'SQLite') {
|
|
1213
|
+
await this.knex.raw('PRAGMA foreign_keys = OFF');
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1217
|
+
* Re-enable FK after the transaction opened via `disableForeignKeys()` completes.
|
|
1218
|
+
* Only acts when post-cutover AND SQLite.
|
|
1219
|
+
*/
|
|
1220
|
+
async enableForeignKeys() {
|
|
1221
|
+
const postCutover = await this.isPostCutover();
|
|
1222
|
+
if (postCutover && this.dbtype === 'SQLite') {
|
|
1223
|
+
await this.knex.raw('PRAGMA foreign_keys = ON');
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
676
1226
|
async findTxLabelMaps(args) {
|
|
677
1227
|
const q = this.findTxLabelMapsQuery(args);
|
|
678
1228
|
const r = await q;
|
|
@@ -734,10 +1284,12 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
734
1284
|
return await this.getCount(this.findOutputTagsQuery(args));
|
|
735
1285
|
}
|
|
736
1286
|
async countProvenTxReqs(args) {
|
|
737
|
-
|
|
1287
|
+
const reqTable = await this.provenTxReqsTableName();
|
|
1288
|
+
return await this.getCount(this.findProvenTxReqsQuery(args, reqTable));
|
|
738
1289
|
}
|
|
739
1290
|
async countProvenTxs(args) {
|
|
740
|
-
|
|
1291
|
+
const tableName = await this.provenTxsTableName();
|
|
1292
|
+
return await this.getCount(this.findProvenTxsQuery(args, tableName));
|
|
741
1293
|
}
|
|
742
1294
|
async countSyncStates(args) {
|
|
743
1295
|
return await this.getCount(this.findSyncStatesQuery(args));
|
|
@@ -985,13 +1537,19 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
985
1537
|
* - sending (if excludeSending is false)
|
|
986
1538
|
*/
|
|
987
1539
|
async countChangeInputs(userId, basketId, excludeSending) {
|
|
988
|
-
const
|
|
1540
|
+
const legacyStatus = ['completed', 'unproven'];
|
|
989
1541
|
if (!excludeSending)
|
|
990
|
-
|
|
1542
|
+
legacyStatus.push('sending');
|
|
1543
|
+
const postCutover = await this.isPostCutover();
|
|
991
1544
|
const q = this.knex('outputs as o')
|
|
992
1545
|
.join('transactions as t', 'o.transactionId', 't.transactionId')
|
|
993
|
-
.where({ 'o.userId': userId, 'o.spendable': true, 'o.basketId': basketId })
|
|
994
|
-
|
|
1546
|
+
.where({ 'o.userId': userId, 'o.spendable': true, 'o.basketId': basketId });
|
|
1547
|
+
if (postCutover) {
|
|
1548
|
+
q.whereIn('t.processing', this.legacyStatiToProcessing(legacyStatus));
|
|
1549
|
+
}
|
|
1550
|
+
else {
|
|
1551
|
+
q.whereIn('t.status', legacyStatus);
|
|
1552
|
+
}
|
|
995
1553
|
const count = await this.getCount(q);
|
|
996
1554
|
return count;
|
|
997
1555
|
}
|
|
@@ -1072,15 +1630,20 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
1072
1630
|
return byTag;
|
|
1073
1631
|
}
|
|
1074
1632
|
async sumSpendableSatoshisInBasket(userId, basketId, excludeSending, trx) {
|
|
1075
|
-
const
|
|
1633
|
+
const legacyStatus = ['completed', 'unproven'];
|
|
1076
1634
|
if (!excludeSending)
|
|
1077
|
-
|
|
1078
|
-
const
|
|
1635
|
+
legacyStatus.push('sending');
|
|
1636
|
+
const postCutover = await this.isPostCutover();
|
|
1637
|
+
const q = this.toDb(trx)('outputs as o')
|
|
1079
1638
|
.join('transactions as t', 'o.transactionId', 't.transactionId')
|
|
1080
|
-
.where({ 'o.userId': userId, 'o.spendable': true, 'o.basketId': basketId })
|
|
1081
|
-
|
|
1082
|
-
.
|
|
1083
|
-
|
|
1639
|
+
.where({ 'o.userId': userId, 'o.spendable': true, 'o.basketId': basketId });
|
|
1640
|
+
if (postCutover) {
|
|
1641
|
+
q.whereIn('t.processing', this.legacyStatiToProcessing(legacyStatus));
|
|
1642
|
+
}
|
|
1643
|
+
else {
|
|
1644
|
+
q.whereIn('t.status', legacyStatus);
|
|
1645
|
+
}
|
|
1646
|
+
const row = await q.sum({ totalSatoshis: 'o.satoshis' }).first();
|
|
1084
1647
|
return Number(((row != null) && row.totalSatoshis) || 0);
|
|
1085
1648
|
}
|
|
1086
1649
|
/**
|
|
@@ -1089,44 +1652,78 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
|
|
|
1089
1652
|
* Transactionally allocate the output such that
|
|
1090
1653
|
*/
|
|
1091
1654
|
async allocateChangeInput(userId, basketId, targetSatoshis, exactSatoshis, excludeSending, transactionId) {
|
|
1092
|
-
const
|
|
1655
|
+
const legacyStatus = ['completed', 'unproven'];
|
|
1093
1656
|
if (!excludeSending)
|
|
1094
|
-
|
|
1095
|
-
const
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1657
|
+
legacyStatus.push('sending');
|
|
1658
|
+
const postCutover = await this.isPostCutover();
|
|
1659
|
+
const processingFilter = postCutover ? this.legacyStatiToProcessing(legacyStatus) : undefined;
|
|
1660
|
+
// Post-cutover bridge-period: `transactionId` here is a legacy row in
|
|
1661
|
+
// `transactions_legacy` (new unsigned tx created by createAction). The
|
|
1662
|
+
// `outputs.spentBy` FK now references `transactions.transactionId`, so
|
|
1663
|
+
// setting spentBy to a transactions_legacy ID would violate the constraint.
|
|
1664
|
+
// PRAGMA foreign_keys is silently ignored inside SQLite transactions, so
|
|
1665
|
+
// we toggle FK enforcement on the bare connection BEFORE opening the
|
|
1666
|
+
// transaction. processAction will remap spentBy to the real new transactionId
|
|
1667
|
+
// once the txid is known and the new schema row is created.
|
|
1668
|
+
const isSqlite = this.dbtype === 'SQLite';
|
|
1669
|
+
if (postCutover && isSqlite) {
|
|
1670
|
+
await this.knex.raw('PRAGMA foreign_keys = OFF');
|
|
1671
|
+
}
|
|
1672
|
+
let r;
|
|
1673
|
+
try {
|
|
1674
|
+
r = await this.knex.transaction(async (trx) => {
|
|
1675
|
+
const baseQuery = () => {
|
|
1676
|
+
const q = trx('outputs as o')
|
|
1677
|
+
.join('transactions as t', 'o.transactionId', 't.transactionId')
|
|
1678
|
+
.where('o.userId', userId)
|
|
1679
|
+
.where('o.spendable', true)
|
|
1680
|
+
.where('o.basketId', basketId)
|
|
1681
|
+
.select('o.*');
|
|
1682
|
+
if (processingFilter != null) {
|
|
1683
|
+
q.whereIn('t.processing', processingFilter);
|
|
1684
|
+
}
|
|
1685
|
+
else {
|
|
1686
|
+
q.whereIn('t.status', legacyStatus);
|
|
1687
|
+
}
|
|
1688
|
+
return q;
|
|
1689
|
+
};
|
|
1690
|
+
let output;
|
|
1691
|
+
if (exactSatoshis !== undefined) {
|
|
1692
|
+
output = await baseQuery().where('o.satoshis', exactSatoshis).orderBy('o.outputId', 'asc').first();
|
|
1693
|
+
}
|
|
1694
|
+
output !== null && output !== void 0 ? output : (output = await baseQuery()
|
|
1695
|
+
.where('o.satoshis', '>=', targetSatoshis)
|
|
1696
|
+
.orderBy('o.satoshis', 'asc')
|
|
1697
|
+
.orderBy('o.outputId', 'asc')
|
|
1698
|
+
.first());
|
|
1699
|
+
output !== null && output !== void 0 ? output : (output = await baseQuery()
|
|
1700
|
+
.where('o.satoshis', '<', targetSatoshis)
|
|
1701
|
+
.orderBy('o.satoshis', 'desc')
|
|
1702
|
+
.orderBy('o.outputId', 'desc')
|
|
1703
|
+
.first());
|
|
1704
|
+
if (output == null)
|
|
1705
|
+
return undefined;
|
|
1706
|
+
// Post-cutover: transactionId is in transactions_legacy; FK would fail.
|
|
1707
|
+
// markOutputAsSpentBy disables FK via raw knex handle, bypassing
|
|
1708
|
+
// verifyReadyForDatabaseAccess that would re-enable it.
|
|
1709
|
+
// Mapping §2: bridge-period spentBy → transactions_legacy during createAction.
|
|
1710
|
+
await this.markOutputAsSpentBy(output.outputId, {
|
|
1711
|
+
spendable: false,
|
|
1712
|
+
spentBy: transactionId
|
|
1713
|
+
}, trx);
|
|
1714
|
+
// Keep behavior identical to the pre-optimization path: ensure lockingScript
|
|
1715
|
+
// is present even when it was offloaded from outputs into rawTx storage.
|
|
1716
|
+
await this.validateOutputScript(output, trx);
|
|
1717
|
+
output.spendable = false;
|
|
1718
|
+
output.spentBy = transactionId;
|
|
1719
|
+
return output;
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
finally {
|
|
1723
|
+
if (postCutover && isSqlite) {
|
|
1724
|
+
await this.knex.raw('PRAGMA foreign_keys = ON');
|
|
1106
1725
|
}
|
|
1107
|
-
|
|
1108
|
-
.where('o.satoshis', '>=', targetSatoshis)
|
|
1109
|
-
.orderBy('o.satoshis', 'asc')
|
|
1110
|
-
.orderBy('o.outputId', 'asc')
|
|
1111
|
-
.first());
|
|
1112
|
-
output !== null && output !== void 0 ? output : (output = await baseQuery()
|
|
1113
|
-
.where('o.satoshis', '<', targetSatoshis)
|
|
1114
|
-
.orderBy('o.satoshis', 'desc')
|
|
1115
|
-
.orderBy('o.outputId', 'desc')
|
|
1116
|
-
.first());
|
|
1117
|
-
if (output == null)
|
|
1118
|
-
return undefined;
|
|
1119
|
-
await this.updateOutput(output.outputId, {
|
|
1120
|
-
spendable: false,
|
|
1121
|
-
spentBy: transactionId
|
|
1122
|
-
}, trx);
|
|
1123
|
-
// Keep behavior identical to the pre-optimization path: ensure lockingScript
|
|
1124
|
-
// is present even when it was offloaded from outputs into rawTx storage.
|
|
1125
|
-
await this.validateOutputScript(output, trx);
|
|
1126
|
-
output.spendable = false;
|
|
1127
|
-
output.spentBy = transactionId;
|
|
1128
|
-
return output;
|
|
1129
|
-
});
|
|
1726
|
+
}
|
|
1130
1727
|
return r;
|
|
1131
1728
|
}
|
|
1132
1729
|
/** Convert null→undefined and Buffer→number[] on a retrieved entity in-place. */
|