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",
|
"highlight_style": "yellow bold",
|
||||||
"popup_style": "blue",
|
"popup_style": "blue",
|
||||||
"popup_title_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": {
|
"keybindings": {
|
||||||
"Menu": {
|
"Menu": {
|
||||||
@ -49,6 +60,12 @@
|
|||||||
"<Ctrl-c>": "Quit",
|
"<Ctrl-c>": "Quit",
|
||||||
"<Ctrl-z>": "Suspend",
|
"<Ctrl-z>": "Suspend",
|
||||||
},
|
},
|
||||||
|
"Validator": {
|
||||||
|
"<q>": "Quit",
|
||||||
|
"<Ctrl-d>": "Quit",
|
||||||
|
"<Ctrl-c>": "Quit",
|
||||||
|
"<Ctrl-z>": "Suspend",
|
||||||
|
},
|
||||||
"Empty": {
|
"Empty": {
|
||||||
"<q>": "Quit",
|
"<q>": "Quit",
|
||||||
"<Ctrl-d>": "Quit",
|
"<Ctrl-d>": "Quit",
|
||||||
|
@ -4,8 +4,8 @@ use strum::Display;
|
|||||||
use subxt::utils::H256;
|
use subxt::utils::H256;
|
||||||
use subxt::config::substrate::DigestItem;
|
use subxt::config::substrate::DigestItem;
|
||||||
|
|
||||||
use crate::{
|
use crate::types::{
|
||||||
types::{SystemAccount, ActionLevel, EraInfo, CasperExtrinsicDetails},
|
ActionLevel, CasperExtrinsicDetails, EraInfo, Nominator, PeerInformation, SessionKeyInfo, SystemAccount
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)]
|
||||||
@ -42,6 +42,7 @@ pub enum Action {
|
|||||||
|
|
||||||
TransferBalance(String, [u8; 32], u128),
|
TransferBalance(String, [u8; 32], u128),
|
||||||
WalletLog(String, ActionLevel),
|
WalletLog(String, ActionLevel),
|
||||||
|
ValidatorLog(String, ActionLevel),
|
||||||
|
|
||||||
NewBestBlock(u32),
|
NewBestBlock(u32),
|
||||||
NewBestHash(H256),
|
NewBestHash(H256),
|
||||||
@ -49,6 +50,10 @@ pub enum Action {
|
|||||||
NewFinalizedHash(H256),
|
NewFinalizedHash(H256),
|
||||||
BestBlockUpdated(u32),
|
BestBlockUpdated(u32),
|
||||||
ExtrinsicsLength(u32, usize),
|
ExtrinsicsLength(u32, usize),
|
||||||
|
ValidatorsNumber(u32),
|
||||||
|
NominatorsNumber(u32),
|
||||||
|
Inflation(String),
|
||||||
|
Apy(String),
|
||||||
|
|
||||||
GetBlockAuthor(H256, Vec<DigestItem>),
|
GetBlockAuthor(H256, Vec<DigestItem>),
|
||||||
SetBlockAuthor(H256, String),
|
SetBlockAuthor(H256, String),
|
||||||
@ -59,25 +64,54 @@ pub enum Action {
|
|||||||
GetChainName,
|
GetChainName,
|
||||||
GetChainVersion,
|
GetChainVersion,
|
||||||
GetPendingExtrinsics,
|
GetPendingExtrinsics,
|
||||||
|
GetConnectedPeers,
|
||||||
|
GetSessionKeys([u8; 32]),
|
||||||
|
GetQueuedSessionKeys([u8; 32]),
|
||||||
|
GetListenAddresses,
|
||||||
|
GetLocalIdentity,
|
||||||
|
|
||||||
GetLatestBlock,
|
GetLatestBlock,
|
||||||
GetFinalizedBlock,
|
GetFinalizedBlock,
|
||||||
GetActiveEra,
|
GetActiveEra,
|
||||||
|
GetCurrentEra,
|
||||||
GetEpochProgress,
|
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>),
|
SetNodeName(Option<String>),
|
||||||
SetSystemHealth(Option<usize>, bool, bool),
|
SetSystemHealth(Option<usize>, bool, bool),
|
||||||
SetGenesisHash(Option<H256>),
|
SetGenesisHash(Option<H256>),
|
||||||
SetChainName(Option<String>),
|
SetChainName(Option<String>),
|
||||||
SetChainVersion(Option<String>),
|
SetChainVersion(Option<String>),
|
||||||
|
SetStashAccount([u8; 32]),
|
||||||
|
|
||||||
BestBlockInformation(H256, u32),
|
BestBlockInformation(H256, u32),
|
||||||
FinalizedBlockInformation(H256, u32),
|
FinalizedBlockInformation(H256, u32),
|
||||||
ExtrinsicsForBlock(u32, Vec<CasperExtrinsicDetails>),
|
ExtrinsicsForBlock(u32, Vec<CasperExtrinsicDetails>),
|
||||||
SetActiveEra(EraInfo),
|
SetActiveEra(EraInfo),
|
||||||
|
SetCurrentEra(u32),
|
||||||
SetEpochProgress(u64, u64),
|
SetEpochProgress(u64, u64),
|
||||||
SetPendingExtrinsicsLength(usize),
|
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,
|
GetTotalIssuance,
|
||||||
GetExistentialDeposit,
|
GetExistentialDeposit,
|
||||||
|
20
src/app.rs
20
src/app.rs
@ -12,7 +12,8 @@ use crate::{
|
|||||||
tui::{Event, Tui},
|
tui::{Event, Tui},
|
||||||
components::{
|
components::{
|
||||||
menu::Menu, version::Version, explorer::Explorer, wallet::Wallet,
|
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,
|
Menu,
|
||||||
Explorer,
|
Explorer,
|
||||||
Wallet,
|
Wallet,
|
||||||
WalletActive,
|
Validator,
|
||||||
ExplorerActive,
|
|
||||||
Empty,
|
Empty,
|
||||||
EmptyActive,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Mode {
|
impl Default for Mode {
|
||||||
@ -71,6 +70,7 @@ impl App {
|
|||||||
Box::new(Version::default()),
|
Box::new(Version::default()),
|
||||||
Box::new(Explorer::default()),
|
Box::new(Explorer::default()),
|
||||||
Box::new(Wallet::default()),
|
Box::new(Wallet::default()),
|
||||||
|
Box::new(Validator::default()),
|
||||||
Box::new(Empty::default()),
|
Box::new(Empty::default()),
|
||||||
],
|
],
|
||||||
should_quite: false,
|
should_quite: false,
|
||||||
@ -161,6 +161,7 @@ impl App {
|
|||||||
|
|
||||||
fn trigger_node_fast_events(&mut self) -> Result<()> {
|
fn trigger_node_fast_events(&mut self) -> Result<()> {
|
||||||
self.network_tx.send(Action::GetPendingExtrinsics)?;
|
self.network_tx.send(Action::GetPendingExtrinsics)?;
|
||||||
|
self.network_tx.send(Action::GetConnectedPeers)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,6 +172,8 @@ impl App {
|
|||||||
self.network_tx.send(Action::GetChainName)?;
|
self.network_tx.send(Action::GetChainName)?;
|
||||||
self.network_tx.send(Action::GetChainVersion)?;
|
self.network_tx.send(Action::GetChainVersion)?;
|
||||||
self.network_tx.send(Action::GetExistentialDeposit)?;
|
self.network_tx.send(Action::GetExistentialDeposit)?;
|
||||||
|
self.network_tx.send(Action::GetLocalIdentity)?;
|
||||||
|
self.network_tx.send(Action::GetListenAddresses)?;
|
||||||
Ok(())
|
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 Some(component) = self.components.last_mut() {
|
||||||
if let Err(err) = component.draw(frame, frame.area()) {
|
if let Err(err) = component.draw(frame, frame.area()) {
|
||||||
|
@ -20,6 +20,8 @@ pub struct Health {
|
|||||||
is_syncing: bool,
|
is_syncing: bool,
|
||||||
should_have_peers: bool,
|
should_have_peers: bool,
|
||||||
tx_pool_length: usize,
|
tx_pool_length: usize,
|
||||||
|
validators_count: u32,
|
||||||
|
nominators_count: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Health {
|
impl Default for Health {
|
||||||
@ -36,6 +38,8 @@ impl Health {
|
|||||||
is_syncing: true,
|
is_syncing: true,
|
||||||
should_have_peers: false,
|
should_have_peers: false,
|
||||||
tx_pool_length: 0,
|
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::SetNodeName(name) => self.name = name,
|
||||||
Action::SetPendingExtrinsicsLength(length) => self.tx_pool_length = length,
|
Action::SetPendingExtrinsicsLength(length) => self.tx_pool_length = length,
|
||||||
|
Action::NominatorsNumber(number) => self.nominators_count = number,
|
||||||
|
Action::ValidatorsNumber(number) => self.validators_count = number,
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@ -82,11 +88,13 @@ impl Component for Health {
|
|||||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||||
let [place, _] = super::header_layout(area);
|
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.name_as_string(),
|
||||||
self.tx_pool_length,
|
self.tx_pool_length,
|
||||||
self.peers_as_string(),
|
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 span = Span::styled(message, Style::new().dim());
|
||||||
let paragraph = Paragraph::new(span).left_aligned();
|
let paragraph = Paragraph::new(span).left_aligned();
|
||||||
|
@ -29,8 +29,8 @@ impl Menu {
|
|||||||
items: vec![
|
items: vec![
|
||||||
String::from("Explorer"),
|
String::from("Explorer"),
|
||||||
String::from("Wallet"),
|
String::from("Wallet"),
|
||||||
|
String::from("Validator"),
|
||||||
String::from("Prices"),
|
String::from("Prices"),
|
||||||
String::from("Staking"),
|
|
||||||
String::from("Governance"),
|
String::from("Governance"),
|
||||||
String::from("Operations"),
|
String::from("Operations"),
|
||||||
],
|
],
|
||||||
@ -57,6 +57,7 @@ impl Menu {
|
|||||||
match i {
|
match i {
|
||||||
0 => Ok(Some(Action::SetMode(Mode::Explorer))),
|
0 => Ok(Some(Action::SetMode(Mode::Explorer))),
|
||||||
1 => Ok(Some(Action::SetMode(Mode::Wallet))),
|
1 => Ok(Some(Action::SetMode(Mode::Wallet))),
|
||||||
|
2 => Ok(Some(Action::SetMode(Mode::Validator))),
|
||||||
_ => Ok(Some(Action::SetMode(Mode::Empty))),
|
_ => Ok(Some(Action::SetMode(Mode::Empty))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -76,6 +77,7 @@ impl Menu {
|
|||||||
match i {
|
match i {
|
||||||
0 => Ok(Some(Action::SetMode(Mode::Explorer))),
|
0 => Ok(Some(Action::SetMode(Mode::Explorer))),
|
||||||
1 => Ok(Some(Action::SetMode(Mode::Wallet))),
|
1 => Ok(Some(Action::SetMode(Mode::Wallet))),
|
||||||
|
2 => Ok(Some(Action::SetMode(Mode::Validator))),
|
||||||
_ => Ok(Some(Action::SetMode(Mode::Empty))),
|
_ => Ok(Some(Action::SetMode(Mode::Empty))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -115,6 +117,7 @@ impl Component for Menu {
|
|||||||
match self.list_state.selected() {
|
match self.list_state.selected() {
|
||||||
Some(0) => Ok(Some(Action::SetActiveScreen(Mode::Explorer))),
|
Some(0) => Ok(Some(Action::SetActiveScreen(Mode::Explorer))),
|
||||||
Some(1) => Ok(Some(Action::SetActiveScreen(Mode::Wallet))),
|
Some(1) => Ok(Some(Action::SetActiveScreen(Mode::Wallet))),
|
||||||
|
Some(2) => Ok(Some(Action::SetActiveScreen(Mode::Validator))),
|
||||||
_ => Ok(Some(Action::SetActiveScreen(Mode::Empty))),
|
_ => Ok(Some(Action::SetActiveScreen(Mode::Empty))),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -15,6 +15,7 @@ pub mod menu;
|
|||||||
pub mod version;
|
pub mod version;
|
||||||
pub mod explorer;
|
pub mod explorer;
|
||||||
pub mod wallet;
|
pub mod wallet;
|
||||||
|
pub mod validator;
|
||||||
pub mod empty;
|
pub mod empty;
|
||||||
|
|
||||||
pub trait Component {
|
pub trait Component {
|
||||||
@ -75,8 +76,8 @@ pub fn global_layout(area: Rect) -> [Rect; 2] {
|
|||||||
pub fn header_layout(area: Rect) -> [Rect; 2] {
|
pub fn header_layout(area: Rect) -> [Rect; 2] {
|
||||||
let [header, _] = global_layout(area);
|
let [header, _] = global_layout(area);
|
||||||
Layout::horizontal([
|
Layout::horizontal([
|
||||||
Constraint::Percentage(50),
|
Constraint::Fill(1),
|
||||||
Constraint::Percentage(50),
|
Constraint::Length(27),
|
||||||
]).areas(header)
|
]).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
|
Frame
|
||||||
};
|
};
|
||||||
use subxt::{
|
use subxt::{
|
||||||
tx::PairSigner,
|
|
||||||
ext::sp_core::{
|
ext::sp_core::{
|
||||||
Pair as PairT,
|
Pair as PairT,
|
||||||
sr25519::Pair,
|
sr25519::Pair,
|
||||||
@ -27,7 +26,6 @@ use tokio::sync::mpsc::UnboundedSender;
|
|||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
|
|
||||||
use super::{PartialComponent, Component, CurrentTab};
|
use super::{PartialComponent, Component, CurrentTab};
|
||||||
use crate::casper::CasperConfig;
|
|
||||||
use crate::types::{SystemAccount, ActionLevel};
|
use crate::types::{SystemAccount, ActionLevel};
|
||||||
use crate::{
|
use crate::{
|
||||||
action::Action,
|
action::Action,
|
||||||
@ -40,8 +38,6 @@ struct AccountInfo {
|
|||||||
address: String,
|
address: String,
|
||||||
account_id: [u8; 32],
|
account_id: [u8; 32],
|
||||||
seed: String,
|
seed: String,
|
||||||
#[allow(dead_code)]
|
|
||||||
pair_signer: PairSigner<CasperConfig, Pair>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Accounts {
|
pub struct Accounts {
|
||||||
@ -140,11 +136,10 @@ impl Accounts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn create_new_account(&mut self, name: String) {
|
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 secret_seed = hex::encode(seed);
|
||||||
let account_id = pair.public().0;
|
let account_id = pair.public().0;
|
||||||
let pair_signer = PairSigner::<CasperConfig, Pair>::new(pair);
|
let address = AccountId32::from(seed)
|
||||||
let address = AccountId32::from(seed.clone())
|
|
||||||
.to_ss58check_with_version(Ss58AddressFormat::custom(1996));
|
.to_ss58check_with_version(Ss58AddressFormat::custom(1996));
|
||||||
|
|
||||||
self.log_event(
|
self.log_event(
|
||||||
@ -158,7 +153,6 @@ impl Accounts {
|
|||||||
address,
|
address,
|
||||||
account_id,
|
account_id,
|
||||||
seed: secret_seed,
|
seed: secret_seed,
|
||||||
pair_signer,
|
|
||||||
});
|
});
|
||||||
self.last_row();
|
self.last_row();
|
||||||
self.save_to_file();
|
self.save_to_file();
|
||||||
@ -242,7 +236,6 @@ impl Accounts {
|
|||||||
let account_id = pair.public().0;
|
let account_id = pair.public().0;
|
||||||
let address = AccountId32::from(account_id)
|
let address = AccountId32::from(account_id)
|
||||||
.to_ss58check_with_version(Ss58AddressFormat::custom(1996));
|
.to_ss58check_with_version(Ss58AddressFormat::custom(1996));
|
||||||
let pair_signer = PairSigner::<CasperConfig, Pair>::new(pair);
|
|
||||||
|
|
||||||
self.send_balance_request(account_id, false);
|
self.send_balance_request(account_id, false);
|
||||||
self.wallet_keys.push(AccountInfo {
|
self.wallet_keys.push(AccountInfo {
|
||||||
@ -250,7 +243,6 @@ impl Accounts {
|
|||||||
account_id,
|
account_id,
|
||||||
address,
|
address,
|
||||||
seed: wallet_key.to_string(),
|
seed: wallet_key.to_string(),
|
||||||
pair_signer,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
self.log_event(format!("read {} wallets from disk",
|
self.log_event(format!("read {} wallets from disk",
|
||||||
@ -288,7 +280,6 @@ impl Accounts {
|
|||||||
let account_id = pair.public().0;
|
let account_id = pair.public().0;
|
||||||
let address = AccountId32::from(pair.public().0)
|
let address = AccountId32::from(pair.public().0)
|
||||||
.to_ss58check_with_version(Ss58AddressFormat::custom(1996));
|
.to_ss58check_with_version(Ss58AddressFormat::custom(1996));
|
||||||
let pair_signer = PairSigner::<CasperConfig, Pair>::new(pair);
|
|
||||||
|
|
||||||
let mut new_file = File::create(file_path)?;
|
let mut new_file = File::create(file_path)?;
|
||||||
writeln!(new_file, "ghostie:0x{}", &secret_seed)?;
|
writeln!(new_file, "ghostie:0x{}", &secret_seed)?;
|
||||||
@ -299,7 +290,6 @@ impl Accounts {
|
|||||||
address,
|
address,
|
||||||
account_id,
|
account_id,
|
||||||
seed: secret_seed,
|
seed: secret_seed,
|
||||||
pair_signer,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,7 @@ use clap::Parser;
|
|||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use subxt::{
|
use subxt::{
|
||||||
OnlineClient,
|
OnlineClient,
|
||||||
backend::{legacy::LegacyRpcMethods, rpc::RpcClient},
|
backend::rpc::RpcClient,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod action;
|
mod action;
|
||||||
@ -54,9 +54,7 @@ async fn main() -> Result<()> {
|
|||||||
let (action_tx, action_rx) = tokio::sync::mpsc::unbounded_channel();
|
let (action_tx, action_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||||
|
|
||||||
let rpc_client = RpcClient::from_url(args.rpc_endpoint).await?;
|
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 finalized_blocks_sub = online_client.blocks().subscribe_finalized().await?;
|
||||||
let best_blocks_sub = online_client.blocks().subscribe_best().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(
|
let mut network = network::Network::new(
|
||||||
cloned_action_tx,
|
cloned_action_tx,
|
||||||
online_client,
|
online_client,
|
||||||
legacy_client_api,
|
|
||||||
rpc_client,
|
rpc_client,
|
||||||
);
|
);
|
||||||
start_tokio_action_loop(sync_io_rx, &mut network);
|
start_tokio_action_loop(sync_io_rx, &mut network);
|
||||||
|
@ -1,27 +1,30 @@
|
|||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
use color_eyre::Result;
|
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(
|
pub async fn get_node_name(
|
||||||
action_tx: &UnboundedSender<Action>,
|
action_tx: &UnboundedSender<Action>,
|
||||||
api: &LegacyRpcMethods<CasperConfig>,
|
rpc_client: &RpcClient,
|
||||||
) -> Result<()> {
|
) -> 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))?;
|
action_tx.send(Action::SetNodeName(maybe_node_name))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_system_health(
|
pub async fn get_system_health(
|
||||||
action_tx: &UnboundedSender<Action>,
|
action_tx: &UnboundedSender<Action>,
|
||||||
api: &LegacyRpcMethods<CasperConfig>,
|
rpc_client: &RpcClient,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let (maybe_peers, is_syncing, should_have_peers) = api
|
let (maybe_peers, is_syncing, should_have_peers) = rpc_client
|
||||||
.system_health()
|
.request("system_health", rpc_params![])
|
||||||
.await
|
.await
|
||||||
.ok()
|
.ok()
|
||||||
.map_or((None, false, false), |health| (
|
.map_or((None, false, false), |health: SystemHealth| (
|
||||||
Some(health.peers),
|
Some(health.peers),
|
||||||
health.is_syncing,
|
health.is_syncing,
|
||||||
health.should_have_peers,
|
health.should_have_peers,
|
||||||
@ -35,10 +38,11 @@ pub async fn get_system_health(
|
|||||||
|
|
||||||
pub async fn get_genesis_hash(
|
pub async fn get_genesis_hash(
|
||||||
action_tx: &UnboundedSender<Action>,
|
action_tx: &UnboundedSender<Action>,
|
||||||
api: &LegacyRpcMethods<CasperConfig>,
|
rpc_client: &RpcClient,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let maybe_genesis_hash = api
|
let params = rpc_params![0u32];
|
||||||
.genesis_hash()
|
let maybe_genesis_hash = rpc_client
|
||||||
|
.request("chain_getBlockHash", params)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
action_tx.send(Action::SetGenesisHash(maybe_genesis_hash))?;
|
action_tx.send(Action::SetGenesisHash(maybe_genesis_hash))?;
|
||||||
@ -47,10 +51,10 @@ pub async fn get_genesis_hash(
|
|||||||
|
|
||||||
pub async fn get_chain_name(
|
pub async fn get_chain_name(
|
||||||
action_tx: &UnboundedSender<Action>,
|
action_tx: &UnboundedSender<Action>,
|
||||||
api: &LegacyRpcMethods<CasperConfig>,
|
rpc_client: &RpcClient,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let maybe_chain_name = api
|
let maybe_chain_name = rpc_client
|
||||||
.system_chain()
|
.request("system_chain", rpc_params![])
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
action_tx.send(Action::SetChainName(maybe_chain_name))?;
|
action_tx.send(Action::SetChainName(maybe_chain_name))?;
|
||||||
@ -59,12 +63,60 @@ pub async fn get_chain_name(
|
|||||||
|
|
||||||
pub async fn get_system_version(
|
pub async fn get_system_version(
|
||||||
action_tx: &UnboundedSender<Action>,
|
action_tx: &UnboundedSender<Action>,
|
||||||
api: &LegacyRpcMethods<CasperConfig>,
|
rpc_client: &RpcClient,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let maybe_system_version = api
|
let maybe_system_version = rpc_client
|
||||||
.system_version()
|
.request("system_version", rpc_params![])
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
action_tx.send(Action::SetChainVersion(maybe_system_version))?;
|
action_tx.send(Action::SetChainVersion(maybe_system_version))?;
|
||||||
Ok(())
|
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 tokio::sync::mpsc::UnboundedSender;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use subxt::{
|
use subxt::{
|
||||||
backend::{
|
backend::rpc::RpcClient,
|
||||||
legacy::LegacyRpcMethods,
|
|
||||||
rpc::RpcClient,
|
|
||||||
},
|
|
||||||
tx::{TxProgress, TxStatus},
|
tx::{TxProgress, TxStatus},
|
||||||
utils::H256,
|
utils::H256,
|
||||||
OnlineClient,
|
OnlineClient,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod legacy_rpc_calls;
|
mod legacy_rpc_calls;
|
||||||
mod predefinded_calls;
|
mod predefined_calls;
|
||||||
|
mod predefined_txs;
|
||||||
mod subscriptions;
|
mod subscriptions;
|
||||||
|
mod miscellaneous;
|
||||||
|
mod raw_calls;
|
||||||
|
|
||||||
|
pub use miscellaneous::{prepare_perbill_fraction_string, calculate_for_fraction};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
types::ActionLevel,
|
types::ActionLevel,
|
||||||
@ -25,10 +27,10 @@ pub use subscriptions::{FinalizedSubscription, BestSubscription};
|
|||||||
pub struct Network {
|
pub struct Network {
|
||||||
action_tx: UnboundedSender<Action>,
|
action_tx: UnboundedSender<Action>,
|
||||||
online_client_api: OnlineClient<CasperConfig>,
|
online_client_api: OnlineClient<CasperConfig>,
|
||||||
legacy_client_api: LegacyRpcMethods<CasperConfig>,
|
|
||||||
rpc_client: RpcClient,
|
rpc_client: RpcClient,
|
||||||
best_hash: Option<H256>,
|
best_hash: Option<H256>,
|
||||||
finalized_hash: Option<H256>,
|
finalized_hash: Option<H256>,
|
||||||
|
stash_to_watch: Option<[u8; 32]>,
|
||||||
accounts_to_watch: std::collections::HashSet<[u8; 32]>,
|
accounts_to_watch: std::collections::HashSet<[u8; 32]>,
|
||||||
transactions_to_watch: Vec<TxProgress<CasperConfig, OnlineClient<CasperConfig>>>,
|
transactions_to_watch: Vec<TxProgress<CasperConfig, OnlineClient<CasperConfig>>>,
|
||||||
}
|
}
|
||||||
@ -37,27 +39,42 @@ impl Network {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
action_tx: UnboundedSender<Action>,
|
action_tx: UnboundedSender<Action>,
|
||||||
online_client_api: OnlineClient<CasperConfig>,
|
online_client_api: OnlineClient<CasperConfig>,
|
||||||
legacy_client_api: LegacyRpcMethods<CasperConfig>,
|
|
||||||
rpc_client: RpcClient,
|
rpc_client: RpcClient,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
action_tx,
|
action_tx,
|
||||||
online_client_api,
|
online_client_api,
|
||||||
legacy_client_api,
|
|
||||||
rpc_client,
|
rpc_client,
|
||||||
best_hash: None,
|
best_hash: None,
|
||||||
finalized_hash: None,
|
finalized_hash: None,
|
||||||
|
stash_to_watch: None,
|
||||||
accounts_to_watch: Default::default(),
|
accounts_to_watch: Default::default(),
|
||||||
transactions_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<()> {
|
pub async fn handle_network_event(&mut self, io_event: Action) -> Result<()> {
|
||||||
match io_event {
|
match io_event {
|
||||||
Action::NewBestHash(hash) => {
|
Action::NewBestHash(hash) => {
|
||||||
self.best_hash = Some(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() {
|
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(())
|
Ok(())
|
||||||
},
|
},
|
||||||
@ -100,26 +117,66 @@ impl Network {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
Action::GetSystemHealth => legacy_rpc_calls::get_system_health(&self.action_tx, &self.legacy_client_api).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.legacy_client_api).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.legacy_client_api).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.legacy_client_api).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.legacy_client_api).await,
|
Action::GetChainVersion => legacy_rpc_calls::get_system_version(&self.action_tx, &self.rpc_client).await,
|
||||||
Action::GetBlockAuthor(hash, logs) => predefinded_calls::get_block_author(&self.action_tx, &self.online_client_api, &logs, &hash).await,
|
Action::GetPendingExtrinsics => legacy_rpc_calls::get_pending_extrinsics(&self.action_tx, &self.rpc_client).await,
|
||||||
Action::GetActiveEra => predefinded_calls::get_active_era(&self.action_tx, &self.online_client_api).await,
|
Action::GetConnectedPeers => legacy_rpc_calls::get_connected_peers(&self.action_tx, &self.rpc_client).await,
|
||||||
Action::GetEpochProgress => predefinded_calls::get_epoch_progress(&self.action_tx, &self.online_client_api).await,
|
Action::GetListenAddresses => legacy_rpc_calls::get_listen_addresses(&self.action_tx, &self.rpc_client).await,
|
||||||
Action::GetPendingExtrinsics => predefinded_calls::get_pending_extrinsics(&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::GetBlockAuthor(hash, logs) => predefined_calls::get_block_author(&self.action_tx, &self.online_client_api, &logs, &hash).await,
|
||||||
Action::GetTotalIssuance => predefinded_calls::get_total_issuance(&self.action_tx, &self.online_client_api).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) => {
|
Action::BalanceRequest(account_id, remove) => {
|
||||||
if remove {
|
if remove {
|
||||||
let _ = self.accounts_to_watch.remove(&account_id);
|
let _ = self.accounts_to_watch.remove(&account_id);
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
let _ = self.accounts_to_watch.insert(account_id);
|
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) => {
|
Action::TransferBalance(sender, receiver, amount) => {
|
||||||
@ -129,7 +186,7 @@ impl Network {
|
|||||||
.try_into()
|
.try_into()
|
||||||
.expect("stored seed is valid length; qed");
|
.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.action_tx,
|
||||||
&self.online_client_api,
|
&self.online_client_api,
|
||||||
&sender,
|
&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::NewBestHash(block_hash))?;
|
||||||
self.network_tx.send(Action::GetBlockAuthor(block_hash, block.header().digest.logs.clone()))?;
|
self.network_tx.send(Action::GetBlockAuthor(block_hash, block.header().digest.logs.clone()))?;
|
||||||
self.network_tx.send(Action::GetActiveEra)?;
|
self.network_tx.send(Action::GetActiveEra)?;
|
||||||
|
self.network_tx.send(Action::GetCurrentEra)?;
|
||||||
self.network_tx.send(Action::GetEpochProgress)?;
|
self.network_tx.send(Action::GetEpochProgress)?;
|
||||||
self.network_tx.send(Action::GetTotalIssuance)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,14 @@ mod era;
|
|||||||
mod extrinsics;
|
mod extrinsics;
|
||||||
mod log;
|
mod log;
|
||||||
mod account;
|
mod account;
|
||||||
|
mod peer;
|
||||||
|
mod session;
|
||||||
|
mod nominator;
|
||||||
|
|
||||||
pub use extrinsics::CasperExtrinsicDetails;
|
pub use extrinsics::CasperExtrinsicDetails;
|
||||||
pub use era::EraInfo;
|
pub use era::EraInfo;
|
||||||
pub use log::ActionLevel;
|
pub use log::ActionLevel;
|
||||||
pub use account::SystemAccount;
|
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 {
|
impl ToString for VerticalBlocks {
|
||||||
fn to_string(&self) -> String {
|
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)
|
self.elements[((chrono::Utc::now().timestamp_millis() % super::CYCLE)
|
||||||
/ (super::CYCLE / self.elements.len() as i64)) as usize]
|
/ (super::CYCLE / self.elements.len() as i64)) as usize]
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
Loading…
Reference in New Issue
Block a user