Skip to content

fix(forkchoice): redesign viz node weight display#328

Merged
MegaRedHand merged 6 commits intolambdaclass:mainfrom
conache:improve-node-weight-display
Apr 30, 2026
Merged

fix(forkchoice): redesign viz node weight display#328
MegaRedHand merged 6 commits intolambdaclass:mainfrom
conache:improve-node-weight-display

Conversation

@conache
Copy link
Copy Markdown
Contributor

@conache conache commented Apr 29, 2026

Motivation

Closes #150 .

In the original fork choice viz, node size scaled with weight, so a high-weight block could grow large enough to obscure neighboring block roots. This PR replaces it with fixed-size nodes whose inner circle fills from the bottom proportional to weight / validator_count.

Description

Every node renders at the same size; the visible inner fill is the weight ratio. The fill animates first, then the role color (head, safe-target, justified, finalized) flips after the fill settles. The tooltip's weight line now reads count/total (pct%).

How to Use

1. Start a local devnet

make run-devnet

Or start a node manually:

cargo run --release -- \
  --custom-network-config-dir ./config \
  --node-key ./keys/node.key \
  --node-id 0 \
  --metrics-port 5054

2. Open the visualization

Navigate to http://localhost:5054/lean/v0/fork_choice/ui in your browser.

The page will start polling automatically and render the fork tree as blocks are produced.

What to look for

  • Block at the head: empty orange ring (no attestations yet).
  • Recent canonical blocks: partially filled rings, getting fuller as attestations arrive.
  • Justified / finalized blocks: full discs in blue / green.
  • Forks (when they happen): competing branches show visibly lower fill than the canonical chain.

Screenshots

Live local devnet

Screenshot 2026-04-29 at 22 41 18

Forked tree (mocked data)

Screenshot 2026-04-29 at 19 59 14

Test plan

  • Verified locally with a single run — 4 validators, watching head → safe-target → justified → finalized transitions.
  • make fmt, make lint, make test — no regressions.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 29, 2026

Greptile Summary

This PR replaces the previous variable-radius node rendering in the fork-choice visualizer with fixed-size ring nodes whose inner circle fills from the bottom proportional to weight / validator_count, eliminating the text-obscuring large-node problem. The animation sequences fill first, then flips the role color, and the tooltip now shows the weight as a count/total (pct%) fraction.

Confidence Score: 4/5

Safe to merge; both findings are P2 visual/defensive issues that do not affect correctness or functionality.

No P0 or P1 issues found. Two P2s: tooltip can render "undefined" as denominator when validator_count is absent, and the fill-mask rect uses a paint-over technique whose corner pixels protrude ~2.4 px outside the outer ring. Both are currently invisible/benign but worth noting.

crates/net/rpc/static/fork_choice.html — fill-mask corner protrusion and tooltip undefined guard

Important Files Changed

Filename Overview
crates/net/rpc/static/fork_choice.html Replaces variable-radius nodes with fixed-size ring+fill animation; two minor P2 issues: tooltip can display "undefined" denominator and the fill-mask rect corners slightly protrude outside the outer ring due to paint-over rather than clip-path approach.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[fetchAndRender poll] --> B[buildLayout]
    B --> C["flatNodes: {...d.data, x, y, _color, _ratio}"]
    C --> D{Node in DOM?}
    D -- New --> E[nodeEnter: append g]
    E --> E1[append node-inner circle fill=_color]
    E1 --> E2[append fill-mask rect height=1-ratio×2R]
    E2 --> E3[append node-outer ring stroke=_color]
    E3 --> E4[append node-hit transparent]
    E4 --> E5[append text label]
    D -- Existing --> F[nodeMerged]
    F --> F1[transition fill-mask height duration=500ms]
    F1 --> F2[delay 500ms flip node-inner fill + node-outer stroke duration=100ms]
    F --> G{hoveredRoot set?}
    G -- Yes --> H[refresh tooltip innerHTML]
    G -- No --> I[done]
    H --> I
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: crates/net/rpc/static/fork_choice.html
Line: 342

Comment:
**Tooltip shows `undefined` when `validator_count` is absent**

`${total}` in the template literal renders as the string `"undefined"` if `total` is `undefined`, producing `weight: X/undefined (0%)` in the tooltip. The `pct` guard prevents `NaN` in the percentage, but the denominator in the fraction is still unguarded.

```suggestion
      `<span class="tt-label">weight:</span> ${d.weight}${total != null ? `/${total} (${pct}%)` : ''}`;
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: crates/net/rpc/static/fork_choice.html
Line: 457-462

Comment:
**`fill-mask` rect corners protrude outside the outer ring**

The mask rect spans `±INNER_RADIUS` (±13 px), so its corners sit at distance √(13²+13²) ≈ 18.4 px from center — outside `NODE_RADIUS` (16 px) by ~2.4 px. This is invisible today because `.fill-mask` fill (`#1a1a2e`) matches the page background, but it is a fragile paint-over rather than a true clip. Any theme change or scrollable container with a different background will expose small dark squares at the diagonal corners of each partially-filled node.

Using an SVG `<clipPath>` with a circle of radius `INNER_RADIUS` applied to the rect would make this robust without a visual change.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "Merge branch 'main' into improve-node-we..." | Re-trigger Greptile

Comment thread crates/net/rpc/static/fork_choice.html
Comment on lines +457 to +462
nodeEnter.append("rect")
.attr("class", "fill-mask")
.attr("x", -INNER_RADIUS)
.attr("width", INNER_RADIUS * 2)
.attr("y", -INNER_RADIUS)
.attr("height", d => (1 - d._ratio) * INNER_RADIUS * 2);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 fill-mask rect corners protrude outside the outer ring

The mask rect spans ±INNER_RADIUS (±13 px), so its corners sit at distance √(13²+13²) ≈ 18.4 px from center — outside NODE_RADIUS (16 px) by ~2.4 px. This is invisible today because .fill-mask fill (#1a1a2e) matches the page background, but it is a fragile paint-over rather than a true clip. Any theme change or scrollable container with a different background will expose small dark squares at the diagonal corners of each partially-filled node.

Using an SVG <clipPath> with a circle of radius INNER_RADIUS applied to the rect would make this robust without a visual change.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/net/rpc/static/fork_choice.html
Line: 457-462

Comment:
**`fill-mask` rect corners protrude outside the outer ring**

The mask rect spans `±INNER_RADIUS` (±13 px), so its corners sit at distance √(13²+13²) ≈ 18.4 px from center — outside `NODE_RADIUS` (16 px) by ~2.4 px. This is invisible today because `.fill-mask` fill (`#1a1a2e`) matches the page background, but it is a fragile paint-over rather than a true clip. Any theme change or scrollable container with a different background will expose small dark squares at the diagonal corners of each partially-filled node.

Using an SVG `<clipPath>` with a circle of radius `INNER_RADIUS` applied to the rect would make this robust without a visual change.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Collaborator

@MegaRedHand MegaRedHand left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

@MegaRedHand MegaRedHand merged commit 634e1c8 into lambdaclass:main Apr 30, 2026
2 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.

Fork choice viz: redesign node weight display to avoid hiding block roots

3 participants