Skip to content
Snippets Groups Projects
indexer.rs 4.55 KiB
Newer Older
use graphql_client::{reqwest::post_graphql, GraphQLQuery};
use sp_core::Bytes;
use crate::*;
use identity_info::*;

// type used in parameters query
// #[allow(non_camel_case_types)]
// type jsonb = serde_json::Value;
#[derive(GraphQLQuery)]
#[graphql(
	schema_path = "res/indexer-schema.json",
	query_path = "res/indexer-queries.graphql"
pub struct IdentityNameByIndex;
// index → identity info
#[derive(GraphQLQuery)]
#[graphql(
	schema_path = "res/indexer-schema.json",
	query_path = "res/indexer-queries.graphql"
)]
pub struct IdentityInfo;
#[derive(GraphQLQuery)]
#[graphql(
	schema_path = "res/indexer-schema.json",
	query_path = "res/indexer-queries.graphql"
pub struct IdentityNameByPubkey;
#[derive(GraphQLQuery)]
#[graphql(
	schema_path = "res/indexer-schema.json",
	query_path = "res/indexer-queries.graphql"
)]
pub struct LatestBlock;

#[derive(GraphQLQuery, Debug)]
	schema_path = "res/indexer-schema.json",
	query_path = "res/indexer-queries.graphql"
)]
pub struct GenesisHash;

#[derive(Clone, Debug)]
Hugo Trentesaux's avatar
Hugo Trentesaux committed
pub struct Indexer {
	pub gql_client: reqwest::Client,
Hugo Trentesaux's avatar
Hugo Trentesaux committed
	pub gql_url: String,
Hugo Trentesaux's avatar
Hugo Trentesaux committed
impl Indexer {
	pub async fn username_by_index(&self, index: u32) -> anyhow::Result<Option<String>> {
		Ok(post_graphql::<IdentityNameByIndex, _>(
			&self.gql_client,
Hugo Trentesaux's avatar
Hugo Trentesaux committed
			&self.gql_url,
			identity_name_by_index::Variables {
			},
		)
		.await?
		.data
		.and_then(move |mut data| data.identities.pop().map(|idty| idty.name)))
	pub async fn username_by_pubkey(&self, pubkey: &str) -> anyhow::Result<Option<String>> {
		Ok(post_graphql::<IdentityNameByPubkey, _>(
			&self.gql_client,
			identity_name_by_pubkey::Variables {
				pubkey: pubkey.to_string(),
			},
		)
		.await?
		.data
		.and_then(move |mut data| data.identities.pop().map(|idty| idty.name)))
	/// index → info
	pub async fn identity_info(&self, index: u32) -> Option<IdentityInfoIdentities> {
		post_graphql::<IdentityInfo, _>(
			&self.gql_client,
			&self.gql_url,
			identity_info::Variables {
				index: index.into(),
			},
		)
		.await
		.expect("problem")
		.data
		.and_then(move |mut data| data.identities.pop())
	}
	/// fetch latest block number
	pub async fn fetch_latest_block(&self) -> Result<u64, anyhow::Error> {
		Ok(post_graphql::<LatestBlock, _>(
			&self.gql_client,
			self.gql_url.clone(),
			latest_block::Variables {},
		)
		.await?
		.data
		.unwrap() // must have a data field
		.blocks
		.first()
		.unwrap() // must have one and only one parameter matching request
		.height
		.try_into()
		.unwrap())

	/// fetch genesis hash
	// since this is always called before any other indexer request, check errors
	pub async fn fetch_genesis_hash(&self) -> Result<Hash, GcliError> {
		// try to connect to indexer
		let response = post_graphql::<GenesisHash, _>(
			&self.gql_client,
			self.gql_url.clone(),
			genesis_hash::Variables {},
		)
		.await
		.map_err(|e| anyhow!(e))?;

		// debug errors if any
		response.errors.map_or_else(Vec::new, |e| dbg!(e));

		// extract hash
		let hash = response
			.data
			.ok_or(GcliError::Indexer(
				"no field 'data' when getting genesis hash".to_string(),
			))?
			.blocks
			.first()
			.unwrap() // must have one and only one block matching request
			.hash
			.clone();

		// convert it
		let hash = TryInto::<[u8; 32]>::try_into(hash.as_ref()).unwrap();
		Ok(hash.into())
/// define indexer subcommands
#[derive(Clone, Default, Debug, clap::Parser)]
pub enum Subcommand {
	#[default]
	/// Check that indexer and node are on the same network (same genesis hash)
	Check,
	/// Fetch latest indexed block
	LatestBlock,
/// handle indexer commands
pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
	// build indexer because it is needed for all subcommands
	let mut data = data.build_indexer().await?;
	// match subcommand
	match command {
		Subcommand::Check => {
			data = data.build_client().await?;
			if data.genesis_hash == data.indexer_genesis_hash {
				println!(
					"{} and {} have the same genesis hash: {}",
					data.cfg.duniter_endpoint,
					data.indexer().gql_url,
					data.genesis_hash
				);
			} else {
				println!(
					"⚠️ {} ({}) and {} ({}) do not share same genesis",
					data.cfg.duniter_endpoint,
					data.genesis_hash,
					data.indexer().gql_url,
					data.indexer_genesis_hash
				);
			}
		Subcommand::LatestBlock => {
			println!(
				"latest block indexed by {} is: {}",
				data.cfg.indexer_endpoint,
				data.indexer().fetch_latest_block().await?
			);
		}