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]
|
[package]
|
||||||
name = "ghost-eye"
|
name = "ghost-eye"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@ -21,20 +21,13 @@ human-panic = "2.0.2"
|
|||||||
json5 = "0.4.1"
|
json5 = "0.4.1"
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
libc = "0.2.159"
|
libc = "0.2.159"
|
||||||
log = "0.4.22"
|
primitive-types = "0.13.1"
|
||||||
once_cell = "1.20.2"
|
|
||||||
pretty_assertions = "1.4.1"
|
|
||||||
rand = "0.8.5"
|
|
||||||
ratatui = { version = "0.28.1", features = ["serde", "macros"] }
|
ratatui = { version = "0.28.1", features = ["serde", "macros"] }
|
||||||
reqwest = { version = "0.12.8", features = ["json"] }
|
|
||||||
serde = { version = "1.0.210", features = ["derive"] }
|
serde = { version = "1.0.210", features = ["derive"] }
|
||||||
serde_json = "1.0.128"
|
|
||||||
signal-hook = "0.3.17"
|
signal-hook = "0.3.17"
|
||||||
sp-consensus-babe = "0.40.0"
|
|
||||||
sp-core = "34.0.0"
|
sp-core = "34.0.0"
|
||||||
sp-runtime = "39.0.2"
|
|
||||||
strip-ansi-escapes = "0.2.0"
|
|
||||||
strum = { version = "0.26.3", features = ["derive"] }
|
strum = { version = "0.26.3", features = ["derive"] }
|
||||||
|
subxt = { version = "0.38.0", features = ["jsonrpsee"] }
|
||||||
tokio = { version = "1.40.0", features = ["full"] }
|
tokio = { version = "1.40.0", features = ["full"] }
|
||||||
tokio-util = "0.7.12"
|
tokio-util = "0.7.12"
|
||||||
tracing = "0.1.37"
|
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 serde::{Deserialize, Serialize};
|
||||||
use strum::Display;
|
use strum::Display;
|
||||||
|
use primitive_types::H256;
|
||||||
|
|
||||||
use crate::types::{
|
use subxt::config::substrate::DigestItem;
|
||||||
block::BlockInfo,
|
|
||||||
era::EraInfo,
|
use crate::{
|
||||||
|
CasperAccountId,
|
||||||
|
types::{EraInfo, CasperExtrinsicDetails},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)]
|
||||||
@ -20,29 +23,39 @@ pub enum Action {
|
|||||||
|
|
||||||
SetMode(crate::app::Mode),
|
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,
|
GetNodeName,
|
||||||
GetSyncState,
|
GetSystemHealth,
|
||||||
GetGenesisHash,
|
GetGenesisHash,
|
||||||
GetChainName,
|
GetChainName,
|
||||||
GetNodeVersion,
|
GetChainVersion,
|
||||||
GetPendingExtrinsics,
|
GetPendingExtrinsics,
|
||||||
|
|
||||||
GetLatestBlock,
|
GetLatestBlock,
|
||||||
GetFinalizedBlock,
|
GetFinalizedBlock,
|
||||||
GetActiveEra,
|
GetActiveEra,
|
||||||
GetEpoch,
|
GetEpochProgress,
|
||||||
GetValidators,
|
GetValidators,
|
||||||
|
|
||||||
SetNodeName(Option<String>),
|
SetNodeName(Option<String>),
|
||||||
SetSyncState(Option<u32>, bool, bool),
|
SetSystemHealth(Option<usize>, bool, bool),
|
||||||
SetGenesisHash(Option<String>),
|
SetGenesisHash(Option<H256>),
|
||||||
SetChainName(Option<String>),
|
SetChainName(Option<String>),
|
||||||
SetNodeVersion(Option<String>),
|
SetChainVersion(Option<String>),
|
||||||
|
|
||||||
SetLatestBlock(String, BlockInfo),
|
BestBlockInformation(H256, u32, Vec<CasperExtrinsicDetails>),
|
||||||
SetFinalizedBlock(String, BlockInfo),
|
FinalizedBlockInformation(H256, u32, Vec<CasperExtrinsicDetails>),
|
||||||
SetActiveEra(EraInfo),
|
SetActiveEra(EraInfo),
|
||||||
SetEpoch(u64, u64),
|
SetEpochProgress(u64, u64),
|
||||||
SetValidators(Vec<String>),
|
SetValidatorsForExplorer(Vec<CasperAccountId>), // TODO: change to BlockAuthor
|
||||||
SetPendingExtrinsicsLength(usize),
|
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::Render => action_tx.send(Action::Render)?,
|
||||||
Event::Resize(x, y) => action_tx.send(Action::Resize(x, y))?,
|
Event::Resize(x, y) => action_tx.send(Action::Resize(x, y))?,
|
||||||
Event::Key(key) => self.handle_key_event(key)?,
|
Event::Key(key) => self.handle_key_event(key)?,
|
||||||
Event::Node => self.trigger_node_events()?,
|
Event::Oneshot => self.trigger_oneshot_node_events()?,
|
||||||
Event::FastNode => self.trigger_node_fast_events()?,
|
Event::Node => self.trigger_node_fast_events()?,
|
||||||
Event::Runtime => self.trigger_runtime_events()?,
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,21 +156,12 @@ impl App {
|
|||||||
Ok(())
|
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::GetNodeName)?;
|
||||||
self.network_tx.send(Action::GetSyncState)?;
|
self.network_tx.send(Action::GetSystemHealth)?;
|
||||||
self.network_tx.send(Action::GetGenesisHash)?;
|
self.network_tx.send(Action::GetGenesisHash)?;
|
||||||
self.network_tx.send(Action::GetChainName)?;
|
self.network_tx.send(Action::GetChainName)?;
|
||||||
self.network_tx.send(Action::GetNodeVersion)?;
|
self.network_tx.send(Action::GetChainVersion)?;
|
||||||
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)?;
|
|
||||||
Ok(())
|
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,
|
pub frame_rate: f32,
|
||||||
|
|
||||||
/// RPC Endpoint to the nodes JSON RPC
|
/// 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,
|
pub rpc_endpoint: String,
|
||||||
|
|
||||||
/// Request timeout in seconds
|
/// Request timeout in seconds
|
||||||
|
@ -36,9 +36,7 @@ impl BlockTicker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn block_found(&mut self, block: &str) -> Result<()> {
|
fn block_found(&mut self, block: u32) -> Result<()> {
|
||||||
let block = block.trim_start_matches("0x");
|
|
||||||
let block = u32::from_str_radix(&block, 16)?;
|
|
||||||
if self.last_block < block {
|
if self.last_block < block {
|
||||||
self.last_block_time = Instant::now();
|
self.last_block_time = Instant::now();
|
||||||
self.last_block = block;
|
self.last_block = block;
|
||||||
@ -50,8 +48,7 @@ impl BlockTicker {
|
|||||||
impl Component for BlockTicker {
|
impl Component for BlockTicker {
|
||||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||||
match action {
|
match action {
|
||||||
Action::SetLatestBlock(_, block_info) =>
|
Action::BestBlockUpdated(block) => self.block_found(block)?,
|
||||||
self.block_found(&block_info.header.number)?,
|
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@ -83,7 +80,7 @@ impl Component for BlockTicker {
|
|||||||
.border_type(border_type)
|
.border_type(border_type)
|
||||||
.title_alignment(Alignment::Right)
|
.title_alignment(Alignment::Right)
|
||||||
.title_style(self.palette.create_title_style())
|
.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"))
|
.title("Passed"))
|
||||||
.alignment(Alignment::Center)
|
.alignment(Alignment::Center)
|
||||||
.wrap(Wrap { trim: true });
|
.wrap(Wrap { trim: true });
|
||||||
@ -108,7 +105,7 @@ impl Component for BlockTicker {
|
|||||||
.wrap(Wrap { trim: true });
|
.wrap(Wrap { trim: true });
|
||||||
frame.render_widget(paragraph, place);
|
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 })
|
let place = place.offset(ratatui::layout::Offset { x: 1, y: height_offset as i32 })
|
||||||
.intersection(place);
|
.intersection(place);
|
||||||
frame.render_widget(big_text, place);
|
frame.render_widget(big_text, place);
|
||||||
|
@ -22,7 +22,7 @@ impl CurrentEpoch {
|
|||||||
const SECONDS_IN_DAY: u64 = 86_400;
|
const SECONDS_IN_DAY: u64 = 86_400;
|
||||||
const SECONDS_IN_HOUR: u64 = 3_600;
|
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.number = number;
|
||||||
self.progress = progress;
|
self.progress = progress;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -32,7 +32,8 @@ impl CurrentEpoch {
|
|||||||
impl Component for CurrentEpoch {
|
impl Component for CurrentEpoch {
|
||||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||||
match action {
|
match action {
|
||||||
Action::SetEpoch(number, progress) => self.update_epoch(number, progress)?,
|
Action::SetEpochProgress(number, progress) =>
|
||||||
|
self.update_epoch_progress(number, progress)?,
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@ -70,7 +71,7 @@ impl Component for CurrentEpoch {
|
|||||||
.border_type(border_type)
|
.border_type(border_type)
|
||||||
.title_alignment(Alignment::Right)
|
.title_alignment(Alignment::Right)
|
||||||
.title_style(self.palette.create_title_style())
|
.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"))
|
.title("Epoch"))
|
||||||
.alignment(Alignment::Center)
|
.alignment(Alignment::Center)
|
||||||
.wrap(Wrap { trim: true });
|
.wrap(Wrap { trim: true });
|
||||||
@ -100,7 +101,7 @@ impl Component for CurrentEpoch {
|
|||||||
.wrap(Wrap { trim: true });
|
.wrap(Wrap { trim: true });
|
||||||
frame.render_widget(paragraph, place);
|
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 })
|
let place = place.offset(ratatui::layout::Offset { x: 1, y: height_offset as i32 })
|
||||||
.intersection(place);
|
.intersection(place);
|
||||||
frame.render_widget(big_text, place);
|
frame.render_widget(big_text, place);
|
||||||
|
@ -7,7 +7,12 @@ use ratatui::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use super::Component;
|
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)]
|
#[derive(Debug, Default)]
|
||||||
pub struct CurrentEra{
|
pub struct CurrentEra{
|
||||||
@ -84,7 +89,7 @@ impl Component for CurrentEra {
|
|||||||
.border_type(border_type)
|
.border_type(border_type)
|
||||||
.title_alignment(Alignment::Right)
|
.title_alignment(Alignment::Right)
|
||||||
.title_style(self.palette.create_title_style())
|
.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"))
|
.title("Era"))
|
||||||
.alignment(Alignment::Center)
|
.alignment(Alignment::Center)
|
||||||
.wrap(Wrap { trim: true });
|
.wrap(Wrap { trim: true });
|
||||||
@ -114,7 +119,7 @@ impl Component for CurrentEra {
|
|||||||
.wrap(Wrap { trim: true });
|
.wrap(Wrap { trim: true });
|
||||||
frame.render_widget(paragraph, place);
|
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 })
|
let place = place.offset(ratatui::layout::Offset { x: 1, y: height_offset as i32 })
|
||||||
.intersection(place);
|
.intersection(place);
|
||||||
frame.render_widget(big_text, place);
|
frame.render_widget(big_text, place);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use crossterm::event::{KeyCode, KeyEvent};
|
use crossterm::event::{KeyCode, KeyEvent};
|
||||||
|
use primitive_types::H256;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::{Alignment, Rect},
|
layout::{Alignment, Rect},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
@ -9,106 +10,83 @@ use ratatui::{
|
|||||||
widgets::{Block, BorderType, Paragraph},
|
widgets::{Block, BorderType, Paragraph},
|
||||||
Frame
|
Frame
|
||||||
};
|
};
|
||||||
use sp_consensus_babe::digests::PreDigest;
|
use sp_core::crypto::{AccountId32, Ss58Codec, Ss58AddressFormat};
|
||||||
use sp_runtime::DigestItem;
|
|
||||||
use codec::Decode;
|
use codec::Decode;
|
||||||
|
|
||||||
use super::Component;
|
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 {
|
struct BlockInfo {
|
||||||
block_number: u32,
|
block_number: u32,
|
||||||
finalized: bool,
|
finalized: bool,
|
||||||
hash: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Default)]
|
||||||
pub struct ExplorerBlocks {
|
pub struct ExplorerBlocks {
|
||||||
blocks: VecDeque<BlockInfo>,
|
blocks: VecDeque<BlockInfo>,
|
||||||
extrinsics: HashMap<String, Vec<String>>,
|
block_headers: HashMap<u32, H256>,
|
||||||
logs: HashMap<String, Vec<String>>,
|
authors: HashMap<H256, CasperAccountId>,
|
||||||
validators: Vec<String>,
|
extrinsics: HashMap<H256, Vec<CasperExtrinsicDetails>>,
|
||||||
palette: StylePalette,
|
palette: StylePalette,
|
||||||
max_block_len: u32,
|
current_block_digit_length: u32,
|
||||||
|
|
||||||
is_active: bool,
|
is_active: bool,
|
||||||
used_paragraph_index: usize,
|
used_paragraph_index: usize,
|
||||||
used_block_index: Option<(usize, u32)>,
|
used_block_number: Option<u32>,
|
||||||
used_ext_index: Option<(String, usize, usize)>,
|
used_ext_index: Option<(H256, usize)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExplorerBlocks {
|
impl ExplorerBlocks {
|
||||||
const MAX_BLOCKS: usize = 50;
|
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 LENGTH_OF_ADDRESS: u16 = 49;
|
||||||
const TOTAL_OFFSETS: u16 = 18;
|
const TOTAL_OFFSETS: u16 = 18;
|
||||||
|
|
||||||
fn update_validator_list(&mut self, validators: Vec<String>) -> Result<()> {
|
fn update_block_author(
|
||||||
self.validators = validators;
|
&mut self,
|
||||||
|
hash: H256,
|
||||||
|
maybe_author: Option<CasperAccountId>,
|
||||||
|
) -> Result<()> {
|
||||||
|
if let Some(author) = maybe_author {
|
||||||
|
self.authors.insert(hash, author);
|
||||||
|
}
|
||||||
Ok(())
|
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(
|
fn update_latest_block_info(
|
||||||
&mut self,
|
&mut self,
|
||||||
hash: String,
|
hash: H256,
|
||||||
number_hex_str: String,
|
block_number: u32,
|
||||||
logs: Vec<String>,
|
extrinsics: Vec<CasperExtrinsicDetails>,
|
||||||
extrinsics: Vec<String>,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let number_hex_str = number_hex_str.trim_start_matches("0x");
|
let front_block_number = match self.blocks.front() {
|
||||||
let block_number = u32::from_str_radix(&number_hex_str, 16)?;
|
Some(block_info) => block_info.block_number,
|
||||||
let front_block_number = if self.blocks.is_empty() {
|
None => 0,
|
||||||
0
|
|
||||||
} else {
|
|
||||||
self.blocks.front().unwrap().block_number
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if front_block_number < block_number {
|
if front_block_number < block_number {
|
||||||
self.blocks.push_front(BlockInfo {
|
self.blocks.push_front(BlockInfo {
|
||||||
block_number,
|
block_number,
|
||||||
finalized: false,
|
finalized: false,
|
||||||
hash: hash.clone(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
self.extrinsics.insert(hash.clone(), extrinsics);
|
self.extrinsics.insert(hash, extrinsics);
|
||||||
self.logs.insert(hash, logs);
|
self.block_headers.insert(block_number, hash);
|
||||||
|
|
||||||
let block_length = block_number.checked_ilog10().unwrap_or(0) + 1;
|
let block_length = block_number.checked_ilog10().unwrap_or(0) + 1;
|
||||||
if self.max_block_len < block_length {
|
if self.current_block_digit_length < block_length {
|
||||||
self.max_block_len = block_length;
|
self.current_block_digit_length = block_length;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.blocks.len() > Self::MAX_BLOCKS {
|
if self.blocks.len() > Self::MAX_BLOCKS {
|
||||||
if let Some(removed_block_info) = self.blocks.pop_back() {
|
if let Some(block) = self.blocks.pop_back() {
|
||||||
self.extrinsics.remove(&removed_block_info.hash);
|
if let Some(hash) = self.block_headers.remove(&block.block_number) {
|
||||||
self.logs.remove(&removed_block_info.hash);
|
self.extrinsics.remove(&hash);
|
||||||
|
self.authors.remove(&hash);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,23 +95,14 @@ impl ExplorerBlocks {
|
|||||||
|
|
||||||
fn update_finalized_block_info(
|
fn update_finalized_block_info(
|
||||||
&mut self,
|
&mut self,
|
||||||
hash: String,
|
_hash: H256,
|
||||||
number_hex_str: String,
|
block_number: u32,
|
||||||
logs: Vec<String>,
|
_extrinsics: Vec<CasperExtrinsicDetails>,
|
||||||
extrinsics: Vec<String>,
|
|
||||||
) -> Result<()> {
|
) -> 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() {
|
for idx in 0..self.blocks.len() {
|
||||||
if self.blocks[idx].finalized { break; }
|
if self.blocks[idx].finalized { break; }
|
||||||
else if self.blocks[idx].block_number > block_number { continue; }
|
else if self.blocks[idx].block_number > block_number { continue; }
|
||||||
else {
|
else { self.blocks[idx].finalized = true; }
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -141,49 +110,52 @@ impl ExplorerBlocks {
|
|||||||
|
|
||||||
fn prepare_block_line_info(&self, current_block: &BlockInfo, width: u16) -> Line {
|
fn prepare_block_line_info(&self, current_block: &BlockInfo, width: u16) -> Line {
|
||||||
let block_number_length = self
|
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;
|
.max(current_block.block_number.checked_ilog10().unwrap_or(0) + 1) as usize;
|
||||||
|
|
||||||
let free_space = width
|
let free_space = width
|
||||||
.saturating_sub(block_number_length as u16)
|
.saturating_sub(block_number_length as u16)
|
||||||
.saturating_sub(Self::TOTAL_OFFSETS);
|
.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
|
let author = self
|
||||||
.logs
|
.authors
|
||||||
.get(¤t_block.hash)
|
.get(&hash)
|
||||||
.map_or(String::from("..."), |maybe_logs| {
|
.map_or(String::from("..."), |author| {
|
||||||
self.get_author_from_digest(maybe_logs.to_vec())
|
let extended_author = AccountId32::decode(&mut author.as_ref())
|
||||||
.map_or(String::from("..."), |maybe_author| maybe_author)
|
.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 {
|
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_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 == "..." {
|
if &author == "..." {
|
||||||
Line::raw(format!("{:^left$}| {} | {:^right$}",
|
Line::raw(format!("{:^left$}| {} | {:^right$}",
|
||||||
current_block.block_number,
|
current_block.block_number,
|
||||||
hash_to_print,
|
hash.to_string(),
|
||||||
author,
|
author,
|
||||||
left=block_number_length,
|
left=block_number_length,
|
||||||
right=(len_for_author + 2) as usize))
|
right=(len_for_author + 2) as usize))
|
||||||
} else {
|
} else {
|
||||||
Line::raw(format!("{} | {} | {}",
|
Line::raw(format!("{} | {} | {}",
|
||||||
current_block.block_number,
|
current_block.block_number,
|
||||||
hash_to_print,
|
hash.to_string(),
|
||||||
format!("{}...", &author[..(len_for_author) as usize])))
|
format!("{}...", &author[..(len_for_author) as usize])))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let total_space_used = block_number_length as u16 + Self::LENGTH_OF_BLOCK_HASH + Self::LENGTH_OF_ADDRESS;
|
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;
|
let str_length = width.saturating_sub(2).saturating_sub(total_space_used) as usize / 3;
|
||||||
Line::raw(format!("{:^margin$}|{:^margin$}|{:^margin$}",
|
Line::raw(format!("{:^length_block_number$}|{:^length_hash$}|{:^length_author$}",
|
||||||
current_block.block_number,
|
current_block.block_number, hash.to_string(), author,
|
||||||
current_block.hash,
|
length_block_number=str_length+block_number_length,
|
||||||
author))
|
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 latest_style = self.palette.create_text_style(false);
|
||||||
let finalized_style = Style::new().fg(self.palette.foreground_hover());
|
let finalized_style = Style::new().fg(self.palette.foreground_hover());
|
||||||
|
|
||||||
let start_index = match self.used_block_index {
|
let start_index = match self.used_block_number {
|
||||||
Some((_, used_block)) if total_length < self.blocks.len() => {
|
Some(used_block) if total_length < self.blocks.len() => {
|
||||||
self.blocks
|
self.blocks
|
||||||
.iter()
|
.iter()
|
||||||
.position(|b| b.block_number == used_block)
|
.position(|info| info.block_number == used_block)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.saturating_add(1)
|
.saturating_add(1)
|
||||||
.saturating_sub(total_length)
|
.saturating_sub(total_length)
|
||||||
@ -211,8 +183,8 @@ impl ExplorerBlocks {
|
|||||||
for (idx, current_block_info) in self.blocks.iter().skip(start_index).enumerate() {
|
for (idx, current_block_info) in self.blocks.iter().skip(start_index).enumerate() {
|
||||||
if idx == total_length { break; }
|
if idx == total_length { break; }
|
||||||
|
|
||||||
let style = match self.used_block_index {
|
let style = match self.used_block_number {
|
||||||
Some((_, used_block)) if current_block_info.block_number == used_block => active_style,
|
Some(used_block) if current_block_info.block_number == used_block => active_style,
|
||||||
_ => {
|
_ => {
|
||||||
if current_block_info.finalized { finalized_style } else { latest_style }
|
if current_block_info.finalized { finalized_style } else { latest_style }
|
||||||
}
|
}
|
||||||
@ -224,13 +196,36 @@ impl ExplorerBlocks {
|
|||||||
items
|
items
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_ext_line_info(&self, index: usize, extrinsic: String, width: u16) -> Line {
|
fn prepare_ext_line_info(
|
||||||
let index_len = index.checked_ilog10().unwrap_or(0) + 1;
|
&self,
|
||||||
let len_for_ext = width.saturating_sub(index_len as u16 + 17) as usize;
|
index: usize,
|
||||||
let len_extrinsic_hash = extrinsic.len();
|
width: u16,
|
||||||
Line::from(format!("{} MODULE METHOD {}",
|
pallet_name: &str,
|
||||||
index,
|
variant_name: &str,
|
||||||
format!("{}...{}", &extrinsic[..len_for_ext], &extrinsic[len_extrinsic_hash - len_for_ext..])))
|
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> {
|
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 normal_style = self.palette.create_text_style(false);
|
||||||
let active_style = self.palette.create_text_style(true);
|
let active_style = self.palette.create_text_style(true);
|
||||||
|
|
||||||
if let Some((_, used_block_number)) = self.used_block_index {
|
if let Some(used_block_number) = self.used_block_number {
|
||||||
let hash = self.blocks
|
let default_hash = H256::repeat_byte(69u8);
|
||||||
.iter()
|
let hash = self.block_headers
|
||||||
.find(|b| b.block_number == used_block_number)
|
.get(&used_block_number)
|
||||||
.map(|b| b.hash.clone())
|
.unwrap_or(&default_hash);
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
if let Some(exts) = self.extrinsics.get(&hash) {
|
if let Some(exts) = self.extrinsics.get(&hash) {
|
||||||
for (index, ext) in exts.iter().enumerate() {
|
for (index, ext) in exts.iter().enumerate() {
|
||||||
if total_length == 0 { break; }
|
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 }
|
if index == used_ext_index { active_style } else { normal_style }
|
||||||
} else {
|
} else {
|
||||||
normal_style
|
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;
|
total_length -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
items
|
items
|
||||||
@ -271,17 +269,20 @@ impl ExplorerBlocks {
|
|||||||
fn prepare_event_lines(&mut self, rect: Rect) -> Line {
|
fn prepare_event_lines(&mut self, rect: Rect) -> Line {
|
||||||
let _width = rect.as_size().width;
|
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 {
|
let details = exts
|
||||||
if let Some(exts) = &self.extrinsics.get(&hash.clone()) {
|
.get(used_index)
|
||||||
Line::from(format!("{}", exts.get(*ext_index).unwrap_or(&String::from("nothing here")))).style(style)
|
.map_or(Vec::new(), |ext| ext.field_bytes.clone());
|
||||||
} else {
|
|
||||||
Line::from("nothing here")
|
Line::from(format!("{}", hex::encode(&details)))
|
||||||
}
|
},
|
||||||
} else {
|
_ => Line::from(""),
|
||||||
Line::from("nothing here")
|
}.style(self.palette.create_text_style(false))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_right(&mut self) {
|
fn move_right(&mut self) {
|
||||||
@ -316,31 +317,21 @@ impl ExplorerBlocks {
|
|||||||
|
|
||||||
fn move_up_extrinsics(&mut self) {
|
fn move_up_extrinsics(&mut self) {
|
||||||
match &self.used_ext_index {
|
match &self.used_ext_index {
|
||||||
Some((header, block_index, used_index)) => {
|
Some((header, used_index)) => {
|
||||||
if *used_index == 0 { return }
|
let new_index = used_index.saturating_sub(1);
|
||||||
let new_index = used_index - 1;
|
if let Some(exts) = self.extrinsics.get(header) {
|
||||||
|
if exts.get(new_index).is_some() {
|
||||||
let maybe_exts = self.extrinsics.get(&*header);
|
self.used_ext_index = Some((*header, new_index));
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
self.used_ext_index = self.blocks
|
self.used_ext_index = self.used_block_number
|
||||||
.front()
|
.map(|block_number| {
|
||||||
.map(|block| {
|
let header = self.block_headers
|
||||||
self.extrinsics
|
.get(&block_number)
|
||||||
.get(&block.hash)
|
.expect("header exists for each block number; qed");
|
||||||
.map(|_| (block.hash.clone(), 0, 0))
|
self.extrinsics.get(&header).map(|_| (*header, 0usize))
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
}
|
}
|
||||||
@ -348,44 +339,35 @@ impl ExplorerBlocks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn move_up_blocks(&mut self) {
|
fn move_up_blocks(&mut self) {
|
||||||
self.used_block_index = match &self.used_block_index {
|
self.used_block_number = match &self.used_block_number {
|
||||||
Some((used_index, used_block)) => {
|
Some(block_number) => {
|
||||||
Some(self.blocks
|
Some(self.blocks
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.find(|info| info.block_number == block_number + 1)
|
||||||
.find(|(_, b)| b.block_number == used_block + 1)
|
.map(|info| info.block_number)
|
||||||
.map(|(idx, b)| (idx, b.block_number))
|
.unwrap_or(*block_number))
|
||||||
.unwrap_or((*used_index, *used_block)))
|
|
||||||
},
|
},
|
||||||
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) {
|
fn move_down_extrinsics(&mut self) {
|
||||||
match &self.used_ext_index {
|
match &self.used_ext_index {
|
||||||
Some((header, block_index, used_index)) => {
|
Some((header, used_index)) => {
|
||||||
let new_index = used_index + 1;
|
let new_index = used_index + 1;
|
||||||
|
if let Some(exts) = self.extrinsics.get(&header) {
|
||||||
let maybe_exts = self.extrinsics.get(&*header);
|
if new_index < exts.len() && exts.get(new_index).is_some() {
|
||||||
if maybe_exts.is_none() { return }
|
self.used_ext_index = Some((*header, new_index));
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
self.used_ext_index = self.blocks
|
self.used_ext_index = self.used_block_number
|
||||||
.front()
|
.map(|block_number| {
|
||||||
.map(|block| {
|
let header = self.block_headers
|
||||||
self.extrinsics
|
.get(&block_number)
|
||||||
.get(&block.hash)
|
.expect("header exists for each block number; qed");
|
||||||
.map(|_| (block.hash.clone(), 0, 0))
|
self.extrinsics.get(&header).map(|_| (*header, 0usize))
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
}
|
}
|
||||||
@ -393,17 +375,16 @@ impl ExplorerBlocks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn move_down_blocks(&mut self) {
|
fn move_down_blocks(&mut self) {
|
||||||
self.used_block_index = match &self.used_block_index {
|
self.used_block_number = match &self.used_block_number {
|
||||||
Some((used_index, used_block)) => {
|
Some(block_number) => {
|
||||||
Some(self.blocks
|
Some(self.blocks
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.find(|info| info.block_number == block_number.saturating_sub(1))
|
||||||
.find(|(_, b)| b.block_number == used_block.saturating_sub(1))
|
.map(|info| info.block_number)
|
||||||
.map(|(idx, b)| (idx, b.block_number))
|
.unwrap_or(*block_number))
|
||||||
.unwrap_or((*used_index, *used_block)))
|
|
||||||
},
|
},
|
||||||
None => {
|
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('l') | KeyCode::Right if self.is_active => self.move_right(),
|
||||||
KeyCode::Char('h') | KeyCode::Left if self.is_active => self.move_left(),
|
KeyCode::Char('h') | KeyCode::Left if self.is_active => self.move_left(),
|
||||||
KeyCode::Esc => {
|
KeyCode::Esc => {
|
||||||
self.used_block_index = None;
|
self.used_block_number = None;
|
||||||
self.used_ext_index = None;
|
self.used_ext_index = None;
|
||||||
self.used_paragraph_index = 0;
|
self.used_paragraph_index = 0;
|
||||||
},
|
},
|
||||||
@ -490,9 +471,9 @@ impl Component for ExplorerBlocks {
|
|||||||
|
|
||||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||||
match action {
|
match action {
|
||||||
Action::SetLatestBlock(hash, block) => self.update_latest_block_info(hash, block.header.number, block.header.digest.logs, block.extrinsics)?,
|
Action::BestBlockInformation(hash, block_number, extrinsics) => self.update_latest_block_info(hash, block_number, extrinsics)?,
|
||||||
Action::SetFinalizedBlock(hash, block) => self.update_finalized_block_info(hash, block.header.number, block.header.digest.logs, block.extrinsics)?,
|
Action::FinalizedBlockInformation(hash, block_number, extrinsics) => self.update_finalized_block_info(hash, block_number, extrinsics)?,
|
||||||
Action::SetValidators(validators) => self.update_validator_list(validators)?,
|
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(Mode::ExplorerActive) if !self.is_active => self.set_active()?,
|
||||||
Action::SetMode(_) if self.is_active => self.unset_active()?,
|
Action::SetMode(_) if self.is_active => self.unset_active()?,
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -44,21 +44,18 @@ impl ExtrinsicsChart {
|
|||||||
.text_value(ext_len.to_string())
|
.text_value(ext_len.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_extrinsics(&mut self, block_number: String, extrinsics_number: usize) -> Result<()> {
|
fn update_extrinsics_length(&mut self, block_number: u32, extrinsics_length: usize) -> Result<()> {
|
||||||
let block_number = block_number.trim_start_matches("0x");
|
|
||||||
let block_number = u32::from_str_radix(&block_number, 16)?;
|
|
||||||
|
|
||||||
match self.extrinsics.back() {
|
match self.extrinsics.back() {
|
||||||
Some(back) => {
|
Some(back) => {
|
||||||
if back.0 < block_number {
|
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 {
|
if self.extrinsics.len() > Self::MAX_LEN {
|
||||||
let _ = self.extrinsics.pop_front();
|
let _ = self.extrinsics.pop_front();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => {
|
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 {
|
impl Component for ExtrinsicsChart {
|
||||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||||
match 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)
|
Ok(None)
|
||||||
|
@ -15,9 +15,8 @@ pub struct FinalizedBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FinalizedBlock {
|
impl FinalizedBlock {
|
||||||
fn update_block_number(&mut self, number_hex_str: String) -> Result<()> {
|
fn update_block_number(&mut self, number: u32) -> Result<()> {
|
||||||
let number_hex_str = number_hex_str.trim_start_matches("0x");
|
self.number = number;
|
||||||
self.number = u32::from_str_radix(&number_hex_str, 16)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -25,7 +24,7 @@ impl FinalizedBlock {
|
|||||||
impl Component for FinalizedBlock {
|
impl Component for FinalizedBlock {
|
||||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||||
match action {
|
match action {
|
||||||
Action::SetFinalizedBlock(_, block) => self.update_block_number(block.header.number)?,
|
Action::NewFinalizedBlock(number) => self.update_block_number(number)?,
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@ -48,7 +47,7 @@ impl Component for FinalizedBlock {
|
|||||||
.border_type(border_type)
|
.border_type(border_type)
|
||||||
.title_alignment(Alignment::Right)
|
.title_alignment(Alignment::Right)
|
||||||
.title_style(self.palette.create_title_style())
|
.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"))
|
.title("Latest"))
|
||||||
.alignment(Alignment::Center)
|
.alignment(Alignment::Center)
|
||||||
.wrap(Wrap { trim: true });
|
.wrap(Wrap { trim: true });
|
||||||
@ -73,7 +72,7 @@ impl Component for FinalizedBlock {
|
|||||||
.wrap(Wrap { trim: true });
|
.wrap(Wrap { trim: true });
|
||||||
frame.render_widget(paragraph, place);
|
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 })
|
let place = place.offset(ratatui::layout::Offset { x: 1, y: height_offset as i32 })
|
||||||
.intersection(place);
|
.intersection(place);
|
||||||
frame.render_widget(big_text, place);
|
frame.render_widget(big_text, place);
|
||||||
|
@ -15,9 +15,8 @@ pub struct LatestBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl LatestBlock {
|
impl LatestBlock {
|
||||||
fn update_block_number(&mut self, number_hex_str: String) -> Result<()> {
|
fn update_block_number(&mut self, number: u32) -> Result<()> {
|
||||||
let number_hex_str = number_hex_str.trim_start_matches("0x");
|
self.number = number;
|
||||||
self.number = u32::from_str_radix(&number_hex_str, 16)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -25,7 +24,7 @@ impl LatestBlock {
|
|||||||
impl Component for LatestBlock {
|
impl Component for LatestBlock {
|
||||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||||
match action {
|
match action {
|
||||||
Action::SetLatestBlock(_, block) => self.update_block_number(block.header.number)?,
|
Action::NewBestBlock(number) => self.update_block_number(number)?,
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@ -48,7 +47,7 @@ impl Component for LatestBlock {
|
|||||||
.border_type(border_type)
|
.border_type(border_type)
|
||||||
.title_alignment(Alignment::Right)
|
.title_alignment(Alignment::Right)
|
||||||
.title_style(self.palette.create_title_style())
|
.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"))
|
.title("Latest"))
|
||||||
.alignment(Alignment::Center)
|
.alignment(Alignment::Center)
|
||||||
.wrap(Wrap { trim: true });
|
.wrap(Wrap { trim: true });
|
||||||
@ -73,7 +72,7 @@ impl Component for LatestBlock {
|
|||||||
.wrap(Wrap { trim: true });
|
.wrap(Wrap { trim: true });
|
||||||
frame.render_widget(paragraph, place);
|
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 })
|
let place = place.offset(ratatui::layout::Offset { x: 1, y: height_offset as i32 })
|
||||||
.intersection(place);
|
.intersection(place);
|
||||||
frame.render_widget(big_text, place);
|
frame.render_widget(big_text, place);
|
||||||
|
@ -16,7 +16,7 @@ use crate::{
|
|||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Health {
|
pub struct Health {
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
peers: Option<u32>,
|
peers: Option<usize>,
|
||||||
is_syncing: bool,
|
is_syncing: bool,
|
||||||
should_have_peers: bool,
|
should_have_peers: bool,
|
||||||
tx_pool_length: usize,
|
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.peers = peers;
|
||||||
self.is_syncing = is_syncing;
|
self.is_syncing = is_syncing;
|
||||||
self.should_have_peers = should_have_peers;
|
self.should_have_peers = should_have_peers;
|
||||||
@ -84,7 +89,7 @@ impl Health {
|
|||||||
impl Component for Health {
|
impl Component for Health {
|
||||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||||
match 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)?
|
self.set_sync_state(peers, is_syncing, should_have_peers)?
|
||||||
},
|
},
|
||||||
Action::SetNodeName(name) => self.set_node_name(name)?,
|
Action::SetNodeName(name) => self.set_node_name(name)?,
|
||||||
|
@ -5,6 +5,7 @@ use ratatui::{
|
|||||||
widgets::{Block, Paragraph, Wrap},
|
widgets::{Block, Paragraph, Wrap},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
use primitive_types::H256;
|
||||||
|
|
||||||
use super::Component;
|
use super::Component;
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -13,7 +14,7 @@ use crate::{
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct Version {
|
pub struct Version {
|
||||||
genesis_hash: Option<String>,
|
genesis_hash: Option<H256>,
|
||||||
node_version: Option<String>,
|
node_version: Option<String>,
|
||||||
chain_name: Option<String>,
|
chain_name: Option<String>,
|
||||||
palette: StylePalette,
|
palette: StylePalette,
|
||||||
@ -30,18 +31,15 @@ impl Version {
|
|||||||
Ok(())
|
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;
|
self.genesis_hash = genesis_hash;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepared_genesis_hash(&self) -> String {
|
fn prepared_genesis_hash(&self) -> String {
|
||||||
if self.genesis_hash.is_some() {
|
match self.genesis_hash {
|
||||||
let genesis_hash = self.genesis_hash.clone().unwrap();
|
Some(genesis_hash) => genesis_hash.to_string(),
|
||||||
let len = genesis_hash.len();
|
None => OghamCenter::default().to_string(),
|
||||||
format!("Genesis: {}...{}", &genesis_hash[0..4], &genesis_hash[len-6..])
|
|
||||||
} else {
|
|
||||||
OghamCenter::default().to_string()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,9 +47,9 @@ impl Version {
|
|||||||
impl Component for Version {
|
impl Component for Version {
|
||||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||||
match action {
|
match action {
|
||||||
Action::SetChainName(name) => self.set_chain_name(name)?,
|
Action::SetChainName(maybe_name) => self.set_chain_name(maybe_name)?,
|
||||||
Action::SetNodeVersion(version) => self.set_node_version(version)?,
|
Action::SetChainVersion(version) => self.set_node_version(version)?,
|
||||||
Action::SetGenesisHash(genesis) => self.set_genesis_hash(genesis)?,
|
Action::SetGenesisHash(maybe_genesis) => self.set_genesis_hash(maybe_genesis)?,
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
63
src/main.rs
63
src/main.rs
@ -1,5 +1,9 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
|
use subxt::{
|
||||||
|
OnlineClient,
|
||||||
|
backend::{legacy::LegacyRpcMethods, rpc::RpcClient},
|
||||||
|
};
|
||||||
|
|
||||||
mod action;
|
mod action;
|
||||||
mod app;
|
mod app;
|
||||||
@ -13,9 +17,15 @@ mod network;
|
|||||||
mod widgets;
|
mod widgets;
|
||||||
mod types;
|
mod types;
|
||||||
mod palette;
|
mod palette;
|
||||||
|
mod casper;
|
||||||
|
|
||||||
|
use casper::{CasperAccountId, CasperConfig};
|
||||||
|
|
||||||
|
#[subxt::subxt(runtime_metadata_path = "./artifacts/casper.scale")]
|
||||||
|
pub mod casper_network {}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn start_tokio(
|
async fn start_tokio_action_loop(
|
||||||
io_rx: std::sync::mpsc::Receiver<action::Action>,
|
io_rx: std::sync::mpsc::Receiver<action::Action>,
|
||||||
network: &mut network::Network,
|
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]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
crate::errors::init()?;
|
crate::errors::init()?;
|
||||||
@ -33,12 +53,45 @@ async fn main() -> Result<()> {
|
|||||||
let (sync_io_tx, sync_io_rx) = std::sync::mpsc::channel();
|
let (sync_io_tx, sync_io_rx) = std::sync::mpsc::channel();
|
||||||
let (action_tx, action_rx) = tokio::sync::mpsc::unbounded_channel();
|
let (action_tx, action_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||||
|
|
||||||
|
let rpc_client = RpcClient::from_url(args.rpc_endpoint).await?;
|
||||||
|
let 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();
|
let cloned_action_tx = action_tx.clone();
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let mut network = network::Network::new(cloned_action_tx)
|
let mut network = network::Network::new(
|
||||||
.with_url(&args.rpc_endpoint)
|
cloned_action_tx,
|
||||||
.with_timeout(args.timeout);
|
online_client,
|
||||||
start_tokio(sync_io_rx, &mut network);
|
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)?
|
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 tokio::sync::mpsc::UnboundedSender;
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use reqwest::Client;
|
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use rand::RngCore;
|
use subxt::{
|
||||||
use serde::{Deserialize, de::DeserializeOwned};
|
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;
|
use crate::{
|
||||||
mod health_check;
|
action::Action,
|
||||||
mod node_name;
|
casper::CasperConfig,
|
||||||
mod genesis_hash;
|
};
|
||||||
mod chain_name;
|
|
||||||
mod node_version;
|
|
||||||
mod latest_block;
|
|
||||||
mod finalized_block;
|
|
||||||
mod current_epoch;
|
|
||||||
mod validators;
|
|
||||||
mod tx_pool;
|
|
||||||
|
|
||||||
pub use active_era::ActiveEraRequest;
|
pub use subscriptions::{FinalizedSubscription, BestSubscription};
|
||||||
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;
|
|
||||||
|
|
||||||
static CLIENT: Lazy<Arc<Client>> = Lazy::new(|| Arc::new(Client::new()));
|
pub struct Network {
|
||||||
const DEFAULT_URL: &str = "http://localhost:9945";
|
action_tx: UnboundedSender<Action>,
|
||||||
|
online_client_api: OnlineClient<CasperConfig>,
|
||||||
pub type AppActionSender = UnboundedSender<Action>;
|
legacy_client_api: LegacyRpcMethods<CasperConfig>,
|
||||||
|
rpc_client: RpcClient,
|
||||||
#[derive(Debug, Deserialize)]
|
best_hash: Option<H256>,
|
||||||
pub struct GhostResponse<ResponseType> {
|
finalized_hash: Option<H256>,
|
||||||
result: ResponseType,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
impl Network {
|
||||||
struct GhostRequestBuilder<'a> {
|
pub fn new(
|
||||||
action_tx: Option<AppActionSender>,
|
action_tx: UnboundedSender<Action>,
|
||||||
id: u32,
|
online_client_api: OnlineClient<CasperConfig>,
|
||||||
url: &'a str,
|
legacy_client_api: LegacyRpcMethods<CasperConfig>,
|
||||||
timeout: std::time::Duration,
|
rpc_client: RpcClient,
|
||||||
}
|
) -> Self {
|
||||||
|
|
||||||
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 {
|
|
||||||
Self {
|
Self {
|
||||||
action_tx,
|
action_tx,
|
||||||
timeout: Default::default(),
|
online_client_api,
|
||||||
internal_randomness: rand::thread_rng(),
|
legacy_client_api,
|
||||||
url: DEFAULT_URL,
|
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<()> {
|
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 {
|
match io_event {
|
||||||
Action::GetSyncState => HealthCheckRequest(request).send().await,
|
Action::NewBestHash(hash) => {
|
||||||
Action::GetNodeName => NodeNameRequest(request).send().await,
|
self.best_hash = Some(hash);
|
||||||
Action::GetGenesisHash => GenesisHashRequest(request).send().await,
|
Ok(())
|
||||||
Action::GetChainName => ChainNameRequest(request).send().await,
|
},
|
||||||
Action::GetNodeVersion => NodeVersionRequest(request).send().await,
|
Action::NewFinalizedHash(hash) => {
|
||||||
Action::GetLatestBlock => LatestBlockRequest(request).send().await,
|
self.finalized_hash = Some(hash);
|
||||||
Action::GetFinalizedBlock => FinalizedBlockRequest(request).send().await,
|
Ok(())
|
||||||
Action::GetActiveEra => ActiveEraRequest(request).send().await,
|
},
|
||||||
Action::GetEpoch => CurrentEpochRequest(request).send().await,
|
Action::GetSystemHealth => legacy_rpc_calls::get_system_health(&self.action_tx, &self.legacy_client_api).await,
|
||||||
Action::GetValidators => ValidatorsRequest(request).send().await,
|
Action::GetNodeName => legacy_rpc_calls::get_node_name(&self.action_tx, &self.legacy_client_api).await,
|
||||||
Action::GetPendingExtrinsics => TxPoolRequest(request).send().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(())
|
_ => 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),
|
Key(KeyEvent),
|
||||||
Mouse(MouseEvent),
|
Mouse(MouseEvent),
|
||||||
Resize(u16, u16),
|
Resize(u16, u16),
|
||||||
|
Oneshot,
|
||||||
Node,
|
Node,
|
||||||
FastNode,
|
|
||||||
Runtime,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Tui {
|
pub struct Tui {
|
||||||
@ -97,9 +96,8 @@ impl Tui {
|
|||||||
let mut tick_interval = interval(Duration::from_secs_f64(1.0 / tick_rate));
|
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 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 oneshot_node_interval = interval(Duration::from_secs_f64(86_400.0));
|
||||||
let mut fast_node_interval = interval(Duration::from_secs_f64(9.0));
|
let mut fast_node_interval = interval(Duration::from_secs_f64(2.0));
|
||||||
let mut runtime_interval = interval(Duration::from_secs_f64(1.69));
|
|
||||||
|
|
||||||
event_tx
|
event_tx
|
||||||
.send(Event::Init)
|
.send(Event::Init)
|
||||||
@ -112,9 +110,8 @@ impl Tui {
|
|||||||
},
|
},
|
||||||
_ = tick_interval.tick() => Event::Tick,
|
_ = tick_interval.tick() => Event::Tick,
|
||||||
_ = render_interval.tick() => Event::Render,
|
_ = render_interval.tick() => Event::Render,
|
||||||
_ = normal_node_interval.tick() => Event::Node,
|
_ = oneshot_node_interval.tick() => Event::Oneshot,
|
||||||
_ = fast_node_interval.tick() => Event::FastNode,
|
_ = fast_node_interval.tick() => Event::Node,
|
||||||
_ = runtime_interval.tick() => Event::Runtime,
|
|
||||||
crossterm_event = event_stream.next().fuse() => match crossterm_event {
|
crossterm_event = event_stream.next().fuse() => match crossterm_event {
|
||||||
Some(Ok(event)) => match event {
|
Some(Ok(event)) => match event {
|
||||||
CrosstermEvent::Key(key) if key.kind == KeyEventKind::Press => Event::Key(key),
|
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;
|
mod era;
|
||||||
pub mod params;
|
mod extrinsics;
|
||||||
pub mod storage;
|
|
||||||
pub mod macros;
|
pub use extrinsics::CasperExtrinsicDetails;
|
||||||
pub mod era;
|
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