diff --git a/.gitignore b/.gitignore index ac6d158ede7acc17cb7cb1c4a9562faebf3fe145..131d75fddfc02a6abe821fdc762e12cde28e8a5a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ **/*.rs.bk *.wot +*.bin diff --git a/Cargo.lock b/Cargo.lock index e7db55386932a1efe17fd46f07f735466144f23b..ea4d9ed9642507e4dc0f26d63276c0891cc91009 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,6 +6,14 @@ dependencies = [ "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "arrayvec" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "base58" version = "0.1.0" @@ -39,6 +47,42 @@ name = "byteorder" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "cfg-if" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "crossbeam-deque" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-epoch 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-utils" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "duniter-crypto" version = "0.1.0" @@ -63,14 +107,20 @@ dependencies = [ [[package]] name = "duniter-wotb" -version = "0.6.0" +version = "0.6.1" dependencies = [ "bincode 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "either" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "fuchsia-zircon" version = "0.3.3" @@ -90,6 +140,11 @@ name = "gcc" version = "0.3.54" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "lazy_static" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "lazy_static" version = "1.0.0" @@ -113,6 +168,24 @@ dependencies = [ "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "memoffset" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "nodrop" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num_cpus" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "quote" version = "0.3.15" @@ -127,6 +200,27 @@ dependencies = [ "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rayon" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rayon-core" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "redox_syscall" version = "0.1.37" @@ -171,6 +265,11 @@ name = "safemem" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "scopeguard" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "serde" version = "1.0.27" @@ -276,26 +375,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4" +"checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef" "checksum base58 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83" "checksum base64 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c4a342b450b268e1be8036311e2c613d7f8a7ed31214dff1cc3b60852a3168d" "checksum bincode 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9a6301db0b49fb63551bc15b5ae348147101cdf323242b93ec7546d5002ff1af" "checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" "checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23" +"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" +"checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3" +"checksum crossbeam-epoch 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "59796cc6cbbdc6bb319161349db0c3250ec73ec7fcb763a51065ec4e2e158552" +"checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9" +"checksum either 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "740178ddf48b1a9e878e6d6509a1442a2d42fd2928aae8e7a6f8a36fb01981b3" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" "checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb" +"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" "checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" "checksum libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)" = "96264e9b293e95d25bfcbbf8a88ffd1aedc85b754eba8b7d78012f638ba220eb" "checksum linked-hash-map 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d2aab0478615bb586559b0114d94dd8eca4fdbb73b443adcb0d00b61692b4bf" "checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" +"checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" +"checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" +"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" "checksum rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)" = "512870020642bb8c221bf68baa1b2573da814f6ccfe5c9699b1c303047abe9b1" +"checksum rayon 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "485541959c8ecc49865526fe6c4de9653dd6e60d829d6edf0be228167b60372d" +"checksum rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9d24ad214285a7729b174ed6d3bcfcb80177807f959d95fafd5bfc5c4f201ac8" "checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd" "checksum regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "744554e01ccbd98fff8c457c3b092cd67af62a555a43bfe97ae8a0451f7799fa" "checksum regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e" "checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" "checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f" +"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" "checksum serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "db99f3919e20faa51bb2996057f5031d8685019b5a06139b1ce761da671b8526" "checksum serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "f4ba7591cfe93755e89eeecdbcc668885624829b020050e6aec99c2a03bd3fd0" "checksum serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6e03f1c9530c3fb0a0a5c9b826bdd9246a5921ae995d75f512ac917fc4dd55b5" diff --git a/wotb/Cargo.toml b/wotb/Cargo.toml index fe318989e46c84174bf933ded504670604082f9a..dc81a5a31760a3ec732ca17ce0cca25e620cfa35 100644 --- a/wotb/Cargo.toml +++ b/wotb/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "duniter-wotb" -version = "0.6.0" +version = "0.6.1" 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/rust/duniter-rs" @@ -15,4 +15,5 @@ path = "lib.rs" serde = "1.0.24" serde_derive = "1.0.24" bincode = "0.9.2" -byteorder = "1.2.1" \ No newline at end of file +byteorder = "1.2.1" +rayon = "1.0.0" diff --git a/wotb/lib.rs b/wotb/lib.rs index 3cc916f5f2f6b81495a4cdf02b5acb63b3dc0080..bcc07a8a034f8842060756f540132dd862b99164 100644 --- a/wotb/lib.rs +++ b/wotb/lib.rs @@ -32,11 +32,14 @@ extern crate bincode; extern crate byteorder; +extern crate rayon; extern crate serde; #[macro_use] extern crate serde_derive; pub mod legacy; +pub mod rusty; + pub use legacy::LegacyWebOfTrust; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; @@ -167,7 +170,7 @@ pub trait WebOfTrust { fn add_node(&mut self) -> NodeId; /// Remove the last node. - /// Returns `None` if the WoT was empty. + /// Returns `None` if the WoT was empty, otherwise new top node id. fn rem_node(&mut self) -> Option<NodeId>; /// Get the size of the WoT. @@ -177,7 +180,7 @@ pub trait WebOfTrust { /// Returns `None` if this node doesn't exist. fn is_enabled(&self, id: NodeId) -> Option<bool>; - /// Set if given node is enabled. + /// Set the enabled state of given node. /// Returns `Null` if this node doesn't exist, `enabled` otherwise. fn set_enabled(&mut self, id: NodeId, enabled: bool) -> Option<bool>; @@ -282,7 +285,6 @@ pub trait WebOfTrust { let mut buffer_3b: Vec<u8> = Vec::with_capacity(3); let mut count_bytes = 0; let mut remaining_links: u8 = 0; - let mut source: u32 = 0; let mut target: u32 = 0; for byte in file_pointing_to_links { if remaining_links == 0 { @@ -293,7 +295,7 @@ pub trait WebOfTrust { buffer_3b.push(byte); if count_bytes % 3 == 2 { let mut buf = &buffer_3b.clone()[..]; - source = buf.read_u24::<BigEndian>().expect("fail to parse source"); + let source = buf.read_u24::<BigEndian>().expect("fail to parse source"); self.add_link(NodeId(source as usize), NodeId((target - 1) as usize)); remaining_links -= 1; buffer_3b.clear(); @@ -351,20 +353,17 @@ pub trait WebOfTrust { } // Write links for n in 0..nodes_count { - match self.get_links_source(NodeId(n as usize)) { - Some(sources) => { - // Write sources_counts - let mut bytes = Vec::with_capacity(1); - bytes.write_u8(sources.len() as u8).unwrap(); + if let Some(sources) = self.get_links_source(NodeId(n as usize)) { + // Write sources_counts + let mut bytes = Vec::with_capacity(1); + bytes.write_u8(sources.len() as u8).unwrap(); + buffer.append(&mut bytes); + for source in sources { + // Write source + let mut bytes: Vec<u8> = Vec::with_capacity(3); + bytes.write_u24::<BigEndian>(source.0 as u32).unwrap(); buffer.append(&mut bytes); - for source in sources.iter() { - // Write source - let mut bytes: Vec<u8> = Vec::with_capacity(3); - bytes.write_u24::<BigEndian>(source.0 as u32).unwrap(); - buffer.append(&mut bytes); - } } - None => {} }; } // Create or open file @@ -629,7 +628,12 @@ mod tests { assert_eq!(wot.get_non_sentries(3).len(), 12); // 12 - 0 assert_eq!(wot.get_paths(NodeId(3), NodeId(0), 1).len(), 0); // KO assert_eq!(wot.get_paths(NodeId(3), NodeId(0), 2).len(), 1); // It exists 3 -> 2 -> 0 - assert_eq!(wot.get_paths(NodeId(3), NodeId(0), 2)[0].len(), 3); // It exists 3 -> 2 -> 0 + assert!(wot.get_paths(NodeId(3), NodeId(0), 2).contains(&vec![ + NodeId(3), + NodeId(2), + NodeId(0), + ])); + assert_eq!( wot.is_outdistanced(WotDistanceParameters { node: NodeId(0), @@ -684,7 +688,12 @@ mod tests { assert_eq!(wot.get_non_sentries(3).len(), 12); // 12 - 0 assert_eq!(wot.get_paths(NodeId(3), NodeId(0), 1).len(), 0); // KO assert_eq!(wot.get_paths(NodeId(3), NodeId(0), 2).len(), 1); // It exists 3 -> 2 -> 0 - assert_eq!(wot.get_paths(NodeId(3), NodeId(0), 2)[0].len(), 3); // It exists 3 -> 2 -> 0 + assert!(wot.get_paths(NodeId(3), NodeId(0), 2).contains(&vec![ + NodeId(3), + NodeId(2), + NodeId(0), + ])); + assert_eq!( wot.is_outdistanced(WotDistanceParameters { node: NodeId(0), diff --git a/wotb/rusty.rs b/wotb/rusty.rs new file mode 100644 index 0000000000000000000000000000000000000000..4e58cea57398974714d2c9e0a8b75b5d5dedaf45 --- /dev/null +++ b/wotb/rusty.rs @@ -0,0 +1,323 @@ +// Copyright (C) 2017-2018 The Duniter Project Developers. +// +// 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 std::collections::HashSet; +use rayon::prelude::*; + +use WebOfTrust; +use WotDistance; +use WotDistanceParameters; +use HasLinkResult; +use RemLinkResult; +use NewLinkResult; + +use NodeId; + +/// A node in the *WoT* graph. +#[derive(Debug, Clone, PartialEq, Eq)] +struct Node { + /// Is this node enabled ? + enabled: bool, + + /// Set of links this node is the target. + links_source: HashSet<NodeId>, + + /// 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)] +pub struct RustyWebOfTrust { + /// List of nodes in the WoT. + nodes: Vec<Node>, + /// Maximum number of links a node can issue. + max_links: usize, +} + +impl RustyWebOfTrust { + /// Create a new Web of Trust with the maximum of links a node can issue. + pub fn new(max_links: usize) -> RustyWebOfTrust { + RustyWebOfTrust { + nodes: vec![], + max_links, + } + } + + /// Test if a node is a sentry. + pub fn is_sentry(&self, node: NodeId, 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, + ) + } +} + +impl WebOfTrust for RustyWebOfTrust { + 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) -> NodeId { + self.nodes.push(Node::new()); + NodeId(self.nodes.len() - 1) + } + + fn rem_node(&mut self) -> Option<NodeId> { + self.nodes.pop(); + + if !self.nodes.is_empty() { + Some(NodeId(self.nodes.len() - 1)) + } else { + None + } + } + + fn size(&self) -> usize { + self.nodes.len() + } + + fn is_enabled(&self, id: NodeId) -> Option<bool> { + self.nodes.get(id.0).map(|n| n.enabled) + } + + fn set_enabled(&mut self, id: NodeId, enabled: bool) -> Option<bool> { + self.nodes + .get_mut(id.0) + .map(|n| n.enabled = enabled) + .map(|_| enabled) + } + + fn get_enabled(&self) -> Vec<NodeId> { + self.nodes + .par_iter() + .enumerate() + .filter(|&(_, n)| n.enabled) + .map(|(i, _)| NodeId(i)) + .collect() + } + + fn get_disabled(&self) -> Vec<NodeId> { + self.nodes + .par_iter() + .enumerate() + .filter(|&(_, n)| !n.enabled) + .map(|(i, _)| NodeId(i)) + .collect() + } + + fn add_link(&mut self, source: NodeId, target: NodeId) -> 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 if self.nodes[target.0].links_source.contains(&source) { + NewLinkResult::AlreadyCertified(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: NodeId, target: NodeId) -> 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: NodeId, target: NodeId) -> 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: NodeId) -> Option<Vec<NodeId>> { + self.nodes + .get(target.0) + .map(|n| n.links_source.iter().cloned().collect()) + } + + fn issued_count(&self, id: NodeId) -> Option<usize> { + self.nodes.get(id.0).map(|n| n.issued_count) + } + + fn get_sentries(&self, sentry_requirement: usize) -> Vec<NodeId> { + self.nodes + .par_iter() + .enumerate() + .filter(|&(_, n)| { + n.enabled && n.issued_count >= sentry_requirement + && n.links_source.len() >= sentry_requirement + }) + .map(|(i, _)| NodeId(i)) + .collect() + } + + fn get_non_sentries(&self, sentry_requirement: usize) -> Vec<NodeId> { + self.nodes + .par_iter() + .enumerate() + .filter(|&(_, n)| { + n.enabled + && (n.issued_count < sentry_requirement + || n.links_source.len() < sentry_requirement) + }) + .map(|(i, _)| NodeId(i)) + .collect() + } + + fn get_paths(&self, from: NodeId, to: NodeId, k_max: u32) -> Vec<Vec<NodeId>> { + if from == to { + vec![vec![to]] + } else if k_max > 0 { + self.nodes[to.0] + .links_source + .par_iter() + .map(|&source| self.get_paths(from, source, k_max - 1)) + .map(|paths| { + paths + .iter() + .map(|path| { + let mut path = path.clone(); + path.push(to); + path + }) + .collect::<Vec<Vec<NodeId>>>() + }) + .reduce( + || vec![], + |mut acc, mut paths| { + acc.append(&mut paths); + acc + }, + ) + } else { + vec![] + } + } + + fn compute_distance(&self, params: WotDistanceParameters) -> Option<WotDistance> { + let WotDistanceParameters { + node, + sentry_requirement, + step_max, + x_percent, + } = params; + + if node.0 >= self.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| { + self.nodes[id.0] + .links_source + .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<_> = self.get_sentries(sentry_requirement as usize); + + let success = area.iter().filter(|n| sentries.contains(n)).count() as u32; + let sentries = sentries.len() as u32 + - if self.is_sentry(node, sentry_requirement as usize).unwrap() { + 1 + } else { + 0 + }; + + Some(WotDistance { + sentries, + reached: (area.len() - 1) as u32, + success, + outdistanced: f64::from(success) < x_percent * f64::from(sentries), + }) + } + + fn is_outdistanced(&self, params: WotDistanceParameters) -> Option<bool> { + self.compute_distance(params) + .map(|result| result.outdistanced) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tests::generic_wot_test; + + #[test] + fn wot_tests() { + let mut wot1 = RustyWebOfTrust::new(3); + let mut wot2 = RustyWebOfTrust::new(3); + generic_wot_test(&mut wot1, &mut wot2); + } +}