diff --git a/src/action.rs b/src/action.rs index ebcd227..79cc9f2 100644 --- a/src/action.rs +++ b/src/action.rs @@ -49,11 +49,13 @@ pub enum Action { UpdateKnownValidator(String), TransferTo(String), AccountDetailsOf(String, Option), + StoreRotatedKeys(String), TransferBalance(String, [u8; 32], u128), BondValidatorExtraFrom([u8; 32], u128), BondValidatorFrom([u8; 32], u128), PayoutStakers([u8; 32], [u8; 32], u32), + SetSessionKeys([u8; 32], String), EventLog(String, ActionLevel, ActionTarget), NewBestBlock(u32), diff --git a/src/components/validator/bond_popup.rs b/src/components/validator/bond_popup.rs index 7ac320c..1a79918 100644 --- a/src/components/validator/bond_popup.rs +++ b/src/components/validator/bond_popup.rs @@ -10,7 +10,11 @@ use std::sync::mpsc::Sender; use super::{Component, PartialComponent, CurrentTab}; use crate::{ - action::Action, config::Config, palette::StylePalette, types::{ActionLevel, ActionTarget}, widgets::{Input, InputRequest} + action::Action, + config::Config, + palette::StylePalette, + types::{ActionLevel, ActionTarget}, + widgets::{Input, InputRequest}, }; #[derive(Debug)] diff --git a/src/components/validator/rotate_popup.rs b/src/components/validator/rotate_popup.rs index a5b296a..26d617d 100644 --- a/src/components/validator/rotate_popup.rs +++ b/src/components/validator/rotate_popup.rs @@ -2,10 +2,11 @@ use crossterm::event::{KeyCode, KeyEvent, KeyEventKind}; use color_eyre::Result; use ratatui::{ layout::{Alignment, Constraint, Flex, Layout, Rect}, - widgets::{Block, Clear, Paragraph}, - Frame + text::Text, + widgets::{Block, Cell, Clear, Row, Table}, + Frame, }; -//use tokio::sync::mpsc::UnboundedSender; +use tokio::sync::mpsc::UnboundedSender; use std::sync::mpsc::Sender; use super::{Component, PartialComponent, CurrentTab}; @@ -19,8 +20,10 @@ use crate::{ #[derive(Debug)] pub struct RotatePopup { is_active: bool, - //action_tx: Option>, + action_tx: Option>, network_tx: Option>, + cached_keys: String, + secret_seed: [u8; 32], palette: StylePalette } @@ -34,17 +37,46 @@ impl RotatePopup { pub fn new() -> Self { Self { is_active: false, - //action_tx: None, + action_tx: None, network_tx: None, + cached_keys: String::new(), + secret_seed: [0u8; 32], palette: StylePalette::default(), } } fn rotate_keys(&mut self) { - todo!(); - //if let Some(network_tx) = &self.network_tx { - // let _ = network_tx.send(Action::RotateSessionKeys); - //} + if !self.cached_keys.is_empty() && self.cached_keys.len() == 258 { + if let Some(network_tx) = &self.network_tx { + let _ = network_tx.send(Action::SetSessionKeys( + self.secret_seed, self.cached_keys.clone())); + } + if let Some(action_tx) = &self.action_tx { + let _ = action_tx.send(Action::ClosePopup); + } + } else { + if let Some(network_tx) = &self.network_tx { + let _ = network_tx.send(Action::RotateSessionKeys); + } + } + } + + fn parse_session_keys(&self) -> (String, String, String, String) { + if !self.cached_keys.is_empty() && self.cached_keys.len() == 258 { + let gran_key = format!("0x{}", &self.cached_keys[2..66]); + let babe_key = format!("0x{}", &self.cached_keys[66..130]); + let audi_key = format!("0x{}", &self.cached_keys[130..194]); + let slow_key = format!("0x{}", &self.cached_keys[194..258]); + + (gran_key, babe_key, audi_key, slow_key) + } else { + ( + String::from("not prepared"), + String::from("not prepared"), + String::from("not prepared"), + String::from("not prepared"), + ) + } } } @@ -73,6 +105,16 @@ impl Component for RotatePopup { } Ok(()) } + + fn update(&mut self, action: Action) -> Result> { + match action { + Action::StoreRotatedKeys(cached_keys) => self.cached_keys = cached_keys, + Action::SetStashSecret(secret_seed) => self.secret_seed = secret_seed, + _ => {} + }; + Ok(None) + } + fn handle_key_event(&mut self, key: KeyEvent) -> Result> { if self.is_active && key.kind == KeyEventKind::Press { match key.code { @@ -87,20 +129,48 @@ impl Component for RotatePopup { fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { if self.is_active { let (border_style, border_type) = self.palette.create_popup_style(); - let popup = Paragraph::new(" Do you want to proceed key rotation?") - .block(Block::bordered() - .border_style(border_style) - .border_type(border_type) - .title_style(self.palette.create_popup_title_style()) - .title_alignment(Alignment::Right) - .title("Enter to proceed / Esc to close")); - let v = Layout::vertical([Constraint::Max(3)]).flex(Flex::Center); - let h = Layout::horizontal([Constraint::Max(50)]).flex(Flex::Center); + + let (gran_key, babe_key, audi_key, slow_key) = self.parse_session_keys(); + let table = Table::new( + vec![ + Row::new(vec![ + Cell::from(Text::from("gran".to_string()).alignment(Alignment::Left)), + Cell::from(Text::from(gran_key).alignment(Alignment::Right)), + ]), + Row::new(vec![ + Cell::from(Text::from("babe".to_string()).alignment(Alignment::Left)), + Cell::from(Text::from(babe_key).alignment(Alignment::Right)), + ]), + Row::new(vec![ + Cell::from(Text::from("audi".to_string()).alignment(Alignment::Left)), + Cell::from(Text::from(audi_key).alignment(Alignment::Right)), + ]), + Row::new(vec![ + Cell::from(Text::from("slow".to_string()).alignment(Alignment::Left)), + Cell::from(Text::from(slow_key).alignment(Alignment::Right)), + ]), + ], + [ + Constraint::Min(4), + 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("Rotate session keys (Enter to proceed / Esc to close)")); + + let v = Layout::vertical([Constraint::Max(6)]).flex(Flex::Center); + let h = Layout::horizontal([Constraint::Max(73)]).flex(Flex::Center); let [area] = v.areas(area); let [area] = h.areas(area); frame.render_widget(Clear, area); - frame.render_widget(popup, area); + frame.render_widget(table, area); } Ok(()) } diff --git a/src/network/legacy_rpc_calls.rs b/src/network/legacy_rpc_calls.rs index 7a306b4..81e6960 100644 --- a/src/network/legacy_rpc_calls.rs +++ b/src/network/legacy_rpc_calls.rs @@ -120,3 +120,15 @@ pub async fn get_local_identity( action_tx.send(Action::SetLocalIdentity(local_peer_id))?; Ok(()) } + +pub async fn rotate_keys( + action_tx: &UnboundedSender, + rpc_client: &RpcClient, +) -> Result<()> { + let rotated_keys: String = rpc_client + .request("author_rotateKeys", rpc_params![]) + .await + .unwrap_or_default(); + action_tx.send(Action::StoreRotatedKeys(rotated_keys))?; + Ok(()) +} diff --git a/src/network/mod.rs b/src/network/mod.rs index 77d3b07..31d2172 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -166,6 +166,7 @@ impl Network { Action::GetConnectedPeers => legacy_rpc_calls::get_connected_peers(&self.action_tx, &self.rpc_client).await, Action::GetListenAddresses => legacy_rpc_calls::get_listen_addresses(&self.action_tx, &self.rpc_client).await, Action::GetLocalIdentity => legacy_rpc_calls::get_local_identity(&self.action_tx, &self.rpc_client).await, + Action::RotateSessionKeys => legacy_rpc_calls::rotate_keys(&self.action_tx, &self.rpc_client).await, Action::GetBlockAuthor(hash, logs) => predefined_calls::get_block_author(&self.action_tx, &self.online_client_api, &logs, &hash).await, Action::GetActiveEra => predefined_calls::get_active_era(&self.action_tx, &self.online_client_api).await, @@ -296,9 +297,9 @@ impl Network { let sender_str = hex::encode(sender); let maybe_nonce = self.senders.get_mut(&sender_str); if let Ok(tx_progress) = predefined_txs::payout_stakers( - &self.action_tx, - &self.online_client_api, - &sender, + &self.action_tx, + &self.online_client_api, + &sender, &stash, era_index, maybe_nonce, @@ -312,6 +313,24 @@ impl Network { } Ok(()) } + Action::SetSessionKeys(sender, hashed_keys) => { + let sender_str = hex::encode(sender); + let maybe_nonce = self.senders.get_mut(&sender_str); + if let Ok(tx_progress) = predefined_txs::set_keys( + &self.action_tx, + &self.online_client_api, + &sender, + &hashed_keys, + maybe_nonce, + ).await { + self.transactions_to_watch.push(TxToWatch { + tx_progress, + sender: sender_str, + target: ActionTarget::ValidatorLog, + }); + } + Ok(()) + } _ => Ok(()) } } diff --git a/src/network/predefined_txs.rs b/src/network/predefined_txs.rs index f6f5204..fbb7d0a 100644 --- a/src/network/predefined_txs.rs +++ b/src/network/predefined_txs.rs @@ -7,10 +7,7 @@ use subxt::{ use tokio::sync::mpsc::UnboundedSender; use crate::{ - action::Action, - types::{ActionLevel, ActionTarget}, - casper::{CasperExtrinsicParamsBuilder, CasperConfig}, - casper_network, + action::Action, casper::{CasperConfig, CasperExtrinsicParamsBuilder}, casper_network::{self, runtime_types}, types::{ActionLevel, ActionTarget} }; pub async fn transfer_balance( @@ -203,3 +200,68 @@ pub async fn payout_stakers( } } } + +pub async fn set_keys( + action_tx: &UnboundedSender, + api: &OnlineClient, + sender: &[u8; 32], + hashed_keys_str: &String, + mut maybe_nonce: Option<&mut u32>, +) -> Result>> { + let (gran_key, babe_key, audi_key, slow_key) = { + let s = hashed_keys_str.trim_start_matches("0x"); + ( + hex::decode(&s[0..64]).unwrap().as_slice().try_into().unwrap(), + hex::decode(&s[64..128]).unwrap().as_slice().try_into().unwrap(), + hex::decode(&s[128..192]).unwrap().as_slice().try_into().unwrap(), + hex::decode(&s[192..256]).unwrap().as_slice().try_into().unwrap(), + ) + }; + + let session_keys = runtime_types::casper_runtime::opaque::SessionKeys { + grandpa: runtime_types::sp_consensus_grandpa::app::Public(gran_key), + babe: runtime_types::sp_consensus_babe::app::Public(babe_key), + authority_discovery: runtime_types::sp_authority_discovery::app::Public(audi_key), + slow_clap: runtime_types::ghost_slow_clap::sr25519::app_sr25519::Public(slow_key), + }; + + // it seems like there is no check for the second paramter, that's why + // we it can be anything. + // Not sure TBH + let transfer_tx = casper_network::tx() + .session() + .set_keys(session_keys, Vec::new()); + + let tx_params = match maybe_nonce { + Some(ref mut nonce) => { + **nonce = nonce.saturating_add(1); + CasperExtrinsicParamsBuilder::new() + .nonce(nonce.saturating_sub(1) as u64) + .build() + }, + None => CasperExtrinsicParamsBuilder::new().build(), + }; + + let pair = Pair::from_seed(sender); + let signer = PairSigner::::new(pair); + + match api + .tx() + .sign_and_submit_then_watch(&transfer_tx, &signer, tx_params) + .await { + Ok(tx_progress) => { + action_tx.send(Action::EventLog( + format!("set keys {} sent", tx_progress.extrinsic_hash()), + ActionLevel::Info, + ActionTarget::ValidatorLog))?; + Ok(tx_progress) + }, + Err(err) => { + action_tx.send(Action::EventLog( + format!("error during set keys: {err}"), + ActionLevel::Error, + ActionTarget::ValidatorLog))?; + Err(err.into()) + } + } +}