sketch for the validators page
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
This commit is contained in:
parent
85e44f0bb8
commit
2e0205a581
@ -29,7 +29,18 @@
|
||||
"highlight_style": "yellow bold",
|
||||
"popup_style": "blue",
|
||||
"popup_title_style": "blue",
|
||||
}
|
||||
},
|
||||
"Validator": {
|
||||
"normal_style": "",
|
||||
"hover_style": "bold yellow italic on blue",
|
||||
"normal_border_style": "blue",
|
||||
"hover_border_style": "blue",
|
||||
"normal_title_style": "blue",
|
||||
"hover_title_style": "",
|
||||
"highlight_style": "yellow bold",
|
||||
"popup_style": "blue",
|
||||
"popup_title_style": "blue",
|
||||
},
|
||||
},
|
||||
"keybindings": {
|
||||
"Menu": {
|
||||
@ -49,6 +60,12 @@
|
||||
"<Ctrl-c>": "Quit",
|
||||
"<Ctrl-z>": "Suspend",
|
||||
},
|
||||
"Validator": {
|
||||
"<q>": "Quit",
|
||||
"<Ctrl-d>": "Quit",
|
||||
"<Ctrl-c>": "Quit",
|
||||
"<Ctrl-z>": "Suspend",
|
||||
},
|
||||
"Empty": {
|
||||
"<q>": "Quit",
|
||||
"<Ctrl-d>": "Quit",
|
||||
|
@ -4,8 +4,8 @@ use strum::Display;
|
||||
use subxt::utils::H256;
|
||||
use subxt::config::substrate::DigestItem;
|
||||
|
||||
use crate::{
|
||||
types::{SystemAccount, ActionLevel, EraInfo, CasperExtrinsicDetails},
|
||||
use crate::types::{
|
||||
ActionLevel, CasperExtrinsicDetails, EraInfo, Nominator, PeerInformation, SessionKeyInfo, SystemAccount
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)]
|
||||
@ -42,6 +42,7 @@ pub enum Action {
|
||||
|
||||
TransferBalance(String, [u8; 32], u128),
|
||||
WalletLog(String, ActionLevel),
|
||||
ValidatorLog(String, ActionLevel),
|
||||
|
||||
NewBestBlock(u32),
|
||||
NewBestHash(H256),
|
||||
@ -49,6 +50,10 @@ pub enum Action {
|
||||
NewFinalizedHash(H256),
|
||||
BestBlockUpdated(u32),
|
||||
ExtrinsicsLength(u32, usize),
|
||||
ValidatorsNumber(u32),
|
||||
NominatorsNumber(u32),
|
||||
Inflation(String),
|
||||
Apy(String),
|
||||
|
||||
GetBlockAuthor(H256, Vec<DigestItem>),
|
||||
SetBlockAuthor(H256, String),
|
||||
@ -59,25 +64,54 @@ pub enum Action {
|
||||
GetChainName,
|
||||
GetChainVersion,
|
||||
GetPendingExtrinsics,
|
||||
GetConnectedPeers,
|
||||
GetSessionKeys([u8; 32]),
|
||||
GetQueuedSessionKeys([u8; 32]),
|
||||
GetListenAddresses,
|
||||
GetLocalIdentity,
|
||||
|
||||
GetLatestBlock,
|
||||
GetFinalizedBlock,
|
||||
GetActiveEra,
|
||||
GetCurrentEra,
|
||||
GetEpochProgress,
|
||||
GetValidators,
|
||||
GetValidatorsNumber,
|
||||
GetNominatorsNumber,
|
||||
GetInflation,
|
||||
GetNominatorsByValidator([u8; 32]),
|
||||
GetValidatorAllRewards([u8; 32]),
|
||||
GetValidatorLedger([u8; 32]),
|
||||
GetIsStashBonded([u8; 32]),
|
||||
GetErasStakersOverview([u8; 32]),
|
||||
GetValidatorPrefs([u8; 32]),
|
||||
|
||||
SetNodeName(Option<String>),
|
||||
SetSystemHealth(Option<usize>, bool, bool),
|
||||
SetGenesisHash(Option<H256>),
|
||||
SetChainName(Option<String>),
|
||||
SetChainVersion(Option<String>),
|
||||
SetStashAccount([u8; 32]),
|
||||
|
||||
BestBlockInformation(H256, u32),
|
||||
FinalizedBlockInformation(H256, u32),
|
||||
ExtrinsicsForBlock(u32, Vec<CasperExtrinsicDetails>),
|
||||
SetActiveEra(EraInfo),
|
||||
SetCurrentEra(u32),
|
||||
SetEpochProgress(u64, u64),
|
||||
SetPendingExtrinsicsLength(usize),
|
||||
SetConnectedPeers(Vec<PeerInformation>),
|
||||
SetSessionKey(String, SessionKeyInfo),
|
||||
SetListenAddresses(Vec<String>),
|
||||
SetLocalIdentity(String),
|
||||
SetNominatorsByValidator(Vec<Nominator>),
|
||||
SetValidatorEraReward(u32, u128),
|
||||
SetValidatorEraClaimed(u32, bool),
|
||||
SetValidatorEraSlash(u32, u128),
|
||||
SetValidatorEraUnlocking(u32, u128),
|
||||
SetBondedAmount(bool),
|
||||
SetStakedAmountRatio(u128, u128),
|
||||
SetStakedRatio(u128, u128),
|
||||
SetValidatorPrefs(u32, bool),
|
||||
|
||||
GetTotalIssuance,
|
||||
GetExistentialDeposit,
|
||||
|
20
src/app.rs
20
src/app.rs
@ -12,7 +12,8 @@ use crate::{
|
||||
tui::{Event, Tui},
|
||||
components::{
|
||||
menu::Menu, version::Version, explorer::Explorer, wallet::Wallet,
|
||||
empty::Empty, health::Health, fps::FpsCounter, Component,
|
||||
validator::Validator, empty::Empty, health::Health, fps::FpsCounter,
|
||||
Component,
|
||||
},
|
||||
};
|
||||
|
||||
@ -21,10 +22,8 @@ pub enum Mode {
|
||||
Menu,
|
||||
Explorer,
|
||||
Wallet,
|
||||
WalletActive,
|
||||
ExplorerActive,
|
||||
Validator,
|
||||
Empty,
|
||||
EmptyActive,
|
||||
}
|
||||
|
||||
impl Default for Mode {
|
||||
@ -71,6 +70,7 @@ impl App {
|
||||
Box::new(Version::default()),
|
||||
Box::new(Explorer::default()),
|
||||
Box::new(Wallet::default()),
|
||||
Box::new(Validator::default()),
|
||||
Box::new(Empty::default()),
|
||||
],
|
||||
should_quite: false,
|
||||
@ -161,6 +161,7 @@ impl App {
|
||||
|
||||
fn trigger_node_fast_events(&mut self) -> Result<()> {
|
||||
self.network_tx.send(Action::GetPendingExtrinsics)?;
|
||||
self.network_tx.send(Action::GetConnectedPeers)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -171,6 +172,8 @@ impl App {
|
||||
self.network_tx.send(Action::GetChainName)?;
|
||||
self.network_tx.send(Action::GetChainVersion)?;
|
||||
self.network_tx.send(Action::GetExistentialDeposit)?;
|
||||
self.network_tx.send(Action::GetLocalIdentity)?;
|
||||
self.network_tx.send(Action::GetListenAddresses)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -252,6 +255,15 @@ impl App {
|
||||
}
|
||||
}
|
||||
},
|
||||
Mode::Validator => {
|
||||
if let Some(component) = self.components.get_mut(6) {
|
||||
if let Err(err) = component.draw(frame, frame.area()) {
|
||||
let _ = self
|
||||
.action_tx
|
||||
.send(Action::Error(format!("failed to draw: {:?}", err)));
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
if let Some(component) = self.components.last_mut() {
|
||||
if let Err(err) = component.draw(frame, frame.area()) {
|
||||
|
@ -20,6 +20,8 @@ pub struct Health {
|
||||
is_syncing: bool,
|
||||
should_have_peers: bool,
|
||||
tx_pool_length: usize,
|
||||
validators_count: u32,
|
||||
nominators_count: u32,
|
||||
}
|
||||
|
||||
impl Default for Health {
|
||||
@ -36,6 +38,8 @@ impl Health {
|
||||
is_syncing: true,
|
||||
should_have_peers: false,
|
||||
tx_pool_length: 0,
|
||||
validators_count: 0,
|
||||
nominators_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,6 +78,8 @@ impl Component for Health {
|
||||
},
|
||||
Action::SetNodeName(name) => self.name = name,
|
||||
Action::SetPendingExtrinsicsLength(length) => self.tx_pool_length = length,
|
||||
Action::NominatorsNumber(number) => self.nominators_count = number,
|
||||
Action::ValidatorsNumber(number) => self.validators_count = number,
|
||||
_ => {}
|
||||
};
|
||||
Ok(None)
|
||||
@ -82,11 +88,13 @@ impl Component for Health {
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
let [place, _] = super::header_layout(area);
|
||||
|
||||
let message = format!("{:^12} | tx.pool: {:^3} | peers: {:^3} | {:^9}",
|
||||
let message = format!("{:^12} | tx.pool: {:^3} | peers: {:^3} | {:^9} | validators {:^4} | nominators {:^4} |",
|
||||
self.name_as_string(),
|
||||
self.tx_pool_length,
|
||||
self.peers_as_string(),
|
||||
self.is_syncing_as_string());
|
||||
self.is_syncing_as_string(),
|
||||
self.validators_count,
|
||||
self.nominators_count);
|
||||
|
||||
let span = Span::styled(message, Style::new().dim());
|
||||
let paragraph = Paragraph::new(span).left_aligned();
|
||||
|
@ -29,8 +29,8 @@ impl Menu {
|
||||
items: vec![
|
||||
String::from("Explorer"),
|
||||
String::from("Wallet"),
|
||||
String::from("Validator"),
|
||||
String::from("Prices"),
|
||||
String::from("Staking"),
|
||||
String::from("Governance"),
|
||||
String::from("Operations"),
|
||||
],
|
||||
@ -57,6 +57,7 @@ impl Menu {
|
||||
match i {
|
||||
0 => Ok(Some(Action::SetMode(Mode::Explorer))),
|
||||
1 => Ok(Some(Action::SetMode(Mode::Wallet))),
|
||||
2 => Ok(Some(Action::SetMode(Mode::Validator))),
|
||||
_ => Ok(Some(Action::SetMode(Mode::Empty))),
|
||||
}
|
||||
}
|
||||
@ -76,6 +77,7 @@ impl Menu {
|
||||
match i {
|
||||
0 => Ok(Some(Action::SetMode(Mode::Explorer))),
|
||||
1 => Ok(Some(Action::SetMode(Mode::Wallet))),
|
||||
2 => Ok(Some(Action::SetMode(Mode::Validator))),
|
||||
_ => Ok(Some(Action::SetMode(Mode::Empty))),
|
||||
}
|
||||
}
|
||||
@ -115,6 +117,7 @@ impl Component for Menu {
|
||||
match self.list_state.selected() {
|
||||
Some(0) => Ok(Some(Action::SetActiveScreen(Mode::Explorer))),
|
||||
Some(1) => Ok(Some(Action::SetActiveScreen(Mode::Wallet))),
|
||||
Some(2) => Ok(Some(Action::SetActiveScreen(Mode::Validator))),
|
||||
_ => Ok(Some(Action::SetActiveScreen(Mode::Empty))),
|
||||
}
|
||||
},
|
||||
|
@ -15,6 +15,7 @@ pub mod menu;
|
||||
pub mod version;
|
||||
pub mod explorer;
|
||||
pub mod wallet;
|
||||
pub mod validator;
|
||||
pub mod empty;
|
||||
|
||||
pub trait Component {
|
||||
@ -75,8 +76,8 @@ pub fn global_layout(area: Rect) -> [Rect; 2] {
|
||||
pub fn header_layout(area: Rect) -> [Rect; 2] {
|
||||
let [header, _] = global_layout(area);
|
||||
Layout::horizontal([
|
||||
Constraint::Percentage(50),
|
||||
Constraint::Percentage(50),
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(27),
|
||||
]).areas(header)
|
||||
}
|
||||
|
||||
|
197
src/components/validator/event_log.rs
Normal file
197
src/components/validator/event_log.rs
Normal file
@ -0,0 +1,197 @@
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use ratatui::{
|
||||
layout::{Alignment, Constraint, Margin, Rect},
|
||||
style::{Color, Style},
|
||||
text::Text,
|
||||
widgets::{
|
||||
Block, Padding, Cell, Row, Scrollbar, ScrollbarOrientation,
|
||||
ScrollbarState, Table, TableState,
|
||||
},
|
||||
Frame
|
||||
};
|
||||
|
||||
use super::{Component, PartialComponent, CurrentTab};
|
||||
use crate::{
|
||||
types::ActionLevel,
|
||||
action::Action,
|
||||
config::Config,
|
||||
palette::StylePalette,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct WalletLog {
|
||||
time: chrono::DateTime<chrono::Local>,
|
||||
level: ActionLevel,
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct EventLogs {
|
||||
is_active: bool,
|
||||
scroll_state: ScrollbarState,
|
||||
table_state: TableState,
|
||||
logs: std::collections::VecDeque<WalletLog>,
|
||||
palette: StylePalette
|
||||
}
|
||||
|
||||
impl EventLogs {
|
||||
const MAX_LOGS: usize = 50;
|
||||
|
||||
fn add_new_log(&mut self, message: String, level: ActionLevel) {
|
||||
self.logs.push_front(WalletLog {
|
||||
time: chrono::Local::now(),
|
||||
level,
|
||||
message,
|
||||
});
|
||||
|
||||
if self.logs.len() > Self::MAX_LOGS {
|
||||
let _ = self.logs.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
fn first_row(&mut self) {
|
||||
if self.logs.len() > 0 {
|
||||
self.table_state.select(Some(0));
|
||||
self.scroll_state = self.scroll_state.position(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn next_row(&mut self) {
|
||||
let i = match self.table_state.selected() {
|
||||
Some(i) => {
|
||||
if i >= self.logs.len() - 1 {
|
||||
i
|
||||
} else {
|
||||
i + 1
|
||||
}
|
||||
},
|
||||
None => 0,
|
||||
};
|
||||
self.table_state.select(Some(i));
|
||||
self.scroll_state = self.scroll_state.position(i);
|
||||
}
|
||||
|
||||
fn last_row(&mut self) {
|
||||
if self.logs.len() > 0 {
|
||||
let last = self.logs.len() - 1;
|
||||
self.table_state.select(Some(last));
|
||||
self.scroll_state = self.scroll_state.position(last);
|
||||
}
|
||||
}
|
||||
|
||||
fn previous_row(&mut self) {
|
||||
let i = match self.table_state.selected() {
|
||||
Some(i) => {
|
||||
if i == 0 {
|
||||
0
|
||||
} else {
|
||||
i - 1
|
||||
}
|
||||
},
|
||||
None => 0
|
||||
};
|
||||
self.table_state.select(Some(i));
|
||||
self.scroll_state = self.scroll_state.position(i);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for EventLogs {
|
||||
fn set_active(&mut self, current_tab: CurrentTab) {
|
||||
match current_tab {
|
||||
CurrentTab::EventLogs => self.is_active = true,
|
||||
_ => {
|
||||
self.is_active = false;
|
||||
self.table_state.select(None);
|
||||
self.scroll_state = self.scroll_state.position(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for EventLogs {
|
||||
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 handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
|
||||
match key.code {
|
||||
KeyCode::Up | KeyCode::Char('k') if self.is_active => self.previous_row(),
|
||||
KeyCode::Down | KeyCode::Char('j') if self.is_active => self.next_row(),
|
||||
KeyCode::Char('g') if self.is_active => self.first_row(),
|
||||
KeyCode::Char('G') if self.is_active => self.last_row(),
|
||||
_ => {},
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||
match action {
|
||||
Action::ValidatorLog(message, level) => self.add_new_log(message, level),
|
||||
_ => {}
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
let [_, _, _, place] = super::validator_layout(area);
|
||||
|
||||
let (border_style, border_type) = self.palette.create_border_style(self.is_active);
|
||||
let error_style = Style::new().fg(Color::Red);
|
||||
let warn_style = Style::new().fg(Color::Yellow);
|
||||
let info_style = Style::new().fg(Color::Green);
|
||||
|
||||
let table = Table::new(
|
||||
self.logs
|
||||
.iter()
|
||||
.map(|log| {
|
||||
let style = match log.level {
|
||||
ActionLevel::Info => info_style,
|
||||
ActionLevel::Warn => warn_style,
|
||||
ActionLevel::Error => error_style,
|
||||
};
|
||||
Row::new(vec![
|
||||
Cell::from(Text::from(log.time.format("%H:%M:%S").to_string()).style(style).alignment(Alignment::Left)),
|
||||
Cell::from(Text::from(log.message.clone()).style(style).alignment(Alignment::Left)),
|
||||
])
|
||||
}),
|
||||
[
|
||||
Constraint::Max(8),
|
||||
Constraint::Min(0),
|
||||
],
|
||||
)
|
||||
.column_spacing(1)
|
||||
.highlight_style(self.palette.create_highlight_style())
|
||||
.block(Block::bordered()
|
||||
.border_style(border_style)
|
||||
.border_type(border_type)
|
||||
.padding(Padding::right(2))
|
||||
.title_alignment(Alignment::Right)
|
||||
.title_style(self.palette.create_title_style(false))
|
||||
.title("Action Logs"));
|
||||
|
||||
let scrollbar = Scrollbar::default()
|
||||
.orientation(ScrollbarOrientation::VerticalRight)
|
||||
.begin_symbol(None)
|
||||
.end_symbol(None)
|
||||
.style(self.palette.create_scrollbar_style());
|
||||
|
||||
frame.render_stateful_widget(table, place, &mut self.table_state);
|
||||
frame.render_stateful_widget(
|
||||
scrollbar,
|
||||
place.inner(Margin { vertical: 1, horizontal: 1 }),
|
||||
&mut self.scroll_state,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
271
src/components/validator/history.rs
Normal file
271
src/components/validator/history.rs
Normal file
@ -0,0 +1,271 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use ratatui::layout::{Constraint, Margin};
|
||||
use ratatui::style::Modifier;
|
||||
use ratatui::{
|
||||
prelude::Stylize,
|
||||
text::Text,
|
||||
layout::{Alignment, Rect},
|
||||
widgets::{
|
||||
Block, Cell, Row, Table, TableState, Scrollbar, Padding,
|
||||
ScrollbarOrientation, ScrollbarState,
|
||||
},
|
||||
Frame
|
||||
};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
|
||||
use super::{PartialComponent, Component, CurrentTab};
|
||||
use crate::{
|
||||
action::Action,
|
||||
config::Config,
|
||||
palette::StylePalette,
|
||||
};
|
||||
|
||||
struct EraStakingInfo {
|
||||
reward: u128,
|
||||
slash: u128,
|
||||
is_claimed: bool,
|
||||
}
|
||||
|
||||
pub struct History {
|
||||
is_active: bool,
|
||||
network_tx: Option<Sender<Action>>,
|
||||
action_tx: Option<UnboundedSender<Action>>,
|
||||
palette: StylePalette,
|
||||
scroll_state: ScrollbarState,
|
||||
table_state: TableState,
|
||||
rewards: BTreeMap<u32, EraStakingInfo>,
|
||||
}
|
||||
|
||||
impl Default for History {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl History {
|
||||
const TICKER: &str = " CSPR";
|
||||
const DECIMALS: usize = 5;
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
is_active: false,
|
||||
network_tx: None,
|
||||
action_tx: None,
|
||||
scroll_state: ScrollbarState::new(0),
|
||||
table_state: TableState::new(),
|
||||
rewards: BTreeMap::new(),
|
||||
palette: StylePalette::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn first_row(&mut self) {
|
||||
if self.rewards.len() > 0 {
|
||||
self.table_state.select(Some(0));
|
||||
self.scroll_state = self.scroll_state.position(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn next_row(&mut self) {
|
||||
let i = match self.table_state.selected() {
|
||||
Some(i) => {
|
||||
if i >= self.rewards.len() - 1 {
|
||||
i
|
||||
} else {
|
||||
i + 1
|
||||
}
|
||||
},
|
||||
None => 0,
|
||||
};
|
||||
self.table_state.select(Some(i));
|
||||
self.scroll_state = self.scroll_state.position(i);
|
||||
}
|
||||
|
||||
fn last_row(&mut self) {
|
||||
if self.rewards.len() > 0 {
|
||||
let last = self.rewards.len() - 1;
|
||||
self.table_state.select(Some(last));
|
||||
self.scroll_state = self.scroll_state.position(last);
|
||||
}
|
||||
}
|
||||
|
||||
fn previous_row(&mut self) {
|
||||
let i = match self.table_state.selected() {
|
||||
Some(i) => {
|
||||
if i == 0 {
|
||||
0
|
||||
} else {
|
||||
i - 1
|
||||
}
|
||||
},
|
||||
None => 0
|
||||
};
|
||||
self.table_state.select(Some(i));
|
||||
self.scroll_state = self.scroll_state.position(i);
|
||||
}
|
||||
|
||||
fn update_rewards(&mut self, era_index: u32, reward: u128) {
|
||||
match self.rewards.get_mut(&era_index) {
|
||||
Some(reward_item) => reward_item.reward = reward,
|
||||
None => {
|
||||
let _ = self.rewards.insert(era_index, EraStakingInfo {
|
||||
reward,
|
||||
slash: 0u128,
|
||||
is_claimed: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_claims(&mut self, era_index: u32, is_claimed: bool) {
|
||||
match self.rewards.get_mut(&era_index) {
|
||||
Some(reward_item) => reward_item.is_claimed = is_claimed,
|
||||
None => {
|
||||
let _ = self.rewards.insert(era_index, EraStakingInfo {
|
||||
reward: 0u128,
|
||||
slash: 0u128,
|
||||
is_claimed,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_slashes(&mut self, era_index: u32, slash: u128) {
|
||||
match self.rewards.get_mut(&era_index) {
|
||||
Some(reward_item) => reward_item.slash = slash,
|
||||
None => {
|
||||
let _ = self.rewards.insert(era_index, EraStakingInfo {
|
||||
reward: 0u128,
|
||||
slash,
|
||||
is_claimed: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_u128(&self, value: u128) -> String {
|
||||
let value = value as f64 / 10f64.powi(18);
|
||||
let after = Self::DECIMALS;
|
||||
format!("{:.after$}{}", value, Self::TICKER)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for History {
|
||||
fn set_active(&mut self, current_tab: CurrentTab) {
|
||||
match current_tab {
|
||||
CurrentTab::History => self.is_active = true,
|
||||
_ => {
|
||||
self.is_active = false;
|
||||
self.table_state.select(None);
|
||||
self.scroll_state = self.scroll_state.position(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for History {
|
||||
fn register_network_handler(&mut self, tx: Sender<Action>) -> Result<()> {
|
||||
self.network_tx = Some(tx);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> 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<Option<Action>> {
|
||||
match action {
|
||||
Action::SetValidatorEraReward(era_index, reward) => self.update_rewards(era_index, reward),
|
||||
Action::SetValidatorEraClaimed(era_index, is_claimed) => self.update_claims(era_index, is_claimed),
|
||||
Action::SetValidatorEraSlash(era_index, slash) => self.update_slashes(era_index, slash),
|
||||
_ => {}
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
|
||||
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_statistics_layout(area);
|
||||
let (border_style, border_type) = self.palette.create_border_style(self.is_active);
|
||||
let table = Table::new(
|
||||
self.rewards
|
||||
.iter()
|
||||
.map(|(key, value)| {
|
||||
let mut era_index_text = Text::from(key.to_string()).alignment(Alignment::Left);
|
||||
let mut slash_text = Text::from(self.prepare_u128(value.slash)).alignment(Alignment::Center);
|
||||
let mut reward_text = Text::from(self.prepare_u128(value.reward)).alignment(Alignment::Right);
|
||||
|
||||
if value.is_claimed {
|
||||
era_index_text = era_index_text.add_modifier(Modifier::CROSSED_OUT);
|
||||
slash_text = slash_text.add_modifier(Modifier::CROSSED_OUT);
|
||||
reward_text = reward_text.add_modifier(Modifier::CROSSED_OUT);
|
||||
}
|
||||
|
||||
Row::new(vec![
|
||||
Cell::from(era_index_text),
|
||||
Cell::from(slash_text),
|
||||
Cell::from(reward_text),
|
||||
])
|
||||
}),
|
||||
[
|
||||
Constraint::Length(4),
|
||||
Constraint::Fill(1),
|
||||
Constraint::Fill(1),
|
||||
],
|
||||
)
|
||||
.highlight_style(self.palette.create_highlight_style())
|
||||
.column_spacing(1)
|
||||
.block(Block::bordered()
|
||||
.border_style(border_style)
|
||||
.border_type(border_type)
|
||||
.padding(Padding::right(2))
|
||||
.title_alignment(Alignment::Right)
|
||||
.title_style(self.palette.create_title_style(false))
|
||||
.title("Staking history"));
|
||||
|
||||
let scrollbar = Scrollbar::default()
|
||||
.orientation(ScrollbarOrientation::VerticalRight)
|
||||
.begin_symbol(None)
|
||||
.end_symbol(None)
|
||||
.style(self.palette.create_scrollbar_style());
|
||||
|
||||
frame.render_stateful_widget(table, place, &mut self.table_state);
|
||||
frame.render_stateful_widget(
|
||||
scrollbar,
|
||||
place.inner(Margin { vertical: 1, horizontal: 1 }),
|
||||
&mut self.scroll_state,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
183
src/components/validator/listen_addresses.rs
Normal file
183
src/components/validator/listen_addresses.rs
Normal file
@ -0,0 +1,183 @@
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use ratatui::layout::Margin;
|
||||
use ratatui::widgets::ListItem;
|
||||
use ratatui::{
|
||||
layout::{Alignment, Rect},
|
||||
widgets::{
|
||||
Block, List, ListState, Scrollbar,
|
||||
ScrollbarOrientation, ScrollbarState,
|
||||
},
|
||||
Frame
|
||||
};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
|
||||
use super::{PartialComponent, Component, CurrentTab};
|
||||
use crate::{
|
||||
action::Action,
|
||||
config::Config,
|
||||
palette::StylePalette,
|
||||
};
|
||||
|
||||
pub struct ListenAddresses {
|
||||
is_active: bool,
|
||||
action_tx: Option<UnboundedSender<Action>>,
|
||||
palette: StylePalette,
|
||||
scroll_state: ScrollbarState,
|
||||
list_state: ListState,
|
||||
listen_addresses: Vec<String>,
|
||||
local_identity: String,
|
||||
}
|
||||
|
||||
impl Default for ListenAddresses {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ListenAddresses {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
is_active: false,
|
||||
action_tx: None,
|
||||
scroll_state: ScrollbarState::new(0),
|
||||
list_state: ListState::default(),
|
||||
palette: StylePalette::default(),
|
||||
listen_addresses: Vec::new(),
|
||||
local_identity: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn first_row(&mut self) {
|
||||
if self.listen_addresses.len() > 0 {
|
||||
self.list_state.select(Some(0));
|
||||
self.scroll_state = self.scroll_state.position(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn next_row(&mut self) {
|
||||
let i = match self.list_state.selected() {
|
||||
Some(i) => {
|
||||
if i >= self.listen_addresses.len() - 1 {
|
||||
i
|
||||
} else {
|
||||
i + 1
|
||||
}
|
||||
},
|
||||
None => 0,
|
||||
};
|
||||
self.list_state.select(Some(i));
|
||||
self.scroll_state = self.scroll_state.position(i);
|
||||
}
|
||||
|
||||
fn last_row(&mut self) {
|
||||
if self.listen_addresses.len() > 0 {
|
||||
let last = self.listen_addresses.len() - 1;
|
||||
self.list_state.select(Some(last));
|
||||
self.scroll_state = self.scroll_state.position(last);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for ListenAddresses {
|
||||
fn set_active(&mut self, current_tab: CurrentTab) {
|
||||
match current_tab {
|
||||
CurrentTab::ListenAddresses => self.is_active = true,
|
||||
_ => {
|
||||
self.is_active = false;
|
||||
self.list_state.select(None);
|
||||
self.scroll_state = self.scroll_state.position(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for ListenAddresses {
|
||||
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> 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::Wallet) {
|
||||
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<Option<Action>> {
|
||||
match action {
|
||||
Action::SetListenAddresses(addresses) => self.listen_addresses = addresses,
|
||||
Action::SetLocalIdentity(identity) => self.local_identity = identity,
|
||||
_ => {}
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
|
||||
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_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
|
||||
.iter()
|
||||
.map(|addr| ListItem::new(addr.clone()))
|
||||
)
|
||||
.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(self.local_identity.clone()));
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
223
src/components/validator/mod.rs
Normal file
223
src/components/validator/mod.rs
Normal file
@ -0,0 +1,223 @@
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use ratatui::{
|
||||
layout::{Constraint, Layout, Rect},
|
||||
Frame,
|
||||
};
|
||||
|
||||
use std::sync::mpsc::Sender;
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
|
||||
use super::Component;
|
||||
use crate::{action::Action, app::Mode, config::Config};
|
||||
|
||||
mod event_log;
|
||||
mod peers;
|
||||
mod stash_info;
|
||||
mod nominators;
|
||||
mod listen_addresses;
|
||||
mod history;
|
||||
mod withdrawals;
|
||||
mod stash_details;
|
||||
mod staking_details;
|
||||
mod reward_details;
|
||||
|
||||
use stash_details::StashDetails;
|
||||
use staking_details::StakingDetails;
|
||||
use reward_details::RewardDetails;
|
||||
use event_log::EventLogs;
|
||||
use peers::Peers;
|
||||
use stash_info::StashInfo;
|
||||
use listen_addresses::ListenAddresses;
|
||||
use nominators::NominatorsByValidator;
|
||||
use history::History;
|
||||
use withdrawals::Withdrawals;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum CurrentTab {
|
||||
Nothing,
|
||||
StashInfo,
|
||||
ListenAddresses,
|
||||
NominatorsByValidator,
|
||||
History,
|
||||
Withdrawals,
|
||||
Peers,
|
||||
EventLogs,
|
||||
}
|
||||
|
||||
pub trait PartialComponent: Component {
|
||||
fn set_active(&mut self, current_tab: CurrentTab);
|
||||
}
|
||||
|
||||
pub struct Validator {
|
||||
is_active: bool,
|
||||
current_tab: CurrentTab,
|
||||
components: Vec<Box<dyn PartialComponent>>,
|
||||
}
|
||||
|
||||
impl Default for Validator {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
is_active: false,
|
||||
current_tab: CurrentTab::Nothing,
|
||||
components: vec![
|
||||
Box::new(StashInfo::default()),
|
||||
Box::new(NominatorsByValidator::default()),
|
||||
Box::new(StashDetails::default()),
|
||||
Box::new(StakingDetails::default()),
|
||||
Box::new(RewardDetails::default()),
|
||||
Box::new(History::default()),
|
||||
Box::new(Withdrawals::default()),
|
||||
Box::new(Peers::default()),
|
||||
Box::new(ListenAddresses::default()),
|
||||
Box::new(EventLogs::default()),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Validator {
|
||||
fn move_left(&mut self) {
|
||||
match self.current_tab {
|
||||
CurrentTab::EventLogs => self.current_tab = CurrentTab::Peers,
|
||||
CurrentTab::Peers => self.current_tab = CurrentTab::Withdrawals,
|
||||
CurrentTab::Withdrawals => self.current_tab = CurrentTab::History,
|
||||
CurrentTab::History => self.current_tab = CurrentTab::NominatorsByValidator,
|
||||
CurrentTab::NominatorsByValidator => self.current_tab = CurrentTab::ListenAddresses,
|
||||
CurrentTab::ListenAddresses => self.current_tab = CurrentTab::StashInfo,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn move_right(&mut self) {
|
||||
match self.current_tab {
|
||||
CurrentTab::Nothing => self.current_tab = CurrentTab::StashInfo,
|
||||
CurrentTab::StashInfo => self.current_tab = CurrentTab::ListenAddresses,
|
||||
CurrentTab::ListenAddresses => self.current_tab = CurrentTab::NominatorsByValidator,
|
||||
CurrentTab::NominatorsByValidator => self.current_tab = CurrentTab::History,
|
||||
CurrentTab::History => self.current_tab = CurrentTab::Withdrawals,
|
||||
CurrentTab::Withdrawals => self.current_tab = CurrentTab::Peers,
|
||||
CurrentTab::Peers => self.current_tab = CurrentTab::EventLogs,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Validator {
|
||||
fn register_network_handler(&mut self, tx: Sender<Action>) -> Result<()> {
|
||||
for component in self.components.iter_mut() {
|
||||
component.register_network_handler(tx.clone())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
|
||||
for component in self.components.iter_mut() {
|
||||
component.register_action_handler(tx.clone())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn register_config_handler(&mut self, config: Config) -> Result<()> {
|
||||
for component in self.components.iter_mut() {
|
||||
component.register_config_handler(config.clone())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
|
||||
if !self.is_active { return Ok(None) }
|
||||
|
||||
match key.code {
|
||||
KeyCode::Esc => {
|
||||
self.is_active = false;
|
||||
self.current_tab = CurrentTab::Nothing;
|
||||
for component in self.components.iter_mut() {
|
||||
component.set_active(self.current_tab.clone());
|
||||
}
|
||||
return Ok(Some(Action::SetActiveScreen(Mode::Menu)));
|
||||
},
|
||||
KeyCode::Char('l') | KeyCode::Right => {
|
||||
self.move_right();
|
||||
for component in self.components.iter_mut() {
|
||||
component.set_active(self.current_tab.clone());
|
||||
}
|
||||
},
|
||||
KeyCode::Char('h') | KeyCode::Left => {
|
||||
self.move_left();
|
||||
for component in self.components.iter_mut() {
|
||||
component.set_active(self.current_tab.clone());
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
for component in self.components.iter_mut() {
|
||||
component.handle_key_event(key)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||
match action {
|
||||
Action::SetActiveScreen(Mode::Validator) => self.is_active = true,
|
||||
_ => {},
|
||||
}
|
||||
for component in self.components.iter_mut() {
|
||||
component.set_active(self.current_tab.clone());
|
||||
component.update(action.clone())?;
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
let screen = super::screen_layout(area);
|
||||
for component in self.components.iter_mut() {
|
||||
component.draw(frame, screen)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validator_layout(area: Rect) -> [Rect; 4] {
|
||||
Layout::vertical([
|
||||
Constraint::Length(16),
|
||||
Constraint::Fill(1),
|
||||
Constraint::Fill(1),
|
||||
Constraint::Percentage(25),
|
||||
]).areas(area)
|
||||
}
|
||||
|
||||
pub fn validator_details_layout(area: Rect) -> [Rect; 2] {
|
||||
let [place, _, _, _] = validator_layout(area);
|
||||
Layout::horizontal([
|
||||
Constraint::Length(31),
|
||||
Constraint::Fill(1),
|
||||
]).areas(place)
|
||||
}
|
||||
|
||||
pub fn validator_session_and_listen_layout(area: Rect) -> [Rect; 2] {
|
||||
let [_, place] = validator_details_layout(area);
|
||||
Layout::vertical([
|
||||
Constraint::Length(6),
|
||||
Constraint::Fill(1),
|
||||
]).areas(place)
|
||||
}
|
||||
|
||||
pub fn validator_statistics_layout(area: Rect) -> [Rect; 3] {
|
||||
let [_, place, _, _] = validator_layout(area);
|
||||
Layout::horizontal([
|
||||
Constraint::Percentage(30),
|
||||
Constraint::Percentage(40),
|
||||
Constraint::Percentage(30),
|
||||
]).areas(place)
|
||||
}
|
||||
|
||||
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),
|
||||
]).areas(place)
|
||||
}
|
210
src/components/validator/nominators.rs
Normal file
210
src/components/validator/nominators.rs
Normal file
@ -0,0 +1,210 @@
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use ratatui::layout::{Constraint, Margin};
|
||||
use ratatui::{
|
||||
text::Text,
|
||||
layout::{Alignment, Rect},
|
||||
widgets::{
|
||||
Block, Cell, Row, Table, TableState, Scrollbar, Padding,
|
||||
ScrollbarOrientation, ScrollbarState,
|
||||
},
|
||||
Frame
|
||||
};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
|
||||
use super::{PartialComponent, Component, CurrentTab};
|
||||
use crate::types::Nominator;
|
||||
use crate::{
|
||||
action::Action,
|
||||
config::Config,
|
||||
palette::StylePalette,
|
||||
};
|
||||
|
||||
pub struct NominatorsByValidator {
|
||||
is_active: bool,
|
||||
action_tx: Option<UnboundedSender<Action>>,
|
||||
palette: StylePalette,
|
||||
scroll_state: ScrollbarState,
|
||||
table_state: TableState,
|
||||
nominators: Vec<Nominator>,
|
||||
}
|
||||
|
||||
impl Default for NominatorsByValidator {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl NominatorsByValidator {
|
||||
const TICKER: &str = " CSPR";
|
||||
const DECIMALS: usize = 5;
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
is_active: false,
|
||||
action_tx: None,
|
||||
scroll_state: ScrollbarState::new(0),
|
||||
table_state: TableState::new(),
|
||||
nominators: Vec::new(),
|
||||
palette: StylePalette::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_nominators(&mut self, nominators: Vec<Nominator>) {
|
||||
if self.nominators.len() > nominators.len() {
|
||||
if let Some(_) = self.table_state.selected() {
|
||||
self.last_row();
|
||||
}
|
||||
}
|
||||
self.nominators = nominators;
|
||||
}
|
||||
|
||||
fn first_row(&mut self) {
|
||||
if self.nominators.len() > 0 {
|
||||
self.table_state.select(Some(0));
|
||||
self.scroll_state = self.scroll_state.position(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn next_row(&mut self) {
|
||||
let i = match self.table_state.selected() {
|
||||
Some(i) => {
|
||||
if i >= self.nominators.len() - 1 {
|
||||
i
|
||||
} else {
|
||||
i + 1
|
||||
}
|
||||
},
|
||||
None => 0,
|
||||
};
|
||||
self.table_state.select(Some(i));
|
||||
self.scroll_state = self.scroll_state.position(i);
|
||||
}
|
||||
|
||||
fn last_row(&mut self) {
|
||||
if self.nominators.len() > 0 {
|
||||
let last = self.nominators.len() - 1;
|
||||
self.table_state.select(Some(last));
|
||||
self.scroll_state = self.scroll_state.position(last);
|
||||
}
|
||||
}
|
||||
|
||||
fn previous_row(&mut self) {
|
||||
let i = match self.table_state.selected() {
|
||||
Some(i) => {
|
||||
if i == 0 {
|
||||
0
|
||||
} else {
|
||||
i - 1
|
||||
}
|
||||
},
|
||||
None => 0
|
||||
};
|
||||
self.table_state.select(Some(i));
|
||||
self.scroll_state = self.scroll_state.position(i);
|
||||
}
|
||||
|
||||
fn prepare_u128(&self, value: u128) -> String {
|
||||
let value = value as f64 / 10f64.powi(18);
|
||||
let after = Self::DECIMALS;
|
||||
format!("{:.after$}{}", value, Self::TICKER)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for NominatorsByValidator {
|
||||
fn set_active(&mut self, current_tab: CurrentTab) {
|
||||
match current_tab {
|
||||
CurrentTab::NominatorsByValidator => self.is_active = true,
|
||||
_ => {
|
||||
self.is_active = false;
|
||||
self.table_state.select(None);
|
||||
self.scroll_state = self.scroll_state.position(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for NominatorsByValidator {
|
||||
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> 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<Option<Action>> {
|
||||
match action {
|
||||
Action::SetNominatorsByValidator(nominators) => self.update_nominators(nominators),
|
||||
_ => {}
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
|
||||
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_statistics_layout(area);
|
||||
let (border_style, border_type) = self.palette.create_border_style(self.is_active);
|
||||
let table = Table::new(
|
||||
self.nominators
|
||||
.iter()
|
||||
.map(|info| {
|
||||
Row::new(vec![
|
||||
Cell::from(Text::from(info.who.clone()).alignment(Alignment::Left)),
|
||||
Cell::from(Text::from(self.prepare_u128(info.value)).alignment(Alignment::Right)),
|
||||
])
|
||||
}),
|
||||
[
|
||||
Constraint::Min(0),
|
||||
Constraint::Min(11),
|
||||
],
|
||||
)
|
||||
.highlight_style(self.palette.create_highlight_style())
|
||||
.column_spacing(1)
|
||||
.block(Block::bordered()
|
||||
.border_style(border_style)
|
||||
.border_type(border_type)
|
||||
.padding(Padding::right(2))
|
||||
.title_alignment(Alignment::Right)
|
||||
.title_style(self.palette.create_title_style(false))
|
||||
.title("My Nominators"));
|
||||
|
||||
let scrollbar = Scrollbar::default()
|
||||
.orientation(ScrollbarOrientation::VerticalRight)
|
||||
.begin_symbol(None)
|
||||
.end_symbol(None)
|
||||
.style(self.palette.create_scrollbar_style());
|
||||
|
||||
frame.render_stateful_widget(table, place, &mut self.table_state);
|
||||
frame.render_stateful_widget(
|
||||
scrollbar,
|
||||
place.inner(Margin { vertical: 1, horizontal: 1 }),
|
||||
&mut self.scroll_state,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
198
src/components/validator/peers.rs
Normal file
198
src/components/validator/peers.rs
Normal file
@ -0,0 +1,198 @@
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use ratatui::layout::{Constraint, Margin};
|
||||
use ratatui::{
|
||||
text::Text,
|
||||
layout::{Alignment, Rect},
|
||||
widgets::{
|
||||
Block, Cell, Row, Table, TableState, Scrollbar, Padding,
|
||||
ScrollbarOrientation, ScrollbarState,
|
||||
},
|
||||
Frame
|
||||
};
|
||||
|
||||
use super::{PartialComponent, Component, CurrentTab};
|
||||
use crate::types::PeerInformation;
|
||||
use crate::{
|
||||
action::Action,
|
||||
config::Config,
|
||||
palette::StylePalette,
|
||||
};
|
||||
|
||||
pub struct Peers {
|
||||
is_active: bool,
|
||||
palette: StylePalette,
|
||||
scroll_state: ScrollbarState,
|
||||
table_state: TableState,
|
||||
peers: Vec<PeerInformation>,
|
||||
}
|
||||
|
||||
impl Default for Peers {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Peers {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
is_active: false,
|
||||
scroll_state: ScrollbarState::new(0),
|
||||
table_state: TableState::new(),
|
||||
peers: Vec::new(),
|
||||
palette: StylePalette::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_peers(&mut self, peers: Vec<PeerInformation>) {
|
||||
if self.peers.len() > peers.len() {
|
||||
if let Some(_) = self.table_state.selected() {
|
||||
self.last_row();
|
||||
}
|
||||
}
|
||||
self.peers = peers;
|
||||
}
|
||||
|
||||
fn first_row(&mut self) {
|
||||
if self.peers.len() > 0 {
|
||||
self.table_state.select(Some(0));
|
||||
self.scroll_state = self.scroll_state.position(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn next_row(&mut self) {
|
||||
let i = match self.table_state.selected() {
|
||||
Some(i) => {
|
||||
if i >= self.peers.len() - 1 {
|
||||
i
|
||||
} else {
|
||||
i + 1
|
||||
}
|
||||
},
|
||||
None => 0,
|
||||
};
|
||||
self.table_state.select(Some(i));
|
||||
self.scroll_state = self.scroll_state.position(i);
|
||||
}
|
||||
|
||||
fn last_row(&mut self) {
|
||||
if self.peers.len() > 0 {
|
||||
let last = self.peers.len() - 1;
|
||||
self.table_state.select(Some(last));
|
||||
self.scroll_state = self.scroll_state.position(last);
|
||||
}
|
||||
}
|
||||
|
||||
fn previous_row(&mut self) {
|
||||
let i = match self.table_state.selected() {
|
||||
Some(i) => {
|
||||
if i == 0 {
|
||||
0
|
||||
} else {
|
||||
i - 1
|
||||
}
|
||||
},
|
||||
None => 0
|
||||
};
|
||||
self.table_state.select(Some(i));
|
||||
self.scroll_state = self.scroll_state.position(i);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for Peers {
|
||||
fn set_active(&mut self, current_tab: CurrentTab) {
|
||||
match current_tab {
|
||||
CurrentTab::Peers => self.is_active = true,
|
||||
_ => {
|
||||
self.is_active = false;
|
||||
self.table_state.select(None);
|
||||
self.scroll_state = self.scroll_state.position(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Peers {
|
||||
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<Option<Action>> {
|
||||
match action {
|
||||
Action::SetConnectedPeers(peers) => self.update_peers(peers),
|
||||
_ => {}
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
|
||||
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_layout(area);
|
||||
let (border_style, border_type) = self.palette.create_border_style(self.is_active);
|
||||
let table = Table::new(
|
||||
self.peers
|
||||
.iter()
|
||||
.map(|info| {
|
||||
|
||||
Row::new(vec![
|
||||
Cell::from(Text::from(info.peer_id.clone()).alignment(Alignment::Left)),
|
||||
Cell::from(Text::from(info.roles.clone()).alignment(Alignment::Center)),
|
||||
Cell::from(Text::from(info.best_hash.to_string()).alignment(Alignment::Center)),
|
||||
Cell::from(Text::from(info.best_number.to_string()).alignment(Alignment::Right)),
|
||||
])
|
||||
}),
|
||||
[
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(11),
|
||||
Constraint::Length(11),
|
||||
Constraint::Length(11),
|
||||
],
|
||||
)
|
||||
.highlight_style(self.palette.create_highlight_style())
|
||||
.column_spacing(1)
|
||||
.block(Block::bordered()
|
||||
.border_style(border_style)
|
||||
.border_type(border_type)
|
||||
.padding(Padding::right(2))
|
||||
.title_alignment(Alignment::Right)
|
||||
.title_style(self.palette.create_title_style(false))
|
||||
.title("My Peers"));
|
||||
|
||||
let scrollbar = Scrollbar::default()
|
||||
.orientation(ScrollbarOrientation::VerticalRight)
|
||||
.begin_symbol(None)
|
||||
.end_symbol(None)
|
||||
.style(self.palette.create_scrollbar_style());
|
||||
|
||||
frame.render_stateful_widget(table, place, &mut self.table_state);
|
||||
frame.render_stateful_widget(
|
||||
scrollbar,
|
||||
place.inner(Margin { vertical: 1, horizontal: 1 }),
|
||||
&mut self.scroll_state,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
121
src/components/validator/reward_details.rs
Normal file
121
src/components/validator/reward_details.rs
Normal file
@ -0,0 +1,121 @@
|
||||
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::{
|
||||
action::Action,
|
||||
config::Config,
|
||||
palette::StylePalette,
|
||||
};
|
||||
|
||||
pub struct RewardDetails {
|
||||
palette: StylePalette,
|
||||
commission: u32,
|
||||
nominators_blocked: bool,
|
||||
apy: String,
|
||||
inflation: String,
|
||||
}
|
||||
|
||||
impl Default for RewardDetails {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl RewardDetails {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
palette: StylePalette::default(),
|
||||
commission: 0,
|
||||
nominators_blocked: false,
|
||||
apy: String::from("0.0%"),
|
||||
inflation: String::from("0.0%"),
|
||||
}
|
||||
}
|
||||
|
||||
fn comission_to_string(&self) -> String {
|
||||
if self.nominators_blocked {
|
||||
"blocked".to_string()
|
||||
} else {
|
||||
let result = self.commission as f64 / 1_000_000_000.0;
|
||||
format!("{:.1}%", result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for RewardDetails {
|
||||
fn set_active(&mut self, _current_tab: CurrentTab) { }
|
||||
}
|
||||
|
||||
impl Component for RewardDetails {
|
||||
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<Option<Action>> {
|
||||
match action {
|
||||
Action::SetValidatorPrefs(commission, disabled) => {
|
||||
self.commission = commission;
|
||||
self.nominators_blocked = disabled;
|
||||
}
|
||||
Action::Apy(apy) => self.apy = apy,
|
||||
Action::Inflation(inflation) => self.inflation = inflation,
|
||||
_ => {}
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
let [_, _, place] = super::validator_balance_layout(area);
|
||||
let (border_style, border_type) = self.palette.create_border_style(false);
|
||||
|
||||
let table = Table::new(
|
||||
vec![
|
||||
Row::new(vec![
|
||||
Cell::from(Text::from("Nominators".to_string()).alignment(Alignment::Left)),
|
||||
Cell::from(Text::from(self.comission_to_string()).alignment(Alignment::Right)),
|
||||
]),
|
||||
Row::new(vec![
|
||||
Cell::from(Text::from("Current APY".to_string()).alignment(Alignment::Left)),
|
||||
Cell::from(Text::from(self.apy.clone()).alignment(Alignment::Right)),
|
||||
]),
|
||||
Row::new(vec![
|
||||
Cell::from(Text::from("Inflation".to_string()).alignment(Alignment::Left)),
|
||||
Cell::from(Text::from(self.inflation.clone()).alignment(Alignment::Right)),
|
||||
]),
|
||||
],
|
||||
[
|
||||
Constraint::Min(11),
|
||||
Constraint::Min(0),
|
||||
],
|
||||
)
|
||||
.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("Reward details"));
|
||||
|
||||
frame.render_widget(table, place);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
115
src/components/validator/staking_details.rs
Normal file
115
src/components/validator/staking_details.rs
Normal file
@ -0,0 +1,115 @@
|
||||
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::{
|
||||
action::Action,
|
||||
config::Config,
|
||||
palette::StylePalette,
|
||||
};
|
||||
|
||||
pub struct StakingDetails {
|
||||
palette: StylePalette,
|
||||
staked_own: u128,
|
||||
staked_total: u128,
|
||||
}
|
||||
|
||||
impl Default for StakingDetails {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl StakingDetails {
|
||||
const TICKER: &str = " CSPR";
|
||||
const DECIMALS: usize = 5;
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
palette: StylePalette::default(),
|
||||
staked_own: 0,
|
||||
staked_total: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_u128(&self, value: u128) -> String {
|
||||
let value = value as f64 / 10f64.powi(18);
|
||||
let after = Self::DECIMALS;
|
||||
format!("{:.after$}{}", value, Self::TICKER)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for StakingDetails {
|
||||
fn set_active(&mut self, _current_tab: CurrentTab) { }
|
||||
}
|
||||
|
||||
impl Component for StakingDetails {
|
||||
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<Option<Action>> {
|
||||
match action {
|
||||
Action::SetStakedRatio(total, own) => {
|
||||
self.staked_total = total;
|
||||
self.staked_own = own;
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
let [_, place, _] = super::validator_balance_layout(area);
|
||||
let (border_style, border_type) = self.palette.create_border_style(false);
|
||||
|
||||
let table = Table::new(
|
||||
vec![
|
||||
Row::new(vec![
|
||||
Cell::from(Text::from("Stake value".to_string()).alignment(Alignment::Left)),
|
||||
Cell::from(Text::from(self.prepare_u128(self.staked_total)).alignment(Alignment::Right)),
|
||||
]),
|
||||
Row::new(vec![
|
||||
Cell::from(Text::from("Own stake".to_string()).alignment(Alignment::Left)),
|
||||
Cell::from(Text::from(self.prepare_u128(self.staked_own)).alignment(Alignment::Right)),
|
||||
]),
|
||||
Row::new(vec![
|
||||
Cell::from(Text::from("Other stake".to_string()).alignment(Alignment::Left)),
|
||||
Cell::from(Text::from(self.prepare_u128(self.staked_total.saturating_sub(self.staked_own))).alignment(Alignment::Right)),
|
||||
]),
|
||||
],
|
||||
[
|
||||
Constraint::Min(11),
|
||||
Constraint::Min(0),
|
||||
],
|
||||
)
|
||||
.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("Staking details"));
|
||||
|
||||
frame.render_widget(table, place);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
140
src/components/validator/stash_details.rs
Normal file
140
src/components/validator/stash_details.rs
Normal file
@ -0,0 +1,140 @@
|
||||
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::{
|
||||
action::Action,
|
||||
config::Config,
|
||||
palette::StylePalette,
|
||||
};
|
||||
|
||||
pub struct StashDetails {
|
||||
palette: StylePalette,
|
||||
is_bonded: bool,
|
||||
free_balance: u128,
|
||||
staked_total: u128,
|
||||
staked_active: u128,
|
||||
stash_account_id: [u8; 32],
|
||||
}
|
||||
|
||||
impl Default for StashDetails {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl StashDetails {
|
||||
const TICKER: &str = " CSPR";
|
||||
const DECIMALS: usize = 5;
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
palette: StylePalette::default(),
|
||||
is_bonded: false,
|
||||
free_balance: 0,
|
||||
staked_total: 0,
|
||||
staked_active: 0,
|
||||
stash_account_id: [0u8; 32],
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_u128(&self, value: u128) -> String {
|
||||
let value = value as f64 / 10f64.powi(18);
|
||||
let after = Self::DECIMALS;
|
||||
format!("{:.after$}{}", value, Self::TICKER)
|
||||
}
|
||||
|
||||
fn is_bonded_to_string(&self) -> String {
|
||||
if self.is_bonded {
|
||||
"bonded".to_string()
|
||||
} else {
|
||||
"no bond".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for StashDetails {
|
||||
fn set_active(&mut self, _current_tab: CurrentTab) { }
|
||||
}
|
||||
|
||||
impl Component for StashDetails {
|
||||
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<Option<Action>> {
|
||||
match action {
|
||||
Action::SetStashAccount(account_id) => self.stash_account_id = account_id,
|
||||
Action::SetBondedAmount(is_bonded) => self.is_bonded = is_bonded,
|
||||
Action::SetStakedAmountRatio(total, active) => {
|
||||
self.staked_total = total;
|
||||
self.staked_active = active;
|
||||
},
|
||||
Action::BalanceResponse(account_id, balance) if account_id == self.stash_account_id => {
|
||||
self.free_balance = balance.free
|
||||
.saturating_sub(balance.frozen)
|
||||
.saturating_sub(balance.reserved);
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
let [place, _, _] = super::validator_balance_layout(area);
|
||||
let (border_style, border_type) = self.palette.create_border_style(false);
|
||||
|
||||
let table = Table::new(
|
||||
vec![
|
||||
Row::new(vec![
|
||||
Cell::from(Text::from("Bond ready".to_string()).alignment(Alignment::Left)),
|
||||
Cell::from(Text::from(self.is_bonded_to_string()).alignment(Alignment::Right)),
|
||||
]),
|
||||
Row::new(vec![
|
||||
Cell::from(Text::from("Free balance".to_string()).alignment(Alignment::Left)),
|
||||
Cell::from(Text::from(self.prepare_u128(self.free_balance)).alignment(Alignment::Right)),
|
||||
]),
|
||||
Row::new(vec![
|
||||
Cell::from(Text::from("Total staked".to_string()).alignment(Alignment::Left)),
|
||||
Cell::from(Text::from(self.prepare_u128(self.staked_total)).alignment(Alignment::Right)),
|
||||
]),
|
||||
Row::new(vec![
|
||||
Cell::from(Text::from("Active staked".to_string()).alignment(Alignment::Left)),
|
||||
Cell::from(Text::from(self.prepare_u128(self.staked_active)).alignment(Alignment::Right)),
|
||||
]),
|
||||
],
|
||||
[
|
||||
Constraint::Min(14),
|
||||
Constraint::Min(0),
|
||||
],
|
||||
)
|
||||
.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("Stash details"));
|
||||
|
||||
frame.render_widget(table, place);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
354
src/components/validator/stash_info.rs
Normal file
354
src/components/validator/stash_info.rs
Normal file
@ -0,0 +1,354 @@
|
||||
use std::path::PathBuf;
|
||||
use std::fs::File;
|
||||
use std::io::{Write, BufRead, BufReader};
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use ratatui::layout::{Constraint, Margin};
|
||||
use ratatui::style::{Modifier, Stylize};
|
||||
use ratatui::{
|
||||
text::Text,
|
||||
layout::{Alignment, Rect},
|
||||
widgets::{
|
||||
Block, Cell, Row, Table, TableState, Scrollbar,
|
||||
ScrollbarOrientation, ScrollbarState,
|
||||
},
|
||||
Frame
|
||||
};
|
||||
|
||||
use subxt::{
|
||||
tx::PairSigner,
|
||||
ext::sp_core::{
|
||||
Pair as PairT,
|
||||
sr25519::Pair,
|
||||
crypto::{Ss58Codec, Ss58AddressFormat, AccountId32},
|
||||
},
|
||||
};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
use super::{PartialComponent, Component, CurrentTab};
|
||||
use crate::casper::CasperConfig;
|
||||
use crate::types::ActionLevel;
|
||||
use crate::{
|
||||
types::SessionKeyInfo,
|
||||
action::Action,
|
||||
config::Config,
|
||||
palette::StylePalette,
|
||||
};
|
||||
|
||||
pub struct StashInfo {
|
||||
is_active: bool,
|
||||
action_tx: Option<UnboundedSender<Action>>,
|
||||
network_tx: Option<Sender<Action>>,
|
||||
palette: StylePalette,
|
||||
scroll_state: ScrollbarState,
|
||||
table_state: TableState,
|
||||
stash_pair: Option<PairSigner<CasperConfig, Pair>>,
|
||||
stash_address: String,
|
||||
session_keys: std::collections::HashMap<String, SessionKeyInfo>,
|
||||
key_names: &'static [&'static str],
|
||||
file_path: PathBuf,
|
||||
}
|
||||
|
||||
impl Default for StashInfo {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl StashInfo {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
is_active: false,
|
||||
action_tx: None,
|
||||
network_tx: None,
|
||||
scroll_state: ScrollbarState::new(0),
|
||||
table_state: TableState::new(),
|
||||
palette: StylePalette::default(),
|
||||
stash_address: String::new(),
|
||||
stash_pair: None,
|
||||
session_keys: Default::default(),
|
||||
key_names: &["gran", "babe", "audi", "slow"],
|
||||
file_path: PathBuf::from("/etc/ghost/stash-key"),
|
||||
}
|
||||
}
|
||||
|
||||
fn log_event(&mut self, message: String, level: ActionLevel) {
|
||||
if let Some(action_tx) = &self.action_tx {
|
||||
let _ = action_tx.send(Action::ValidatorLog(message, level));
|
||||
}
|
||||
}
|
||||
|
||||
fn first_row(&mut self) {
|
||||
if self.session_keys.len() > 0 {
|
||||
self.table_state.select(Some(0));
|
||||
self.scroll_state = self.scroll_state.position(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn next_row(&mut self) {
|
||||
let i = match self.table_state.selected() {
|
||||
Some(i) => {
|
||||
if i >= self.session_keys.len() - 1 {
|
||||
i
|
||||
} else {
|
||||
i + 1
|
||||
}
|
||||
},
|
||||
None => 0,
|
||||
};
|
||||
self.table_state.select(Some(i));
|
||||
self.scroll_state = self.scroll_state.position(i);
|
||||
}
|
||||
|
||||
fn last_row(&mut self) {
|
||||
if self.session_keys.len() > 0 {
|
||||
let last = self.session_keys.len() - 1;
|
||||
self.table_state.select(Some(last));
|
||||
self.scroll_state = self.scroll_state.position(last);
|
||||
}
|
||||
}
|
||||
|
||||
fn previous_row(&mut self) {
|
||||
let i = match self.table_state.selected() {
|
||||
Some(i) => {
|
||||
if i == 0 {
|
||||
0
|
||||
} else {
|
||||
i - 1
|
||||
}
|
||||
},
|
||||
None => 0
|
||||
};
|
||||
self.table_state.select(Some(i));
|
||||
self.scroll_state = self.scroll_state.position(i);
|
||||
}
|
||||
|
||||
fn read_or_create_stash(&mut self) -> Result<()> {
|
||||
match File::open(&self.file_path) {
|
||||
Ok(file) => {
|
||||
let reader = BufReader::new(file);
|
||||
if let Some(Ok(line)) = reader.lines().next() {
|
||||
let stash_key = line.replace("\n", "");
|
||||
let stash_key = &stash_key[2..];
|
||||
|
||||
let seed: [u8; 32] = hex::decode(stash_key)
|
||||
.expect("stored seed is valid hex string; qed")
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.expect("stored seed is valid length; qed");
|
||||
|
||||
let pair = Pair::from_seed(&seed);
|
||||
let account_id = pair.public().0;
|
||||
let address = AccountId32::from(account_id)
|
||||
.to_ss58check_with_version(Ss58AddressFormat::custom(1996));
|
||||
let pair_signer = PairSigner::<CasperConfig, Pair>::new(pair);
|
||||
|
||||
self.initiate_stash_info(account_id);
|
||||
self.log_event(
|
||||
format!("stash key {address} read from disk"),
|
||||
ActionLevel::Info);
|
||||
|
||||
self.stash_address = address;
|
||||
self.stash_pair = Some(pair_signer);
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
self.log_event(
|
||||
format!("file at '{:?}' is empty, trying to create new key", &self.file_path),
|
||||
ActionLevel::Warn);
|
||||
|
||||
self.generate_and_save_new_key()
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
self.log_event(
|
||||
format!("file at '{:?}' not found, trying to create new key", &self.file_path),
|
||||
ActionLevel::Warn);
|
||||
|
||||
self.generate_and_save_new_key()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_and_save_new_key(&mut self) -> Result<()> {
|
||||
let (pair, seed) = Pair::generate(); // TODO: revisit
|
||||
let secret_seed = hex::encode(seed);
|
||||
let address = AccountId32::from(pair.public().0)
|
||||
.to_ss58check_with_version(Ss58AddressFormat::custom(1996));
|
||||
let account_id = pair.public().0;
|
||||
let pair_signer = PairSigner::<CasperConfig, Pair>::new(pair);
|
||||
|
||||
let mut new_file = File::create(&self.file_path)?;
|
||||
writeln!(new_file, "0x{}", &secret_seed)?;
|
||||
|
||||
self.initiate_stash_info(account_id);
|
||||
self.log_event(
|
||||
format!("new stash key {} created and stored at {:?}", &address, self.file_path),
|
||||
ActionLevel::Info);
|
||||
|
||||
self.stash_address = address;
|
||||
self.stash_pair = Some(pair_signer);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn initiate_stash_info(&self, account_id: [u8; 32]) {
|
||||
if let Some(action_tx) = &self.action_tx {
|
||||
let _ = action_tx.send(Action::SetStashAccount(account_id));
|
||||
}
|
||||
|
||||
if let Some(network_tx) = &self.network_tx {
|
||||
let _ = network_tx.send(Action::BalanceRequest(account_id, false));
|
||||
let _ = network_tx.send(Action::GetValidatorLedger(account_id));
|
||||
let _ = network_tx.send(Action::GetIsStashBonded(account_id));
|
||||
let _ = network_tx.send(Action::GetErasStakersOverview(account_id));
|
||||
let _ = network_tx.send(Action::GetValidatorPrefs(account_id));
|
||||
let _ = network_tx.send(Action::GetNominatorsByValidator(account_id));
|
||||
let _ = network_tx.send(Action::GetQueuedSessionKeys(account_id));
|
||||
let _ = network_tx.send(Action::GetSessionKeys(account_id));
|
||||
let _ = network_tx.send(Action::GetValidatorAllRewards(account_id));
|
||||
}
|
||||
}
|
||||
|
||||
fn set_new_key(&mut self, name: String, key_info: SessionKeyInfo) {
|
||||
if let Some(info) = self.session_keys.get_mut(&name) {
|
||||
let key_changed = info.key != key_info.key;
|
||||
let is_stored_changed = info.is_stored != key_info.is_stored;
|
||||
|
||||
if key_changed || is_stored_changed {
|
||||
*info = key_info;
|
||||
}
|
||||
} else {
|
||||
let _ = self.session_keys.insert(name, key_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for StashInfo {
|
||||
fn set_active(&mut self, current_tab: CurrentTab) {
|
||||
match current_tab {
|
||||
CurrentTab::StashInfo => self.is_active = true,
|
||||
_ => {
|
||||
self.is_active = false;
|
||||
self.table_state.select(None);
|
||||
self.scroll_state = self.scroll_state.position(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for StashInfo {
|
||||
fn register_network_handler(&mut self, tx: Sender<Action>) -> Result<()> {
|
||||
self.network_tx = Some(tx);
|
||||
Ok(())
|
||||
}
|
||||
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> 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::Wallet) {
|
||||
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());
|
||||
}
|
||||
self.read_or_create_stash()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||
match action {
|
||||
Action::SetSessionKey(name, key_info) => self.set_new_key(name, key_info),
|
||||
_ => {}
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
|
||||
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_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
|
||||
.iter()
|
||||
.map(|name| {
|
||||
let address_text = match self.session_keys.get(*name) {
|
||||
Some(key_info) => {
|
||||
let mut address_text = Text::from(key_info.key.clone()).alignment(Alignment::Center);
|
||||
if !key_info.is_stored {
|
||||
address_text = address_text.add_modifier(Modifier::CROSSED_OUT);
|
||||
}
|
||||
address_text
|
||||
},
|
||||
None => Text::from("-").alignment(Alignment::Center),
|
||||
};
|
||||
let queued_name = format!("q_{}", name);
|
||||
let queued_address_text = match self.session_keys.get(&queued_name) {
|
||||
Some(key_info) => {
|
||||
let mut queued_address_text = Text::from(key_info.key.clone()).alignment(Alignment::Right);
|
||||
if !key_info.is_stored {
|
||||
queued_address_text = queued_address_text.add_modifier(Modifier::CROSSED_OUT);
|
||||
}
|
||||
queued_address_text
|
||||
},
|
||||
None => Text::from("-").alignment(Alignment::Right),
|
||||
};
|
||||
Row::new(vec![
|
||||
Cell::from(Text::from(name.to_string()).alignment(Alignment::Left)),
|
||||
Cell::from(address_text),
|
||||
Cell::from(Text::from("-->".to_string()).alignment(Alignment::Center)),
|
||||
Cell::from(queued_address_text),
|
||||
])
|
||||
}),
|
||||
[
|
||||
Constraint::Length(4),
|
||||
Constraint::Min(0),
|
||||
Constraint::Length(3),
|
||||
Constraint::Min(0),
|
||||
],
|
||||
)
|
||||
.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(self.stash_address.clone()));
|
||||
|
||||
let scrollbar = Scrollbar::default()
|
||||
.orientation(ScrollbarOrientation::VerticalRight)
|
||||
.begin_symbol(None)
|
||||
.end_symbol(None)
|
||||
.style(self.palette.create_scrollbar_style());
|
||||
|
||||
frame.render_stateful_widget(table, place, &mut self.table_state);
|
||||
frame.render_stateful_widget(
|
||||
scrollbar,
|
||||
place.inner(Margin { vertical: 1, horizontal: 1 }),
|
||||
&mut self.scroll_state,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
227
src/components/validator/withdrawals.rs
Normal file
227
src/components/validator/withdrawals.rs
Normal file
@ -0,0 +1,227 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use ratatui::layout::{Constraint, Margin};
|
||||
use ratatui::style::{Modifier, Stylize};
|
||||
use ratatui::{
|
||||
text::Text,
|
||||
layout::{Alignment, Rect},
|
||||
widgets::{
|
||||
Block, Cell, Row, Table, TableState, Scrollbar,
|
||||
ScrollbarOrientation, ScrollbarState,
|
||||
},
|
||||
Frame
|
||||
};
|
||||
|
||||
use super::{PartialComponent, Component, CurrentTab};
|
||||
use crate::{
|
||||
action::Action,
|
||||
config::Config,
|
||||
palette::StylePalette,
|
||||
};
|
||||
|
||||
pub struct Withdrawals {
|
||||
is_active: bool,
|
||||
palette: StylePalette,
|
||||
scroll_state: ScrollbarState,
|
||||
table_state: TableState,
|
||||
unlockings: BTreeMap<u32, u128>,
|
||||
current_era: u32,
|
||||
}
|
||||
|
||||
impl Default for Withdrawals {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Withdrawals {
|
||||
const TICKER: &str = " CSPR";
|
||||
const DECIMALS: usize = 5;
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
is_active: false,
|
||||
scroll_state: ScrollbarState::new(0),
|
||||
table_state: TableState::new(),
|
||||
palette: StylePalette::default(),
|
||||
unlockings: BTreeMap::new(),
|
||||
current_era: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn first_row(&mut self) {
|
||||
if self.unlockings.len() > 0 {
|
||||
self.table_state.select(Some(0));
|
||||
self.scroll_state = self.scroll_state.position(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn next_row(&mut self) {
|
||||
let i = match self.table_state.selected() {
|
||||
Some(i) => {
|
||||
if i >= self.unlockings.len() - 1 {
|
||||
i
|
||||
} else {
|
||||
i + 1
|
||||
}
|
||||
},
|
||||
None => 0,
|
||||
};
|
||||
self.table_state.select(Some(i));
|
||||
self.scroll_state = self.scroll_state.position(i);
|
||||
}
|
||||
|
||||
fn last_row(&mut self) {
|
||||
if self.unlockings.len() > 0 {
|
||||
let last = self.unlockings.len() - 1;
|
||||
self.table_state.select(Some(last));
|
||||
self.scroll_state = self.scroll_state.position(last);
|
||||
}
|
||||
}
|
||||
|
||||
fn previous_row(&mut self) {
|
||||
let i = match self.table_state.selected() {
|
||||
Some(i) => {
|
||||
if i == 0 {
|
||||
0
|
||||
} else {
|
||||
i - 1
|
||||
}
|
||||
},
|
||||
None => 0
|
||||
};
|
||||
self.table_state.select(Some(i));
|
||||
self.scroll_state = self.scroll_state.position(i);
|
||||
}
|
||||
|
||||
fn add_new_unlocking(&mut self, era_index: u32, unlocking: u128) {
|
||||
match self.unlockings.get_mut(&era_index) {
|
||||
Some(unlck) => *unlck = unlocking,
|
||||
None => {
|
||||
let _ = self.unlockings.insert(era_index, unlocking);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_u128(&self, value: u128) -> String {
|
||||
let value = value as f64 / 10f64.powi(18);
|
||||
let after = Self::DECIMALS;
|
||||
format!("{:.after$}{}", value, Self::TICKER)
|
||||
}
|
||||
|
||||
fn estimate_time(&self, era_index: u32) -> String {
|
||||
if era_index > self.current_era {
|
||||
format!("{} eras", era_index - self.current_era)
|
||||
} else {
|
||||
String::from("ready")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for Withdrawals {
|
||||
fn set_active(&mut self, current_tab: CurrentTab) {
|
||||
match current_tab {
|
||||
CurrentTab::Withdrawals => self.is_active = true,
|
||||
_ => {
|
||||
self.is_active = false;
|
||||
self.table_state.select(None);
|
||||
self.scroll_state = self.scroll_state.position(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Withdrawals {
|
||||
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<Option<Action>> {
|
||||
match action {
|
||||
Action::SetCurrentEra(current_era) => self.current_era = current_era,
|
||||
Action::SetValidatorEraUnlocking(era_index, unlocking) =>
|
||||
self.add_new_unlocking(era_index, unlocking),
|
||||
_ => {}
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
|
||||
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_statistics_layout(area);
|
||||
let (border_style, border_type) = self.palette.create_border_style(self.is_active);
|
||||
let table = Table::new(
|
||||
self.unlockings
|
||||
.iter()
|
||||
.map(|(key, value)| {
|
||||
let mut era_index_text = Text::from(key.to_string()).alignment(Alignment::Left);
|
||||
let mut est_era_text = Text::from(self.estimate_time(*key)).alignment(Alignment::Center);
|
||||
let mut value_text = Text::from(self.prepare_u128(*value)).alignment(Alignment::Right);
|
||||
|
||||
if *key > self.current_era {
|
||||
era_index_text = era_index_text.add_modifier(Modifier::CROSSED_OUT);
|
||||
est_era_text = est_era_text.add_modifier(Modifier::CROSSED_OUT);
|
||||
value_text = value_text.add_modifier(Modifier::CROSSED_OUT);
|
||||
}
|
||||
|
||||
Row::new(vec![
|
||||
Cell::from(era_index_text),
|
||||
Cell::from(est_era_text),
|
||||
Cell::from(value_text),
|
||||
])
|
||||
}),
|
||||
[
|
||||
Constraint::Length(12),
|
||||
Constraint::Length(13),
|
||||
Constraint::Min(0),
|
||||
],
|
||||
)
|
||||
.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("Withdrawals"));
|
||||
|
||||
let scrollbar = Scrollbar::default()
|
||||
.orientation(ScrollbarOrientation::VerticalRight)
|
||||
.begin_symbol(None)
|
||||
.end_symbol(None)
|
||||
.style(self.palette.create_scrollbar_style());
|
||||
|
||||
frame.render_stateful_widget(table, place, &mut self.table_state);
|
||||
frame.render_stateful_widget(
|
||||
scrollbar,
|
||||
place.inner(Margin { vertical: 1, horizontal: 1 }),
|
||||
&mut self.scroll_state,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -16,7 +16,6 @@ use ratatui::{
|
||||
Frame
|
||||
};
|
||||
use subxt::{
|
||||
tx::PairSigner,
|
||||
ext::sp_core::{
|
||||
Pair as PairT,
|
||||
sr25519::Pair,
|
||||
@ -27,7 +26,6 @@ use tokio::sync::mpsc::UnboundedSender;
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
use super::{PartialComponent, Component, CurrentTab};
|
||||
use crate::casper::CasperConfig;
|
||||
use crate::types::{SystemAccount, ActionLevel};
|
||||
use crate::{
|
||||
action::Action,
|
||||
@ -40,8 +38,6 @@ struct AccountInfo {
|
||||
address: String,
|
||||
account_id: [u8; 32],
|
||||
seed: String,
|
||||
#[allow(dead_code)]
|
||||
pair_signer: PairSigner<CasperConfig, Pair>,
|
||||
}
|
||||
|
||||
pub struct Accounts {
|
||||
@ -140,11 +136,10 @@ impl Accounts {
|
||||
}
|
||||
|
||||
fn create_new_account(&mut self, name: String) {
|
||||
let (pair, seed) = Pair::generate();
|
||||
let (pair, seed) = Pair::generate(); // TODO: generate_with_phrase()
|
||||
let secret_seed = hex::encode(seed);
|
||||
let account_id = pair.public().0;
|
||||
let pair_signer = PairSigner::<CasperConfig, Pair>::new(pair);
|
||||
let address = AccountId32::from(seed.clone())
|
||||
let address = AccountId32::from(seed)
|
||||
.to_ss58check_with_version(Ss58AddressFormat::custom(1996));
|
||||
|
||||
self.log_event(
|
||||
@ -158,7 +153,6 @@ impl Accounts {
|
||||
address,
|
||||
account_id,
|
||||
seed: secret_seed,
|
||||
pair_signer,
|
||||
});
|
||||
self.last_row();
|
||||
self.save_to_file();
|
||||
@ -242,7 +236,6 @@ impl Accounts {
|
||||
let account_id = pair.public().0;
|
||||
let address = AccountId32::from(account_id)
|
||||
.to_ss58check_with_version(Ss58AddressFormat::custom(1996));
|
||||
let pair_signer = PairSigner::<CasperConfig, Pair>::new(pair);
|
||||
|
||||
self.send_balance_request(account_id, false);
|
||||
self.wallet_keys.push(AccountInfo {
|
||||
@ -250,7 +243,6 @@ impl Accounts {
|
||||
account_id,
|
||||
address,
|
||||
seed: wallet_key.to_string(),
|
||||
pair_signer,
|
||||
});
|
||||
}
|
||||
self.log_event(format!("read {} wallets from disk",
|
||||
@ -288,7 +280,6 @@ impl Accounts {
|
||||
let account_id = pair.public().0;
|
||||
let address = AccountId32::from(pair.public().0)
|
||||
.to_ss58check_with_version(Ss58AddressFormat::custom(1996));
|
||||
let pair_signer = PairSigner::<CasperConfig, Pair>::new(pair);
|
||||
|
||||
let mut new_file = File::create(file_path)?;
|
||||
writeln!(new_file, "ghostie:0x{}", &secret_seed)?;
|
||||
@ -299,7 +290,6 @@ impl Accounts {
|
||||
address,
|
||||
account_id,
|
||||
seed: secret_seed,
|
||||
pair_signer,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ use clap::Parser;
|
||||
use color_eyre::Result;
|
||||
use subxt::{
|
||||
OnlineClient,
|
||||
backend::{legacy::LegacyRpcMethods, rpc::RpcClient},
|
||||
backend::rpc::RpcClient,
|
||||
};
|
||||
|
||||
mod action;
|
||||
@ -54,9 +54,7 @@ async fn main() -> Result<()> {
|
||||
let (action_tx, action_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
|
||||
let rpc_client = RpcClient::from_url(args.rpc_endpoint).await?;
|
||||
let legacy_client_api = LegacyRpcMethods::<CasperConfig>::new(rpc_client.clone());
|
||||
let online_client =
|
||||
OnlineClient::<CasperConfig>::from_rpc_client(rpc_client.clone()).await?;
|
||||
let online_client = OnlineClient::<CasperConfig>::from_rpc_client(rpc_client.clone()).await?;
|
||||
|
||||
let finalized_blocks_sub = online_client.blocks().subscribe_finalized().await?;
|
||||
let best_blocks_sub = online_client.blocks().subscribe_best().await?;
|
||||
@ -66,7 +64,6 @@ async fn main() -> Result<()> {
|
||||
let mut network = network::Network::new(
|
||||
cloned_action_tx,
|
||||
online_client,
|
||||
legacy_client_api,
|
||||
rpc_client,
|
||||
);
|
||||
start_tokio_action_loop(sync_io_rx, &mut network);
|
||||
|
@ -1,27 +1,30 @@
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use color_eyre::Result;
|
||||
use subxt::backend::legacy::rpc_methods::LegacyRpcMethods;
|
||||
use subxt::{backend::{legacy::rpc_methods::SystemHealth, rpc::RpcClient}, rpc_params};
|
||||
|
||||
use crate::{action::Action, casper::CasperConfig};
|
||||
use crate::{action::Action, types::PeerInformation};
|
||||
|
||||
pub async fn get_node_name(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &LegacyRpcMethods<CasperConfig>,
|
||||
rpc_client: &RpcClient,
|
||||
) -> Result<()> {
|
||||
let maybe_node_name = api.system_name().await.ok();
|
||||
let maybe_node_name = rpc_client
|
||||
.request("system_name", rpc_params![])
|
||||
.await
|
||||
.ok();
|
||||
action_tx.send(Action::SetNodeName(maybe_node_name))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_system_health(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &LegacyRpcMethods<CasperConfig>,
|
||||
rpc_client: &RpcClient,
|
||||
) -> Result<()> {
|
||||
let (maybe_peers, is_syncing, should_have_peers) = api
|
||||
.system_health()
|
||||
let (maybe_peers, is_syncing, should_have_peers) = rpc_client
|
||||
.request("system_health", rpc_params![])
|
||||
.await
|
||||
.ok()
|
||||
.map_or((None, false, false), |health| (
|
||||
.map_or((None, false, false), |health: SystemHealth| (
|
||||
Some(health.peers),
|
||||
health.is_syncing,
|
||||
health.should_have_peers,
|
||||
@ -35,10 +38,11 @@ pub async fn get_system_health(
|
||||
|
||||
pub async fn get_genesis_hash(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &LegacyRpcMethods<CasperConfig>,
|
||||
rpc_client: &RpcClient,
|
||||
) -> Result<()> {
|
||||
let maybe_genesis_hash = api
|
||||
.genesis_hash()
|
||||
let params = rpc_params![0u32];
|
||||
let maybe_genesis_hash = rpc_client
|
||||
.request("chain_getBlockHash", params)
|
||||
.await
|
||||
.ok();
|
||||
action_tx.send(Action::SetGenesisHash(maybe_genesis_hash))?;
|
||||
@ -47,10 +51,10 @@ pub async fn get_genesis_hash(
|
||||
|
||||
pub async fn get_chain_name(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &LegacyRpcMethods<CasperConfig>,
|
||||
rpc_client: &RpcClient,
|
||||
) -> Result<()> {
|
||||
let maybe_chain_name = api
|
||||
.system_chain()
|
||||
let maybe_chain_name = rpc_client
|
||||
.request("system_chain", rpc_params![])
|
||||
.await
|
||||
.ok();
|
||||
action_tx.send(Action::SetChainName(maybe_chain_name))?;
|
||||
@ -59,12 +63,60 @@ pub async fn get_chain_name(
|
||||
|
||||
pub async fn get_system_version(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &LegacyRpcMethods<CasperConfig>,
|
||||
rpc_client: &RpcClient,
|
||||
) -> Result<()> {
|
||||
let maybe_system_version = api
|
||||
.system_version()
|
||||
let maybe_system_version = rpc_client
|
||||
.request("system_version", rpc_params![])
|
||||
.await
|
||||
.ok();
|
||||
action_tx.send(Action::SetChainVersion(maybe_system_version))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_pending_extrinsics(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
rpc_client: &RpcClient,
|
||||
) -> Result<()> {
|
||||
let pending_extrinsics: Vec<String> = rpc_client
|
||||
.request("author_pendingExtrinsics", rpc_params![])
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
action_tx.send(Action::SetPendingExtrinsicsLength(pending_extrinsics.len()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_connected_peers(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
rpc_client: &RpcClient,
|
||||
) -> Result<()> {
|
||||
let connected_peers: Vec<PeerInformation> = rpc_client
|
||||
.request("system_peers", rpc_params![])
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
action_tx.send(Action::SetConnectedPeers(connected_peers))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_listen_addresses(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
rpc_client: &RpcClient,
|
||||
) -> Result<()> {
|
||||
let listen_addresses: Vec<String> = rpc_client
|
||||
.request("system_localListenAddresses", rpc_params![])
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
action_tx.send(Action::SetListenAddresses(listen_addresses))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_local_identity(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
rpc_client: &RpcClient,
|
||||
) -> Result<()> {
|
||||
let local_peer_id: String = rpc_client
|
||||
.request("system_localPeerId", rpc_params![])
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
action_tx.send(Action::SetLocalIdentity(local_peer_id))?;
|
||||
Ok(())
|
||||
}
|
||||
|
115
src/network/miscellaneous.rs
Normal file
115
src/network/miscellaneous.rs
Normal file
@ -0,0 +1,115 @@
|
||||
use subxt::ext::sp_runtime::Perbill;
|
||||
|
||||
// generated outside, based on params
|
||||
// MIN_INFLATION: u32 = 0_025_000;
|
||||
// MAX_INFLATION: u32 = 0_100_000;
|
||||
// IDEAL_STAKE: u32 = 0_750_000;
|
||||
// FALLOFF: u32 = 0_050_000;
|
||||
// MAX_PIECE_COUNT: u32 = 40;
|
||||
const PIECEWISE_LINEAR_POUNTS: [(Perbill, Perbill); 33] = [
|
||||
(Perbill::from_parts(0), Perbill::from_parts(25000000)),
|
||||
(Perbill::from_parts(750000000), Perbill::from_parts(100000000)),
|
||||
(Perbill::from_parts(758333000), Perbill::from_parts(91817000)),
|
||||
(Perbill::from_parts(766666000), Perbill::from_parts(84528000)),
|
||||
(Perbill::from_parts(774999000), Perbill::from_parts(78033000)),
|
||||
(Perbill::from_parts(783331000), Perbill::from_parts(72248000)),
|
||||
(Perbill::from_parts(791663000), Perbill::from_parts(67094000)),
|
||||
(Perbill::from_parts(799996000), Perbill::from_parts(62502000)),
|
||||
(Perbill::from_parts(808328000), Perbill::from_parts(58411000)),
|
||||
(Perbill::from_parts(816661000), Perbill::from_parts(54766000)),
|
||||
(Perbill::from_parts(824993000), Perbill::from_parts(51519000)),
|
||||
(Perbill::from_parts(833325000), Perbill::from_parts(48626000)),
|
||||
(Perbill::from_parts(841656000), Perbill::from_parts(46049000)),
|
||||
(Perbill::from_parts(849988000), Perbill::from_parts(43753000)),
|
||||
(Perbill::from_parts(858321000), Perbill::from_parts(41707000)),
|
||||
(Perbill::from_parts(866651000), Perbill::from_parts(39885000)),
|
||||
(Perbill::from_parts(874984000), Perbill::from_parts(38261000)),
|
||||
(Perbill::from_parts(883313000), Perbill::from_parts(36815000)),
|
||||
(Perbill::from_parts(891646000), Perbill::from_parts(35526000)),
|
||||
(Perbill::from_parts(899976000), Perbill::from_parts(34378000)),
|
||||
(Perbill::from_parts(908308000), Perbill::from_parts(33355000)),
|
||||
(Perbill::from_parts(916636000), Perbill::from_parts(32444000)),
|
||||
(Perbill::from_parts(924968000), Perbill::from_parts(31632000)),
|
||||
(Perbill::from_parts(933295000), Perbill::from_parts(30909000)),
|
||||
(Perbill::from_parts(941619000), Perbill::from_parts(30265000)),
|
||||
(Perbill::from_parts(949946000), Perbill::from_parts(29691000)),
|
||||
(Perbill::from_parts(958265000), Perbill::from_parts(29180000)),
|
||||
(Perbill::from_parts(966598000), Perbill::from_parts(28724000)),
|
||||
(Perbill::from_parts(974925000), Perbill::from_parts(28318000)),
|
||||
(Perbill::from_parts(983258000), Perbill::from_parts(27956000)),
|
||||
(Perbill::from_parts(991578000), Perbill::from_parts(27634000)),
|
||||
(Perbill::from_parts(999899000), Perbill::from_parts(27347000)),
|
||||
(Perbill::from_parts(1000000000), Perbill::from_parts(27343000)),
|
||||
];
|
||||
const MAXIMUM_INFLATION: Perbill = Perbill::from_parts(100000000);
|
||||
|
||||
pub fn calculate_for_fraction(n: u128, d: u128) -> (Perbill, Perbill) {
|
||||
let n = n.min(d.clone());
|
||||
|
||||
if PIECEWISE_LINEAR_POUNTS.is_empty() {
|
||||
return (MAXIMUM_INFLATION, Perbill::zero())
|
||||
}
|
||||
|
||||
let next_point_index = PIECEWISE_LINEAR_POUNTS.iter().position(|p| n < p.0 * d.clone());
|
||||
|
||||
let (prev, next) = if let Some(next_point_index) = next_point_index {
|
||||
if let Some(previous_point_index) = next_point_index.checked_sub(1) {
|
||||
(PIECEWISE_LINEAR_POUNTS[previous_point_index], PIECEWISE_LINEAR_POUNTS[next_point_index])
|
||||
} else {
|
||||
// There is no previous points, take first point ordinate
|
||||
let fraction = PIECEWISE_LINEAR_POUNTS.first().map(|p| p.1).unwrap_or_else(Perbill::zero);
|
||||
return (MAXIMUM_INFLATION, fraction)
|
||||
}
|
||||
} else {
|
||||
// There is no next points, take last point ordinate
|
||||
let fraction = PIECEWISE_LINEAR_POUNTS.last().map(|p| p.1).unwrap_or_else(Perbill::zero);
|
||||
return (MAXIMUM_INFLATION, fraction)
|
||||
};
|
||||
|
||||
let delta_y = multiply_by_rational_saturating(
|
||||
abs_sub(n.clone(), prev.0 * d.clone()),
|
||||
abs_sub(next.1.deconstruct(), prev.1.deconstruct()),
|
||||
// Must not saturate as prev abscissa > next abscissa
|
||||
next.0.deconstruct().saturating_sub(prev.0.deconstruct()),
|
||||
);
|
||||
|
||||
// If both subtractions are same sign then result is positive
|
||||
let fraction = if (n > prev.0 * d.clone()) == (next.1.deconstruct() > prev.1.deconstruct()) {
|
||||
(prev.1 * d).saturating_add(delta_y)
|
||||
} else {
|
||||
// Otherwise result is negative
|
||||
(prev.1 * d).saturating_sub(delta_y)
|
||||
};
|
||||
|
||||
(MAXIMUM_INFLATION, Perbill::from_rational(fraction, d))
|
||||
}
|
||||
|
||||
fn abs_sub<N: Ord + core::ops::Sub<Output = N> + Clone>(a: N, b: N) -> N where {
|
||||
a.clone().max(b.clone()) - a.min(b)
|
||||
}
|
||||
|
||||
fn multiply_by_rational_saturating(value: u128, p: u32, q: u32) -> u128 {
|
||||
let q = q.max(1);
|
||||
let result_divisor_part = (value / q as u128).saturating_mul(p as u128);
|
||||
let result_remainder_part = {
|
||||
let rem = value % q as u128;
|
||||
let rem_u32 = rem as u32;
|
||||
let rem_part = rem_u32 as u64 * p as u64 / q as u64;
|
||||
rem_part as u128
|
||||
};
|
||||
result_divisor_part.saturating_add(result_remainder_part)
|
||||
}
|
||||
|
||||
pub fn prepare_perbill_fraction_string(value: Perbill) -> String {
|
||||
let d = value.deconstruct();
|
||||
let mut m = 10_000_000;
|
||||
|
||||
let units = d / m;
|
||||
let rest = d % m;
|
||||
|
||||
for _ in 0..2 {
|
||||
m /= 10;
|
||||
}
|
||||
|
||||
format!("{}.{}%", units, rest / m)
|
||||
}
|
@ -1,18 +1,20 @@
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use color_eyre::Result;
|
||||
use subxt::{
|
||||
backend::{
|
||||
legacy::LegacyRpcMethods,
|
||||
rpc::RpcClient,
|
||||
},
|
||||
backend::rpc::RpcClient,
|
||||
tx::{TxProgress, TxStatus},
|
||||
utils::H256,
|
||||
OnlineClient,
|
||||
};
|
||||
|
||||
mod legacy_rpc_calls;
|
||||
mod predefinded_calls;
|
||||
mod predefined_calls;
|
||||
mod predefined_txs;
|
||||
mod subscriptions;
|
||||
mod miscellaneous;
|
||||
mod raw_calls;
|
||||
|
||||
pub use miscellaneous::{prepare_perbill_fraction_string, calculate_for_fraction};
|
||||
|
||||
use crate::{
|
||||
types::ActionLevel,
|
||||
@ -25,10 +27,10 @@ pub use subscriptions::{FinalizedSubscription, BestSubscription};
|
||||
pub struct Network {
|
||||
action_tx: UnboundedSender<Action>,
|
||||
online_client_api: OnlineClient<CasperConfig>,
|
||||
legacy_client_api: LegacyRpcMethods<CasperConfig>,
|
||||
rpc_client: RpcClient,
|
||||
best_hash: Option<H256>,
|
||||
finalized_hash: Option<H256>,
|
||||
stash_to_watch: Option<[u8; 32]>,
|
||||
accounts_to_watch: std::collections::HashSet<[u8; 32]>,
|
||||
transactions_to_watch: Vec<TxProgress<CasperConfig, OnlineClient<CasperConfig>>>,
|
||||
}
|
||||
@ -37,27 +39,42 @@ impl Network {
|
||||
pub fn new(
|
||||
action_tx: UnboundedSender<Action>,
|
||||
online_client_api: OnlineClient<CasperConfig>,
|
||||
legacy_client_api: LegacyRpcMethods<CasperConfig>,
|
||||
rpc_client: RpcClient,
|
||||
) -> Self {
|
||||
Self {
|
||||
action_tx,
|
||||
online_client_api,
|
||||
legacy_client_api,
|
||||
rpc_client,
|
||||
best_hash: None,
|
||||
finalized_hash: None,
|
||||
stash_to_watch: None,
|
||||
accounts_to_watch: Default::default(),
|
||||
transactions_to_watch: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn store_stash_if_possible(&mut self, new_stash: [u8; 32]) {
|
||||
match self.stash_to_watch {
|
||||
Some(stash) if stash == new_stash => {},
|
||||
_ => self.stash_to_watch = Some(new_stash),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_network_event(&mut self, io_event: Action) -> Result<()> {
|
||||
match io_event {
|
||||
Action::NewBestHash(hash) => {
|
||||
self.best_hash = Some(hash);
|
||||
if let Some(stash_to_watch) = self.stash_to_watch {
|
||||
predefined_calls::get_session_keys(&self.action_tx, &self.online_client_api, &self.rpc_client, &stash_to_watch).await?;
|
||||
predefined_calls::get_queued_session_keys(&self.action_tx, &self.online_client_api, &self.rpc_client, &stash_to_watch).await?;
|
||||
predefined_calls::get_nominators_by_validator(&self.action_tx, &self.online_client_api, &stash_to_watch).await?;
|
||||
predefined_calls::get_validator_prefs(&self.action_tx, &self.online_client_api, &stash_to_watch).await?;
|
||||
predefined_calls::get_staking_value_ratio(&self.action_tx, &self.online_client_api, &stash_to_watch).await?;
|
||||
predefined_calls::get_is_stash_bonded(&self.action_tx, &self.online_client_api, &stash_to_watch).await?;
|
||||
predefined_calls::get_validators_ledger(&self.action_tx, &self.online_client_api, &stash_to_watch).await?;
|
||||
}
|
||||
for account_id in self.accounts_to_watch.iter() {
|
||||
predefinded_calls::get_balance(&self.action_tx, &self.online_client_api, &account_id).await?;
|
||||
predefined_calls::get_balance(&self.action_tx, &self.online_client_api, &account_id).await?;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
@ -100,26 +117,66 @@ impl Network {
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
Action::GetSystemHealth => legacy_rpc_calls::get_system_health(&self.action_tx, &self.legacy_client_api).await,
|
||||
Action::GetNodeName => legacy_rpc_calls::get_node_name(&self.action_tx, &self.legacy_client_api).await,
|
||||
Action::GetGenesisHash => legacy_rpc_calls::get_genesis_hash(&self.action_tx, &self.legacy_client_api).await,
|
||||
Action::GetChainName => legacy_rpc_calls::get_chain_name(&self.action_tx, &self.legacy_client_api).await,
|
||||
Action::GetChainVersion => legacy_rpc_calls::get_system_version(&self.action_tx, &self.legacy_client_api).await,
|
||||
Action::GetBlockAuthor(hash, logs) => predefinded_calls::get_block_author(&self.action_tx, &self.online_client_api, &logs, &hash).await,
|
||||
Action::GetActiveEra => predefinded_calls::get_active_era(&self.action_tx, &self.online_client_api).await,
|
||||
Action::GetEpochProgress => predefinded_calls::get_epoch_progress(&self.action_tx, &self.online_client_api).await,
|
||||
Action::GetPendingExtrinsics => predefinded_calls::get_pending_extrinsics(&self.action_tx, &self.rpc_client).await,
|
||||
Action::GetSystemHealth => legacy_rpc_calls::get_system_health(&self.action_tx, &self.rpc_client).await,
|
||||
Action::GetNodeName => legacy_rpc_calls::get_node_name(&self.action_tx, &self.rpc_client).await,
|
||||
Action::GetGenesisHash => legacy_rpc_calls::get_genesis_hash(&self.action_tx, &self.rpc_client).await,
|
||||
Action::GetChainName => legacy_rpc_calls::get_chain_name(&self.action_tx, &self.rpc_client).await,
|
||||
Action::GetChainVersion => legacy_rpc_calls::get_system_version(&self.action_tx, &self.rpc_client).await,
|
||||
Action::GetPendingExtrinsics => legacy_rpc_calls::get_pending_extrinsics(&self.action_tx, &self.rpc_client).await,
|
||||
Action::GetConnectedPeers => legacy_rpc_calls::get_connected_peers(&self.action_tx, &self.rpc_client).await,
|
||||
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::GetExistentialDeposit => predefinded_calls::get_existential_deposit(&self.action_tx, &self.online_client_api).await,
|
||||
Action::GetTotalIssuance => predefinded_calls::get_total_issuance(&self.action_tx, &self.online_client_api).await,
|
||||
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::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,
|
||||
Action::GetValidatorsNumber => predefined_calls::get_validators_number(&self.action_tx, &self.online_client_api).await,
|
||||
Action::GetNominatorsNumber => predefined_calls::get_nominators_number(&self.action_tx, &self.online_client_api).await,
|
||||
Action::GetInflation => predefined_calls::get_inflation(&self.action_tx, &self.online_client_api).await,
|
||||
|
||||
Action::GetValidatorLedger(stash) => {
|
||||
self.store_stash_if_possible(stash);
|
||||
predefined_calls::get_validators_ledger(&self.action_tx, &self.online_client_api, &stash).await
|
||||
}
|
||||
Action::GetIsStashBonded(stash) => {
|
||||
self.store_stash_if_possible(stash);
|
||||
predefined_calls::get_is_stash_bonded(&self.action_tx, &self.online_client_api, &stash).await
|
||||
},
|
||||
Action::GetErasStakersOverview(stash) => {
|
||||
self.store_stash_if_possible(stash);
|
||||
predefined_calls::get_staking_value_ratio(&self.action_tx, &self.online_client_api, &stash).await
|
||||
},
|
||||
Action::GetValidatorPrefs(stash) => {
|
||||
self.store_stash_if_possible(stash);
|
||||
predefined_calls::get_validator_prefs(&self.action_tx, &self.online_client_api, &stash).await
|
||||
},
|
||||
Action::GetValidatorAllRewards(stash) => {
|
||||
self.store_stash_if_possible(stash);
|
||||
predefined_calls::get_validator_staking_results(&self.action_tx, &self.online_client_api, &stash).await
|
||||
},
|
||||
Action::GetNominatorsByValidator(stash) => {
|
||||
self.store_stash_if_possible(stash);
|
||||
predefined_calls::get_nominators_by_validator(&self.action_tx, &self.online_client_api, &stash).await
|
||||
},
|
||||
Action::GetQueuedSessionKeys(stash) => {
|
||||
self.store_stash_if_possible(stash);
|
||||
predefined_calls::get_queued_session_keys(&self.action_tx, &self.online_client_api, &self.rpc_client, &stash).await
|
||||
},
|
||||
Action::GetSessionKeys(stash) => {
|
||||
self.store_stash_if_possible(stash);
|
||||
predefined_calls::get_session_keys(&self.action_tx, &self.online_client_api, &self.rpc_client, &stash).await
|
||||
},
|
||||
Action::BalanceRequest(account_id, remove) => {
|
||||
if remove {
|
||||
let _ = self.accounts_to_watch.remove(&account_id);
|
||||
Ok(())
|
||||
} else {
|
||||
let _ = self.accounts_to_watch.insert(account_id);
|
||||
predefinded_calls::get_balance(&self.action_tx, &self.online_client_api, &account_id).await
|
||||
predefined_calls::get_balance(&self.action_tx, &self.online_client_api, &account_id).await
|
||||
}
|
||||
}
|
||||
Action::TransferBalance(sender, receiver, amount) => {
|
||||
@ -129,7 +186,7 @@ impl Network {
|
||||
.try_into()
|
||||
.expect("stored seed is valid length; qed");
|
||||
|
||||
if let Ok(tx_progress) = predefinded_calls::transfer_balance(
|
||||
if let Ok(tx_progress) = predefined_txs::transfer_balance(
|
||||
&self.action_tx,
|
||||
&self.online_client_api,
|
||||
&sender,
|
||||
|
@ -1,221 +0,0 @@
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use color_eyre::Result;
|
||||
use subxt::{
|
||||
backend::rpc::RpcClient,
|
||||
client::OnlineClient,
|
||||
config::substrate::DigestItem,
|
||||
ext::sp_core::{
|
||||
crypto::{AccountId32, Ss58AddressFormat, Ss58Codec},
|
||||
Pair as PairT,
|
||||
sr25519::Pair,
|
||||
},
|
||||
rpc_params,
|
||||
tx::{PairSigner, TxProgress},
|
||||
utils::H256,
|
||||
};
|
||||
|
||||
|
||||
use crate::{
|
||||
action::Action,
|
||||
casper_network::{
|
||||
self,
|
||||
runtime_types::sp_consensus_slots,
|
||||
},
|
||||
types::{SystemAccount, EraInfo, ActionLevel},
|
||||
CasperAccountId, CasperConfig
|
||||
};
|
||||
|
||||
pub async fn get_block_author(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
logs: &Vec<DigestItem>,
|
||||
at_hash: &H256,
|
||||
) -> Result<()> {
|
||||
use codec::Decode;
|
||||
use crate::casper_network::runtime_types::sp_consensus_babe::digests::PreDigest;
|
||||
|
||||
let storage_key = casper_network::storage().session().validators();
|
||||
let validators = api.storage().at(*at_hash).fetch(&storage_key).await?.unwrap_or_default();
|
||||
|
||||
let maybe_author = match logs.iter().find(|item| matches!(item, DigestItem::PreRuntime(..))) {
|
||||
Some(DigestItem::PreRuntime(engine, data)) if *engine == [b'B', b'A', b'B', b'E'] => {
|
||||
match PreDigest::decode(&mut &data[..]) {
|
||||
Ok(PreDigest::Primary(primary)) => validators.get(primary.authority_index as usize),
|
||||
Ok(PreDigest::SecondaryPlain(secondary)) => validators.get(secondary.authority_index as usize),
|
||||
Ok(PreDigest::SecondaryVRF(secondary)) => validators.get(secondary.authority_index as usize),
|
||||
_ => None,
|
||||
}
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let validator = match maybe_author {
|
||||
Some(author) => {
|
||||
let extended_author = CasperAccountId::decode(&mut author.as_ref())
|
||||
.expect("author should be valid AccountId32; qed");
|
||||
let account_id = AccountId32::from(extended_author.0);
|
||||
account_id.to_ss58check_with_version(Ss58AddressFormat::custom(1996))
|
||||
},
|
||||
None => "...".to_string(),
|
||||
};
|
||||
|
||||
action_tx.send(Action::SetBlockAuthor(*at_hash, validator))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_active_era(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
) -> Result<()> {
|
||||
let storage_key = casper_network::storage().staking().active_era();
|
||||
if let Some(active_era) = api.storage().at_latest().await?.fetch(&storage_key).await? {
|
||||
action_tx.send(Action::SetActiveEra(EraInfo {
|
||||
index: active_era.index,
|
||||
start: active_era.start,
|
||||
}))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_epoch_progress(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
) -> Result<()> {
|
||||
|
||||
let storage_key = casper_network::storage().babe().current_slot();
|
||||
let current_slot = api.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.fetch(&storage_key)
|
||||
.await?
|
||||
.unwrap_or(sp_consensus_slots::Slot(0u64));
|
||||
|
||||
let storage_key = casper_network::storage().babe().epoch_index();
|
||||
let epoch_index = api.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.fetch(&storage_key)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
let storage_key = casper_network::storage().babe().genesis_slot();
|
||||
let genesis_slot = api.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.fetch(&storage_key)
|
||||
.await?
|
||||
.unwrap_or(sp_consensus_slots::Slot(0u64));
|
||||
|
||||
let constant_query = casper_network::constants().babe().epoch_duration();
|
||||
let epoch_duration = api.constants().at(&constant_query)?;
|
||||
|
||||
let epoch_start_slot = epoch_index * epoch_duration + genesis_slot.0;
|
||||
let progress = current_slot.0.saturating_sub(epoch_start_slot);
|
||||
|
||||
action_tx.send(Action::SetEpochProgress(epoch_index, progress))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_pending_extrinsics(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
rpc_client: &RpcClient,
|
||||
) -> Result<()> {
|
||||
let pending_extrinsics: Vec<String> = rpc_client
|
||||
.request("author_pendingExtrinsics", rpc_params![])
|
||||
.await?;
|
||||
action_tx.send(Action::SetPendingExtrinsicsLength(pending_extrinsics.len()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_total_issuance(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
) -> Result<()> {
|
||||
let storage_key = casper_network::storage().balances().total_issuance();
|
||||
let total_issuance = api.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.fetch(&storage_key)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
action_tx.send(Action::SetTotalIssuance(total_issuance))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_existential_deposit(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
) -> Result<()> {
|
||||
let constant_query = casper_network::constants().balances().existential_deposit();
|
||||
let existential_deposit = api.constants().at(&constant_query)?;
|
||||
action_tx.send(Action::SetExistentialDeposit(existential_deposit))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_balance(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
account_id: &[u8; 32],
|
||||
) -> Result<()> {
|
||||
let account_id_converted = subxt::utils::AccountId32::from(*account_id);
|
||||
let storage_key = casper_network::storage().system().account(account_id_converted);
|
||||
|
||||
let maybe_balance = api
|
||||
.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.fetch(&storage_key)
|
||||
.await?;
|
||||
|
||||
let balance = match maybe_balance {
|
||||
Some(balance) => {
|
||||
SystemAccount {
|
||||
nonce: balance.nonce,
|
||||
free: balance.data.free,
|
||||
reserved: balance.data.reserved,
|
||||
frozen: balance.data.frozen,
|
||||
}
|
||||
},
|
||||
None => SystemAccount::default(),
|
||||
};
|
||||
|
||||
action_tx.send(Action::BalanceResponse(*account_id, balance))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn transfer_balance(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
sender: &[u8; 32],
|
||||
receiver: &[u8; 32],
|
||||
amount: &u128,
|
||||
) -> Result<TxProgress<CasperConfig, OnlineClient<CasperConfig>>> {
|
||||
let receiver_id = subxt::utils::MultiAddress::Id(
|
||||
subxt::utils::AccountId32::from(*receiver)
|
||||
);
|
||||
|
||||
let transfer_tx = casper_network::tx()
|
||||
.balances()
|
||||
.transfer_allow_death(receiver_id, *amount);
|
||||
|
||||
let pair = Pair::from_seed(sender);
|
||||
let signer = PairSigner::<CasperConfig, Pair>::new(pair);
|
||||
|
||||
match api
|
||||
.tx()
|
||||
.sign_and_submit_then_watch_default(&transfer_tx, &signer)
|
||||
.await {
|
||||
Ok(tx_progress) => {
|
||||
action_tx.send(Action::WalletLog(
|
||||
format!("transfer transaction {} sent", tx_progress.extrinsic_hash()),
|
||||
ActionLevel::Info))?;
|
||||
Ok(tx_progress)
|
||||
},
|
||||
Err(err) => {
|
||||
action_tx.send(Action::WalletLog(
|
||||
format!("error during transfer: {err}"), ActionLevel::Error))?;
|
||||
Err(err.into())
|
||||
}
|
||||
}
|
||||
}
|
476
src/network/predefined_calls.rs
Normal file
476
src/network/predefined_calls.rs
Normal file
@ -0,0 +1,476 @@
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use color_eyre::Result;
|
||||
use subxt::{
|
||||
backend::rpc::RpcClient,
|
||||
client::OnlineClient,
|
||||
config::substrate::DigestItem,
|
||||
ext::sp_core::crypto::{
|
||||
AccountId32, Ss58AddressFormat, Ss58Codec,
|
||||
},
|
||||
rpc_params,
|
||||
utils::H256,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
action::Action,
|
||||
casper_network::runtime_types::sp_consensus_slots,
|
||||
types::{EraInfo, Nominator, SessionKeyInfo, SystemAccount},
|
||||
CasperAccountId, CasperConfig
|
||||
};
|
||||
|
||||
pub async fn get_block_author(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
logs: &Vec<DigestItem>,
|
||||
at_hash: &H256,
|
||||
) -> Result<()> {
|
||||
use codec::Decode;
|
||||
use crate::casper_network::runtime_types::sp_consensus_babe::digests::PreDigest;
|
||||
|
||||
let validators = super::raw_calls::session::validators(api, Some(at_hash))
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
let maybe_author = match logs.iter().find(|item| matches!(item, DigestItem::PreRuntime(..))) {
|
||||
Some(DigestItem::PreRuntime(engine, data)) if *engine == [b'B', b'A', b'B', b'E'] => {
|
||||
match PreDigest::decode(&mut &data[..]) {
|
||||
Ok(PreDigest::Primary(primary)) => validators.get(primary.authority_index as usize),
|
||||
Ok(PreDigest::SecondaryPlain(secondary)) => validators.get(secondary.authority_index as usize),
|
||||
Ok(PreDigest::SecondaryVRF(secondary)) => validators.get(secondary.authority_index as usize),
|
||||
_ => None,
|
||||
}
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let validator = match maybe_author {
|
||||
Some(author) => {
|
||||
let extended_author = CasperAccountId::decode(&mut author.as_ref())
|
||||
.expect("author should be valid AccountId32; qed");
|
||||
let account_id = AccountId32::from(extended_author.0);
|
||||
account_id.to_ss58check_with_version(Ss58AddressFormat::custom(1996))
|
||||
},
|
||||
None => "...".to_string(),
|
||||
};
|
||||
|
||||
action_tx.send(Action::SetBlockAuthor(*at_hash, validator))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_current_era(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
) -> Result<()> {
|
||||
let current_era = super::raw_calls::staking::current_era(api, None)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
action_tx.send(Action::SetCurrentEra(current_era))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_active_era(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
) -> Result<()> {
|
||||
if let Some(active_era) = super::raw_calls::staking::active_era(api, None).await? {
|
||||
action_tx.send(Action::SetActiveEra(EraInfo {
|
||||
index: active_era.index,
|
||||
start: active_era.start,
|
||||
}))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_epoch_progress(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
) -> Result<()> {
|
||||
let current_slot = super::raw_calls::babe::current_slot(api, None)
|
||||
.await?
|
||||
.unwrap_or(sp_consensus_slots::Slot(0u64));
|
||||
|
||||
let epoch_index = super::raw_calls::babe::epoch_index(api, None)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
let genesis_slot = super::raw_calls::babe::genesis_slot(api, None)
|
||||
.await?
|
||||
.unwrap_or(sp_consensus_slots::Slot(0u64));
|
||||
|
||||
let epoch_duration = super::raw_calls::babe::epoch_duration(api)?;
|
||||
|
||||
let epoch_start_slot = epoch_index * epoch_duration + genesis_slot.0;
|
||||
let progress = current_slot.0.saturating_sub(epoch_start_slot);
|
||||
|
||||
action_tx.send(Action::SetEpochProgress(epoch_index, progress))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_total_issuance(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
) -> Result<()> {
|
||||
let total_issuance = super::raw_calls::balances::total_issuance(api, None)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
action_tx.send(Action::SetTotalIssuance(total_issuance))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_existential_deposit(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
) -> Result<()> {
|
||||
let existential_deposit = super::raw_calls::balances::existential_deposit(api)?;
|
||||
action_tx.send(Action::SetExistentialDeposit(existential_deposit))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_balance(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
account_id: &[u8; 32],
|
||||
) -> Result<()> {
|
||||
let maybe_balance = super::raw_calls::system::balance(api, None, account_id)
|
||||
.await?;
|
||||
|
||||
let balance = match maybe_balance {
|
||||
Some(balance) => {
|
||||
SystemAccount {
|
||||
nonce: balance.nonce,
|
||||
free: balance.data.free,
|
||||
reserved: balance.data.reserved,
|
||||
frozen: balance.data.frozen,
|
||||
}
|
||||
},
|
||||
None => SystemAccount::default(),
|
||||
};
|
||||
|
||||
action_tx.send(Action::BalanceResponse(*account_id, balance))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_validators_number(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
) -> Result<()> {
|
||||
let counter_for_validators = super::raw_calls::staking::counter_for_validators(api, None)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
action_tx.send(Action::ValidatorsNumber(counter_for_validators))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_nominators_number(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
) -> Result<()> {
|
||||
let counter_for_nominators = super::raw_calls::staking::counter_for_nominators(api, None)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
action_tx.send(Action::NominatorsNumber(counter_for_nominators))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_inflation(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
) -> Result<()> {
|
||||
let total_issuance = super::raw_calls::balances::total_issuance(api, None)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
let active_era_index = super::raw_calls::staking::active_era(api, None)
|
||||
.await?
|
||||
.map(|era_info| era_info.index)
|
||||
.unwrap_or_default();
|
||||
|
||||
let total_staked = super::raw_calls::staking::eras_total_stake(api, None, active_era_index)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
let (inflation, fraction) = super::calculate_for_fraction(total_staked, total_issuance);
|
||||
let inflation_str = super::prepare_perbill_fraction_string(inflation);
|
||||
let fraction_str = super::prepare_perbill_fraction_string(fraction);
|
||||
|
||||
action_tx.send(Action::Inflation(inflation_str))?;
|
||||
action_tx.send(Action::Apy(fraction_str))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_session_keys(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
rpc_client: &RpcClient,
|
||||
account_id: &[u8; 32],
|
||||
) -> Result<()> {
|
||||
let maybe_session_keys = super::raw_calls::session::next_keys(api, None, account_id).await?;
|
||||
let (gran_key, babe_key, audi_key, slow_key) = match maybe_session_keys {
|
||||
Some(session_keys) => {
|
||||
let gran_key = format!("0x{}", hex::encode(session_keys.grandpa.0));
|
||||
let babe_key = format!("0x{}", hex::encode(session_keys.babe.0));
|
||||
let audi_key = format!("0x{}", hex::encode(session_keys.authority_discovery.0));
|
||||
let slow_key = format!("0x{}", hex::encode(session_keys.slow_clap.0));
|
||||
|
||||
(gran_key, babe_key, audi_key, slow_key)
|
||||
},
|
||||
None => (String::new(), String::new(), String::new(), String::new()),
|
||||
};
|
||||
|
||||
check_author_has_key(rpc_client, action_tx, &gran_key, "gran").await?;
|
||||
check_author_has_key(rpc_client, action_tx, &babe_key, "babe").await?;
|
||||
check_author_has_key(rpc_client, action_tx, &audi_key, "audi").await?;
|
||||
check_author_has_key(rpc_client, action_tx, &slow_key, "slow").await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_queued_session_keys(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
rpc_client: &RpcClient,
|
||||
account_id: &[u8; 32],
|
||||
) -> Result<()> {
|
||||
let account = super::raw_calls::convert_array_to_account_id(account_id);
|
||||
let maybe_queued_keys = super::raw_calls::session::queued_keys(api, None).await?;
|
||||
|
||||
let (gran_key, babe_key, audi_key, slow_key) = match maybe_queued_keys {
|
||||
Some(session_keys) => {
|
||||
match session_keys.iter().find(|tuple| tuple.0 == account) {
|
||||
Some(keys) => {
|
||||
let session_keys = &keys.1;
|
||||
let gran_key = format!("0x{}", hex::encode(session_keys.grandpa.0));
|
||||
let babe_key = format!("0x{}", hex::encode(session_keys.babe.0));
|
||||
let audi_key = format!("0x{}", hex::encode(session_keys.authority_discovery.0));
|
||||
let slow_key = format!("0x{}", hex::encode(session_keys.slow_clap.0));
|
||||
|
||||
(gran_key, babe_key, audi_key, slow_key)
|
||||
},
|
||||
None => (String::new(), String::new(), String::new(), String::new()),
|
||||
}
|
||||
},
|
||||
None => (String::new(), String::new(), String::new(), String::new()),
|
||||
};
|
||||
|
||||
check_author_has_key(rpc_client, action_tx, &gran_key, "q_gran").await?;
|
||||
check_author_has_key(rpc_client, action_tx, &babe_key, "q_babe").await?;
|
||||
check_author_has_key(rpc_client, action_tx, &audi_key, "q_audi").await?;
|
||||
check_author_has_key(rpc_client, action_tx, &slow_key, "q_slow").await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn check_author_has_key(
|
||||
rpc_client: &RpcClient,
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
key: &str,
|
||||
name: &str,
|
||||
) -> Result<()> {
|
||||
let params_name = if name.starts_with("q_") {
|
||||
&name[2..]
|
||||
} else {
|
||||
name
|
||||
};
|
||||
let is_stored: bool = rpc_client
|
||||
.request("author_hasKey", rpc_params![key, params_name])
|
||||
.await?;
|
||||
let session_key_info = SessionKeyInfo {
|
||||
key: key.to_string(),
|
||||
is_stored
|
||||
};
|
||||
action_tx.send(Action::SetSessionKey(name.to_string(), session_key_info))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_validator_staking_results(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
account_id: &[u8; 32],
|
||||
) -> Result<()> {
|
||||
let (start, end) = super::raw_calls::historical::stored_range(api, None)
|
||||
.await?
|
||||
.map(|range| (range.0 / 6, range.1 / 6))
|
||||
.unwrap_or((0, 0));
|
||||
for era_index in start..end.saturating_sub(2) {
|
||||
get_validator_staking_result(action_tx, api, account_id, era_index).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_validator_staking_result(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
account_id: &[u8; 32],
|
||||
era_index: u32,
|
||||
) -> Result<()> {
|
||||
get_validator_reward_in_era(action_tx, api, account_id, era_index).await?;
|
||||
get_validator_claims_in_era(action_tx, api, account_id, era_index).await?;
|
||||
get_validator_slashes_in_era(action_tx, api, account_id, era_index).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_validator_reward_in_era(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
account_id: &[u8; 32],
|
||||
era_index: u32,
|
||||
) -> Result<()> {
|
||||
let maybe_era_reward_points = super::raw_calls::staking::eras_reward_points(api, None, era_index)
|
||||
.await?;
|
||||
|
||||
let era_reward = super::raw_calls::staking::eras_validator_reward(api, None, era_index)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
let my_reward = match maybe_era_reward_points {
|
||||
Some(era_reward_points) => {
|
||||
let my_points = era_reward_points.individual
|
||||
.iter()
|
||||
.find(|(acc, _)| acc.0 == *account_id)
|
||||
.map(|info| info.1)
|
||||
.unwrap_or_default();
|
||||
era_reward
|
||||
.saturating_mul(my_points as u128)
|
||||
.saturating_div(era_reward_points.total as u128)
|
||||
},
|
||||
None => 0u128,
|
||||
};
|
||||
|
||||
action_tx.send(Action::SetValidatorEraReward(era_index, my_reward))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_validator_claims_in_era(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
account_id: &[u8; 32],
|
||||
era_index: u32,
|
||||
) -> Result<()> {
|
||||
let maybe_claimed_rewards = super::raw_calls::staking::claimed_rewards(api, None, era_index, account_id)
|
||||
.await?;
|
||||
|
||||
if let Some(claimed_rewards) = maybe_claimed_rewards {
|
||||
let already_claimed = claimed_rewards
|
||||
.first()
|
||||
.map(|x| *x == 1)
|
||||
.unwrap_or(false);
|
||||
action_tx.send(Action::SetValidatorEraClaimed(era_index, already_claimed))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_validator_slashes_in_era(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
account_id: &[u8; 32],
|
||||
era_index: u32,
|
||||
) -> Result<()> {
|
||||
let maybe_slash_in_era = super::raw_calls::staking::validator_slash_in_era(api, None, era_index, account_id)
|
||||
.await?;
|
||||
|
||||
if let Some(slash_in_era) = maybe_slash_in_era {
|
||||
action_tx.send(Action::SetValidatorEraSlash(era_index, slash_in_era.1))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_validators_ledger(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
account_id: &[u8; 32],
|
||||
) -> Result<()> {
|
||||
let maybe_ledger = super::raw_calls::staking::ledger(api, None, account_id)
|
||||
.await?;
|
||||
|
||||
if let Some(ledger) = maybe_ledger {
|
||||
action_tx.send(Action::SetStakedAmountRatio(ledger.total, ledger.active))?;
|
||||
for chunk in ledger.unlocking.0.iter() {
|
||||
action_tx.send(Action::SetValidatorEraUnlocking(chunk.era, chunk.value))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_nominators_by_validator(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
account_id: &[u8; 32],
|
||||
) -> Result<()> {
|
||||
let active_era_index = super::raw_calls::staking::active_era(api, None)
|
||||
.await?
|
||||
.map(|era_info| era_info.index)
|
||||
.unwrap_or_default();
|
||||
|
||||
let maybe_eras_stakers = super::raw_calls::staking::eras_stakers(api, None, active_era_index, account_id)
|
||||
.await?;
|
||||
|
||||
let nominators = match maybe_eras_stakers {
|
||||
Some(eras_stakers) => eras_stakers
|
||||
.others
|
||||
.iter()
|
||||
.map(|info| {
|
||||
Nominator {
|
||||
who: AccountId32::from(info.who.0)
|
||||
.to_ss58check_with_version(Ss58AddressFormat::custom(1996)),
|
||||
value: info.value,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
None => Vec::new(),
|
||||
};
|
||||
|
||||
action_tx.send(Action::SetNominatorsByValidator(nominators))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_is_stash_bonded(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
account_id: &[u8; 32],
|
||||
) -> Result<()> {
|
||||
let is_bonded = super::raw_calls::staking::bonded(api, None, account_id)
|
||||
.await?
|
||||
.is_some();
|
||||
action_tx.send(Action::SetBondedAmount(is_bonded))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_staking_value_ratio(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
account_id: &[u8; 32],
|
||||
) -> Result<()> {
|
||||
let active_era_index = super::raw_calls::staking::active_era(api, None)
|
||||
.await?
|
||||
.map(|era_info| era_info.index)
|
||||
.unwrap_or_default();
|
||||
let maybe_era_stakers_overview = super::raw_calls::staking::eras_stakers_overview(api, None, active_era_index, account_id)
|
||||
.await?;
|
||||
let (total, own) = match maybe_era_stakers_overview {
|
||||
Some(overview) => (overview.total, overview.own),
|
||||
None => (0, 0),
|
||||
};
|
||||
action_tx.send(Action::SetStakedRatio(total, own))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_validator_prefs(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
account_id: &[u8; 32],
|
||||
) -> Result<()> {
|
||||
let maybe_validator_prefs = super::raw_calls::staking::validators(api, None, account_id)
|
||||
.await?;
|
||||
let (comission, blocked) = match maybe_validator_prefs {
|
||||
Some(prefs) => (prefs.commission.0, prefs.blocked),
|
||||
None => (0, false),
|
||||
};
|
||||
|
||||
action_tx.send(Action::SetValidatorPrefs(comission, blocked))?;
|
||||
Ok(())
|
||||
}
|
45
src/network/predefined_txs.rs
Normal file
45
src/network/predefined_txs.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use color_eyre::Result;
|
||||
use subxt::{
|
||||
ext::sp_core::{Pair as PairT, sr25519::Pair},
|
||||
tx::{PairSigner, TxProgress},
|
||||
OnlineClient,
|
||||
};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
|
||||
use crate::{action::Action, casper::CasperConfig, casper_network, types::ActionLevel};
|
||||
|
||||
pub async fn transfer_balance(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
sender: &[u8; 32],
|
||||
receiver: &[u8; 32],
|
||||
amount: &u128,
|
||||
) -> Result<TxProgress<CasperConfig, OnlineClient<CasperConfig>>> {
|
||||
let receiver_id = subxt::utils::MultiAddress::Id(
|
||||
subxt::utils::AccountId32::from(*receiver)
|
||||
);
|
||||
|
||||
let transfer_tx = casper_network::tx()
|
||||
.balances()
|
||||
.transfer_allow_death(receiver_id, *amount);
|
||||
|
||||
let pair = Pair::from_seed(sender);
|
||||
let signer = PairSigner::<CasperConfig, Pair>::new(pair);
|
||||
|
||||
match api
|
||||
.tx()
|
||||
.sign_and_submit_then_watch_default(&transfer_tx, &signer)
|
||||
.await {
|
||||
Ok(tx_progress) => {
|
||||
action_tx.send(Action::WalletLog(
|
||||
format!("transfer transaction {} sent", tx_progress.extrinsic_hash()),
|
||||
ActionLevel::Info))?;
|
||||
Ok(tx_progress)
|
||||
},
|
||||
Err(err) => {
|
||||
action_tx.send(Action::WalletLog(
|
||||
format!("error during transfer: {err}"), ActionLevel::Error))?;
|
||||
Err(err.into())
|
||||
}
|
||||
}
|
||||
}
|
42
src/network/raw_calls/babe.rs
Normal file
42
src/network/raw_calls/babe.rs
Normal file
@ -0,0 +1,42 @@
|
||||
use color_eyre::Result;
|
||||
use subxt::{
|
||||
utils::H256,
|
||||
client::OnlineClient,
|
||||
};
|
||||
|
||||
use crate::{casper_network::{self, runtime_types::sp_consensus_slots}, CasperConfig};
|
||||
|
||||
pub async fn current_slot(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
at_hash: Option<&H256>,
|
||||
) -> Result<Option<sp_consensus_slots::Slot>> {
|
||||
let storage_key = casper_network::storage().babe().current_slot();
|
||||
let maybe_current_slot = super::do_storage_call(online_client, &storage_key, at_hash).await?;
|
||||
Ok(maybe_current_slot)
|
||||
}
|
||||
|
||||
pub async fn epoch_index(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
at_hash: Option<&H256>,
|
||||
) -> Result<Option<u64>> {
|
||||
let storage_key = casper_network::storage().babe().epoch_index();
|
||||
let maybe_epoch_index = super::do_storage_call(online_client, &storage_key, at_hash).await?;
|
||||
Ok(maybe_epoch_index)
|
||||
}
|
||||
|
||||
pub async fn genesis_slot(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
at_hash: Option<&H256>,
|
||||
) -> Result<Option<sp_consensus_slots::Slot>> {
|
||||
let storage_key = casper_network::storage().babe().genesis_slot();
|
||||
let maybe_genesis_slot = super::do_storage_call(online_client, &storage_key, at_hash).await?;
|
||||
Ok(maybe_genesis_slot)
|
||||
}
|
||||
|
||||
pub fn epoch_duration(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
) -> Result<u64> {
|
||||
let constant_query = casper_network::constants().babe().epoch_duration();
|
||||
let epoch_duration = super::do_constant_call(online_client, &constant_query)?;
|
||||
Ok(epoch_duration)
|
||||
}
|
24
src/network/raw_calls/balances.rs
Normal file
24
src/network/raw_calls/balances.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use color_eyre::Result;
|
||||
use subxt::{
|
||||
utils::H256,
|
||||
client::OnlineClient,
|
||||
};
|
||||
|
||||
use crate::{CasperConfig, casper_network};
|
||||
|
||||
pub async fn total_issuance(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
at_hash: Option<&H256>,
|
||||
) -> Result<Option<u128>> {
|
||||
let storage_key = casper_network::storage().balances().total_issuance();
|
||||
let maybe_total_issuance = super::do_storage_call(online_client, &storage_key, at_hash).await?;
|
||||
Ok(maybe_total_issuance)
|
||||
}
|
||||
|
||||
pub fn existential_deposit(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
) -> Result<u128> {
|
||||
let constant_query = casper_network::constants().balances().existential_deposit();
|
||||
let existential_deposit = super::do_constant_call(online_client, &constant_query)?;
|
||||
Ok(existential_deposit)
|
||||
}
|
19
src/network/raw_calls/historical.rs
Normal file
19
src/network/raw_calls/historical.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use color_eyre::Result;
|
||||
use subxt::{
|
||||
utils::H256,
|
||||
client::OnlineClient,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
casper_network,
|
||||
CasperConfig,
|
||||
};
|
||||
|
||||
pub async fn stored_range(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
at_hash: Option<&H256>,
|
||||
) -> Result<Option<(u32, u32)>> {
|
||||
let storage_key = casper_network::storage().historical().stored_range();
|
||||
let maybe_stored_range = super::do_storage_call(online_client, &storage_key, at_hash).await?;
|
||||
Ok(maybe_stored_range)
|
||||
}
|
54
src/network/raw_calls/mod.rs
Normal file
54
src/network/raw_calls/mod.rs
Normal file
@ -0,0 +1,54 @@
|
||||
use color_eyre::Result;
|
||||
use subxt::{
|
||||
backend::BlockRef,
|
||||
utils::{Yes, H256, AccountId32},
|
||||
client::OnlineClient,
|
||||
};
|
||||
|
||||
use crate::CasperConfig;
|
||||
|
||||
pub mod session;
|
||||
pub mod staking;
|
||||
pub mod system;
|
||||
pub mod babe;
|
||||
pub mod balances;
|
||||
pub mod historical;
|
||||
|
||||
pub async fn do_storage_call<'address, Addr>(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
storage_key: &'address Addr,
|
||||
maybe_at_hash: Option<&H256>,
|
||||
) -> Result<Option<Addr::Target>, subxt::Error>
|
||||
where
|
||||
Addr: subxt::storage::Address<IsFetchable = Yes> + 'address,
|
||||
{
|
||||
let at_hash = match maybe_at_hash {
|
||||
Some(at_hash) => BlockRef::from_hash(*at_hash),
|
||||
None => online_client
|
||||
.backend()
|
||||
.latest_finalized_block_ref()
|
||||
.await?,
|
||||
};
|
||||
|
||||
online_client
|
||||
.storage()
|
||||
.at(at_hash)
|
||||
.fetch(storage_key)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn do_constant_call<'address, Addr>(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
constant_query: &'address Addr,
|
||||
) -> Result<Addr::Target, subxt::Error>
|
||||
where
|
||||
Addr: subxt::constants::Address + 'address
|
||||
{
|
||||
let constant_client = online_client.constants();
|
||||
constant_client.validate(constant_query).expect("constant query should be correct; qed");
|
||||
constant_client.at(constant_query)
|
||||
}
|
||||
|
||||
pub fn convert_array_to_account_id(who: &[u8; 32]) -> AccountId32 {
|
||||
AccountId32::from(*who)
|
||||
}
|
36
src/network/raw_calls/session.rs
Normal file
36
src/network/raw_calls/session.rs
Normal file
@ -0,0 +1,36 @@
|
||||
use color_eyre::Result;
|
||||
use subxt::{
|
||||
utils::{AccountId32, H256},
|
||||
client::OnlineClient,
|
||||
};
|
||||
|
||||
use crate::{casper_network::{self, runtime_types::casper_runtime::opaque}, CasperConfig};
|
||||
|
||||
pub async fn validators(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
at_hash: Option<&H256>,
|
||||
) -> Result<Option<Vec<AccountId32>>> {
|
||||
let storage_key = casper_network::storage().session().validators();
|
||||
let maybe_validators = super::do_storage_call(online_client, &storage_key, at_hash).await?;
|
||||
Ok(maybe_validators)
|
||||
}
|
||||
|
||||
pub async fn next_keys(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
at_hash: Option<&H256>,
|
||||
account: &[u8; 32],
|
||||
) -> Result<Option<opaque::SessionKeys>> {
|
||||
let account_id = super::convert_array_to_account_id(account);
|
||||
let storage_key = casper_network::storage().session().next_keys(account_id);
|
||||
let maybe_next_keys = super::do_storage_call(online_client, &storage_key, at_hash).await?;
|
||||
Ok(maybe_next_keys)
|
||||
}
|
||||
|
||||
pub async fn queued_keys(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
at_hash: Option<&H256>,
|
||||
) -> Result<Option<Vec<(AccountId32, opaque::SessionKeys)>>> {
|
||||
let storage_key = casper_network::storage().session().queued_keys();
|
||||
let maybe_queued_keys = super::do_storage_call(online_client, &storage_key, at_hash).await?;
|
||||
Ok(maybe_queued_keys)
|
||||
}
|
164
src/network/raw_calls/staking.rs
Normal file
164
src/network/raw_calls/staking.rs
Normal file
@ -0,0 +1,164 @@
|
||||
use color_eyre::Result;
|
||||
use subxt::{
|
||||
client::OnlineClient,
|
||||
utils::{AccountId32, H256},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
casper_network::{
|
||||
self,
|
||||
runtime_types::{
|
||||
pallet_staking::{ActiveEraInfo, EraRewardPoints, StakingLedger, ValidatorPrefs},
|
||||
sp_arithmetic::per_things::Perbill,
|
||||
sp_staking::{Exposure, PagedExposureMetadata},
|
||||
},
|
||||
},
|
||||
CasperConfig,
|
||||
};
|
||||
|
||||
pub async fn current_era(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
at_hash: Option<&H256>,
|
||||
) -> Result<Option<u32>> {
|
||||
let storage_key = casper_network::storage().staking().current_era();
|
||||
let maybe_current_era = super::do_storage_call(online_client, &storage_key, at_hash).await?;
|
||||
Ok(maybe_current_era)
|
||||
}
|
||||
|
||||
pub async fn active_era(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
at_hash: Option<&H256>,
|
||||
) -> Result<Option<ActiveEraInfo>> {
|
||||
let storage_key = casper_network::storage().staking().active_era();
|
||||
let maybe_active_era = super::do_storage_call(online_client, &storage_key, at_hash).await?;
|
||||
Ok(maybe_active_era)
|
||||
}
|
||||
|
||||
pub async fn counter_for_validators(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
at_hash: Option<&H256>,
|
||||
) -> Result<Option<u32>> {
|
||||
let storage_key = casper_network::storage().staking().counter_for_validators();
|
||||
let maybe_counter_for_validators = super::do_storage_call(online_client, &storage_key, at_hash).await?;
|
||||
Ok(maybe_counter_for_validators)
|
||||
}
|
||||
|
||||
pub async fn counter_for_nominators(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
at_hash: Option<&H256>,
|
||||
) -> Result<Option<u32>> {
|
||||
let storage_key = casper_network::storage().staking().counter_for_nominators();
|
||||
let maybe_counter_for_nominators = super::do_storage_call(online_client, &storage_key, at_hash).await?;
|
||||
Ok(maybe_counter_for_nominators)
|
||||
}
|
||||
|
||||
pub async fn eras_total_stake(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
at_hash: Option<&H256>,
|
||||
era_index: u32,
|
||||
) -> Result<Option<u128>> {
|
||||
let storage_key = casper_network::storage().staking().eras_total_stake(era_index);
|
||||
let maybe_eras_total_stake = super::do_storage_call(online_client, &storage_key, at_hash).await?;
|
||||
Ok(maybe_eras_total_stake)
|
||||
}
|
||||
|
||||
pub async fn eras_validator_reward(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
at_hash: Option<&H256>,
|
||||
era_index: u32,
|
||||
) -> Result<Option<u128>> {
|
||||
let storage_key = casper_network::storage().staking().eras_validator_reward(era_index);
|
||||
let maybe_eras_validator_reward = super::do_storage_call(online_client, &storage_key, at_hash).await?;
|
||||
Ok(maybe_eras_validator_reward)
|
||||
}
|
||||
|
||||
pub async fn eras_reward_points(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
at_hash: Option<&H256>,
|
||||
era_index: u32,
|
||||
) -> Result<Option<EraRewardPoints<AccountId32>>> {
|
||||
let storage_key = casper_network::storage().staking().eras_reward_points(era_index);
|
||||
let maybe_eras_reward_points = super::do_storage_call(online_client, &storage_key, at_hash).await?;
|
||||
Ok(maybe_eras_reward_points)
|
||||
}
|
||||
|
||||
pub async fn claimed_rewards(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
at_hash: Option<&H256>,
|
||||
era_index: u32,
|
||||
account: &[u8; 32],
|
||||
) -> Result<Option<Vec<u32>>> {
|
||||
let account_id = super::convert_array_to_account_id(account);
|
||||
let storage_key = casper_network::storage().staking().claimed_rewards(era_index, account_id);
|
||||
let maybe_claimed_rewards = super::do_storage_call(online_client, &storage_key, at_hash).await?;
|
||||
Ok(maybe_claimed_rewards)
|
||||
}
|
||||
|
||||
pub async fn validator_slash_in_era(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
at_hash: Option<&H256>,
|
||||
era_index: u32,
|
||||
account: &[u8; 32],
|
||||
) -> Result<Option<(Perbill, u128)>> {
|
||||
let account_id = super::convert_array_to_account_id(account);
|
||||
let storage_key = casper_network::storage().staking().validator_slash_in_era(era_index, account_id);
|
||||
let maybe_validator_slash_in_era = super::do_storage_call(online_client, &storage_key, at_hash).await?;
|
||||
Ok(maybe_validator_slash_in_era)
|
||||
}
|
||||
|
||||
pub async fn ledger(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
at_hash: Option<&H256>,
|
||||
account: &[u8; 32],
|
||||
) -> Result<Option<StakingLedger>> {
|
||||
let account_id = super::convert_array_to_account_id(account);
|
||||
let storage_key = casper_network::storage().staking().ledger(account_id);
|
||||
let maybe_ledger = super::do_storage_call(online_client, &storage_key, at_hash).await?;
|
||||
Ok(maybe_ledger)
|
||||
}
|
||||
|
||||
pub async fn eras_stakers(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
at_hash: Option<&H256>,
|
||||
era_index: u32,
|
||||
account: &[u8; 32],
|
||||
) -> Result<Option<Exposure<AccountId32, u128>>> {
|
||||
let account_id = super::convert_array_to_account_id(account);
|
||||
let storage_key = casper_network::storage().staking().eras_stakers(era_index, account_id);
|
||||
let maybe_eras_stakers = super::do_storage_call(online_client, &storage_key, at_hash).await?;
|
||||
Ok(maybe_eras_stakers)
|
||||
}
|
||||
|
||||
pub async fn bonded(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
at_hash: Option<&H256>,
|
||||
account: &[u8; 32],
|
||||
) -> Result<Option<AccountId32>> {
|
||||
let account_id = super::convert_array_to_account_id(account);
|
||||
let storage_key = casper_network::storage().staking().bonded(account_id);
|
||||
let maybe_bonded = super::do_storage_call(online_client, &storage_key, at_hash).await?;
|
||||
Ok(maybe_bonded)
|
||||
}
|
||||
|
||||
pub async fn eras_stakers_overview(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
at_hash: Option<&H256>,
|
||||
era_index: u32,
|
||||
account: &[u8; 32],
|
||||
) -> Result<Option<PagedExposureMetadata<u128>>> {
|
||||
let account_id = super::convert_array_to_account_id(account);
|
||||
let storage_key = casper_network::storage().staking().eras_stakers_overview(era_index, account_id);
|
||||
let maybe_eras_stakers_overview = super::do_storage_call(online_client, &storage_key, at_hash).await?;
|
||||
Ok(maybe_eras_stakers_overview)
|
||||
}
|
||||
|
||||
pub async fn validators(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
at_hash: Option<&H256>,
|
||||
account: &[u8; 32],
|
||||
) -> Result<Option<ValidatorPrefs>> {
|
||||
let account_id = super::convert_array_to_account_id(account);
|
||||
let storage_key = casper_network::storage().staking().validators(account_id);
|
||||
let maybe_validators = super::do_storage_call(online_client, &storage_key, at_hash).await?;
|
||||
Ok(maybe_validators)
|
||||
}
|
24
src/network/raw_calls/system.rs
Normal file
24
src/network/raw_calls/system.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use color_eyre::Result;
|
||||
use subxt::{
|
||||
utils::H256,
|
||||
client::OnlineClient,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
casper_network::{
|
||||
self,
|
||||
runtime_types::{frame_system::AccountInfo, pallet_balances::types::AccountData},
|
||||
},
|
||||
CasperConfig,
|
||||
};
|
||||
|
||||
pub async fn balance(
|
||||
online_client: &OnlineClient<CasperConfig>,
|
||||
at_hash: Option<&H256>,
|
||||
account: &[u8; 32],
|
||||
) -> Result<Option<AccountInfo<u32, AccountData<u128>>>> {
|
||||
let account_id = super::convert_array_to_account_id(account);
|
||||
let storage_key = casper_network::storage().system().account(account_id);
|
||||
let maybe_balance = super::do_storage_call(online_client, &storage_key, at_hash).await?;
|
||||
Ok(maybe_balance)
|
||||
}
|
0
src/network/raw_rpc.rs
Normal file
0
src/network/raw_rpc.rs
Normal file
@ -112,8 +112,12 @@ impl BestSubscription {
|
||||
self.network_tx.send(Action::NewBestHash(block_hash))?;
|
||||
self.network_tx.send(Action::GetBlockAuthor(block_hash, block.header().digest.logs.clone()))?;
|
||||
self.network_tx.send(Action::GetActiveEra)?;
|
||||
self.network_tx.send(Action::GetCurrentEra)?;
|
||||
self.network_tx.send(Action::GetEpochProgress)?;
|
||||
self.network_tx.send(Action::GetTotalIssuance)?;
|
||||
self.network_tx.send(Action::GetValidatorsNumber)?;
|
||||
self.network_tx.send(Action::GetNominatorsNumber)?;
|
||||
self.network_tx.send(Action::GetInflation)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2,8 +2,14 @@ mod era;
|
||||
mod extrinsics;
|
||||
mod log;
|
||||
mod account;
|
||||
mod peer;
|
||||
mod session;
|
||||
mod nominator;
|
||||
|
||||
pub use extrinsics::CasperExtrinsicDetails;
|
||||
pub use era::EraInfo;
|
||||
pub use log::ActionLevel;
|
||||
pub use account::SystemAccount;
|
||||
pub use peer::PeerInformation;
|
||||
pub use session::SessionKeyInfo;
|
||||
pub use nominator::Nominator;
|
||||
|
8
src/types/nominator.rs
Normal file
8
src/types/nominator.rs
Normal file
@ -0,0 +1,8 @@
|
||||
use codec::Decode;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, Decode)]
|
||||
pub struct Nominator {
|
||||
pub who: String,
|
||||
pub value: u128,
|
||||
}
|
12
src/types/peer.rs
Normal file
12
src/types/peer.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use subxt::utils::H256;
|
||||
use codec::Decode;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, Decode)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PeerInformation {
|
||||
pub peer_id: String,
|
||||
pub roles: String,
|
||||
pub best_hash: H256,
|
||||
pub best_number: u32,
|
||||
}
|
8
src/types/session.rs
Normal file
8
src/types/session.rs
Normal file
@ -0,0 +1,8 @@
|
||||
use codec::Decode;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, Decode)]
|
||||
pub struct SessionKeyInfo {
|
||||
pub key: String,
|
||||
pub is_stored: bool,
|
||||
}
|
@ -14,6 +14,8 @@ impl Default for VerticalBlocks {
|
||||
|
||||
impl ToString for VerticalBlocks {
|
||||
fn to_string(&self) -> String {
|
||||
// TODO: how it can be equal to len()??
|
||||
// do we really need super::CYCLE in denominator?
|
||||
self.elements[((chrono::Utc::now().timestamp_millis() % super::CYCLE)
|
||||
/ (super::CYCLE / self.elements.len() as i64)) as usize]
|
||||
.to_owned()
|
||||
|
Loading…
Reference in New Issue
Block a user