Skip to content
Snippets Groups Projects
Select Git revision
  • json-output
  • nostr
  • master default protected
  • 48-error-base-58-requirement-is-violated
  • no-rename
  • hugo/tx-comments
  • poka/dev
  • hugo/dev
  • tuxmain/mail
  • 0.4.3-RC1
  • 0.4.2
  • 0.4.1
  • 0.4.0
  • 0.3.0
  • 0.2.17
  • 0.2.16
  • 0.2.15
  • 0.2.14
  • 0.2.13
  • 0.2.12
  • 0.2.10
  • 0.2.9
  • 0.2.8
  • 0.2.7
  • 0.2.6
  • 0.2.5
  • 0.2.4
  • 0.2.3
  • 0.2.1
29 results

indexer.rs

Blame
  • indexer.rs 6.77 KiB
    pub mod queries;
    
    use crate::*;
    use comfy_table::*;
    use comfy_table::{ContentArrangement, Table};
    use graphql_client::reqwest::post_graphql;
    use graphql_client::GraphQLQuery;
    use queries::*;
    // use sp_core::Bytes;
    
    #[derive(Clone, Debug)]
    pub struct Indexer {
    	pub gql_client: reqwest::Client,
    	pub gql_url: String,
    }
    
    impl Indexer {
    	/// graphql query without error management
    	async fn query<T: GraphQLQuery>(
    		&self,
    		var: <T as GraphQLQuery>::Variables,
    	) -> <T as GraphQLQuery>::ResponseData {
    		let response = post_graphql::<T, _>(&self.gql_client, &self.gql_url, var)
    			.await
    			.expect("indexer connexion error");
    		if let Some(errs) = response.errors {
    			log::debug!("{:?}", errs)
    		}
    		response.data.expect("indexer error")
    	}
    
    	/// index → name
    	pub async fn username_by_index(&self, index: u32) -> Option<String> {
    		self.query::<IdentityNameByIndex>(identity_name_by_index::Variables {
    			index: index.into(),
    		})
    		.await
    		.identity
    		.pop()
    		.map(|idty| idty.name)
    	}
    
    	/// index → name (multiple)
    	pub async fn names_by_indexes(&self, indexes: &[IdtyId]) -> Vec<(IdtyId, String)> {
    		self.query::<NamesByIndexes>(names_by_indexes::Variables {
    			indexes: indexes.iter().map(|i| *i as i64).collect(),
    		})
    		.await
    		.identity
    		.into_iter()
    		.map(|idty| (idty.index as IdtyId, idty.name))
    		.collect()
    	}
    
    	/// pubkey → name
    	pub async fn username_by_pubkey(&self, pubkey: &str) -> Option<String> {
    		self.query::<IdentityNameByPubkey>(identity_name_by_pubkey::Variables {
    			pubkey: pubkey.to_string(),
    		})
    		.await
    		.identity
    		.pop()
    		.map(|idty| idty.name)
    	}
    
    	/// pubkey → was name
    	pub async fn wasname_by_pubkey(&self, pubkey: &str) -> Option<String> {
    		self.query::<WasIdentityNameByPubkey>(was_identity_name_by_pubkey::Variables {
    			pubkey: pubkey.to_string(),
    		})
    		.await
    		.account_by_pk
    		.and_then(|mut acc| acc.was_identity.pop())
    		.map(|idty| idty.identity.unwrap().name)
    	}
    
    	/// index → info
    	pub async fn identity_info(&self, index: u32) -> Option<identity_info::IdentityInfoIdentity> {
    		self.query::<IdentityInfo>(identity_info::Variables {
    			index: index.into(),
    		})
    		.await
    		.identity
    		.pop()
    	}
    
    	/// fetch latest block
    	pub async fn fetch_latest_block(&self) -> Option<latest_block::LatestBlockBlock> {
    		self.query::<LatestBlock>(latest_block::Variables {})
    			.await
    			.block
    			.pop()
    	}
    
    	/// fetch block by number
    	pub async fn fetch_block_by_number(
    		&self,
    		number: BlockNumber,
    	) -> Option<block_by_number::BlockByNumberBlock> {
    		self.query::<BlockByNumber>(block_by_number::Variables {
    			number: number.into(),
    		})
    		.await
    		.block
    		.pop()
    	}
    
    	/// fetch genesis hash
    	// since this is always called before any other indexer request, check errors in a more detailed way
    	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| {
    			dbg!(_e); // for more info
    			GcliError::Indexer(format!("can not connect to indexer {}", &self.gql_url))
    		})?;
    
    		// 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(),
    			))?
    			.block
    			.first()
    			.ok_or_else(|| GcliError::Indexer("genesis block not yet indexed".to_string()))?
    			.hash
    			.clone();
    
    		// convert it
    		Ok(convert_hash_bytea(hash))
    	}
    }
    
    /// convert indexer bytes into hash
    // pub fn convert_hash(hash: Bytes) -> Hash {
    // 	let hash = TryInto::<[u8; 32]>::try_into(hash.as_ref()).unwrap();
    // 	hash.into()
    // }
    /// convert indexer bytes into hash
    pub fn convert_hash_bytea(hash: queries::Bytea) -> Hash {
    	let hash = TryInto::<[u8; 32]>::try_into(hash.bytes.as_ref()).unwrap();
    	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
    	/// (genesis hash, latest indexed block...)
    	Check,
    }
    
    /// handle indexer commands
    pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
    	// build indexer because it is needed for all subcommands
    	let data = data.build_client().await?.build_indexer().await?;
    	let indexer = data
    		.indexer
    		.clone()
    		.ok_or_else(|| GcliError::Logic("indexer needed for this command".to_string()))?;
    
    	// match subcommand
    	match command {
    		Subcommand::Check => {
    			let d_url = &data.cfg.duniter_endpoint;
    			let i_url = &indexer.gql_url;
    			let d_gen_hash = &data.genesis_hash.to_string();
    			let i_gen_hash = &data.indexer_genesis_hash.to_string();
    			let (d_finalized_n, d_finalized_h) =
    				commands::blockchain::fetch_finalized_number_and_hash(&data).await?;
    			let i_finalized_block = indexer.fetch_block_by_number(d_finalized_n).await;
    			let (i_finalized_h, i_finalized_n) = if let Some(block) = i_finalized_block {
    				(Some(convert_hash_bytea(block.hash)), Some(block.height))
    			} else {
    				(None, None)
    			};
    
    			let (d_latest_n, d_latest_h) =
    				commands::blockchain::fetch_latest_number_and_hash(&data).await?;
    			let i_latest_block = indexer.fetch_latest_block().await.expect("no latest block");
    			let i_latest_h = convert_hash_bytea(i_latest_block.hash);
    			let i_latest_n = i_latest_block.height;
    
    			fn color(x: bool) -> Color {
    				match x {
    					true => Color::Green,
    					false => Color::Red,
    				}
    			}
    
    			let mut table = Table::new();
    			table
    				.load_preset(presets::UTF8_FULL)
    				.apply_modifier(modifiers::UTF8_ROUND_CORNERS)
    				.set_content_arrangement(ContentArrangement::Dynamic)
    				.set_width(120)
    				.set_header(vec!["Variable", "Duniter", "Indexer"])
    				.add_row(vec!["URL", d_url, i_url])
    				.add_row(vec![
    					Cell::new("genesis hash"),
    					Cell::new(d_gen_hash),
    					Cell::new(i_gen_hash).fg(color(d_gen_hash == i_gen_hash)),
    				])
    				.add_row(vec![
    					Cell::new("finalized block number"),
    					Cell::new(d_finalized_n),
    					match i_finalized_n {
    						None => Cell::new("not indexed").fg(Color::Yellow),
    						Some(n) => {
    							// if it exists, it must be the same
    							assert_eq!(n, d_finalized_n as i64);
    							Cell::new("")
    						}
    					},
    				])
    				.add_row(vec![
    					Cell::new("finalized block hash"),
    					Cell::new(d_finalized_h),
    					match i_finalized_h {
    						// number already tells it is not indexed
    						None => Cell::new(""),
    						Some(h) => Cell::new(h).fg(color(h == d_finalized_h)),
    					},
    				])
    				.add_row(vec![
    					Cell::new("latest block number"),
    					Cell::new(d_latest_n),
    					Cell::new(i_latest_n).fg(color(i_latest_n == d_latest_n as i64)),
    				])
    				.add_row(vec![
    					Cell::new("latest block hash"),
    					Cell::new(d_latest_h),
    					Cell::new(i_latest_h).fg(color(i_latest_h == d_latest_h)),
    				]);
    
    			println!("{table}");
    		}
    	};
    
    	Ok(())
    }