From cff8d3d84856adcb4cb481075fc5d33a926ac323 Mon Sep 17 00:00:00 2001 From: Raunak Kumar Date: Sat, 2 May 2026 09:52:34 +0000 Subject: [PATCH 1/3] Add chain_info in P2Pool Status --- Cargo.lock | 252 +++++++++++++++++- Cargo.toml | 3 + config/config.toml | 5 + dummy.toml | 49 ++++ src/app.rs | 37 +++ src/components/mod.rs | 1 + src/components/p2pool_client.rs | 206 ++++++++++++++ src/components/p2pool_status_view.rs | 67 ++++- src/config.rs | 29 ++ src/lib.rs | 1 + src/main.rs | 1 + ...i__tests__p2pool_status_screen_render.snap | 24 +- src/ui.rs | 21 +- 13 files changed, 667 insertions(+), 29 deletions(-) create mode 100644 config/config.toml create mode 100644 dummy.toml create mode 100644 src/components/p2pool_client.rs create mode 100644 src/config.rs diff --git a/Cargo.lock b/Cargo.lock index a15efaa..e767838 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,6 +46,16 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "async-trait" version = "0.1.89" @@ -264,6 +274,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "colored" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "compact_str" version = "0.9.0" @@ -555,7 +574,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -638,7 +657,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -744,6 +763,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -763,6 +783,12 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + [[package]] name = "futures-sink" version = "0.3.32" @@ -782,7 +808,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", + "futures-io", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "slab", ] @@ -804,8 +833,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -815,9 +846,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 5.3.0", "wasip2", + "wasm-bindgen", ] [[package]] @@ -962,6 +995,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hyper" version = "1.8.1" @@ -976,6 +1015,7 @@ dependencies = [ "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "pin-utils", @@ -998,6 +1038,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", + "webpki-roots", ] [[package]] @@ -1353,6 +1394,12 @@ dependencies = [ "hashbrown 0.16.1", ] +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "mac_address" version = "1.1.8" @@ -1417,6 +1464,31 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "mockito" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90820618712cab19cfc46b274c6c22546a82affcb3c3bdf0f29e3db8e1bb92c0" +dependencies = [ + "assert-json-diff", + "bytes", + "colored", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "log", + "pin-project-lite", + "rand 0.9.4", + "regex", + "serde_json", + "serde_urlencoded", + "similar", + "tokio", +] + [[package]] name = "native-tls" version = "0.2.18" @@ -1463,7 +1535,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1636,9 +1708,12 @@ dependencies = [ "crossterm", "directories", "insta", + "mockito", "p2poolv2_config", "ratatui", + "reqwest", "serde", + "serde_json", "serial_test", "tempfile", "toml 0.8.23", @@ -1722,7 +1797,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", - "rand", + "rand 0.8.5", ] [[package]] @@ -1786,6 +1861,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "prettyplease" version = "0.2.37" @@ -1805,6 +1889,61 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.4", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.42" @@ -1832,7 +1971,27 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -1841,6 +2000,15 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "ratatui" version = "0.30.0" @@ -1984,7 +2152,9 @@ dependencies = [ "base64 0.22.1", "bytes", "encoding_rs", + "futures-channel", "futures-core", + "futures-util", "h2", "http", "http-body", @@ -1999,6 +2169,8 @@ dependencies = [ "native-tls", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", "rustls-pki-types", "serde", "serde_json", @@ -2006,6 +2178,7 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", + "tokio-rustls", "tower", "tower-http", "tower-service", @@ -2013,6 +2186,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots", ] [[package]] @@ -2074,6 +2248,12 @@ dependencies = [ "ordered-multimap 0.7.3", ] +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + [[package]] name = "rustc_version" version = "0.4.1" @@ -2093,7 +2273,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2103,6 +2283,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "once_cell", + "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -2115,6 +2296,7 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ + "web-time", "zeroize", ] @@ -2178,7 +2360,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ "bitcoin_hashes", - "rand", + "rand 0.8.5", "secp256k1-sys", "serde", ] @@ -2540,7 +2722,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2695,6 +2877,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.50.0" @@ -3207,6 +3404,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "wezterm-bidi" version = "0.2.3" @@ -3653,6 +3869,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "zerofrom" version = "0.1.6" diff --git a/Cargo.toml b/Cargo.toml index 7b690d0..d686749 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,9 @@ unicode-width = "0.2" p2poolv2_config = { git = "https://github.com/p2poolv2/p2poolv2", package = "p2poolv2_config" } bitcoin = "0.32.5" toml_edit = "0.22" +reqwest = { version = "0.12", features = ["json", "rustls-tls", "blocking"] } +mockito = "1.7.2" +serde_json = "1.0.149" [dev-dependencies] insta = "1.44.3" diff --git a/config/config.toml b/config/config.toml new file mode 100644 index 0000000..8d74355 --- /dev/null +++ b/config/config.toml @@ -0,0 +1,5 @@ +[api] +host = "127.0.0.1" +port = 46884 +auth_user = "p2pool" +auth_pass = "p2pool" diff --git a/dummy.toml b/dummy.toml new file mode 100644 index 0000000..bd5642b --- /dev/null +++ b/dummy.toml @@ -0,0 +1,49 @@ + +[network] +listen_address = "/ip4/127.0.0.1/tcp/6884" +dial_peers = [] +max_pending_incoming = 10 +max_pending_outgoing = 10 +max_established_incoming = 50 +max_established_outgoing = 50 +max_established_per_peer = 1 +max_workbase_per_second = 10 +max_userworkbase_per_second = 10 +max_miningshare_per_second = 100 +max_inventory_per_second = 100 +max_transaction_per_second = 100 +max_requests_per_second = 100 +dial_timeout_secs = 30 + +[store] +path = "./store.db" +background_task_frequency_hours = 24 +pplns_ttl_days = 7 + +[stratum] +hostname = "pool.example.com" +port = 3333 +start_difficulty = 10000 +minimum_difficulty = 100 +solo_address = "tb1qyazxde6558qj6z3d9np5e6msmrspwpf6k0qggk" +bootstrap_address = "tb1qyazxde6558qj6z3d9np5e6msmrspwpf6k0qggk" +zmqpubhashblock = "tcp://127.0.0.1:28332" +network = "signet" +version_mask = "1fffe000" +difficulty_multiplier = 1.0 +pool_signature = "P2Poolv2" + +[bitcoinrpc] +url = "http://127.0.0.1:38332" +username = "p2pool" +password = "p2pool" + +[logging] +file = "./logs/p2pool.log" +console = true +level = "info" +stats_dir = "./logs/stats" + +[api] +hostname = "127.0.0.1" +port = 46884 diff --git a/src/app.rs b/src/app.rs index 8452e30..d315ab6 100644 --- a/src/app.rs +++ b/src/app.rs @@ -5,6 +5,7 @@ use crate::bitcoin_config::ConfigEntry as BitcoinEntry; use crate::components::bitcoin_config_view::BitcoinConfigView; use crate::components::file_explorer::FileExplorer; +use crate::components::p2pool_client::{ChainInfo, P2PoolClient}; use crate::components::p2pool_config_view::P2PoolConfigView; use crate::components::settings_view::SettingsView; use crate::settings::Settings; @@ -31,6 +32,11 @@ pub const BITCOIN_STATUS_TABS: &[&str] = &["Chain Info", "System", "Logs", "Peer pub const MAX_BITCOIN_STATUS_TAB: usize = BITCOIN_STATUS_TABS.len() - 1; +/// Tab labels for the P2Pool Status view +pub const P2POOL_STATUS_TABS: &[&str] = &["Chain Info"]; + +pub const MAX_P2POOL_STATUS_TAB: usize = P2POOL_STATUS_TABS.len() - 1; + #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum CurrentScreen { Home, @@ -96,12 +102,16 @@ pub struct App { pub bitcoin_data: Vec, pub bitcoin_status_tab: usize, pub settings: Settings, + pub p2pool_client: P2PoolClient, /// Cached value of the `HOME` environment variable, used for path display. /// Populated once at startup to avoid repeated syscalls during rendering. pub home_dir: String, /// Cached result of `settings::config_dir()`, used to display the default /// settings storage path without repeated env-var lookups during rendering. pub config_dir: PathBuf, + pub p2pool_status_tab: usize, + pub chain_info: Option, + pub p2pool_chain_info_error: Option, } impl App { @@ -121,8 +131,32 @@ impl App { bitcoin_data: Vec::new(), bitcoin_status_tab: 0, settings: Settings::default(), + p2pool_client: P2PoolClient::new(), home_dir: std::env::var("HOME").unwrap_or_default(), config_dir: crate::settings::config_dir().unwrap_or_default(), + p2pool_status_tab: 0, + chain_info: None, + p2pool_chain_info_error: None, + } + } + + #[must_use] + pub fn new_with_client(client: P2PoolClient) -> App { + let mut app = App::new(); + app.p2pool_client = client; + app + } + + pub fn refresh_chain_info(&mut self) { + match self.p2pool_client.fetch_chain_info() { + Ok(info) => { + self.chain_info = Some(info); + self.p2pool_chain_info_error = None; + } + Err(e) => { + self.chain_info = None; + self.p2pool_chain_info_error = Some(e.to_string()); + } } } @@ -142,6 +176,9 @@ impl App { } if let Some(&(_, screen)) = SIDEBAR_ITEMS.get(self.sidebar_index) { self.current_screen = screen; + if self.current_screen == CurrentScreen::P2PoolStatus { + self.refresh_chain_info(); + } } } } diff --git a/src/components/mod.rs b/src/components/mod.rs index 65ecad5..1f94aa6 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -8,6 +8,7 @@ pub mod file_explorer; pub mod home_view; pub mod ln_config_view; pub mod ln_status_view; +pub mod p2pool_client; pub mod p2pool_config_view; pub mod p2pool_status_view; pub mod settings_view; diff --git a/src/components/p2pool_client.rs b/src/components/p2pool_client.rs new file mode 100644 index 0000000..8f9c898 --- /dev/null +++ b/src/components/p2pool_client.rs @@ -0,0 +1,206 @@ +// SPDX-FileCopyrightText: 2024 PDM Authors +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use crate::config::load_api_config; +use reqwest::blocking::Client; +use serde::Deserialize; +use std::time::Duration; + +const REQUEST_TIMEOUT_SECONDS: u64 = 10; + +#[derive(Debug, Clone)] +pub struct P2PoolClient { + client: Client, + base_url: String, + auth_credentials: Option<(String, String)>, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct ChainInfo { + pub genesis_blockhash: Option, + pub chain_tip_height: Option, + pub total_work: String, + pub chain_tip_blockhash: Option, +} + +fn build_client() -> Client { + Client::builder() + .timeout(Duration::from_secs(REQUEST_TIMEOUT_SECONDS)) + .build() + .expect("Failed to build reqwest client") +} + +impl P2PoolClient { + pub fn new() -> Self { + let (base_url, auth_credentials) = load_api_config() + .map(|cfg| { + let credentials = cfg.auth_user.zip(cfg.auth_pass); + (cfg.base_url, credentials) + }) + .unwrap_or_else(|_| ("http://localhost:8332".to_string(), None)); + + Self { + client: build_client(), + base_url, + auth_credentials, + } + } + + pub fn with_base_url(base_url: impl Into) -> Self { + Self { + client: build_client(), + base_url: base_url.into(), + auth_credentials: None, + } + } + + pub fn with_client(client: Client, base_url: impl Into) -> Self { + Self { + client, + base_url: base_url.into(), + auth_credentials: None, + } + } + + pub fn with_auth(mut self, user: String, pass: String) -> Self { + self.auth_credentials = Some((user, pass)); + self + } + + pub fn fetch_chain_info(&self) -> Result { + let url = format!("{}/chain_info", self.base_url); + let mut request = self.client.get(url); + + if let Some((user, pass)) = &self.auth_credentials { + request = request.basic_auth(user, Some(pass)); + } + + let response = request.send()?.error_for_status()?; + let data = response.json::()?; + + Ok(data) + } +} + +impl Default for P2PoolClient { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use mockito::Server; + use serde_json::json; + + #[test] + fn test_fetch_chain_info_success() { + let mut server = Server::new(); + + let mock = server + .mock("GET", "/chain_info") + .with_status(200) + .with_header("content-type", "application/json") + .with_body( + json!({ + "genesis_blockhash": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", + "chain_tip_height": 850_000u64, + "total_work": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "chain_tip_blockhash": "00000000000000000002a7c4c1e48d76c5a37902165a270156b7a8d72728a054" + }) + .to_string(), + ) + .create(); + + let client = P2PoolClient::with_base_url(server.url()); + let result = client.fetch_chain_info().unwrap(); + + assert_eq!(result.chain_tip_height, Some(850_000)); + assert_eq!( + result.total_work, + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + ); + assert_eq!( + result.genesis_blockhash.unwrap(), + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" + ); + assert_eq!( + result.chain_tip_blockhash.unwrap(), + "00000000000000000002a7c4c1e48d76c5a37902165a270156b7a8d72728a054" + ); + mock.assert(); + } + + #[test] + fn test_fetch_chain_info_sends_basic_auth() { + let mut server = Server::new(); + + let mock = server + .mock("GET", "/chain_info") + .match_header("authorization", "Basic dXNlcjpwYXNzd29yZA==") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(json!({ "total_work": "abc" }).to_string()) + .create(); + + let client = + P2PoolClient::with_base_url(server.url()).with_auth("user".into(), "password".into()); + + client.fetch_chain_info().unwrap(); + mock.assert(); + } + + #[test] + fn test_fetch_chain_info_errors_on_http_500() { + let mut server = Server::new(); + + server.mock("GET", "/chain_info").with_status(500).create(); + + let client = P2PoolClient::with_base_url(server.url()); + assert!(client.fetch_chain_info().is_err()); + } + + #[test] + fn test_fetch_chain_info_returns_error_on_missing_required_field() { + let mut server = Server::new(); + + server + .mock("GET", "/chain_info") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(json!({ "chain_tip_height": 100 }).to_string()) + .create(); + + let client = P2PoolClient::with_base_url(server.url()); + assert!(client.fetch_chain_info().is_err()); + } + + #[test] + fn test_with_client_can_be_injected_for_isolated_tests() { + let mut server = Server::new(); + + let mock = server + .mock("GET", "/chain_info") + .with_status(200) + .with_header("content-type", "application/json") + .with_body( + json!({ + "genesis_blockhash": null, + "chain_tip_height": 1, + "total_work": "abc", + "chain_tip_blockhash": null + }) + .to_string(), + ) + .create(); + + let client = P2PoolClient::with_client(build_client(), server.url()); + let result = client.fetch_chain_info().unwrap(); + + assert_eq!(result.chain_tip_height, Some(1)); + assert_eq!(result.total_work, "abc"); + mock.assert(); + } +} diff --git a/src/components/p2pool_status_view.rs b/src/components/p2pool_status_view.rs index e4fab69..5b3ef43 100644 --- a/src/components/p2pool_status_view.rs +++ b/src/components/p2pool_status_view.rs @@ -2,10 +2,10 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -use crate::app::App; +use crate::app::{App, P2POOL_STATUS_TABS}; use ratatui::{ prelude::*, - widgets::{Block, Borders, Paragraph}, + widgets::{Block, Borders, Paragraph, Tabs, Wrap}, }; #[derive(Debug, Clone)] @@ -18,13 +18,62 @@ impl P2PoolStatusView { } // P2Pool Status - pub fn render(f: &mut Frame, _app: &mut App, area: Rect) { - let p = Paragraph::new("P2Pool Status").block( - Block::default() - .borders(Borders::ALL) - .title(" P2Pool Status "), - ); - f.render_widget(p, area); + pub fn render(f: &mut Frame, app: &App, area: Rect) { + let outer = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(4), // Tabs bar + Constraint::Min(0), // Content area + ]) + .split(area); + + let tabs = Tabs::new(P2POOL_STATUS_TABS.to_vec()) + .block(Block::default().borders(Borders::ALL).title(" Info ")) + .select(app.p2pool_status_tab) + .highlight_style(Style::default().bg(Color::Gray).fg(Color::Black)); + + f.render_widget(tabs, outer[0]); + + match app.p2pool_status_tab { + 0 => Self::render_chain_info(f, app, outer[1]), + _ => {} + } + } + + fn render_chain_info(f: &mut Frame, app: &App, area: Rect) { + let text = if let Some(info) = &app.chain_info { + vec![ + Line::from(format!( + "Genesis Blockhash : {}", + info.genesis_blockhash.as_deref().unwrap_or("-") + )), + Line::from(format!( + "Chain Tip Height : {}", + info.chain_tip_height.unwrap_or(0) + )), + Line::from(format!( + "Chain Tip Blockhash : {}", + info.chain_tip_blockhash.as_deref().unwrap_or("-") + )), + Line::from(format!("Total Work : {}", info.total_work)), + ] + } else if let Some(err) = &app.p2pool_chain_info_error { + vec![Line::from(Span::styled( + format!("Failed to fetch chain info: {err}"), + Style::default().fg(Color::Red), + ))] + } else { + vec![Line::from(Span::styled( + "Loading chain info...", + Style::default().fg(Color::DarkGray), + ))] + }; + + let paragraph = Paragraph::new(text) + .block(Block::default().borders(Borders::ALL).title(" Chain Info ")) + .wrap(Wrap { trim: true }); + + f.render_widget(paragraph, area); } } diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..606604b --- /dev/null +++ b/src/config.rs @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2024 PDM Authors +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use anyhow::Result; +use config::{Config, File}; + +pub struct ApiConfig { + pub base_url: String, + pub auth_user: Option, + pub auth_pass: Option, +} + +pub fn load_api_config() -> Result { + let settings = Config::builder() + .add_source(File::with_name("config/config")) + .build()?; + + let host: String = settings.get("api.host")?; + let port: u16 = settings.get("api.port")?; + let auth_user: Option = settings.get("api.auth_user").ok(); + let auth_pass: Option = settings.get("api.auth_pass").ok(); + + Ok(ApiConfig { + base_url: format!("http://{}:{}", host, port), + auth_user, + auth_pass, + }) +} diff --git a/src/lib.rs b/src/lib.rs index 82d6883..2ec9158 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ pub mod app; pub mod bitcoin_config; pub mod components; +pub mod config; pub mod p2poolv2_config; pub mod settings; pub mod ui; diff --git a/src/main.rs b/src/main.rs index 0771b47..30b59b4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,6 +36,7 @@ fn main() -> Result<()> { let mut app = App::new(); app.settings = load_settings(); bootstrap_from_settings(&mut app); + app.refresh_chain_info(); let res = run_app(&mut terminal, &mut app); // Restore Terminal diff --git a/src/snapshots/pdm__ui__tests__p2pool_status_screen_render.snap b/src/snapshots/pdm__ui__tests__p2pool_status_screen_render.snap index 3c98693..4ff5c3e 100644 --- a/src/snapshots/pdm__ui__tests__p2pool_status_screen_render.snap +++ b/src/snapshots/pdm__ui__tests__p2pool_status_screen_render.snap @@ -1,23 +1,23 @@ --- source: src/ui.rs -assertion_line: 190 +assertion_line: 219 expression: terminal.backend() --- TestBackend { buffer: Buffer { area: Rect { x: 0, y: 0, width: 80, height: 24 }, content: [ - "┌ PDM ──────────────────┐┌ P2Pool Status ──────────────────────────────────────┐", - "│Home ││P2Pool Status │", + "┌ PDM ──────────────────┐┌ Info ───────────────────────────────────────────────┐", + "│Home ││ Chain Info │", "│Bitcoin Config ││ │", - "│Bitcoin Status ││ │", - "│P2Pool Config ││ │", - "│P2Pool Status ││ │", - "│LN Config ││ │", - "│LN Status ││ │", - "│Shares Market ││ │", - "│Settings ││ │", - "│ ││ │", + "│Bitcoin Status │└─────────────────────────────────────────────────────┘", + "│P2Pool Config │┌ Chain Info ─────────────────────────────────────────┐", + "│P2Pool Status ││Genesis Blockhash : - │", + "│LN Config ││Chain Tip Height : 1 │", + "│LN Status ││Chain Tip Blockhash : - │", + "│Shares Market ││Total Work : │", + "│Settings ││fffffffffffffffffffffffffffffffffffffffffffffffffffff│", + "│ ││fff │", "│ ││ │", "│ ││ │", "│ ││ │", @@ -34,6 +34,8 @@ TestBackend { ], styles: [ x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE, + x: 27, y: 1, fg: Black, bg: Gray, underline: Reset, modifier: NONE, + x: 37, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: NONE, x: 1, y: 5, fg: Black, bg: Gray, underline: Reset, modifier: NONE, x: 24, y: 5, fg: Reset, bg: Reset, underline: Reset, modifier: NONE, x: 0, y: 23, fg: White, bg: DarkGray, underline: Reset, modifier: NONE, diff --git a/src/ui.rs b/src/ui.rs index 322542c..e9fbede 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -112,6 +112,8 @@ pub fn ui(f: &mut Frame, app: &mut App) { mod tests { use super::*; use crate::app::App; + use crate::components::p2pool_client::P2PoolClient; + use mockito::Server; use ratatui::Terminal; use ratatui::backend::TestBackend; @@ -192,8 +194,25 @@ mod tests { #[test] fn test_p2pool_status_screen_render() { + let mut server = Server::new(); + let _mock = server + .mock("GET", "/chain_info") + .with_status(200) + .with_header("content-type", "application/json") + .with_body( + serde_json::json!({ + "genesis_blockhash": null, + "chain_tip_height": 1, + "total_work": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "chain_tip_blockhash": null + }) + .to_string(), + ) + .create(); + + let client = P2PoolClient::with_base_url(server.url()); + let mut app = App::new_with_client(client); let mut terminal = make_terminal(); - let mut app = App::new(); app.sidebar_index = 4; app.toggle_menu(); terminal.draw(|f| ui(f, &mut app)).unwrap(); From af1afd902678cad555f4944c194fd356175d676d Mon Sep 17 00:00:00 2001 From: Raunak Kumar Date: Sat, 2 May 2026 09:59:51 +0000 Subject: [PATCH 2/3] remove unintended file dummy.toml --- dummy.toml | 49 ------------------------------------------------- 1 file changed, 49 deletions(-) delete mode 100644 dummy.toml diff --git a/dummy.toml b/dummy.toml deleted file mode 100644 index bd5642b..0000000 --- a/dummy.toml +++ /dev/null @@ -1,49 +0,0 @@ - -[network] -listen_address = "/ip4/127.0.0.1/tcp/6884" -dial_peers = [] -max_pending_incoming = 10 -max_pending_outgoing = 10 -max_established_incoming = 50 -max_established_outgoing = 50 -max_established_per_peer = 1 -max_workbase_per_second = 10 -max_userworkbase_per_second = 10 -max_miningshare_per_second = 100 -max_inventory_per_second = 100 -max_transaction_per_second = 100 -max_requests_per_second = 100 -dial_timeout_secs = 30 - -[store] -path = "./store.db" -background_task_frequency_hours = 24 -pplns_ttl_days = 7 - -[stratum] -hostname = "pool.example.com" -port = 3333 -start_difficulty = 10000 -minimum_difficulty = 100 -solo_address = "tb1qyazxde6558qj6z3d9np5e6msmrspwpf6k0qggk" -bootstrap_address = "tb1qyazxde6558qj6z3d9np5e6msmrspwpf6k0qggk" -zmqpubhashblock = "tcp://127.0.0.1:28332" -network = "signet" -version_mask = "1fffe000" -difficulty_multiplier = 1.0 -pool_signature = "P2Poolv2" - -[bitcoinrpc] -url = "http://127.0.0.1:38332" -username = "p2pool" -password = "p2pool" - -[logging] -file = "./logs/p2pool.log" -console = true -level = "info" -stats_dir = "./logs/stats" - -[api] -hostname = "127.0.0.1" -port = 46884 From 07b09f9e2d67d063af8981c40f4281c1b441ec0c Mon Sep 17 00:00:00 2001 From: Raunak Kumar Date: Mon, 4 May 2026 09:02:43 +0000 Subject: [PATCH 3/3] feat(p2pool): add async chain_info fetching --- Cargo.lock | 1 + Cargo.toml | 5 +- src/app.rs | 38 +++++++++---- src/components/p2pool_client.rs | 50 ++++++++--------- src/config.rs | 9 ++-- src/main.rs | 5 +- ...pdm__ui__tests__bitcoin_screen_render.snap | 1 - ..._ui__tests__bitcoin_screen_render.snap.new | 52 ------------------ .../pdm__ui__tests__p2pool_screen_render.snap | 1 - ...__ui__tests__p2pool_screen_render.snap.new | 54 ------------------- ...i__tests__p2pool_status_screen_render.snap | 15 +++--- 11 files changed, 74 insertions(+), 157 deletions(-) delete mode 100644 src/snapshots/pdm__ui__tests__bitcoin_screen_render.snap.new delete mode 100644 src/snapshots/pdm__ui__tests__p2pool_screen_render.snap.new diff --git a/Cargo.lock b/Cargo.lock index e767838..9c3d324 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1716,6 +1716,7 @@ dependencies = [ "serde_json", "serial_test", "tempfile", + "tokio", "toml 0.8.23", "toml_edit", "unicode-width", diff --git a/Cargo.toml b/Cargo.toml index d686749..d680fc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,10 +18,11 @@ p2poolv2_config = { git = "https://github.com/p2poolv2/p2poolv2", package = "p2p bitcoin = "0.32.5" toml_edit = "0.22" reqwest = { version = "0.12", features = ["json", "rustls-tls", "blocking"] } -mockito = "1.7.2" -serde_json = "1.0.149" +tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time"] } [dev-dependencies] insta = "1.44.3" serial_test = "3" tempfile = "3" +mockito = "1.7.2" +serde_json = "1.0.149" diff --git a/src/app.rs b/src/app.rs index d315ab6..e7d7550 100644 --- a/src/app.rs +++ b/src/app.rs @@ -11,6 +11,7 @@ use crate::components::settings_view::SettingsView; use crate::settings::Settings; use p2poolv2_config::Config as P2PoolConfig; use std::path::PathBuf; +use tokio::sync::mpsc; /// Sidebar items labels pub const SIDEBAR_ITEMS: &[(&str, CurrentScreen)] = &[ @@ -112,11 +113,15 @@ pub struct App { pub p2pool_status_tab: usize, pub chain_info: Option, pub p2pool_chain_info_error: Option, + // async channel to receive chain info updates from the background task that fetches it when the P2Pool Status screen is opened + pub chain_info_tx: mpsc::UnboundedSender>, + pub chain_info_rx: mpsc::UnboundedReceiver>, } impl App { #[must_use] pub fn new() -> App { + let (tx, rx) = mpsc::unbounded_channel(); App { current_screen: CurrentScreen::Home, sidebar_index: 0, @@ -137,6 +142,8 @@ impl App { p2pool_status_tab: 0, chain_info: None, p2pool_chain_info_error: None, + chain_info_tx: tx, + chain_info_rx: rx, } } @@ -147,15 +154,18 @@ impl App { app } - pub fn refresh_chain_info(&mut self) { - match self.p2pool_client.fetch_chain_info() { - Ok(info) => { - self.chain_info = Some(info); - self.p2pool_chain_info_error = None; - } - Err(e) => { - self.chain_info = None; - self.p2pool_chain_info_error = Some(e.to_string()); + /// Non-blocking result handler + pub fn poll_chain_info(&mut self) { + while let Ok(result) = self.chain_info_rx.try_recv() { + match result { + Ok(info) => { + self.chain_info = Some(info); + self.p2pool_chain_info_error = None; + } + Err(e) => { + self.chain_info = None; + self.p2pool_chain_info_error = Some(e.to_string()); + } } } } @@ -177,7 +187,15 @@ impl App { if let Some(&(_, screen)) = SIDEBAR_ITEMS.get(self.sidebar_index) { self.current_screen = screen; if self.current_screen == CurrentScreen::P2PoolStatus { - self.refresh_chain_info(); + let client = self.p2pool_client.clone(); + let tx = self.chain_info_tx.clone(); + + if let Ok(handle) = tokio::runtime::Handle::try_current() { + handle.spawn(async move { + let res = client.fetch_chain_info().await; + let _ = tx.send(res.map_err(anyhow::Error::from)); + }); + } } } } diff --git a/src/components/p2pool_client.rs b/src/components/p2pool_client.rs index 8f9c898..af89d13 100644 --- a/src/components/p2pool_client.rs +++ b/src/components/p2pool_client.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later use crate::config::load_api_config; -use reqwest::blocking::Client; +use reqwest::Client; use serde::Deserialize; use std::time::Duration; @@ -38,7 +38,7 @@ impl P2PoolClient { let credentials = cfg.auth_user.zip(cfg.auth_pass); (cfg.base_url, credentials) }) - .unwrap_or_else(|_| ("http://localhost:8332".to_string(), None)); + .unwrap_or_else(|_| ("http://localhost:46884".to_string(), None)); Self { client: build_client(), @@ -68,7 +68,7 @@ impl P2PoolClient { self } - pub fn fetch_chain_info(&self) -> Result { + pub async fn fetch_chain_info(&self) -> Result { let url = format!("{}/chain_info", self.base_url); let mut request = self.client.get(url); @@ -76,8 +76,8 @@ impl P2PoolClient { request = request.basic_auth(user, Some(pass)); } - let response = request.send()?.error_for_status()?; - let data = response.json::()?; + let response = request.send().await?.error_for_status()?; + let data = response.json::().await?; Ok(data) } @@ -95,9 +95,9 @@ mod tests { use mockito::Server; use serde_json::json; - #[test] - fn test_fetch_chain_info_success() { - let mut server = Server::new(); + #[tokio::test] + async fn test_fetch_chain_info_success() { + let mut server = Server::new_async().await; let mock = server .mock("GET", "/chain_info") @@ -115,7 +115,7 @@ mod tests { .create(); let client = P2PoolClient::with_base_url(server.url()); - let result = client.fetch_chain_info().unwrap(); + let result = client.fetch_chain_info().await.unwrap(); assert_eq!(result.chain_tip_height, Some(850_000)); assert_eq!( @@ -133,9 +133,9 @@ mod tests { mock.assert(); } - #[test] - fn test_fetch_chain_info_sends_basic_auth() { - let mut server = Server::new(); + #[tokio::test] + async fn test_fetch_chain_info_sends_basic_auth() { + let mut server = Server::new_async().await; let mock = server .mock("GET", "/chain_info") @@ -148,23 +148,23 @@ mod tests { let client = P2PoolClient::with_base_url(server.url()).with_auth("user".into(), "password".into()); - client.fetch_chain_info().unwrap(); + client.fetch_chain_info().await.unwrap(); mock.assert(); } - #[test] - fn test_fetch_chain_info_errors_on_http_500() { - let mut server = Server::new(); + #[tokio::test] + async fn test_fetch_chain_info_errors_on_http_500() { + let mut server = Server::new_async().await; server.mock("GET", "/chain_info").with_status(500).create(); let client = P2PoolClient::with_base_url(server.url()); - assert!(client.fetch_chain_info().is_err()); + assert!(client.fetch_chain_info().await.is_err()); } - #[test] - fn test_fetch_chain_info_returns_error_on_missing_required_field() { - let mut server = Server::new(); + #[tokio::test] + async fn test_fetch_chain_info_returns_error_on_missing_required_field() { + let mut server = Server::new_async().await; server .mock("GET", "/chain_info") @@ -174,12 +174,12 @@ mod tests { .create(); let client = P2PoolClient::with_base_url(server.url()); - assert!(client.fetch_chain_info().is_err()); + assert!(client.fetch_chain_info().await.is_err()); } - #[test] - fn test_with_client_can_be_injected_for_isolated_tests() { - let mut server = Server::new(); + #[tokio::test] + async fn test_with_client_can_be_injected_for_isolated_tests() { + let mut server = Server::new_async().await; let mock = server .mock("GET", "/chain_info") @@ -197,7 +197,7 @@ mod tests { .create(); let client = P2PoolClient::with_client(build_client(), server.url()); - let result = client.fetch_chain_info().unwrap(); + let result = client.fetch_chain_info().await.unwrap(); assert_eq!(result.chain_tip_height, Some(1)); assert_eq!(result.total_work, "abc"); diff --git a/src/config.rs b/src/config.rs index 606604b..fd56aa1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,11 +13,14 @@ pub struct ApiConfig { pub fn load_api_config() -> Result { let settings = Config::builder() - .add_source(File::with_name("config/config")) + .add_source(File::with_name("config/config").required(false)) + .add_source( + File::with_name(concat!(env!("CARGO_MANIFEST_DIR"), "/config/config")).required(false), + ) .build()?; - let host: String = settings.get("api.host")?; - let port: u16 = settings.get("api.port")?; + let host: String = settings.get("api.host").unwrap_or("127.0.0.1".into()); + let port: u16 = settings.get("api.port").unwrap_or(9332); let auth_user: Option = settings.get("api.auth_user").ok(); let auth_pass: Option = settings.get("api.auth_pass").ok(); diff --git a/src/main.rs b/src/main.rs index 30b59b4..76b9060 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,7 +24,8 @@ use crossterm::{ use ratatui::{Terminal, backend::Backend, backend::CrosstermBackend}; use std::io; -fn main() -> Result<()> { +#[tokio::main] +async fn main() -> Result<()> { // Setup Terminal enable_raw_mode()?; let mut stdout = io::stdout(); @@ -36,7 +37,6 @@ fn main() -> Result<()> { let mut app = App::new(); app.settings = load_settings(); bootstrap_from_settings(&mut app); - app.refresh_chain_info(); let res = run_app(&mut terminal, &mut app); // Restore Terminal @@ -70,6 +70,7 @@ where ::Error: Send + Sync + 'static, { loop { + app.poll_chain_info(); terminal.draw(|f| ui::ui(f, app))?; if let Event::Key(key) = event::read()? { diff --git a/src/snapshots/pdm__ui__tests__bitcoin_screen_render.snap b/src/snapshots/pdm__ui__tests__bitcoin_screen_render.snap index ea4c542..4463830 100644 --- a/src/snapshots/pdm__ui__tests__bitcoin_screen_render.snap +++ b/src/snapshots/pdm__ui__tests__bitcoin_screen_render.snap @@ -1,6 +1,5 @@ --- source: src/ui.rs -assertion_line: 409 expression: terminal.backend() --- TestBackend { diff --git a/src/snapshots/pdm__ui__tests__bitcoin_screen_render.snap.new b/src/snapshots/pdm__ui__tests__bitcoin_screen_render.snap.new deleted file mode 100644 index ea4c542..0000000 --- a/src/snapshots/pdm__ui__tests__bitcoin_screen_render.snap.new +++ /dev/null @@ -1,52 +0,0 @@ ---- -source: src/ui.rs -assertion_line: 409 -expression: terminal.backend() ---- -TestBackend { - buffer: Buffer { - area: Rect { x: 0, y: 0, width: 80, height: 20 }, - content: [ - "┌ PDM ──────────────────┐┌ Bitcoin Config ─────────────────────────────────────┐", - "│Home ││Press [Enter] to select a bitcoin.conf file │", - "│Bitcoin Config ││ │", - "│Bitcoin Status ││ │", - "│P2Pool Config ││ │", - "│P2Pool Status ││ │", - "│LN Config ││ │", - "│LN Status ││ │", - "│Shares Market ││ │", - "│ ││ │", - "│ ││ │", - "│ ││ │", - "│ ││ │", - "│ ││ │", - "│ ││ │", - "│ ││ │", - "│ ││ │", - "│ ││ │", - "└───────────────────────┘└─────────────────────────────────────────────────────┘", - " ↑↓ Navigate sidebar Enter Select q Quit ", - ], - styles: [ - x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE, - x: 1, y: 2, fg: Black, bg: Gray, underline: Reset, modifier: NONE, - x: 24, y: 2, fg: Reset, bg: Reset, underline: Reset, modifier: NONE, - x: 0, y: 19, fg: White, bg: DarkGray, underline: Reset, modifier: NONE, - x: 4, y: 19, fg: DarkGray, bg: Black, underline: Reset, modifier: NONE, - x: 23, y: 19, fg: White, bg: DarkGray, underline: Reset, modifier: NONE, - x: 30, y: 19, fg: DarkGray, bg: Black, underline: Reset, modifier: NONE, - x: 39, y: 19, fg: White, bg: DarkGray, underline: Reset, modifier: NONE, - x: 42, y: 19, fg: DarkGray, bg: Black, underline: Reset, modifier: NONE, - x: 49, y: 19, fg: Reset, bg: Black, underline: Reset, modifier: NONE, - ] - }, - scrollback: Buffer { - area: Rect { x: 0, y: 0, width: 80, height: 0 } - }, - cursor: false, - pos: ( - 0, - 0, - ), -} diff --git a/src/snapshots/pdm__ui__tests__p2pool_screen_render.snap b/src/snapshots/pdm__ui__tests__p2pool_screen_render.snap index cea0888..2facef3 100644 --- a/src/snapshots/pdm__ui__tests__p2pool_screen_render.snap +++ b/src/snapshots/pdm__ui__tests__p2pool_screen_render.snap @@ -1,6 +1,5 @@ --- source: src/ui.rs -assertion_line: 423 expression: terminal.backend() --- TestBackend { diff --git a/src/snapshots/pdm__ui__tests__p2pool_screen_render.snap.new b/src/snapshots/pdm__ui__tests__p2pool_screen_render.snap.new deleted file mode 100644 index cea0888..0000000 --- a/src/snapshots/pdm__ui__tests__p2pool_screen_render.snap.new +++ /dev/null @@ -1,54 +0,0 @@ ---- -source: src/ui.rs -assertion_line: 423 -expression: terminal.backend() ---- -TestBackend { - buffer: Buffer { - area: Rect { x: 0, y: 0, width: 80, height: 20 }, - content: [ - "┌ PDM ──────────────────┐┌ Info ───────────────────────────────────────────────┐", - "│Home ││ Chain Info │ System │ Logs │ Peers │", - "│Bitcoin Config ││ │", - "│Bitcoin Status │└─────────────────────────────────────────────────────┘", - "│P2Pool Config │┌─────────────────────────────────────────────────────┐", - "│P2Pool Status ││Chain Info │", - "│LN Config ││ │", - "│LN Status ││ │", - "│Shares Market ││ │", - "│ ││ │", - "│ ││ │", - "│ ││ │", - "│ ││ │", - "│ ││ │", - "│ ││ │", - "│ ││ │", - "│ ││ │", - "│ ││ │", - "└───────────────────────┘└─────────────────────────────────────────────────────┘", - " ↑↓ Navigate sidebar ←→ Switch tab q Quit ", - ], - styles: [ - x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE, - x: 27, y: 1, fg: Black, bg: Gray, underline: Reset, modifier: NONE, - x: 37, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: NONE, - x: 1, y: 3, fg: Black, bg: Gray, underline: Reset, modifier: NONE, - x: 24, y: 3, fg: Reset, bg: Reset, underline: Reset, modifier: NONE, - x: 0, y: 19, fg: White, bg: DarkGray, underline: Reset, modifier: NONE, - x: 4, y: 19, fg: DarkGray, bg: Black, underline: Reset, modifier: NONE, - x: 23, y: 19, fg: White, bg: DarkGray, underline: Reset, modifier: NONE, - x: 27, y: 19, fg: DarkGray, bg: Black, underline: Reset, modifier: NONE, - x: 40, y: 19, fg: White, bg: DarkGray, underline: Reset, modifier: NONE, - x: 43, y: 19, fg: DarkGray, bg: Black, underline: Reset, modifier: NONE, - x: 50, y: 19, fg: Reset, bg: Black, underline: Reset, modifier: NONE, - ] - }, - scrollback: Buffer { - area: Rect { x: 0, y: 0, width: 80, height: 0 } - }, - cursor: false, - pos: ( - 0, - 0, - ), -} diff --git a/src/snapshots/pdm__ui__tests__p2pool_status_screen_render.snap b/src/snapshots/pdm__ui__tests__p2pool_status_screen_render.snap index 4ff5c3e..77cbcf1 100644 --- a/src/snapshots/pdm__ui__tests__p2pool_status_screen_render.snap +++ b/src/snapshots/pdm__ui__tests__p2pool_status_screen_render.snap @@ -1,6 +1,5 @@ --- source: src/ui.rs -assertion_line: 219 expression: terminal.backend() --- TestBackend { @@ -12,12 +11,12 @@ TestBackend { "│Bitcoin Config ││ │", "│Bitcoin Status │└─────────────────────────────────────────────────────┘", "│P2Pool Config │┌ Chain Info ─────────────────────────────────────────┐", - "│P2Pool Status ││Genesis Blockhash : - │", - "│LN Config ││Chain Tip Height : 1 │", - "│LN Status ││Chain Tip Blockhash : - │", - "│Shares Market ││Total Work : │", - "│Settings ││fffffffffffffffffffffffffffffffffffffffffffffffffffff│", - "│ ││fff │", + "│P2Pool Status ││Loading chain info... │", + "│LN Config ││ │", + "│LN Status ││ │", + "│Shares Market ││ │", + "│Settings ││ │", + "│ ││ │", "│ ││ │", "│ ││ │", "│ ││ │", @@ -38,6 +37,8 @@ TestBackend { x: 37, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: NONE, x: 1, y: 5, fg: Black, bg: Gray, underline: Reset, modifier: NONE, x: 24, y: 5, fg: Reset, bg: Reset, underline: Reset, modifier: NONE, + x: 26, y: 5, fg: DarkGray, bg: Reset, underline: Reset, modifier: NONE, + x: 47, y: 5, fg: Reset, bg: Reset, underline: Reset, modifier: NONE, x: 0, y: 23, fg: White, bg: DarkGray, underline: Reset, modifier: NONE, x: 4, y: 23, fg: DarkGray, bg: Black, underline: Reset, modifier: NONE, x: 23, y: 23, fg: White, bg: DarkGray, underline: Reset, modifier: NONE,