diff --git a/README.md b/README.md index 6000c80..f770422 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,29 @@ This repository contains the backend ingestion server for Substrate Telemetry (w The backend is a Rust project and the frontend is React/Typescript project. +Substrate based nodes can be connected to an arbitrary Telemetry backend using the `--telemetry-url` (see below for more complete instructions on how to get this up and running). + +### Messages + +Depending on the configured verbosity, substrate nodes will send different types of messages to the Telemetry server. Verbosity level `0` is sufficient to provide the Telemetry server with almost all of the node information needed. Using this verbosity level will lead to the substrate node sending the following message types to Telemetry: + +``` +system.connected +system.interval +block.import +notify.finalized +``` + +Increasing the verbosity level to 1 will lead to additional "consensus info" messages being sent, one of which has the identifier: + +``` +afg.authority_set +``` + +Which we use to populate the "validator address" field if applicable. + +Increasing the verbosity level beyond 1 is unnecessary, and will not result in any additional messages that Telemetry can handle (but other metric gathering systems might find them useful). + ## Getting Started To run the backend, you will need `cargo` to build the binary. We recommend using [`rustup`](https://rustup.rs/). diff --git a/backend/common/src/node_message.rs b/backend/common/src/node_message.rs index a966936..1448ffd 100644 --- a/backend/common/src/node_message.rs +++ b/backend/common/src/node_message.rs @@ -59,15 +59,7 @@ pub enum Payload { SystemInterval(SystemInterval), BlockImport(Block), NotifyFinalized(Finalized), - TxPoolImport, - AfgFinalized(AfgFinalized), - AfgReceivedPrecommit(AfgReceived), - AfgReceivedPrevote(AfgReceived), - AfgReceivedCommit(AfgReceived), AfgAuthoritySet(AfgAuthoritySet), - AfgFinalizedBlocksUpTo, - AuraPreSealedBlock, - PreparedBlockForProposing, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -94,19 +86,6 @@ pub struct Finalized { pub height: Box, } -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct AfgFinalized { - pub finalized_hash: BlockHash, - pub finalized_number: Box, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct AfgReceived { - pub target_hash: BlockHash, - pub target_number: Box, - pub voter: Option>, -} - #[derive(Serialize, Deserialize, Debug, Clone)] pub struct AfgAuthoritySet { pub authority_id: Box, @@ -210,34 +189,6 @@ mod tests { }); } - #[test] - fn bincode_can_serialize_and_deserialize_node_message_tx_pool_import() { - bincode_can_serialize_and_deserialize(NodeMessage::V1 { - payload: Payload::TxPoolImport, - }); - } - - #[test] - fn bincode_can_serialize_and_deserialize_node_message_afg_finalized() { - bincode_can_serialize_and_deserialize(NodeMessage::V1 { - payload: Payload::AfgFinalized(AfgFinalized { - finalized_hash: BlockHash::zero(), - finalized_number: "foo".into(), - }), - }); - } - - #[test] - fn bincode_can_serialize_and_deserialize_node_message_afg_received() { - bincode_can_serialize_and_deserialize(NodeMessage::V1 { - payload: Payload::AfgReceivedPrecommit(AfgReceived { - target_hash: BlockHash::zero(), - target_number: "foo".into(), - voter: None, - }), - }); - } - #[test] fn bincode_can_serialize_and_deserialize_node_message_afg_authority_set() { bincode_can_serialize_and_deserialize(NodeMessage::V1 { diff --git a/backend/telemetry_core/src/aggregator/inner_loop.rs b/backend/telemetry_core/src/aggregator/inner_loop.rs index 8aaf632..ef6973e 100644 --- a/backend/telemetry_core/src/aggregator/inner_loop.rs +++ b/backend/telemetry_core/src/aggregator/inner_loop.rs @@ -25,7 +25,7 @@ use common::{ node_types::BlockHash, time, MultiMapUnique, }; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::sync::{ atomic::{AtomicU64, Ordering}, Arc, @@ -95,10 +95,6 @@ pub enum FromFeedWebsocket { /// The feed can subscribe to a chain to receive /// messages relating to it. Subscribe { chain: BlockHash }, - /// The feed wants finality info for the chain, too. - SendFinality, - /// The feed doesn't want any more finality info for the chain. - NoMoreFinality, /// An explicit ping message. Ping { value: Box }, /// The feed is disconnected. @@ -114,8 +110,6 @@ pub struct Metrics { pub chains_subscribed_to: usize, /// Number of subscribed feeds. pub subscribed_feeds: usize, - /// Number of subscribed feeds that also asked for finality information. - pub subscribed_finality_feeds: usize, /// How many messages are currently queued up in internal channels /// waiting to be sent out to feeds. pub total_messages_to_feeds: usize, @@ -148,8 +142,6 @@ impl FromStr for FromFeedWebsocket { "subscribe" => Ok(FromFeedWebsocket::Subscribe { chain: value.parse()?, }), - "send-finality" => Ok(FromFeedWebsocket::SendFinality), - "no-more-finality" => Ok(FromFeedWebsocket::NoMoreFinality), _ => return Err(anyhow::anyhow!("Command {} not recognised", cmd)), } } @@ -178,9 +170,6 @@ pub struct InnerLoop { /// Which feeds are subscribed to a given chain? chain_to_feed_conn_ids: MultiMapUnique, - /// These feeds want finality info, too. - feed_conn_id_finality: HashSet, - /// Send messages here to make geographical location requests. tx_to_locator: flume::Sender<(NodeId, Ipv4Addr)>, @@ -203,7 +192,6 @@ impl InnerLoop { feed_channels: HashMap::new(), shard_channels: HashMap::new(), chain_to_feed_conn_ids: MultiMapUnique::new(), - feed_conn_id_finality: HashSet::new(), tx_to_locator, max_queue_len, } @@ -282,7 +270,6 @@ impl InnerLoop { let connected_nodes = self.node_ids.len(); let subscribed_feeds = self.chain_to_feed_conn_ids.num_values(); let chains_subscribed_to = self.chain_to_feed_conn_ids.num_keys(); - let subscribed_finality_feeds = self.feed_conn_id_finality.len(); let connected_shards = self.shard_channels.len(); let connected_feeds = self.feed_channels.len(); let total_messages_to_feeds: usize = self.feed_channels.values().map(|c| c.len()).sum(); @@ -292,7 +279,6 @@ impl InnerLoop { timestamp_unix_ms, chains_subscribed_to, subscribed_feeds, - subscribed_finality_feeds, total_messages_to_feeds, current_messages_to_aggregator, total_messages_to_aggregator, @@ -429,23 +415,15 @@ impl InnerLoop { }; let mut feed_message_serializer = FeedMessageSerializer::new(); - let broadcast_finality = - self.node_state - .update_node(node_id, payload, &mut feed_message_serializer); + self.node_state + .update_node(node_id, payload, &mut feed_message_serializer); if let Some(chain) = self.node_state.get_chain_by_node_id(node_id) { let genesis_hash = chain.genesis_hash(); - if broadcast_finality { - self.finalize_and_broadcast_to_chain_finality_feeds( - &genesis_hash, - feed_message_serializer, - ); - } else { - self.finalize_and_broadcast_to_chain_feeds( - &genesis_hash, - feed_message_serializer, - ); - } + self.finalize_and_broadcast_to_chain_feeds( + &genesis_hash, + feed_message_serializer, + ); } } FromShardWebsocket::Disconnected => { @@ -509,9 +487,6 @@ impl InnerLoop { // Unsubscribe from previous chain if subscribed to one: let old_genesis_hash = self.chain_to_feed_conn_ids.remove_value(&feed_conn_id); - // Untoggle request for finality feeds: - self.feed_conn_id_finality.remove(&feed_conn_id); - // Get old chain if there was one: let node_state = &self.node_state; let old_chain = @@ -583,17 +558,10 @@ impl InnerLoop { self.chain_to_feed_conn_ids .insert(new_genesis_hash, feed_conn_id); } - FromFeedWebsocket::SendFinality => { - self.feed_conn_id_finality.insert(feed_conn_id); - } - FromFeedWebsocket::NoMoreFinality => { - self.feed_conn_id_finality.remove(&feed_conn_id); - } FromFeedWebsocket::Disconnected => { // The feed has disconnected; clean up references to it: self.chain_to_feed_conn_ids.remove_value(&feed_conn_id); self.feed_channels.remove(&feed_conn_id); - self.feed_conn_id_finality.remove(&feed_conn_id); } } } @@ -706,32 +674,4 @@ impl InnerLoop { let _ = chan.send(message.clone()); } } - - /// Finalize a [`FeedMessageSerializer`] and broadcast the result to chain finality feeds - fn finalize_and_broadcast_to_chain_finality_feeds( - &mut self, - genesis_hash: &BlockHash, - serializer: FeedMessageSerializer, - ) { - if let Some(bytes) = serializer.into_finalized() { - self.broadcast_to_chain_finality_feeds(genesis_hash, ToFeedWebsocket::Bytes(bytes)); - } - } - - /// Send a message to all chain finality feeds. - fn broadcast_to_chain_finality_feeds( - &mut self, - genesis_hash: &BlockHash, - message: ToFeedWebsocket, - ) { - if let Some(feeds) = self.chain_to_feed_conn_ids.get_values(genesis_hash) { - // Get all feeds for the chain, but only broadcast to those feeds that - // are also subscribed to receive finality updates. - for &feed_id in feeds.union(&self.feed_conn_id_finality) { - if let Some(chan) = self.feed_channels.get_mut(&feed_id) { - let _ = chan.send(message.clone()); - } - } - } - } } diff --git a/backend/telemetry_core/src/feed_message.rs b/backend/telemetry_core/src/feed_message.rs index 99c8869..7b63274 100644 --- a/backend/telemetry_core/src/feed_message.rs +++ b/backend/telemetry_core/src/feed_message.rs @@ -25,7 +25,6 @@ use common::node_types::{ }; use serde_json::to_writer; -type Address = Box; type FeedNodeId = usize; pub trait FeedMessage { @@ -119,10 +118,8 @@ actions! { 13: SubscribedTo, 14: UnsubscribedFrom, 15: Pong<'_>, - 16: AfgFinalized, - 17: AfgReceivedPrevote, - 18: AfgReceivedPrecommit, - 19: AfgAuthoritySet, + // Note; some now-unused messages were removed between IDs 15 and 20. + // We maintain existing IDs for backward compatibility. 20: StaleNode, 21: NodeIOUpdate<'_>, } @@ -177,34 +174,6 @@ pub struct UnsubscribedFrom(pub BlockHash); #[derive(Serialize)] pub struct Pong<'a>(pub &'a str); -#[derive(Serialize)] -pub struct AfgFinalized(pub Address, pub BlockNumber, pub BlockHash); - -#[derive(Serialize)] -pub struct AfgReceivedPrevote( - pub Address, - pub BlockNumber, - pub BlockHash, - pub Option
, -); - -#[derive(Serialize)] -pub struct AfgReceivedPrecommit( - pub Address, - pub BlockNumber, - pub BlockHash, - pub Option
, -); - -#[derive(Serialize)] -pub struct AfgAuthoritySet( - pub Address, - pub Address, - pub Address, - pub BlockNumber, - pub BlockHash, -); - #[derive(Serialize)] pub struct StaleNode(pub FeedNodeId); diff --git a/backend/telemetry_core/src/main.rs b/backend/telemetry_core/src/main.rs index 977b048..b4bd918 100644 --- a/backend/telemetry_core/src/main.rs +++ b/backend/telemetry_core/src/main.rs @@ -539,11 +539,6 @@ async fn return_prometheus_metrics(aggregator: AggregatorSet) -> Response. use common::node_message::Payload; +use common::node_types::BlockHash; use common::node_types::{Block, Timestamp}; -use common::node_types::{BlockHash, BlockNumber}; use common::{id_type, time, DenseMap, MostSeen, NumStats}; use once_cell::sync::Lazy; use std::collections::HashSet; @@ -149,13 +149,12 @@ impl Chain { } /// Attempt to update the best block seen in this chain. - /// Returns a boolean which denotes whether the output is for finalization feeds (true) or not (false). pub fn update_node( &mut self, nid: ChainNodeId, payload: Payload, feed: &mut FeedMessageSerializer, - ) -> bool { + ) { if let Some(block) = payload.best_block() { self.handle_block(block, nid, feed); } @@ -163,65 +162,26 @@ impl Chain { if let Some(node) = self.nodes.get_mut(nid) { match payload { Payload::SystemInterval(ref interval) => { + // Send a feed message if any of the relevant node details change: if node.update_hardware(interval) { feed.push(feed_message::Hardware(nid.into(), node.hardware())); } - if let Some(stats) = node.update_stats(interval) { feed.push(feed_message::NodeStatsUpdate(nid.into(), stats)); } - if let Some(io) = node.update_io(interval) { feed.push(feed_message::NodeIOUpdate(nid.into(), io)); } } Payload::AfgAuthoritySet(authority) => { - node.set_validator_address(authority.authority_id.clone()); - return false; - } - Payload::AfgFinalized(finalized) => { - if let Ok(finalized_number) = finalized.finalized_number.parse::() - { - if let Some(addr) = node.details().validator.clone() { - feed.push(feed_message::AfgFinalized( - addr, - finalized_number, - finalized.finalized_hash, - )); - } + // If our node validator address (and thus details) change, send an + // updated "add node" feed message: + if node.set_validator_address(authority.authority_id.clone()) { + feed.push(feed_message::AddedNode(nid.into(), &node)); } - return true; + return; } - Payload::AfgReceivedPrecommit(precommit) => { - if let Ok(finalized_number) = precommit.target_number.parse::() { - if let Some(addr) = node.details().validator.clone() { - let voter = precommit.voter.clone(); - feed.push(feed_message::AfgReceivedPrecommit( - addr, - finalized_number, - precommit.target_hash, - voter, - )); - } - } - return true; - } - Payload::AfgReceivedPrevote(prevote) => { - if let Ok(finalized_number) = prevote.target_number.parse::() { - if let Some(addr) = node.details().validator.clone() { - let voter = prevote.voter.clone(); - feed.push(feed_message::AfgReceivedPrevote( - addr, - finalized_number, - prevote.target_hash, - voter, - )); - } - } - return true; - } - Payload::AfgReceivedCommit(_) => {} - _ => (), + _ => {} } if let Some(block) = payload.finalized_block() { @@ -242,8 +202,6 @@ impl Chain { } } } - - false } fn handle_block(&mut self, block: &Block, nid: ChainNodeId, feed: &mut FeedMessageSerializer) { diff --git a/backend/telemetry_core/src/state/node.rs b/backend/telemetry_core/src/state/node.rs index 3853420..dbaa58b 100644 --- a/backend/telemetry_core/src/state/node.rs +++ b/backend/telemetry_core/src/state/node.rs @@ -213,8 +213,13 @@ impl Node { self.stale } - pub fn set_validator_address(&mut self, addr: Box) { - self.details.validator = Some(addr); + pub fn set_validator_address(&mut self, addr: Box) -> bool { + if self.details.validator.as_ref() == Some(&addr) { + false + } else { + self.details.validator = Some(addr); + true + } } pub fn startup_time(&self) -> Option { diff --git a/backend/telemetry_core/src/state/state.rs b/backend/telemetry_core/src/state/state.rs index e5ca5ec..6337739 100644 --- a/backend/telemetry_core/src/state/state.rs +++ b/backend/telemetry_core/src/state/state.rs @@ -213,18 +213,17 @@ impl State { } /// Attempt to update the best block seen, given a node and block. - /// Returns a boolean which denotes whether the output is for finalization feeds (true) or not (false). pub fn update_node( &mut self, NodeId(chain_id, chain_node_id): NodeId, payload: Payload, feed: &mut FeedMessageSerializer, - ) -> bool { + ) { let chain = match self.chains.get_mut(chain_id) { Some(chain) => chain, None => { log::error!("Cannot find chain for node with ID {:?}", chain_id); - return false; + return; } }; diff --git a/backend/telemetry_shard/src/json_message/node_message.rs b/backend/telemetry_shard/src/json_message/node_message.rs index c45a90f..75468f5 100644 --- a/backend/telemetry_shard/src/json_message/node_message.rs +++ b/backend/telemetry_shard/src/json_message/node_message.rs @@ -74,24 +74,8 @@ pub enum Payload { BlockImport(Block), #[serde(rename = "notify.finalized")] NotifyFinalized(Finalized), - #[serde(rename = "txpool.import")] - TxPoolImport, - #[serde(rename = "afg.finalized")] - AfgFinalized(AfgFinalized), - #[serde(rename = "afg.received_precommit")] - AfgReceivedPrecommit(AfgReceived), - #[serde(rename = "afg.received_prevote")] - AfgReceivedPrevote(AfgReceived), - #[serde(rename = "afg.received_commit")] - AfgReceivedCommit(AfgReceived), #[serde(rename = "afg.authority_set")] AfgAuthoritySet(AfgAuthoritySet), - #[serde(rename = "afg.finalized_blocks_up_to")] - AfgFinalizedBlocksUpTo, - #[serde(rename = "aura.pre_sealed_block")] - AuraPreSealedBlock, - #[serde(rename = "prepared_block_for_proposing")] - PreparedBlockForProposing, } impl From for internal::Payload { @@ -101,15 +85,7 @@ impl From for internal::Payload { Payload::SystemInterval(m) => internal::Payload::SystemInterval(m.into()), Payload::BlockImport(m) => internal::Payload::BlockImport(m.into()), Payload::NotifyFinalized(m) => internal::Payload::NotifyFinalized(m.into()), - Payload::TxPoolImport => internal::Payload::TxPoolImport, - Payload::AfgFinalized(m) => internal::Payload::AfgFinalized(m.into()), - Payload::AfgReceivedPrecommit(m) => internal::Payload::AfgReceivedPrecommit(m.into()), - Payload::AfgReceivedPrevote(m) => internal::Payload::AfgReceivedPrevote(m.into()), - Payload::AfgReceivedCommit(m) => internal::Payload::AfgReceivedCommit(m.into()), Payload::AfgAuthoritySet(m) => internal::Payload::AfgAuthoritySet(m.into()), - Payload::AfgFinalizedBlocksUpTo => internal::Payload::AfgFinalizedBlocksUpTo, - Payload::AuraPreSealedBlock => internal::Payload::AuraPreSealedBlock, - Payload::PreparedBlockForProposing => internal::Payload::PreparedBlockForProposing, } } } @@ -191,38 +167,6 @@ impl From for internal::AfgAuthoritySet { } } -#[derive(Deserialize, Debug, Clone)] -pub struct AfgFinalized { - pub finalized_hash: Hash, - pub finalized_number: Box, -} - -impl From for internal::AfgFinalized { - fn from(msg: AfgFinalized) -> Self { - internal::AfgFinalized { - finalized_hash: msg.finalized_hash.into(), - finalized_number: msg.finalized_number, - } - } -} - -#[derive(Deserialize, Debug, Clone)] -pub struct AfgReceived { - pub target_hash: Hash, - pub target_number: Box, - pub voter: Option>, -} - -impl From for internal::AfgReceived { - fn from(msg: AfgReceived) -> Self { - internal::AfgReceived { - target_hash: msg.target_hash.into(), - target_number: msg.target_number, - voter: msg.voter, - } - } -} - #[derive(Deserialize, Debug, Clone, Copy)] pub struct Block { #[serde(rename = "best")] @@ -309,30 +253,6 @@ mod tests { ); } - #[test] - fn message_v2_received_precommit() { - let json = r#"{ - "id":1, - "ts":"2021-01-13T12:22:20.053527101+01:00", - "payload":{ - "target_hash":"0xcc41708573f2acaded9dd75e07dac2d4163d136ca35b3061c558d7a35a09dd8d", - "target_number":"209", - "voter":"foo", - "msg":"afg.received_precommit" - } - }"#; - assert!( - matches!( - serde_json::from_str::(json).unwrap(), - NodeMessage::V2 { - payload: Payload::AfgReceivedPrecommit(..), - .. - }, - ), - "message did not match the expected output", - ); - } - #[test] fn message_v2_tx_pool_import() { // We should happily ignore any fields we don't care about. @@ -343,14 +263,16 @@ mod tests { "foo":"Something", "bar":123, "wibble":"wobble", - "msg":"txpool.import" + "msg":"block.import", + "best":"0xcc41708573f2acaded9dd75e07dac2d4163d136ca35b3061c558d7a35a09dd8d", + "height": 1234 } }"#; assert!( matches!( serde_json::from_str::(json).unwrap(), NodeMessage::V2 { - payload: Payload::TxPoolImport, + payload: Payload::BlockImport(Block { .. }), .. }, ), diff --git a/frontend/src/AfgHandling.ts b/frontend/src/AfgHandling.ts deleted file mode 100644 index 6dc00b1..0000000 --- a/frontend/src/AfgHandling.ts +++ /dev/null @@ -1,325 +0,0 @@ -// Source code for the Substrate Telemetry Server. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -import { Types } from './common'; -import { State, Update } from './state'; -import { ConsensusDetail } from './common/types'; - -// Number of blocks which are kept in memory -const BLOCKS_LIMIT = 50; - -export class AfgHandling { - constructor( - private readonly appUpdate: Update, - private readonly appState: Readonly - ) { - this.appUpdate = appUpdate; - this.appState = appState; - } - - public receivedAuthoritySet( - authoritySetId: Types.AuthoritySetId, - authorities: Types.Authorities - ) { - if ( - this.appState.authoritySetId != null && - authoritySetId !== this.appState.authoritySetId - ) { - // the visualization is restarted when we receive a new authority set - this.appUpdate({ - authoritySetId, - authorities, - consensusInfo: [], - displayConsensusLoadingScreen: false, - }); - } else if (this.appState.authoritySetId == null) { - // initial display - this.appUpdate({ - authoritySetId, - authorities, - consensusInfo: [], - displayConsensusLoadingScreen: true, - }); - } - return null; - } - - public receivedFinalized( - addr: Types.Address, - finalizedNumber: Types.BlockNumber, - finalizedHash: Types.BlockHash - ) { - const state = this.appState; - if (finalizedNumber < state.best - BLOCKS_LIMIT) { - return; - } - - const data = { - Finalized: true, - FinalizedHash: finalizedHash, - FinalizedHeight: finalizedNumber, - - // this is extrapolated. if this app was just started up we - // might not yet have received prevotes/precommits. but - // those are a necessary precondition for finalization, so - // we can set them and display them in the ui. - Prevote: true, - Precommit: true, - } as Types.ConsensusDetail; - this.initialiseConsensusView( - state.consensusInfo, - finalizedNumber, - addr, - addr - ); - - this.updateConsensusInfo( - state.consensusInfo, - finalizedNumber, - addr, - addr, - data as Partial - ); - - // Finalizing a block implicitly includes finalizing all - // preceding blocks. This function marks the preceding - // blocks as implicitly finalized on and stores a pointer - // to the block which contains the explicit finalization. - const op = (i: Types.BlockNumber, index: number): boolean => { - const consensusDetail = state.consensusInfo[index][1][addr][addr]; - if (consensusDetail.Finalized || consensusDetail.ImplicitFinalized) { - return false; - } - - state.consensusInfo[index][1][addr][addr] = { - Finalized: true, - FinalizedHeight: i, - ImplicitFinalized: true, - ImplicitPointer: finalizedNumber, - - // this is extrapolated. if this app was just started up we - // might not yet have received prevotes/precommits. but - // those are a necessary precondition for finalization, so - // we can set them and display them in the ui. - Prevote: true, - Precommit: true, - ImplicitPrevote: true, - ImplicitPrecommit: true, - }; - return true; - }; - this.backfill(state.consensusInfo, finalizedNumber, op, addr, addr); - - this.pruneBlocks(state.consensusInfo); - this.appUpdate({ consensusInfo: state.consensusInfo }); - } - - public receivedPre( - addr: Types.Address, - height: Types.BlockNumber, - voter: Types.Address, - what: string - ) { - const state = this.appState; - if (height < state.best - BLOCKS_LIMIT) { - return; - } - - const data = what === 'prevote' ? { Prevote: true } : { Precommit: true }; - this.initialiseConsensusView(state.consensusInfo, height, addr, voter); - this.updateConsensusInfo( - state.consensusInfo, - height, - addr, - voter, - data as Partial - ); - - // A Prevote or Precommit on a block implicitly includes - // a vote on all preceding blocks. This function marks - // the preceding blocks as implicitly voted on and stores - // a pointer to the block which contains the explicit vote. - const op = (index: number): boolean => { - const consensusDetail = state.consensusInfo[index][1][addr][voter]; - if ( - what === 'prevote' && - (consensusDetail.Prevote || consensusDetail.ImplicitPrevote) - ) { - return false; - } - if ( - what === 'precommit' && - (consensusDetail.Precommit || consensusDetail.ImplicitPrecommit) && - // because of extrapolation a prevote needs to be set as well. - // if it is not we continue backfilling (and set it during that process). - (consensusDetail.Prevote || consensusDetail.ImplicitPrevote) - ) { - return false; - } - - if (what === 'prevote') { - consensusInfo[index][1][addr][voter].ImplicitPrevote = true; - } else if (what === 'precommit') { - consensusInfo[index][1][addr][voter].ImplicitPrecommit = true; - - // Extrapolate. Precommit implies Prevote. - consensusInfo[index][1][addr][voter].ImplicitPrevote = true; - } - consensusInfo[index][1][addr][voter].ImplicitPointer = height; - return true; - }; - const consensusInfo = this.appState.consensusInfo; - this.backfill(consensusInfo, height, op, addr, voter); - - this.pruneBlocks(consensusInfo); - this.appUpdate({ consensusInfo }); - } - - // Initializes the `ConsensusView` with empty objects. - private initialiseConsensusView( - consensusInfo: Types.ConsensusInfo, - height: Types.BlockNumber, - own: Types.Address, - other: Types.Address - ) { - const found = consensusInfo.find(([blockNumber]) => blockNumber === height); - - let consensusView; - if (found) { - [, consensusView] = found; - this.initialiseConsensusViewByRef(consensusView, own, other); - } else { - consensusView = {} as Types.ConsensusView; - - this.initialiseConsensusViewByRef(consensusView, own, other); - - const item: Types.ConsensusItem = [height, consensusView]; - const insertPos = consensusInfo.findIndex( - ([elHeight]) => elHeight < height - ); - if (insertPos >= 0) { - consensusInfo.splice(insertPos, 0, item); - } else { - consensusInfo.push(item); - } - } - } - - // Initializes the `ConsensusView` with empty objects. - private initialiseConsensusViewByRef( - consensusView: Types.ConsensusView, - own: Types.Address, - other: Types.Address - ) { - if (!consensusView[own]) { - consensusView[own] = {} as Types.ConsensusState; - } - - if (!consensusView[own][other]) { - consensusView[own][other] = {} as Types.ConsensusDetail; - } - } - - // Fill the block cache back from the `to` number to the last block. - // The function `f` is used to evaluate if we should continue backfilling. - // `f` returns false when backfilling the cache should be stopped, true to continue. - // - // Returns block number until which we backfilled. - private backfill( - consensusInfo: Types.ConsensusInfo, - start: Types.BlockNumber, - f: (i: Types.BlockNumber, index: number) => boolean, - own: Types.Address, - other: Types.Address - ) { - // if this is the first block then we don't fill latter blocks - // if there is only one block, then it also doesn't make - // sense to backfill, because we could potentially backfill - // until 0 (which could be unfortunate if the first received - // block is e.g. 28317. - if (consensusInfo.length < 2) { - return; - } - - let firstBlockNumber = consensusInfo[consensusInfo.length - 1][0]; - const limit = this.appState.best - BLOCKS_LIMIT; - if (firstBlockNumber < limit) { - firstBlockNumber = limit as Types.BlockNumber; - } - - if (start - 1 < firstBlockNumber) { - // if the first block which would be backfilled is already - // less than the first block number we can abort. - // - // this can happen if e.g. one authority is hanging behind, - // most of them could e.g. be at 3000 and one is hanging behind - // and sending info for 2000. then we can't start backfilling - // from 2000. - return; - } - - let counter = 0; - while (start-- > 0) { - counter++; - if (counter >= BLOCKS_LIMIT) { - break; - } - - const startBlockNumber = start as Types.BlockNumber; - this.initialiseConsensusView(consensusInfo, startBlockNumber, own, other); - const index = consensusInfo.findIndex( - ([blockNumber]) => blockNumber === start - ); - const cont = f(start, index); - if (!cont) { - break; - } - - // we don't want to fill into nirvana - const firstBlockReached = startBlockNumber <= firstBlockNumber; - if (firstBlockReached) { - break; - } - } - } - - private updateConsensusInfo( - consensusInfo: Types.ConsensusInfo, - height: Types.BlockNumber, - addr: Types.Address, - voter: Types.Address, - data: Partial - ) { - const found = consensusInfo.findIndex( - ([blockNumber]) => blockNumber === height - ); - if (found < 0) { - return; - } - - for (const k in data) { - if (data.hasOwnProperty(k)) { - consensusInfo[found][1][addr][voter][k] = data[k]; - } - } - } - - private pruneBlocks(consensusInfo: Types.ConsensusInfo) { - if (consensusInfo.length >= BLOCKS_LIMIT) { - consensusInfo.length = BLOCKS_LIMIT; - } - } -} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index bb145ef..dae3f73 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -101,11 +101,6 @@ export default class App extends React.Component<{}, {}> { status: 'offline', best: 0 as Types.BlockNumber, finalized: 0 as Types.BlockNumber, - consensusInfo: new Array() as Types.ConsensusInfo, - displayConsensusLoadingScreen: true, - authorities: new Array() as Types.Authorities, - authoritySetId: null, - sendFinality: false, blockTimestamp: 0 as Types.Timestamp, blockAverage: null, timeDiff: 0 as Types.Milliseconds, diff --git a/frontend/src/Connection.ts b/frontend/src/Connection.ts index 39d296a..85abba9 100644 --- a/frontend/src/Connection.ts +++ b/frontend/src/Connection.ts @@ -18,8 +18,6 @@ import { VERSION, timestamp, FeedMessage, Types, Maybe, sleep } from './common'; import { State, Update, Node, ChainData, PINNED_CHAINS } from './state'; import { PersistentSet } from './persist'; import { getHashData, setHashData } from './utils'; -import { AfgHandling } from './AfgHandling'; -import { VIS_AUTHORITIES_LIMIT } from './components/Consensus'; import { ACTIONS } from './common/feed'; import { Column, @@ -126,8 +124,6 @@ export class Connection { private pingSent: Maybe = null; // chain label to resubsribe to on reconnect private resubscribeTo: Maybe = getHashData().chain; - // flag whether or not FE should subscribe to consensus updates on reconnect - private resubscribeSendFinality: boolean = getHashData().tab === 'consensus'; constructor( private socket: WebSocket, @@ -154,35 +150,11 @@ export class Connection { this.socket.send(`subscribe:${chain}`); } - public subscribeConsensus(chain: Types.GenesisHash) { - if (this.appState.authorities.length <= VIS_AUTHORITIES_LIMIT) { - setHashData({ chain }); - this.resubscribeSendFinality = true; - this.socket.send(`send-finality:${chain}`); - } - } - - public resetConsensus() { - this.appUpdate({ - consensusInfo: new Array() as Types.ConsensusInfo, - displayConsensusLoadingScreen: true, - authorities: [] as Types.Address[], - authoritySetId: null, - }); - } - - public unsubscribeConsensus(chain: Types.GenesisHash) { - this.resubscribeSendFinality = true; - this.socket.send(`no-more-finality:${chain}`); - } - private handleMessages = (messages: FeedMessage.Message[]) => { this.messageTimeout?.reset(); const { nodes, chains, sortBy, selectedColumns } = this.appState; const nodesStateRef = nodes.ref; - const afg = new AfgHandling(this.appUpdate, this.appState); - let sortByColumn: Maybe = null; if (sortBy != null) { @@ -361,7 +333,6 @@ export class Connection { if (this.appState.subscribed === message.payload) { nodes.clear(); this.appUpdate({ subscribed: null, nodes, chains }); - this.resetConsensus(); } break; @@ -391,37 +362,6 @@ export class Connection { break; } - case ACTIONS.AfgFinalized: { - const [nodeAddress, finalizedNumber, finalizedHash] = message.payload; - const no = parseInt(String(finalizedNumber), 10) as Types.BlockNumber; - afg.receivedFinalized(nodeAddress, no, finalizedHash); - - break; - } - - case ACTIONS.AfgReceivedPrevote: { - const [nodeAddress, blockNumber, blockHash, voter] = message.payload; - const no = parseInt(String(blockNumber), 10) as Types.BlockNumber; - afg.receivedPre(nodeAddress, no, voter, 'prevote'); - - break; - } - - case ACTIONS.AfgReceivedPrecommit: { - const [nodeAddress, blockNumber, blockHash, voter] = message.payload; - const no = parseInt(String(blockNumber), 10) as Types.BlockNumber; - afg.receivedPre(nodeAddress, no, voter, 'precommit'); - - break; - } - - case ACTIONS.AfgAuthoritySet: { - const [authoritySetId, authorities] = message.payload; - afg.receivedAuthoritySet(authoritySetId, authorities); - - break; - } - default: { break; } @@ -456,8 +396,7 @@ export class Connection { if (this.appState.subscribed) { this.resubscribeTo = this.appState.subscribed; - this.resubscribeSendFinality = this.appState.sendFinality; - this.appUpdate({ subscribed: null, sendFinality: false }); + this.appUpdate({ subscribed: null }); } this.socket.addEventListener('message', this.handleFeedData); @@ -544,7 +483,7 @@ export class Connection { private autoSubscribe() { const { subscribed, chains } = this.appState; - const { resubscribeTo, resubscribeSendFinality } = this; + const { resubscribeTo } = this; if (subscribed) { return; @@ -553,9 +492,6 @@ export class Connection { if (resubscribeTo) { if (chains.has(resubscribeTo)) { this.subscribe(resubscribeTo); - if (resubscribeSendFinality) { - this.subscribeConsensus(resubscribeTo); - } return; } } @@ -581,7 +517,6 @@ export class Connection { private handleDisconnect = async () => { console.warn('Disconnecting; will attempt reconnect'); this.appUpdate({ status: 'offline' }); - this.resetConsensus(); this.clean(); this.socket.close(); this.socket = await Connection.socket(); diff --git a/frontend/src/common/types.ts b/frontend/src/common/types.ts index c3c3d33..3f984ad 100644 --- a/frontend/src/common/types.ts +++ b/frontend/src/common/types.ts @@ -80,21 +80,6 @@ export declare type AuthoritySetInfo = [ BlockNumber, BlockHash ]; -export declare type ConsensusItem = [BlockNumber, ConsensusView]; -export declare type ConsensusInfo = Array; -export declare type ConsensusView = Map; -export declare type ConsensusState = Map; -export interface ConsensusDetail { - Precommit: Precommit; - ImplicitPrecommit: ImplicitPrecommit; - Prevote: Prevote; - ImplicitPrevote: ImplicitPrevote; - ImplicitPointer: ImplicitPointer; - Finalized: ImplicitFinalized; - ImplicitFinalized: Finalized; - FinalizedHash: BlockHash; - FinalizedHeight: BlockNumber; -} export declare type Precommit = Opaque; export declare type Prevote = Opaque; export declare type Finalized = Opaque; diff --git a/frontend/src/components/AllChains.tsx b/frontend/src/components/AllChains.tsx index f026604..f0389b6 100644 --- a/frontend/src/components/AllChains.tsx +++ b/frontend/src/components/AllChains.tsx @@ -70,6 +70,5 @@ export class AllChains extends React.Component { const connection = await this.props.connection; connection.subscribe(chain); - connection.resetConsensus(); } } diff --git a/frontend/src/components/Chain/Chain.tsx b/frontend/src/components/Chain/Chain.tsx index 44a575a..cd2d512 100644 --- a/frontend/src/components/Chain/Chain.tsx +++ b/frontend/src/components/Chain/Chain.tsx @@ -20,7 +20,7 @@ import { Types, Maybe } from '../../common'; import { State as AppState, Update as AppUpdate } from '../../state'; import { getHashData } from '../../utils'; import { Header } from './'; -import { Tile, Ago, List, Map, Settings, Consensus } from '../'; +import { List, Map, Settings } from '../'; import { Persistent, PersistentObject, PersistentSet } from '../../persist'; import './Chain.css'; @@ -55,9 +55,6 @@ export class Chain extends React.Component { case 'settings': display = 'settings'; break; - case 'consensus': - display = 'consensus'; - break; } this.state = { @@ -96,10 +93,6 @@ export class Chain extends React.Component { const { appState, appUpdate, connection, pins, sortBy } = this.props; - if (display === 'consensus') { - return ; - } - return display === 'list' ? ( { current={currentTab} setDisplay={setDisplay} /> - { const connection = await this.props.connection; connection.subscribe(chain); - connection.resetConsensus(); } } diff --git a/frontend/src/components/Consensus/Consensus.css b/frontend/src/components/Consensus/Consensus.css deleted file mode 100644 index 41cbf71..0000000 --- a/frontend/src/components/Consensus/Consensus.css +++ /dev/null @@ -1,185 +0,0 @@ -/* -Source code for the Substrate Telemetry Server. -Copyright (C) 2021 Parity Technologies (UK) Ltd. - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -.Consensus .ConsensusList { - opacity: 0; /* the box should only show up once flexing has been applied */ -} - -.Consensus .ConsensusList table { - border-spacing: 0px; -} - -.Consensus .flexContainerLargeRow { - display: flex; - align-items: stretch; - flex-direction: row; - opacity: 1; -} - -.Consensus .flexContainerLargeRow .firstInRow { - width: 100%; -} - -.Consensus .flexContainerLargeRow .firstInRow .emptyLegend, -.Consensus .flexContainerLargeRow .firstInRow .nameLegend { - width: 99%; - flex-grow: 1000000000; - align-self: stretch; -} - -.Consensus .flexContainerSmallRow { - display: flex; - align-items: stretch; - flex-direction: row; - flex-wrap: wrap; - opacity: 1; -} - -.Consensus .flexContainerSmallRow div { - align-self: stretch; - flex: 1; -} - -.Consensus .flexContainerSmallRow table .legend { - width: 100%; -} - -.Consensus .ConsensusList { - margin-bottom: 2px; -} - -.Consensus { - width: 100%; - min-width: 1350px; - min-height: 100%; - position: absolute; - top: 0px; - left: 0px; -} - -.Consensus .SmallRow { - float: left; - clear: both; - font-size: 8px !important; - width: 100%; -} - -.Consensus .SmallRow svg { - width: 14px; - height: 14px; -} - -.Consensus .hatching svg { - width: 12px !important; - height: 12px !important; -} - -.Consensus .SmallRow .hatching svg { - width: 10px !important; - height: 10px !important; -} - -.Consensus .matrixXLegend .Tooltip-container { - height: auto !important; -} - -.Consensus .legend { - text-align: center !important; -} - -.Consensus .nameLegend { - border-right: none; - border-bottom: 1px dotted #555; -} - -.Consensus .SmallRow .nameLegend { - display: none; -} - -.Consensus .SmallRow .finalizedInfo .Tooltip-container { - float: none; - display: inline-block !important; - vertical-align: middle; -} - -.Consensus .SmallRow .finalizedInfo { - min-height: 40px; - min-width: 40px; -} - -.Consensus .SmallRow .explicit, -.Consensus .SmallRow .implicit { - height: 12px; -} - -.Consensus .SmallRow .finalizedInfo .explicit, -.Consensus .SmallRow .finalizedInfo .implicit { - margin-right: 6px; -} - -.Consensus .nodeAddress { - margin-top: 4px; -} - -.Consensus .first_false .legend .nodeAddress, -.Consensus .SmallRow .legend .nodeAddress, -.Consensus th.finalizedInfo .Tooltip-container { - float: none !important; - text-align: center !important; -} - -.Consensus .noStretchOnLastRow::after { - content: ''; - flex-grow: 1000000000; -} - -.Consensus .flexContainerLargeRow .noStretchOnLastRow .firstInRow table { - width: auto !important; -} - -.Consensus .flexContainerLargeRow .noStretchOnLastRow .firstInRow .emptyLegend { - width: auto !important; -} - -.Consensus .flexContainerLargeRow .noStretchOnLastRow .firstInRow { - width: auto !important; -} - -/* similar to .App-no-telemetry */ -.Consensus .noData { - width: 100vw; - line-height: 60vh; - font-size: 56px; - font-weight: 100; - text-align: center; - color: #888; -} - -/* similar to .App-no-telemetry */ -.Consensus .tooManyAuthorities { - width: 100vw; - line-height: 20vh; - font-size: 56px; - font-weight: 100; - text-align: center; - color: #888; -} - -.Consensus svg { - z-index: 999999999; -} diff --git a/frontend/src/components/Consensus/Consensus.tsx b/frontend/src/components/Consensus/Consensus.tsx deleted file mode 100644 index e2d0cfb..0000000 --- a/frontend/src/components/Consensus/Consensus.tsx +++ /dev/null @@ -1,439 +0,0 @@ -// Source code for the Substrate Telemetry Server. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -import * as React from 'react'; -import { Types, Maybe } from '../../common'; -import { Connection } from '../../Connection'; -import Measure, { BoundingRect, ContentRect } from 'react-measure'; - -import { ConsensusBlock } from './'; -import { State as AppState } from '../../state'; - -import './Consensus.css'; - -// Maximum number of authorities the visualization is -// allowed of processing. -export const VIS_AUTHORITIES_LIMIT = 10; - -export namespace Consensus { - export interface Props { - appState: Readonly; - connection: Promise; - } - - export interface State { - dimensions: BoundingRect; - - largeBlockWithLegend: BoundingRect; - largeBlock: BoundingRect; - countBlocksInLargeRow: number; - largeRowsAddFlexClass: boolean; - - smallBlock: BoundingRect; - smallBlocksRows: number; - countBlocksInSmallRow: number; - smallRowsAddFlexClass: boolean; - lastConsensusInfo: string; - } -} - -export class Consensus extends React.Component { - public state = { - // entire area available for rendering the visualization - dimensions: { width: -1, height: -1 } as BoundingRect, - - largeBlockWithLegend: { width: -1, height: -1 } as BoundingRect, - largeBlock: { width: -1, height: -1 } as BoundingRect, - countBlocksInLargeRow: 2, - largeRowsAddFlexClass: false, - - smallBlock: { width: -1, height: -1 } as BoundingRect, - smallBlocksRows: 1, - countBlocksInSmallRow: 1, - smallRowsAddFlexClass: false, - lastConsensusInfo: '', - }; - - public shouldComponentUpdate( - nextProps: Consensus.Props, - nextState: Consensus.State - ): boolean { - if ( - this.props.appState.authorities.length === 0 && - nextProps.appState.authorities.length === 0 - ) { - return false; - } - - this.calculateBoxCount(false); - - // size detected, but flex class has not yet been added - const largeBlocksSizeDetected = - this.largeBlocksSizeDetected(nextState) === true && - this.state.largeRowsAddFlexClass === false; - if (largeBlocksSizeDetected) { - return true; - } - - const smallBlocksSizeDetected = - this.smallBlocksSizeDetected(nextState) === true && - this.state.smallRowsAddFlexClass === false; - if (smallBlocksSizeDetected) { - return true; - } - - const windowSizeChanged = - JSON.stringify(this.state.dimensions) !== - JSON.stringify(nextState.dimensions); - if (windowSizeChanged) { - return true; - } - - const newConsensusInfoAvailable = - this.state.lastConsensusInfo !== - JSON.stringify(nextProps.appState.consensusInfo); - if (newConsensusInfoAvailable) { - return true; - } - - const authoritySetIdDidChange = - this.props.appState.authoritySetId !== nextProps.appState.authoritySetId; - if (authoritySetIdDidChange) { - return true; - } - - const authoritiesDidChange = - JSON.stringify(this.props.appState.authorities) !== - JSON.stringify(nextProps.appState.authorities); - if (authoritiesDidChange) { - return true; - } - - return false; - } - - public componentDidMount() { - if (this.props.appState.subscribed != null) { - const chain = this.props.appState.subscribed; - this.subscribeConsensus(chain); - } - } - - public componentWillUnmount() { - if (this.props.appState.subscribed != null) { - const chain = this.props.appState.subscribed; - this.unsubscribeConsensus(chain); - } - } - - public largeBlocksSizeDetected(state: Consensus.State): boolean { - // we can only state that we detected the two block sizes (with - // legend and without) if at least two blocks have been added: - // the first displayed block will always have a legend with the - // node names attached, the second not. - if (this.props.appState.consensusInfo.length < 2) { - return false; - } - - // if there is more than one block then the size of the first block (with legend) - // will be different from the succeeding blocks (without legend) - return ( - state.largeBlockWithLegend.width > -1 && - state.largeBlockWithLegend.height > -1 && - state.largeBlock.width > -1 && - state.largeBlock.height > -1 - ); - } - - public smallBlocksSizeDetected(state: Consensus.State): boolean { - return ( - state.smallBlock.width > -1 && state.largeBlockWithLegend.height > -1 - ); - } - - public calculateBoxCount(wasResized: boolean) { - // if the css class for flexing has already been added we don't calculate - // any box measurements then, because the box sizes would be skewed then. - if ( - (wasResized || this.state.largeRowsAddFlexClass === false) && - this.largeBlocksSizeDetected(this.state) - ) { - // we need to add +2 because of the last block which doesn't contain a border. - let countBlocks = - (this.state.dimensions.width - - this.state.largeBlockWithLegend.width + - 2) / - (this.state.largeBlock.width + 2); - - // +1 because the firstRect was subtracted above and needs to be counted back in. - // default count is 2 because we need two blocks to measure properly (one with legend - // and one without. these measures are necessary to calculate the number of blocks - // which fit. - countBlocks = - Math.floor(countBlocks + 1) < 1 ? 2 : Math.floor(countBlocks + 1); - - this.setState({ - largeRowsAddFlexClass: true, - countBlocksInLargeRow: countBlocks, - }); - } - - if ( - (wasResized || this.state.smallRowsAddFlexClass === false) && - this.smallBlocksSizeDetected(this.state) - ) { - const howManyRows = 2; - - const heightLeft = - this.state.dimensions.height - - this.state.largeBlock.height * howManyRows; - - let smallBlocksRows = heightLeft / this.state.smallBlock.height; - smallBlocksRows = smallBlocksRows < 1 ? 1 : Math.floor(smallBlocksRows); - - let countBlocksInSmallRow = - this.state.dimensions.width / this.state.smallBlock.width; - countBlocksInSmallRow = - countBlocksInSmallRow < 1 ? 1 : Math.floor(countBlocksInSmallRow); - - this.setState({ - smallRowsAddFlexClass: true, - countBlocksInSmallRow, - smallBlocksRows, - }); - } - } - - public render() { - this.state.lastConsensusInfo = JSON.stringify( - this.props.appState.consensusInfo - ); - const lastBlocks = this.props.appState.consensusInfo; - - if (this.props.appState.authorities.length > VIS_AUTHORITIES_LIMIT) { - return ( -
-
-

Too many authorities.

-

- Won't display for more than {VIS_AUTHORITIES_LIMIT} authorities to - protect your browser. -

-
- ; -
- ); - } - - if ( - this.props.appState.displayConsensusLoadingScreen && - lastBlocks.length < 2 - ) { - return ( -
-
- {lastBlocks.length === 0 ? 'No ' : 'Not yet enough '} - GRANDPA data received by the authorities… -
- ; -
- ); - } - - let from = 0; - let to = this.state.countBlocksInLargeRow; - const firstLargeRow = this.getLargeRow(lastBlocks.slice(from, to), 0); - - from = to; - to = to + this.state.countBlocksInLargeRow; - const secondLargeRow = this.getLargeRow(lastBlocks.slice(from, to), 1); - - from = to; - to = to + this.state.smallBlocksRows * this.state.countBlocksInSmallRow; - const smallRow = this.getSmallRow(lastBlocks.slice(from, to)); - - const get = (measureRef: Maybe<(ref: Element | null) => void>) => ( -
- {firstLargeRow} - {secondLargeRow} - {smallRow} -
- ); - - if ( - !(this.state.smallRowsAddFlexClass && this.state.largeRowsAddFlexClass) - ) { - return ( - - - {({ measureRef }) => get(measureRef)} - - - ); - } else { - return get(null); - } - } - - private handleOnResize = (contentRect: ContentRect) => { - this.setState({ dimensions: contentRect.bounds as BoundingRect }); - this.calculateBoxCount(true); - }; - - private getAuthorities(): Types.Authority[] { - // find the node for each of these authority addresses - if (this.props.appState.authorities == null) { - return []; - } - - return this.props.appState.authorities.map((address) => { - const node2 = this.props.appState.nodes - .sorted() - .filter((node) => node.validator === address)[0]; - if (!node2) { - return { - Address: address, - NodeId: null, - Name: null, - } as Types.Authority; - } - return { - Address: address, - NodeId: node2.id, - Name: node2.name, - } as Types.Authority; - }); - } - - private getLargeRow(blocks: Types.ConsensusInfo, id: number) { - const largeBlockSizeChanged = ( - isFirstBlock: boolean, - rect: BoundingRect - ) => { - if (this.largeBlocksSizeDetected(this.state)) { - return; - } - if (isFirstBlock) { - this.setState({ - largeBlockWithLegend: { width: rect.width, height: rect.height }, - }); - } else { - this.setState({ - largeBlock: { width: rect.width, height: rect.height }, - }); - } - }; - - const stretchLastRowMajor = - blocks.length < this.state.countBlocksInLargeRow - ? 'noStretchOnLastRow' - : ''; - const flexClass = this.state.largeRowsAddFlexClass - ? 'flexContainerLargeRow' - : ''; - - return ( -
- {blocks.map((item, i) => { - const [height, consensusView] = item; - return ( - - ); - })} -
- ); - } - - private getSmallRow(blocks: Types.ConsensusInfo) { - const smallBlockSizeChanged = ( - _isFirstBlock: boolean, - rect: BoundingRect - ) => { - if (this.smallBlocksSizeDetected(this.state)) { - return; - } - const dimensionsChanged = - this.state.smallBlock.height !== rect.height && - this.state.smallBlock.width !== rect.width; - if (dimensionsChanged) { - this.setState({ - smallBlock: { width: rect.width, height: rect.height }, - }); - } - }; - const stretchLastRow = - blocks.length < - this.state.countBlocksInSmallRow * this.state.smallBlocksRows - ? 'noStretchOnLastRow' - : ''; - const classes = `ConsensusList SmallRow ${ - this.state.smallRowsAddFlexClass ? 'flexContainerSmallRow' : '' - } ${stretchLastRow}`; - - return ( -
- {blocks.map((item, i) => { - const [height, consensusView] = item; - let lastInRow = - (i + 1) % this.state.countBlocksInSmallRow === 0 ? true : false; - if (lastInRow && i === 0) { - // should not be marked as last one in row if it's the very first in row - lastInRow = false; - } - - return ( - - ); - })} -
- ); - } - - private async subscribeConsensus(chain: Types.GenesisHash) { - const connection = await this.props.connection; - connection.subscribeConsensus(chain); - } - - private async unsubscribeConsensus(chain: Types.GenesisHash) { - const connection = await this.props.connection; - connection.unsubscribeConsensus(chain); - } -} diff --git a/frontend/src/components/Consensus/ConsensusBlock.css b/frontend/src/components/Consensus/ConsensusBlock.css deleted file mode 100644 index 2357c0d..0000000 --- a/frontend/src/components/Consensus/ConsensusBlock.css +++ /dev/null @@ -1,221 +0,0 @@ -/* -Source code for the Substrate Telemetry Server. -Copyright (C) 2021 Parity Technologies (UK) Ltd. - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -.Consensus .BlockConsensusMatrice { - background-color: #222; - font-family: monospace, sans-serif; - border-spacing: 0px; - border-right: 2px solid lightgrey; - border-bottom: 1px solid #999; -} - -.Consensus .LargeRow .BlockConsensusMatrice:last-child { - border-right: none; -} - -.Consensus .SmallRow .lastInRow { - clear: right; - width: 99%; - page-break-after: always; -} - -.Consensus .BlockConsensusMatrice th { - font-weight: normal; - border-bottom: 1px dashed #999; -} - -.Consensus .finalizedInfo, -.legend { - border-bottom: 1px dotted #555555; -} - -.Consensus .finalizedInfo { - white-space: nowrap; -} - -.Consensus .finalizedInfo .Tooltip-container { - display: inline-block; - white-space: nowrap; - vertical-align: middle; -} - -.Consensus .BlockConsensusMatrice .matrice { - width: 28px; - height: 28px; -} - -.Consensus .BlockConsensusMatrice .matrice { - font-weight: normal; - border-right: 1px dotted #555555; - border-bottom: 1px dotted #555; -} - -.Consensus .BlockConsensusMatrice tr .matrice:last-child { - border-right: none; -} - -.Consensus .BlockConsensusMatrice .matrixXLegend { - text-align: center; - border-right: 1px dotted #555555; -} - -.Consensus .BlockConsensusMatrice .matrixXLegend:last-child { - border-right: none; -} - -.Consensus .matrice { - text-align: center !important; - min-width: 35px; -} - -.Consensus .SmallRow .matrixXLegend, -.Consensus .SmallRow .matrice { - min-width: 26px; - min-height: 26px; -} - -.Consensus .finalizedInfo { - text-align: center !important; -} - -.Consensus .SmallRow .finalizedInfo { - min-width: 40px; -} - -.Consensus .finalizedInfo { - text-align: right; - border-right: 1px dashed #999; - min-width: 50px; -} - -.Consensus .finalizedInfo .Tooltip-container { - float: none; -} - -.Consensus .explicit { - fill: #e70e81; -} - -.Consensus .nodeName { - float: left; - padding-right: 10px; - padding-top: 4px; -} - -.Consensus .flexContainerLargeRow .firstInRow .nodeContent { - white-space: nowrap; -} - -.Consensus .flexContainerLargeRow .firstInRow .nodeName { - display: inline-block !important; - float: none !important; - vertical-align: middle; - margin-bottom: 3px; -} - -.Consensus .flexContainerLargeRow .firstInRow .nodeAddress { - display: inline-block !important; - float: none !important; - vertical-align: middle; - margin-right: 3px; -} - -.Consensus .legend { - border-right: 1px solid #999; - white-space: nowrap; -} - -.Consensus .first_false .nodeName { - display: none; -} - -.Consensus .legend .nodeAddress { - float: right; -} - -.Consensus .Row { - color: #999; - cursor: pointer; -} - -.Consensus .Row th, -.Consensus .Row td { - text-align: left; - padding: 2px; -} - -.Consensus .Row td { - position: relative; -} - -.Consensus .Row .Row-truncate { - position: absolute; - left: 0; - right: 0; - top: 0; - padding: inherit; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.Consensus .Row .Row-Tooltip { - position: initial; - padding: inherit; -} - -.Consensus .Row:hover { - background-color: #161616; -} - -.Consensus .nodeAddress svg { - cursor: pointer; -} - -.Consensus .nodeAddress svg:hover { - transform: scale(2); -} - -.Consensus .matrice .Icon ~ .Icon { - margin-left: -4px; -} - -.Consensus .SmallRow .matrice .Prevote svg { - margin-left: 3px; - margin-bottom: -11px; -} - -.Consensus .SmallRow .matrice .Precommit svg { - margin-left: -1px; - margin-top: -6px; - margin-bottom: 0px; -} - -.Consensus .jdenticonPlaceholder { - width: 28px; - float: right; -} - -.Consensus .SmallRow .jdenticonPlaceholder { - width: 14px; - float: right; -} - -.Consensus .even { - background-color: #333; -} diff --git a/frontend/src/components/Consensus/ConsensusBlock.tsx b/frontend/src/components/Consensus/ConsensusBlock.tsx deleted file mode 100644 index 02313be..0000000 --- a/frontend/src/components/Consensus/ConsensusBlock.tsx +++ /dev/null @@ -1,342 +0,0 @@ -// Source code for the Substrate Telemetry Server. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -import * as React from 'react'; - -import Measure, { BoundingRect, ContentRect } from 'react-measure'; -import { Types, Maybe } from '../../common'; - -import { Icon, Tooltip, PolkadotIcon } from '../'; -import Jdenticon from './Jdenticon'; - -import checkIcon from '../../icons/check.svg'; -import finalizedIcon from '../../icons/finalized.svg'; -import hatchingIcon from '../../icons/hatching.svg'; - -import './ConsensusBlock.css'; - -export namespace ConsensusBlock { - export interface Props { - authorities: Types.Authority[]; - authoritySetId: Maybe; - height: Types.BlockNumber; - firstInRow: boolean; - lastInRow: boolean; - compact: boolean; - measure: boolean; - consensusView: Types.ConsensusView; - changeBlocks: (first: boolean, boundsRect: BoundingRect) => void; - } -} - -export class ConsensusBlock extends React.Component { - public state = { - lastConsensusView: '', - }; - - public shouldComponentUpdate(nextProps: ConsensusBlock.Props): boolean { - if ( - this.props.authorities.length === 0 && - nextProps.authorities.length === 0 - ) { - return false; - } - - const positionInfoChanged = - this.props.firstInRow !== nextProps.firstInRow || - this.props.lastInRow !== nextProps.lastInRow; - if (positionInfoChanged) { - return true; - } - - const newConsensusInfo = - JSON.stringify(nextProps.consensusView) !== this.state.lastConsensusView; - if (newConsensusInfo) { - return true; - } - - return false; - } - - public render() { - this.state.lastConsensusView = JSON.stringify(this.props.consensusView); - const finalizedByWhom = this.props.authorities.filter((authority) => - this.isFinalized(authority) - ); - - const ratio = finalizedByWhom.length + '/' + this.props.authorities.length; - let titleFinal = {ratio}; - - const majorityFinalized = - finalizedByWhom.length / this.props.authorities.length >= 2 / 3; - if (majorityFinalized && !this.props.compact) { - titleFinal = FINAL; - } else if (majorityFinalized && this.props.compact) { - const hash = this.getFinalizedHash(finalizedByWhom[0]); - titleFinal = ( - - ); - } - - const handleOnResize = (contentRect: ContentRect) => { - this.props.changeBlocks( - this.props.firstInRow, - contentRect.bounds as BoundingRect - ); - }; - - const get = (measureRef: Maybe<(ref: Element | null) => void>) => { - return ( -
- - - - {this.props.firstInRow && !this.props.compact ? ( - - ) : null} - - - {this.props.authorities.map((authority) => ( - - ))} - - - - {this.props.authorities.map((authority, row) => - this.renderMatriceRow(authority, this.props.authorities, row) - )} - -
-   - - - {this.displayBlockNumber()} - - {titleFinal} - - {this.getAuthorityContent(authority)} -
-
- ); - }; - - if (this.props.measure) { - return ( - - {({ measureRef }) => get(measureRef)} - - ); - } else { - return get(null); - } - } - - private displayBlockNumber(): string { - const blockNumber = String(this.props.height); - return blockNumber.length > 2 - ? '…' + blockNumber.substr(blockNumber.length - 2, blockNumber.length) - : blockNumber; - } - - private isFinalized(authority: Types.Authority): boolean { - if (!authority || authority.NodeId == null || authority.Address == null) { - return false; - } - - const { Address: addr } = authority; - const consensus = this.props.consensusView; - - return ( - consensus != null && - addr in consensus && - addr in consensus[addr] && - consensus[addr][addr].Finalized === true - ); - } - - private getFinalizedHash(authority: Types.Authority): Maybe { - if (this.isFinalized(authority)) { - const { Address: addr } = authority; - return this.props.consensusView[addr][addr].FinalizedHash; - } - return null; - } - - private renderMatriceRow( - authority: Types.Authority, - authorities: Types.Authority[], - row: number - ): JSX.Element { - let finalizedInfo =  ; - let finalizedHash; - - if (authority.NodeId != null && this.isFinalized(authority)) { - const matrice = this.props.consensusView[authority.Address][ - authority.Address - ]; - - finalizedInfo = matrice.ImplicitFinalized ? ( - - ) : ( - - ); - - finalizedHash = matrice.FinalizedHash ? ( - - ) : ( -
 
- ); - } - - const name = authority.Name ? ( - {authority.Name} - ) : ( - no data received from node - ); - const firstName = this.props.firstInRow ? ( - - {name} - - ) : ( - '' - ); - - return ( - - {firstName} - - {this.getAuthorityContent(authority)} - - - {finalizedInfo} - {finalizedHash} - - {authorities.map((columnNode, column) => { - const evenOdd = ((row % 2) + column) % 2 === 0 ? 'even' : 'odd'; - return ( - - {this.getCellContent(authority, columnNode)} - - ); - })} - - ); - } - - private getAuthorityContent(authority: Types.Authority): JSX.Element { - return ( -
-
- -
-
- ); - } - - private getCellContent( - rowAuthority: Types.Authority, - columnAuthority: Types.Authority - ) { - const consensusInfo = - this.props.consensusView && - rowAuthority.Address && - rowAuthority.Address in this.props.consensusView && - columnAuthority.Address in this.props.consensusView[rowAuthority.Address] - ? this.props.consensusView[rowAuthority.Address][ - columnAuthority.Address - ] - : null; - - const prevote = consensusInfo && consensusInfo.Prevote; - const implicitPrevote = consensusInfo && consensusInfo.ImplicitPrevote; - - const precommit = consensusInfo && consensusInfo.Precommit; - const implicitPrecommit = consensusInfo && consensusInfo.ImplicitPrecommit; - - if (rowAuthority.Address !== columnAuthority.Address) { - let statPrevote; - let statPrecommit; - - if (implicitPrevote) { - statPrevote = ; - } - if (implicitPrecommit) { - statPrecommit = ; - } - - if (prevote) { - statPrevote = ; - } - if (precommit) { - statPrecommit = ; - } - - return ( - - {statPrevote} - {statPrecommit} - - ); - } else { - return ; - } - } -} diff --git a/frontend/src/components/Consensus/Jdenticon.css b/frontend/src/components/Consensus/Jdenticon.css deleted file mode 100644 index c40961f..0000000 --- a/frontend/src/components/Consensus/Jdenticon.css +++ /dev/null @@ -1,27 +0,0 @@ -/* -Source code for the Substrate Telemetry Server. -Copyright (C) 2021 Parity Technologies (UK) Ltd. - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -.Jdenticon { - cursor: pointer; - vertical-align: middle; - background-color: #fff; -} - -.Jdenticon:hover { - transform: scale(2); -} diff --git a/frontend/src/components/Consensus/Jdenticon.tsx b/frontend/src/components/Consensus/Jdenticon.tsx deleted file mode 100644 index 02c03bc..0000000 --- a/frontend/src/components/Consensus/Jdenticon.tsx +++ /dev/null @@ -1,61 +0,0 @@ -// Source code for the Substrate Telemetry Server. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -import * as React from 'react'; - -import './Jdenticon.css'; - -export interface Props { - hash: string; - size: string; -} - -class Jdenticon extends React.Component { - private element = null; - - public componentDidUpdate() { - const jdenticon = (window as any).jdenticon; - if (jdenticon) { - jdenticon.update(this.element); - } - } - - public componentDidMount() { - const jdenticon = (window as any).jdenticon; - if (jdenticon) { - jdenticon.update(this.element); - } - } - - public render() { - const { hash, size } = this.props; - return ( - this.handleRef(element)} - width={size} - height={size} - data-jdenticon-value={hash} - /> - ); - } - - private handleRef(element: any) { - this.element = element; - } -} - -export default Jdenticon; diff --git a/frontend/src/components/Consensus/index.ts b/frontend/src/components/Consensus/index.ts deleted file mode 100644 index bdc4ed2..0000000 --- a/frontend/src/components/Consensus/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Source code for the Substrate Telemetry Server. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -export * from './Consensus'; -export * from './ConsensusBlock'; diff --git a/frontend/src/components/index.ts b/frontend/src/components/index.ts index db4ab5d..1a08853 100644 --- a/frontend/src/components/index.ts +++ b/frontend/src/components/index.ts @@ -20,7 +20,6 @@ export * from './Chain'; export * from './List'; export * from './Map'; export * from './Settings'; -export * from './Consensus'; export * from './Icon'; export * from './Tile'; export * from './Ago'; diff --git a/frontend/src/state.ts b/frontend/src/state.ts index 29510b2..2ba1e7d 100644 --- a/frontend/src/state.ts +++ b/frontend/src/state.ts @@ -268,12 +268,7 @@ export interface State { status: 'online' | 'offline' | 'upgrade-requested'; best: Types.BlockNumber; finalized: Types.BlockNumber; - consensusInfo: Types.ConsensusInfo; - displayConsensusLoadingScreen: boolean; tab: string; - authorities: Types.Address[]; - authoritySetId: Maybe; - sendFinality: boolean; blockTimestamp: Types.Timestamp; blockAverage: Maybe; timeDiff: Types.Milliseconds;