diff --git a/Cargo.toml b/Cargo.toml index 17614b9..19f514e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "ghost-eye" authors = ["str3tch "] description = "Application for interacting with Casper/Ghost nodes that are exposing RPC only to the localhost" -version = "0.3.63" +version = "0.3.64" edition = "2021" homepage = "https://git.ghostchain.io/ghostchain" repository = "https://git.ghostchain.io/ghostchain/ghost-eye" diff --git a/src/action.rs b/src/action.rs index 7c179c7..f59b075 100644 --- a/src/action.rs +++ b/src/action.rs @@ -7,7 +7,7 @@ use subxt::config::substrate::DigestItem; use crate::types::{ ActionLevel, ActionTarget, CasperExtrinsicDetails, EraInfo, EraRewardPoints, Nominator, Nominations, PeerInformation, SessionKeyInfo, UnlockChunk, SystemAccount, - RewardDestination, + RewardDestination, Gatekeeper, BlockRange, }; #[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)] @@ -120,9 +120,11 @@ pub enum Action { SetGenesisHash(Option), SetChainName(Option), SetChainVersion(Option), + SetBlockRange(u64, BlockRange), SetStashAccount([u8; 32]), SetStashSecret([u8; 32]), SetChoosenValidator([u8; 32], u32, u32), + SetChoosenGatekeeper(u64), SetSlashingSpansLength(usize, [u8; 32]), BestBlockInformation(H256, u32), @@ -153,8 +155,11 @@ pub enum Action { GetTotalIssuance, GetExistentialDeposit, GetMinValidatorBond, + GetGatekeepedNetwork(u64), + GetBlockRange, SetExistentialDeposit(u128), SetMinValidatorBond(u128), + SetGatekeepedNetwork(Gatekeeper), SetTotalIssuance(Option), } diff --git a/src/app.rs b/src/app.rs index b1afff7..17e38fb 100644 --- a/src/app.rs +++ b/src/app.rs @@ -150,6 +150,7 @@ impl App { self.network_tx.send(Action::GetPendingExtrinsics)?; self.network_tx.send(Action::GetConnectedPeers)?; self.network_tx.send(Action::CheckPendingTransactions)?; + self.network_tx.send(Action::GetBlockRange)?; Ok(()) } diff --git a/src/components/validator/gatekeeper_details.rs b/src/components/validator/gatekeeper_details.rs new file mode 100644 index 0000000..169a6e5 --- /dev/null +++ b/src/components/validator/gatekeeper_details.rs @@ -0,0 +1,126 @@ +use std::collections::HashMap; +use color_eyre::Result; +use ratatui::layout::Constraint; +use ratatui::{ + text::Text, + layout::{Alignment, Rect}, + widgets::{Block, Cell, Row, Table}, + Frame +}; + +use super::{PartialComponent, Component, CurrentTab}; +use crate::types::BlockRange; +use crate::{ + action::Action, + config::Config, + palette::StylePalette, +}; + +#[derive(Debug, Default, Clone)] +struct Details { + incoming_fee: String, + outgoing_fee: String, + gatekeeper: String, +} + +#[derive(Default)] +pub struct GatekeeperDetails { + palette: StylePalette, + gatekeeper_details: HashMap, + block_ranges: HashMap, + selected_chain_id: u64, +} + +impl PartialComponent for GatekeeperDetails { + fn set_active(&mut self, _current_tab: CurrentTab) { } +} + +impl Component for GatekeeperDetails { + fn register_config_handler(&mut self, config: Config) -> Result<()> { + if let Some(style) = config.styles.get(&crate::app::Mode::Validator) { + self.palette.with_normal_style(style.get("normal_style").copied()); + self.palette.with_hover_style(style.get("hover_style").copied()); + self.palette.with_normal_border_style(style.get("normal_border_style").copied()); + self.palette.with_hover_border_style(style.get("hover_border_style").copied()); + self.palette.with_normal_title_style(style.get("normal_title_style").copied()); + self.palette.with_hover_title_style(style.get("hover_title_style").copied()); + self.palette.with_highlight_style(style.get("highlight_style").copied()); + self.palette.with_scrollbar_style(style.get("scrollbar_style").copied()); + } + Ok(()) + } + + fn update(&mut self, action: Action) -> Result> { + match action { + Action::SetChoosenGatekeeper(chain_id) => self.selected_chain_id = chain_id, + Action::SetBlockRange(chain_id, block_range) => { + let _ = self.block_ranges.insert(chain_id, block_range); + }, + Action::SetGatekeepedNetwork(network) => { + self.gatekeeper_details.insert(network.chain_id, Details { + incoming_fee: format!("{:.5}%", network.incoming_fee as f64 / 1_000_000_000.0), + outgoing_fee: format!("{:.5}%", network.outgoing_fee as f64 / 1_000_000_000.0), + gatekeeper: network.gatekeeper, + }); + } + _ => {} + }; + Ok(None) + } + + fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { + let [_, place] = super::validator_gatekeeped_networks_layout(area); + let (border_style, border_type) = self.palette.create_border_style(false); + + let current_gatekeeper_details = self.gatekeeper_details + .get(&self.selected_chain_id) + .cloned() + .unwrap_or_default(); + + let current_block_range = self.block_ranges + .get(&self.selected_chain_id) + .copied() + .unwrap_or_default(); + + let table = Table::new( + vec![ + Row::new(vec![ + Cell::from(Text::from("From block".to_string()).alignment(Alignment::Left)), + Cell::from(Text::from(current_block_range.from_block.to_string()).alignment(Alignment::Right)), + ]), + Row::new(vec![ + Cell::from(Text::from("To block".to_string()).alignment(Alignment::Left)), + Cell::from(Text::from(current_block_range.to_block.to_string()).alignment(Alignment::Right)), + ]), + Row::new(vec![ + Cell::from(Text::from("Incoming fee".to_string()).alignment(Alignment::Left)), + Cell::from(Text::from(current_gatekeeper_details.incoming_fee).alignment(Alignment::Right)), + ]), + Row::new(vec![ + Cell::from(Text::from("Outgoing fee".to_string()).alignment(Alignment::Left)), + Cell::from(Text::from(current_gatekeeper_details.outgoing_fee).alignment(Alignment::Right)), + ]), + Row::new(vec![ + Cell::from(Text::from("Gatekeeper".to_string()).alignment(Alignment::Left)), + Cell::from(Text::from(current_gatekeeper_details.gatekeeper).alignment(Alignment::Right)), + ]), + ], + [ + Constraint::Length(12), + Constraint::Fill(1), + ], + ) + .highlight_style(self.palette.create_highlight_style()) + .column_spacing(1) + .block(Block::bordered() + .border_style(border_style) + .border_type(border_type) + .title_alignment(Alignment::Right) + .title_style(self.palette.create_title_style(false)) + .title(format!("Chain ID: {}", self.selected_chain_id))); + + frame.render_widget(table, place); + + Ok(()) + } +} diff --git a/src/components/validator/gatekeepers.rs b/src/components/validator/gatekeepers.rs new file mode 100644 index 0000000..e1fefd3 --- /dev/null +++ b/src/components/validator/gatekeepers.rs @@ -0,0 +1,235 @@ +use std::collections::HashMap; +use color_eyre::Result; +use crossterm::event::{KeyCode, KeyEvent}; +use ratatui::layout::Margin; +use ratatui::{ + layout::{Alignment, Rect}, + widgets::{ + Block, List, ListState, ListItem, Scrollbar, + ScrollbarOrientation, ScrollbarState, + }, + Frame +}; +use tokio::sync::mpsc::UnboundedSender; + +use super::{PartialComponent, Component, CurrentTab}; +use crate::types::Gatekeeper; +use crate::{ + action::Action, + config::Config, + palette::StylePalette, +}; + +struct NetworkDetails { + chain_id: u64, + chain_name: String, + chain_type: String, +} + +pub struct Gatekeepers { + is_active: bool, + action_tx: Option>, + palette: StylePalette, + scroll_state: ScrollbarState, + list_state: ListState, + gatekeepers: Vec, + chain_ids: HashMap, +} + +impl Default for Gatekeepers { + fn default() -> Self { + Self::new() + } +} + +impl Gatekeepers { + pub fn new() -> Self { + Self { + is_active: false, + action_tx: None, + scroll_state: ScrollbarState::new(0), + list_state: ListState::default(), + gatekeepers: Vec::new(), + palette: StylePalette::default(), + chain_ids: Default::default(), + } + } + + fn change_choosen_gatekeeper(&mut self) { + if let Some(action_tx) = &self.action_tx { + if let Some(chain_id) = self.list_state + .selected() + .map(|index| self.gatekeepers + .get(index) + .map(|data| data.chain_id) + ) + .flatten() + { + let _ = action_tx.send(Action::SetChoosenGatekeeper(chain_id)); + } + } + } + + fn update_gatekeeped_network(&mut self, network: Gatekeeper) { + if let Some(index) = self.chain_ids.get(&network.chain_id) { + self.gatekeepers[*index] = NetworkDetails { + chain_id: network.chain_id, + chain_name: network.chain_name, + chain_type: network.chain_type, + }; + } else { + let position = self.gatekeepers.len(); + self.chain_ids.insert(network.chain_id, position); + self.gatekeepers.push(NetworkDetails { + chain_id: network.chain_id, + chain_name: network.chain_name, + chain_type: network.chain_type, + }); + + if position == 0 { + self.first_row(); + } + } + } + + fn first_row(&mut self) { + if self.gatekeepers.len() > 0 { + self.list_state.select(Some(0)); + self.scroll_state = self.scroll_state.position(0); + } + self.change_choosen_gatekeeper(); + } + + fn next_row(&mut self) { + let i = match self.list_state.selected() { + Some(i) => { + if i >= self.gatekeepers.len() - 1 { + i + } else { + i + 1 + } + }, + None => 0, + }; + self.list_state.select(Some(i)); + self.scroll_state = self.scroll_state.position(i); + self.change_choosen_gatekeeper(); + } + + fn last_row(&mut self) { + if self.gatekeepers.len() > 0 { + let last = self.gatekeepers.len() - 1; + self.list_state.select(Some(last)); + self.scroll_state = self.scroll_state.position(last); + } + self.change_choosen_gatekeeper(); + } + + fn previous_row(&mut self) { + let i = match self.list_state.selected() { + Some(i) => { + if i == 0 { + 0 + } else { + i - 1 + } + }, + None => 0 + }; + self.list_state.select(Some(i)); + self.scroll_state = self.scroll_state.position(i); + self.change_choosen_gatekeeper(); + } +} + +impl PartialComponent for Gatekeepers { + fn set_active(&mut self, current_tab: CurrentTab) { + match current_tab { + CurrentTab::Gatekeepers => self.is_active = true, + _ => { + self.is_active = false; + self.list_state.select(None); + self.scroll_state = self.scroll_state.position(0); + } + } + } +} + +impl Component for Gatekeepers { + fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { + self.action_tx = Some(tx); + Ok(()) + } + + fn register_config_handler(&mut self, config: Config) -> Result<()> { + if let Some(style) = config.styles.get(&crate::app::Mode::Validator) { + self.palette.with_normal_style(style.get("normal_style").copied()); + self.palette.with_hover_style(style.get("hover_style").copied()); + self.palette.with_normal_border_style(style.get("normal_border_style").copied()); + self.palette.with_hover_border_style(style.get("hover_border_style").copied()); + self.palette.with_normal_title_style(style.get("normal_title_style").copied()); + self.palette.with_hover_title_style(style.get("hover_title_style").copied()); + self.palette.with_highlight_style(style.get("highlight_style").copied()); + self.palette.with_scrollbar_style(style.get("scrollbar_style").copied()); + } + Ok(()) + } + + fn update(&mut self, action: Action) -> Result> { + match action { + Action::SetGatekeepedNetwork(network) => + self.update_gatekeeped_network(network), + _ => {} + }; + Ok(None) + } + + fn handle_key_event(&mut self, key: KeyEvent) -> Result> { + if self.is_active { + match key.code { + KeyCode::Up | KeyCode::Char('k') => self.previous_row(), + KeyCode::Down | KeyCode::Char('j') => self.next_row(), + KeyCode::Char('g') => self.first_row(), + KeyCode::Char('G') => self.last_row(), + _ => {}, + }; + } + Ok(None) + } + + fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { + let [place, _] = super::validator_gatekeeped_networks_layout(area); + let (border_style, border_type) = self.palette.create_border_style(self.is_active); + let list = List::new( + self.gatekeepers + .iter() + .map(|network| ListItem::new(format!("{} ({}) | {}", + network.chain_name, + network.chain_id, + network.chain_type)) + ) + ) + .highlight_style(self.palette.create_highlight_style()) + .block(Block::bordered() + .border_style(border_style) + .border_type(border_type) + .title_alignment(Alignment::Right) + .title_style(self.palette.create_title_style(false)) + .title("Gatekeeped Networks")); + + let scrollbar = Scrollbar::default() + .orientation(ScrollbarOrientation::VerticalRight) + .begin_symbol(None) + .end_symbol(None) + .style(self.palette.create_scrollbar_style()); + + frame.render_stateful_widget(list, place, &mut self.list_state); + frame.render_stateful_widget( + scrollbar, + place.inner(Margin { vertical: 1, horizontal: 1 }), + &mut self.scroll_state, + ); + + Ok(()) + } +} diff --git a/src/components/validator/listen_addresses.rs b/src/components/validator/listen_addresses.rs index 3bc745c..5f3f421 100644 --- a/src/components/validator/listen_addresses.rs +++ b/src/components/validator/listen_addresses.rs @@ -150,7 +150,7 @@ impl Component for ListenAddresses { } fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { - let [_, place] = super::validator_session_and_listen_layout(area); + let [_, place, _] = super::validator_session_and_listen_layout(area); let (border_style, border_type) = self.palette.create_border_style(self.is_active); let list = List::new( self.listen_addresses diff --git a/src/components/validator/mod.rs b/src/components/validator/mod.rs index 0ae731c..6bf83e8 100644 --- a/src/components/validator/mod.rs +++ b/src/components/validator/mod.rs @@ -16,6 +16,8 @@ mod peers; mod stash_info; mod nominators; mod listen_addresses; +mod gatekeepers; +mod gatekeeper_details; mod history; mod withdrawals; mod stash_details; @@ -39,6 +41,8 @@ use event_log::EventLogs; use peers::Peers; use stash_info::StashInfo; use listen_addresses::ListenAddresses; +use gatekeepers::Gatekeepers; +use gatekeeper_details::GatekeeperDetails; use nominators::NominatorsByValidator; use history::History; use withdrawals::Withdrawals; @@ -57,6 +61,7 @@ use payee_popup::PayeePopup; pub enum CurrentTab { Nothing, ListenAddresses, + Gatekeepers, NominatorsByValidator, History, Withdrawals, @@ -97,10 +102,12 @@ impl Default for Validator { Box::new(StashDetails::default()), Box::new(StakingDetails::default()), Box::new(RewardDetails::default()), + Box::new(GatekeeperDetails::default()), Box::new(History::default()), Box::new(Withdrawals::default()), Box::new(Peers::default()), Box::new(ListenAddresses::default()), + Box::new(Gatekeepers::default()), Box::new(EventLogs::default()), Box::new(BondPopup::default()), Box::new(PayoutPopup::default()), @@ -124,14 +131,16 @@ impl Validator { CurrentTab::Peers => self.current_tab = CurrentTab::Withdrawals, CurrentTab::Withdrawals => self.current_tab = CurrentTab::History, CurrentTab::History => self.current_tab = CurrentTab::NominatorsByValidator, - CurrentTab::ListenAddresses => self.current_tab = CurrentTab::NominatorsByValidator, + CurrentTab::NominatorsByValidator => self.current_tab = CurrentTab::Gatekeepers, + CurrentTab::ListenAddresses => self.current_tab = CurrentTab::Gatekeepers, _ => {} } } fn move_right(&mut self) { match self.current_tab { - CurrentTab::ListenAddresses => self.current_tab = CurrentTab::NominatorsByValidator, + CurrentTab::ListenAddresses => self.current_tab = CurrentTab::Gatekeepers, + CurrentTab::Gatekeepers => self.current_tab = CurrentTab::NominatorsByValidator, CurrentTab::Nothing => self.current_tab = CurrentTab::NominatorsByValidator, CurrentTab::NominatorsByValidator => self.current_tab = CurrentTab::History, CurrentTab::History => self.current_tab = CurrentTab::Withdrawals, @@ -278,7 +287,7 @@ impl Component for Validator { pub fn validator_layout(area: Rect) -> [Rect; 4] { Layout::vertical([ - Constraint::Length(16), + Constraint::Length(19), Constraint::Fill(1), Constraint::Fill(1), Constraint::Percentage(25), @@ -293,14 +302,23 @@ pub fn validator_details_layout(area: Rect) -> [Rect; 2] { ]).areas(place) } -pub fn validator_session_and_listen_layout(area: Rect) -> [Rect; 2] { +pub fn validator_session_and_listen_layout(area: Rect) -> [Rect; 3] { let [_, place] = validator_details_layout(area); Layout::vertical([ + Constraint::Length(6), Constraint::Length(6), Constraint::Fill(1), ]).areas(place) } +pub fn validator_gatekeeped_networks_layout(area: Rect) -> [Rect; 2] { + let [_, _, place] = validator_session_and_listen_layout(area); + Layout::horizontal([ + Constraint::Fill(1), + Constraint::Length(57), + ]).areas(place) +} + pub fn validator_statistics_layout(area: Rect) -> [Rect; 3] { let [_, place, _, _] = validator_layout(area); Layout::horizontal([ @@ -314,7 +332,7 @@ pub fn validator_balance_layout(area: Rect) -> [Rect; 3] { let [place, _] = validator_details_layout(area); Layout::vertical([ Constraint::Length(6), - Constraint::Length(5), - Constraint::Length(5), + Constraint::Length(6), + Constraint::Length(9), ]).areas(place) } diff --git a/src/components/validator/stash_info.rs b/src/components/validator/stash_info.rs index 039fb71..85ec760 100644 --- a/src/components/validator/stash_info.rs +++ b/src/components/validator/stash_info.rs @@ -217,7 +217,7 @@ impl Component for StashInfo { } fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { - let [place, _] = super::validator_session_and_listen_layout(area); + let [place, _, _] = super::validator_session_and_listen_layout(area); let (border_style, border_type) = self.palette.create_border_style(self.is_active); let table = Table::new( self.key_names diff --git a/src/network/legacy_rpc_calls.rs b/src/network/legacy_rpc_calls.rs index 81e6960..42afafa 100644 --- a/src/network/legacy_rpc_calls.rs +++ b/src/network/legacy_rpc_calls.rs @@ -1,8 +1,9 @@ use tokio::sync::mpsc::UnboundedSender; use color_eyre::Result; use subxt::{backend::{legacy::rpc_methods::SystemHealth, rpc::RpcClient}, rpc_params}; +use codec::{Encode, Decode}; -use crate::{action::Action, types::PeerInformation}; +use crate::{action::Action, network::miscellaneous::get_slow_clap_storage_key, types::{BlockRange, PeerInformation}}; pub async fn get_node_name( action_tx: &UnboundedSender, @@ -132,3 +133,31 @@ pub async fn rotate_keys( action_tx.send(Action::StoreRotatedKeys(rotated_keys))?; Ok(()) } + +pub async fn get_block_range( + action_tx: &UnboundedSender, + rpc_client: &RpcClient, + chain_id: u64 +) -> Result<()> { + let chain_id_encoded = chain_id.encode(); + let block_range_key_raw = get_slow_clap_storage_key(b"block-", &chain_id_encoded); + let mut block_range_key = String::from("0x"); + for byte in block_range_key_raw { + block_range_key.push_str(&format!("{:02x}", byte)); + } + let block_range: BlockRange = rpc_client + .request("offchain_localStorageGet", rpc_params!["PERSISTENT", block_range_key]) + .await + .ok() + .map(|hex_string: String| { + let bytes = hex::decode(&hex_string[2..]).expect("Invalid hex string"); + let mut cursor = &bytes[..]; + let from_block: u64 = u64::decode(&mut cursor).expect("first valid"); + let to_block: u64 = u64::decode(&mut cursor).expect("second valid"); + BlockRange { from_block, to_block } + }) + .unwrap_or_default(); + + action_tx.send(Action::SetBlockRange(chain_id, block_range))?; + Ok(()) +} diff --git a/src/network/miscellaneous.rs b/src/network/miscellaneous.rs index 173e368..fba1874 100644 --- a/src/network/miscellaneous.rs +++ b/src/network/miscellaneous.rs @@ -1,5 +1,7 @@ use subxt::ext::sp_runtime::Perbill; +const SLOW_CLAP_DB_PREFIX: &[u8] = b"slow_clap::"; + // generated outside, based on params // MIN_INFLATION: u32 = 0_006_900; // MAX_INFLATION: u32 = 0_690_000; @@ -112,3 +114,10 @@ pub fn prepare_perbill_fraction_string(value: Perbill) -> String { format!("{}.{:02}%", units, rest / m) } + +pub fn get_slow_clap_storage_key(first: &[u8], second: &[u8]) -> Vec { + let mut key = SLOW_CLAP_DB_PREFIX.to_vec(); + key.extend(first); + key.extend(second); + key +} diff --git a/src/network/mod.rs b/src/network/mod.rs index 21a4e31..a6e63c8 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -21,6 +21,10 @@ use crate::{ pub use subscriptions::{FinalizedSubscription, BestSubscription}; +const GATEKEEPED_CHAIN_IDS: [u64; 1] = [ + 11155111, //Sepolia +]; + struct TxToWatch { tx_progress: TxProgress>, sender: String, @@ -186,12 +190,20 @@ impl Network { Action::GetListenAddresses => legacy_rpc_calls::get_listen_addresses(&self.action_tx, &self.rpc_client).await, Action::GetLocalIdentity => legacy_rpc_calls::get_local_identity(&self.action_tx, &self.rpc_client).await, Action::RotateSessionKeys => legacy_rpc_calls::rotate_keys(&self.action_tx, &self.rpc_client).await, + Action::GetBlockRange => { + for chain_id in GATEKEEPED_CHAIN_IDS { + legacy_rpc_calls::get_block_range(&self.action_tx, &self.rpc_client, chain_id).await?; + } + Ok(()) + } Action::GetBlockAuthor(hash, logs) => predefined_calls::get_block_author(&self.action_tx, &self.online_client_api, &logs, &hash).await, Action::GetActiveEra => predefined_calls::get_active_era(&self.action_tx, &self.online_client_api).await, Action::GetCurrentEra => predefined_calls::get_current_era(&self.action_tx, &self.online_client_api).await, Action::GetEpochProgress => predefined_calls::get_epoch_progress(&self.action_tx, &self.online_client_api).await, Action::GetMinValidatorBond => predefined_calls::get_minimal_validator_bond(&self.action_tx, &self.online_client_api).await, + Action::GetGatekeepedNetwork(chain_id) => predefined_calls::get_gatekeeped_network(&self.action_tx, &self.online_client_api, chain_id).await, + Action::GetExistentialDeposit => predefined_calls::get_existential_deposit(&self.action_tx, &self.online_client_api).await, Action::GetTotalIssuance => predefined_calls::get_total_issuance(&self.action_tx, &self.online_client_api).await, diff --git a/src/network/predefined_calls.rs b/src/network/predefined_calls.rs index 9f39172..542b7d4 100644 --- a/src/network/predefined_calls.rs +++ b/src/network/predefined_calls.rs @@ -13,8 +13,8 @@ use subxt::{ use crate::{ action::Action, - casper_network::runtime_types::{pallet_staking::RewardDestination, sp_consensus_slots}, - types::{EraInfo, EraRewardPoints, Nominator, Nominations, SessionKeyInfo, SystemAccount, UnlockChunk}, + casper_network::runtime_types::{ghost_networks::NetworkType, pallet_staking::RewardDestination, sp_consensus_slots}, + types::{EraInfo, EraRewardPoints, Gatekeeper, Nominations, Nominator, SessionKeyInfo, SystemAccount, UnlockChunk}, CasperAccountId, CasperConfig }; @@ -644,3 +644,29 @@ pub async fn get_account_payee( action_tx.send(Action::SetStakingPayee(payee, *account_id))?; Ok(()) } + +pub async fn get_gatekeeped_network( + action_tx: &UnboundedSender, + api: &OnlineClient, + chain_id: u64, +) -> Result<()> { + let gatekeeped_network = super::raw_calls::networks::networks(api, None, chain_id) + .await? + .map(|network| Gatekeeper { + chain_id, + chain_name: String::from_utf8_lossy(&network.chain_name) + .to_string(), + chain_type: match network.network_type { + NetworkType::Evm => String::from("EVM"), + NetworkType::Utxo => String::from("UTXO"), + NetworkType::Undefined => String::from("???"), + }, + gatekeeper: String::from_utf8_lossy(&network.gatekeeper) + .to_string(), + incoming_fee: network.incoming_fee, + outgoing_fee: network.outgoing_fee, + }) + .unwrap_or_default(); + action_tx.send(Action::SetGatekeepedNetwork(gatekeeped_network))?; + Ok(()) +} diff --git a/src/network/raw_calls/networks.rs b/src/network/raw_calls/networks.rs index c4c05ca..4813204 100644 --- a/src/network/raw_calls/networks.rs +++ b/src/network/raw_calls/networks.rs @@ -7,7 +7,7 @@ use subxt::{ use crate::{ casper_network::{ self, - runtime_types::ghost_networks::BridgeAdjustment, + runtime_types::ghost_networks::{BridgeAdjustment, NetworkData}, }, CasperConfig, }; @@ -29,3 +29,13 @@ pub async fn accumulated_commission( let maybe_accumulated_commission = super::do_storage_call(online_client, &storage_key, at_hash).await?; Ok(maybe_accumulated_commission) } + +pub async fn networks( + online_client: &OnlineClient, + at_hash: Option<&H256>, + chain_id: u64, +) -> Result> { + let storage_key = casper_network::storage().ghost_networks().networks(chain_id); + let maybe_network = super::do_storage_call(online_client, &storage_key, at_hash).await?; + Ok(maybe_network) +} diff --git a/src/network/subscriptions.rs b/src/network/subscriptions.rs index 3fe77f8..6c8646a 100644 --- a/src/network/subscriptions.rs +++ b/src/network/subscriptions.rs @@ -1,6 +1,12 @@ -use crate::{types::CasperExtrinsicDetails, action::Action, casper::CasperBlock}; +use crate::{ + types::CasperExtrinsicDetails, + action::Action, + casper::CasperBlock, +}; use color_eyre::Result; +use super::GATEKEEPED_CHAIN_IDS; + pub struct FinalizedSubscription { action_tx: tokio::sync::mpsc::UnboundedSender, network_tx: std::sync::mpsc::Sender, @@ -120,6 +126,10 @@ impl BestSubscription { self.network_tx.send(Action::GetInflation)?; self.network_tx.send(Action::GetCurrentValidatorEraRewards)?; self.network_tx.send(Action::GetMinValidatorBond)?; + + for chain_id in GATEKEEPED_CHAIN_IDS { + self.network_tx.send(Action::GetGatekeepedNetwork(chain_id))?; + } } Ok(()) } diff --git a/src/types/mod.rs b/src/types/mod.rs index d27c49c..ad46334 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -6,6 +6,7 @@ mod peer; mod session; mod nominator; mod staking; +mod networks; pub use extrinsics::CasperExtrinsicDetails; pub use era::{EraRewardPoints, EraInfo}; @@ -18,3 +19,5 @@ pub use nominator::Nominator; pub use nominator::Nominations; pub use staking::UnlockChunk; pub use staking::RewardDestination; +pub use networks::Gatekeeper; +pub use networks::BlockRange; diff --git a/src/types/networks.rs b/src/types/networks.rs new file mode 100644 index 0000000..ae170e0 --- /dev/null +++ b/src/types/networks.rs @@ -0,0 +1,18 @@ +use codec::Decode; +use serde::{Serialize, Deserialize}; + +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, Decode)] +pub struct Gatekeeper { + pub chain_id: u64, + pub chain_name: String, + pub chain_type: String, + pub gatekeeper: String, + pub incoming_fee: u32, + pub outgoing_fee: u32, +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Decode)] +pub struct BlockRange { + pub from_block: u64, + pub to_block: u64, +}