Subscribe to chains by genesis hash (#395)

* Handle subscription by hash in the frontend

* Forward-ported backend changes

* Fix unit tests

* Remove unused `chains_by_label`

* fmt

* Updated but failing E2E tests

* subscribe by genesis hash in tests

* fmt

* Copy `BlockHash` instead of returning a ref

* Pin chains by genesisHash

Co-authored-by: James Wilson <james@jsdw.me>
This commit is contained in:
Maciej Hirsz
2021-09-02 17:54:19 +02:00
committed by GitHub
parent ec5db0fbbf
commit a4069e4b3d
16 changed files with 156 additions and 140 deletions
@@ -94,7 +94,7 @@ pub enum FromFeedWebsocket {
},
/// The feed can subscribe to a chain to receive
/// messages relating to it.
Subscribe { chain: Box<str> },
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.
@@ -136,12 +136,16 @@ impl FromStr for FromFeedWebsocket {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (cmd, value) = match s.find(':') {
Some(idx) => (&s[..idx], s[idx + 1..].into()),
Some(idx) => (&s[..idx], &s[idx + 1..]),
None => return Err(anyhow::anyhow!("Expecting format `CMD:CHAIN_NAME`")),
};
match cmd {
"ping" => Ok(FromFeedWebsocket::Ping { value }),
"subscribe" => Ok(FromFeedWebsocket::Subscribe { chain: value }),
"ping" => Ok(FromFeedWebsocket::Ping {
value: value.into(),
}),
"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)),
@@ -306,7 +310,7 @@ impl InnerLoop {
let chain_genesis_hash = self
.node_state
.get_chain_by_node_id(node_id)
.map(|chain| *chain.genesis_hash());
.map(|chain| chain.genesis_hash());
if let Some(chain_genesis_hash) = chain_genesis_hash {
self.finalize_and_broadcast_to_chain_feeds(
@@ -353,7 +357,6 @@ impl InnerLoop {
self.node_ids.insert(node_id, (shard_conn_id, local_id));
// Don't hold onto details too long because we want &mut self later:
let old_chain_label = details.old_chain_label.to_owned();
let new_chain_label = details.new_chain_label.to_owned();
let chain_node_count = details.chain_node_count;
let has_chain_label_changed = details.has_chain_label_changed;
@@ -371,11 +374,13 @@ impl InnerLoop {
// Tell everybody about the new node count and potential rename:
let mut feed_messages_for_all = FeedMessageSerializer::new();
if has_chain_label_changed {
feed_messages_for_all
.push(feed_message::RemovedChain(&old_chain_label));
feed_messages_for_all.push(feed_message::RemovedChain(genesis_hash));
}
feed_messages_for_all
.push(feed_message::AddedChain(&new_chain_label, chain_node_count));
feed_messages_for_all.push(feed_message::AddedChain(
&new_chain_label,
genesis_hash,
chain_node_count,
));
self.finalize_and_broadcast_to_all_feeds(feed_messages_for_all);
// Ask for the grographical location of the node.
@@ -419,7 +424,7 @@ impl InnerLoop {
.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();
let genesis_hash = chain.genesis_hash();
if broadcast_finality {
self.finalize_and_broadcast_to_chain_finality_feeds(
&genesis_hash,
@@ -456,10 +461,13 @@ impl InnerLoop {
// Tell the new feed subscription some basic things to get it going:
let mut feed_serializer = FeedMessageSerializer::new();
feed_serializer.push(feed_message::Version(31));
feed_serializer.push(feed_message::Version(32));
for chain in self.node_state.iter_chains() {
feed_serializer
.push(feed_message::AddedChain(chain.label(), chain.node_count()));
feed_serializer.push(feed_message::AddedChain(
chain.label(),
chain.genesis_hash(),
chain.node_count(),
));
}
// Send this to the channel that subscribed:
@@ -498,7 +506,7 @@ impl InnerLoop {
old_genesis_hash.and_then(|hash| node_state.get_chain_by_genesis_hash(&hash));
// Get new chain, ignoring the rest if it doesn't exist.
let new_chain = match self.node_state.get_chain_by_label(&chain) {
let new_chain = match self.node_state.get_chain_by_genesis_hash(&chain) {
Some(chain) => chain,
None => return,
};
@@ -506,9 +514,9 @@ impl InnerLoop {
// Send messages to the feed about this subscription:
let mut feed_serializer = FeedMessageSerializer::new();
if let Some(old_chain) = old_chain {
feed_serializer.push(feed_message::UnsubscribedFrom(old_chain.label()));
feed_serializer.push(feed_message::UnsubscribedFrom(old_chain.genesis_hash()));
}
feed_serializer.push(feed_message::SubscribedTo(new_chain.label()));
feed_serializer.push(feed_message::SubscribedTo(new_chain.genesis_hash()));
feed_serializer.push(feed_message::TimeSync(time::now()));
feed_serializer.push(feed_message::BestBlock(
new_chain.best_block().height,
@@ -559,7 +567,7 @@ impl InnerLoop {
}
// Actually make a note of the new chain subsciption:
let new_genesis_hash = *new_chain.genesis_hash();
let new_genesis_hash = new_chain.genesis_hash();
self.chain_to_feed_conn_ids
.insert(new_genesis_hash, feed_conn_id);
}
@@ -585,7 +593,7 @@ impl InnerLoop {
for node_id in node_ids.into_iter() {
if let Some(chain) = self.node_state.get_chain_by_node_id(node_id) {
node_ids_per_chain
.entry(*chain.genesis_hash())
.entry(chain.genesis_hash())
.or_default()
.push(node_id);
}
@@ -629,13 +637,16 @@ impl InnerLoop {
// The chain has been removed (no nodes left in it, or it was renamed):
if removed_details.chain_node_count == 0 || removed_details.has_chain_label_changed {
feed_for_all.push(feed_message::RemovedChain(&removed_details.old_chain_label));
feed_for_all.push(feed_message::RemovedChain(
removed_details.chain_genesis_hash,
));
}
// If the chain still exists, tell everybody about the new label or updated node count:
if removed_details.chain_node_count != 0 {
feed_for_all.push(feed_message::AddedChain(
&removed_details.new_chain_label,
removed_details.chain_genesis_hash,
removed_details.chain_node_count,
));
}
+7 -7
View File
@@ -115,9 +115,9 @@ actions! {
9: Hardware<'_>,
10: TimeSync,
11: AddedChain<'_>,
12: RemovedChain<'_>,
13: SubscribedTo<'_>,
14: UnsubscribedFrom<'_>,
12: RemovedChain,
13: SubscribedTo,
14: UnsubscribedFrom,
15: Pong<'_>,
16: AfgFinalized,
17: AfgReceivedPrevote,
@@ -163,16 +163,16 @@ pub struct Hardware<'a>(pub FeedNodeId, pub &'a NodeHardware);
pub struct TimeSync(pub u64);
#[derive(Serialize)]
pub struct AddedChain<'a>(pub &'a str, pub usize);
pub struct AddedChain<'a>(pub &'a str, pub BlockHash, pub usize);
#[derive(Serialize)]
pub struct RemovedChain<'a>(pub &'a str);
pub struct RemovedChain(pub BlockHash);
#[derive(Serialize)]
pub struct SubscribedTo<'a>(pub &'a str);
pub struct SubscribedTo(pub BlockHash);
#[derive(Serialize)]
pub struct UnsubscribedFrom<'a>(pub &'a str);
pub struct UnsubscribedFrom(pub BlockHash);
#[derive(Serialize)]
pub struct Pong<'a>(pub &'a str);
+2 -2
View File
@@ -369,8 +369,8 @@ impl Chain {
pub fn finalized_block(&self) -> &Block {
&self.finalized
}
pub fn genesis_hash(&self) -> &BlockHash {
&self.genesis_hash
pub fn genesis_hash(&self) -> BlockHash {
self.genesis_hash
}
}
+8 -36
View File
@@ -47,7 +47,6 @@ pub struct State {
// Find the right chain given various details.
chains_by_genesis_hash: HashMap<BlockHash, ChainId>,
chains_by_label: HashMap<Box<str>, ChainId>,
/// Chain labels that we do not want to allow connecting.
denylist: HashSet<String>,
@@ -96,6 +95,8 @@ pub struct RemovedNode {
pub has_chain_label_changed: bool,
/// The old label of the chain.
pub old_chain_label: Box<str>,
/// Genesis hash of the chain to be updated.
pub chain_genesis_hash: BlockHash,
/// The new label of the chain.
pub new_chain_label: Box<str>,
}
@@ -105,7 +106,6 @@ impl State {
State {
chains: DenseMap::new(),
chains_by_genesis_hash: HashMap::new(),
chains_by_label: HashMap::new(),
denylist: denylist.into_iter().collect(),
}
}
@@ -127,13 +127,6 @@ impl State {
.map(|chain| StateChain { chain })
}
pub fn get_chain_by_label(&self, label: &str) -> Option<StateChain<'_>> {
self.chains_by_label
.get(label)
.and_then(|&chain_id| self.chains.get(chain_id))
.map(|chain| StateChain { chain })
}
pub fn add_node(
&mut self,
genesis_hash: BlockHash,
@@ -169,17 +162,10 @@ impl State {
chain::AddNodeResult::Added { id, chain_renamed } => {
let chain = &*chain;
// Update the label we use to reference the chain if
// it changes (it'll always change first time a node's added):
if chain_renamed {
self.chains_by_label.remove(&old_chain_label);
self.chains_by_label.insert(chain.label().into(), chain_id);
}
AddNodeResult::NodeAddedToChain(NodeAddedToChain {
id: NodeId(chain_id, id),
node: chain.get_node(id).expect("node added above"),
old_chain_label: old_chain_label,
old_chain_label,
new_chain_label: chain.label(),
chain_node_count: chain.node_count(),
has_chain_label_changed: chain_renamed,
@@ -199,26 +185,20 @@ impl State {
// Get updated chain details.
let new_chain_label: Box<str> = chain.label().into();
let chain_node_count = chain.node_count();
let chain_genesis_hash = chain.genesis_hash();
// Is the chain empty? Remove if so and clean up indexes to it
if chain_node_count == 0 {
let genesis_hash = *chain.genesis_hash();
self.chains_by_label.remove(&old_chain_label);
let genesis_hash = chain.genesis_hash();
self.chains_by_genesis_hash.remove(&genesis_hash);
self.chains.remove(chain_id);
}
// Make sure chains always referenced by their most common label:
if remove_result.chain_renamed {
self.chains_by_label.remove(&old_chain_label);
self.chains_by_label
.insert(new_chain_label.clone(), chain_id);
}
Some(RemovedNode {
old_chain_label,
new_chain_label,
chain_node_count: chain_node_count,
chain_node_count,
chain_genesis_hash,
has_chain_label_changed: remove_result.chain_renamed,
})
}
@@ -268,7 +248,7 @@ impl<'a> StateChain<'a> {
pub fn label(&self) -> &'a str {
self.chain.label()
}
pub fn genesis_hash(&self) -> &'a BlockHash {
pub fn genesis_hash(&self) -> BlockHash {
self.chain.genesis_hash()
}
pub fn node_count(&self) -> usize {
@@ -358,7 +338,6 @@ mod test {
.label(),
"Chain One"
);
assert!(state.get_chain_by_label("Chain One").is_some());
assert!(state.get_chain_by_genesis_hash(&chain1_genesis).is_some());
let node_id1 = state
@@ -373,7 +352,6 @@ mod test {
.label(),
"Chain One"
);
assert!(state.get_chain_by_label("Chain One").is_some());
assert!(state.get_chain_by_genesis_hash(&chain1_genesis).is_some());
let node_id2 = state
@@ -388,8 +366,6 @@ mod test {
.label(),
"Chain Two"
);
assert!(state.get_chain_by_label("Chain One").is_none());
assert!(state.get_chain_by_label("Chain Two").is_some());
assert!(state.get_chain_by_genesis_hash(&chain1_genesis).is_some());
state.remove_node(node_id1).expect("Removal OK (id: 1)");
@@ -403,8 +379,6 @@ mod test {
.label(),
"Chain One"
);
assert!(state.get_chain_by_label("Chain One").is_some());
assert!(state.get_chain_by_label("Chain Two").is_none());
assert!(state.get_chain_by_genesis_hash(&chain1_genesis).is_some());
}
@@ -417,13 +391,11 @@ mod test {
.add_node(chain1_genesis, node("A", "Chain One")) // 0
.unwrap_id();
assert!(state.get_chain_by_label("Chain One").is_some());
assert!(state.get_chain_by_genesis_hash(&chain1_genesis).is_some());
assert_eq!(state.iter_chains().count(), 1);
state.remove_node(node_id);
assert!(state.get_chain_by_label("Chain One").is_none());
assert!(state.get_chain_by_genesis_hash(&chain1_genesis).is_none());
assert_eq!(state.iter_chains().count(), 0);
}
+49 -27
View File
@@ -46,6 +46,11 @@ use test_utils::{
workspace::{start_server, start_server_debug, CoreOpts, ServerOpts, ShardOpts},
};
/// Helper for concise testing
fn ghash(id: u64) -> BlockHash {
BlockHash::from_low_u64_be(id)
}
/// The simplest test we can run; the main benefit of this test (since we check similar)
/// below) is just to give a feel for _how_ we can test basic feed related things.
#[ignore]
@@ -60,7 +65,7 @@ async fn e2e_feed_sent_version_on_connect() {
let feed_messages = feed_rx.recv_feed_messages().await.unwrap();
assert_eq!(
feed_messages,
vec![FeedMessage::Version(31)],
vec![FeedMessage::Version(32)],
"expecting version"
);
@@ -120,7 +125,7 @@ async fn e2e_multiple_feeds_sent_version_on_connect() {
for feed_messages in responses {
assert_eq!(
feed_messages.expect("should have messages"),
vec![FeedMessage::Version(31)],
vec![FeedMessage::Version(32)],
"expecting version"
);
}
@@ -159,7 +164,7 @@ async fn e2e_lots_of_mute_messages_dont_cause_a_deadlock() {
"authority":true,
"chain":"Local Testnet",
"config":"",
"genesis_hash": BlockHash::from_low_u64_ne(1),
"genesis_hash": ghash(1),
"implementation":"Substrate Node",
"msg":"system.connected",
"name": format!("Alice {}", idx),
@@ -217,7 +222,7 @@ async fn e2e_feed_add_and_remove_node() {
"authority":true,
"chain":"Local Testnet",
"config":"",
"genesis_hash": BlockHash::from_low_u64_ne(1),
"genesis_hash": ghash(1),
"implementation":"Substrate Node",
"msg":"system.connected",
"name":"Alice",
@@ -239,7 +244,8 @@ async fn e2e_feed_add_and_remove_node() {
let feed_messages = feed_rx.recv_feed_messages().await.unwrap();
assert!(feed_messages.contains(&FeedMessage::AddedChain {
name: "Local Testnet".to_owned(),
node_count: 1
genesis_hash: ghash(1),
node_count: 1,
}));
// Disconnect the node:
@@ -247,7 +253,7 @@ async fn e2e_feed_add_and_remove_node() {
let feed_messages = feed_rx.recv_feed_messages().await.unwrap();
assert!(feed_messages.contains(&FeedMessage::RemovedChain {
name: "Local Testnet".to_owned(),
genesis_hash: ghash(1),
}));
// Tidy up:
@@ -278,7 +284,7 @@ async fn e2e_feeds_told_about_chain_rename_and_stay_subscribed() {
"authority":true,
"chain": chain_name,
"config":"",
"genesis_hash": BlockHash::from_low_u64_ne(1),
"genesis_hash": ghash(1),
"implementation":"Substrate Node",
"msg":"system.connected",
"name": node_name,
@@ -297,15 +303,18 @@ async fn e2e_feeds_told_about_chain_rename_and_stay_subscribed() {
// Connect a feed and subscribe to the above chain:
let (feed_tx, mut feed_rx) = server.get_core().connect_feed().await.unwrap();
feed_tx
.send_command("subscribe", "Initial chain name")
.send_command(
"subscribe",
"0x0000000000000000000000000000000000000000000000000000000000000001",
)
.unwrap();
// Feed is told about the chain, and the node on this chain:
let feed_messages = feed_rx.recv_feed_messages().await.unwrap();
assert_contains_matches!(
feed_messages,
FeedMessage::AddedChain { name, node_count: 1 } if name == "Initial chain name",
FeedMessage::SubscribedTo { name } if name == "Initial chain name",
FeedMessage::AddedChain { name, genesis_hash, node_count: 1 } if name == "Initial chain name" && genesis_hash == ghash(1),
FeedMessage::SubscribedTo { genesis_hash } if genesis_hash == ghash(1),
FeedMessage::AddedNode { node: NodeDetails { name: node_name, .. }, ..} if node_name == "Node 1",
);
@@ -318,7 +327,7 @@ async fn e2e_feeds_told_about_chain_rename_and_stay_subscribed() {
assert_contains_matches!(
feed_messages,
FeedMessage::AddedNode { node: NodeDetails { name: node_name, .. }, ..} if node_name == "Node 2",
FeedMessage::AddedChain { name, node_count: 2 } if name == "Initial chain name",
FeedMessage::AddedChain { name, genesis_hash, node_count: 2 } if name == "Initial chain name" && genesis_hash == ghash(1),
);
// Subscribe a third node. The chain renames, so we're told about the new node but also
@@ -330,8 +339,8 @@ async fn e2e_feeds_told_about_chain_rename_and_stay_subscribed() {
assert_contains_matches!(
feed_messages,
FeedMessage::AddedNode { node: NodeDetails { name: node_name, .. }, ..} if node_name == "Node 3",
FeedMessage::RemovedChain { name } if name == "Initial chain name",
FeedMessage::AddedChain { name, node_count: 3 } if name == "New chain name",
FeedMessage::RemovedChain { genesis_hash } if genesis_hash == ghash(1),
FeedMessage::AddedChain { name, genesis_hash, node_count: 3 } if name == "New chain name" && genesis_hash == ghash(1),
);
// Just to be sure, subscribing a fourth node on this chain will still lead to updates
@@ -343,7 +352,7 @@ async fn e2e_feeds_told_about_chain_rename_and_stay_subscribed() {
assert_contains_matches!(
feed_messages,
FeedMessage::AddedNode { node: NodeDetails { name: node_name, .. }, ..} if node_name == "Node 4",
FeedMessage::AddedChain { name, node_count: 4 } if name == "New chain name",
FeedMessage::AddedChain { name, genesis_hash, node_count: 4 } if name == "New chain name" && genesis_hash == ghash(1),
);
}
@@ -377,7 +386,7 @@ async fn e2e_feed_add_and_remove_shard() {
"authority":true,
"chain": format!("Local Testnet {}", id),
"config":"",
"genesis_hash": BlockHash::from_low_u64_ne(id),
"genesis_hash": ghash(id),
"implementation":"Substrate Node",
"msg":"system.connected",
"name":"Alice",
@@ -399,10 +408,12 @@ async fn e2e_feed_add_and_remove_shard() {
let feed_messages = feed_rx.recv_feed_messages().await.unwrap();
assert!(feed_messages.contains(&FeedMessage::AddedChain {
name: "Local Testnet 1".to_owned(),
genesis_hash: ghash(1),
node_count: 1
}));
assert!(feed_messages.contains(&FeedMessage::AddedChain {
name: "Local Testnet 2".to_owned(),
genesis_hash: ghash(2),
node_count: 1
}));
@@ -412,12 +423,12 @@ async fn e2e_feed_add_and_remove_shard() {
// We should be told about the node connected to that shard disconnecting:
let feed_messages = feed_rx.recv_feed_messages().await.unwrap();
assert!(feed_messages.contains(&FeedMessage::RemovedChain {
name: "Local Testnet 1".to_owned(),
genesis_hash: ghash(1),
}));
assert!(!feed_messages.contains(
// Spot the "!"; this chain was not removed.
&FeedMessage::RemovedChain {
name: "Local Testnet 2".to_owned(),
genesis_hash: ghash(2),
}
));
@@ -453,7 +464,7 @@ async fn e2e_feed_can_subscribe_and_unsubscribe_from_chain() {
"authority":true,
"chain":format!("Local Testnet {}", id),
"config":"",
"genesis_hash": BlockHash::from_low_u64_ne(id),
"genesis_hash": ghash(id),
"implementation":"Substrate Node",
"msg":"system.connected",
"name":format!("Alice {}", id),
@@ -470,17 +481,20 @@ async fn e2e_feed_can_subscribe_and_unsubscribe_from_chain() {
let (feed_tx, mut feed_rx) = server.get_core().connect_feed().await.unwrap();
let feed_messages = feed_rx.recv_feed_messages().await.unwrap();
assert_contains_matches!(feed_messages, AddedChain { name, node_count: 1 } if name == "Local Testnet 1");
assert_contains_matches!(feed_messages, AddedChain { name, genesis_hash, node_count: 1 } if name == "Local Testnet 1" && genesis_hash == ghash(1));
// Subscribe it to a chain
feed_tx
.send_command("subscribe", "Local Testnet 1")
.send_command(
"subscribe",
"0x0000000000000000000000000000000000000000000000000000000000000001",
)
.unwrap();
let feed_messages = feed_rx.recv_feed_messages().await.unwrap();
assert_contains_matches!(
feed_messages,
SubscribedTo { name } if name == "Local Testnet 1",
SubscribedTo { genesis_hash } if genesis_hash == ghash(1),
TimeSync {..},
BestBlock { block_number: 0, timestamp: 0, avg_block_time: None },
BestFinalized { block_number: 0, .. },
@@ -507,15 +521,18 @@ async fn e2e_feed_can_subscribe_and_unsubscribe_from_chain() {
// We can change our subscription:
feed_tx
.send_command("subscribe", "Local Testnet 2")
.send_command(
"subscribe",
"0x0000000000000000000000000000000000000000000000000000000000000002",
)
.unwrap();
let feed_messages = feed_rx.recv_feed_messages().await.unwrap();
// We are told about the subscription change and given similar on-subscribe messages to above.
assert_contains_matches!(
&feed_messages,
UnsubscribedFrom { name } if name == "Local Testnet 1",
SubscribedTo { name } if name == "Local Testnet 2",
UnsubscribedFrom { genesis_hash } if *genesis_hash == ghash(1),
SubscribedTo { genesis_hash } if *genesis_hash == ghash(2),
TimeSync {..},
BestBlock {..},
BestFinalized {..},
@@ -630,7 +647,7 @@ async fn e2e_slow_feeds_are_disconnected() {
"authority":true,
"chain":"Polkadot",
"config":"",
"genesis_hash": BlockHash::from_low_u64_ne(1),
"genesis_hash": ghash(1),
"implementation":"Substrate Node",
"msg":"system.connected",
"name": format!("Alice {}", n),
@@ -721,7 +738,7 @@ async fn e2e_max_nodes_per_connection_is_enforced() {
"authority":true,
"chain":"Test Chain",
"config":"",
"genesis_hash": BlockHash::from_low_u64_ne(1),
"genesis_hash": ghash(1),
"implementation":"Polkadot",
"msg":"system.connected",
"name": format!("Alice {}", n),
@@ -779,7 +796,12 @@ async fn e2e_max_nodes_per_connection_is_enforced() {
// (now that the chain "Test Chain" is known about, subscribe to it for update messages.
// This wasn't needed to receive messages re the above since everybody hears about node
// count changes)
feed_tx.send_command("subscribe", "Test Chain").unwrap();
feed_tx
.send_command(
"subscribe",
"0x0000000000000000000000000000000000000000000000000000000000000001",
)
.unwrap();
feed_rx.recv_feed_messages().await.unwrap();
// Update about non-ignored IDs should still lead to feed output:
+21 -15
View File
@@ -73,16 +73,17 @@ pub enum FeedMessage {
},
AddedChain {
name: String,
genesis_hash: BlockHash,
node_count: usize,
},
RemovedChain {
name: String,
genesis_hash: BlockHash,
},
SubscribedTo {
name: String,
genesis_hash: BlockHash,
},
UnsubscribedFrom {
name: String,
genesis_hash: BlockHash,
},
Pong {
msg: String,
@@ -260,23 +261,27 @@ impl FeedMessage {
}
// AddedChain
11 => {
let (name, node_count) = serde_json::from_str(raw_val.get())?;
FeedMessage::AddedChain { name, node_count }
let (name, genesis_hash, node_count) = serde_json::from_str(raw_val.get())?;
FeedMessage::AddedChain {
name,
genesis_hash,
node_count,
}
}
// RemovedChain
12 => {
let name = serde_json::from_str(raw_val.get())?;
FeedMessage::RemovedChain { name }
let genesis_hash = serde_json::from_str(raw_val.get())?;
FeedMessage::RemovedChain { genesis_hash }
}
// SubscribedTo
13 => {
let name = serde_json::from_str(raw_val.get())?;
FeedMessage::SubscribedTo { name }
let genesis_hash = serde_json::from_str(raw_val.get())?;
FeedMessage::SubscribedTo { genesis_hash }
}
// UnsubscribedFrom
14 => {
let name = serde_json::from_str(raw_val.get())?;
FeedMessage::UnsubscribedFrom { name }
let genesis_hash = serde_json::from_str(raw_val.get())?;
FeedMessage::UnsubscribedFrom { genesis_hash }
}
// Pong
15 => {
@@ -355,12 +360,12 @@ mod test {
#[test]
fn decode_remove_node_msg() {
// "remove chain ''":
let msg = r#"[12,""]"#;
let msg = r#"[12,"0x0000000000000000000000000000000000000000000000000000000000000000"]"#;
assert_eq!(
FeedMessage::from_bytes(msg.as_bytes()).unwrap(),
vec![FeedMessage::RemovedChain {
name: "".to_owned()
genesis_hash: BlockHash::zero(),
}]
);
}
@@ -368,16 +373,17 @@ mod test {
#[test]
fn decode_remove_then_add_node_msg() {
// "remove chain '', then add chain 'Local Testnet' with 1 node":
let msg = r#"[12,"",11,["Local Testnet",1]]"#;
let msg = r#"[12,"0x0000000000000000000000000000000000000000000000000000000000000000",11,["Local Testnet","0x0000000000000000000000000000000000000000000000000000000000000000",1]]"#;
assert_eq!(
FeedMessage::from_bytes(msg.as_bytes()).unwrap(),
vec![
FeedMessage::RemovedChain {
name: "".to_owned()
genesis_hash: BlockHash::zero(),
},
FeedMessage::AddedChain {
name: "Local Testnet".to_owned(),
genesis_hash: BlockHash::zero(),
node_count: 1
},
]
+1 -1
View File
@@ -228,7 +228,7 @@ export default class App extends React.Component<{}, {}> {
this.chainsCache = stable.inplace(
Array.from(this.appState.chains.values()),
(a, b) => {
const pinned = comparePinnedChains(a.label, b.label);
const pinned = comparePinnedChains(a.genesisHash, b.genesisHash);
if (pinned !== 0) {
return pinned;
+8 -8
View File
@@ -119,7 +119,7 @@ export class Connection {
// timestamp at which the last ping has been sent
private pingSent: Maybe<Types.Timestamp> = null;
// chain label to resubsribe to on reconnect
private resubscribeTo: Maybe<Types.ChainLabel> = getHashData().chain;
private resubscribeTo: Maybe<Types.GenesisHash> = getHashData().chain;
// flag whether or not FE should subscribe to consensus updates on reconnect
private resubscribeSendFinality: boolean = getHashData().tab === 'consensus';
@@ -132,7 +132,7 @@ export class Connection {
this.bindSocket();
}
public subscribe(chain: Types.ChainLabel) {
public subscribe(chain: Types.GenesisHash) {
if (
this.appState.subscribed != null &&
this.appState.subscribed !== chain
@@ -148,7 +148,7 @@ export class Connection {
this.socket.send(`subscribe:${chain}`);
}
public subscribeConsensus(chain: Types.ChainLabel) {
public subscribeConsensus(chain: Types.GenesisHash) {
if (this.appState.authorities.length <= VIS_AUTHORITIES_LIMIT) {
setHashData({ chain });
this.resubscribeSendFinality = true;
@@ -165,7 +165,7 @@ export class Connection {
});
}
public unsubscribeConsensus(chain: Types.ChainLabel) {
public unsubscribeConsensus(chain: Types.GenesisHash) {
this.resubscribeSendFinality = true;
this.socket.send(`no-more-finality:${chain}`);
}
@@ -334,13 +334,13 @@ export class Connection {
}
case ACTIONS.AddedChain: {
const [label, nodeCount] = message.payload;
const chain = chains.get(label);
const [label, genesisHash, nodeCount] = message.payload;
const chain = chains.get(genesisHash);
if (chain) {
chain.nodeCount = nodeCount;
} else {
chains.set(label, { label, nodeCount });
chains.set(genesisHash, { label, genesisHash, nodeCount });
}
this.appUpdate({ chains });
@@ -552,7 +552,7 @@ export class Connection {
}
if (topChain) {
this.subscribe(topChain.label);
this.subscribe(topChain.genesisHash);
}
}
+5 -4
View File
@@ -35,6 +35,7 @@ import {
Timestamp,
Milliseconds,
ChainLabel,
GenesisHash,
AuthoritySetInfo,
} from './types';
@@ -142,22 +143,22 @@ export namespace Variants {
export interface AddedChainMessage extends MessageBase {
action: typeof ACTIONS.AddedChain;
payload: [ChainLabel, NodeCount];
payload: [ChainLabel, GenesisHash, NodeCount];
}
export interface RemovedChainMessage extends MessageBase {
action: typeof ACTIONS.RemovedChain;
payload: ChainLabel;
payload: GenesisHash;
}
export interface SubscribedToMessage extends MessageBase {
action: typeof ACTIONS.SubscribedTo;
payload: ChainLabel;
payload: GenesisHash;
}
export interface UnsubscribedFromMessage extends MessageBase {
action: typeof ACTIONS.UnsubscribedFrom;
payload: ChainLabel;
payload: GenesisHash;
}
export interface PongMessage extends MessageBase {
+1 -1
View File
@@ -25,4 +25,4 @@ import * as FeedMessage from './feed';
export { Types, FeedMessage };
// Increment this if breaking changes were made to types in `feed.ts`
export const VERSION: Types.FeedVersion = 31 as Types.FeedVersion;
export const VERSION: Types.FeedVersion = 32 as Types.FeedVersion;
+1
View File
@@ -19,6 +19,7 @@ import { Id } from './id';
export type FeedVersion = Opaque<number, 'FeedVersion'>;
export type ChainLabel = Opaque<string, 'ChainLabel'>;
export type GenesisHash = Opaque<string, 'GenesisHash'>;
export type FeedId = Id<'Feed'>;
export type NodeId = Id<'Node'>;
export type NodeName = Opaque<string, 'NodeName'>;
+5 -5
View File
@@ -24,7 +24,7 @@ import './AllChains.css';
export namespace AllChains {
export interface Props {
chains: ChainData[];
subscribed: Maybe<Types.ChainLabel>;
subscribed: Maybe<Types.GenesisHash>;
connection: Promise<Connection>;
}
}
@@ -45,10 +45,10 @@ export class AllChains extends React.Component<AllChains.Props, {}> {
}
private renderChain(chain: ChainData): React.ReactNode {
const { label, nodeCount } = chain;
const { label, genesisHash, nodeCount } = chain;
const className =
label === this.props.subscribed
genesisHash === this.props.subscribed
? 'AllChains-chain AllChains-chain-selected'
: 'AllChains-chain';
@@ -56,7 +56,7 @@ export class AllChains extends React.Component<AllChains.Props, {}> {
<a
key={label}
className={className}
onClick={this.subscribe.bind(this, label)}
onClick={this.subscribe.bind(this, genesisHash)}
>
{label}{' '}
<span className="AllChains-node-count" title="Node Count">
@@ -66,7 +66,7 @@ export class AllChains extends React.Component<AllChains.Props, {}> {
);
}
private async subscribe(chain: Types.ChainLabel) {
private async subscribe(chain: Types.GenesisHash) {
const connection = await this.props.connection;
connection.subscribe(chain);
+6 -6
View File
@@ -27,7 +27,7 @@ import './Chains.css';
export namespace Chains {
export interface Props {
chains: ChainData[];
subscribed: Maybe<Types.ChainLabel>;
subscribed: Maybe<Types.GenesisHash>;
connection: Promise<Connection>;
}
}
@@ -39,7 +39,7 @@ const RENDER_THROTTLE = 1000;
export class Chains extends React.Component<Chains.Props, {}> {
private lastRender = performance.now();
private clicked: Maybe<Types.ChainLabel>;
private clicked: Maybe<Types.GenesisHash>;
public shouldComponentUpdate(nextProps: Chains.Props) {
if (nextProps.subscribed !== this.clicked) {
@@ -83,11 +83,11 @@ export class Chains extends React.Component<Chains.Props, {}> {
}
private renderChain(chain: ChainData): React.ReactNode {
const { label, nodeCount } = chain;
const { label, genesisHash, nodeCount } = chain;
let className = 'Chains-chain';
if (label === this.props.subscribed) {
if (genesisHash === this.props.subscribed) {
className += ' Chains-chain-selected';
}
@@ -95,7 +95,7 @@ export class Chains extends React.Component<Chains.Props, {}> {
<a
key={label}
className={className}
onClick={this.subscribe.bind(this, label)}
onClick={this.subscribe.bind(this, genesisHash)}
>
{label}
<span className="Chains-node-count" title="Node Count">
@@ -105,7 +105,7 @@ export class Chains extends React.Component<Chains.Props, {}> {
);
}
private async subscribe(chain: Types.ChainLabel) {
private async subscribe(chain: Types.GenesisHash) {
if (chain === this.clicked) {
return;
}
@@ -427,12 +427,12 @@ export class Consensus extends React.Component<Consensus.Props, {}> {
);
}
private async subscribeConsensus(chain: Types.ChainLabel) {
private async subscribeConsensus(chain: Types.GenesisHash) {
const connection = await this.props.connection;
connection.subscribeConsensus(chain);
}
private async unsubscribeConsensus(chain: Types.ChainLabel) {
private async unsubscribeConsensus(chain: Types.GenesisHash) {
const connection = await this.props.connection;
connection.unsubscribeConsensus(chain);
}
+7 -4
View File
@@ -19,8 +19,10 @@ import { Types, Maybe, SortedCollection } from './common';
import { Column } from './components/List';
export const PINNED_CHAINS = {
Kusama: 2,
Polkadot: 1,
// Kusama
'0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe': 2,
// Polkadot
'0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3': 1,
};
export function comparePinnedChains(a: string, b: string) {
@@ -275,8 +277,8 @@ export interface State {
blockTimestamp: Types.Timestamp;
blockAverage: Maybe<Types.Milliseconds>;
timeDiff: Types.Milliseconds;
subscribed: Maybe<Types.ChainLabel>;
chains: Map<Types.ChainLabel, ChainData>;
subscribed: Maybe<Types.GenesisHash>;
chains: Map<Types.GenesisHash, ChainData>;
nodes: SortedCollection<Node>;
settings: Readonly<State.Settings>;
pins: Readonly<Set<Types.NodeName>>;
@@ -290,5 +292,6 @@ export type Update = <K extends keyof State>(
export interface ChainData {
label: Types.ChainLabel;
genesisHash: Types.GenesisHash;
nodeCount: Types.NodeCount;
}
+2 -2
View File
@@ -88,7 +88,7 @@ export function secondsWithPrecision(num: number): string {
export interface HashData {
tab?: string;
chain?: Types.ChainLabel;
chain?: Types.GenesisHash;
}
export function getHashData(): HashData {
@@ -99,7 +99,7 @@ export function getHashData(): HashData {
}
const [tab, rawChain] = hash.substr(1).split('/');
const chain = decodeURIComponent(rawChain) as Types.ChainLabel;
const chain = decodeURIComponent(rawChain) as Types.GenesisHash;
return { tab, chain };
}