Skip to content

Tell the operator whether their node exchanges transactions, not just blocks - #51

Merged
MendeMatthias merged 8 commits into
mainfrom
feat/tx-relay-health
Oct 29, 2026
Merged

MendeMatthias merged 8 commits into
mainfrom
feat/tx-relay-health

Conversation

@MendeMatthias

@MendeMatthias MendeMatthias commented Sep 6, 2026 •

Copy link
Copy Markdown
Owner

The gap. Every health signal easyNode has is about blocks: synced, peers, bytes served, archive census, stall verdict. A node can pass all of them and never receive a transaction, and nobody would know.

What the operator sees now. "Your node's role" ends with a Transactions line, same shape as the lines above it (mark, value, one plain note), sentence rendered in Rust:

verdict value note (example)
receiving Exchanging transactions 14 of 44 peers have sent this node transactions, so payments reach it and it passes them on.
unknown Not known yet Connected for 5 hours. Transactions are rare on BTX, so hearing none yet proves nothing. This answers after a day. (or: only N peers / still catching up)
isolated, no open peer Not receiving any None of the 8 peers takes transactions: they pass only blocks, or are still catching up themselves. In 30 hours no transaction has reached this node. Restart the node to find other peers.
isolated, open peers Not receiving any 5 of 8 peers take transactions, but in 30 hours not one has sent this node any. Either nobody is sending, or those peers do not pass them on. Restart the node to find other peers, and if it still says this a day later, ask in the BTX Community group.

unknown reads grey (helps = null), never green.

How it works. summarize_tx_relay reads the per-peer message byte counters (bytesrecv_per_msg.tx) from the getpeerinfo the refresher already runs each tick, so it costs no extra RPC. judge_tx_relay(summary, initial_block_download) turns it into receiving, isolated or unknown; the refresher caches it (AppState::tx_relay_cache, cleared on stop/start like the archive census), get_node_status returns it as tx_relay (TS type TxRelayHealth in main.ts) and appends tx_relay_line to role_lines. The local service report carries it as tx_relay, schema 3 (main was still at 2, so 3 was free).

PeerInfo gained conntime, relaytxes and minfeefilter. A peer object without conntime is unknown rather than connected since 1970.

Re-measured today, and two things changed from September. 2026-10-29 10:46 and 10:58 UTC, the two btxd v0.34.15 nodes behind btxscan.io (btx-esplora-1, /data/btx and /data/btx2), read-only getpeerinfo, getnetworkinfo, getmempoolinfo only:

node 1 node 2
peers 44 (27 in / 17 out) 31 (16 / 15)
peers that had delivered a transaction 14 16
peers that want transactions (relaytxes) 33 23
peers at the 0.09170997 "fee" 2 (0.32.3, 0.32.12) 3 (0.31.0, 0.33.2, 0.34.10)
minfeefilter values seen 0, 0.00001, 0.09170997 same
mempool 0 tx, mempoolminfee 0.00001 0 tx, 0.00001

Open peers (want transactions at the relay floor) by how long connected, open / delivered:

connected node 1 node 2
under 6 h 17 / 0 4 / 2
6 to 24 h 4 / 2 7 / 5
a day or more 13 / 12 9 / 9
  1. The window is now a day, not six hours. Relay works today, unlike September, but traffic is thin: 17 open peers under six hours old on a node that relays fine had delivered nothing between them, and two peers 6.5 h and 14.8 h old had each delivered the same 65.6 KB. Six hours would have called a healthy node isolated. After a day, 21 of 22 open peers had delivered.
  2. The 9,171 sat/byte peers are not pricing anything. 0.09170997 BTX/kvB is exactly MAX_MONEY rounded to the top fee-filter bucket, which is what a node sends while it is in initial block download. Every peer showing it ran an old engine. The wall threshold (0.01 BTX/kvB) still separates the three values peers actually send by orders of magnitude, so it stays; the wording now says "still catching up" instead of "asks a fee nobody pays", and a new open_to_tx count (wants transactions and not walled) picks the reason sentence. For the same reason the judge now answers unknown while this node itself is in initial block download, since it asks its peers for nothing then.

Measurements and dates are in comments at TX_WALL_BTX_PER_KVB and TX_RELAY_WINDOW_SECS in crates/btx-core/src/node_api.rs.

EasyBTX site: nothing reads service-report.json there (searched EasyBTX/site); the report is a local file and checkin.rs has its own schema. No site change needed.

Tests. 12 tx-relay tests in node_api.rs (the September peer shapes, plus: six quiet hours are not isolation, a node in initial download is not judged, the four line wordings, the wall threshold against the values peers send). Ran locally what CI runs, on this branch merged with main: web tsc --noEmit, npm test (280 passed), vite build; btx-core cargo check --locked --all-targets, cargo test --locked (1228 passed, 2 ignored, before the last main merge; after it, node::tests::an_adopted_node_is_found_by_name_and_lifted_before_its_stop hung past 60 s on this Mac, the known local-node case, and platform::unix::tests::process_cpu_time_reads_a_live_process failed once under load and passes alone; neither is touched here, CI is the judge); tauri shell cargo check --locked --all-targets and cargo test --locked (297 passed, 1 ignored).

Not in this change: no attempt to fix relay. This makes the failure visible, which is the precondition for that.

🤖 Generated with Claude Code

machine and others added 8 commits September 6, 2026 05:24
… blocks

A node can be synced, well peered, serving blocks and healthy by every
measure this app already has, while never once receiving a transaction.
Measured across two independent public BTX nodes on 2026-09-06: 21 of 22
peers had never sent one, the two nodes' mempools shared none of their 10
and 32 entries, and valid transactions paying twice the going rate had
waited four days while blocks were mined empty. Nothing reported a problem
because nothing was measuring it.

summarize_tx_relay reads the per-peer message byte counters directly, the
same evidence that identified the one working archive during the
api.btxscan.io incident, and judge_tx_relay turns that into receiving,
isolated or unknown. It rides the peer census the app already runs, so it
costs no RPC, and it lands in the local service report as tx_relay
(schema 3).

Unknown is the default and a first-class answer. A node with under three
peers, or whose oldest connection is under six hours, has observed nothing:
BTX carries roughly ten transactions an hour network-wide, so a quiet half
hour proves nothing, and a check that cannot evaluate must never read as a
check that passed. PeerInfo gained conntime, relaytxes and minfeefilter; a
peer object without conntime is unknown rather than connected since 1970,
which is the difference between a fresh node and an accusation.

Six tests, built on the live peer shapes from that measurement.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
# Conflicts:
#	apps/node/CHANGELOG.md
#	crates/btx-core/src/node_api.rs
The tx-relay verdict now reaches the screen. "Your node's role" ends with a
Transactions line (btx_core::node_api::tx_relay_line), from the refresher's
per-tick peer census: Exchanging transactions, Not receiving any with the
likely reason and what to do, or Not known yet and why. get_node_status
also returns the verdict itself as tx_relay, with its TypeScript type.

Re-measured 2026-10-29 on the two v0.34.15 nodes behind btxscan.io (read-
only getpeerinfo/getnetworkinfo/getmempoolinfo). Relay works today, but 17
open peers under six hours old on one node had delivered no transaction,
so six hours would call a healthy node isolated; the window is a day.
The 0.09170997 BTX/kvB peers are nodes in initial block download, not a
fee policy, so the wording says so, the judge counts peers open to
transactions, and a node in initial download is not judged at all. The
measurements sit beside TX_WALL_BTX_PER_KVB and TX_RELAY_WINDOW_SECS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g entries

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MendeMatthias
MendeMatthias merged commit 97cbbf8 into main Oct 29, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant