integration of subxt dependency
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
This commit is contained in:
parent
1508ed818f
commit
73db8ca32a
13
Cargo.toml
13
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"
|
||||
|
BIN
artifacts/casper.scale
Normal file
BIN
artifacts/casper.scale
Normal file
Binary file not shown.
@ -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<DigestItem>),
|
||||
SetBlockAuthor(H256, Option<CasperAccountId>),
|
||||
|
||||
GetNodeName,
|
||||
GetSyncState,
|
||||
GetSystemHealth,
|
||||
GetGenesisHash,
|
||||
GetChainName,
|
||||
GetNodeVersion,
|
||||
GetChainVersion,
|
||||
GetPendingExtrinsics,
|
||||
|
||||
GetLatestBlock,
|
||||
GetFinalizedBlock,
|
||||
GetActiveEra,
|
||||
GetEpoch,
|
||||
GetEpochProgress,
|
||||
GetValidators,
|
||||
|
||||
SetNodeName(Option<String>),
|
||||
SetSyncState(Option<u32>, bool, bool),
|
||||
SetGenesisHash(Option<String>),
|
||||
SetSystemHealth(Option<usize>, bool, bool),
|
||||
SetGenesisHash(Option<H256>),
|
||||
SetChainName(Option<String>),
|
||||
SetNodeVersion(Option<String>),
|
||||
SetChainVersion(Option<String>),
|
||||
|
||||
SetLatestBlock(String, BlockInfo),
|
||||
SetFinalizedBlock(String, BlockInfo),
|
||||
BestBlockInformation(H256, u32, Vec<CasperExtrinsicDetails>),
|
||||
FinalizedBlockInformation(H256, u32, Vec<CasperExtrinsicDetails>),
|
||||
SetActiveEra(EraInfo),
|
||||
SetEpoch(u64, u64),
|
||||
SetValidators(Vec<String>),
|
||||
SetPendingExtrinsicsLength(usize),
|
||||
SetEpochProgress(u64, u64),
|
||||
SetValidatorsForExplorer(Vec<CasperAccountId>), // TODO: change to BlockAuthor
|
||||
SetPendingExtrinsicsLength(usize), // TODO: rename in oreder to match tx.pool
|
||||
}
|
||||
|
20
src/app.rs
20
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(())
|
||||
}
|
||||
|
||||
|
33
src/casper.rs
Normal file
33
src/casper.rs
Normal file
@ -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 = <SubstrateConfig as Config>::Hash;
|
||||
type AccountId = <SubstrateConfig as Config>::AccountId;
|
||||
type Address = <SubstrateConfig as Config>::Address;
|
||||
type Signature = <SubstrateConfig as Config>::Signature;
|
||||
type Hasher = <SubstrateConfig as Config>::Hasher;
|
||||
type Header = <SubstrateConfig as Config>::Header;
|
||||
type ExtrinsicParams = CasperExtrinsicParams<Self>;
|
||||
type AssetId = u32;
|
||||
}
|
||||
|
||||
|
||||
pub type CasperAccountId = subxt::utils::AccountId32;
|
||||
pub type CasperBlock = Block<CasperConfig, OnlineClient<CasperConfig>>;
|
||||
|
||||
/// A struct representing the signed extra and additional parameters required to construct a
|
||||
/// transaction for a polkadot node.
|
||||
pub type CasperExtrinsicParams<T> = DefaultExtrinsicParams<T>;
|
||||
|
||||
///// A builder which leads to [`CasperExtrinsicParams`] being constructed. This is what we provide
|
||||
///// to methods like `sign_and_submit()`.
|
||||
//pub type CasperExtrinsicParamsBuilder<T> = DefaultExtrinsicParamsBuilder<T>;
|
@ -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
|
||||
|
@ -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<Option<Action>> {
|
||||
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);
|
||||
|
@ -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<Option<Action>> {
|
||||
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);
|
||||
|
@ -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);
|
||||
|
@ -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<BlockInfo>,
|
||||
extrinsics: HashMap<String, Vec<String>>,
|
||||
logs: HashMap<String, Vec<String>>,
|
||||
validators: Vec<String>,
|
||||
block_headers: HashMap<u32, H256>,
|
||||
authors: HashMap<H256, CasperAccountId>,
|
||||
extrinsics: HashMap<H256, Vec<CasperExtrinsicDetails>>,
|
||||
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<u32>,
|
||||
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<String>) -> Result<()> {
|
||||
self.validators = validators;
|
||||
fn update_block_author(
|
||||
&mut self,
|
||||
hash: H256,
|
||||
maybe_author: Option<CasperAccountId>,
|
||||
) -> Result<()> {
|
||||
if let Some(author) = maybe_author {
|
||||
self.authors.insert(hash, author);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_author_from_digest(&self, logs: Vec<String>) -> Option<String> {
|
||||
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::<Vec<_>>();
|
||||
|
||||
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<String>,
|
||||
extrinsics: Vec<String>,
|
||||
hash: H256,
|
||||
block_number: u32,
|
||||
extrinsics: Vec<CasperExtrinsicDetails>,
|
||||
) -> 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<String>,
|
||||
extrinsics: Vec<String>,
|
||||
_hash: H256,
|
||||
block_number: u32,
|
||||
_extrinsics: Vec<CasperExtrinsicDetails>,
|
||||
) -> 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$}::{:<variant_name_offset$}{:^hash_length$}",
|
||||
index, pallet_name, variant_name, hash,
|
||||
pallet_name_offset=offset_pallet+pallet_name_length,
|
||||
variant_name_offset=offset_variant+variant_name_length))
|
||||
}
|
||||
|
||||
fn prepare_ext_lines(&mut self, rect: Rect) -> Vec<Line> {
|
||||
@ -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<Option<Action>> {
|
||||
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()?,
|
||||
_ => {}
|
||||
|
@ -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<Option<Action>> {
|
||||
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)
|
||||
|
@ -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<Option<Action>> {
|
||||
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);
|
||||
|
@ -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<Option<Action>> {
|
||||
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);
|
||||
|
@ -16,7 +16,7 @@ use crate::{
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Health {
|
||||
name: Option<String>,
|
||||
peers: Option<u32>,
|
||||
peers: Option<usize>,
|
||||
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<u32>, is_syncing: bool, should_have_peers: bool) -> Result<()> {
|
||||
fn set_sync_state(
|
||||
&mut self,
|
||||
peers: Option<usize>,
|
||||
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<Option<Action>> {
|
||||
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)?,
|
||||
|
@ -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<String>,
|
||||
genesis_hash: Option<H256>,
|
||||
node_version: Option<String>,
|
||||
chain_name: Option<String>,
|
||||
palette: StylePalette,
|
||||
@ -30,18 +31,15 @@ impl Version {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_genesis_hash(&mut self, genesis_hash: Option<String>) -> Result<()> {
|
||||
fn set_genesis_hash(&mut self, genesis_hash: Option<H256>) -> 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<Option<Action>> {
|
||||
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)
|
||||
|
63
src/main.rs
63
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<action::Action>,
|
||||
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::<CasperConfig>::new(rpc_client.clone());
|
||||
let online_client =
|
||||
OnlineClient::<CasperConfig>::from_rpc_client(rpc_client.clone()).await?;
|
||||
|
||||
let finalized_blocks_sub = online_client.blocks().subscribe_finalized().await?;
|
||||
let best_blocks_sub = online_client.blocks().subscribe_best().await?;
|
||||
|
||||
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)?
|
||||
|
@ -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::<String>("state_getStorage", rpc_params![storage_key])
|
||||
.await
|
||||
.map(|r| {
|
||||
hex::decode(r.result.trim_start_matches("0x")).or::<Vec<u8>>(Ok(vec![]))
|
||||
})
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let active_era = EraInfo::decode(&mut &result_hex[..])?;
|
||||
self.0.action_tx.send(Action::SetActiveEra(active_era))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -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::<String>("system_chain", RpcParams::new())
|
||||
.await
|
||||
.ok()
|
||||
.map(|response| response.result);
|
||||
self.0.action_tx.send(Action::SetChainName(chain_name))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -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<u64> {
|
||||
let storage_key = GhostStorage::new()
|
||||
.with_module(module)
|
||||
.with_method(method)
|
||||
.build_storage_key();
|
||||
|
||||
let result_hex = self
|
||||
.0
|
||||
.send::<String>("state_getStorage", rpc_params![storage_key])
|
||||
.await
|
||||
.map(|r| {
|
||||
hex::decode(r.result.trim_start_matches("0x")).or::<Vec<u8>>(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(())
|
||||
}
|
||||
}
|
@ -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<String>
|
||||
}
|
||||
|
||||
#[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::<String>("chain_getFinalizedHead", RpcParams::new())
|
||||
.await
|
||||
.map_or(String::new(), |response| response.result);
|
||||
let finalized_block = self
|
||||
.0
|
||||
.send::<LatestBlockResponse>("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(())
|
||||
}
|
||||
}
|
@ -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::<String>("chain_getBlockHash", rpc_params!["0"])
|
||||
.await
|
||||
.ok()
|
||||
.map(|response| response.result);
|
||||
self.0.action_tx.send(Action::SetGenesisHash(genesis_hash))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -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::<HealthCheckResponse>("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(())
|
||||
}
|
||||
}
|
@ -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<String>
|
||||
}
|
||||
|
||||
#[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::<String>("chain_getBlockHash", RpcParams::new())
|
||||
.await
|
||||
.map_or(String::new(), |response| response.result);
|
||||
let latest_block = self
|
||||
.0
|
||||
.send::<LatestBlockResponse>("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(())
|
||||
}
|
||||
}
|
70
src/network/legacy_rpc_calls.rs
Normal file
70
src/network/legacy_rpc_calls.rs
Normal file
@ -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<Action>,
|
||||
api: &LegacyRpcMethods<CasperConfig>,
|
||||
) -> 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<Action>,
|
||||
api: &LegacyRpcMethods<CasperConfig>,
|
||||
) -> 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<Action>,
|
||||
api: &LegacyRpcMethods<CasperConfig>,
|
||||
) -> 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<Action>,
|
||||
api: &LegacyRpcMethods<CasperConfig>,
|
||||
) -> 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<Action>,
|
||||
api: &LegacyRpcMethods<CasperConfig>,
|
||||
) -> Result<()> {
|
||||
let maybe_system_version = api
|
||||
.system_version()
|
||||
.await
|
||||
.ok();
|
||||
action_tx.send(Action::SetChainVersion(maybe_system_version))?;
|
||||
Ok(())
|
||||
}
|
@ -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<Arc<Client>> = Lazy::new(|| Arc::new(Client::new()));
|
||||
const DEFAULT_URL: &str = "http://localhost:9945";
|
||||
|
||||
pub type AppActionSender = UnboundedSender<Action>;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct GhostResponse<ResponseType> {
|
||||
result: ResponseType,
|
||||
pub struct Network {
|
||||
action_tx: UnboundedSender<Action>,
|
||||
online_client_api: OnlineClient<CasperConfig>,
|
||||
legacy_client_api: LegacyRpcMethods<CasperConfig>,
|
||||
rpc_client: RpcClient,
|
||||
best_hash: Option<H256>,
|
||||
finalized_hash: Option<H256>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct GhostRequestBuilder<'a> {
|
||||
action_tx: Option<AppActionSender>,
|
||||
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<ResponseType>(
|
||||
&self,
|
||||
method: &str,
|
||||
params: RpcParams,
|
||||
) -> Result<GhostResponse<ResponseType>>
|
||||
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::<GhostResponse<ResponseType>>()
|
||||
.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<Action>,
|
||||
online_client_api: OnlineClient<CasperConfig>,
|
||||
legacy_client_api: LegacyRpcMethods<CasperConfig>,
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
@ -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::<String>("system_name", RpcParams::new())
|
||||
.await
|
||||
.ok()
|
||||
.map(|response| response.result);
|
||||
self.0.action_tx.send(Action::SetNodeName(name))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -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::<String>("system_version", RpcParams::new())
|
||||
.await
|
||||
.ok()
|
||||
.map(|response| response.result);
|
||||
self.0.action_tx.send(Action::SetNodeVersion(version))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
110
src/network/predefinded_calls.rs
Normal file
110
src/network/predefinded_calls.rs
Normal file
@ -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<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
logs: &Vec<DigestItem>,
|
||||
at_hash: &H256,
|
||||
) -> Result<()> {
|
||||
use codec::Decode;
|
||||
use crate::casper_network::runtime_types::sp_consensus_babe::digests::PreDigest;
|
||||
|
||||
let storage_key = casper_network::storage().session().validators();
|
||||
let validators = api.storage().at(*at_hash).fetch(&storage_key).await?.unwrap_or_default();
|
||||
|
||||
let maybe_author = match logs.iter().find(|item| matches!(item, DigestItem::PreRuntime(..))) {
|
||||
Some(DigestItem::PreRuntime(engine, data)) if *engine == [b'B', b'A', b'B', b'E'] => {
|
||||
match PreDigest::decode(&mut &data[..]) {
|
||||
Ok(PreDigest::Primary(primary)) => validators.get(primary.authority_index as usize),
|
||||
Ok(PreDigest::SecondaryPlain(secondary)) => validators.get(secondary.authority_index as usize),
|
||||
Ok(PreDigest::SecondaryVRF(secondary)) => validators.get(secondary.authority_index as usize),
|
||||
_ => None,
|
||||
}
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
|
||||
action_tx.send(Action::SetBlockAuthor(*at_hash, maybe_author.cloned()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_active_era(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
) -> Result<()> {
|
||||
let storage_key = casper_network::storage().staking().active_era();
|
||||
if let Some(active_era) = api.storage().at_latest().await?.fetch(&storage_key).await? {
|
||||
action_tx.send(Action::SetActiveEra(EraInfo {
|
||||
index: active_era.index,
|
||||
start: active_era.start,
|
||||
}))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_epoch_progress(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
) -> Result<()> {
|
||||
|
||||
let storage_key = casper_network::storage().babe().current_slot();
|
||||
let current_slot = api.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.fetch(&storage_key)
|
||||
.await?
|
||||
.unwrap_or(sp_consensus_slots::Slot(0u64));
|
||||
|
||||
let storage_key = casper_network::storage().babe().epoch_index();
|
||||
let epoch_index = api.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.fetch(&storage_key)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
let storage_key = casper_network::storage().babe().genesis_slot();
|
||||
let genesis_slot = api.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.fetch(&storage_key)
|
||||
.await?
|
||||
.unwrap_or(sp_consensus_slots::Slot(0u64));
|
||||
|
||||
let constant_query = casper_network::constants().babe().epoch_duration();
|
||||
let epoch_duration = api.constants().at(&constant_query)?;
|
||||
|
||||
let epoch_start_slot = epoch_index * epoch_duration + genesis_slot.0;
|
||||
let progress = current_slot.0.saturating_sub(epoch_start_slot);
|
||||
|
||||
action_tx.send(Action::SetEpochProgress(epoch_index, progress))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_pending_extrinsics(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
rpc_client: &RpcClient,
|
||||
) -> Result<()> {
|
||||
let pending_extrinsics: Vec<String> = rpc_client
|
||||
.request("author_pendingExtrinsics", rpc_params![])
|
||||
.await?;
|
||||
action_tx.send(Action::SetPendingExtrinsicsLength(pending_extrinsics.len()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
119
src/network/subscriptions.rs
Normal file
119
src/network/subscriptions.rs
Normal file
@ -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<Action>,
|
||||
network_tx: std::sync::mpsc::Sender<Action>,
|
||||
finalized_blocks_sub: subxt::backend::StreamOfResults<CasperBlock>,
|
||||
}
|
||||
|
||||
impl FinalizedSubscription {
|
||||
pub fn new(
|
||||
action_tx: tokio::sync::mpsc::UnboundedSender<Action>,
|
||||
network_tx: std::sync::mpsc::Sender<Action>,
|
||||
finalized_blocks_sub: subxt::backend::StreamOfResults<CasperBlock>,
|
||||
) -> 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<Action>,
|
||||
network_tx: std::sync::mpsc::Sender<Action>,
|
||||
best_blocks_sub: subxt::backend::StreamOfResults<CasperBlock>,
|
||||
}
|
||||
|
||||
impl BestSubscription {
|
||||
pub fn new(
|
||||
action_tx: tokio::sync::mpsc::UnboundedSender<Action>,
|
||||
network_tx: std::sync::mpsc::Sender<Action>,
|
||||
best_blocks_sub: subxt::backend::StreamOfResults<CasperBlock>,
|
||||
) -> 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(())
|
||||
}
|
||||
}
|
@ -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::<Vec<String>>("author_pendingExtrinsics", RpcParams::new())
|
||||
.await
|
||||
.ok()
|
||||
.map(|response| response.result)
|
||||
.unwrap_or_default();
|
||||
self.0.action_tx.send(Action::SetPendingExtrinsicsLength(tx_pool.len()))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -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::<String>("state_getStorage", rpc_params![storage_key])
|
||||
.await
|
||||
.map(|response| {
|
||||
hex::decode(response.result.trim_start_matches("0x"))
|
||||
.ok()
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.unwrap();
|
||||
let validators = <Vec<AccountId32>>::decode(&mut &result_hex[..])
|
||||
.ok()
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(|v| v.to_ss58check_with_version(Ss58AddressFormat::custom(1996)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
self.0.action_tx.send(Action::SetValidators(validators))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
13
src/tui.rs
13
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),
|
||||
|
@ -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<String>,
|
||||
}
|
||||
|
||||
#[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<String>,
|
||||
}
|
35
src/types/extrinsics.rs
Normal file
35
src/types/extrinsics.rs
Normal file
@ -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<u8>,
|
||||
pub address_bytes: Option<Vec<u8>>,
|
||||
pub pallet_name: String,
|
||||
pub variant_name: String,
|
||||
}
|
||||
|
||||
impl CasperExtrinsicDetails {
|
||||
pub fn new(
|
||||
index: u32,
|
||||
hash: H256,
|
||||
is_signed: bool,
|
||||
field_bytes: Vec<u8>,
|
||||
address_bytes: Option<Vec<u8>>,
|
||||
pallet_name: String,
|
||||
variant_name: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
index,
|
||||
hash,
|
||||
is_signed,
|
||||
field_bytes,
|
||||
address_bytes,
|
||||
pallet_name,
|
||||
variant_name,
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}}
|
||||
}
|
@ -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;
|
||||
|
@ -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<P: Serialize>(&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<u8>,
|
||||
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<P: Serialize>(&mut self, value: P) -> Result<()> {
|
||||
self.maybe_initialize();
|
||||
|
||||
serde_json::to_writer(&mut self.bytes, &value)?;
|
||||
self.bytes.push(b',');
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user