mirror of
https://github.com/pezkuwichain/pezkuwi-telemetry.git
synced 2026-06-09 19:11:01 +00:00
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:
@@ -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,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
},
|
||||
]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'>;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user