Skip to content

Commit 59b6176

Browse files
committed
Improve wallet show. (linera-io#4642)
It's hard to tell from `wallet show` which chain is the admin chain. The table layout uses a lot of horizontal space. Improve the `wallet show` output: ``` WALLET (4 chains in total) Child Chain [DEFAULT] Chain ID: a08244c18c828b7b89289f7164428e0b861fb462042416f6c2cfb1f4b53c8621 Owner: 0x86843df3f162b0a820724d37612ecde13c9a022d0cc96b765be127d34cec2d19 Timestamp: 2025-09-24 12:54:53.051239 Blocks: 0 Epoch: 0 Admin Chain Chain ID: 82fc0ff002b793c66db0c57e657c05a30a98cd49d335f8125d0c3bdc4997ffa1 Owner: No owner key Timestamp: 2025-09-24 12:53:29.841442 Blocks: 0 Epoch: 0 Root Chain 1 Chain ID: 0b67f224ab915776d170eeb7d497e8d1ed831126f22eaf9138de48e3c47c8639 Owner: No owner key Timestamp: 2025-09-24 12:55:04.030389 Blocks: 2 Epoch: 0 Latest Block: f67e7ee881b6f9235f1a4cc6f2515d9ce4afccb1bd799f8780987901a581ac03 Child Chain Chain ID: 87c4ef0bfc8d6581529dc32c98d05ba99be9e9202f8f788f9c0c2a872c02fdf6 Owner: 0x9010b58faf7b4908de3069aa7aa042ba4f2eb0de13724059960aaa5be33aa6a6 Timestamp: 2025-09-24 12:55:04.030389 Blocks: 0 Epoch: 0 ``` * Discuss the new layout! * CI * I ran the README tests locally. - These changes _could_ be backported to `testnet_conway`, and - released in a new SDK. - Original attempt: linera-io#4541 - [reviewer checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
1 parent 8871c63 commit 59b6176

File tree

6 files changed

+90
-95
lines changed

6 files changed

+90
-95
lines changed

Cargo.lock

Lines changed: 1 addition & 41 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ chrono = { version = "0.4.35", default-features = false }
100100
clap = { version = "4", features = ["cargo", "derive", "env"] }
101101
clap-markdown = "0.1.3"
102102
colored = "2.1.0"
103-
comfy-table = "7.1.0"
104103
console_error_panic_hook = "0.1.7"
105104
convert_case = "0.6.0"
106105
criterion = { version = "0.5.1", default-features = false }

linera-base/src/data_types.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,14 @@ impl ChainOrigin {
724724
pub fn is_child(&self) -> bool {
725725
matches!(self, ChainOrigin::Child { .. })
726726
}
727+
728+
/// Returns the root chain number, if this is a root chain.
729+
pub fn root(&self) -> Option<u32> {
730+
match self {
731+
ChainOrigin::Root(i) => Some(*i),
732+
ChainOrigin::Child { .. } => None,
733+
}
734+
}
727735
}
728736

729737
/// A number identifying the configuration of the chain (aka the committee).

linera-service/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ chrono = { workspace = true, features = ["clock"] }
8080
clap.workspace = true
8181
clap-markdown.workspace = true
8282
colored.workspace = true
83-
comfy-table.workspace = true
8483
convert_case.workspace = true
8584
current_platform = "0.2.0"
8685
custom_debug_derive.workspace = true

linera-service/src/cli/main.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2576,7 +2576,6 @@ async fn run(options: &ClientOptions) -> Result<i32, Error> {
25762576
short,
25772577
owned,
25782578
} => {
2579-
let start_time = Instant::now();
25802579
let wallet = options.wallet()?;
25812580
let chain_ids = if let Some(chain_id) = chain_id {
25822581
ensure!(!owned, "Cannot specify both --owned and a chain ID");
@@ -2593,7 +2592,6 @@ async fn run(options: &ClientOptions) -> Result<i32, Error> {
25932592
} else {
25942593
wallet::pretty_print(&wallet, chain_ids);
25952594
}
2596-
info!("Wallet shown in {} ms", start_time.elapsed().as_millis());
25972595
Ok(0)
25982596
}
25992597

linera-service/src/wallet.rs

Lines changed: 81 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,95 @@
11
// Copyright (c) Zefchain Labs, Inc.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
use comfy_table::{
5-
modifiers::UTF8_ROUND_CORNERS, presets::UTF8_FULL, Attribute, Cell, Color, ContentArrangement,
6-
Table,
4+
use linera_base::{
5+
data_types::{ChainDescription, ChainOrigin},
6+
identifiers::ChainId,
77
};
8-
use linera_base::identifiers::ChainId;
98
pub use linera_client::wallet::*;
109

1110
pub fn pretty_print(wallet: &Wallet, chain_ids: impl IntoIterator<Item = ChainId>) {
12-
let mut table = Table::new();
13-
table
14-
.load_preset(UTF8_FULL)
15-
.apply_modifier(UTF8_ROUND_CORNERS)
16-
.set_content_arrangement(ContentArrangement::Dynamic)
17-
.set_header(vec![
18-
Cell::new("Chain ID").add_attribute(Attribute::Bold),
19-
Cell::new("Latest Block").add_attribute(Attribute::Bold),
20-
]);
21-
for chain_id in chain_ids {
11+
let chain_ids: Vec<_> = chain_ids.into_iter().collect();
12+
let total_chains = chain_ids.len();
13+
14+
if total_chains == 0 {
15+
println!("No chains in wallet.");
16+
return;
17+
}
18+
19+
let plural_s = if total_chains == 1 { "" } else { "s" };
20+
println!("\n\x1b[1mWALLET ({total_chains} chain{plural_s} in total)\x1b[0m",);
21+
22+
let mut chains = chain_ids
23+
.into_iter()
24+
.map(|chain_id| ChainDetails::new(chain_id, wallet))
25+
.collect::<Vec<_>>();
26+
// Print first the default, then the admin chain, then other root chains, and finally the
27+
// child chains.
28+
chains.sort_unstable_by_key(|chain| {
29+
let root_id = chain
30+
.origin
31+
.and_then(|origin| origin.root())
32+
.unwrap_or(u32::MAX);
33+
let chain_id = chain.user_chain.chain_id;
34+
(!chain.is_default, !chain.is_admin, root_id, chain_id)
35+
});
36+
for chain in chains {
37+
println!();
38+
chain.print_paragraph();
39+
}
40+
}
41+
42+
struct ChainDetails<'a> {
43+
is_default: bool,
44+
is_admin: bool,
45+
origin: Option<ChainOrigin>,
46+
user_chain: &'a UserChain,
47+
}
48+
49+
impl<'a> ChainDetails<'a> {
50+
fn new(chain_id: ChainId, wallet: &'a Wallet) -> Self {
2251
let Some(user_chain) = wallet.chains.get(&chain_id) else {
2352
panic!("Chain {} not found.", chain_id);
2453
};
25-
update_table_with_chain(
26-
&mut table,
27-
chain_id,
54+
ChainDetails {
55+
is_default: Some(chain_id) == wallet.default,
56+
is_admin: chain_id == wallet.genesis_admin_chain(),
57+
origin: wallet
58+
.genesis_config()
59+
.chains
60+
.iter()
61+
.find(|description| description.id() == chain_id)
62+
.map(ChainDescription::origin),
2863
user_chain,
29-
Some(chain_id) == wallet.default,
30-
);
64+
}
3165
}
32-
println!("{}", table);
33-
}
3466

35-
fn update_table_with_chain(
36-
table: &mut Table,
37-
chain_id: ChainId,
38-
user_chain: &UserChain,
39-
is_default_chain: bool,
40-
) {
41-
let chain_id_cell = if is_default_chain {
42-
Cell::new(format!("{}", chain_id)).fg(Color::Green)
43-
} else {
44-
Cell::new(format!("{}", chain_id))
45-
};
46-
let account_owner = user_chain.owner;
47-
table.add_row(vec![
48-
chain_id_cell,
49-
Cell::new(format!(
50-
r#"AccountOwner: {}
51-
Block Hash: {}
52-
Timestamp: {}
53-
Next Block Height: {}"#,
54-
account_owner
55-
.as_ref()
56-
.map_or_else(|| "-".to_string(), |o| o.to_string()),
57-
user_chain
58-
.block_hash
59-
.map_or_else(|| "-".to_string(), |bh| bh.to_string()),
60-
user_chain.timestamp,
61-
user_chain.next_block_height
62-
)),
63-
]);
67+
fn print_paragraph(&self) {
68+
let title = if self.is_admin {
69+
"Admin Chain".to_string()
70+
} else {
71+
match self.origin {
72+
Some(ChainOrigin::Root(i)) => format!("Root Chain {i}"),
73+
_ => "Child Chain".to_string(),
74+
}
75+
};
76+
let default_marker = if self.is_default { " [DEFAULT]" } else { "" };
77+
78+
// Print chain header in bold
79+
println!("\x1b[1m{}{}\x1b[0m", title, default_marker);
80+
println!(" Chain ID: {}", self.user_chain.chain_id);
81+
if let Some(owner) = &self.user_chain.owner {
82+
println!(" Owner: {owner}");
83+
} else {
84+
println!(" Owner: No owner key");
85+
}
86+
println!(" Timestamp: {}", self.user_chain.timestamp);
87+
println!(" Blocks: {}", self.user_chain.next_block_height);
88+
if let Some(hash) = self.user_chain.block_hash {
89+
println!(" Latest Block: {}", hash);
90+
}
91+
if self.user_chain.pending_proposal.is_some() {
92+
println!(" Status: ⚠ Pending proposal");
93+
}
94+
}
6495
}

0 commit comments

Comments
 (0)