#[cfg(not(test))]
pub mod api;
#[cfg(test)]
pub mod mock;
#[cfg(test)]
mod tests;
#[cfg(test)]
pub use mock as api;
use api::{AccountId, IdtyIndex};
use codec::Encode;
use fnv::{FnvHashMap, FnvHashSet};
use log::{debug, error, info, warn};
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use std::{io::Write, path::PathBuf};
#[subxt::subxt(runtime_metadata_path = "../resources/metadata.scale")]
pub mod runtime {}
pub enum RuntimeConfig {}
impl subxt::config::Config for RuntimeConfig {
type AccountId = AccountId;
type Address = subxt::ext::sp_runtime::MultiAddress<Self::AccountId, u32>;
type AssetId = ();
type ExtrinsicParams = subxt::config::substrate::SubstrateExtrinsicParams<Self>;
type Hash = sp_core::H256;
type Hasher = subxt::config::substrate::BlakeTwo256;
type Header =
subxt::config::substrate::SubstrateHeader<u32, subxt::config::substrate::BlakeTwo256>;
type Signature = subxt::ext::sp_runtime::MultiSignature;
}
#[derive(Copy, Clone, Debug, Default, Encode)]
pub struct Tip {
#[codec(compact)]
tip: u64,
}
impl Tip {
pub fn new(amount: u64) -> Self {
Tip { tip: amount }
}
}
impl From<u64> for Tip {
fn from(n: u64) -> Self {
Self::new(n)
}
}
pub struct Settings {
pub evaluation_result_dir: PathBuf,
pub rpc_url: String,
}
impl Default for Settings {
fn default() -> Self {
Self {
evaluation_result_dir: PathBuf::from("/tmp/duniter/chains/gdev/distance"),
rpc_url: String::from("ws://127.0.0.1:9944"),
}
}
}
pub async fn run_and_save(client: &api::Client, settings: Settings) {
let Some((evaluation, current_pool_index, evaluation_result_path)) =
run(client, &settings, true).await
else {
return;
};
debug!("Saving distance evaluation result to file `{evaluation_result_path:?}`");
let mut evaluation_result_file = std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&evaluation_result_path)
.unwrap_or_else(|e| {
panic!(
"Cannot open distance evaluation result file `{evaluation_result_path:?}`: {e:?}"
)
});
evaluation_result_file
.write_all(
&sp_distance::ComputationResult {
distances: evaluation,
}
.encode(),
)
.unwrap_or_else(|e| {
panic!(
"Cannot write distance evaluation result to file `{evaluation_result_path:?}`: {e:?}"
)
});
let mut files_to_remove = Vec::new();
for entry in settings
.evaluation_result_dir
.read_dir()
.unwrap_or_else(|e| {
panic!(
"Cannot read distance evaluation result directory `{0:?}`: {e:?}",
settings.evaluation_result_dir
)
})
.flatten()
{
if let Ok(entry_name) = entry.file_name().into_string() {
if let Ok(entry_pool) = entry_name.parse::<isize>() {
if current_pool_index as isize - entry_pool > 3 {
files_to_remove.push(entry.path());
}
}
}
}
files_to_remove.into_iter().for_each(|f| {
std::fs::remove_file(&f)
.unwrap_or_else(move |e| warn!("Cannot remove old result file `{f:?}`: {e:?}"));
});
}
pub async fn run(
client: &api::Client,
settings: &Settings,
handle_fs: bool,
) -> Option<(Vec<sp_runtime::Perbill>, u32, PathBuf)> {
let parent_hash = api::parent_hash(client).await;
let max_depth = api::max_referee_distance(client).await;
let current_pool_index = api::current_pool_index(client, parent_hash).await;
let Some(evaluation_pool) = api::current_pool(client, parent_hash, current_pool_index).await
else {
info!("Nothing to do: Pool does not exist");
return None;
};
if evaluation_pool.evaluations.0.is_empty() {
info!("Nothing to do: Pool is empty");
return None;
}
let evaluation_result_path = settings
.evaluation_result_dir
.join((current_pool_index + 1).to_string());
if handle_fs {
if evaluation_result_path
.try_exists()
.expect("Result path unavailable")
{
info!("Nothing to do: File already exists");
return None;
}
std::fs::create_dir_all(&settings.evaluation_result_dir).unwrap_or_else(|e| {
error!(
"Cannot create distance evaluation result directory `{0:?}`: {e:?}",
settings.evaluation_result_dir
);
});
}
info!("Evaluating distance for pool {}", current_pool_index);
let evaluation_block = api::evaluation_block(client, parent_hash).await;
let mut members = FnvHashMap::<IdtyIndex, u32>::default();
let mut members_iter = api::member_iter(client, evaluation_block).await;
while let Some(member_idty) = members_iter
.next()
.await
.expect("Cannot fetch next members")
{
members.insert(member_idty, 0);
}
let min_certs_for_referee = (members.len() as f32).powf(1. / (max_depth as f32)).ceil() as u32;
let mut received_certs = FnvHashMap::<IdtyIndex, Vec<IdtyIndex>>::default();
let mut certs_iter = api::cert_iter(client, evaluation_block).await;
while let Some((receiver, issuers)) = certs_iter
.next()
.await
.expect("Cannot fetch next certification")
{
if (issuers.len() as u32) < min_certs_for_referee {
members.remove(&receiver);
}
for (issuer, _removable_on) in issuers.iter() {
if let Some(issued_certs) = members.get_mut(issuer) {
*issued_certs += 1;
}
}
received_certs.insert(
receiver,
issuers
.into_iter()
.map(|(issuer, _removable_on)| issuer)
.collect(),
);
}
members.retain(|_idty, issued_certs| *issued_certs >= min_certs_for_referee);
let referees = members;
let evaluation = evaluation_pool
.evaluations
.0
.as_slice()
.par_iter()
.map(|(idty, _)| distance_rule(&received_certs, &referees, max_depth, *idty))
.collect();
Some((evaluation, current_pool_index, evaluation_result_path))
}
fn distance_rule_recursive(
received_certs: &FnvHashMap<IdtyIndex, Vec<IdtyIndex>>,
referees: &FnvHashMap<IdtyIndex, u32>,
idty: IdtyIndex,
accessible_referees: &mut FnvHashSet<IdtyIndex>,
known_idties: &mut FnvHashMap<IdtyIndex, u32>,
depth: u32,
) {
match known_idties.entry(idty) {
std::collections::hash_map::Entry::Occupied(mut entry) => {
if *entry.get() >= depth {
return;
} else {
*entry.get_mut() = depth;
}
}
std::collections::hash_map::Entry::Vacant(entry) => {
entry.insert(depth);
}
}
if referees.contains_key(&idty) {
accessible_referees.insert(idty);
}
if depth == 0 {
return;
}
for &certifier in received_certs.get(&idty).unwrap_or(&vec![]).iter() {
distance_rule_recursive(
received_certs,
referees,
certifier,
accessible_referees,
known_idties,
depth - 1,
);
}
}
fn distance_rule(
received_certs: &FnvHashMap<IdtyIndex, Vec<IdtyIndex>>,
referees: &FnvHashMap<IdtyIndex, u32>,
depth: u32,
idty: IdtyIndex,
) -> sp_runtime::Perbill {
debug!("Evaluating distance for idty {}", idty);
let mut accessible_referees =
FnvHashSet::<IdtyIndex>::with_capacity_and_hasher(referees.len(), Default::default());
let mut known_idties =
FnvHashMap::<IdtyIndex, u32>::with_capacity_and_hasher(referees.len(), Default::default());
distance_rule_recursive(
received_certs,
referees,
idty,
&mut accessible_referees,
&mut known_idties,
depth,
);
let result = if referees.contains_key(&idty) {
sp_runtime::Perbill::from_rational(
accessible_referees.len() as u32 - 1,
referees.len() as u32 - 1,
)
} else {
sp_runtime::Perbill::from_rational(accessible_referees.len() as u32, referees.len() as u32)
};
info!(
"Distance for idty {}: {}/{} = {}%",
idty,
accessible_referees.len(),
referees.len(),
result.deconstruct() as f32 / 1_000_000_000f32 * 100f32
);
result
}