From 61d833568c9ec431ea6ea8b27f1e2da96e0c6680 Mon Sep 17 00:00:00 2001
From: Hugo Trentesaux <hugo@trentesaux.fr>
Date: Tue, 6 Jun 2023 10:13:26 +0200
Subject: [PATCH] add genesis hash and current block checks

---
 doc/example.md              | 11 ++++++++-
 res/indexer-queries.graphql |  6 +++++
 src/data.rs                 |  8 +++++++
 src/indexer.rs              | 46 +++++++++++++++++++++++++++++++++----
 src/main.rs                 | 44 +++++++++++++++++++++++++++++++++--
 5 files changed, 107 insertions(+), 8 deletions(-)

diff --git a/doc/example.md b/doc/example.md
index 5d9b716..2c36bae 100644
--- a/doc/example.md
+++ b/doc/example.md
@@ -26,6 +26,8 @@ with derivations:
 ## Commands
 
 ```sh
+# get duniter current block
+gcli current-block
 # get balance of test1 account
 gcli --address 5FeggKqw2AbnGZF9Y9WPM2QTgzENS3Hit94Ewgmzdg5a3LNa get-balance
 # get information about test1 identity (needs indexer)
@@ -36,7 +38,14 @@ gcli --secret "pipe paddle ketchup filter life ice feel embody glide quantum rid
 
 ## Indexer commands
 
-These commands uniquely relate with indexer
+You can check first that indexer is on the same network as Duniter node:
+
+```sh
+# check if indexer is on the same chain as duniter
+gcli check-indexer-against-blockchain
+```
+
+The following commands uniquely relate with indexer.
 
 ```sh
 # show latest indexer indexed block
diff --git a/res/indexer-queries.graphql b/res/indexer-queries.graphql
index edf068e..570a7cd 100644
--- a/res/indexer-queries.graphql
+++ b/res/indexer-queries.graphql
@@ -15,3 +15,9 @@ query LatestBlock {
     value
   }
 }
+
+query GenesisHash {
+  block(where: {number: {_eq: 0}}) {
+    hash
+  }
+}
diff --git a/src/data.rs b/src/data.rs
index 80ee1e0..64c63be 100644
--- a/src/data.rs
+++ b/src/data.rs
@@ -1,4 +1,5 @@
 use crate::*;
+use indexer::Indexer;
 
 // data derived from command arguments
 
@@ -24,6 +25,8 @@ pub struct Data {
 	pub token_symbol: String,
 	// genesis hash
 	pub genesis_hash: Hash,
+	// indexer genesis hash
+	pub indexer_genesis_hash: Hash,
 }
 
 /// system properties defined in client specs
@@ -146,6 +149,11 @@ impl Data {
 			.unwrap();
 		Ok(self)
 	}
+	/// get indexer genesis hash
+	pub async fn fetch_indexer_genesis_hash(mut self) -> Result<Self, anyhow::Error> {
+		self.indexer_genesis_hash = self.indexer().fetch_genesis_hash().await?;
+		Ok(self)
+	}
 	/// get properties
 	pub async fn fetch_system_properties(mut self) -> Result<Self, anyhow::Error> {
 		let system_properties = self.client().rpc().system_properties().await?;
diff --git a/src/indexer.rs b/src/indexer.rs
index d02b1d9..e8e8de2 100644
--- a/src/indexer.rs
+++ b/src/indexer.rs
@@ -28,6 +28,13 @@ pub struct IdentityPubkeyByName;
 )]
 pub struct LatestBlock;
 
+#[derive(GraphQLQuery)]
+#[graphql(
+	schema_path = "res/indexer-schema.json",
+	query_path = "res/indexer-queries.graphql"
+)]
+pub struct GenesisHash;
+
 #[derive(Clone, Debug)]
 pub struct Indexer {
 	pub gql_client: reqwest::Client,
@@ -61,7 +68,7 @@ impl Indexer {
 		.and_then(|data| data.identity_by_pk.map(|idty| idty.pubkey)))
 	}
 
-	/// fetch latest block
+	/// fetch latest block number
 	pub async fn fetch_latest_block(&self) -> Result<u64, anyhow::Error> {
 		Ok(post_graphql::<LatestBlock, _>(
 			&self.gql_client,
@@ -80,29 +87,58 @@ impl Indexer {
 		.as_u64()
 		.unwrap()) // must be a Number of blocks
 	}
+
+	/// fetch genesis hash
+	pub async fn fetch_genesis_hash(&self) -> Result<Hash, anyhow::Error> {
+		Ok(post_graphql::<GenesisHash, _>(
+			&self.gql_client,
+			self.gql_url.clone(),
+			genesis_hash::Variables {},
+		)
+		.await?
+		.data
+		.unwrap() // must have a data field
+		.block
+		.first()
+		.unwrap() // must have one and only one block matching request
+		.hash
+		.clone()
+		.parse::<Hash>()
+		.unwrap())
+	}
 }
 
 #[derive(Clone, Default, Debug, clap::Parser)]
-pub enum IndexerSubcommand {
+pub enum Subcommand {
 	#[default]
 	/// Show indexer endpoint
 	ShowEndpoint,
 	/// Fetch latest indexed block
 	LatestBlock,
+	/// Fetch genesis block hash
+	GenesisHash,
 }
 
-pub async fn handle_command(data: Data, command: IndexerSubcommand) -> anyhow::Result<()> {
+pub async fn handle_command(data: Data, command: Subcommand) -> anyhow::Result<()> {
+	// build indexer because it is needed for all subcommands
 	let data = data.build_indexer()?;
+	// match subcommand
 	match command {
-		IndexerSubcommand::ShowEndpoint => {
+		Subcommand::ShowEndpoint => {
 			println!("indexer endpoint: {}", data.indexer().gql_url);
 		}
-		IndexerSubcommand::LatestBlock => {
+		Subcommand::LatestBlock => {
 			println!(
 				"latest indexed block is: {}",
 				data.indexer().fetch_latest_block().await?
 			);
 		}
+		Subcommand::GenesisHash => {
+			println!(
+				"hash of genesis block is: {}",
+				data.indexer().fetch_genesis_hash().await?
+			);
+		}
 	};
 
 	Ok(())
diff --git a/src/main.rs b/src/main.rs
index a656192..ca457a1 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,7 +7,6 @@ mod keys;
 use clap::Parser;
 use codec::Encode;
 use data::*;
-use indexer::*;
 use keys::*;
 use serde::Deserialize;
 use sp_core::{sr25519::Pair, Pair as _, H256};
@@ -251,9 +250,13 @@ pub enum Subcommand {
 	UpdateKeys,
 	/// Get information about runtime
 	RuntimeInfo,
+	/// Check current block
+	CurrentBlock,
+	/// Check that indexer and node are on the same network
+	CheckIndexerAgainstBlockchain,
 	/// Indexer subcommands
 	#[clap(subcommand)]
-	Indexer(IndexerSubcommand),
+	Indexer(indexer::Subcommand),
 }
 
 #[tokio::main(flavor = "current_thread")]
@@ -567,6 +570,43 @@ async fn main() -> Result<(), GcliError> {
 			data = data.build_client().await.fetch_system_properties().await?;
 			commands::runtime::runtime_info(data).await;
 		}
+		Subcommand::CurrentBlock => {
+			data = data.build_client().await;
+			println!(
+				"current block: {}",
+				data.client()
+					.storage()
+					.fetch(&runtime::storage().system().number(), None)
+					.await?
+					.unwrap()
+			);
+		}
+		Subcommand::CheckIndexerAgainstBlockchain => {
+			data = data
+				.build_client()
+				.await
+				.build_indexer()?
+				.fetch_genesis_hash()
+				.await?
+				.fetch_indexer_genesis_hash()
+				.await?;
+			if data.genesis_hash == data.indexer_genesis_hash {
+				println!(
+					"{} and {} have the same genesis hash: {}",
+					data.args.url,
+					data.indexer().gql_url,
+					data.genesis_hash
+				);
+			} else {
+				println!(
+					"⚠️ {} ({}) and {} ({}) do not share same genesis",
+					data.args.url,
+					data.genesis_hash,
+					data.indexer().gql_url,
+					data.indexer_genesis_hash
+				);
+			}
+		}
 		Subcommand::Indexer(subcommand) => indexer::handle_command(data, subcommand).await?,
 	}
 
-- 
GitLab