diff --git a/backend/src/api/database-migration.ts b/backend/src/api/database-migration.ts index ec201de0c..20e5ab339 100644 --- a/backend/src/api/database-migration.ts +++ b/backend/src/api/database-migration.ts @@ -4,7 +4,7 @@ import logger from '../logger'; import { Common } from './common'; class DatabaseMigration { - private static currentVersion = 41; + private static currentVersion = 42; private queryTimeout = 120000; private statisticsAddedIndexed = false; private uniqueLogs: string[] = []; @@ -352,6 +352,10 @@ class DatabaseMigration { if (databaseSchemaVersion < 41 && isBitcoin === true) { await this.$executeQuery('UPDATE channels SET closing_reason = NULL WHERE closing_reason = 1'); } + + if (databaseSchemaVersion < 42 && isBitcoin === true) { + await this.$executeQuery('ALTER TABLE `channels` ADD closing_resolved tinyint(1) DEFAULT 0'); + } } /** diff --git a/backend/src/api/explorer/channels.api.ts b/backend/src/api/explorer/channels.api.ts index a52b0f28f..787bbe521 100644 --- a/backend/src/api/explorer/channels.api.ts +++ b/backend/src/api/explorer/channels.api.ts @@ -117,6 +117,17 @@ class ChannelsApi { } } + public async $getUnresolvedClosedChannels(): Promise { + try { + const query = `SELECT * FROM channels WHERE status = 2 AND closing_reason = 2 AND closing_resolved = 0 AND closing_transaction_id != ''`; + const [rows]: any = await DB.query(query); + return rows; + } catch (e) { + logger.err('$getUnresolvedClosedChannels error: ' + (e instanceof Error ? e.message : e)); + throw e; + } + } + public async $getChannelsWithoutCreatedDate(): Promise { try { const query = `SELECT * FROM channels WHERE created IS NULL`; diff --git a/backend/src/tasks/lightning/network-sync.service.ts b/backend/src/tasks/lightning/network-sync.service.ts index 838170a3e..70173d6bc 100644 --- a/backend/src/tasks/lightning/network-sync.service.ts +++ b/backend/src/tasks/lightning/network-sync.service.ts @@ -309,7 +309,7 @@ class NetworkSyncService { └──────────────────┘ */ - private async $runClosedChannelsForensics(): Promise { + private async $runClosedChannelsForensics(skipUnresolved: boolean = false): Promise { if (!config.ESPLORA.REST_API_URL) { return; } @@ -318,9 +318,18 @@ class NetworkSyncService { try { logger.info(`Started running closed channel forensics...`); - const channels = await channelsApi.$getClosedChannelsWithoutReason(); + let channels; + const closedChannels = await channelsApi.$getClosedChannelsWithoutReason(); + if (skipUnresolved) { + channels = closedChannels; + } else { + const unresolvedChannels = await channelsApi.$getUnresolvedClosedChannels(); + channels = [...closedChannels, ...unresolvedChannels]; + } + for (const channel of channels) { let reason = 0; + let resolvedForceClose = false; // Only Esplora backend can retrieve spent transaction outputs try { let outspends: IEsploraApi.Outspend[] | undefined; @@ -350,6 +359,7 @@ class NetworkSyncService { reason = 3; } else { reason = 2; + resolvedForceClose = true; } } else { /* @@ -374,6 +384,9 @@ class NetworkSyncService { if (reason) { logger.debug('Setting closing reason ' + reason + ' for channel: ' + channel.id + '.'); await DB.query(`UPDATE channels SET closing_reason = ? WHERE id = ?`, [reason, channel.id]); + if (reason === 2 && resolvedForceClose) { + await DB.query(`UPDATE channels SET closing_resolved = ? WHERE id = ?`, [true, channel.id]); + } } } catch (e) { logger.err(`$runClosedChannelsForensics() failed for channel ${channel.short_id}. Reason: ${e instanceof Error ? e.message : e}`);