Select Git revision
-
Hugo Trentesaux authored
closes #35
Hugo Trentesaux authoredcloses #35
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(())
}