diff --git a/engine/src/dot/http_rpc.rs b/engine/src/dot/http_rpc.rs index 6dafa758dd6..9e00f9d3506 100644 --- a/engine/src/dot/http_rpc.rs +++ b/engine/src/dot/http_rpc.rs @@ -21,6 +21,7 @@ use subxt::{ }; use anyhow::Result; +use tracing::{error, warn}; use utilities::{make_periodic_tick, redact_endpoint_secret::SecretUrl}; use crate::constants::DOT_AVERAGE_BLOCK_TIME; @@ -80,7 +81,10 @@ pub struct DotHttpRpcClient { } impl DotHttpRpcClient { - pub fn new(url: SecretUrl) -> Result> { + pub fn new( + url: SecretUrl, + expected_genesis_hash: Option, + ) -> Result> { let polkadot_http_client = Arc::new(PolkadotHttpClient::new(&url)?); Ok(async move { @@ -94,9 +98,23 @@ impl DotHttpRpcClient { match OnlineClient::::from_rpc_client(polkadot_http_client.clone()) .await { - Ok(online_client) => break online_client, + Ok(online_client) => { + if let Some(expected_genesis_hash) = expected_genesis_hash { + let genesis_hash = online_client.genesis_hash(); + if genesis_hash == expected_genesis_hash { + break online_client + } else { + error!( + "Connected to Polkadot node at {url} but the genesis hash {genesis_hash} does not match the expected genesis hash {expected_genesis_hash}. Please check your CFE configuration file." + ) + } + } else { + warn!("Skipping Polkadot genesis hash check"); + break online_client + } + }, Err(e) => { - tracing::error!( + error!( "Failed to connect to Polkadot node at {url} with error: {e}. Please check your CFE configuration file. Retrying in {:?}...", poll_interval.period() @@ -201,7 +219,8 @@ mod tests { #[ignore = "requires local node"] #[tokio::test] async fn test_http_rpc() { - let dot_http_rpc = DotHttpRpcClient::new("http://localhost:9945".into()).unwrap().await; + let dot_http_rpc = + DotHttpRpcClient::new("http://localhost:9945".into(), None).unwrap().await; let block_hash = dot_http_rpc.block_hash(1).await.unwrap(); println!("block_hash: {:?}", block_hash); } diff --git a/engine/src/dot/retry_rpc.rs b/engine/src/dot/retry_rpc.rs index 8becefa1e88..8febd7f16a1 100644 --- a/engine/src/dot/retry_rpc.rs +++ b/engine/src/dot/retry_rpc.rs @@ -44,6 +44,16 @@ impl DotRetryRpcClient { pub fn new( scope: &Scope<'_, anyhow::Error>, nodes: NodeContainer, + expected_genesis_hash: PolkadotHash, + ) -> Result { + Self::new_inner(scope, nodes, Some(expected_genesis_hash)) + } + + fn new_inner( + scope: &Scope<'_, anyhow::Error>, + nodes: NodeContainer, + // The genesis hash is optional to facilitate testing + expected_genesis_hash: Option, ) -> Result { let f_create_clients = |endpoints: WsHttpEndpoints| { Result::<_, anyhow::Error>::Ok(( @@ -57,7 +67,7 @@ impl DotRetryRpcClient { let (backup_rpc_client, backup_sub_client) = option_inner(nodes.backup.map(f_create_clients).transpose()?); - Ok(Self { + Ok(DotRetryRpcClient { rpc_retry_client: RetrierClient::new( scope, "dot_rpc", @@ -306,7 +316,7 @@ mod tests { async fn my_test() { task_scope(|scope| { async move { - let dot_retry_rpc_client = DotRetryRpcClient::new( + let dot_retry_rpc_client = DotRetryRpcClient::new_inner( scope, NodeContainer { primary: WsHttpEndpoints { @@ -315,6 +325,7 @@ mod tests { }, backup: None, }, + None, ) .unwrap(); diff --git a/engine/src/dot/rpc.rs b/engine/src/dot/rpc.rs index 1d55829889f..59807a08991 100644 --- a/engine/src/dot/rpc.rs +++ b/engine/src/dot/rpc.rs @@ -12,6 +12,7 @@ use subxt::{ Config, OnlineClient, PolkadotConfig, }; use tokio::sync::RwLock; +use tracing::warn; use utilities::redact_endpoint_secret::SecretUrl; use anyhow::{anyhow, Result}; @@ -136,11 +137,12 @@ impl DotRpcApi for DotRpcClient { #[derive(Clone)] pub struct DotSubClient { pub ws_endpoint: SecretUrl, + expected_genesis_hash: Option, } impl DotSubClient { - pub fn new(ws_endpoint: SecretUrl) -> Self { - Self { ws_endpoint } + pub fn new(ws_endpoint: SecretUrl, expected_genesis_hash: Option) -> Self { + Self { ws_endpoint, expected_genesis_hash } } } @@ -149,7 +151,8 @@ impl DotSubscribeApi for DotSubClient { async fn subscribe_best_heads( &self, ) -> Result> + Send>>> { - let client = OnlineClient::::from_url(&self.ws_endpoint).await?; + let client = create_online_client(&self.ws_endpoint, self.expected_genesis_hash).await?; + Ok(Box::pin( client .blocks() @@ -163,7 +166,8 @@ impl DotSubscribeApi for DotSubClient { async fn subscribe_finalized_heads( &self, ) -> Result> + Send>>> { - let client = OnlineClient::::from_url(&self.ws_endpoint).await?; + let client = create_online_client(&self.ws_endpoint, self.expected_genesis_hash).await?; + Ok(Box::pin( client .blocks() @@ -175,6 +179,26 @@ impl DotSubscribeApi for DotSubClient { } } +/// Creates an OnlineClient from the given websocket endpoint and checks the genesis hash if +/// provided. +async fn create_online_client( + ws_endpoint: &SecretUrl, + expected_genesis_hash: Option, +) -> Result> { + let client = OnlineClient::::from_url(ws_endpoint).await?; + + if let Some(expected_genesis_hash) = expected_genesis_hash { + let genesis_hash = client.genesis_hash(); + if genesis_hash != expected_genesis_hash { + bail!("Expected Polkadot genesis hash {expected_genesis_hash} but got {genesis_hash}"); + } + } else { + warn!("Skipping Polkadot genesis hash check"); + } + + Ok(client) +} + #[async_trait] impl DotSubscribeApi for DotRpcClient { async fn subscribe_best_heads(