use anyhow::Error;
use async_trait::async_trait;
use chrono::Utc;
use clap::Parser;
use tari_comms::peer_manager::PeerFeatures;
use tari_core::base_node::state_machine_service::states::PeerMetadata;
use super::{CommandContext, HandleCommand};
use crate::{table::Table, utils::format_duration_basic};
#[derive(Debug, Parser)]
pub struct Args {
filter: Option<String>,
}
#[async_trait]
impl HandleCommand<Args> for CommandContext {
async fn handle_command(&mut self, args: Args) -> Result<(), Error> {
self.list_peers(args.filter).await
}
}
impl CommandContext {
pub async fn list_peers(&self, filter: Option<String>) -> Result<(), Error> {
let features = filter.as_ref().and_then(|value| match value.to_lowercase().as_str() {
"basenode" | "basenodes" | "base_node" | "base-node" | "bn" => Some(PeerFeatures::COMMUNICATION_NODE),
"wallet" | "wallets" | "w" => Some(PeerFeatures::COMMUNICATION_CLIENT),
_ => {
println!("Unknown filter '{filter:?}'; list-peers");
println!(" Try 'basenode', 'basenodes', 'base-node', 'base_node', 'wallet', 'wallets', 'w'");
None
},
});
let mut peers = self.comms.peer_manager().all(features).await?;
let num_peers = peers.len();
println!();
let mut table = Table::new();
table.set_titles(vec!["NodeId", "Public Key", "Role", "User Agent", "Info"]);
peers.sort_by(|a, b| a.node_id.cmp(&b.node_id));
for peer in peers {
let info_str = {
let mut s = vec![];
if peer.is_seed() {
s.push("SEED".to_string());
}
if peer.is_offline() && !peer.is_banned() {
s.push("OFFLINE".to_string());
}
if let Some(dt) = peer.banned_until() {
s.push(format!(
"BANNED({}, {})",
dt.signed_duration_since(Utc::now().naive_utc())
.to_std()
.map(format_duration_basic)
.unwrap_or_else(|_| "∞".to_string()),
peer.banned_reason
));
}
if let Some(metadata) = peer
.get_metadata(1)
.and_then(|v| bincode::deserialize::<PeerMetadata>(v).ok())
{
s.push(format!("chain height: {}", metadata.metadata.best_block_height()));
}
if let Some(last_seen) = peer.addresses.last_seen() {
let duration = Utc::now()
.naive_utc()
.signed_duration_since(last_seen)
.to_std()
.map(format_duration_basic)
.unwrap_or_else(|_| "?".into());
s.push(format!("last seen: {duration}"));
}
if s.is_empty() { "--".to_string() } else { s.join(", ") }
};
let ua = peer.user_agent;
table.add_row(row![
peer.node_id,
peer.public_key,
{
if peer.features.is_client() {
"Wallet"
} else {
"Base node"
}
},
{ if ua.is_empty() { "<unknown>" } else { ua.as_ref() } },
info_str,
]);
}
table.print_stdout();
println!("{num_peers} peer(s) known by this node");
Ok(())
}
}