Skip to content

explorer: restore treasury view by treating explicit height=0 distinctly from a missing height#2064

Merged
Maxnflaxl merged 1 commit into
masterfrom
fix-treasury-req
Jul 6, 2026
Merged

explorer: restore treasury view by treating explicit height=0 distinctly from a missing height#2064
Maxnflaxl merged 1 commit into
masterfrom
fix-treasury-req

Conversation

@Maxnflaxl

@Maxnflaxl Maxnflaxl commented Jul 5, 2026

Copy link
Copy Markdown
Member

Summary

The treasury view (?network=mainnet&type=treasury, which requests block?height=0) was silently broken: it displayed the current tip block instead of the treasury UTXO groups.

The cause is #1990 ("display current block instead of treasury when calling block with no arguments"). That change implemented "no arguments" as if (height) { … } else { return tip }, which treats an absent height and an explicit height=0 identically. As a result, the only route that reaches get_treasury() on a synced full node was removed, and the frontend's treasury link — which deliberately sends height=0 — started rendering the latest block.

The get_treasury() fallback in get_block() was noted at the time as "kept for safety … in practice never reached under normal operation." That was accurate, but it was the symptom: the code path had a real caller (type=treasuryheight=0), so making it unreachable broke a feature rather than pruning dead code.

Root cause

"No argument" and "height=0" were never actually distinct in this code — the ambiguity is structural, in two layers:

  1. Height is uint64_t and 0 is a valid height (the treasury pseudo-block), yet 0 is also the "unspecified" sentinel. The value meaning "give me the zero-height block" is the same value meaning "caller gave nothing."
  2. get_int_arg("height", 0) erases the distinction that still existed in the raw request. The args map knows whether height was in the URL, but get_int_arg returns the default 0 for an absent arg — the identical int64_t produced by an explicit &height=0. From that call onward the two are physically indistinguishable, and get_block's if (height) reads both as "no height."

So it wasn't a deliberate decision to equate them — the code simply never carried a "was it provided?" bit, and reused 0 for two meanings. That's why the fix has to reach underneath get_int_arg.

Fix

Distinguish the two cases at the router level, in Server::on_request_block, by checking the raw args map (the one layer that still preserves presence-vs-absence):

A get_treasury() method is added to the IAdapter interface so the router can dispatch to it directly, and the existing fallback inside get_block() is annotated as a genuine edge-case guard (fresh node with no blocks, or prune-navigation via adj<0).

Changes

  • explorer/server.cpp — in on_request_block, route an explicit height=0 to get_treasury(); a missing height still falls through to get_block().
  • explorer/adapter.h — add virtual json get_treasury() = 0; to IAdapter.
  • explorer/adapter.cpp — expose get_treasury() (wraps the existing private get_treasury(json&)); document the in-get_block fallback as an edge-case guard.

Behavior

Request Before After
block (no height) tip tip
block?height=0 tip ❌ treasury ✅
block?height=N block N block N
?type=treasury (frontend) tip block ❌ treasury UTXO groups ✅

No frontend change required. Compiles clean (explorer target).

Testing

Tested the above behaviors, all good.

#1990 made `block` with no arguments return the current tip instead of the
treasury, but implemented "no arguments" as `if (height)`, which treats an
absent height and an explicit height=0 identically. That silently broke the
treasury view (`?type=treasury` requests `block?height=0`), which then
rendered the latest block.

Root cause: Height is uint64_t with 0 as a valid height (the treasury
pseudo-block), yet 0 is also the "unspecified" sentinel. get_int_arg("height",
0) collapses an absent arg onto the same 0 as an explicit &height=0, so the two
become indistinguishable before get_block ever sees them.

Fix: distinguish the cases in Server::on_request_block, underneath get_int_arg,
by checking the raw args map (the one layer that still knows presence):
  - height absent      -> latest block (keeps #1990's intent)
  - explicit height=0  -> treasury (restores the frontend link, no HTML change)

Add get_treasury() to IAdapter so the router can dispatch to it directly, and
annotate the fallback in get_block() as an edge-case guard.
@Maxnflaxl
Maxnflaxl requested a review from valdok July 5, 2026 10:48
@Maxnflaxl
Maxnflaxl merged commit 4e01b68 into master Jul 6, 2026
10 of 11 checks passed
@Maxnflaxl
Maxnflaxl deleted the fix-treasury-req branch July 6, 2026 12:29
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.

2 participants