From d9197b3ce264d968bcdc0cecc66a45822808ce04 Mon Sep 17 00:00:00 2001 From: conache Date: Fri, 1 May 2026 14:48:26 +0300 Subject: [PATCH 1/6] Completely fill the finalized block node in the forkchoice visualization --- crates/net/rpc/static/fork_choice.html | 40 ++++++++++++++++---------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/crates/net/rpc/static/fork_choice.html b/crates/net/rpc/static/fork_choice.html index 5a49d966..fb0cf293 100644 --- a/crates/net/rpc/static/fork_choice.html +++ b/crates/net/rpc/static/fork_choice.html @@ -188,6 +188,7 @@ let svg, gLinks, gNodes, gAxis; let currentData = null; + let hoveredRoot = null; function initSVG() { svg = d3.select("#chart-container") @@ -212,8 +213,17 @@ return COLORS.default; } - function weightRatio(node, validatorCount) { - if (!validatorCount) return 0; + function nodeStroke(node, data) { + const color = nodeColor(node, data); + return d3.color(color).darker(0.5).toString(); + } + + function nodeRatio(node, data) { + // Finalized blocks have full support by definition — fill completely + // rather than scaling by fork-choice weight (which is 0 at the root). + if (node.slot <= data.finalized.slot) return 1; + const validatorCount = data.validator_count; + if (!validatorCount || validatorCount === 0) return 0; return Math.max(0, Math.min(1, node.weight / validatorCount)); } @@ -309,7 +319,8 @@ x: d.x, y: d.y, _color: nodeColor(d.data, data), - _ratio: weightRatio(d.data, data.validator_count) + _stroke: nodeStroke(d.data, data), + _ratio: nodeRatio(d.data, data) })); const links = []; @@ -334,21 +345,20 @@ return { nodes: flatNodes, links, width: svgWidth, height: svgHeight, slots }; } - // Tracked so render() can refresh the tooltip on each poll without - // requiring the user to move the mouse. - let hoveredRoot = null; - - function tooltipHtml(d, total) { - const pct = total ? parseFloat(((d.weight / total) * 100).toFixed(2)) : 0; + function tooltipHtml(d, data) { + const isFinalized = d.slot <= data.finalized.slot; + const weightLine = isFinalized + ? `status: finalized` + : `weight: ${d.weight}`; return `root: ${truncateRoot(d.root)}
` + `slot: ${d.slot}
` + `proposer: ${d.proposer_index}
` + - `weight: ${d.weight}${total != null ? `/${total} (${pct}%)` : ''}`; + weightLine; } - function showTooltip(event, d) { + function showTooltip(event, d, data) { hoveredRoot = d.root; - tooltip.innerHTML = tooltipHtml(d, currentData?.validator_count); + tooltip.innerHTML = tooltipHtml(d, data); tooltip.style.opacity = 1; tooltip.style.left = (event.clientX + 14) + "px"; tooltip.style.top = (event.clientY - 14) + "px"; @@ -479,8 +489,8 @@ const nodeMerged = nodeEnter.merge(nodeGroups); nodeMerged - .on("mouseover", function (event, d) { showTooltip(event, d); }) - .on("mousemove", function (event, d) { showTooltip(event, d); }) + .on("mouseover", function (event, d) { showTooltip(event, d, data); }) + .on("mousemove", function (event, d) { showTooltip(event, d, data); }) .on("mouseout", hideTooltip); nodeMerged @@ -513,7 +523,7 @@ // Keep the tooltip live while the user holds the mouse still over a node. if (hoveredRoot) { const hovered = layout.nodes.find(n => n.root === hoveredRoot); - if (hovered) tooltip.innerHTML = tooltipHtml(hovered, data.validator_count); + if (hovered) tooltip.innerHTML = tooltipHtml(hovered, data); } // Auto-scroll to head node From ea2b5acfdb9d94c1392d0a8836d3c3d09f0a7ba4 Mon Sep 17 00:00:00 2001 From: conache Date: Fri, 1 May 2026 15:49:21 +0300 Subject: [PATCH 2/6] Cleanup changes based on initial reviews --- crates/net/rpc/static/fork_choice.html | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/crates/net/rpc/static/fork_choice.html b/crates/net/rpc/static/fork_choice.html index fb0cf293..e11b6558 100644 --- a/crates/net/rpc/static/fork_choice.html +++ b/crates/net/rpc/static/fork_choice.html @@ -347,13 +347,19 @@ function tooltipHtml(d, data) { const isFinalized = d.slot <= data.finalized.slot; - const weightLine = isFinalized - ? `status: finalized` - : `weight: ${d.weight}`; + let lastLine; + if (isFinalized) { + lastLine = `status: finalized`; + } else { + const total = data.validator_count; + const pct = total ? parseFloat(((d.weight / total) * 100).toFixed(2)) : 0; + const suffix = total != null ? `/${total} (${pct}%)` : ""; + lastLine = `weight: ${d.weight}${suffix}`; + } return `root: ${truncateRoot(d.root)}
` + `slot: ${d.slot}
` + `proposer: ${d.proposer_index}
` + - weightLine; + lastLine; } function showTooltip(event, d, data) { @@ -474,7 +480,7 @@ nodeEnter.append("circle") .attr("class", "node-outer") .attr("r", NODE_RADIUS) - .attr("stroke", d => d._color); + .attr("stroke", d => d._stroke); // Invisible hit target so hover works regardless of fill level. nodeEnter.append("circle") @@ -515,7 +521,7 @@ .transition() .delay(TRANSITION_DURATION) .duration(100) - .attr("stroke", d => d._color); + .attr("stroke", d => d._stroke); nodeMerged.select("text") .text(d => truncateRoot(d.root)); From faab73a7a2234625a8936042ec7a251c599f170a Mon Sep 17 00:00:00 2001 From: conache Date: Thu, 14 May 2026 18:52:03 +0300 Subject: [PATCH 3/6] Implement suggested changes --- crates/net/rpc/static/fork_choice.html | 58 ++++++++++++++------------ 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/crates/net/rpc/static/fork_choice.html b/crates/net/rpc/static/fork_choice.html index 91232289..bc31a7c9 100644 --- a/crates/net/rpc/static/fork_choice.html +++ b/crates/net/rpc/static/fork_choice.html @@ -269,7 +269,6 @@ let lastScrollTop = 0; let programmaticScrollTimeout = null; let isProgrammaticScroll = false; - let hoveredRoot = null; function initSVG() { svg = d3.select("#chart-container") @@ -298,18 +297,8 @@ return roles; } - function nodeStroke(node, data) { - const roles = nodeRoles(node, data); - const color = roles.length > 0 ? COLORS[roles[0]] : COLORS.default; - return d3.color(color).darker(0.5).toString(); - } - - function nodeRatio(node, data) { - // Finalized blocks have full support by definition — fill completely - // rather than scaling by fork-choice weight (which is 0 at the root). - if (node.slot <= data.finalized.slot) return 1; - const validatorCount = data.validator_count; - if (!validatorCount || validatorCount === 0) return 0; + function weightRatio(node, validatorCount) { + if (!validatorCount) return 0; return Math.max(0, Math.min(1, node.weight / validatorCount)); } @@ -402,13 +391,14 @@ const flatNodes = allDescendants.map(d => { const roles = nodeRoles(d.data, data); + // Finalized blocks are filled by default, otherwise scale by fork-choice weight. + const isFinalized = roles.includes("finalized"); return { ...d.data, x: d.x, y: d.y, _color: roles.length > 0 ? COLORS[roles[0]] : COLORS.default, - _stroke: nodeStroke(d.data, data), - _ratio: nodeRatio(d.data, data), + _ratio: isFinalized ? 1 : weightRatio(d.data, data.validator_count), // Colors of secondary roles, in priority order (after the primary). _haloColors: roles.slice(1).map(r => COLORS[r]) }; @@ -436,21 +426,35 @@ return { nodes: flatNodes, links, width: svgWidth, height: svgHeight, slots }; } + let hoveredRoot = null; + function tooltipHtml(d, data) { - const isFinalized = d.slot <= data.finalized.slot; - let lastLine; - if (isFinalized) { - lastLine = `status: finalized`; - } else { + const roles = nodeRoles(d, data); + const lines = [ + `root: ${truncateRoot(d.root)}`, + `slot: ${d.slot}`, + `proposer: ${d.proposer_index}`, + ]; + + if (roles.length > 0) { + const roleSpans = roles + .map(r => `${ROLE_LABELS[r]}`) + .join(", "); + lines.push(`status: ${roleSpans}`); + } + + // Skip the weight line for purely-finalized blocks: their fork-choice + // weight is 0 by design and reading "weight: 0" alongside "finalized" is + // misleading. Any non-finalized role still gets a meaningful number. + const isPureFinalized = roles.length === 1 && roles[0] === "finalized"; + if (!isPureFinalized) { const total = data.validator_count; const pct = total ? parseFloat(((d.weight / total) * 100).toFixed(2)) : 0; const suffix = total != null ? `/${total} (${pct}%)` : ""; - lastLine = `weight: ${d.weight}${suffix}`; + lines.push(`weight: ${d.weight}${suffix}`); } - return `root: ${truncateRoot(d.root)}
` + - `slot: ${d.slot}
` + - `proposer: ${d.proposer_index}
` + - lastLine; + + return lines.join("
"); } function showTooltip(event, d, data) { @@ -615,7 +619,7 @@ nodeEnter.append("circle") .attr("class", "node-outer") .attr("r", NODE_RADIUS) - .attr("stroke", d => d._stroke); + .attr("stroke", d => d._color); // Halo rings, one per secondary role. Always rendered with a transparent // stroke when no role applies so transitions can animate stroke color @@ -668,7 +672,7 @@ .transition() .delay(TRANSITION_DURATION) .duration(100) - .attr("stroke", d => d._stroke); + .attr("stroke", d => d._color); HALO_OFFSETS.forEach((_, i) => { nodeMerged.select(`.halo-${i}`) From d31291048cf319b7adeaee3c2b4ea7f3c0a2cecd Mon Sep 17 00:00:00 2001 From: conache Date: Thu, 14 May 2026 18:59:39 +0300 Subject: [PATCH 4/6] Revert redundant change --- crates/blockchain/.DS_Store | Bin 0 -> 6148 bytes crates/common/.DS_Store | Bin 0 -> 6148 bytes crates/net/.DS_Store | Bin 0 -> 6148 bytes crates/net/rpc/.DS_Store | Bin 0 -> 6148 bytes crates/net/rpc/static/fork_choice.html | 2 ++ 5 files changed, 2 insertions(+) create mode 100644 crates/blockchain/.DS_Store create mode 100644 crates/common/.DS_Store create mode 100644 crates/net/.DS_Store create mode 100644 crates/net/rpc/.DS_Store diff --git a/crates/blockchain/.DS_Store b/crates/blockchain/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..4c2542fb193a0fda3d3a62b0cf4bfe954074b382 GIT binary patch literal 6148 zcmeHKy-or_5S|4zM4Ahg<+XOw7|WBKPrw5xf67JRh+u!6y(AV^Ru;a1k6>eAY%HvO z55L)+z-<7PMva+CX1~3inVXp}>@7<~rhd_>5d}n4hBB76Ff0+avo1(MRyf$$cN|fh zdX!QER8a<$fxpfG@7)TWV9%b@8RhqHw|`n|w9~=JwNQOu?Kw!}xYbWv@F*uYjjPL- z$AizjJ8O84dig4LxtJo2QEl6+uP~g>E%|z`ANHr!H(QUy4bpl3MXQ&uaLf4uYVP(B1uaV)8ZaJk2=!GFQI(b>h6f=$ZT4upM!4W50vuo^Q?9 zy7@ZeGZ&w)YPpA>Uw%|vh01R+fHRw=TydzQGN24712YEr`w&7IBf-p}{d8ckR{&rM z%T_QCcmfOsBf-oeED+;DfiBd@6~nl2tcT(k31$vmI2pNo7|CqphGJ}Xyg#(=WFm(; zDg(-Z&p^>V27LY>uD<{KLHeW&CV==%5VzC<7lfP@mfX literal 0 HcmV?d00001 diff --git a/crates/common/.DS_Store b/crates/common/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..c8ff8e5a74dd7c73f5dc8f89c25c711e72a5c3af GIT binary patch literal 6148 zcmeHKJx{|h5IsYI2!vEIv5|olp^62GC9T4aRDJ;J2Om;Hs+LNyWn*Fi@jv(%>@c#x zh* za5_hC0CtG^+)g(qXS+PFUbb&mp0s8Y=65;&uEOVk$NcBOm)~l@_j$`7b@{EY?SH;g zr{3i=I>Woa((jWwh)u5o*t6N9C4=Hc0Z~8{7%IT)LyX3lSS$?cr2|eM0e~fhwV}_y z6kHQqOe_`#F#=Pj6lhA7Jz^+Rj(BYI5{rdFQ%=erK9s$)vL_U!?~eInolYt-C|(p0 z1$+f&&1ISQ|K0KDf4@ksL;+FYUn!tUX+5psk?h_YJRI+}G1>td8|M`UbqSo^j&+B( d;#D+l7>l_BOe_`#5rN5%fR;g=DDbBWd;r-CoJIfu literal 0 HcmV?d00001 diff --git a/crates/net/.DS_Store b/crates/net/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..d9af69c0ba49533a80b52867f21dbffd232e0a62 GIT binary patch literal 6148 zcmeHKJxc>Y5S_^djA#+8EcXZ4#7es}oWCG~mYM`VAjcho{egv@h51poVk`a-JKyY# zxjRlRCCCiyzP*{9eeBy~KZuCOck>a^kcb8-g1t>t-k!90GF@RN&8f` z`1t;#jx_lmPaAwOFE47oZnVXxe`rFtV4Su2E|-xnMw)yF?}y8sH=CbDYrbx@#m6r) zKQ~vv6>tUCT>;tQVS_D2uU!FGz!g|2Ap1i=5ljs$#kh5#iWe+kAMR*q%UeQnf?;Y{ zDIx-6O$BNy+Y^H|9sXebQo~A7(~0$JM{LY&FBFdK7(c}B#Hpg!u7E4hRv^*Op`8E6 zKfnLmN&e*uxB~x50q$p$Y>X+{+1i<$oV5Yv97Rm}Rf?+=G~!W=zH$`rqC|r~NCTJ} TR*LYz{2~x#@X8hVQw2T&HK|(X literal 0 HcmV?d00001 diff --git a/crates/net/rpc/.DS_Store b/crates/net/rpc/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..66544f017c880eea9e05fd2d544a58a0a328c19d GIT binary patch literal 6148 zcmeHKOKQU~5S>X)F?8c)m%2i3AWV9KT%aX^PzWxxN!DKVTsd0ad^X1I&e?=FFnZEx zo`l}Q;}H?<&-=AVCn7D}P=0Ngo9&y=Y?ToO!g0o@>@M5y;rKNiX4!uSjQcD4-ZW$e#hzg+>MLt-uXFauu5Z literal 0 HcmV?d00001 diff --git a/crates/net/rpc/static/fork_choice.html b/crates/net/rpc/static/fork_choice.html index bc31a7c9..7b496d4c 100644 --- a/crates/net/rpc/static/fork_choice.html +++ b/crates/net/rpc/static/fork_choice.html @@ -426,6 +426,8 @@ return { nodes: flatNodes, links, width: svgWidth, height: svgHeight, slots }; } + // Tracked so render() can refresh the tooltip on each poll without + // requiring the user to move the mouse. let hoveredRoot = null; function tooltipHtml(d, data) { From aea0682a8c3cf764ccdfbf8c4bc030407226cbf2 Mon Sep 17 00:00:00 2001 From: conache Date: Thu, 14 May 2026 19:01:09 +0300 Subject: [PATCH 5/6] Remove unwanted files --- crates/net/.DS_Store | Bin 6148 -> 0 bytes crates/net/rpc/.DS_Store | Bin 6148 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 crates/net/.DS_Store delete mode 100644 crates/net/rpc/.DS_Store diff --git a/crates/net/.DS_Store b/crates/net/.DS_Store deleted file mode 100644 index d9af69c0ba49533a80b52867f21dbffd232e0a62..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKJxc>Y5S_^djA#+8EcXZ4#7es}oWCG~mYM`VAjcho{egv@h51poVk`a-JKyY# zxjRlRCCCiyzP*{9eeBy~KZuCOck>a^kcb8-g1t>t-k!90GF@RN&8f` z`1t;#jx_lmPaAwOFE47oZnVXxe`rFtV4Su2E|-xnMw)yF?}y8sH=CbDYrbx@#m6r) zKQ~vv6>tUCT>;tQVS_D2uU!FGz!g|2Ap1i=5ljs$#kh5#iWe+kAMR*q%UeQnf?;Y{ zDIx-6O$BNy+Y^H|9sXebQo~A7(~0$JM{LY&FBFdK7(c}B#Hpg!u7E4hRv^*Op`8E6 zKfnLmN&e*uxB~x50q$p$Y>X+{+1i<$oV5Yv97Rm}Rf?+=G~!W=zH$`rqC|r~NCTJ} TR*LYz{2~x#@X8hVQw2T&HK|(X diff --git a/crates/net/rpc/.DS_Store b/crates/net/rpc/.DS_Store deleted file mode 100644 index 66544f017c880eea9e05fd2d544a58a0a328c19d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKOKQU~5S>X)F?8c)m%2i3AWV9KT%aX^PzWxxN!DKVTsd0ad^X1I&e?=FFnZEx zo`l}Q;}H?<&-=AVCn7D}P=0Ngo9&y=Y?ToO!g0o@>@M5y;rKNiX4!uSjQcD4-ZW$e#hzg+>MLt-uXFauu5Z From 52330fd8f7f010efdbee9b0a45183e6542d41899 Mon Sep 17 00:00:00 2001 From: conache Date: Thu, 14 May 2026 19:01:54 +0300 Subject: [PATCH 6/6] Remove .DS_Store included files --- crates/blockchain/.DS_Store | Bin 6148 -> 0 bytes crates/common/.DS_Store | Bin 6148 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 crates/blockchain/.DS_Store delete mode 100644 crates/common/.DS_Store diff --git a/crates/blockchain/.DS_Store b/crates/blockchain/.DS_Store deleted file mode 100644 index 4c2542fb193a0fda3d3a62b0cf4bfe954074b382..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKy-or_5S|4zM4Ahg<+XOw7|WBKPrw5xf67JRh+u!6y(AV^Ru;a1k6>eAY%HvO z55L)+z-<7PMva+CX1~3inVXp}>@7<~rhd_>5d}n4hBB76Ff0+avo1(MRyf$$cN|fh zdX!QER8a<$fxpfG@7)TWV9%b@8RhqHw|`n|w9~=JwNQOu?Kw!}xYbWv@F*uYjjPL- z$AizjJ8O84dig4LxtJo2QEl6+uP~g>E%|z`ANHr!H(QUy4bpl3MXQ&uaLf4uYVP(B1uaV)8ZaJk2=!GFQI(b>h6f=$ZT4upM!4W50vuo^Q?9 zy7@ZeGZ&w)YPpA>Uw%|vh01R+fHRw=TydzQGN24712YEr`w&7IBf-p}{d8ckR{&rM z%T_QCcmfOsBf-oeED+;DfiBd@6~nl2tcT(k31$vmI2pNo7|CqphGJ}Xyg#(=WFm(; zDg(-Z&p^>V27LY>uD<{KLHeW&CV==%5VzC<7lfP@mfX diff --git a/crates/common/.DS_Store b/crates/common/.DS_Store deleted file mode 100644 index c8ff8e5a74dd7c73f5dc8f89c25c711e72a5c3af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKJx{|h5IsYI2!vEIv5|olp^62GC9T4aRDJ;J2Om;Hs+LNyWn*Fi@jv(%>@c#x zh* za5_hC0CtG^+)g(qXS+PFUbb&mp0s8Y=65;&uEOVk$NcBOm)~l@_j$`7b@{EY?SH;g zr{3i=I>Woa((jWwh)u5o*t6N9C4=Hc0Z~8{7%IT)LyX3lSS$?cr2|eM0e~fhwV}_y z6kHQqOe_`#F#=Pj6lhA7Jz^+Rj(BYI5{rdFQ%=erK9s$)vL_U!?~eInolYt-C|(p0 z1$+f&&1ISQ|K0KDf4@ksL;+FYUn!tUX+5psk?h_YJRI+}G1>td8|M`UbqSo^j&+B( d;#D+l7>l_BOe_`#5rN5%fR;g=DDbBWd;r-CoJIfu