Fix CLN channel connection status shown inconsistently (#1606)

CLN's listpeerchannels reports connection state as peer_connected, but the
open/pending channel list columns read the legacy `connected` field, which the
backend never populated. It was therefore always empty, so the list always
rendered "Disconnected" while the detail panel (which reads peer_connected)
showed the true state — the contradiction reported in #1606.

Normalize `connected = peer_connected` in the backend listPeerChannels response
so legacy consumers stay in sync, and point the list columns at peer_connected
directly. Add regression specs asserting the connected column follows
peer_connected even when the legacy field disagrees.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
saubyk 2026-07-16 22:26:45 -07:00 committed by Suheb
parent 42cb3c76e8
commit 16d48417d8
5 changed files with 53 additions and 2 deletions

View file

@ -21,6 +21,9 @@ export const listPeerChannels = (req, res, next) => {
const getPeerAliasesTasks = body.channels.map((channel) => () => {
channel.to_them_msat = channel.total_msat - channel.to_us_msat;
channel.balancedness = (channel.total_msat === 0) ? 1 : (1 - Math.abs((channel.to_us_msat - channel.to_them_msat) / channel.total_msat)).toFixed(3);
// listpeerchannels reports connection state as peer_connected. Mirror it onto the
// legacy 'connected' field so backward-compat consumers stay in sync (issue #1606).
channel.connected = channel.peer_connected;
return getAlias(req.session.selectedNode, channel, 'peer_id');
});
common.runWithConcurrencyLimit(getPeerAliasesTasks, 20, () => {

View file

@ -66,7 +66,7 @@
</ng-container>
<ng-container matColumnDef="connected">
<th *matHeaderCellDef mat-header-cell mat-sort-header>Connected</th>
<td *matCellDef="let channel" mat-cell>{{(channel?.connected) ? 'Connected' : 'Disconnected'}}</td>
<td *matCellDef="let channel" mat-cell>{{(channel?.peer_connected) ? 'Connected' : 'Disconnected'}}</td>
</ng-container>
<ng-container matColumnDef="our_channel_reserve_satoshis">
<th *matHeaderCellDef mat-header-cell mat-sort-header arrowPosition="before">Local Reserve (Sats)</th>

View file

@ -19,6 +19,7 @@ import { CLNChannelOpenTableComponent } from './channel-open-table.component';
import { ExtraOptions, Route, Router } from '@angular/router';
import { HttpClientTestingModule, provideHttpClientTesting } from '@angular/common/http/testing';
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { MatTableDataSource } from '@angular/material/table';
describe('CLNChannelOpenTableComponent', () => {
let component: CLNChannelOpenTableComponent;
@ -58,6 +59,29 @@ describe('CLNChannelOpenTableComponent', () => {
expect(component).toBeTruthy();
});
const connectedCellText = (): string => {
const cell = fixture.nativeElement.querySelector('td.mat-column-connected');
return cell ? cell.textContent.trim() : '';
};
const renderSingleChannel = (channel: any) => {
component.displayedColumns = ['connected'];
component.channels = new MatTableDataSource<any>([channel]);
fixture.detectChanges();
};
// Issue #1606: the connected column must reflect peer_connected (what listpeerchannels
// returns), not the legacy 'connected' field, so it stays consistent with the detail panel.
it('should render Connected from peer_connected even when legacy connected is false', () => {
renderSingleChannel({ peer_connected: true, connected: false });
expect(connectedCellText()).toBe('Connected');
});
it('should render Disconnected from peer_connected even when legacy connected is true', () => {
renderSingleChannel({ peer_connected: false, connected: true });
expect(connectedCellText()).toBe('Disconnected');
});
afterEach(() => {
TestBed.resetTestingModule();
});

View file

@ -58,7 +58,7 @@
</ng-container>
<ng-container matColumnDef="connected">
<th *matHeaderCellDef mat-header-cell mat-sort-header>Connected</th>
<td *matCellDef="let channel" mat-cell>{{(channel?.connected) ? 'Connected' : 'Disconnected'}}</td>
<td *matCellDef="let channel" mat-cell>{{(channel?.peer_connected) ? 'Connected' : 'Disconnected'}}</td>
</ng-container>
<ng-container matColumnDef="state">
<th *matHeaderCellDef mat-header-cell mat-sort-header>State</th>

View file

@ -15,6 +15,7 @@ import { RTLEffects } from '../../../../../store/rtl.effects';
import { SharedModule } from '../../../../../shared/shared.module';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { DataService } from '../../../../../shared/services/data.service';
import { MatTableDataSource } from '@angular/material/table';
describe('CLNChannelPendingTableComponent', () => {
let component: CLNChannelPendingTableComponent;
@ -49,6 +50,29 @@ describe('CLNChannelPendingTableComponent', () => {
expect(component).toBeTruthy();
});
const connectedCellText = (): string => {
const cell = fixture.nativeElement.querySelector('td.mat-column-connected');
return cell ? cell.textContent.trim() : '';
};
const renderSingleChannel = (channel: any) => {
component.displayedColumns = ['connected'];
component.channels = new MatTableDataSource<any>([channel]);
fixture.detectChanges();
};
// Issue #1606: the connected column must reflect peer_connected (what listpeerchannels
// returns), not the legacy 'connected' field, so it stays consistent with the detail panel.
it('should render Connected from peer_connected even when legacy connected is false', () => {
renderSingleChannel({ peer_connected: true, connected: false });
expect(connectedCellText()).toBe('Connected');
});
it('should render Disconnected from peer_connected even when legacy connected is true', () => {
renderSingleChannel({ peer_connected: false, connected: true });
expect(connectedCellText()).toBe('Disconnected');
});
afterEach(() => {
TestBed.resetTestingModule();
});