Skip to content
Snippets Groups Projects
  • Benjamin Gallois's avatar
    93853b2d
    Upgrade to polkadot-v1.6.0 (!229) · 93853b2d
    Benjamin Gallois authored and Hugo Trentesaux's avatar Hugo Trentesaux committed
    * check live tests
    
    * cargo check benchmarks after rebase
    
    * cargo check tests after rebase
    
    * update metadata after rebase
    
    * cargo check after rebase
    
    * cargo fmt after rebase
    
    * upgrade runtime to polkadot v1.6.0
    
    upgrade node to polkadot v1.6.0
    
    upgrade distance oracle to polkadot v1.6.0
    
    upgrade end2end tests
    
    upgrade xtask to polkadot v1.6.0
    
    update repository paths to duniter polkadot v1.6.0
    
    fix #131
    
    optimize end2end test
    
    fix review errors
    
    update subxt branch
    
    update test comment
    93853b2d
    History
    Upgrade to polkadot-v1.6.0 (!229)
    Benjamin Gallois authored and Hugo Trentesaux's avatar Hugo Trentesaux committed
    * check live tests
    
    * cargo check benchmarks after rebase
    
    * cargo check tests after rebase
    
    * update metadata after rebase
    
    * cargo check after rebase
    
    * cargo fmt after rebase
    
    * upgrade runtime to polkadot v1.6.0
    
    upgrade node to polkadot v1.6.0
    
    upgrade distance oracle to polkadot v1.6.0
    
    upgrade end2end tests
    
    upgrade xtask to polkadot v1.6.0
    
    update repository paths to duniter polkadot v1.6.0
    
    fix #131
    
    optimize end2end test
    
    fix review errors
    
    update subxt branch
    
    update test comment
lib.rs 9.81 KiB
// Copyright 2023 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.

#[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;
use rayon::iter::ParallelIterator;
use std::io::Write;
use std::path::PathBuf;

// TODO select metadata file using features
#[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_session, 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:?}"
            )
        });

    // Remove old results
    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_session) = entry_name.parse::<isize>() {
                if current_session as isize - entry_session > 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:?}"));
    });
}

/// Returns `Option<(evaluation, current_session, evaluation_result_path)>`
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_session = api::current_session(client, parent_hash).await;

    // Fetch the pending identities
    let Some(evaluation_pool) = api::current_pool(client, parent_hash, current_session).await
    else {
        info!("Nothing to do: Pool does not exist");
        return None;
    };

    // Stop if nothing to evaluate
    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_session + 1).to_string());

    if handle_fs {
        // Stop if already evaluated
        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 session {}", current_session);
    let evaluation_block = api::evaluation_block(client, parent_hash).await;

    // member idty -> issued certs
    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;

    // idty -> received certs
    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 {
            // This member is not 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(),
        );
    }

    // Only retain referees
    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_session, 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,
) {
    // Do not re-explore identities that have already been explored at least as deeply
    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 referee, add it to the list
    if referees.contains_key(&idty) {
        accessible_referees.insert(idty);
    }

    // If reached the maximum distance, stop exploring
    if depth == 0 {
        return;
    }
    // Explore certifiers
    for &certifier in received_certs.get(&idty).unwrap_or(&vec![]).iter() {
        distance_rule_recursive(
            received_certs,
            referees,
            certifier,
            accessible_referees,
            known_idties,
            depth - 1,
        );
    }
}

/// Returns the fraction `nb_accessible_referees / nb_referees`
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
}