explorer: name the block's transaction count what the SPA reads

Block rows and block details rendered the Txn label with nothing after it.
Lux block 1106999 has 4 transactions by RPC and the page showed no number.

formatBlock emitted the column's own name, tx_count. The SPA's Block type
declares transactions_count and every render site reads it — BlocksTableItem,
BlocksListItem, LatestBlocksItem, BlockDetails — so the value arrived under a
key nobody looked up and each cell fell back to undefined. Rows that arrived
over the realtime channel DID show a count, which is what made this look like a
data problem rather than a naming one: that path already renames the field
(lib/socket/sse.ts toApiBlock), so the same block read correctly when pushed
and blank when fetched.

Renamed rather than aliased: nothing reads a block's tx_count, so a second
spelling would only be a second thing to keep in step. formatPlatformBlock gets
the same name — P-Chain rows go through the same block widgets.

The suite asserted the old key by PRESENCE, in three places, which is why a
rename is what it takes to notice. TestBlockFieldValues now pins the value:
want 3, got <nil> on the old name.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-08-08 06:17:16 -07:00
co-authored by Hanzo Dev
parent d8b8f8fa23
commit 7c85b79a62
5 changed files with 18 additions and 9 deletions
+1 -1
View File
@@ -434,7 +434,7 @@ func testChain(t *testing.T, cd chainDef) {
t.Errorf("hash: want %s, got %v", wantHash, body["hash"])
}
// Response fields.
for _, field := range []string{"height", "hash", "parent_hash", "miner", "gas_limit", "gas_used", "timestamp", "tx_count", "type"} {
for _, field := range []string{"height", "hash", "parent_hash", "miner", "gas_limit", "gas_used", "timestamp", "transactions_count", "type"} {
if _, ok := body[field]; !ok {
t.Errorf("missing field %q", field)
}
+1 -1
View File
@@ -127,7 +127,7 @@ func TestHandleListBlocks(t *testing.T) {
resp := formatBlock(maps[0])
// Verify required fields exist
for _, field := range []string{"height", "hash", "parent_hash", "miner", "size", "gas_limit", "gas_used", "timestamp", "tx_count", "type"} {
for _, field := range []string{"height", "hash", "parent_hash", "miner", "size", "gas_limit", "gas_used", "timestamp", "transactions_count", "type"} {
if _, ok := resp[field]; !ok {
t.Errorf("missing field: %s", field)
}
+10 -3
View File
@@ -249,9 +249,16 @@ func formatBlock(b map[string]any) map[string]any {
"burnt_fees": "0",
"rewards": []any{},
"timestamp": fmtTimestamp(b["timestamp"]),
"tx_count": col(b, "tx_count", "transaction_count"),
"state_root": nil,
"type": "block",
// The SPA reads `transactions_count` on a block — its Block type
// declares that name and all nine render sites use it (block rows, the
// home page's latest-blocks list, block details). This emitted the
// column's own name, `tx_count`, so every REST-loaded row rendered the
// Txn label with nothing after it, while rows pushed over the realtime
// channel showed a number, because that path already renamed the field.
// One name, and it is the reader's.
"transactions_count": col(b, "tx_count", "transaction_count"),
"state_root": nil,
"type": "block",
}
}
+4 -2
View File
@@ -2390,8 +2390,10 @@ func formatPlatformBlock(b map[string]any) map[string]any {
"parent_hash": parent, // SPA compatibility
"timestamp": fmtTimestamp(b["timestamp"]),
"status": platformStr(b["status"]),
"tx_count": toInt64(b["tx_count"]),
"tx_ids": parsePlatformTxIDs(b["tx_ids"]),
// Same name the EVM block carries — these rows go through the SPA's
// block widgets, which read `transactions_count`. See formatBlock.
"transactions_count": toInt64(b["tx_count"]),
"tx_ids": parsePlatformTxIDs(b["tx_ids"]),
}
}
+2 -2
View File
@@ -463,7 +463,7 @@ func TestListBlocks_ResponseFields(t *testing.T) {
body := getJSON(t, ts, "/v1/explorer/blocks", 200)
got := itemMaps(t, body)
b := got[0]
for _, field := range []string{"height", "hash", "parent_hash", "miner", "gas_limit", "gas_used", "timestamp", "tx_count", "type"} {
for _, field := range []string{"height", "hash", "parent_hash", "miner", "gas_limit", "gas_used", "timestamp", "transactions_count", "type"} {
if _, ok := b[field]; !ok {
t.Errorf("missing field %q", field)
}
@@ -1778,7 +1778,7 @@ func TestBlockFieldValues(t *testing.T) {
{"gas_used", "1234567"},
{"base_fee_per_gas", "25000000000"},
{"size", float64(2048)},
{"tx_count", float64(3)},
{"transactions_count", float64(3)},
{"type", "block"},
}