clndash: initial peer channel listing

Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
William Casarin
2025-08-08 15:20:45 -07:00
parent 53b4a8da5c
commit 382ef772f5
3 changed files with 81 additions and 25 deletions

4
Cargo.lock generated
View File

@@ -3072,9 +3072,9 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
[[package]] [[package]]
name = "lnsocket" name = "lnsocket"
version = "0.3.0" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a88bd51e5bb3753f89b0d3e73baa565064c5a9f5b2aad3ab3f3db5fffb89955" checksum = "6a373bcde8b65d6db11a0cd0f70dd4a24af854dd7a112b0a51258593c65f48ff"
dependencies = [ dependencies = [
"bitcoin", "bitcoin",
"hashbrown 0.13.2", "hashbrown 0.13.2",

View File

@@ -8,7 +8,7 @@ egui = { workspace = true }
notedeck = { workspace = true } notedeck = { workspace = true }
#notedeck_ui = { workspace = true } #notedeck_ui = { workspace = true }
eframe = { workspace = true } eframe = { workspace = true }
lnsocket = "0.3.0" lnsocket = "0.4.0"
tracing = { workspace = true } tracing = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }

View File

@@ -3,14 +3,19 @@ use lnsocket::bitcoin::secp256k1::{PublicKey, SecretKey, rand};
use lnsocket::{CommandoClient, LNSocket}; use lnsocket::{CommandoClient, LNSocket};
use notedeck::{AppAction, AppContext}; use notedeck::{AppAction, AppContext};
use serde_json::{Value, json}; use serde_json::{Value, json};
use std::collections::HashMap;
use std::str::FromStr; use std::str::FromStr;
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel}; use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel};
type JsonCache = HashMap<String, String>;
#[derive(Default)] #[derive(Default)]
pub struct ClnDash { pub struct ClnDash {
initialized: bool, initialized: bool,
connection_state: ConnectionState, connection_state: ConnectionState,
get_info: Option<String>, get_info: Option<String>,
peer_channels: Option<Vec<Value>>,
json_cache: JsonCache,
channel: Option<Channel>, channel: Option<Channel>,
} }
@@ -27,7 +32,8 @@ struct Channel {
/// Responses from the socket /// Responses from the socket
enum ClnResponse { enum ClnResponse {
GetInfo(Result<Value, String>), GetInfo(Value),
ListPeerChannels(Value),
} }
enum ConnectionState { enum ConnectionState {
@@ -36,8 +42,10 @@ enum ConnectionState {
Active, Active,
} }
#[derive(Eq, PartialEq, Clone, Debug)]
enum Request { enum Request {
GetInfo, GetInfo,
ListPeerChannels,
} }
enum Event { enum Event {
@@ -92,11 +100,15 @@ impl ClnDash {
egui::Frame::new() egui::Frame::new()
.inner_margin(egui::Margin::same(50)) .inner_margin(egui::Margin::same(50))
.show(ui, |ui| { .show(ui, |ui| {
connection_state_ui(ui, &self.connection_state); egui::ScrollArea::vertical().show(ui, |ui| {
connection_state_ui(ui, &self.connection_state);
if let Some(info) = self.get_info.as_ref() { channels_ui(ui, &mut self.json_cache, &self.peer_channels);
get_info_ui(ui, info);
} if let Some(info) = self.get_info.as_ref() {
get_info_ui(ui, info);
}
});
}); });
} }
@@ -127,8 +139,10 @@ impl ClnDash {
} }
}; };
let rune = "Vns1Zxvidr4J8pP2ZCg3Wjp2SyGyyf5RHgvFG8L36yM9MzMmbWV0aG9kPWdldGluZm8="; // getinfo only atm let rune = std::env::var("RUNE").unwrap_or(
let commando = CommandoClient::spawn(lnsocket, rune); "Vns1Zxvidr4J8pP2ZCg3Wjp2SyGyyf5RHgvFG8L36yM9MzMmbWV0aG9kPWdldGluZm8=".to_string(),
);
let commando = CommandoClient::spawn(lnsocket, &rune);
loop { loop {
match req_rx.recv().await { match req_rx.recv().await {
@@ -139,18 +153,32 @@ impl ClnDash {
break; break;
} }
Some(req) => match req { Some(req) => {
Request::GetInfo => match commando.call("getinfo", json!({})).await { tracing::debug!("calling {req:?}");
Ok(v) => { match req {
let _ = event_tx.send(Event::Response(ClnResponse::GetInfo(Ok(v)))); Request::GetInfo => match commando.call("getinfo", json!({})).await {
Ok(v) => {
let _ = event_tx.send(Event::Response(ClnResponse::GetInfo(v)));
}
Err(err) => {
tracing::error!("get_info error {}", err);
}
},
Request::ListPeerChannels => {
match commando.call("listpeerchannels", json!({})).await {
Ok(v) => {
let _ = event_tx.send(Event::Response(
ClnResponse::ListPeerChannels(v),
));
}
Err(err) => {
tracing::error!("listpeerchannels error {}", err);
}
}
} }
Err(err) => { }
let _ = event_tx.send(Event::Ended { }
reason: err.to_string(),
});
}
},
},
} }
} }
}); });
@@ -170,14 +198,17 @@ impl ClnDash {
Event::Connected => { Event::Connected => {
self.connection_state = ConnectionState::Active; self.connection_state = ConnectionState::Active;
let _ = channel.req_tx.send(Request::GetInfo); let _ = channel.req_tx.send(Request::GetInfo);
let _ = channel.req_tx.send(Request::ListPeerChannels);
} }
Event::Response(resp) => match resp { Event::Response(resp) => match resp {
ClnResponse::GetInfo(value) => { ClnResponse::ListPeerChannels(chans) => {
let Ok(value) = value else { if let Some(vs) = chans["channels"].as_array() {
return; self.peer_channels = Some(vs.to_owned());
}; }
}
ClnResponse::GetInfo(value) => {
if let Ok(s) = serde_json::to_string_pretty(&value) { if let Ok(s) = serde_json::to_string_pretty(&value) {
self.get_info = Some(s); self.get_info = Some(s);
} }
@@ -193,3 +224,28 @@ fn get_info_ui(ui: &mut egui::Ui, info: &str) {
ui.add(Label::new(info).wrap_mode(egui::TextWrapMode::Wrap)); ui.add(Label::new(info).wrap_mode(egui::TextWrapMode::Wrap));
}); });
} }
fn channel_ui(ui: &mut egui::Ui, cache: &mut JsonCache, channel: &Value) {
let short_channel_id = channel["short_channel_id"].as_str().unwrap_or("??");
egui::CollapsingHeader::new(format!("channel {short_channel_id}"))
.id_salt(("section", short_channel_id))
.show(ui, |ui| {
let json: &String = cache
.entry(short_channel_id.to_owned())
.or_insert_with(|| serde_json::to_string_pretty(channel).unwrap());
ui.add(Label::new(json).wrap_mode(egui::TextWrapMode::Wrap));
});
}
fn channels_ui(ui: &mut egui::Ui, json_cache: &mut JsonCache, channels: &Option<Vec<Value>>) {
let Some(channels) = channels else {
ui.label("no channels");
return;
};
for channel in channels {
channel_ui(ui, json_cache, channel);
}
}