Mar 25, 11-12 PM (36)
Mar 25, 12-1 PM (86)
Mar 25, 1-2 PM (29)
Mar 25, 2-3 PM (28)
Mar 25, 3-4 PM (65)
Mar 25, 4-5 PM (30)
Mar 25, 5-6 PM (16)
Mar 25, 6-7 PM (37)
Mar 25, 7-8 PM (10)
Mar 25, 8-9 PM (9)
Mar 25, 9-10 PM (6)
Mar 25, 10-11 PM (25)
Mar 25, 11-12 AM (16)
Mar 26, 12-1 AM (3)
Mar 26, 1-2 AM (9)
Mar 26, 2-3 AM (21)
Mar 26, 3-4 AM (10)
Mar 26, 4-5 AM (1)
Mar 26, 5-6 AM (14)
Mar 26, 6-7 AM (4)
Mar 26, 7-8 AM (8)
Mar 26, 8-9 AM (18)
Mar 26, 9-10 AM (33)
Mar 26, 10-11 AM (21)
Mar 26, 11-12 PM (34)
Mar 26, 12-1 PM (33)
Mar 26, 1-2 PM (77)
Mar 26, 2-3 PM (46)
Mar 26, 3-4 PM (51)
Mar 26, 4-5 PM (40)
Mar 26, 5-6 PM (19)
Mar 26, 6-7 PM (19)
Mar 26, 7-8 PM (15)
Mar 26, 8-9 PM (9)
Mar 26, 9-10 PM (17)
Mar 26, 10-11 PM (38)
Mar 26, 11-12 AM (11)
Mar 27, 12-1 AM (3)
Mar 27, 1-2 AM (1)
Mar 27, 2-3 AM (26)
Mar 27, 3-4 AM (12)
Mar 27, 4-5 AM (6)
Mar 27, 5-6 AM (3)
Mar 27, 6-7 AM (10)
Mar 27, 7-8 AM (18)
Mar 27, 8-9 AM (38)
Mar 27, 9-10 AM (26)
Mar 27, 10-11 AM (38)
Mar 27, 11-12 PM (26)
Mar 27, 12-1 PM (57)
Mar 27, 1-2 PM (31)
Mar 27, 2-3 PM (60)
Mar 27, 3-4 PM (40)
Mar 27, 4-5 PM (20)
Mar 27, 5-6 PM (30)
Mar 27, 6-7 PM (29)
Mar 27, 7-8 PM (15)
Mar 27, 8-9 PM (17)
Mar 27, 9-10 PM (13)
Mar 27, 10-11 PM (24)
Mar 27, 11-12 AM (17)
Mar 28, 12-1 AM (2)
Mar 28, 1-2 AM (2)
Mar 28, 2-3 AM (12)
Mar 28, 3-4 AM (1)
Mar 28, 4-5 AM (2)
Mar 28, 5-6 AM (1)
Mar 28, 6-7 AM (0)
Mar 28, 7-8 AM (2)
Mar 28, 8-9 AM (7)
Mar 28, 9-10 AM (7)
Mar 28, 10-11 AM (7)
Mar 28, 11-12 PM (7)
Mar 28, 12-1 PM (4)
Mar 28, 1-2 PM (5)
Mar 28, 2-3 PM (12)
Mar 28, 3-4 PM (3)
Mar 28, 4-5 PM (5)
Mar 28, 5-6 PM (5)
Mar 28, 6-7 PM (0)
Mar 28, 7-8 PM (2)
Mar 28, 8-9 PM (0)
Mar 28, 9-10 PM (1)
Mar 28, 10-11 PM (21)
Mar 28, 11-12 AM (21)
Mar 29, 12-1 AM (2)
Mar 29, 1-2 AM (6)
Mar 29, 2-3 AM (6)
Mar 29, 3-4 AM (6)
Mar 29, 4-5 AM (3)
Mar 29, 5-6 AM (5)
Mar 29, 6-7 AM (0)
Mar 29, 7-8 AM (0)
Mar 29, 8-9 AM (13)
Mar 29, 9-10 AM (0)
Mar 29, 10-11 AM (1)
Mar 29, 11-12 PM (2)
Mar 29, 12-1 PM (13)
Mar 29, 1-2 PM (2)
Mar 29, 2-3 PM (2)
Mar 29, 3-4 PM (4)
Mar 29, 4-5 PM (6)
Mar 29, 5-6 PM (8)
Mar 29, 6-7 PM (9)
Mar 29, 7-8 PM (6)
Mar 29, 8-9 PM (4)
Mar 29, 9-10 PM (10)
Mar 29, 10-11 PM (24)
Mar 29, 11-12 AM (17)
Mar 30, 12-1 AM (5)
Mar 30, 1-2 AM (5)
Mar 30, 2-3 AM (7)
Mar 30, 3-4 AM (7)
Mar 30, 4-5 AM (3)
Mar 30, 5-6 AM (12)
Mar 30, 6-7 AM (3)
Mar 30, 7-8 AM (36)
Mar 30, 8-9 AM (27)
Mar 30, 9-10 AM (10)
Mar 30, 10-11 AM (67)
Mar 30, 11-12 PM (47)
Mar 30, 12-1 PM (30)
Mar 30, 1-2 PM (39)
Mar 30, 2-3 PM (63)
Mar 30, 3-4 PM (33)
Mar 30, 4-5 PM (20)
Mar 30, 5-6 PM (41)
Mar 30, 6-7 PM (17)
Mar 30, 7-8 PM (18)
Mar 30, 8-9 PM (13)
Mar 30, 9-10 PM (28)
Mar 30, 10-11 PM (44)
Mar 30, 11-12 AM (28)
Mar 31, 12-1 AM (16)
Mar 31, 1-2 AM (5)
Mar 31, 2-3 AM (15)
Mar 31, 3-4 AM (6)
Mar 31, 4-5 AM (4)
Mar 31, 5-6 AM (7)
Mar 31, 6-7 AM (12)
Mar 31, 7-8 AM (43)
Mar 31, 8-9 AM (47)
Mar 31, 9-10 AM (30)
Mar 31, 10-11 AM (37)
Mar 31, 11-12 PM (29)
Mar 31, 12-1 PM (38)
Mar 31, 1-2 PM (34)
Mar 31, 2-3 PM (52)
Mar 31, 3-4 PM (38)
Mar 31, 4-5 PM (48)
Mar 31, 5-6 PM (39)
Mar 31, 6-7 PM (36)
Mar 31, 7-8 PM (16)
Mar 31, 8-9 PM (13)
Mar 31, 9-10 PM (9)
Mar 31, 10-11 PM (24)
Mar 31, 11-12 AM (15)
Apr 01, 12-1 AM (1)
Apr 01, 1-2 AM (2)
Apr 01, 2-3 AM (8)
Apr 01, 3-4 AM (5)
Apr 01, 4-5 AM (8)
Apr 01, 5-6 AM (10)
Apr 01, 6-7 AM (3)
Apr 01, 7-8 AM (81)
Apr 01, 8-9 AM (40)
Apr 01, 9-10 AM (26)
Apr 01, 10-11 AM (26)
Apr 01, 11-12 PM (7)
3,183 commits this week Mar 25, 2026 - Apr 01, 2026
fix(ledger): fix pipeline restart race conditions causing permanent stalls (#1783)
Three fixes for chainsync pipeline stalls found during deep concurrency audit:

1. Iterator rollback signal missed on new iterators (chain/chain.go):
   rollbackLocked() checked iter.lastPoint.Slot > point.Slot, but new
   iterators have zero-value lastPoint. Use startPoint as fallback
   reference so newly created iterators receive rollback signals.

2. Pipeline reader reads currentTip without lock (ledger/state.go):
   ledgerReadChain() read ls.currentTip.Point without RLock, racing with
   concurrent rollbacks. Snapshot under lock and handle FromPoint failure
   gracefully — the outer loop retries after rollback updates the tip.

3. Rollback doesn't wake blocked iterators (chain/chain.go):
   rollbackLocked() set needsRollback but never called
   notifyWaitingIterators(). Blocked iterators would sleep until new
   blocks arrived, stalling indefinitely if connections died after
   rollback.

Also includes defer close(resultCh) and cached batch done-channel fix
from the pipeline deadlock PR.

Signed-off-by: wcatz <[email protected]>
fix(ledger): fix pipeline restart race conditions causing permanent stalls
Three fixes for chainsync pipeline stalls found during deep concurrency audit:

1. Iterator rollback signal missed on new iterators (chain/chain.go):
   rollbackLocked() checked iter.lastPoint.Slot > point.Slot, but new
   iterators have zero-value lastPoint. Use startPoint as fallback
   reference so newly created iterators receive rollback signals.

2. Pipeline reader reads currentTip without lock (ledger/state.go):
   ledgerReadChain() read ls.currentTip.Point without RLock, racing with
   concurrent rollbacks. Snapshot under lock and handle FromPoint failure
   gracefully — the outer loop retries after rollback updates the tip.

3. Rollback doesn't wake blocked iterators (chain/chain.go):
   rollbackLocked() set needsRollback but never called
   notifyWaitingIterators(). Blocked iterators would sleep until new
   blocks arrived, stalling indefinitely if connections died after
   rollback.

Also includes defer close(resultCh) and cached batch done-channel fix
from the pipeline deadlock PR.

Signed-off-by: wcatz <[email protected]>
net-rs: add chain tree visualization to node inspector
Display the node's consensus chain tree in the inspector panel as an
SVG diagram of linked blocks. The main chain is shown in blue with the
tip in green; fork blocks appear in orange. Fork blocks share the main
lane when their column is free, and only get their own lane when there
is a collision — descendants inherit their parent's lane.

Backend: add ChainTreeEntry type and ChainTree::snapshot() which walks
backward from the adopted tip for up to 10 blocks, collecting fork
blocks within the window and extending to include fork points. The
snapshot is included in the stats POST to the cluster server and
forwarded to the UI via the stats API.

Cluster: mirror ChainTreeEntry in net-cluster types with serde(default)
for backward compatibility with older net-node binaries.

UI: new ChainTreeView SVG component with auto-scroll to latest blocks.
Integrated into InspectorPanel below the tip display. Dark-themed
scrollbars across all components via MUI CssBaseline overrides.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
net-rs: fetch intermediate blocks on fork switch via range requests
Previously, when a peer announced a longer competing chain, consensus
only fetched the tip block. Intermediate blocks between the common
ancestor and the new tip were never fetched, leaving the ChainStore
incomplete and causing the chain tree display to show a truncated chain
after fork switches.

Changes:

Coordinator: forward all ChainSync headers to the application (not just
tips ahead of best), so consensus can build a complete chain tree with
intermediate block headers from competing forks.

Consensus (on_tip_advanced): use the header's own point (not the tip
point) when inserting intermediate headers during catch-up, since the
header and tip differ when a peer is ahead by multiple blocks. Issue
FetchBlockRange instead of FetchBlock, walking the chain tree backward
to find the earliest unvalidated ancestor and fetching the full range
in one request.

Consensus (on_validation_complete): handle out-of-order block arrival
from range fetches. When a validated block is an ancestor of the adopted
tip (not a fork), inject it without triggering a spurious fork switch.
After injection, check for remaining unvalidated ancestors and fetch any
gaps. Track validated blocks in a HashSet for efficient lookup.

Protocol: add FetchBlockRange { from, to } to NetworkCommand, routed by
the coordinator to the best peer via the existing BlockFetch range
protocol (PeerCommand::FetchBlocks).

Telemetry: derive tip_block_no and tip_hash from the adopted tip (same
source as chain_tree snapshot) rather than from speculative TipAdvanced
events, ensuring consistency between the stats fields and chain tree.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>