Skip to content
Snippets Groups Projects
Commit 2bab6358 authored by Éloïs's avatar Éloïs
Browse files

Merge branch 'import-wot-crate-and-upgrade-dubp-common' into 'dev'

Import wot crate and upgrade dubp common

See merge request !1327
parents 3611f067 dfce8e4d
Branches
Tags
1 merge request!1327Import wot crate and upgrade dubp common
Showing
with 1602 additions and 7 deletions
......@@ -131,21 +131,22 @@ checksum = "697c714f50560202b1f4e2e09cd50a421881c83e9025db75d15f276616f04f40"
[[package]]
name = "dubp-common"
version = "0.1.1"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aef0856c14d0ebd70bd577f3a85ab0bb08be080ee646a4c43d3e9c332bef9cac"
checksum = "5b41d722bf752e5f87d07685aba6df25f69881f9aafa7060fae459e2948d8080"
dependencies = [
"dup-crypto",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "dubp-wot"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0f76dd4b734d34be5613c470402ccdb21cb21b84ecd75acdc81cf80c618201a"
dependencies = [
"bincode",
"dubp-common",
"log",
"rayon",
"serde",
......@@ -207,6 +208,12 @@ dependencies = [
"libc",
]
[[package]]
name = "itoa"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
[[package]]
name = "js-sys"
version = "0.3.37"
......@@ -394,6 +401,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "scopeguard"
version = "1.1.0"
......@@ -435,6 +448,17 @@ dependencies = [
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "spin"
version = "0.5.2"
......
[workspace]
members = ["neon/native"]
members = [
"neon/native",
"rust-libs/dubp-wot"
]
[patch.crates-io]
#dubp-common = { path = "../dubp-rs-libs/common" }
......@@ -17,7 +17,7 @@ neon-build = "0.4.0"
[dependencies]
bincode = "1.2.1"
bs58 = "0.3.0"
dubp-common = { version = "0.1.1", features = ["rand", "scrypt"] }
dubp-wot = "0.11.0"
dubp-common = { version = "0.2.0", features = ["rand", "scrypt"] }
dubp-wot = { path = "../../rust-libs/dubp-wot" }
flate2 = "1.0.16"
neon = "0.4.0"
[package]
name = "dubp-wot"
version = "0.11.0"
authors = ["nanocryk <nanocryk@duniter.org>", "elois <elois@duniter.org>"]
description = "Makes Web of Trust computations for the Duniter project."
repository = "https://git.duniter.org/nodes/typescript/duniter"
readme = "README.md"
keywords = ["duniter", "wot", "trust"]
license = "AGPL-3.0"
edition = "2018"
[lib]
path = "src/lib.rs"
[dependencies]
log = "0.4.8"
rayon = "1.3.0"
serde = { version = "1.0.105", features = ["derive"] }
[dev-dependencies]
bincode = "1.2.0"
dubp-common = { version = "0.2.0", features = ["rand", "scrypt"] }
[features]
# wot
`dubp-wot` is a crate making "Web of Trust" computations for
the [Duniter] project.
[Duniter]: https://duniter.org/en/
## How to use it
You can add `dubp-wot` as a `cargo` dependency in your Rust project.
// Copyright (C) 2017-2019 The AXIOM TEAM Association.
//
// This program 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, either version 3 of the
// License, or (at your option) any later version.
//
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Provide data structures to manage web of trusts.
//! `LegacyWebOfTrust` is almost a translation of the legacy C++ coden while
//! `RustyWebOfTrust` is a brand new implementation with a more "rusty" style.
pub mod rusty;
use serde::de::{self, Deserialize, DeserializeOwned, Deserializer, Visitor};
use serde::{Serialize, Serializer};
use std::{
fmt::{self, Debug},
io::Write,
};
/// Wrapper for a node id.
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct WotId(pub usize);
impl Serialize for WotId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u32(self.0 as u32)
}
}
struct WotIdVisitor;
impl<'de> Visitor<'de> for WotIdVisitor {
type Value = WotId;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("an integer between -2^31 and 2^31")
}
fn visit_u8<E>(self, value: u8) -> Result<WotId, E>
where
E: de::Error,
{
Ok(WotId(value as usize))
}
fn visit_u32<E>(self, value: u32) -> Result<WotId, E>
where
E: de::Error,
{
Ok(WotId(value as usize))
}
fn visit_u64<E>(self, value: u64) -> Result<WotId, E>
where
E: de::Error,
{
use std::usize;
if value >= usize::MIN as u64 && value <= usize::MAX as u64 {
Ok(WotId(value as usize))
} else {
Err(E::custom(format!("u32 out of range: {}", value)))
}
}
}
impl<'de> Deserialize<'de> for WotId {
fn deserialize<D>(deserializer: D) -> Result<WotId, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_u32(WotIdVisitor)
}
}
/// Results of a certification, with the current certification count
/// of the destination as parameter.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum NewLinkResult {
/// Certification worked.
Ok(usize),
/// All available certifications has been used.
AllCertificationsUsed(usize),
/// Unknown source.
UnknownSource(),
/// Unknown target.
UnknownTarget(),
/// Self linking is forbidden.
SelfLinkingForbidden(),
}
/// Results of a certification removal, with the current certification count
/// of the destination as parameter.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum RemLinkResult {
/// Certification has been removed.
Removed(usize),
/// Requested certification doesn't exist.
UnknownCert(usize),
/// Unknown source.
UnknownSource(),
/// Unknown target.
UnknownTarget(),
}
/// Results of a certification test.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum HasLinkResult {
/// Both nodes are known, here is the result.
Link(bool),
/// Unknown source.
UnknownSource(),
/// Unknown target.
UnknownTarget(),
}
/// Trait for a Web Of Trust.
/// Allow to provide other implementations of the `WoT` logic instead of the legacy C++
/// translated one.
pub trait WebOfTrust: Clone + Debug + Default + DeserializeOwned + Send + Serialize + Sync {
/// Create a new Web of Trust with the maximum of links a node can issue.
fn new(max_links: usize) -> Self;
/// Clear Web of Trust datas
fn clear(&mut self);
/// Get the maximum number of links per user.
fn get_max_link(&self) -> usize;
/// Set the maximum number of links per user.
fn set_max_link(&mut self, max_link: usize);
/// Add a new node.
fn add_node(&mut self) -> WotId;
/// Remove the last node.
/// Returns `None` if the WoT was empty, otherwise new top node id.
fn rem_node(&mut self) -> Option<WotId>;
/// Get the size of the WoT.
fn size(&self) -> usize;
/// Check if given node is enabled.
/// Returns `None` if this node doesn't exist.
fn is_enabled(&self, id: WotId) -> Option<bool>;
/// Set the enabled state of given node.
/// Returns `Null` if this node doesn't exist, `enabled` otherwise.
fn set_enabled(&mut self, id: WotId, enabled: bool) -> Option<bool>;
/// Get enabled node array.
fn get_enabled(&self) -> Vec<WotId>;
/// Get disabled node array.
fn get_disabled(&self) -> Vec<WotId>;
/// Try to add a link from the source to the target.
fn add_link(&mut self, source: WotId, target: WotId) -> NewLinkResult;
/// Try to remove a link from the source to the target.
fn rem_link(&mut self, source: WotId, target: WotId) -> RemLinkResult;
/// Test if there is a link from the source to the target.
fn has_link(&self, source: WotId, target: WotId) -> HasLinkResult;
/// Get the list of links source for this target.
/// Returns `None` if this node doesn't exist.
fn get_links_source(&self, target: WotId) -> Option<Vec<WotId>>;
/// Get the number of issued links by a node.
/// Returns `None` if this node doesn't exist.
fn issued_count(&self, id: WotId) -> Option<usize>;
/// Test if a node is a sentry.
fn is_sentry(&self, node: WotId, sentry_requirement: usize) -> Option<bool>;
/// Get sentries array.
fn get_sentries(&self, sentry_requirement: usize) -> Vec<WotId>;
/// Get non sentries array.
fn get_non_sentries(&self, sentry_requirement: usize) -> Vec<WotId>;
/// Dump wot
fn dump<W: Write>(&self, output: &mut W) -> std::io::Result<()>;
}
// Copyright (C) 2017-2019 The AXIOM TEAM Association.
//
// This program 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, either version 3 of the
// License, or (at your option) any later version.
//
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Experimental implementation of the Web of Trust in a more "rusty" style.
use super::{HasLinkResult, NewLinkResult, RemLinkResult};
use crate::WebOfTrust;
use crate::WotId;
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
/// A node in the `WoT` graph.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
struct Node {
/// Is this node enabled ?
enabled: bool,
/// Set of links this node is the target.
links_source: HashSet<WotId>,
/// Number of links the node issued.
issued_count: usize,
}
impl Node {
/// Create a new node.
pub fn new() -> Node {
Node {
enabled: true,
links_source: HashSet::new(),
issued_count: 0,
}
}
}
/// A more idiomatic implementation of a Web of Trust.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RustyWebOfTrust {
/// List of nodes in the WoT.
nodes: Vec<Node>,
/// Maximum number of links a node can issue.
max_links: usize,
}
impl Default for RustyWebOfTrust {
fn default() -> RustyWebOfTrust {
RustyWebOfTrust {
nodes: Vec::new(),
max_links: 4_000_000_000,
}
}
}
impl WebOfTrust for RustyWebOfTrust {
fn new(max_links: usize) -> RustyWebOfTrust {
RustyWebOfTrust {
nodes: vec![],
max_links,
}
}
fn clear(&mut self) {
self.nodes = Vec::new();
}
fn get_max_link(&self) -> usize {
self.max_links
}
fn set_max_link(&mut self, max_links: usize) {
self.max_links = max_links;
}
fn add_node(&mut self) -> WotId {
self.nodes.push(Node::new());
WotId(self.nodes.len() - 1)
}
fn rem_node(&mut self) -> Option<WotId> {
if !self.nodes.is_empty() {
self.nodes.pop();
Some(WotId(self.nodes.len()))
} else {
None
}
}
fn size(&self) -> usize {
self.nodes.len()
}
fn is_enabled(&self, id: WotId) -> Option<bool> {
self.nodes.get(id.0).map(|n| n.enabled)
}
fn set_enabled(&mut self, id: WotId, enabled: bool) -> Option<bool> {
self.nodes
.get_mut(id.0)
.map(|n| n.enabled = enabled)
.map(|_| enabled)
}
fn get_enabled(&self) -> Vec<WotId> {
self.nodes
.par_iter()
.enumerate()
.filter(|&(_, n)| n.enabled)
.map(|(i, _)| WotId(i))
.collect()
}
fn get_disabled(&self) -> Vec<WotId> {
self.nodes
.par_iter()
.enumerate()
.filter(|&(_, n)| !n.enabled)
.map(|(i, _)| WotId(i))
.collect()
}
fn add_link(&mut self, source: WotId, target: WotId) -> NewLinkResult {
if source == target {
NewLinkResult::SelfLinkingForbidden()
} else if source.0 >= self.size() {
NewLinkResult::UnknownSource()
} else if target.0 >= self.size() {
NewLinkResult::UnknownTarget()
} else if self.nodes[source.0].issued_count >= self.max_links {
NewLinkResult::AllCertificationsUsed(self.nodes[target.0].links_source.len())
} else {
self.nodes[source.0].issued_count += 1;
self.nodes[target.0].links_source.insert(source);
NewLinkResult::Ok(self.nodes[target.0].links_source.len())
}
}
fn rem_link(&mut self, source: WotId, target: WotId) -> RemLinkResult {
if source.0 >= self.size() {
RemLinkResult::UnknownSource()
} else if target.0 >= self.size() {
RemLinkResult::UnknownTarget()
} else if !self.nodes[target.0].links_source.contains(&source) {
RemLinkResult::UnknownCert(self.nodes[target.0].links_source.len())
} else {
self.nodes[source.0].issued_count -= 1;
self.nodes[target.0].links_source.remove(&source);
RemLinkResult::Removed(self.nodes[target.0].links_source.len())
}
}
fn has_link(&self, source: WotId, target: WotId) -> HasLinkResult {
if source.0 >= self.size() {
HasLinkResult::UnknownSource()
} else if target.0 >= self.size() {
HasLinkResult::UnknownTarget()
} else {
HasLinkResult::Link(self.nodes[target.0].links_source.contains(&source))
}
}
fn get_links_source(&self, target: WotId) -> Option<Vec<WotId>> {
self.nodes
.get(target.0)
.map(|n| n.links_source.iter().cloned().collect())
}
fn issued_count(&self, id: WotId) -> Option<usize> {
self.nodes.get(id.0).map(|n| n.issued_count)
}
fn is_sentry(&self, node: WotId, sentry_requirement: usize) -> Option<bool> {
if node.0 >= self.size() {
return None;
}
let node = &self.nodes[node.0];
Some(
node.enabled
&& node.issued_count >= sentry_requirement
&& node.links_source.len() >= sentry_requirement,
)
}
fn get_sentries(&self, sentry_requirement: usize) -> Vec<WotId> {
self.nodes
.par_iter()
.enumerate()
.filter(|&(_, n)| {
n.enabled
&& n.issued_count >= sentry_requirement
&& n.links_source.len() >= sentry_requirement
})
.map(|(i, _)| WotId(i))
.collect()
}
fn get_non_sentries(&self, sentry_requirement: usize) -> Vec<WotId> {
self.nodes
.par_iter()
.enumerate()
.filter(|&(_, n)| {
n.enabled
&& (n.issued_count < sentry_requirement
|| n.links_source.len() < sentry_requirement)
})
.map(|(i, _)| WotId(i))
.collect()
}
fn dump<W: std::io::Write>(&self, output: &mut W) -> std::io::Result<()> {
writeln!(output, "max_links={}", self.max_links)?;
writeln!(output, "nodes_count={}", self.nodes.len())?;
for (node_id, node) in self.nodes.iter().enumerate() {
write!(output, "{:03}: ", node_id)?;
if !node.enabled {
write!(output, "disabled ")?;
}
// dump sources
write!(output, "[")?;
let mut sorted_sources = node.links_source.iter().copied().collect::<Vec<WotId>>();
sorted_sources.sort_unstable();
let mut remaining_sources = sorted_sources.len();
for source in &sorted_sources {
if remaining_sources == 1 {
write!(output, "{}", source.0)?;
} else {
write!(output, "{}, ", source.0)?;
remaining_sources -= 1;
}
}
writeln!(output, "]")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::generic_wot_test;
#[test]
fn wot_tests() {
generic_wot_test::<RustyWebOfTrust>();
}
}
// Copyright (C) 2017-2020 The AXIOM TEAM Association.
//
// This program 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, either version 3 of the
// License, or (at your option) any later version.
//
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! `wot` is a crate making "Web of Trust" computations for
//! the [Duniter] project.
//!
//! [Duniter]: https://duniter.org/
//!
//! It defines a trait representing a Web of Trust and allow to do calculations on it.
//!
//! It also contains an "legacy" implementation translated from the original C++ code.
//!
//! Web of Trust tests are translated from [duniter/wot Javascript test][js-tests].
//!
//! [js-tests]: https://github.com/duniter/wot/blob/master/wotcpp/webOfTrust.cpp
#![deny(
clippy::unwrap_used,
missing_docs,
missing_debug_implementations,
missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
unstable_features,
unused_import_braces,
unused_qualifications
)]
pub mod data;
pub mod operations;
pub use crate::data::{WebOfTrust, WotId};
#[cfg(test)]
mod tests {
use super::*;
use crate::data::*;
use crate::operations::centrality::*;
use crate::operations::distance::*;
use crate::operations::path::*;
use std::path::Path;
/// Test translated from https://github.com/duniter/wot/blob/master/tests/test.js
///
/// Clone and file tests are not included in this generic test and should be done in
/// the implementation test.
#[allow(clippy::cognitive_complexity)]
pub fn generic_wot_test<W>()
where
W: WebOfTrust + Sync,
{
let centralities_calculator = UlrikBrandesCentralityCalculator {};
let distance_calculator = RustyDistanceCalculator {};
let path_finder = RustyPathFinder {};
let mut wot = W::new(3);
// should have an initial size of 0
assert_eq!(wot.size(), 0);
// should return `None()` if testing `is_enabled()` with out-of-bounds node
assert_eq!(wot.is_enabled(WotId(0)), None);
assert_eq!(wot.is_enabled(WotId(23)), None);
// should give nomber 0 if we add a node
// - add a node
assert_eq!(wot.add_node(), WotId(0));
assert_eq!(wot.size(), 1);
assert_eq!(wot.get_disabled().len(), 0);
// delete top node (return new top node id)
assert_eq!(wot.rem_node(), Some(WotId(0)));
assert_eq!(wot.size(), 0);
// readd node
assert_eq!(wot.add_node(), WotId(0));
// - add another
assert_eq!(wot.add_node(), WotId(1));
assert_eq!(wot.size(), 2);
assert_eq!(wot.get_disabled().len(), 0);
// - add 10 nodes
for i in 0..10 {
assert_eq!(wot.add_node(), WotId(i + 2));
}
assert_eq!(wot.size(), 12);
// shouldn't be able to self cert
assert_eq!(
wot.add_link(WotId(0), WotId(0)),
NewLinkResult::SelfLinkingForbidden()
);
// should add certs only in the boundaries of max_cert
assert_eq!(wot.add_link(WotId(0), WotId(1)), NewLinkResult::Ok(1));
assert_eq!(wot.add_link(WotId(0), WotId(2)), NewLinkResult::Ok(1));
assert_eq!(wot.add_link(WotId(0), WotId(3)), NewLinkResult::Ok(1));
assert_eq!(
wot.add_link(WotId(0), WotId(4)),
NewLinkResult::AllCertificationsUsed(0)
);
assert_eq!(wot.get_max_link(), 3);
assert_eq!(wot.has_link(WotId(0), WotId(1)), HasLinkResult::Link(true));
assert_eq!(wot.has_link(WotId(0), WotId(2)), HasLinkResult::Link(true));
assert_eq!(wot.has_link(WotId(0), WotId(3)), HasLinkResult::Link(true));
assert_eq!(wot.has_link(WotId(0), WotId(4)), HasLinkResult::Link(false));
wot.set_max_link(4);
assert_eq!(wot.get_max_link(), 4);
assert_eq!(wot.has_link(WotId(0), WotId(4)), HasLinkResult::Link(false));
wot.add_link(WotId(0), WotId(4));
assert_eq!(wot.has_link(WotId(0), WotId(4)), HasLinkResult::Link(true));
wot.rem_link(WotId(0), WotId(1));
wot.rem_link(WotId(0), WotId(2));
wot.rem_link(WotId(0), WotId(3));
wot.rem_link(WotId(0), WotId(4));
// false when not linked + test out of bounds
assert_eq!(wot.has_link(WotId(0), WotId(6)), HasLinkResult::Link(false));
assert_eq!(
wot.has_link(WotId(23), WotId(0)),
HasLinkResult::UnknownSource()
);
assert_eq!(
wot.has_link(WotId(2), WotId(53)),
HasLinkResult::UnknownTarget()
);
// created nodes should be enabled
assert_eq!(wot.is_enabled(WotId(0)), Some(true));
assert_eq!(wot.is_enabled(WotId(1)), Some(true));
assert_eq!(wot.is_enabled(WotId(2)), Some(true));
assert_eq!(wot.is_enabled(WotId(3)), Some(true));
assert_eq!(wot.is_enabled(WotId(11)), Some(true));
// should be able to disable some nodes
assert_eq!(wot.set_enabled(WotId(0), false), Some(false));
assert_eq!(wot.set_enabled(WotId(1), false), Some(false));
assert_eq!(wot.set_enabled(WotId(2), false), Some(false));
assert_eq!(wot.get_disabled().len(), 3);
assert_eq!(wot.set_enabled(WotId(1), true), Some(true));
// node 0 and 2 should be disabled
assert_eq!(wot.is_enabled(WotId(0)), Some(false));
assert_eq!(wot.is_enabled(WotId(1)), Some(true));
assert_eq!(wot.is_enabled(WotId(2)), Some(false));
assert_eq!(wot.is_enabled(WotId(3)), Some(true));
// - set enabled again
assert_eq!(wot.set_enabled(WotId(0), true), Some(true));
assert_eq!(wot.set_enabled(WotId(1), true), Some(true));
assert_eq!(wot.set_enabled(WotId(2), true), Some(true));
assert_eq!(wot.set_enabled(WotId(1), true), Some(true));
assert_eq!(wot.get_disabled().len(), 0);
// should not exist a link from 2 to 0
assert_eq!(wot.has_link(WotId(2), WotId(0)), HasLinkResult::Link(false));
// should be able to add some links, cert count is returned
assert_eq!(wot.add_link(WotId(2), WotId(0)), NewLinkResult::Ok(1));
assert_eq!(wot.add_link(WotId(4), WotId(0)), NewLinkResult::Ok(2));
assert_eq!(wot.add_link(WotId(5), WotId(0)), NewLinkResult::Ok(3));
// should exist new links
/* WoT is:
*
* 2 --> 0
* 4 --> 0
* 5 --> 0
*/
assert_eq!(wot.has_link(WotId(2), WotId(0)), HasLinkResult::Link(true));
assert_eq!(wot.has_link(WotId(4), WotId(0)), HasLinkResult::Link(true));
assert_eq!(wot.has_link(WotId(5), WotId(0)), HasLinkResult::Link(true));
assert_eq!(wot.has_link(WotId(2), WotId(1)), HasLinkResult::Link(false));
// should be able to remove some links
assert_eq!(wot.rem_link(WotId(4), WotId(0)), RemLinkResult::Removed(2));
/*
* WoT is now:
*
* 2 --> 0
* 5 --> 0
*/
// should exist less links
assert_eq!(wot.has_link(WotId(2), WotId(0)), HasLinkResult::Link(true));
assert_eq!(wot.has_link(WotId(4), WotId(0)), HasLinkResult::Link(false));
assert_eq!(wot.has_link(WotId(5), WotId(0)), HasLinkResult::Link(true));
assert_eq!(wot.has_link(WotId(2), WotId(1)), HasLinkResult::Link(false));
// should successfully use distance rule
assert_eq!(
distance_calculator.is_outdistanced(
&wot,
WotDistanceParameters {
node: WotId(0),
sentry_requirement: 1,
step_max: 1,
x_percent: 1.0,
},
),
Some(false)
);
// => no because 2,4,5 have certified him
assert_eq!(
distance_calculator.is_outdistanced(
&wot,
WotDistanceParameters {
node: WotId(0),
sentry_requirement: 2,
step_max: 1,
x_percent: 1.0,
},
),
Some(false)
);
// => no because only member 2 has 2 certs, and has certified him
assert_eq!(
distance_calculator.is_outdistanced(
&wot,
WotDistanceParameters {
node: WotId(0),
sentry_requirement: 3,
step_max: 1,
x_percent: 1.0,
},
),
Some(false)
);
// => no because no member has issued 3 certifications
// - we add links from member 3
assert_eq!(wot.add_link(WotId(3), WotId(1)), NewLinkResult::Ok(1));
assert_eq!(wot.add_link(WotId(3), WotId(2)), NewLinkResult::Ok(1));
/*
* WoT is now:
*
* 2 --> 0
* 5 --> 0
* 3 --> 1
* 3 --> 2
*/
assert_eq!(wot.size(), 12);
assert_eq!(wot.get_sentries(1).len(), 1);
assert_eq!(wot.get_sentries(1)[0], WotId(2));
assert_eq!(wot.get_sentries(2).len(), 0);
assert_eq!(wot.get_sentries(3).len(), 0);
assert_eq!(wot.get_non_sentries(1).len(), 11); // 12 - 1
assert_eq!(wot.get_non_sentries(2).len(), 12); // 12 - 0
assert_eq!(wot.get_non_sentries(3).len(), 12); // 12 - 0
assert_eq!(path_finder.find_paths(&wot, WotId(3), WotId(0), 1).len(), 0); // KO
assert_eq!(path_finder.find_paths(&wot, WotId(3), WotId(0), 2).len(), 1); // It exists 3 -> 2 -> 0
assert!(path_finder
.find_paths(&wot, WotId(3), WotId(0), 2)
.contains(&vec![WotId(3), WotId(2), WotId(0)]));
assert_eq!(
distance_calculator.is_outdistanced(
&wot,
WotDistanceParameters {
node: WotId(0),
sentry_requirement: 1,
step_max: 1,
x_percent: 1.0,
},
),
Some(false)
); // OK : 2 -> 0
assert_eq!(
distance_calculator.is_outdistanced(
&wot,
WotDistanceParameters {
node: WotId(0),
sentry_requirement: 2,
step_max: 1,
x_percent: 1.0,
},
),
Some(false)
); // OK : 2 -> 0
assert_eq!(
distance_calculator.is_outdistanced(
&wot,
WotDistanceParameters {
node: WotId(0),
sentry_requirement: 3,
step_max: 1,
x_percent: 1.0,
},
),
Some(false)
); // OK : no stry \w 3 lnk
assert_eq!(
distance_calculator.is_outdistanced(
&wot,
WotDistanceParameters {
node: WotId(0),
sentry_requirement: 2,
step_max: 2,
x_percent: 1.0,
},
),
Some(false)
); // OK : 2 -> 0
wot.add_link(WotId(1), WotId(3));
wot.add_link(WotId(2), WotId(3));
assert_eq!(wot.size(), 12);
assert_eq!(wot.get_sentries(1).len(), 3);
assert_eq!(wot.get_sentries(1)[0], WotId(1));
assert_eq!(wot.get_sentries(1)[1], WotId(2));
assert_eq!(wot.get_sentries(1)[2], WotId(3));
assert_eq!(wot.get_sentries(2).len(), 1);
assert_eq!(wot.get_sentries(2)[0], WotId(3));
assert_eq!(wot.get_sentries(3).len(), 0);
assert_eq!(wot.get_non_sentries(1).len(), 9); // 12 - 3
assert_eq!(wot.get_non_sentries(2).len(), 11); // 12 - 1
assert_eq!(wot.get_non_sentries(3).len(), 12); // 12 - 0
assert_eq!(path_finder.find_paths(&wot, WotId(3), WotId(0), 1).len(), 0); // KO
assert_eq!(path_finder.find_paths(&wot, WotId(3), WotId(0), 2).len(), 1); // It exists 3 -> 2 -> 0
assert!(path_finder
.find_paths(&wot, WotId(3), WotId(0), 2)
.contains(&vec![WotId(3), WotId(2), WotId(0)]));
assert_eq!(
distance_calculator.is_outdistanced(
&wot,
WotDistanceParameters {
node: WotId(0),
sentry_requirement: 1,
step_max: 1,
x_percent: 1.0,
},
),
Some(true)
); // KO : No path 3 -> 0
/*assert_eq!(
distance_calculator.is_outdistanced(
&wot,
WotDistanceParameters {
node: WotId(0),
sentry_requirement: 2,
step_max: 1,
x_percent: 1.0,
},
),
Some(true)
);*/ // KO : No path 3 -> 0
assert_eq!(
distance_calculator.is_outdistanced(
&wot,
WotDistanceParameters {
node: WotId(0),
sentry_requirement: 3,
step_max: 1,
x_percent: 1.0,
},
),
Some(false)
); // OK : no stry \w 3 lnk
assert_eq!(
distance_calculator.is_outdistanced(
&wot,
WotDistanceParameters {
node: WotId(0),
sentry_requirement: 2,
step_max: 2,
x_percent: 1.0,
},
),
Some(false)
); // OK : 3 -> 2 -> 0
// should have 12 nodes
assert_eq!(wot.size(), 12);
// delete top node (return new top node id)
assert_eq!(wot.rem_node(), Some(WotId(11)));
// should have 11 nodes
assert_eq!(wot.size(), 11);
// should work with member 3 disabled
// - with member 3 disabled (non-member)
assert_eq!(wot.set_enabled(WotId(3), false), Some(false));
assert_eq!(wot.get_disabled().len(), 1);
assert_eq!(
distance_calculator.is_outdistanced(
&wot,
WotDistanceParameters {
node: WotId(0),
sentry_requirement: 2,
step_max: 1,
x_percent: 1.0,
},
),
Some(false)
); // OK : Disabled
// Write wot in file
dubp_common::bin_file::write_bin_file(
Path::new("test.wot"),
&bincode::serialize(&wot).expect("fail to serialize wot"),
)
.expect("fail to write wot file");
let wot2_bin = dubp_common::bin_file::read_bin_file(Path::new("test.wot"))
.expect("fail to read wot file");
let wot2: W = bincode::deserialize(&wot2_bin).expect("fail to deserialize wot");
// Read wot from file
{
assert_eq!(wot.size(), wot2.size());
assert_eq!(
wot.get_non_sentries(1).len(),
wot2.get_non_sentries(1).len()
);
assert_eq!(wot.get_disabled().len(), wot2.get_disabled().len());
assert_eq!(wot2.get_disabled().len(), 1);
assert_eq!(wot2.is_enabled(WotId(3)), Some(false));
assert_eq!(
distance_calculator.is_outdistanced(
&wot2,
WotDistanceParameters {
node: WotId(0),
sentry_requirement: 2,
step_max: 1,
x_percent: 1.0,
},
),
Some(false)
);
}
// Dump wot
let mut dump_wot2_chars = Vec::new();
wot2.dump(&mut dump_wot2_chars).expect("fail to dump wot2");
let dump_wot2_str = String::from_utf8(dump_wot2_chars).expect("invalid utf8 chars");
assert_eq!(
dump_wot2_str,
"max_links=4
nodes_count=11
000: [2, 5]
001: [3]
002: [3]
003: disabled [1, 2]
004: []
005: []
006: []
007: []
008: []
009: []
010: []
"
);
// Read g1_genesis wot
let wot3_bin = dubp_common::bin_file::read_bin_file(Path::new("tests/g1_genesis.bin"))
.expect("fail to read g1_genesis wot file");
let wot3: W = bincode::deserialize(&wot3_bin).expect("fail to deserialize g1_genesis wot");
// Check g1_genesis wot members_count
let members_count = wot3.get_enabled().len() as u64;
assert_eq!(members_count, 59);
// Test compute_distance in g1_genesis wot
assert_eq!(
distance_calculator.compute_distance(
&wot3,
WotDistanceParameters {
node: WotId(37),
sentry_requirement: 3,
step_max: 5,
x_percent: 0.8,
},
),
Some(WotDistance {
sentries: 48,
success: 48,
success_at_border: 3,
reached: 51,
reached_at_border: 3,
outdistanced: false,
},)
);
// Test betweenness centralities computation in g1_genesis wot
let centralities = centralities_calculator.betweenness_centralities(&wot3);
assert_eq!(centralities.len(), 59);
assert_eq!(
centralities,
vec![
148, 30, 184, 11, 60, 51, 40, 115, 24, 140, 47, 69, 16, 34, 94, 126, 151, 0, 34,
133, 20, 103, 38, 144, 73, 523, 124, 23, 47, 17, 9, 64, 77, 281, 6, 105, 54, 0,
111, 21, 6, 2, 0, 1, 47, 59, 28, 236, 0, 0, 0, 0, 60, 6, 0, 1, 8, 33, 169,
]
);
// Test stress centralities computation in g1_genesis wot
let stress_centralities = centralities_calculator.stress_centralities(&wot3);
assert_eq!(stress_centralities.len(), 59);
assert_eq!(
stress_centralities,
vec![
848, 240, 955, 80, 416, 203, 290, 645, 166, 908, 313, 231, 101, 202, 487, 769, 984,
0, 154, 534, 105, 697, 260, 700, 496, 1726, 711, 160, 217, 192, 89, 430, 636, 1276,
41, 420, 310, 0, 357, 125, 50, 15, 0, 12, 275, 170, 215, 1199, 0, 0, 0, 0, 201, 31,
0, 9, 55, 216, 865,
]
);
// Test distance stress centralities computation in g1_genesis wot
let distance_stress_centralities =
centralities_calculator.distance_stress_centralities(&wot3, 5);
assert_eq!(distance_stress_centralities.len(), 59);
assert_eq!(
distance_stress_centralities,
vec![
848, 240, 955, 80, 416, 203, 290, 645, 166, 908, 313, 231, 101, 202, 487, 769, 984,
0, 154, 534, 105, 697, 260, 700, 496, 1726, 711, 160, 217, 192, 89, 430, 636, 1276,
41, 420, 310, 0, 357, 125, 50, 15, 0, 12, 275, 170, 215, 1199, 0, 0, 0, 0, 201, 31,
0, 9, 55, 216, 865,
]
);
}
}
// Copyright (C) 2017-2019 The AXIOM TEAM Association.
//
// This program 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, either version 3 of the
// License, or (at your option) any later version.
//
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Provide a trait and implementations to find paths between nodes.
use crate::data::WebOfTrust;
use crate::data::WotId;
use std::collections::{HashMap, VecDeque};
/// Find paths between 2 nodes of a `WebOfTrust`.
pub trait CentralitiesCalculator<T: WebOfTrust> {
/// Compute betweenness centrality of all members.
fn betweenness_centralities(&self, wot: &T) -> Vec<u64>;
/// Compute stress centrality of all members.
fn stress_centralities(&self, wot: &T) -> Vec<u64>;
/// Compute distance stress centrality of all members.
fn distance_stress_centralities(&self, wot: &T, step_max: usize) -> Vec<u64>;
}
/// An implementation based on "Ulrik brandes" algo.
#[derive(Debug, Clone, Copy)]
pub struct UlrikBrandesCentralityCalculator;
impl<T: WebOfTrust> CentralitiesCalculator<T> for UlrikBrandesCentralityCalculator {
fn betweenness_centralities(&self, wot: &T) -> Vec<u64> {
let wot_size = wot.size();
let mut centralities = vec![0.0; wot_size];
let enabled_nodes = wot.get_enabled();
// The source of any path belongs to enabled_nodes
for s in enabled_nodes.clone() {
let mut stack: Vec<WotId> = Vec::with_capacity(wot_size);
let mut paths: HashMap<WotId, Vec<WotId>> = HashMap::with_capacity(wot_size);
let mut sigma = vec![0.0; wot_size];
let mut d: Vec<isize> = vec![-1; wot_size];
let mut q: VecDeque<WotId> = VecDeque::with_capacity(wot_size);
sigma[s.0] = 1.0;
d[s.0] = 0;
q.push_back(s);
while let Some(v) = q.pop_front() {
stack.push(v);
for w in wot.get_links_source(v).expect("v don't have any source !") {
// w found for the first time ?
if d[w.0] < 0 {
q.push_back(w);
d[w.0] = d[v.0] + 1;
}
// Shortest path to w via v
if d[w.0] == d[v.0] + 1 {
sigma[w.0] += sigma[v.0];
paths.entry(w).or_insert_with(Vec::new).push(v);
}
}
}
let mut delta = vec![0.0; wot_size];
// stack returns vertices in order of non-increasing distance from s
while let Some(w) = stack.pop() {
if paths.contains_key(&w) {
for v in paths.get(&w).expect("Not found w in p !") {
if enabled_nodes.contains(&w) {
delta[v.0] += (sigma[v.0] / sigma[w.0]) * (1.0 + delta[w.0]);
} else {
// If w not in enabled_nodes, no path can end at w
delta[v.0] += (sigma[v.0] / sigma[w.0]) * delta[w.0];
}
}
}
if w != s {
centralities[w.0] += delta[w.0];
}
}
}
centralities.into_iter().map(|c| c as u64).collect()
}
fn stress_centralities(&self, wot: &T) -> Vec<u64> {
let wot_size = wot.size();
let mut centralities = vec![0.0; wot_size];
let enabled_nodes = wot.get_enabled();
// The source of any path belongs to enabled_nodes
for s in enabled_nodes.clone() {
let mut stack: Vec<WotId> = Vec::with_capacity(wot_size);
let mut paths: HashMap<WotId, Vec<WotId>> = HashMap::with_capacity(wot_size);
let mut sigma = vec![0.0; wot_size];
let mut d: Vec<isize> = vec![-1; wot_size];
let mut q: VecDeque<WotId> = VecDeque::with_capacity(wot_size);
sigma[s.0] = 1.0;
d[s.0] = 0;
q.push_back(s);
while let Some(v) = q.pop_front() {
stack.push(v);
for w in wot.get_links_source(v).expect("v don't have any source !") {
// w found for the first time ?
if d[w.0] < 0 {
q.push_back(w);
d[w.0] = d[v.0] + 1;
}
// Shortest path to w via v
if d[w.0] == d[v.0] + 1 {
sigma[w.0] += sigma[v.0];
paths.entry(w).or_insert_with(Vec::new).push(v);
}
}
}
let mut delta = vec![0.0; wot_size];
// stack returns vertices in order of non-increasing distance from s
while let Some(w) = stack.pop() {
if paths.contains_key(&w) {
for v in paths.get(&w).expect("Not found w in p !") {
if enabled_nodes.contains(&w) {
delta[v.0] += sigma[v.0] * (1.0 + (delta[w.0] / sigma[w.0]));
} else {
// If w not in enabled_nodes, no path can end at w
delta[v.0] += sigma[v.0] * (delta[w.0] / sigma[w.0]);
}
}
}
if w != s {
centralities[w.0] += delta[w.0];
}
}
}
centralities.into_iter().map(|c| c as u64).collect()
}
fn distance_stress_centralities(&self, wot: &T, step_max: usize) -> Vec<u64> {
let wot_size = wot.size();
let mut centralities = vec![0.0; wot_size];
let enabled_nodes = wot.get_enabled();
// The source of any path belongs to enabled_nodes
for s in enabled_nodes.clone() {
let mut stack: Vec<WotId> = Vec::with_capacity(wot_size);
let mut paths: HashMap<WotId, Vec<WotId>> = HashMap::with_capacity(wot_size);
let mut sigma = vec![0.0; wot_size];
let mut d: Vec<isize> = vec![-1; wot_size];
let mut q: VecDeque<WotId> = VecDeque::with_capacity(wot_size);
sigma[s.0] = 1.0;
d[s.0] = 0;
q.push_back(s);
while let Some(v) = q.pop_front() {
stack.push(v);
if d[v.0] < step_max as isize {
for w in wot.get_links_source(v).expect("v don't have any source !") {
// w found for the first time ?
if d[w.0] < 0 {
q.push_back(w);
d[w.0] = d[v.0] + 1;
}
// Shortest path to w via v
if d[w.0] == d[v.0] + 1 {
sigma[w.0] += sigma[v.0];
paths.entry(w).or_insert_with(Vec::new).push(v);
}
}
}
}
let mut delta = vec![0.0; wot_size];
// stack returns vertices in order of non-increasing distance from s
while let Some(w) = stack.pop() {
if paths.contains_key(&w) {
for v in paths.get(&w).expect("Not found w in p !") {
if enabled_nodes.contains(&w) {
delta[v.0] += sigma[v.0] * (1.0 + (delta[w.0] / sigma[w.0]));
} else {
// If w not in enabled_nodes, no path can end at w
delta[v.0] += sigma[v.0] * (delta[w.0] / sigma[w.0]);
}
}
}
if w != s {
centralities[w.0] += delta[w.0];
}
}
}
centralities.into_iter().map(|c| c as u64).collect()
}
}
// Copyright (C) 2017-2019 The AXIOM TEAM Association.
//
// This program 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, either version 3 of the
// License, or (at your option) any later version.
//
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Provide function to compute average density.
use crate::data::WebOfTrust;
/// Compute average density
pub fn calculate_average_density<T: WebOfTrust>(wot: &T) -> usize {
let enabled_members = wot.get_enabled();
let enabled_members_count = enabled_members.len();
let mut count_actives_links: usize = 0;
for member in &enabled_members {
count_actives_links += wot
.issued_count(*member)
.unwrap_or_else(|| panic!("Fail to get issued_count of wot_id {}", (*member).0));
}
((count_actives_links as f32 / enabled_members_count as f32) * 1_000.0) as usize
}
// Copyright (C) 2017-2019 The AXIOM TEAM Association.
//
// This program 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, either version 3 of the
// License, or (at your option) any later version.
//
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Provide a trait and implementations to compute distances.
use crate::data::WebOfTrust;
use crate::data::WotId;
use rayon::prelude::*;
use std::collections::HashSet;
/// Paramters for `WoT` distance calculations
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct WotDistanceParameters {
/// Node from where distances are calculated.
pub node: WotId,
/// Links count received AND issued to be a sentry.
pub sentry_requirement: u32,
/// Currency parameter.
pub step_max: u32,
/// Currency parameter.
pub x_percent: f64,
}
/// Results of `WebOfTrust::compute_distance`.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct WotDistance {
/// Sentries count
pub sentries: u32,
/// Success count
pub success: u32,
/// Succes at border count
pub success_at_border: u32,
/// Reached count
pub reached: u32,
/// Reached at border count
pub reached_at_border: u32,
/// Is the node outdistanced ?
pub outdistanced: bool,
}
/// Compute distance between nodes of a `WebOfTrust`.
pub trait DistanceCalculator<T: WebOfTrust> {
/// Compute distance between a node and the network.
/// Returns `None` if this node doesn't exist.
fn compute_distance(&self, wot: &T, params: WotDistanceParameters) -> Option<WotDistance>;
/// Compute distances of all members
fn compute_distances(
&self,
wot: &T,
sentry_requirement: u32,
step_max: u32,
x_percent: f64,
) -> (usize, Vec<usize>, usize, Vec<usize>);
/// Test if a node is outdistanced in the network.
/// Returns `Node` if this node doesn't exist.
fn is_outdistanced(&self, wot: &T, params: WotDistanceParameters) -> Option<bool>;
}
/// Calculate distances between 2 members in a `WebOfTrust`.
#[derive(Debug, Clone, Copy)]
pub struct RustyDistanceCalculator;
impl<T: WebOfTrust + Sync> DistanceCalculator<T> for RustyDistanceCalculator {
fn compute_distance(&self, wot: &T, params: WotDistanceParameters) -> Option<WotDistance> {
let WotDistanceParameters {
node,
sentry_requirement,
step_max,
x_percent,
} = params;
if node.0 >= wot.size() {
return None;
}
let mut area = HashSet::new();
area.insert(node);
let mut border = HashSet::new();
border.insert(node);
for _ in 0..step_max {
border = border
.par_iter()
.map(|&id| {
wot.get_links_source(id)
.expect("get_links_source must return a value")
.iter()
.filter(|source| !area.contains(source))
.cloned()
.collect::<HashSet<_>>()
})
.reduce(HashSet::new, |mut acc, sources| {
for source in sources {
acc.insert(source);
}
acc
});
area.extend(border.iter());
}
let sentries: Vec<_> = wot.get_sentries(sentry_requirement as usize);
let mut success = area.iter().filter(|n| sentries.contains(n)).count() as u32;
let success_at_border = border.iter().filter(|n| sentries.contains(n)).count() as u32;
let mut sentries = sentries.len() as u32;
if wot
.is_sentry(node, sentry_requirement as usize)
.expect("is_sentry must return a value")
{
sentries -= 1;
success -= 1;
}
Some(WotDistance {
sentries,
reached: area.len() as u32 - 1,
reached_at_border: border.len() as u32,
success,
success_at_border,
outdistanced: f64::from(success) < ((x_percent * f64::from(sentries)).trunc() - 1.0),
})
}
fn is_outdistanced(&self, wot: &T, params: WotDistanceParameters) -> Option<bool> {
Self::compute_distance(&self, wot, params).map(|result| result.outdistanced)
}
fn compute_distances(
&self,
wot: &T,
sentry_requirement: u32,
step_max: u32,
x_percent: f64,
) -> (usize, Vec<usize>, usize, Vec<usize>) {
let members_count = wot.get_enabled().len();
let mut distances = Vec::new();
let mut average_distance: usize = 0;
let mut connectivities = Vec::new();
let mut average_connectivity: usize = 0;
for i in 0..wot.size() {
let distance_datas: WotDistance = Self::compute_distance(
&self,
wot,
WotDistanceParameters {
node: WotId(i),
sentry_requirement,
step_max,
x_percent,
},
)
.expect("Fatal Error: compute_distance return None !");
let distance = ((f64::from(distance_datas.success)
/ (x_percent * f64::from(distance_datas.sentries)))
* 100.0) as usize;
distances.push(distance);
average_distance += distance;
let connectivity =
((f64::from(distance_datas.success - distance_datas.success_at_border)
/ (x_percent * f64::from(distance_datas.sentries)))
* 100.0) as usize;
connectivities.push(connectivity);
average_connectivity += connectivity;
}
average_distance /= members_count;
average_connectivity /= members_count;
(
average_distance,
distances,
average_connectivity,
connectivities,
)
}
}
// Copyright (C) 2017-2019 The AXIOM TEAM Association.
//
// This program 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, either version 3 of the
// License, or (at your option) any later version.
//
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Provide operation traits and implementations on `WebOfTrust` objects.
pub mod centrality;
pub mod density;
pub mod distance;
pub mod path;
// Copyright (C) 2017-2019 The AXIOM TEAM Association.
//
// This program 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, either version 3 of the
// License, or (at your option) any later version.
//
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Provide a trait and implementations to find paths between nodes.
use crate::data::WebOfTrust;
use crate::data::WotId;
use std::collections::HashSet;
/// Find paths between 2 nodes of a `WebOfTrust`.
pub trait PathFinder<T: WebOfTrust> {
/// Get paths from one node to the other.
fn find_paths(&self, wot: &T, from: WotId, to: WotId, k_max: u32) -> Vec<Vec<WotId>>;
}
/// A new "rusty-er" implementation of `WoT` path finding.
#[derive(Debug, Clone, Copy)]
pub struct RustyPathFinder;
impl<T: WebOfTrust> PathFinder<T> for RustyPathFinder {
fn find_paths(&self, wot: &T, from: WotId, to: WotId, k_max: u32) -> Vec<Vec<WotId>> {
if from.0 >= wot.size() || to.0 >= wot.size() {
return vec![];
}
// 1. We explore the k_max area around `to`, and only remember backward
// links of the smallest distance.
// Stores for each node its distance to `to` node and its backward links.
// By default all nodes are out of range (`k_max + 1`) and links are known.
let mut graph: Vec<(u32, Vec<WotId>)> =
(0..wot.size()).map(|_| (k_max + 1, vec![])).collect();
// `to` node is at distance 0, and have no backward links.
graph[to.0] = (0, vec![]);
// Explored zone border.
let mut border = HashSet::new();
border.insert(to);
for distance in 1..=k_max {
let mut next_border = HashSet::new();
for node in border {
for source in &wot
.get_links_source(node)
.expect("links source must not be None")
{
match graph[source.0].0 {
path_distance if path_distance > distance => {
// shorter path, we replace
graph[source.0] = (distance, vec![node]);
next_border.insert(*source);
}
path_distance if path_distance == distance => {
// same length, we combine
graph[source.0].1.push(node);
next_border.insert(*source);
}
_ => unreachable!(),
}
}
}
border = next_border;
}
// 2. If `from` is found, we follow the backward links and build paths.
// For each path, we look at the last element sources and build new paths with them.
let mut paths = vec![vec![from]];
for _ in 1..=k_max {
let mut new_paths = vec![];
for path in &paths {
let node = path.last().expect("path should not be empty");
if node == &to {
// If path is complete, we keep it.
new_paths.push(path.clone())
} else {
// If not complete we comlete paths
let sources = &graph[node.0];
for source in &sources.1 {
let mut new_path = path.clone();
new_path.push(*source);
new_paths.push(new_path);
}
}
}
paths = new_paths;
}
paths
}
}
File added
File added
File added
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment