Add ASCII fork choice tree to terminal logs#143
Add ASCII fork choice tree to terminal logs#143pablodeymo wants to merge 2 commits intoadd-fork-choice-visualizationfrom
Conversation
Print a decorated fork choice tree at interval 4 (end-of-slot) so developers can see chain shape, forks, and weights directly in the terminal without opening a browser. The tree shows finalized/justified/head checkpoints, linear trunk until first fork, Unicode branch connectors, missing-slot indicators, head marker (*), leaf weights ([w:N]), and depth truncation at 20 nodes.
🤖 Kimi Code ReviewReview SummaryThis PR introduces a fork choice tree visualization feature. The code is generally well-structured and follows Rust best practices. Here are my findings: Issues Found
Security Considerations
Code Quality Suggestions
Minor Issues
The implementation is solid overall and follows good Rust practices with proper error handling in most places. The visualization will be very helpful for debugging fork choice issues. Automated review by Kimi (Moonshot AI) · custom prompt |
🤖 Codex Code ReviewSummary Findings
Consensus-layer review If you want next steps
Automated review by OpenAI Codex · custom prompt |
Greptile SummaryAdds ASCII fork choice tree visualization logged to terminal at end-of-slot (interval 4), enabling developers to monitor chain state without external tools. Key changes:
Implementation quality:
Confidence Score: 5/5
|
| Filename | Overview |
|---|---|
| crates/blockchain/src/fork_choice_tree.rs | New ASCII fork choice tree formatter with comprehensive tests, clean implementation using String::fmt::Write, proper depth truncation, and well-structured recursive rendering |
| crates/blockchain/src/lib.rs | Simple module declaration restricted to pub(crate) visibility as intended |
| crates/blockchain/src/store.rs | Adds log_tree parameter to control fork choice tree logging, only enabled at interval 4 to avoid excessive output, integrates cleanly with existing fork choice logic |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[on_tick - Interval 4] --> B{log_tree == true?}
B -->|Yes| C[accept_new_attestations]
C --> D[update_head]
D --> E[compute_lmd_ghost_head]
D --> F[compute_block_weights]
F --> G[format_fork_choice_tree]
G --> H[TreeRenderer.render_trunk]
H --> I{Fork point?}
I -->|Yes| J[TreeRenderer.render_branches]
I -->|No| K[render single chain]
J --> L[render_branch_line for each child]
L --> M{Sub-fork?}
M -->|Yes| J
M -->|No| N[Leaf node with weight]
G --> O[info! log to terminal]
B -->|No| P[Skip logging]
Last reviewed commit: e5892ae
🤖 Claude Code ReviewNow I have enough context for a thorough review. Let me compile it. PR #143 Review — ASCII Fork Choice TreeSummaryClean addition of a pure ASCII renderer with no new dependencies and a solid test suite. The logic is generally sound. A few correctness and performance issues worth addressing before merging. Bugs
This means that if the head block sits inside a linear segment whose tip is a deeper descendant — a realistic scenario when a new block extends a chain but doesn't yet have enough weight to become the new head — the
1 => {
if is_head { write!(output, " *").unwrap(); }
write!(output, "── ").unwrap();
...
}The same guard needs to be added in
blocks
.iter()
.filter(|(_, (_, parent))| parent.is_zero() || !blocks.contains_key(parent))
.min_by_key(|(_, (slot, _))| *slot)
.map(|(root, _)| *root)
.expect("blocks is non-empty")Two problems:
Both are low-probability in normal operation but worth guarding against. Return an Performance
let weights =
ethlambda_fork_choice::compute_block_weights(finalized.slot, &blocks, &attestations);This runs again immediately after. For a large live chain, you're walking all attestations twice per slot. Consider exposing the weights alongside the head from Minor Issues
Test GapsThe unit tests are well-structured and cover the documented cases. Two scenarios worth adding:
What's Good
Automated review by Claude (Anthropic) · custom prompt |
…add missing tests - Change module and function visibility from pub to pub(crate) since the formatter is only used within the blockchain crate. - Replace .cloned().unwrap_or_default() with slice pattern matching to avoid allocating a new Vec on every branch iteration. - Add tests for: single-block chain, depth truncation at MAX_DISPLAY_DEPTH, and nested forks (sub-fork within a branch).
|
@greptile-apps review this PR again |
Motivation
This PR adds an ASCII tree directly to terminal logs so developers can monitor fork choice state without leaving the terminal.
Description
Adds a new
fork_choice_treemodule that renders the fork choice tree as ASCII art and logs it once per slot at interval 4 (end-of-slot attestation acceptance).Output format
Conventions
root(slot)format using ShortRoot (8 hex chars)──connectors for linear chains├──/└──Unicode tree characters for forks*marker on the head block[w:N]weight annotation on leaf nodes[ ]for single missing slot,[N]for N consecutive missing slots...when chain exceeds 20 nodesChanges
crates/blockchain/src/fork_choice_tree.rsformat_fork_choice_tree()pure formatter +TreeRendererstruct for recursive renderingcrates/blockchain/src/lib.rspub(crate) mod fork_choice_treedeclarationcrates/blockchain/src/store.rslog_treeparameter toupdate_head/accept_new_attestations; log tree at interval 4 onlyLogging frequency
To avoid excessive output, the tree is only printed at interval 4 (end-of-slot), giving one tree per slot. Block imports (interval 0) and
on_blockcalls passlog_tree: false.No new dependencies
The formatter uses only
std::fmt::Writeand types already available in the blockchain crate (H256,ShortRoot,Checkpoint).How to Test
Unit tests
├──/└──connectors and branch count[ ]indicator[N]indicator(empty)output*appears on the correct node...at MAX_DISPLAY_DEPTH (20)