// 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 . //! `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::option_unwrap_used, clippy::result_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() 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); // - 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(10))); // 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 durs_common_tools::fns::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 = durs_common_tools::fns::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) ); } // Read g1_genesis wot let wot3_bin = durs_common_tools::fns::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, ] ); } }