diff --git a/Cargo.toml b/Cargo.toml index 745b104..43d1c9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ghost-eye" -version = "0.1.0" +version = "0.2.0" edition = "2021" [dependencies] @@ -21,20 +21,13 @@ human-panic = "2.0.2" json5 = "0.4.1" lazy_static = "1.5.0" libc = "0.2.159" -log = "0.4.22" -once_cell = "1.20.2" -pretty_assertions = "1.4.1" -rand = "0.8.5" +primitive-types = "0.13.1" ratatui = { version = "0.28.1", features = ["serde", "macros"] } -reqwest = { version = "0.12.8", features = ["json"] } serde = { version = "1.0.210", features = ["derive"] } -serde_json = "1.0.128" signal-hook = "0.3.17" -sp-consensus-babe = "0.40.0" sp-core = "34.0.0" -sp-runtime = "39.0.2" -strip-ansi-escapes = "0.2.0" strum = { version = "0.26.3", features = ["derive"] } +subxt = { version = "0.38.0", features = ["jsonrpsee"] } tokio = { version = "1.40.0", features = ["full"] } tokio-util = "0.7.12" tracing = "0.1.37" diff --git a/artifacts/casper.scale b/artifacts/casper.scale new file mode 100644 index 0000000..f99ff36 Binary files /dev/null and b/artifacts/casper.scale differ diff --git a/src/action.rs b/src/action.rs index 053b4ab..4f8832f 100644 --- a/src/action.rs +++ b/src/action.rs @@ -1,9 +1,12 @@ use serde::{Deserialize, Serialize}; use strum::Display; +use primitive_types::H256; -use crate::types::{ - block::BlockInfo, - era::EraInfo, +use subxt::config::substrate::DigestItem; + +use crate::{ + CasperAccountId, + types::{EraInfo, CasperExtrinsicDetails}, }; #[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)] @@ -20,29 +23,39 @@ pub enum Action { SetMode(crate::app::Mode), + NewBestBlock(u32), + NewBestHash(H256), + NewFinalizedBlock(u32), + NewFinalizedHash(H256), + BestBlockUpdated(u32), + ExtrinsicsLength(u32, usize), + + GetBlockAuthor(H256, Vec), + SetBlockAuthor(H256, Option), + GetNodeName, - GetSyncState, + GetSystemHealth, GetGenesisHash, GetChainName, - GetNodeVersion, + GetChainVersion, GetPendingExtrinsics, GetLatestBlock, GetFinalizedBlock, GetActiveEra, - GetEpoch, + GetEpochProgress, GetValidators, SetNodeName(Option), - SetSyncState(Option, bool, bool), - SetGenesisHash(Option), + SetSystemHealth(Option, bool, bool), + SetGenesisHash(Option), SetChainName(Option), - SetNodeVersion(Option), + SetChainVersion(Option), - SetLatestBlock(String, BlockInfo), - SetFinalizedBlock(String, BlockInfo), + BestBlockInformation(H256, u32, Vec), + FinalizedBlockInformation(H256, u32, Vec), SetActiveEra(EraInfo), - SetEpoch(u64, u64), - SetValidators(Vec), - SetPendingExtrinsicsLength(usize), + SetEpochProgress(u64, u64), + SetValidatorsForExplorer(Vec), // TODO: change to BlockAuthor + SetPendingExtrinsicsLength(usize), // TODO: rename in oreder to match tx.pool } diff --git a/src/app.rs b/src/app.rs index 783f48d..a167f8e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -138,9 +138,8 @@ impl App { Event::Render => action_tx.send(Action::Render)?, Event::Resize(x, y) => action_tx.send(Action::Resize(x, y))?, Event::Key(key) => self.handle_key_event(key)?, - Event::Node => self.trigger_node_events()?, - Event::FastNode => self.trigger_node_fast_events()?, - Event::Runtime => self.trigger_runtime_events()?, + Event::Oneshot => self.trigger_oneshot_node_events()?, + Event::Node => self.trigger_node_fast_events()?, _ => {} } @@ -157,21 +156,12 @@ impl App { Ok(()) } - fn trigger_node_events(&mut self) -> Result<()> { + fn trigger_oneshot_node_events(&mut self) -> Result<()> { self.network_tx.send(Action::GetNodeName)?; - self.network_tx.send(Action::GetSyncState)?; + self.network_tx.send(Action::GetSystemHealth)?; self.network_tx.send(Action::GetGenesisHash)?; self.network_tx.send(Action::GetChainName)?; - self.network_tx.send(Action::GetNodeVersion)?; - Ok(()) - } - - fn trigger_runtime_events(&mut self) -> Result<()> { - self.network_tx.send(Action::GetLatestBlock)?; - self.network_tx.send(Action::GetFinalizedBlock)?; - self.network_tx.send(Action::GetActiveEra)?; - self.network_tx.send(Action::GetEpoch)?; - self.network_tx.send(Action::GetValidators)?; + self.network_tx.send(Action::GetChainVersion)?; Ok(()) } diff --git a/src/casper.rs b/src/casper.rs new file mode 100644 index 0000000..d5922a3 --- /dev/null +++ b/src/casper.rs @@ -0,0 +1,33 @@ +//! Casper specific configuration + +use subxt::{ + Config, blocks::Block, client::OnlineClient, + config::{DefaultExtrinsicParams, SubstrateConfig}, +}; + +/// Default set of commonly used type by Casper nodes. +#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub enum CasperConfig {} + +impl Config for CasperConfig { + type Hash = ::Hash; + type AccountId = ::AccountId; + type Address = ::Address; + type Signature = ::Signature; + type Hasher = ::Hasher; + type Header = ::Header; + type ExtrinsicParams = CasperExtrinsicParams; + type AssetId = u32; +} + + +pub type CasperAccountId = subxt::utils::AccountId32; +pub type CasperBlock = Block>; + +/// A struct representing the signed extra and additional parameters required to construct a +/// transaction for a polkadot node. +pub type CasperExtrinsicParams = DefaultExtrinsicParams; + +///// A builder which leads to [`CasperExtrinsicParams`] being constructed. This is what we provide +///// to methods like `sign_and_submit()`. +//pub type CasperExtrinsicParamsBuilder = DefaultExtrinsicParamsBuilder; diff --git a/src/cli.rs b/src/cli.rs index ba0a3ce..942608f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -14,7 +14,7 @@ pub struct Cli { pub frame_rate: f32, /// RPC Endpoint to the nodes JSON RPC - #[arg(short, long, default_value_t = String::from("http://localhost:9945"))] + #[arg(short, long, default_value_t = String::from("ws://localhost:9945"))] pub rpc_endpoint: String, /// Request timeout in seconds diff --git a/src/components/explorer/block_ticker.rs b/src/components/explorer/block_ticker.rs index b0b0148..107911b 100644 --- a/src/components/explorer/block_ticker.rs +++ b/src/components/explorer/block_ticker.rs @@ -36,9 +36,7 @@ impl BlockTicker { } } - fn block_found(&mut self, block: &str) -> Result<()> { - let block = block.trim_start_matches("0x"); - let block = u32::from_str_radix(&block, 16)?; + fn block_found(&mut self, block: u32) -> Result<()> { if self.last_block < block { self.last_block_time = Instant::now(); self.last_block = block; @@ -50,8 +48,7 @@ impl BlockTicker { impl Component for BlockTicker { fn update(&mut self, action: Action) -> Result> { match action { - Action::SetLatestBlock(_, block_info) => - self.block_found(&block_info.header.number)?, + Action::BestBlockUpdated(block) => self.block_found(block)?, _ => {} }; Ok(None) @@ -83,7 +80,7 @@ impl Component for BlockTicker { .border_type(border_type) .title_alignment(Alignment::Right) .title_style(self.palette.create_title_style()) - .padding(Padding::new(0, 0, (height - 2) / 2, 0)) + .padding(Padding::new(0, 0, height.saturating_sub(2) / 2, 0)) .title("Passed")) .alignment(Alignment::Center) .wrap(Wrap { trim: true }); @@ -108,7 +105,7 @@ impl Component for BlockTicker { .wrap(Wrap { trim: true }); frame.render_widget(paragraph, place); - let height_offset = (height - 2) / 2; + let height_offset = height.saturating_sub(2) / 2; let place = place.offset(ratatui::layout::Offset { x: 1, y: height_offset as i32 }) .intersection(place); frame.render_widget(big_text, place); diff --git a/src/components/explorer/current_epoch.rs b/src/components/explorer/current_epoch.rs index 3db463d..a037111 100644 --- a/src/components/explorer/current_epoch.rs +++ b/src/components/explorer/current_epoch.rs @@ -22,7 +22,7 @@ impl CurrentEpoch { const SECONDS_IN_DAY: u64 = 86_400; const SECONDS_IN_HOUR: u64 = 3_600; - fn update_epoch(&mut self, number: u64, progress: u64) -> Result<()> { + fn update_epoch_progress(&mut self, number: u64, progress: u64) -> Result<()> { self.number = number; self.progress = progress; Ok(()) @@ -32,7 +32,8 @@ impl CurrentEpoch { impl Component for CurrentEpoch { fn update(&mut self, action: Action) -> Result> { match action { - Action::SetEpoch(number, progress) => self.update_epoch(number, progress)?, + Action::SetEpochProgress(number, progress) => + self.update_epoch_progress(number, progress)?, _ => {} }; Ok(None) @@ -70,7 +71,7 @@ impl Component for CurrentEpoch { .border_type(border_type) .title_alignment(Alignment::Right) .title_style(self.palette.create_title_style()) - .padding(Padding::new(0, 0, (height - 3) / 2, 0)) + .padding(Padding::new(0, 0, height.saturating_sub(3) / 2, 0)) .title("Epoch")) .alignment(Alignment::Center) .wrap(Wrap { trim: true }); @@ -100,7 +101,7 @@ impl Component for CurrentEpoch { .wrap(Wrap { trim: true }); frame.render_widget(paragraph, place); - let height_offset = (height - 2) / 2; + let height_offset = height.saturating_sub(2) / 2; let place = place.offset(ratatui::layout::Offset { x: 1, y: height_offset as i32 }) .intersection(place); frame.render_widget(big_text, place); diff --git a/src/components/explorer/current_era.rs b/src/components/explorer/current_era.rs index 203da5f..8203d87 100644 --- a/src/components/explorer/current_era.rs +++ b/src/components/explorer/current_era.rs @@ -7,7 +7,12 @@ use ratatui::{ }; use super::Component; -use crate::{action::Action, palette::StylePalette, types::era::EraInfo, widgets::{PixelSize, BigText}}; +use crate::{ + action::Action, + palette::StylePalette, + types::EraInfo, + widgets::{PixelSize, BigText}, +}; #[derive(Debug, Default)] pub struct CurrentEra{ @@ -84,7 +89,7 @@ impl Component for CurrentEra { .border_type(border_type) .title_alignment(Alignment::Right) .title_style(self.palette.create_title_style()) - .padding(Padding::new(0, 0, (height - 3) / 2, 0)) + .padding(Padding::new(0, 0, (height.saturating_sub(3)) / 2, 0)) .title("Era")) .alignment(Alignment::Center) .wrap(Wrap { trim: true }); @@ -114,7 +119,7 @@ impl Component for CurrentEra { .wrap(Wrap { trim: true }); frame.render_widget(paragraph, place); - let height_offset = (height - 2) / 2; + let height_offset = height.saturating_sub(2) / 2; let place = place.offset(ratatui::layout::Offset { x: 1, y: height_offset as i32 }) .intersection(place); frame.render_widget(big_text, place); diff --git a/src/components/explorer/explorer_blocks.rs b/src/components/explorer/explorer_blocks.rs index 6d35e8b..25996a4 100644 --- a/src/components/explorer/explorer_blocks.rs +++ b/src/components/explorer/explorer_blocks.rs @@ -1,6 +1,7 @@ use std::collections::{HashMap, VecDeque}; use color_eyre::Result; use crossterm::event::{KeyCode, KeyEvent}; +use primitive_types::H256; use ratatui::{ layout::{Alignment, Rect}, prelude::*, @@ -9,106 +10,83 @@ use ratatui::{ widgets::{Block, BorderType, Paragraph}, Frame }; -use sp_consensus_babe::digests::PreDigest; -use sp_runtime::DigestItem; +use sp_core::crypto::{AccountId32, Ss58Codec, Ss58AddressFormat}; use codec::Decode; use super::Component; -use crate::{action::Action, app::Mode, palette::StylePalette}; +use crate::{ + types::CasperExtrinsicDetails, CasperAccountId, + action::Action, app::Mode, palette::StylePalette, +}; -#[derive(Debug, Clone)] struct BlockInfo { block_number: u32, finalized: bool, - hash: String, } -#[derive(Debug, Default)] +#[derive(Default)] pub struct ExplorerBlocks { blocks: VecDeque, - extrinsics: HashMap>, - logs: HashMap>, - validators: Vec, + block_headers: HashMap, + authors: HashMap, + extrinsics: HashMap>, palette: StylePalette, - max_block_len: u32, + current_block_digit_length: u32, is_active: bool, used_paragraph_index: usize, - used_block_index: Option<(usize, u32)>, - used_ext_index: Option<(String, usize, usize)>, + used_block_number: Option, + used_ext_index: Option<(H256, usize)>, } impl ExplorerBlocks { const MAX_BLOCKS: usize = 50; - const LENGTH_OF_BLOCK_HASH: u16 = 66; // hash + 0x prefix + const LENGTH_OF_BLOCK_HASH: u16 = 13; const LENGTH_OF_ADDRESS: u16 = 49; const TOTAL_OFFSETS: u16 = 18; - fn update_validator_list(&mut self, validators: Vec) -> Result<()> { - self.validators = validators; + fn update_block_author( + &mut self, + hash: H256, + maybe_author: Option, + ) -> Result<()> { + if let Some(author) = maybe_author { + self.authors.insert(hash, author); + } Ok(()) } - fn get_author_from_digest(&self, logs: Vec) -> Option { - let logs = logs - .iter() - .map_while(|log| { - hex::decode(log.trim_start_matches("0x")) - .ok() - .map(|log_hex| DigestItem::decode(&mut &log_hex[..]).ok()) - }) - .filter_map(|digest| digest) - .collect::>(); - - 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)) => self.validators.get(primary.authority_index as usize), - Ok(PreDigest::SecondaryPlain(secondary)) => self.validators.get(secondary.authority_index as usize), - Ok(PreDigest::SecondaryVRF(secondary)) => self.validators.get(secondary.authority_index as usize), - _ => None, - } - }, - _ => None, - }; - - maybe_author.cloned() - } - fn update_latest_block_info( &mut self, - hash: String, - number_hex_str: String, - logs: Vec, - extrinsics: Vec, + hash: H256, + block_number: u32, + extrinsics: Vec, ) -> Result<()> { - let number_hex_str = number_hex_str.trim_start_matches("0x"); - let block_number = u32::from_str_radix(&number_hex_str, 16)?; - let front_block_number = if self.blocks.is_empty() { - 0 - } else { - self.blocks.front().unwrap().block_number + let front_block_number = match self.blocks.front() { + Some(block_info) => block_info.block_number, + None => 0, }; if front_block_number < block_number { self.blocks.push_front(BlockInfo { block_number, finalized: false, - hash: hash.clone(), }); - self.extrinsics.insert(hash.clone(), extrinsics); - self.logs.insert(hash, logs); + self.extrinsics.insert(hash, extrinsics); + self.block_headers.insert(block_number, hash); let block_length = block_number.checked_ilog10().unwrap_or(0) + 1; - if self.max_block_len < block_length { - self.max_block_len = block_length; + if self.current_block_digit_length < block_length { + self.current_block_digit_length = block_length; } if self.blocks.len() > Self::MAX_BLOCKS { - if let Some(removed_block_info) = self.blocks.pop_back() { - self.extrinsics.remove(&removed_block_info.hash); - self.logs.remove(&removed_block_info.hash); + if let Some(block) = self.blocks.pop_back() { + if let Some(hash) = self.block_headers.remove(&block.block_number) { + self.extrinsics.remove(&hash); + self.authors.remove(&hash); + } } } } @@ -117,23 +95,14 @@ impl ExplorerBlocks { fn update_finalized_block_info( &mut self, - hash: String, - number_hex_str: String, - logs: Vec, - extrinsics: Vec, + _hash: H256, + block_number: u32, + _extrinsics: Vec, ) -> Result<()> { - let number_hex_str = number_hex_str.trim_start_matches("0x"); - let block_number = u32::from_str_radix(&number_hex_str, 16)?; - for idx in 0..self.blocks.len() { if self.blocks[idx].finalized { break; } else if self.blocks[idx].block_number > block_number { continue; } - else { - self.blocks[idx].finalized = true; - self.blocks[idx].hash = hash.clone(); - self.extrinsics.insert(hash.clone(), extrinsics.clone()); - self.logs.insert(hash.clone(), logs.clone()); - } + else { self.blocks[idx].finalized = true; } } Ok(()) @@ -141,49 +110,52 @@ impl ExplorerBlocks { fn prepare_block_line_info(&self, current_block: &BlockInfo, width: u16) -> Line { let block_number_length = self - .max_block_len + .current_block_digit_length .max(current_block.block_number.checked_ilog10().unwrap_or(0) + 1) as usize; let free_space = width .saturating_sub(block_number_length as u16) .saturating_sub(Self::TOTAL_OFFSETS); + let default_hash = H256::repeat_byte(69u8); + let hash = self + .block_headers + .get(¤t_block.block_number) + .unwrap_or(&default_hash); + let author = self - .logs - .get(¤t_block.hash) - .map_or(String::from("..."), |maybe_logs| { - self.get_author_from_digest(maybe_logs.to_vec()) - .map_or(String::from("..."), |maybe_author| maybe_author) + .authors + .get(&hash) + .map_or(String::from("..."), |author| { + let extended_author = AccountId32::decode(&mut author.as_ref()) + .expect("author should be valid AccountId32; qed"); + extended_author.to_ss58check_with_version(Ss58AddressFormat::custom(1996)) }); if free_space < Self::LENGTH_OF_BLOCK_HASH + Self::LENGTH_OF_ADDRESS { let len_for_author = free_space * Self::LENGTH_OF_ADDRESS / (Self::LENGTH_OF_BLOCK_HASH + Self::LENGTH_OF_ADDRESS); - let len_for_hash = (free_space - len_for_author) / 2; - - let hash_to_print = format!("{}...{}", - ¤t_block.hash[..len_for_hash as usize], - ¤t_block.hash[(Self::LENGTH_OF_BLOCK_HASH - len_for_hash) as usize..]); if &author == "..." { Line::raw(format!("{:^left$}| {} | {:^right$}", current_block.block_number, - hash_to_print, + hash.to_string(), author, left=block_number_length, right=(len_for_author + 2) as usize)) } else { Line::raw(format!("{} | {} | {}", current_block.block_number, - hash_to_print, + hash.to_string(), format!("{}...", &author[..(len_for_author) as usize]))) } } else { let total_space_used = block_number_length as u16 + Self::LENGTH_OF_BLOCK_HASH + Self::LENGTH_OF_ADDRESS; - let margin = (width - total_space_used) as usize / 3; - Line::raw(format!("{:^margin$}|{:^margin$}|{:^margin$}", - current_block.block_number, - current_block.hash, - author)) + let str_length = width.saturating_sub(2).saturating_sub(total_space_used) as usize / 3; + Line::raw(format!("{:^length_block_number$}|{:^length_hash$}|{:^length_author$}", + current_block.block_number, hash.to_string(), author, + length_block_number=str_length+block_number_length, + length_hash=str_length+(Self::LENGTH_OF_BLOCK_HASH as usize), + length_author=str_length+(Self::LENGTH_OF_ADDRESS as usize))) } } @@ -196,11 +168,11 @@ impl ExplorerBlocks { let latest_style = self.palette.create_text_style(false); let finalized_style = Style::new().fg(self.palette.foreground_hover()); - let start_index = match self.used_block_index { - Some((_, used_block)) if total_length < self.blocks.len() => { + let start_index = match self.used_block_number { + Some(used_block) if total_length < self.blocks.len() => { self.blocks .iter() - .position(|b| b.block_number == used_block) + .position(|info| info.block_number == used_block) .unwrap_or_default() .saturating_add(1) .saturating_sub(total_length) @@ -211,8 +183,8 @@ impl ExplorerBlocks { for (idx, current_block_info) in self.blocks.iter().skip(start_index).enumerate() { if idx == total_length { break; } - let style = match self.used_block_index { - Some((_, used_block)) if current_block_info.block_number == used_block => active_style, + let style = match self.used_block_number { + Some(used_block) if current_block_info.block_number == used_block => active_style, _ => { if current_block_info.finalized { finalized_style } else { latest_style } } @@ -224,13 +196,36 @@ impl ExplorerBlocks { items } - fn prepare_ext_line_info(&self, index: usize, extrinsic: String, width: u16) -> Line { - let index_len = index.checked_ilog10().unwrap_or(0) + 1; - let len_for_ext = width.saturating_sub(index_len as u16 + 17) as usize; - let len_extrinsic_hash = extrinsic.len(); - Line::from(format!("{} MODULE METHOD {}", - index, - format!("{}...{}", &extrinsic[..len_for_ext], &extrinsic[len_extrinsic_hash - len_for_ext..]))) + fn prepare_ext_line_info( + &self, + index: usize, + width: u16, + pallet_name: &str, + variant_name: &str, + hash: &str, + ) -> Line { + let index_length = 4; // always 4, two digits and two spaces + let hash_length = Self::LENGTH_OF_BLOCK_HASH as usize + 2; + let pallet_name_length = pallet_name.len(); + let variant_name_length = variant_name.len(); + + let offset_variant = (width as usize) + .saturating_sub(index_length) + .saturating_sub(pallet_name_length) + .saturating_sub(variant_name_length) + .saturating_sub(hash_length) + .saturating_sub(2) / 2; + + let offset_pallet = if offset_variant % 2 == 0 { + offset_variant + } else { + offset_variant + 1 + }; + + Line::from(format!("{:^index_length$}{:>pallet_name_offset$}::{: Vec { @@ -241,28 +236,31 @@ impl ExplorerBlocks { let normal_style = self.palette.create_text_style(false); let active_style = self.palette.create_text_style(true); - if let Some((_, used_block_number)) = self.used_block_index { - let hash = self.blocks - .iter() - .find(|b| b.block_number == used_block_number) - .map(|b| b.hash.clone()) - .unwrap_or_default(); + if let Some(used_block_number) = self.used_block_number { + let default_hash = H256::repeat_byte(69u8); + let hash = self.block_headers + .get(&used_block_number) + .unwrap_or(&default_hash); if let Some(exts) = self.extrinsics.get(&hash) { for (index, ext) in exts.iter().enumerate() { if total_length == 0 { break; } - let style = if let Some((_, _, used_ext_index)) = self.used_ext_index { + let style = if let Some((_, used_ext_index)) = self.used_ext_index { if index == used_ext_index { active_style } else { normal_style } } else { normal_style }; - items.push(self.prepare_ext_line_info(0, ext.to_string(), width).style(style)); + items.push(self.prepare_ext_line_info( + index, + width.saturating_sub(2), + &ext.pallet_name, + &ext.variant_name, + &ext.hash.to_string()).style(style)); total_length -= 1; } } - } items @@ -271,17 +269,20 @@ impl ExplorerBlocks { fn prepare_event_lines(&mut self, rect: Rect) -> Line { let _width = rect.as_size().width; - let style = self.palette.create_text_style(false); + match self.used_ext_index { + Some((header, used_index)) if self.extrinsics.get(&header).is_some() => { + let exts = self.extrinsics + .get(&header) + .expect("extrinsics should exists, checked before"); - if let Some((hash, _, ext_index)) = &self.used_ext_index { - if let Some(exts) = &self.extrinsics.get(&hash.clone()) { - Line::from(format!("{}", exts.get(*ext_index).unwrap_or(&String::from("nothing here")))).style(style) - } else { - Line::from("nothing here") - } - } else { - Line::from("nothing here") - } + let details = exts + .get(used_index) + .map_or(Vec::new(), |ext| ext.field_bytes.clone()); + + Line::from(format!("{}", hex::encode(&details))) + }, + _ => Line::from(""), + }.style(self.palette.create_text_style(false)) } fn move_right(&mut self) { @@ -316,94 +317,74 @@ impl ExplorerBlocks { fn move_up_extrinsics(&mut self) { match &self.used_ext_index { - Some((header, block_index, used_index)) => { - if *used_index == 0 { return } - let new_index = used_index - 1; - - let maybe_exts = self.extrinsics.get(&*header); - if maybe_exts.is_none() { return } - - let found = maybe_exts - .unwrap() - .get(new_index) - .is_some(); - - if found { - self.used_ext_index = - Some(((&*header).clone(), *block_index, new_index)); + Some((header, used_index)) => { + let new_index = used_index.saturating_sub(1); + if let Some(exts) = self.extrinsics.get(header) { + if exts.get(new_index).is_some() { + self.used_ext_index = Some((*header, new_index)); + } } - }, None => { - self.used_ext_index = self.blocks - .front() - .map(|block| { - self.extrinsics - .get(&block.hash) - .map(|_| (block.hash.clone(), 0, 0)) + self.used_ext_index = self.used_block_number + .map(|block_number| { + let header = self.block_headers + .get(&block_number) + .expect("header exists for each block number; qed"); + self.extrinsics.get(&header).map(|_| (*header, 0usize)) }) - .flatten() + .flatten() } } } fn move_up_blocks(&mut self) { - self.used_block_index = match &self.used_block_index { - Some((used_index, used_block)) => { + self.used_block_number = match &self.used_block_number { + Some(block_number) => { Some(self.blocks .iter() - .enumerate() - .find(|(_, b)| b.block_number == used_block + 1) - .map(|(idx, b)| (idx, b.block_number)) - .unwrap_or((*used_index, *used_block))) + .find(|info| info.block_number == block_number + 1) + .map(|info| info.block_number) + .unwrap_or(*block_number)) }, - None => self.blocks.front().map(|b| (0usize, b.block_number)), + None => self.blocks.front().map(|info| info.block_number), } } fn move_down_extrinsics(&mut self) { match &self.used_ext_index { - Some((header, block_index, used_index)) => { + Some((header, used_index)) => { let new_index = used_index + 1; - - let maybe_exts = self.extrinsics.get(&*header); - if maybe_exts.is_none() { return } - let exts = maybe_exts.unwrap(); - - let found = exts - .get(new_index) - .is_some(); - - if found && new_index < exts.len() { - self.used_ext_index = - Some(((&*header).clone(), *block_index, new_index)); + if let Some(exts) = self.extrinsics.get(&header) { + if new_index < exts.len() && exts.get(new_index).is_some() { + self.used_ext_index = Some((*header, new_index)); + } } }, None => { - self.used_ext_index = self.blocks - .front() - .map(|block| { - self.extrinsics - .get(&block.hash) - .map(|_| (block.hash.clone(), 0, 0)) + self.used_ext_index = self.used_block_number + .map(|block_number| { + let header = self.block_headers + .get(&block_number) + .expect("header exists for each block number; qed"); + self.extrinsics.get(&header).map(|_| (*header, 0usize)) }) - .flatten() + .flatten() } } } fn move_down_blocks(&mut self) { - self.used_block_index = match &self.used_block_index { - Some((used_index, used_block)) => { + self.used_block_number = match &self.used_block_number { + Some(block_number) => { Some(self.blocks .iter() - .enumerate() - .find(|(_, b)| b.block_number == used_block.saturating_sub(1)) - .map(|(idx, b)| (idx, b.block_number)) - .unwrap_or((*used_index, *used_block))) + .find(|info| info.block_number == block_number.saturating_sub(1)) + .map(|info| info.block_number) + .unwrap_or(*block_number)) }, None => { - self.blocks.front().map(|b| (0usize, b.block_number)) + self.blocks.front().map(|info| info.block_number) } } } @@ -478,7 +459,7 @@ impl Component for ExplorerBlocks { KeyCode::Char('l') | KeyCode::Right if self.is_active => self.move_right(), KeyCode::Char('h') | KeyCode::Left if self.is_active => self.move_left(), KeyCode::Esc => { - self.used_block_index = None; + self.used_block_number = None; self.used_ext_index = None; self.used_paragraph_index = 0; }, @@ -490,9 +471,9 @@ impl Component for ExplorerBlocks { fn update(&mut self, action: Action) -> Result> { match action { - Action::SetLatestBlock(hash, block) => self.update_latest_block_info(hash, block.header.number, block.header.digest.logs, block.extrinsics)?, - Action::SetFinalizedBlock(hash, block) => self.update_finalized_block_info(hash, block.header.number, block.header.digest.logs, block.extrinsics)?, - Action::SetValidators(validators) => self.update_validator_list(validators)?, + Action::BestBlockInformation(hash, block_number, extrinsics) => self.update_latest_block_info(hash, block_number, extrinsics)?, + Action::FinalizedBlockInformation(hash, block_number, extrinsics) => self.update_finalized_block_info(hash, block_number, extrinsics)?, + Action::SetBlockAuthor(hash, maybe_author) => self.update_block_author(hash, maybe_author)?, Action::SetMode(Mode::ExplorerActive) if !self.is_active => self.set_active()?, Action::SetMode(_) if self.is_active => self.unset_active()?, _ => {} diff --git a/src/components/explorer/extrinsics_chart.rs b/src/components/explorer/extrinsics_chart.rs index fa30518..272a336 100644 --- a/src/components/explorer/extrinsics_chart.rs +++ b/src/components/explorer/extrinsics_chart.rs @@ -44,21 +44,18 @@ impl ExtrinsicsChart { .text_value(ext_len.to_string()) } - fn update_extrinsics(&mut self, block_number: String, extrinsics_number: usize) -> Result<()> { - let block_number = block_number.trim_start_matches("0x"); - let block_number = u32::from_str_radix(&block_number, 16)?; - + fn update_extrinsics_length(&mut self, block_number: u32, extrinsics_length: usize) -> Result<()> { match self.extrinsics.back() { Some(back) => { if back.0 < block_number { - self.extrinsics.push_back((block_number, extrinsics_number)); + self.extrinsics.push_back((block_number, extrinsics_length)); if self.extrinsics.len() > Self::MAX_LEN { let _ = self.extrinsics.pop_front(); } } }, None => { - self.extrinsics.push_back((block_number, extrinsics_number)); + self.extrinsics.push_back((block_number, extrinsics_length)); } }; @@ -69,7 +66,7 @@ impl ExtrinsicsChart { impl Component for ExtrinsicsChart { fn update(&mut self, action: Action) -> Result> { match action { - Action::SetLatestBlock(_, block) => self.update_extrinsics(block.header.number, block.extrinsics.len())?, + Action::ExtrinsicsLength(block, length) => self.update_extrinsics_length(block, length)?, _ => {} }; Ok(None) diff --git a/src/components/explorer/finalized_block.rs b/src/components/explorer/finalized_block.rs index 127f0c5..c0bce92 100644 --- a/src/components/explorer/finalized_block.rs +++ b/src/components/explorer/finalized_block.rs @@ -15,9 +15,8 @@ pub struct FinalizedBlock { } impl FinalizedBlock { - fn update_block_number(&mut self, number_hex_str: String) -> Result<()> { - let number_hex_str = number_hex_str.trim_start_matches("0x"); - self.number = u32::from_str_radix(&number_hex_str, 16)?; + fn update_block_number(&mut self, number: u32) -> Result<()> { + self.number = number; Ok(()) } } @@ -25,7 +24,7 @@ impl FinalizedBlock { impl Component for FinalizedBlock { fn update(&mut self, action: Action) -> Result> { match action { - Action::SetFinalizedBlock(_, block) => self.update_block_number(block.header.number)?, + Action::NewFinalizedBlock(number) => self.update_block_number(number)?, _ => {} }; Ok(None) @@ -48,7 +47,7 @@ impl Component for FinalizedBlock { .border_type(border_type) .title_alignment(Alignment::Right) .title_style(self.palette.create_title_style()) - .padding(Padding::new(0, 0, (height - 2) / 2, 0)) + .padding(Padding::new(0, 0, height.saturating_sub(2) / 2, 0)) .title("Latest")) .alignment(Alignment::Center) .wrap(Wrap { trim: true }); @@ -73,7 +72,7 @@ impl Component for FinalizedBlock { .wrap(Wrap { trim: true }); frame.render_widget(paragraph, place); - let height_offset = (height - 2) / 2; + let height_offset = height.saturating_sub(2) / 2; let place = place.offset(ratatui::layout::Offset { x: 1, y: height_offset as i32 }) .intersection(place); frame.render_widget(big_text, place); diff --git a/src/components/explorer/latest_block.rs b/src/components/explorer/latest_block.rs index db8c351..e9e4cbe 100644 --- a/src/components/explorer/latest_block.rs +++ b/src/components/explorer/latest_block.rs @@ -15,9 +15,8 @@ pub struct LatestBlock { } impl LatestBlock { - fn update_block_number(&mut self, number_hex_str: String) -> Result<()> { - let number_hex_str = number_hex_str.trim_start_matches("0x"); - self.number = u32::from_str_radix(&number_hex_str, 16)?; + fn update_block_number(&mut self, number: u32) -> Result<()> { + self.number = number; Ok(()) } } @@ -25,7 +24,7 @@ impl LatestBlock { impl Component for LatestBlock { fn update(&mut self, action: Action) -> Result> { match action { - Action::SetLatestBlock(_, block) => self.update_block_number(block.header.number)?, + Action::NewBestBlock(number) => self.update_block_number(number)?, _ => {} }; Ok(None) @@ -48,7 +47,7 @@ impl Component for LatestBlock { .border_type(border_type) .title_alignment(Alignment::Right) .title_style(self.palette.create_title_style()) - .padding(Padding::new(0, 0, (height - 2) / 2, 0)) + .padding(Padding::new(0, 0, height.saturating_sub(2) / 2, 0)) .title("Latest")) .alignment(Alignment::Center) .wrap(Wrap { trim: true }); @@ -73,7 +72,7 @@ impl Component for LatestBlock { .wrap(Wrap { trim: true }); frame.render_widget(paragraph, place); - let height_offset = (height - 2) / 2; + let height_offset = height.saturating_sub(2) / 2; let place = place.offset(ratatui::layout::Offset { x: 1, y: height_offset as i32 }) .intersection(place); frame.render_widget(big_text, place); diff --git a/src/components/health.rs b/src/components/health.rs index 1b2246d..e003a3a 100644 --- a/src/components/health.rs +++ b/src/components/health.rs @@ -16,7 +16,7 @@ use crate::{ #[derive(Debug, Clone, PartialEq)] pub struct Health { name: Option, - peers: Option, + peers: Option, is_syncing: bool, should_have_peers: bool, tx_pool_length: usize, @@ -39,7 +39,12 @@ impl Health { } } - fn set_sync_state(&mut self, peers: Option, is_syncing: bool, should_have_peers: bool) -> Result<()> { + fn set_sync_state( + &mut self, + peers: Option, + is_syncing: bool, + should_have_peers: bool, + ) -> Result<()> { self.peers = peers; self.is_syncing = is_syncing; self.should_have_peers = should_have_peers; @@ -84,7 +89,7 @@ impl Health { impl Component for Health { fn update(&mut self, action: Action) -> Result> { match action { - Action::SetSyncState(peers, is_syncing, should_have_peers) => { + Action::SetSystemHealth(peers, is_syncing, should_have_peers) => { self.set_sync_state(peers, is_syncing, should_have_peers)? }, Action::SetNodeName(name) => self.set_node_name(name)?, diff --git a/src/components/version.rs b/src/components/version.rs index a754e2c..8d6eb14 100644 --- a/src/components/version.rs +++ b/src/components/version.rs @@ -5,6 +5,7 @@ use ratatui::{ widgets::{Block, Paragraph, Wrap}, Frame, }; +use primitive_types::H256; use super::Component; use crate::{ @@ -13,7 +14,7 @@ use crate::{ #[derive(Debug, Clone, Default)] pub struct Version { - genesis_hash: Option, + genesis_hash: Option, node_version: Option, chain_name: Option, palette: StylePalette, @@ -30,18 +31,15 @@ impl Version { Ok(()) } - fn set_genesis_hash(&mut self, genesis_hash: Option) -> Result<()> { + fn set_genesis_hash(&mut self, genesis_hash: Option) -> Result<()> { self.genesis_hash = genesis_hash; Ok(()) } fn prepared_genesis_hash(&self) -> String { - if self.genesis_hash.is_some() { - let genesis_hash = self.genesis_hash.clone().unwrap(); - let len = genesis_hash.len(); - format!("Genesis: {}...{}", &genesis_hash[0..4], &genesis_hash[len-6..]) - } else { - OghamCenter::default().to_string() + match self.genesis_hash { + Some(genesis_hash) => genesis_hash.to_string(), + None => OghamCenter::default().to_string(), } } } @@ -49,9 +47,9 @@ impl Version { impl Component for Version { fn update(&mut self, action: Action) -> Result> { match action { - Action::SetChainName(name) => self.set_chain_name(name)?, - Action::SetNodeVersion(version) => self.set_node_version(version)?, - Action::SetGenesisHash(genesis) => self.set_genesis_hash(genesis)?, + Action::SetChainName(maybe_name) => self.set_chain_name(maybe_name)?, + Action::SetChainVersion(version) => self.set_node_version(version)?, + Action::SetGenesisHash(maybe_genesis) => self.set_genesis_hash(maybe_genesis)?, _ => {} }; Ok(None) diff --git a/src/main.rs b/src/main.rs index 2dec150..b0bc8da 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,9 @@ use clap::Parser; use color_eyre::Result; +use subxt::{ + OnlineClient, + backend::{legacy::LegacyRpcMethods, rpc::RpcClient}, +}; mod action; mod app; @@ -13,9 +17,15 @@ mod network; mod widgets; mod types; mod palette; +mod casper; + +use casper::{CasperAccountId, CasperConfig}; + +#[subxt::subxt(runtime_metadata_path = "./artifacts/casper.scale")] +pub mod casper_network {} #[tokio::main] -async fn start_tokio( +async fn start_tokio_action_loop( io_rx: std::sync::mpsc::Receiver, network: &mut network::Network, ) { @@ -24,6 +34,16 @@ async fn start_tokio( } } +#[tokio::main] +async fn start_tokio_finalized_subscription(sub: &mut network::FinalizedSubscription) { + let _ = sub.subscribe_finalized_blocks().await; +} + +#[tokio::main] +async fn start_tokio_best_subscription(sub: &mut network::BestSubscription) { + let _ = sub.subscribe_best_blocks().await; +} + #[tokio::main] async fn main() -> Result<()> { crate::errors::init()?; @@ -33,12 +53,45 @@ async fn main() -> Result<()> { let (sync_io_tx, sync_io_rx) = std::sync::mpsc::channel(); let (action_tx, action_rx) = tokio::sync::mpsc::unbounded_channel(); + let rpc_client = RpcClient::from_url(args.rpc_endpoint).await?; + let legacy_client_api = LegacyRpcMethods::::new(rpc_client.clone()); + let online_client = + OnlineClient::::from_rpc_client(rpc_client.clone()).await?; + + let finalized_blocks_sub = online_client.blocks().subscribe_finalized().await?; + let best_blocks_sub = online_client.blocks().subscribe_best().await?; + let cloned_action_tx = action_tx.clone(); std::thread::spawn(move || { - let mut network = network::Network::new(cloned_action_tx) - .with_url(&args.rpc_endpoint) - .with_timeout(args.timeout); - start_tokio(sync_io_rx, &mut network); + let mut network = network::Network::new( + cloned_action_tx, + online_client, + legacy_client_api, + rpc_client, + ); + start_tokio_action_loop(sync_io_rx, &mut network); + }); + + let cloned_action_tx = action_tx.clone(); + let cloned_sync_tx = sync_io_tx.clone(); + std::thread::spawn(move || { + let mut subscription = network::FinalizedSubscription::new( + cloned_action_tx, + cloned_sync_tx, + finalized_blocks_sub, + ); + start_tokio_finalized_subscription(&mut subscription); + }); + + let cloned_action_tx = action_tx.clone(); + let cloned_sync_tx = sync_io_tx.clone(); + std::thread::spawn(move || { + let mut subscription = network::BestSubscription::new( + cloned_action_tx, + cloned_sync_tx, + best_blocks_sub, + ); + start_tokio_best_subscription(&mut subscription); }); app::App::new(sync_io_tx, action_tx, action_rx)? diff --git a/src/network/active_era.rs b/src/network/active_era.rs deleted file mode 100644 index 8c730c4..0000000 --- a/src/network/active_era.rs +++ /dev/null @@ -1,33 +0,0 @@ -use color_eyre::Result; -use codec::Decode; - -use crate::network::GhostRequest; -use crate::action::Action; -use crate::rpc_params; -use crate::types::{era::EraInfo, storage::GhostStorage}; - -#[derive(Debug)] -pub struct ActiveEraRequest<'a>(pub GhostRequest<'a>); -impl<'a> ActiveEraRequest<'a> { - pub async fn send(self) -> Result<()> { - let storage_key = GhostStorage::new() - .with_module("Staking") - .with_method("ActiveEra") - .build_storage_key(); - - let result_hex = self - .0 - .send::("state_getStorage", rpc_params![storage_key]) - .await - .map(|r| { - hex::decode(r.result.trim_start_matches("0x")).or::>(Ok(vec![])) - }) - .unwrap() - .unwrap(); - - let active_era = EraInfo::decode(&mut &result_hex[..])?; - self.0.action_tx.send(Action::SetActiveEra(active_era))?; - - Ok(()) - } -} diff --git a/src/network/chain_name.rs b/src/network/chain_name.rs deleted file mode 100644 index 0148f41..0000000 --- a/src/network/chain_name.rs +++ /dev/null @@ -1,20 +0,0 @@ -use color_eyre::Result; - -use crate::network::GhostRequest; -use crate::action::Action; -use crate::types::params::RpcParams; - -#[derive(Debug)] -pub struct ChainNameRequest<'a>(pub GhostRequest<'a>); -impl<'a> ChainNameRequest<'a> { - pub async fn send(self) -> Result<()> { - let chain_name = self - .0 - .send::("system_chain", RpcParams::new()) - .await - .ok() - .map(|response| response.result); - self.0.action_tx.send(Action::SetChainName(chain_name))?; - Ok(()) - } -} diff --git a/src/network/current_epoch.rs b/src/network/current_epoch.rs deleted file mode 100644 index e719299..0000000 --- a/src/network/current_epoch.rs +++ /dev/null @@ -1,41 +0,0 @@ -use color_eyre::Result; -use codec::Decode; - -use crate::network::GhostRequest; -use crate::action::Action; -use crate::rpc_params; -use crate::types::storage::GhostStorage; - -#[derive(Debug)] -pub struct CurrentEpochRequest<'a>(pub GhostRequest<'a>); -impl<'a> CurrentEpochRequest<'a> { - async fn do_request(&self, module: &str, method: &str) -> Result { - let storage_key = GhostStorage::new() - .with_module(module) - .with_method(method) - .build_storage_key(); - - let result_hex = self - .0 - .send::("state_getStorage", rpc_params![storage_key]) - .await - .map(|r| { - hex::decode(r.result.trim_start_matches("0x")).or::>(Ok(vec![])) - }) - .unwrap() - .unwrap(); - let value = u64::decode(&mut &result_hex[..])?; - Ok(value) - } - - pub async fn send(self) -> Result<()> { - let current_slot = self.do_request("Babe", "CurrentSlot").await?; - let epoch_index = self.do_request("Babe", "EpochIndex").await?; - let genesis_slot = self.do_request("Babe", "GenesisSlot").await?; - - let epoch_start_slot = epoch_index * 2_400 + genesis_slot; - let progress = current_slot.saturating_sub(epoch_start_slot); - self.0.action_tx.send(Action::SetEpoch(epoch_index, progress))?; - Ok(()) - } -} diff --git a/src/network/finalized_block.rs b/src/network/finalized_block.rs deleted file mode 100644 index c0d7e37..0000000 --- a/src/network/finalized_block.rs +++ /dev/null @@ -1,35 +0,0 @@ -use color_eyre::Result; -use serde::Deserialize; - -use crate::network::GhostRequest; -use crate::action::Action; -use crate::rpc_params; -use crate::types::block::BlockInfo; -use crate::types::params::RpcParams; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct LatestBlockResponse { - pub block: BlockInfo, - // justifications: Option -} - -#[derive(Debug)] -pub struct FinalizedBlockRequest<'a>(pub GhostRequest<'a>); -impl<'a> FinalizedBlockRequest<'a> { - pub async fn send(self) -> Result<()> { - let finalized_head = self - .0 - .send::("chain_getFinalizedHead", RpcParams::new()) - .await - .map_or(String::new(), |response| response.result); - let finalized_block = self - .0 - .send::("chain_getBlock", rpc_params![finalized_head.clone()]) - .await - .map_or(BlockInfo::default(), |response| response.result.block); - self.0.action_tx.send(Action::SetFinalizedBlock(finalized_head, finalized_block))?; - - Ok(()) - } -} diff --git a/src/network/genesis_hash.rs b/src/network/genesis_hash.rs deleted file mode 100644 index 5cea705..0000000 --- a/src/network/genesis_hash.rs +++ /dev/null @@ -1,18 +0,0 @@ -use color_eyre::Result; - -use crate::network::GhostRequest; -use crate::action::Action; -use crate::rpc_params; - -#[derive(Debug)] -pub struct GenesisHashRequest<'a>(pub GhostRequest<'a>); -impl<'a> GenesisHashRequest<'a> { - pub async fn send(self) -> Result<()> { - let genesis_hash = self.0.send::("chain_getBlockHash", rpc_params!["0"]) - .await - .ok() - .map(|response| response.result); - self.0.action_tx.send(Action::SetGenesisHash(genesis_hash))?; - Ok(()) - } -} diff --git a/src/network/health_check.rs b/src/network/health_check.rs deleted file mode 100644 index 4822ef8..0000000 --- a/src/network/health_check.rs +++ /dev/null @@ -1,37 +0,0 @@ -use color_eyre::Result; -use serde::Deserialize; - -use crate::network::GhostRequest; -use crate::action::Action; -use crate::types::params::RpcParams; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct HealthCheckResponse { - pub peers: u32, - pub is_syncing: bool, - pub should_have_peers: bool -} - -#[derive(Debug)] -pub struct HealthCheckRequest<'a>(pub GhostRequest<'a>); -impl<'a> HealthCheckRequest<'a> { - pub async fn send(self) -> Result<()> { - let (peers, is_syncing, should_have_peers) = self - .0 - .send::("system_health", RpcParams::new()) - .await - .ok() - .map_or((None, false, false), |response| ( - Some(response.result.peers), - response.result.is_syncing, - response.result.should_have_peers, - )); - - self.0.action_tx.send(Action::SetSyncState( - peers, - is_syncing, - should_have_peers))?; - Ok(()) - } -} diff --git a/src/network/latest_block.rs b/src/network/latest_block.rs deleted file mode 100644 index e9f777a..0000000 --- a/src/network/latest_block.rs +++ /dev/null @@ -1,35 +0,0 @@ -use color_eyre::Result; -use serde::Deserialize; - -use crate::network::GhostRequest; -use crate::action::Action; -use crate::rpc_params; -use crate::types::block::BlockInfo; -use crate::types::params::RpcParams; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct LatestBlockResponse { - pub block: BlockInfo, - // justifications: Option -} - -#[derive(Debug)] -pub struct LatestBlockRequest<'a>(pub GhostRequest<'a>); -impl<'a> LatestBlockRequest<'a> { - pub async fn send(self) -> Result<()> { - let latest_head = self - .0 - .send::("chain_getBlockHash", RpcParams::new()) - .await - .map_or(String::new(), |response| response.result); - let latest_block = self - .0 - .send::("chain_getBlock", rpc_params![latest_head.clone()]) - .await - .map_or(BlockInfo::default(), |response| response.result.block); - self.0.action_tx.send(Action::SetLatestBlock(latest_head, latest_block))?; - - Ok(()) - } -} diff --git a/src/network/legacy_rpc_calls.rs b/src/network/legacy_rpc_calls.rs new file mode 100644 index 0000000..dec77ca --- /dev/null +++ b/src/network/legacy_rpc_calls.rs @@ -0,0 +1,70 @@ +use tokio::sync::mpsc::UnboundedSender; +use color_eyre::Result; +use subxt::backend::legacy::rpc_methods::LegacyRpcMethods; + +use crate::{action::Action, casper::CasperConfig}; + +pub async fn get_node_name( + action_tx: &UnboundedSender, + api: &LegacyRpcMethods, +) -> Result<()> { + let maybe_node_name = api.system_name().await.ok(); + action_tx.send(Action::SetNodeName(maybe_node_name))?; + Ok(()) +} + +pub async fn get_system_health( + action_tx: &UnboundedSender, + api: &LegacyRpcMethods, +) -> Result<()> { + let (maybe_peers, is_syncing, should_have_peers) = api + .system_health() + .await + .ok() + .map_or((None, false, false), |health| ( + Some(health.peers), + health.is_syncing, + health.should_have_peers, + )); + action_tx.send(Action::SetSystemHealth( + maybe_peers, + is_syncing, + should_have_peers))?; + Ok(()) +} + +pub async fn get_genesis_hash( + action_tx: &UnboundedSender, + api: &LegacyRpcMethods, +) -> Result<()> { + let maybe_genesis_hash = api + .genesis_hash() + .await + .ok(); + action_tx.send(Action::SetGenesisHash(maybe_genesis_hash))?; + Ok(()) +} + +pub async fn get_chain_name( + action_tx: &UnboundedSender, + api: &LegacyRpcMethods, +) -> Result<()> { + let maybe_chain_name = api + .system_chain() + .await + .ok(); + action_tx.send(Action::SetChainName(maybe_chain_name))?; + Ok(()) +} + +pub async fn get_system_version( + action_tx: &UnboundedSender, + api: &LegacyRpcMethods, +) -> Result<()> { + let maybe_system_version = api + .system_version() + .await + .ok(); + action_tx.send(Action::SetChainVersion(maybe_system_version))?; + Ok(()) +} diff --git a/src/network/mod.rs b/src/network/mod.rs index 325f839..1c28d07 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -1,164 +1,70 @@ -use std::sync::Arc; use tokio::sync::mpsc::UnboundedSender; -use once_cell::sync::Lazy; -use reqwest::Client; use color_eyre::Result; -use rand::RngCore; -use serde::{Deserialize, de::DeserializeOwned}; +use subxt::{ + backend::{ + legacy::LegacyRpcMethods, + rpc::RpcClient, + }, + utils::H256, + OnlineClient +}; -use crate::{action::Action, types::params::RpcParams}; +mod legacy_rpc_calls; +mod predefinded_calls; +mod subscriptions; -mod active_era; -mod health_check; -mod node_name; -mod genesis_hash; -mod chain_name; -mod node_version; -mod latest_block; -mod finalized_block; -mod current_epoch; -mod validators; -mod tx_pool; +use crate::{ + action::Action, + casper::CasperConfig, +}; -pub use active_era::ActiveEraRequest; -pub use health_check::HealthCheckRequest; -pub use node_name::NodeNameRequest; -pub use genesis_hash::GenesisHashRequest; -pub use chain_name::ChainNameRequest; -pub use node_version::NodeVersionRequest; -pub use latest_block::LatestBlockRequest; -pub use finalized_block::FinalizedBlockRequest; -pub use current_epoch::CurrentEpochRequest; -pub use validators::ValidatorsRequest; -pub use tx_pool::TxPoolRequest; +pub use subscriptions::{FinalizedSubscription, BestSubscription}; -static CLIENT: Lazy> = Lazy::new(|| Arc::new(Client::new())); -const DEFAULT_URL: &str = "http://localhost:9945"; - -pub type AppActionSender = UnboundedSender; - -#[derive(Debug, Deserialize)] -pub struct GhostResponse { - result: ResponseType, +pub struct Network { + action_tx: UnboundedSender, + online_client_api: OnlineClient, + legacy_client_api: LegacyRpcMethods, + rpc_client: RpcClient, + best_hash: Option, + finalized_hash: Option, } -#[derive(Default)] -struct GhostRequestBuilder<'a> { - action_tx: Option, - id: u32, - url: &'a str, - timeout: std::time::Duration, -} - -impl<'a> GhostRequestBuilder<'a> { - pub fn with_id(mut self, id: u32) -> Self { - self.id = id; - self - } - - pub fn with_url(mut self, url: &'a str) -> Self { - self.url = url; - self - } - - pub fn with_timeout(mut self, timeout: std::time::Duration) -> Self { - self.timeout = timeout; - self - } - - pub fn with_action_tx(mut self, action_tx: AppActionSender) -> Self { - self.action_tx = Some(action_tx); - self - } - - pub fn build(self) -> GhostRequest<'a> { - GhostRequest { - action_tx: self.action_tx.expect("channel sender should exist"), - id: self.id, - url: self.url, - timeout: self.timeout, - } - } -} - -#[derive(Debug)] -pub struct GhostRequest<'a> { - action_tx: AppActionSender, - id: u32, - url: &'a str, - timeout: std::time::Duration, -} - -impl<'a> GhostRequest<'a> { - pub async fn send( - &self, - method: &str, - params: RpcParams, - ) -> Result> - where - ResponseType: DeserializeOwned, - { - Ok(CLIENT - .post(self.url) - .header(reqwest::header::CONTENT_TYPE, "application/json") - .body(format!("{{\"id\":{},\"jsonrpc\":\"2.0\",\"method\":{:?},\"params\":{}}}", - self.id, method, params.build())) - .timeout(self.timeout) - .send() - .await? - .json::>() - .await? - ) - } -} - -pub struct Network<'a> { - action_tx: AppActionSender, - timeout: std::time::Duration, - internal_randomness: rand::rngs::ThreadRng, - url: &'a str, -} - -impl<'a> Network<'a> { - pub fn new(action_tx: AppActionSender) -> Self { +impl Network { + pub fn new( + action_tx: UnboundedSender, + online_client_api: OnlineClient, + legacy_client_api: LegacyRpcMethods, + rpc_client: RpcClient, + ) -> Self { Self { action_tx, - timeout: Default::default(), - internal_randomness: rand::thread_rng(), - url: DEFAULT_URL, + online_client_api, + legacy_client_api, + rpc_client, + best_hash: None, + finalized_hash: None, } } - pub fn with_url(mut self, url: &'a str) -> Self { - self.url = url; - self - } - - pub fn with_timeout(mut self, timeout: u64) -> Self { - self.timeout = std::time::Duration::from_secs(timeout); - self - } - pub async fn handle_network_event(&mut self, io_event: Action) -> Result<()> { - let request = GhostRequestBuilder::default() - .with_action_tx(self.action_tx.clone()) - .with_id(self.internal_randomness.next_u32()) - .with_url(self.url) - .with_timeout(self.timeout) - .build(); - match io_event { - Action::GetSyncState => HealthCheckRequest(request).send().await, - Action::GetNodeName => NodeNameRequest(request).send().await, - Action::GetGenesisHash => GenesisHashRequest(request).send().await, - Action::GetChainName => ChainNameRequest(request).send().await, - Action::GetNodeVersion => NodeVersionRequest(request).send().await, - Action::GetLatestBlock => LatestBlockRequest(request).send().await, - Action::GetFinalizedBlock => FinalizedBlockRequest(request).send().await, - Action::GetActiveEra => ActiveEraRequest(request).send().await, - Action::GetEpoch => CurrentEpochRequest(request).send().await, - Action::GetValidators => ValidatorsRequest(request).send().await, - Action::GetPendingExtrinsics => TxPoolRequest(request).send().await, + Action::NewBestHash(hash) => { + self.best_hash = Some(hash); + Ok(()) + }, + Action::NewFinalizedHash(hash) => { + self.finalized_hash = Some(hash); + Ok(()) + }, + Action::GetSystemHealth => legacy_rpc_calls::get_system_health(&self.action_tx, &self.legacy_client_api).await, + Action::GetNodeName => legacy_rpc_calls::get_node_name(&self.action_tx, &self.legacy_client_api).await, + Action::GetGenesisHash => legacy_rpc_calls::get_genesis_hash(&self.action_tx, &self.legacy_client_api).await, + Action::GetChainName => legacy_rpc_calls::get_chain_name(&self.action_tx, &self.legacy_client_api).await, + Action::GetChainVersion => legacy_rpc_calls::get_system_version(&self.action_tx, &self.legacy_client_api).await, + Action::GetBlockAuthor(hash, logs) => predefinded_calls::get_block_author(&self.action_tx, &self.online_client_api, &logs, &hash).await, + Action::GetActiveEra => predefinded_calls::get_active_era(&self.action_tx, &self.online_client_api).await, + Action::GetEpochProgress => predefinded_calls::get_epoch_progress(&self.action_tx, &self.online_client_api).await, + Action::GetPendingExtrinsics => predefinded_calls::get_pending_extrinsics(&self.action_tx, &self.rpc_client).await, _ => Ok(()) } } diff --git a/src/network/node_name.rs b/src/network/node_name.rs deleted file mode 100644 index b0ff924..0000000 --- a/src/network/node_name.rs +++ /dev/null @@ -1,20 +0,0 @@ -use color_eyre::Result; - -use crate::network::GhostRequest; -use crate::action::Action; -use crate::types::params::RpcParams; - -#[derive(Debug)] -pub struct NodeNameRequest<'a>(pub GhostRequest<'a>); -impl<'a> NodeNameRequest<'a> { - pub async fn send(self) -> Result<()> { - let name = self - .0 - .send::("system_name", RpcParams::new()) - .await - .ok() - .map(|response| response.result); - self.0.action_tx.send(Action::SetNodeName(name))?; - Ok(()) - } -} diff --git a/src/network/node_version.rs b/src/network/node_version.rs deleted file mode 100644 index e983e51..0000000 --- a/src/network/node_version.rs +++ /dev/null @@ -1,20 +0,0 @@ -use color_eyre::Result; - -use crate::network::GhostRequest; -use crate::action::Action; -use crate::types::params::RpcParams; - -#[derive(Debug)] -pub struct NodeVersionRequest<'a>(pub GhostRequest<'a>); -impl<'a> NodeVersionRequest<'a> { - pub async fn send(self) -> Result<()> { - let version = self - .0 - .send::("system_version", RpcParams::new()) - .await - .ok() - .map(|response| response.result); - self.0.action_tx.send(Action::SetNodeVersion(version))?; - Ok(()) - } -} diff --git a/src/network/predefinded_calls.rs b/src/network/predefinded_calls.rs new file mode 100644 index 0000000..6db040e --- /dev/null +++ b/src/network/predefinded_calls.rs @@ -0,0 +1,110 @@ +use primitive_types::H256; +use tokio::sync::mpsc::UnboundedSender; +use color_eyre::Result; +use subxt::{ + backend::rpc::RpcClient, + client::OnlineClient, + config::substrate::DigestItem, + rpc_params, +}; + +use crate::{ + action::Action, + types::EraInfo, + casper_network::{self, runtime_types::sp_consensus_slots}, + CasperConfig, +}; + +pub async fn get_block_author( + action_tx: &UnboundedSender, + api: &OnlineClient, + logs: &Vec, + 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, + }; + + action_tx.send(Action::SetBlockAuthor(*at_hash, maybe_author.cloned()))?; + + Ok(()) +} + +pub async fn get_active_era( + action_tx: &UnboundedSender, + api: &OnlineClient, +) -> 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, + api: &OnlineClient, +) -> 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, + rpc_client: &RpcClient, +) -> Result<()> { + let pending_extrinsics: Vec = rpc_client + .request("author_pendingExtrinsics", rpc_params![]) + .await?; + action_tx.send(Action::SetPendingExtrinsicsLength(pending_extrinsics.len()))?; + Ok(()) +} + diff --git a/src/network/runtime_version.rs b/src/network/runtime_version.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/network/subscriptions.rs b/src/network/subscriptions.rs new file mode 100644 index 0000000..3fbba19 --- /dev/null +++ b/src/network/subscriptions.rs @@ -0,0 +1,119 @@ +use crate::{types::CasperExtrinsicDetails, action::Action, casper::CasperBlock}; +use color_eyre::Result; + +pub struct FinalizedSubscription { + action_tx: tokio::sync::mpsc::UnboundedSender, + network_tx: std::sync::mpsc::Sender, + finalized_blocks_sub: subxt::backend::StreamOfResults, +} + +impl FinalizedSubscription { + pub fn new( + action_tx: tokio::sync::mpsc::UnboundedSender, + network_tx: std::sync::mpsc::Sender, + finalized_blocks_sub: subxt::backend::StreamOfResults, + ) -> Self { + Self { action_tx, network_tx, finalized_blocks_sub } + } + + pub async fn subscribe_finalized_blocks(&mut self) -> Result<()> { + while let Some(block) = self.finalized_blocks_sub.next().await { + let block = block?; + let block_number = block.header().number; + let block_hash = block.hash(); + let extrinsics = block.extrinsics().await?; + + let mut extrinsic_details = Vec::new(); + for ext in extrinsics.iter() { + let pallet_name = match ext.pallet_name() { + Ok(pallet_name) => pallet_name, + Err(_) => continue, + }; + + let variant_name = match ext.variant_name() { + Ok(variant_name) => variant_name, + Err(_) => continue, + }; + + extrinsic_details.push(CasperExtrinsicDetails::new( + ext.index(), + ext.hash(), + ext.is_signed(), + ext.field_bytes().to_vec(), + ext.address_bytes().map(|addr| addr.to_vec()), + pallet_name.to_string(), + variant_name.to_string(), + )); + } + + self.action_tx.send(Action::FinalizedBlockInformation( + block_hash, block_number, extrinsic_details))?; + self.action_tx.send(Action::NewFinalizedHash(block_hash))?; + self.action_tx.send(Action::NewFinalizedBlock(block_number))?; + + self.network_tx.send(Action::GetBlockAuthor(block_hash, block.header().digest.logs.clone()))?; + } + Ok(()) + } +} + +pub struct BestSubscription { + action_tx: tokio::sync::mpsc::UnboundedSender, + network_tx: std::sync::mpsc::Sender, + best_blocks_sub: subxt::backend::StreamOfResults, +} + +impl BestSubscription { + pub fn new( + action_tx: tokio::sync::mpsc::UnboundedSender, + network_tx: std::sync::mpsc::Sender, + best_blocks_sub: subxt::backend::StreamOfResults, + ) -> Self { + Self { action_tx, network_tx, best_blocks_sub } + } + + pub async fn subscribe_best_blocks(&mut self) -> Result<()> { + while let Some(block) = self.best_blocks_sub.next().await { + let block = block?; + let block_number = block.header().number; + let block_hash = block.hash(); + let extrinsics = block.extrinsics().await?; + let extrinsics_length = extrinsics.len(); + + let mut extrinsic_details = Vec::new(); + for ext in extrinsics.iter() { + let pallet_name = match ext.pallet_name() { + Ok(pallet_name) => pallet_name, + Err(_) => continue, + }; + + let variant_name = match ext.variant_name() { + Ok(variant_name) => variant_name, + Err(_) => continue, + }; + + extrinsic_details.push(CasperExtrinsicDetails::new( + ext.index(), + ext.hash(), + ext.is_signed(), + ext.field_bytes().to_vec(), + ext.address_bytes().map(|addr| addr.to_vec()), + pallet_name.to_string(), + variant_name.to_string(), + )); + } + + self.action_tx.send(Action::BestBlockInformation( + block_hash, block_number, extrinsic_details))?; + self.action_tx.send(Action::NewBestHash(block_hash))?; + self.action_tx.send(Action::BestBlockUpdated(block_number))?; + self.action_tx.send(Action::NewBestBlock(block_number))?; + self.action_tx.send(Action::ExtrinsicsLength(block_number, extrinsics_length))?; + + self.network_tx.send(Action::GetBlockAuthor(block_hash, block.header().digest.logs.clone()))?; + self.network_tx.send(Action::GetActiveEra)?; + self.network_tx.send(Action::GetEpochProgress)?; + } + Ok(()) + } +} diff --git a/src/network/tx_pool.rs b/src/network/tx_pool.rs deleted file mode 100644 index ec6f749..0000000 --- a/src/network/tx_pool.rs +++ /dev/null @@ -1,21 +0,0 @@ -use color_eyre::Result; - -use crate::network::GhostRequest; -use crate::action::Action; -use crate::types::params::RpcParams; - -#[derive(Debug)] -pub struct TxPoolRequest<'a>(pub GhostRequest<'a>); -impl<'a> TxPoolRequest<'a> { - pub async fn send(self) -> Result<()> { - let tx_pool = self - .0 - .send::>("author_pendingExtrinsics", RpcParams::new()) - .await - .ok() - .map(|response| response.result) - .unwrap_or_default(); - self.0.action_tx.send(Action::SetPendingExtrinsicsLength(tx_pool.len()))?; - Ok(()) - } -} diff --git a/src/network/validators.rs b/src/network/validators.rs deleted file mode 100644 index 0328ff9..0000000 --- a/src/network/validators.rs +++ /dev/null @@ -1,37 +0,0 @@ -use color_eyre::Result; -use codec::Decode; -use sp_core::crypto::{AccountId32, Ss58AddressFormat, Ss58Codec}; - -use crate::network::GhostRequest; -use crate::action::Action; -use crate::rpc_params; -use crate::types::storage::GhostStorage; - -#[derive(Debug)] -pub struct ValidatorsRequest<'a>(pub GhostRequest<'a>); -impl<'a> ValidatorsRequest<'a> { - pub async fn send(self) -> Result<()> { - let storage_key = GhostStorage::new() - .with_module("Session") - .with_method("Validators") - .build_storage_key(); - - let result_hex = self.0.send::("state_getStorage", rpc_params![storage_key]) - .await - .map(|response| { - hex::decode(response.result.trim_start_matches("0x")) - .ok() - .unwrap_or_default() - }) - .unwrap(); - let validators = >::decode(&mut &result_hex[..]) - .ok() - .unwrap_or_default() - .iter() - .map(|v| v.to_ss58check_with_version(Ss58AddressFormat::custom(1996))) - .collect::>(); - - self.0.action_tx.send(Action::SetValidators(validators))?; - Ok(()) - } -} diff --git a/src/tui.rs b/src/tui.rs index 843cbd8..77bf09b 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -39,9 +39,8 @@ pub enum Event { Key(KeyEvent), Mouse(MouseEvent), Resize(u16, u16), + Oneshot, Node, - FastNode, - Runtime, } pub struct Tui { @@ -97,9 +96,8 @@ impl Tui { let mut tick_interval = interval(Duration::from_secs_f64(1.0 / tick_rate)); let mut render_interval = interval(Duration::from_secs_f64(1.0 / frame_rate)); - let mut normal_node_interval = interval(Duration::from_secs_f64(9.0)); - let mut fast_node_interval = interval(Duration::from_secs_f64(9.0)); - let mut runtime_interval = interval(Duration::from_secs_f64(1.69)); + let mut oneshot_node_interval = interval(Duration::from_secs_f64(86_400.0)); + let mut fast_node_interval = interval(Duration::from_secs_f64(2.0)); event_tx .send(Event::Init) @@ -112,9 +110,8 @@ impl Tui { }, _ = tick_interval.tick() => Event::Tick, _ = render_interval.tick() => Event::Render, - _ = normal_node_interval.tick() => Event::Node, - _ = fast_node_interval.tick() => Event::FastNode, - _ = runtime_interval.tick() => Event::Runtime, + _ = oneshot_node_interval.tick() => Event::Oneshot, + _ = fast_node_interval.tick() => Event::Node, crossterm_event = event_stream.next().fuse() => match crossterm_event { Some(Ok(event)) => match event { CrosstermEvent::Key(key) if key.kind == KeyEventKind::Press => Event::Key(key), diff --git a/src/types/block.rs b/src/types/block.rs deleted file mode 100644 index cf47fff..0000000 --- a/src/types/block.rs +++ /dev/null @@ -1,24 +0,0 @@ -use serde::{Serialize, Deserialize}; - -#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LogInfo { - pub logs: Vec, -} - -#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct HeaderInfo { - pub parent_hash: String, - pub number: String, - pub state_root: String, - pub extrinsics_root: String, - pub digest: LogInfo -} - -#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct BlockInfo { - pub header: HeaderInfo, - pub extrinsics: Vec, -} diff --git a/src/types/extrinsics.rs b/src/types/extrinsics.rs new file mode 100644 index 0000000..1f7c256 --- /dev/null +++ b/src/types/extrinsics.rs @@ -0,0 +1,35 @@ +use serde::{Serialize, Deserialize}; +use subxt::utils::H256; + +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct CasperExtrinsicDetails { + pub index: u32, + pub hash: H256, + pub is_signed: bool, + pub field_bytes: Vec, + pub address_bytes: Option>, + pub pallet_name: String, + pub variant_name: String, +} + +impl CasperExtrinsicDetails { + pub fn new( + index: u32, + hash: H256, + is_signed: bool, + field_bytes: Vec, + address_bytes: Option>, + pallet_name: String, + variant_name: String, + ) -> Self { + Self { + index, + hash, + is_signed, + field_bytes, + address_bytes, + pallet_name, + variant_name, + } + } +} diff --git a/src/types/macros.rs b/src/types/macros.rs deleted file mode 100644 index b949851..0000000 --- a/src/types/macros.rs +++ /dev/null @@ -1,14 +0,0 @@ -#[macro_export] -macro_rules! rpc_params { - ($($param:expr),*) => {{ - use crate::types::params::RpcParams; - - let mut params = RpcParams::new(); - $( - if let Err(err) = params.insert($param) { - panic!("parameter `{}` cannot be serialized: {:?}", stringify!($param), err); - } - )* - params - }} -} diff --git a/src/types/mod.rs b/src/types/mod.rs index bdefc8a..55a1ebb 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,5 +1,5 @@ -pub mod block; -pub mod params; -pub mod storage; -pub mod macros; -pub mod era; +mod era; +mod extrinsics; + +pub use extrinsics::CasperExtrinsicDetails; +pub use era::EraInfo; diff --git a/src/types/params.rs b/src/types/params.rs deleted file mode 100644 index ad4a178..0000000 --- a/src/types/params.rs +++ /dev/null @@ -1,75 +0,0 @@ -use color_eyre::Result; -use serde::Serialize; - -#[derive(Debug)] -pub struct RpcParams(ParamsBuilder); - -impl RpcParams { - pub fn new() -> Self { - Self::default() - } - - pub fn insert(&mut self, value: P) -> Result<()> { - self.0.insert(value) - } - - pub fn build(self) -> String { - self.0.build() - } -} - -impl Default for RpcParams { - fn default() -> Self { - Self(ParamsBuilder::positional()) - } -} - -const PARAM_BYTES_CAPACITY: usize = 128; - -#[derive(Debug)] -pub struct ParamsBuilder { - bytes: Vec, - start: char, - end: char, -} - -impl ParamsBuilder { - fn new(start: char, end: char) -> Self { - Self { bytes: Vec::new(), start, end } - } - - fn positional() -> Self { - Self::new('[', ']') - } - - fn maybe_initialize(&mut self) { - if self.bytes.is_empty() { - self.bytes.reserve(PARAM_BYTES_CAPACITY); - self.bytes.push(self.start as u8); - } - } - - pub fn build(mut self) -> String { - if self.bytes.is_empty() { - return format!("{}{}", self.start, self.end); - } - - let index = self.bytes.len() - 1; - if self.bytes[index] == b',' { - self.bytes[index] = self.end as u8; - } else { - self.bytes.push(self.end as u8); - } - - unsafe { String::from_utf8_unchecked(self.bytes) } - } - - pub fn insert(&mut self, value: P) -> Result<()> { - self.maybe_initialize(); - - serde_json::to_writer(&mut self.bytes, &value)?; - self.bytes.push(b','); - - Ok(()) - } -} diff --git a/src/types/storage.rs b/src/types/storage.rs deleted file mode 100644 index 5db24a4..0000000 --- a/src/types/storage.rs +++ /dev/null @@ -1,32 +0,0 @@ -#[derive(Debug, Default)] -pub struct GhostStorage<'a> { - module: &'a str, - method: &'a str, -} - -impl<'a> GhostStorage<'a> { - pub fn new() -> Self { - Self { - module: "", - method: "", - } - } - - pub fn with_module(mut self, module: &'a str) -> Self { - self.module = module; - self - } - - pub fn with_method(mut self, method: &'a str) -> Self { - self.method = method; - self - } - - pub fn build_storage_key(&self) -> String { - let module_hex = hex::encode(sp_core::twox_128(self.module.as_bytes())); - let method_hex = hex::encode(sp_core::twox_128(self.method.as_bytes())); - - let storage_key = format!("0x{}{}", module_hex, method_hex); - storage_key - } -}