qt: add Peers sub-tab in Text Based Worship — port from Bitcoin Core 0.10 #13

Closed
opened 2026-06-06 22:05:43 +00:00 by dobbscoin · 1 comment
dobbscoin commented 2026-06-06 22:05:43 +00:00

Why

Today, to see who your OFF wallet is talking to, you have to:

  1. Help → Text Based Worship → Console
  2. Type getpeerinfo
  3. Scrape JSON for IPs, subversions, ping times, ban scores
  4. Repeat every time you want a fresh view

Every other Bitcoin-Core-descended wallet of comparable vintage ships a Peers tab in the Debug Window: a live QTableView of connected peers (address, subversion, ping), with a per-peer detail pane below (connection direction, services, traffic, starting height, last send/recv, ban score, etc.). It's the single most useful operator-facing screen in the wallet.

OFF doesn't have it. The ancestor codebase (thegreatoldone's v1.6.2) appears to have been forked from upstream before the Peers tab landed and never picked it up. The sibling (BOB) / Dobbscoin wallet of the same 0.10-fork lineage has it — see dobbscoin-source/src/qt/peertablemodel.{cpp,h} and the corresponding tab_peers widget in rpcconsole.ui.

This issue is the port of that tab into OFF.

What's already in place

The good news: the entire backend data plumbing already exists in OFF. No net.cpp / main.cpp changes are required.

Component OFF location Status
CNodeStats src/net.h:127 ✓ present
CNode::copyStats(CNodeStats&) src/net.cpp:493 ✓ present
CNodeStateStats src/main.h:214 ✓ present
GetNodeStateStats(NodeId, CNodeStateStats&) src/main.cpp:324 ✓ present
NodeId typedef src/net.h:58 ✓ present
nLastNodeId src/net.h:116 ✓ present

So this is a Qt-only patch. The PeerTableModel reads from APIs that already exist.

Placement: fourth sub-tab inside Text Based Worship

The QTabWidget at src/qt/forms/rpcconsole.ui:18 (named tabWidget) currently holds three sub-tabs:

  1. Information
  2. Console
  3. Network Traffic

After this issue lands, four:

  1. Information
  2. Console
  3. Network Traffic
  4. Peers (new)

(Note: there's an open companion issue #8 proposing a fifth — Mining — for post-Codex. The two tabs are independent and can land in any order. The natural reading-order of the sub-tabs once both are present is Information / Console / Network Traffic / Peers / Mining.)

Reference layout — what the tab looks like

Mirror upstream / (BOB):

  • Top pane: QTableView peerWidget showing all connected peers, three columns:
    • Address (e.g. 185.197.251.40:48146)
    • Subversion (e.g. /Offerings:2.0.5/)
    • Ping (latency in ms, or 0 if not yet measured)
    • Click-to-sort on any column. Auto-refresh every ~1s while tab visible (timer started/stopped by the on_tabWidget_currentChanged hook at src/qt/rpcconsole.cpp:436).
  • Bottom pane: per-peer detail group box, populated on row selection:
    • Direction (Inbound / Outbound)
    • Version (numeric nVersion, e.g. 90003)
    • Services bitmask
    • Starting Height
    • Last Send / Last Receive (relative time)
    • Bytes Sent / Bytes Received
    • Connection Time (uptime)
    • Time Offset (peer's clock skew vs ours)
    • Ping Time / Ping Wait
    • Sync Height (pindexBestKnownBlock->nHeight if known)
    • Misbehavior (ban score)

When no peer selected, the detail pane stays greyed/empty.

Implementation plan

New files (copy-and-rename from (BOB), s/DOBBSCOIN_/OFFERINGS_/):

  1. src/qt/peertablemodel.h — QAbstractTableModel subclass exposing the live peer list. Mirror dobbscoin-source/src/qt/peertablemodel.h (~80 LOC).
  2. src/qt/peertablemodel.cpp — model implementation: refresh() slot polls vNodes + GetNodeStateStats(), populates CNodeCombinedStats rows, emits dataChanged. Mirror dobbscoin-source/src/qt/peertablemodel.cpp (~240 LOC).

Modified files:

  1. src/qt/forms/rpcconsole.ui — add a <widget class=\"QWidget\" name=\"tab_peers\"> block to tabWidget. Inside: a QSplitter with peerWidget (QTableView) on top and a QGroupBox name=\"peerHeading\" of detail labels on the bottom. ~250 lines of .ui markup; pattern copies cleanly from dobbscoin-source/src/qt/forms/rpcconsole.ui at the tab_peers widget. Same PR: opportunistic cleanup — rename windowTitle at rpcconsole.ui:14 from Debug window to Text Based Worship, matching the menu action at bitcoingui.cpp:314.
  2. src/qt/rpcconsole.h — declare peerSelected(const QItemSelection&, const QItemSelection&) slot and updateNodeDetail(const CNodeCombinedStats*) helper. Also declare a clientModel member if not already cached.
  3. src/qt/rpcconsole.cpp — in setClientModel() at line 264, wire ui->peerWidget->setModel(model->getPeerTableModel()), configure column widths/sort behavior, connect the selection model's selectionChanged signal to the new slot. Extend on_tabWidget_currentChanged() at line 436 to call startAutoRefresh() / stopAutoRefresh() on the peer model when entering/leaving the Peers tab (mirrors the existing console/nettraffic polling pattern).
  4. src/qt/clientmodel.h and src/qt/clientmodel.cpp — add PeerTableModel *peerTableModel member, instantiate in the ClientModel constructor, expose via getPeerTableModel() accessor.
  5. src/qt/Makefile.am (or Makefile.qt.include per OFF's autotools layout) — add peertablemodel.cpp to bitcoin_qt_SOURCES and peertablemodel.h to the BITCOIN_QT_H list. Regenerate the bitcoin_qt.qrc MOC manifest if applicable.

Net delta: ~320 LOC C++/Qt + ~250 lines .ui markup. No backend changes. No consensus impact. No fork.

Trade-offs / What this isn't

  • Not a consensus change. No activation height, no protocol bump, no upgrade pressure. Cosmetic UI only.
  • Not a security risk. Reads existing APIs (vNodes, GetNodeStateStats) under the existing locks (cs_vNodes, cs_main); no new attack surface on the network layer.
  • Not a new dependency. Stock Qt 5, same as the rest of the wallet. No bundled binaries.
  • Not urgent. The Console + getpeerinfo path still works. This is operator-quality-of-life, not a missing critical feature. Could land in v2.1.x or v2.2.x; doesn't block the post-Codex v2.1.0 release.

Worth doing now, specifically

With the PROTOCOL_VERSION 80002 → 90003 era bump now live, the Peers tab becomes more useful than it would have been pre-Restoration: at a glance, the operator can see which connected peers are still on 80001 (pre-Restoration nodes, candidates for the future MIN_PEER_PROTO_VERSION eviction lever) vs. 90003 (post-Restoration). Today that data is buried in getpeerinfo JSON; the Peers tab surfaces it in the Version column of the detail pane and in the Subversion column of the table.

Reference behavior to mirror

  • dobbscoin-source/src/qt/peertablemodel.{cpp,h} — drop-in template, change the include guard from DOBBSCOIN_QT_PEERTABLEMODEL_H to OFFERINGS_QT_PEERTABLEMODEL_H
  • dobbscoin-source/src/qt/forms/rpcconsole.ui — <widget class=\"QWidget\" name=\"tab_peers\"> block as the layout template
  • dobbscoin-source/src/qt/rpcconsole.cpp::setClientModel — wiring pattern (table view ↔ model, selection signal ↔ detail-update slot)
  • Bitcoin Core history: PR #4566 introduced the NodeStats model, PR #5288 added the Peers tab; both pre-date the 0.10 release that OFF's lineage descends from

Open questions (decide in PR review)

  • Auto-refresh interval — 1000 ms (matches upstream) or slower? 1s is fine; only the visible-tab burns CPU thanks to the currentChanged start/stop hook.
  • Column set in the top table — minimum is Address / Subversion / Ping. Upstream later added NetNodeId. Worth including for OFF where node-ID-based ban tracking matters? My lean: keep minimum for v1 of this tab, add NetNodeId column in a follow-up.
  • Ban-score visibility — show in the detail pane only (read-only), or also wire the upstream right-click context menu for Disconnect Node / Ban Node / Copy Address? My lean: detail-pane only for v1; context menu in a follow-up PR.

References

  • src/qt/forms/rpcconsole.ui:18 — QTabWidget name=\"tabWidget\" (integration point)
  • src/qt/forms/rpcconsole.ui:14 — windowTitle = \"Debug window\" (rebrand cleanup candidate, same PR)
  • src/qt/rpcconsole.cpp:264::setClientModel — wiring point
  • src/qt/rpcconsole.cpp:436::on_tabWidget_currentChanged — visible-tab polling hook
  • src/qt/clientmodel.{cpp,h} — needs getPeerTableModel() accessor
  • src/net.h:127::CNodeStats, src/net.cpp:493::CNode::copyStats — data source
  • src/main.h:214::CNodeStateStats, src/main.cpp:324::GetNodeStateStats — supplementary data source
  • Reference implementation: dobbscoin-source/src/qt/peertablemodel.* and dobbscoin-source/src/qt/forms/rpcconsole.ui
  • Companion issue (independent): #8 (Mining sub-tab)

Even the Great Old One keeps a guest list. Iä Iä.

## Why Today, to see who your OFF wallet is talking to, you have to: 1. `Help → Text Based Worship → Console` 2. Type `getpeerinfo` 3. Scrape JSON for IPs, subversions, ping times, ban scores 4. Repeat every time you want a fresh view Every other Bitcoin-Core-descended wallet of comparable vintage ships a **Peers** tab in the Debug Window: a live `QTableView` of connected peers (address, subversion, ping), with a per-peer detail pane below (connection direction, services, traffic, starting height, last send/recv, ban score, etc.). It's the single most useful operator-facing screen in the wallet. OFF doesn't have it. The ancestor codebase (thegreatoldone's v1.6.2) appears to have been forked from upstream *before* the Peers tab landed and never picked it up. The sibling (BOB) / Dobbscoin wallet of the same 0.10-fork lineage has it — see `dobbscoin-source/src/qt/peertablemodel.{cpp,h}` and the corresponding `tab_peers` widget in `rpcconsole.ui`. This issue is the port of that tab into OFF. ## What's already in place The good news: **the entire backend data plumbing already exists in OFF.** No `net.cpp` / `main.cpp` changes are required. | Component | OFF location | Status | |---|---|---| | `CNodeStats` | `src/net.h:127` | ✓ present | | `CNode::copyStats(CNodeStats&)` | `src/net.cpp:493` | ✓ present | | `CNodeStateStats` | `src/main.h:214` | ✓ present | | `GetNodeStateStats(NodeId, CNodeStateStats&)` | `src/main.cpp:324` | ✓ present | | `NodeId` typedef | `src/net.h:58` | ✓ present | | `nLastNodeId` | `src/net.h:116` | ✓ present | So this is a **Qt-only patch**. The `PeerTableModel` reads from APIs that already exist. ## Placement: fourth sub-tab inside Text Based Worship The `QTabWidget` at `src/qt/forms/rpcconsole.ui:18` (named `tabWidget`) currently holds three sub-tabs: 1. Information 2. Console 3. Network Traffic After this issue lands, four: 1. Information 2. Console 3. Network Traffic 4. **Peers** *(new)* (Note: there's an open companion issue #8 proposing a fifth — Mining — for post-Codex. The two tabs are independent and can land in any order. The natural reading-order of the sub-tabs once both are present is Information / Console / Network Traffic / Peers / Mining.) ## Reference layout — what the tab looks like Mirror upstream / (BOB): - **Top pane:** `QTableView peerWidget` showing all connected peers, three columns: - `Address` (e.g. `185.197.251.40:48146`) - `Subversion` (e.g. `/Offerings:2.0.5/`) - `Ping` (latency in ms, or `0` if not yet measured) - Click-to-sort on any column. Auto-refresh every ~1s while tab visible (timer started/stopped by the `on_tabWidget_currentChanged` hook at `src/qt/rpcconsole.cpp:436`). - **Bottom pane:** per-peer detail group box, populated on row selection: - Direction (Inbound / Outbound) - Version (numeric `nVersion`, e.g. `90003`) - Services bitmask - Starting Height - Last Send / Last Receive (relative time) - Bytes Sent / Bytes Received - Connection Time (uptime) - Time Offset (peer's clock skew vs ours) - Ping Time / Ping Wait - Sync Height (`pindexBestKnownBlock->nHeight` if known) - Misbehavior (ban score) When no peer selected, the detail pane stays greyed/empty. ## Implementation plan **New files (copy-and-rename from (BOB), `s/DOBBSCOIN_/OFFERINGS_/`):** 1. `src/qt/peertablemodel.h` — `QAbstractTableModel` subclass exposing the live peer list. Mirror `dobbscoin-source/src/qt/peertablemodel.h` (~80 LOC). 2. `src/qt/peertablemodel.cpp` — model implementation: `refresh()` slot polls `vNodes` + `GetNodeStateStats()`, populates `CNodeCombinedStats` rows, emits `dataChanged`. Mirror `dobbscoin-source/src/qt/peertablemodel.cpp` (~240 LOC). **Modified files:** 3. `src/qt/forms/rpcconsole.ui` — add a `<widget class=\"QWidget\" name=\"tab_peers\">` block to `tabWidget`. Inside: a `QSplitter` with `peerWidget` (QTableView) on top and a `QGroupBox name=\"peerHeading\"` of detail labels on the bottom. ~250 lines of `.ui` markup; pattern copies cleanly from `dobbscoin-source/src/qt/forms/rpcconsole.ui` at the `tab_peers` widget. **Same PR: opportunistic cleanup** — rename `windowTitle` at `rpcconsole.ui:14` from `Debug window` to `Text Based Worship`, matching the menu action at `bitcoingui.cpp:314`. 4. `src/qt/rpcconsole.h` — declare `peerSelected(const QItemSelection&, const QItemSelection&)` slot and `updateNodeDetail(const CNodeCombinedStats*)` helper. Also declare a `clientModel` member if not already cached. 5. `src/qt/rpcconsole.cpp` — in `setClientModel()` at line 264, wire `ui->peerWidget->setModel(model->getPeerTableModel())`, configure column widths/sort behavior, connect the selection model's `selectionChanged` signal to the new slot. Extend `on_tabWidget_currentChanged()` at line 436 to call `startAutoRefresh()` / `stopAutoRefresh()` on the peer model when entering/leaving the Peers tab (mirrors the existing console/nettraffic polling pattern). 6. `src/qt/clientmodel.h` and `src/qt/clientmodel.cpp` — add `PeerTableModel *peerTableModel` member, instantiate in the ClientModel constructor, expose via `getPeerTableModel()` accessor. 7. `src/qt/Makefile.am` (or `Makefile.qt.include` per OFF's autotools layout) — add `peertablemodel.cpp` to `bitcoin_qt_SOURCES` and `peertablemodel.h` to the `BITCOIN_QT_H` list. Regenerate the `bitcoin_qt.qrc` MOC manifest if applicable. **Net delta:** ~320 LOC C++/Qt + ~250 lines `.ui` markup. **No backend changes. No consensus impact. No fork.** ## Trade-offs / What this isn't - **Not a consensus change.** No activation height, no protocol bump, no upgrade pressure. Cosmetic UI only. - **Not a security risk.** Reads existing APIs (`vNodes`, `GetNodeStateStats`) under the existing locks (`cs_vNodes`, `cs_main`); no new attack surface on the network layer. - **Not a new dependency.** Stock Qt 5, same as the rest of the wallet. No bundled binaries. - **Not urgent.** The Console + `getpeerinfo` path still works. This is operator-quality-of-life, not a missing critical feature. Could land in v2.1.x or v2.2.x; doesn't block the post-Codex v2.1.0 release. ## Worth doing now, specifically With the `PROTOCOL_VERSION` 80002 → 90003 era bump now live, the Peers tab becomes *more* useful than it would have been pre-Restoration: at a glance, the operator can see which connected peers are still on `80001` (pre-Restoration nodes, candidates for the future `MIN_PEER_PROTO_VERSION` eviction lever) vs. `90003` (post-Restoration). Today that data is buried in `getpeerinfo` JSON; the Peers tab surfaces it in the `Version` column of the detail pane and in the `Subversion` column of the table. ## Reference behavior to mirror - `dobbscoin-source/src/qt/peertablemodel.{cpp,h}` — drop-in template, change the include guard from `DOBBSCOIN_QT_PEERTABLEMODEL_H` to `OFFERINGS_QT_PEERTABLEMODEL_H` - `dobbscoin-source/src/qt/forms/rpcconsole.ui` — `<widget class=\"QWidget\" name=\"tab_peers\">` block as the layout template - `dobbscoin-source/src/qt/rpcconsole.cpp::setClientModel` — wiring pattern (table view ↔ model, selection signal ↔ detail-update slot) - Bitcoin Core history: PR #4566 introduced the `NodeStats` model, PR #5288 added the Peers tab; both pre-date the 0.10 release that OFF's lineage descends from ## Open questions (decide in PR review) - **Auto-refresh interval** — 1000 ms (matches upstream) or slower? 1s is fine; only the visible-tab burns CPU thanks to the `currentChanged` start/stop hook. - **Column set in the top table** — minimum is `Address / Subversion / Ping`. Upstream later added `NetNodeId`. Worth including for OFF where node-ID-based ban tracking matters? My lean: keep minimum for v1 of this tab, add NetNodeId column in a follow-up. - **Ban-score visibility** — show in the detail pane only (read-only), or also wire the upstream right-click context menu for `Disconnect Node` / `Ban Node` / `Copy Address`? My lean: detail-pane only for v1; context menu in a follow-up PR. ## References - `src/qt/forms/rpcconsole.ui:18` — `QTabWidget name=\"tabWidget\"` (integration point) - `src/qt/forms/rpcconsole.ui:14` — `windowTitle = \"Debug window\"` (rebrand cleanup candidate, same PR) - `src/qt/rpcconsole.cpp:264::setClientModel` — wiring point - `src/qt/rpcconsole.cpp:436::on_tabWidget_currentChanged` — visible-tab polling hook - `src/qt/clientmodel.{cpp,h}` — needs `getPeerTableModel()` accessor - `src/net.h:127::CNodeStats`, `src/net.cpp:493::CNode::copyStats` — data source - `src/main.h:214::CNodeStateStats`, `src/main.cpp:324::GetNodeStateStats` — supplementary data source - Reference implementation: `dobbscoin-source/src/qt/peertablemodel.*` and `dobbscoin-source/src/qt/forms/rpcconsole.ui` - Companion issue (independent): #8 (Mining sub-tab) --- *Even the Great Old One keeps a guest list. Iä Iä.*
dobbscoin closed this issue 2026-06-06 22:25:37 +00:00
dobbscoin commented 2026-06-06 22:50:09 +00:00

Tested end-to-end on the merge commit (d8cfdf5):

  • Linux x86_64 (local Qt5 build, stripped): tab renders, populates with live peers, columns sort, row selection fills the detail pane (direction / version / user agent / services / starting height / ban score / connection time / last send-recv / bytes / ping). windowTitle reads "Text Based Worship" as intended.
  • Windows x86_64 (CI artifact from windows-build-depends.yml, Qt 5.15.16 / GCC 13-posix / depends/ cross-compile): same — Peers tab present and functional.

The post-fork visibility argument holds up in practice: with PROTOCOL_VERSION now at 90003, the Peers tab makes pre-Restoration (80001) vs post-Restoration (90003) peers immediately legible in the Version column of the detail pane — no getpeerinfo scraping required.

Deferred to follow-ups (as flagged in the issue's open questions):

  • NetNodeId column in the top table
  • Right-click context menu (Disconnect / Ban / Copy Address)
  • Sync Height — needs nSyncHeight added to CNodeStateStats first, which is a backend touch and out of scope here

Closing on next sync via the closes #13 trailer in d8cfdf5.


The guest list is now visible. Iä Iä.

Tested end-to-end on the merge commit (`d8cfdf5`): - **Linux x86_64** (local Qt5 build, stripped): tab renders, populates with live peers, columns sort, row selection fills the detail pane (direction / version / user agent / services / starting height / ban score / connection time / last send-recv / bytes / ping). `windowTitle` reads "Text Based Worship" as intended. - **Windows x86_64** (CI artifact from `windows-build-depends.yml`, Qt 5.15.16 / GCC 13-posix / depends/ cross-compile): same — Peers tab present and functional. The post-fork visibility argument holds up in practice: with `PROTOCOL_VERSION` now at 90003, the Peers tab makes pre-Restoration (80001) vs post-Restoration (90003) peers immediately legible in the **Version** column of the detail pane — no `getpeerinfo` scraping required. Deferred to follow-ups (as flagged in the issue's open questions): - **NetNodeId** column in the top table - **Right-click context menu** (Disconnect / Ban / Copy Address) - **Sync Height** — needs `nSyncHeight` added to `CNodeStateStats` first, which is a backend touch and out of scope here Closing on next sync via the `closes #13` trailer in `d8cfdf5`. --- *The guest list is now visible. Iä Iä.*
Sign in to join this conversation.
No labels
enhancement
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
SubGeniusFinance/Offerings-to-Cthulhu#13
No description provided.