use tokio::sync::mpsc::UnboundedSender; use color_eyre::Result; use subxt::{ backend::rpc::RpcClient, client::OnlineClient, config::substrate::DigestItem, ext::sp_core::crypto::{ AccountId32, Ss58AddressFormat, Ss58Codec, }, rpc_params, utils::H256, }; use crate::{ action::Action, casper_network::runtime_types::sp_consensus_slots, types::{EraInfo, EraRewardPoints, Nominator, SessionKeyInfo, SystemAccount}, CasperAccountId, CasperConfig }; pub async fn get_block_author( action_tx: &UnboundedSender, api: &OnlineClient, logs: &Vec, at_hash: &H256, ) -> Result<()> { use codec::Decode; use crate::casper_network::runtime_types::sp_consensus_babe::digests::PreDigest; let validators = super::raw_calls::session::validators(api, Some(at_hash)) .await? .unwrap_or_default(); let maybe_author = match logs.iter().find(|item| matches!(item, DigestItem::PreRuntime(..))) { Some(DigestItem::PreRuntime(engine, data)) if *engine == [b'B', b'A', b'B', b'E'] => { match PreDigest::decode(&mut &data[..]) { Ok(PreDigest::Primary(primary)) => validators.get(primary.authority_index as usize), Ok(PreDigest::SecondaryPlain(secondary)) => validators.get(secondary.authority_index as usize), Ok(PreDigest::SecondaryVRF(secondary)) => validators.get(secondary.authority_index as usize), _ => None, } }, _ => None, }; let validator = match maybe_author { Some(author) => { let extended_author = CasperAccountId::decode(&mut author.as_ref()) .expect("author should be valid AccountId32; qed"); let account_id = AccountId32::from(extended_author.0); account_id.to_ss58check_with_version(Ss58AddressFormat::custom(1996)) }, None => "...".to_string(), }; action_tx.send(Action::SetBlockAuthor(*at_hash, validator))?; Ok(()) } pub async fn get_current_era( action_tx: &UnboundedSender, api: &OnlineClient, ) -> Result<()> { let current_era = super::raw_calls::staking::current_era(api, None) .await? .unwrap_or_default(); action_tx.send(Action::SetCurrentEra(current_era))?; Ok(()) } pub async fn get_active_era( action_tx: &UnboundedSender, api: &OnlineClient, ) -> Result<()> { if let Some(active_era) = super::raw_calls::staking::active_era(api, None).await? { action_tx.send(Action::SetActiveEra(EraInfo { index: active_era.index, start: active_era.start, }))?; } Ok(()) } pub async fn get_epoch_progress( action_tx: &UnboundedSender, api: &OnlineClient, ) -> Result<()> { let current_slot = super::raw_calls::babe::current_slot(api, None) .await? .unwrap_or(sp_consensus_slots::Slot(0u64)); let epoch_index = super::raw_calls::babe::epoch_index(api, None) .await? .unwrap_or_default(); let genesis_slot = super::raw_calls::babe::genesis_slot(api, None) .await? .unwrap_or(sp_consensus_slots::Slot(0u64)); let epoch_duration = super::raw_calls::babe::epoch_duration(api)?; let epoch_start_slot = epoch_index * epoch_duration + genesis_slot.0; let progress = current_slot.0.saturating_sub(epoch_start_slot); action_tx.send(Action::SetEpochProgress(epoch_index, progress))?; Ok(()) } pub async fn get_total_issuance( action_tx: &UnboundedSender, api: &OnlineClient, ) -> Result<()> { let maybe_total_issuance = super::raw_calls::balances::total_issuance(api, None) .await?; action_tx.send(Action::SetTotalIssuance(maybe_total_issuance))?; Ok(()) } pub async fn get_existential_deposit( action_tx: &UnboundedSender, api: &OnlineClient, ) -> Result<()> { let existential_deposit = super::raw_calls::balances::existential_deposit(api)?; action_tx.send(Action::SetExistentialDeposit(existential_deposit))?; Ok(()) } pub async fn get_balance( action_tx: &UnboundedSender, api: &OnlineClient, account_id: &[u8; 32], ) -> Result<()> { let maybe_balance = super::raw_calls::system::balance(api, None, account_id) .await? .map(|balance| SystemAccount { nonce: balance.nonce, free: balance.data.free, reserved: balance.data.reserved, frozen: balance.data.frozen, } ); action_tx.send(Action::BalanceResponse(*account_id, maybe_balance))?; Ok(()) } pub async fn get_validators_number( action_tx: &UnboundedSender, api: &OnlineClient, ) -> Result<()> { let counter_for_validators = super::raw_calls::staking::counter_for_validators(api, None) .await? .unwrap_or_default(); action_tx.send(Action::ValidatorsNumber(counter_for_validators))?; Ok(()) } pub async fn get_nominators_number( action_tx: &UnboundedSender, api: &OnlineClient, ) -> Result<()> { let counter_for_nominators = super::raw_calls::staking::counter_for_nominators(api, None) .await? .unwrap_or_default(); action_tx.send(Action::NominatorsNumber(counter_for_nominators))?; Ok(()) } pub async fn get_inflation( action_tx: &UnboundedSender, api: &OnlineClient, ) -> Result<()> { let total_issuance = super::raw_calls::balances::total_issuance(api, None) .await? .unwrap_or_default(); let active_era_index = super::raw_calls::staking::active_era(api, None) .await? .map(|era_info| era_info.index) .unwrap_or_default(); let total_staked = super::raw_calls::staking::eras_total_stake(api, None, active_era_index) .await? .unwrap_or_default(); let (inflation, fraction) = super::calculate_for_fraction(total_staked, total_issuance); let inflation_str = super::prepare_perbill_fraction_string(inflation); let fraction_str = super::prepare_perbill_fraction_string(fraction); action_tx.send(Action::Inflation(inflation_str))?; action_tx.send(Action::Apy(fraction_str))?; Ok(()) } pub async fn get_session_keys( action_tx: &UnboundedSender, api: &OnlineClient, rpc_client: &RpcClient, account_id: &[u8; 32], ) -> Result<()> { let maybe_session_keys = super::raw_calls::session::next_keys(api, None, account_id).await?; let (gran_key, babe_key, audi_key, slow_key) = match maybe_session_keys { Some(session_keys) => { let gran_key = format!("0x{}", hex::encode(session_keys.grandpa.0)); let babe_key = format!("0x{}", hex::encode(session_keys.babe.0)); let audi_key = format!("0x{}", hex::encode(session_keys.authority_discovery.0)); let slow_key = format!("0x{}", hex::encode(session_keys.slow_clap.0)); (gran_key, babe_key, audi_key, slow_key) }, None => (String::new(), String::new(), String::new(), String::new()), }; check_author_has_key(rpc_client, action_tx, &gran_key, "gran").await?; check_author_has_key(rpc_client, action_tx, &babe_key, "babe").await?; check_author_has_key(rpc_client, action_tx, &audi_key, "audi").await?; check_author_has_key(rpc_client, action_tx, &slow_key, "slow").await?; Ok(()) } pub async fn get_queued_session_keys( action_tx: &UnboundedSender, api: &OnlineClient, rpc_client: &RpcClient, account_id: &[u8; 32], ) -> Result<()> { let account = super::raw_calls::convert_array_to_account_id(account_id); let maybe_queued_keys = super::raw_calls::session::queued_keys(api, None).await?; let (gran_key, babe_key, audi_key, slow_key) = match maybe_queued_keys { Some(session_keys) => { match session_keys.iter().find(|tuple| tuple.0 == account) { Some(keys) => { let session_keys = &keys.1; let gran_key = format!("0x{}", hex::encode(session_keys.grandpa.0)); let babe_key = format!("0x{}", hex::encode(session_keys.babe.0)); let audi_key = format!("0x{}", hex::encode(session_keys.authority_discovery.0)); let slow_key = format!("0x{}", hex::encode(session_keys.slow_clap.0)); (gran_key, babe_key, audi_key, slow_key) }, None => (String::new(), String::new(), String::new(), String::new()), } }, None => (String::new(), String::new(), String::new(), String::new()), }; check_author_has_key(rpc_client, action_tx, &gran_key, "q_gran").await?; check_author_has_key(rpc_client, action_tx, &babe_key, "q_babe").await?; check_author_has_key(rpc_client, action_tx, &audi_key, "q_audi").await?; check_author_has_key(rpc_client, action_tx, &slow_key, "q_slow").await?; Ok(()) } async fn check_author_has_key( rpc_client: &RpcClient, action_tx: &UnboundedSender, key: &str, name: &str, ) -> Result<()> { let params_name = if name.starts_with("q_") { &name[2..] } else { name }; let is_stored: bool = rpc_client .request("author_hasKey", rpc_params![key, params_name]) .await?; let session_key_info = SessionKeyInfo { key: key.to_string(), is_stored }; action_tx.send(Action::SetSessionKey(name.to_string(), session_key_info))?; Ok(()) } pub async fn get_validator_staking_results( action_tx: &UnboundedSender, api: &OnlineClient, account_id: &[u8; 32], ) -> Result<()> { let (start, end) = super::raw_calls::historical::stored_range(api, None) .await? .map(|range| { ( range.0 .saturating_div(6) .saturating_sub(1), range.1 .saturating_div(6) .saturating_sub(1), ) }) .unwrap_or((0, 0)); for era_index in start..end { get_validator_staking_result(action_tx, api, account_id, era_index).await?; } Ok(()) } pub async fn get_validator_staking_result( action_tx: &UnboundedSender, api: &OnlineClient, account_id: &[u8; 32], era_index: u32, ) -> Result<()> { get_validator_reward_in_era(action_tx, api, account_id, era_index).await?; get_validator_claims_in_era(action_tx, api, account_id, era_index).await?; get_validator_slashes_in_era(action_tx, api, account_id, era_index).await?; Ok(()) } pub async fn get_current_validator_reward_in_era( action_tx: &UnboundedSender, api: &OnlineClient, ) -> Result<()> { let era_index = super::raw_calls::staking::active_era(api, None) .await? .map(|era_info| era_info.index) .unwrap_or_default(); let disabled_validators = super::raw_calls::staking::disabled_validators(api, None) .await? .unwrap_or_default(); let maybe_era_reward_points = super::raw_calls::staking::eras_reward_points(api, None, era_index) .await?; let (total_points, individual) = match maybe_era_reward_points { Some(era_reward_points) => { ( era_reward_points.total, era_reward_points.individual .iter() .enumerate() .map(|(index, (account_id, points))| { let address = AccountId32::from(account_id.0) .to_ss58check_with_version(Ss58AddressFormat::custom(1996)); EraRewardPoints { address, account_id: account_id.0, points: *points, disabled: disabled_validators.contains(&(index as u32)), } }) .collect(), ) }, None => (0, Vec::new()), }; action_tx.send(Action::SetCurrentValidatorEraRewards( era_index, total_points, individual))?; Ok(()) } async fn get_validator_reward_in_era( action_tx: &UnboundedSender, api: &OnlineClient, account_id: &[u8; 32], era_index: u32, ) -> Result<()> { let maybe_era_reward_points = super::raw_calls::staking::eras_reward_points(api, None, era_index) .await?; let era_reward = super::raw_calls::staking::eras_validator_reward(api, None, era_index) .await? .unwrap_or_default(); let my_reward = match maybe_era_reward_points { Some(era_reward_points) => { let my_points = era_reward_points.individual .iter() .find(|(acc, _)| acc.0 == *account_id) .map(|info| info.1) .unwrap_or_default(); era_reward .saturating_mul(my_points as u128) .saturating_div(era_reward_points.total as u128) }, None => 0u128, }; action_tx.send(Action::SetValidatorEraReward(era_index, my_reward))?; Ok(()) } async fn get_validator_claims_in_era( action_tx: &UnboundedSender, api: &OnlineClient, account_id: &[u8; 32], era_index: u32, ) -> Result<()> { let maybe_claimed_rewards = super::raw_calls::staking::claimed_rewards(api, None, era_index, account_id) .await?; if let Some(claimed_rewards) = maybe_claimed_rewards { let already_claimed = claimed_rewards .first() .map(|x| *x == 1) .unwrap_or(false); action_tx.send(Action::SetValidatorEraClaimed(era_index, already_claimed))?; } Ok(()) } async fn get_validator_slashes_in_era( action_tx: &UnboundedSender, api: &OnlineClient, account_id: &[u8; 32], era_index: u32, ) -> Result<()> { let maybe_slash_in_era = super::raw_calls::staking::validator_slash_in_era(api, None, era_index, account_id) .await?; if let Some(slash_in_era) = maybe_slash_in_era { action_tx.send(Action::SetValidatorEraSlash(era_index, slash_in_era.1))?; } Ok(()) } pub async fn get_validators_ledger( action_tx: &UnboundedSender, api: &OnlineClient, account_id: &[u8; 32], ) -> Result<()> { let maybe_ledger = super::raw_calls::staking::ledger(api, None, account_id) .await?; if let Some(ledger) = maybe_ledger { action_tx.send(Action::SetStakedAmountRatio(ledger.total, ledger.active))?; for chunk in ledger.unlocking.0.iter() { action_tx.send(Action::SetValidatorEraUnlocking(chunk.era, chunk.value))?; } } Ok(()) } pub async fn get_nominators_by_validator( action_tx: &UnboundedSender, api: &OnlineClient, account_id: &[u8; 32], ) -> Result<()> { let active_era_index = super::raw_calls::staking::active_era(api, None) .await? .map(|era_info| era_info.index) .unwrap_or_default(); let maybe_eras_stakers = super::raw_calls::staking::eras_stakers(api, None, active_era_index, account_id) .await?; let nominators = match maybe_eras_stakers { Some(eras_stakers) => eras_stakers .others .iter() .map(|info| { Nominator { who: AccountId32::from(info.who.0) .to_ss58check_with_version(Ss58AddressFormat::custom(1996)), value: info.value, } }) .collect::>(), None => Vec::new(), }; action_tx.send(Action::SetNominatorsByValidator(nominators))?; Ok(()) } pub async fn get_is_stash_bonded( action_tx: &UnboundedSender, api: &OnlineClient, account_id: &[u8; 32], ) -> Result<()> { let is_bonded = super::raw_calls::staking::bonded(api, None, account_id) .await? .is_some(); action_tx.send(Action::SetBondedAmount(is_bonded))?; Ok(()) } pub async fn get_staking_value_ratio( action_tx: &UnboundedSender, api: &OnlineClient, account_id: &[u8; 32], ) -> Result<()> { let active_era_index = super::raw_calls::staking::active_era(api, None) .await? .map(|era_info| era_info.index) .unwrap_or_default(); let maybe_era_stakers_overview = super::raw_calls::staking::eras_stakers_overview(api, None, active_era_index, account_id) .await?; let (total, own) = match maybe_era_stakers_overview { Some(overview) => (overview.total, overview.own), None => (0, 0), }; action_tx.send(Action::SetStakedRatio(total, own))?; Ok(()) } pub async fn get_validator_prefs( action_tx: &UnboundedSender, api: &OnlineClient, account_id: &[u8; 32], ) -> Result<()> { let maybe_validator_prefs = super::raw_calls::staking::validators(api, None, account_id) .await?; let (comission, blocked) = match maybe_validator_prefs { Some(prefs) => (prefs.commission.0, prefs.blocked), None => (0, false), }; action_tx.send(Action::SetValidatorPrefs(comission, blocked))?; Ok(()) }