You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The block tree layout in www/js/blocktree.js is the most intricate part of the frontend and the part with the least test surface: there is none. It is also the part where bugs are only visible as a picture — a block half a slot too close to its sibling, a chain that slides sideways for no reason, a label drawn over another one. Reviewing #159 meant building a throwaway harness to see the change at all, and that harness turned out to be far more useful than the review it was built for. It's worth having as a permanent tool, and worth extending so chains can be built by hand rather than only from canned fixtures.
What the throwaway version did
A static page that draws blocktree.js against handmade data, with no node, no database and no live stratum feed behind it:
A fixture module producing a state_data in the exact shape of api/<n>/data.json (header_infos + nodes), plus a state_stratum_jobs map in the shape main.js builds from the SSE feed.
A pane page that declares the globals blocktree.js reads out of main.js — MINING_ENABLED, state_data, state_stratum_jobs, STRATUM_JOB_TTL_MS, state_selected_network_id, nodeInfoRow, copyToClipboard, update, run — then loads blocktree.js and calls draw().
Two such panes side by side in iframes, one per git revision of blocktree.js, with pan/zoom relayed between them over postMessage so both always frame the same patch of layout.
A readout in each pane that re-runs preprocess_data() and reports, in pixels, how far each block from the feed sits from its siblings. That turned "the block looks too close" into "60.0px apart, and a slot is 120px" — and after the fix, 120.0px.
That last part is the bit I'd keep above all others. The layout's invariants are numeric (NODE_SIZE slots, BLOCK_SIZE faces, sibling separation, pinned-vs-unpinned positions), so a page that measures the layout instead of only drawing it is one step away from being an actual regression test.
What to build
A sandbox page served out of www/ — so it comes up at /sandbox.html on any running instance, needs no separate server, and no special browser. Just open it.
The new part is building the tree by hand instead of picking from a list of fixtures:
Click a block to attach a child to it. Attaching to anything other than the current tip creates a fork; that is the whole point.
Mark any block as a tip reported by one or more fake nodes, with a status from active / valid-fork / valid-headers / headers-only / invalid, and a count, since the tip status boxes stack by node count.
Attach a stratum job to any block: pick a pool name and the block it's mining on. That covers the ordinary being-mined block, the "just found" stand-in (job.height == max_height + 2 against a parent we don't know), and the fork-the-tip case from fix: draw a to-be-mined block that forks the tip as a fork #159, without waiting for the real feed to produce one.
Delete blocks, and a "reorg" action that moves the active tip to another branch, so the transition animation can be watched deliberately rather than at 3am.
A countdown height, since state_data.countdown changes what isInteresting() keeps.
Everything should round-trip through the URL or an export/import JSON blob, so a scenario can be pasted into an issue or a PR review. Being able to paste in a real data.json from a live instance and then edit it by hand would cover the "reproduce what I just saw in production" case.
Notes for whoever builds it
Things the throwaway version ran into, so they don't have to be rediscovered:
blocktree.js is plain script scope with module-level state bound to #drawing-area — two copies cannot coexist in one document. Iframes are what make a side-by-side comparison possible, and they're cheap. If a before/after mode is kept, the "before" copy has to be a checked-out copy of the old file; a small script that extracts one from a given revision is enough.
The root header's prev_id must be the MAX_USIZE sentinel (18446744073709551615), which is what d3.stratify() keys the root off.
stripUninteresting(treeData, 4) collapses linear runs longer than four, so a hand-built chain needs a real length before it looks like the production view, and a block's status (via a node tip) is one of the things that makes it survive the collapse.
Blocks sort by (height, hash), so which side of a fork a branch lands on is decided by the hash — hence wanting to edit it.
The feed's prev_hash arrives in header (little-endian) word order and main.js reverses the words before it ever reaches state_stratum_jobs; the sandbox should store the display-order hash directly and skip that conversion.
from_stratum_feed() keys off the synthetic statuses in MINING_TAGS (mining, just-found), and those blocks are given synthetic mining-/found- hash prefixes deliberately so they can't collide with a real header once a node reports it.
The orientation is chosen from the window aspect ratio at load; in a narrow iframe that silently picks bottom-to-top. Both orientations are worth a control, since they have separate label and link geometry and bugs show up in only one of them often enough.
Not needed: any headless browser, screenshot tooling, or a driver. This is a thing to open and look at. If it later grows assertions, the measurement readout is the seam to build them on — and at that point a headless run becomes a genuine test rather than a way of taking pictures.
The block tree layout in
www/js/blocktree.jsis the most intricate part of the frontend and the part with the least test surface: there is none. It is also the part where bugs are only visible as a picture — a block half a slot too close to its sibling, a chain that slides sideways for no reason, a label drawn over another one. Reviewing #159 meant building a throwaway harness to see the change at all, and that harness turned out to be far more useful than the review it was built for. It's worth having as a permanent tool, and worth extending so chains can be built by hand rather than only from canned fixtures.What the throwaway version did
A static page that draws
blocktree.jsagainst handmade data, with no node, no database and no live stratum feed behind it:state_datain the exact shape ofapi/<n>/data.json(header_infos+nodes), plus astate_stratum_jobsmap in the shapemain.jsbuilds from the SSE feed.blocktree.jsreads out ofmain.js—MINING_ENABLED,state_data,state_stratum_jobs,STRATUM_JOB_TTL_MS,state_selected_network_id,nodeInfoRow,copyToClipboard,update,run— then loadsblocktree.jsand callsdraw().blocktree.js, with pan/zoom relayed between them overpostMessageso both always frame the same patch of layout.preprocess_data()and reports, in pixels, how far each block from the feed sits from its siblings. That turned "the block looks too close" into "60.0px apart, and a slot is 120px" — and after the fix, 120.0px.That last part is the bit I'd keep above all others. The layout's invariants are numeric (
NODE_SIZEslots,BLOCK_SIZEfaces, sibling separation, pinned-vs-unpinned positions), so a page that measures the layout instead of only drawing it is one step away from being an actual regression test.What to build
A sandbox page served out of
www/— so it comes up at/sandbox.htmlon any running instance, needs no separate server, and no special browser. Just open it.The new part is building the tree by hand instead of picking from a list of fixtures:
version(so the BIP-9 signalling chips from Support showing tags for BIP-9 style version bit signaling (initially for BIP-110) #156 can be exercised),difficulty_int(MIN_DIFFICULTYgets the accent stroke), and the hash — auto-generated, but editable so a specific sort order can be forced.active/valid-fork/valid-headers/headers-only/invalid, and a count, since the tip status boxes stack by node count.job.height == max_height + 2against a parent we don't know), and the fork-the-tip case from fix: draw a to-be-mined block that forks the tip as a fork #159, without waiting for the real feed to produce one.state_data.countdownchanges whatisInteresting()keeps.Everything should round-trip through the URL or an export/import JSON blob, so a scenario can be pasted into an issue or a PR review. Being able to paste in a real
data.jsonfrom a live instance and then edit it by hand would cover the "reproduce what I just saw in production" case.Notes for whoever builds it
Things the throwaway version ran into, so they don't have to be rediscovered:
blocktree.jsis plain script scope with module-level state bound to#drawing-area— two copies cannot coexist in one document. Iframes are what make a side-by-side comparison possible, and they're cheap. If a before/after mode is kept, the "before" copy has to be a checked-out copy of the old file; a small script that extracts one from a given revision is enough.prev_idmust be theMAX_USIZEsentinel (18446744073709551615), which is whatd3.stratify()keys the root off.stripUninteresting(treeData, 4)collapses linear runs longer than four, so a hand-built chain needs a real length before it looks like the production view, and a block'sstatus(via a node tip) is one of the things that makes it survive the collapse.(height, hash), so which side of a fork a branch lands on is decided by the hash — hence wanting to edit it.prev_hasharrives in header (little-endian) word order andmain.jsreverses the words before it ever reachesstate_stratum_jobs; the sandbox should store the display-order hash directly and skip that conversion.from_stratum_feed()keys off the synthetic statuses inMINING_TAGS(mining,just-found), and those blocks are given syntheticmining-/found-hash prefixes deliberately so they can't collide with a real header once a node reports it.bottom-to-top. Both orientations are worth a control, since they have separate label and link geometry and bugs show up in only one of them often enough.Not needed: any headless browser, screenshot tooling, or a driver. This is a thing to open and look at. If it later grows assertions, the measurement readout is the seam to build them on — and at that point a headless run becomes a genuine test rather than a way of taking pictures.