From 73db8ca32ab66a3c4ca3e1186bd7ff47a78926a4 Mon Sep 17 00:00:00 2001 From: Uncle Stretch Date: Sun, 24 Nov 2024 19:01:41 +0300 Subject: [PATCH] integration of subxt dependency Signed-off-by: Uncle Stretch --- Cargo.toml | 13 +- artifacts/casper.scale | Bin 0 -> 280144 bytes src/action.rs | 41 ++- src/app.rs | 20 +- src/casper.rs | 33 ++ src/cli.rs | 2 +- src/components/explorer/block_ticker.rs | 11 +- src/components/explorer/current_epoch.rs | 9 +- src/components/explorer/current_era.rs | 11 +- src/components/explorer/explorer_blocks.rs | 345 +++++++++----------- src/components/explorer/extrinsics_chart.rs | 11 +- src/components/explorer/finalized_block.rs | 11 +- src/components/explorer/latest_block.rs | 11 +- src/components/health.rs | 11 +- src/components/version.rs | 20 +- src/main.rs | 63 +++- src/network/active_era.rs | 33 -- src/network/chain_name.rs | 20 -- src/network/current_epoch.rs | 41 --- src/network/finalized_block.rs | 35 -- src/network/genesis_hash.rs | 18 - src/network/health_check.rs | 37 --- src/network/latest_block.rs | 35 -- src/network/legacy_rpc_calls.rs | 70 ++++ src/network/mod.rs | 198 +++-------- src/network/node_name.rs | 20 -- src/network/node_version.rs | 20 -- src/network/predefinded_calls.rs | 110 +++++++ src/network/runtime_version.rs | 0 src/network/subscriptions.rs | 119 +++++++ src/network/tx_pool.rs | 21 -- src/network/validators.rs | 37 --- src/tui.rs | 13 +- src/types/block.rs | 24 -- src/types/extrinsics.rs | 35 ++ src/types/macros.rs | 14 - src/types/mod.rs | 10 +- src/types/params.rs | 75 ----- src/types/storage.rs | 32 -- 39 files changed, 734 insertions(+), 895 deletions(-) create mode 100644 artifacts/casper.scale create mode 100644 src/casper.rs delete mode 100644 src/network/active_era.rs delete mode 100644 src/network/chain_name.rs delete mode 100644 src/network/current_epoch.rs delete mode 100644 src/network/finalized_block.rs delete mode 100644 src/network/genesis_hash.rs delete mode 100644 src/network/health_check.rs delete mode 100644 src/network/latest_block.rs create mode 100644 src/network/legacy_rpc_calls.rs delete mode 100644 src/network/node_name.rs delete mode 100644 src/network/node_version.rs create mode 100644 src/network/predefinded_calls.rs delete mode 100644 src/network/runtime_version.rs create mode 100644 src/network/subscriptions.rs delete mode 100644 src/network/tx_pool.rs delete mode 100644 src/network/validators.rs delete mode 100644 src/types/block.rs create mode 100644 src/types/extrinsics.rs delete mode 100644 src/types/macros.rs delete mode 100644 src/types/params.rs delete mode 100644 src/types/storage.rs 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 0000000000000000000000000000000000000000..f99ff36452d7dbb67a4b5c4fc5ea5b57151199ec GIT binary patch literal 280144 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ǢwwpC$qHZ3{v2ExG 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, + 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 - } -}