diff --git a/doc/example.md b/doc/example.md index 5d9b7167c4214500b0466b58370518c2b00b0d37..2c36bae46a6dffdb4815125e78ae5d38d2f9d28a 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 edf068eef96c3167710a4a597a068cb072bd3099..570a7cd1f07c58e0b7dd07688eb16709cb30563e 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 80ee1e0d5f9ea2ebb88efd99122b3b8f567cda78..64c63bea405effa012380bc46e212c2cbac434e3 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 d02b1d9a064b272fe8dddbb1340634be4079b161..e8e8de2f2f48ac22cbf6412b2c1f4125b65e7d4c 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 a656192afa9af2b168e91af1605cd22266167283..ca457a1e1250e4a1202977f6cf45918d8340302e 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?, }