diff --git a/Cargo.toml b/Cargo.toml index db6fa47..5dfab5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,11 @@ itertools = "0.7" serde_derive = "1.0.55" signifix = "0.9" proptest = "0.8.7" +# Note: `rand_core` is solely used for the randomness adapter in `net_utils.rs` +# tests and should be removed as soon as a migration path to rand 0.5 +# appears. +rand_core = "0.2.1" +integer-sqrt = "0.1.1" [[example]] name = "consensus-node" diff --git a/tests/README.md b/tests/README.md index bf8b639..fe10f39 100644 --- a/tests/README.md +++ b/tests/README.md @@ -150,8 +150,8 @@ fn do_example_test(seed: TestRngSeed, dimension: NetworkDimension) { // Instantiates a new random number generator for the test. let mut rng: TestRng = TestRng::from_seed(cfg.seed); - let mut net = NetBuilder::new(0..dimension.size) - .num_faulty(cfg.dimension.faulty) + let mut net = NetBuilder::new(0..dimension.size()) + .num_faulty(cfg.dimension.faulty()) // Setting `rng` ensures that randomness will only be retrieved by // `VirtualNet` from the `TestRng` instantiated above. .rng(rng) @@ -199,8 +199,8 @@ proptest! { } fn do_basic_operations(dimension: NetworkDimension, num_txs: u32) { - let mut net = NetBuilder::new(0..cfg.dimension.size) - .num_faulty(cfg.dimension.faulty) + let mut net = NetBuilder::new(0..cfg.dimension.size()) + .num_faulty(cfg.dimension.faulty()) // ... } ``` diff --git a/tests/net/mod.rs b/tests/net/mod.rs index bb70ba8..6ab75e8 100644 --- a/tests/net/mod.rs +++ b/tests/net/mod.rs @@ -491,7 +491,9 @@ where // Note: Closure is not redundant, won't compile without it. #[cfg_attr(feature = "cargo-clippy", allow(redundant_closure))] - let mut net = VirtualNet::new(self.node_ids, self.num_faulty, rng, move |node| cons(node))?; + let mut net = VirtualNet::new(self.node_ids, self.num_faulty as usize, rng, move |node| { + cons(node) + })?; if self.adversary.is_some() { net.adversary = self.adversary; diff --git a/tests/net/proptest.rs b/tests/net/proptest.rs index 16d1a0f..76e358f 100644 --- a/tests/net/proptest.rs +++ b/tests/net/proptest.rs @@ -3,6 +3,7 @@ //! This module houses strategies to generate (and reduce/expand) various `hbbft` and `net` related //! structures. +use integer_sqrt::IntegerSquareRoot; use proptest::arbitrary::any; use proptest::prelude::Rng; use proptest::strategy::{Strategy, ValueTree}; @@ -34,61 +35,68 @@ pub fn gen_seed() -> impl Strategy { /// A `NetworkDimension` describes the number of correct and faulty nodes in a network. It can also /// be checked, "averaged" (using the `average_higher` function) and generated using /// `NetworkDimensionTree`. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub struct NetworkDimension { /// Total number of nodes in network. - pub size: usize, + size: u16, /// Number of faulty nodes in a network. - pub faulty: usize, + faulty: u16, } impl NetworkDimension { /// Creates a new `NetworkDimension` with the supplied parameters. /// - /// Dimensions that do not satisfy BFT conditions (see `is_bft`) can be created using this - /// function. - pub fn new(size: usize, faulty: usize) -> Self { - NetworkDimension { size, faulty } + /// # Panics + /// + + #[inline] + pub fn new(size: u16, faulty: u16) -> Self { + let dim = NetworkDimension { size, faulty }; + assert!( + dim.is_bft(), + "Tried to create network dimension that violates BFT-property." + ); + dim + } + + #[inline] + pub fn faulty(self) -> usize { + self.faulty.into() + } + + #[inline] + pub fn size(self) -> usize { + self.size.into() } /// Checks whether the network dimension satisfies the `3 * faulty + 1 <= size` condition. - pub fn is_bft(&self) -> bool { + #[inline] + fn is_bft(self) -> bool { self.faulty * 3 < self.size } - /// Creates a new dimension of average complexity. - /// - /// The new dimension is approximately half way in the interval of `[self, high]` and will - /// conform to the constraint checked by `is_bft()`. - /// - /// # Panics - /// - /// `high` must be have a higher or equal size and faulty node count. - pub fn average_higher(&self, high: NetworkDimension) -> NetworkDimension { - assert!(high.size >= self.size); - assert!(high.faulty >= self.faulty); - - // We try halving both values, rounding down. If `size` is at the minimum, `faulty` will - // shrink afterwards. - let mut half = NetworkDimension { - size: self.size + (high.size - self.size) / 2, - faulty: self.faulty + (high.faulty - self.faulty) / 2, - }; - - // Reduce the number of faulty nodes, if we are outside our limits. - if half.faulty * 3 > half.size { - half.faulty -= 1; - } - - // This assert just checks for bugs. - assert!(half.is_bft()); - - half + /// Creates a proptest strategy to create network dimensions within a certain range. + #[inline] + pub fn range(min_size: u16, max_size: u16) -> NetworkDimensionStrategy { + NetworkDimensionStrategy { min_size, max_size } } - /// Creates a proptest strategy to create network dimensions within a certain range. - pub fn range(min_size: usize, max_size: usize) -> NetworkDimensionStrategy { - NetworkDimensionStrategy { min_size, max_size } + /// Returns next-larger network dimension. + /// + /// The order on `NetworkDimension` is canonically defined by `(size, faulty)`. The `succ` + /// function returns the next-higher valid instance by first trying to increase `faulty`, then + /// `size`. + #[inline] + pub fn succ(self) -> NetworkDimension { + let mut n = self.size; + let mut f = self.faulty + 1; + + if 3 * f >= n { + f = 0; + n += 1; + } + + NetworkDimension::new(n, f) } } @@ -98,11 +106,11 @@ impl NetworkDimension { #[derive(Copy, Clone, Debug)] pub struct NetworkDimensionTree { /// The upper bound for any generated dimension. - high: NetworkDimension, + high: u32, /// The currently generated network dimension. - current: NetworkDimension, + current: u32, /// The lower bound for any generated dimension value (changes during generation or shrinking). - low: NetworkDimension, + low: u32, } impl NetworkDimensionTree { @@ -114,7 +122,7 @@ impl NetworkDimensionTree { /// # Panics /// /// The minimum `min_size` is 1 and `min_size` must be less than or equal `max_size`. - pub fn gen(mut rng: R, min_size: usize, max_size: usize) -> Self { + pub fn gen(mut rng: R, min_size: u16, max_size: u16) -> Self { // A common mistake, add an extra assert for a more helpful error message. assert!(min_size > 0, "minimum network size is 1"); @@ -122,22 +130,12 @@ impl NetworkDimensionTree { let max_faulty = (total - 1) / 3; let faulty = rng.gen_range(0, max_faulty + 1); - let high = NetworkDimension { - size: total, - faulty, - }; - assert!(high.is_bft()); - - let low = NetworkDimension { - size: min_size, - faulty: 0, - }; - assert!(low.is_bft()); + let high = NetworkDimension::new(total, faulty); NetworkDimensionTree { - high, - current: high, - low, + high: high.into(), + current: high.into(), + low: 0, } } } @@ -146,15 +144,14 @@ impl ValueTree for NetworkDimensionTree { type Value = NetworkDimension; fn current(&self) -> Self::Value { - self.current + self.current.into() } fn simplify(&mut self) -> bool { - // Shrinking is simply done through `average_higher`. let prev = *self; - self.high = prev.current; - self.current = self.low.average_higher(prev.high); + self.high = self.current; + self.current = (self.low + self.high) / 2; (prev.high != self.high || prev.current != self.current) } @@ -162,34 +159,78 @@ impl ValueTree for NetworkDimensionTree { fn complicate(&mut self) -> bool { let prev = *self; - // Minimally increase the faulty-node ratio by adjusting the number of faulty nodes and the - // size slightly less. If we are at the maximum number of faulty nodes, we would end up - // increasing the network size instead (see branch below though). - let mut new_low = self.current; - new_low.faulty += 1; - new_low.size = (new_low.size + 2).max(new_low.faulty * 3 + 1); - assert!(new_low.is_bft()); - - // Instead of growing the network, return unchanged if the new network would be larger than - // the current high. - if new_low.size > self.high.size { + if self.high == self.current { return false; } - self.current = new_low.average_higher(self.high); - self.low = new_low; + self.low = self.current + 1; + self.current = (self.low + self.high) / 2; (prev.current != self.current || prev.low != self.low) } } +impl From for u32 { + fn from(dim: NetworkDimension) -> u32 { + // `b` is the "Block index" here. Counting through `NetworkDimensions` a pattern shows: + // + // n f + // 1 0 \ + // 2 0 |- Block 0 + // 3 0 / + // 4 0 \ + // 4 1 \ + // 5 0 > Block 1 + // 5 1 | + // 6 0 / + // 6 1 / + // 7 0 ... + // + // We observe that each block starts at index `3 * (b(b+1)/2)`. Along with the offset, + // we can calculate a mapping onto the natural numbers using this: + + let b = (u32::from(dim.size) - 1) / 3; + let start = 3 * b * (b + 1) / 2; + let offset = (u32::from(dim.size) - 3 * b - 1) * (b + 1) + u32::from(dim.faulty); + + start + offset + } +} + +impl From for NetworkDimension { + fn from(n: u32) -> NetworkDimension { + // Inverse of `u32 as From`: + + // Find the block number first: + let b = max_sum(n / 3); + + // Calculate the block start and the resulting offset of `n`: + let start = 3 * b * (b + 1) / 2; + let offset = n - start; + + let faulty = offset % (b + 1); + let size = 3 * b + 1 + offset / (b + 1); + + NetworkDimension::new(size as u16, faulty as u16) + } +} + +/// Finds the largest consecutive summand less or equal than `n`. +/// +/// The return value `k` will satisfy `SUM 1..k <= n`. +pub fn max_sum(n: u32) -> u32 { + // Derived by quadratically solving `n(n+1)/2`; we only want the "positive" result. + // `integer_sqrt` functions as a `floor` function here. + ((1 + 8 * n).integer_sqrt() - 1) / 2 +} + /// Network dimension strategy for proptest. #[derive(Debug)] pub struct NetworkDimensionStrategy { /// Minimum number of nodes for newly generated networks dimensions. - pub min_size: usize, + pub min_size: u16, /// Maximum number of nodes for newly generated networks dimensions. - pub max_size: usize, + pub max_size: u16, } impl Strategy for NetworkDimensionStrategy { diff --git a/tests/net_dynamic_hb.rs b/tests/net_dynamic_hb.rs index 653132b..9f486c2 100644 --- a/tests/net_dynamic_hb.rs +++ b/tests/net_dynamic_hb.rs @@ -2,12 +2,13 @@ extern crate failure; extern crate hbbft; #[macro_use] extern crate proptest; +extern crate integer_sqrt; extern crate rand; extern crate threshold_crypto; pub mod net; -use std::collections; +use std::{collections, time}; use hbbft::dynamic_honey_badger::{Change, ChangeState, DynamicHoneyBadger, Input}; use hbbft::messaging::DistAlgorithm; @@ -92,10 +93,14 @@ fn do_drop_and_readd(cfg: TestConfig) { let mut rng: TestRng = TestRng::from_seed(cfg.seed); // First, we create a new test network with Honey Badger instances. - let mut net = NetBuilder::new(0..cfg.dimension.size) - .num_faulty(cfg.dimension.faulty) - .message_limit(200_000) // Limited to 200k messages for now. - .rng(rng.gen::()) // Ensure runs are reproducible. + let mut net = NetBuilder::new(0..cfg.dimension.size()) + .num_faulty(cfg.dimension.faulty()) + // Limited to 15k messages per node. + .message_limit(15_000 * cfg.dimension.size() as usize) + // 30 secs per node. + .time_limit(time::Duration::from_secs(30 * cfg.dimension.size() as u64)) + // Ensure runs are reproducible. + .rng(rng.gen::()) .using_step(move |node| { println!("Constructing new dynamic honey badger node #{}", node.id); DynamicHoneyBadger::builder() diff --git a/tests/net_util.proptest-regressions b/tests/net_util.proptest-regressions new file mode 100644 index 0000000..ac844e5 --- /dev/null +++ b/tests/net_util.proptest-regressions @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +xs 2984819682 1621205622 3953858650 3756562836 # shrinks to seed = [724246048, 1190443809, 4113437293, 1855000933], ops = [Simplify, Complicate] diff --git a/tests/net_util.rs b/tests/net_util.rs index 4e060f9..f76b512 100644 --- a/tests/net_util.rs +++ b/tests/net_util.rs @@ -2,128 +2,219 @@ extern crate failure; extern crate hbbft; #[macro_use] extern crate proptest; +extern crate integer_sqrt; extern crate rand; +extern crate rand_core; extern crate threshold_crypto; pub mod net; -use proptest::strategy::ValueTree; -use proptest::test_runner::TestRunner; +use proptest::arbitrary::any; +use proptest::prelude::RngCore; +use proptest::strategy::{Strategy, ValueTree}; +use rand::{Rng as Rng4, SeedableRng as SeedableRng4}; -use net::proptest::{NetworkDimension, NetworkDimensionTree}; +use net::proptest::{max_sum, NetworkDimension, NetworkDimensionTree}; -/// Checks the `check_sanity` function with various inputs. -#[test] -fn check_sanity_works() { - assert!(NetworkDimension::new(3, 0).is_bft()); - assert!(NetworkDimension::new(4, 0).is_bft()); - assert!(NetworkDimension::new(5, 0).is_bft()); - assert!(NetworkDimension::new(6, 0).is_bft()); - assert!(!NetworkDimension::new(3, 1).is_bft()); - assert!(NetworkDimension::new(4, 1).is_bft()); - assert!(NetworkDimension::new(5, 1).is_bft()); - assert!(NetworkDimension::new(6, 1).is_bft()); - assert!(NetworkDimension::new(16, 3).is_bft()); - assert!(NetworkDimension::new(17, 3).is_bft()); - assert!(NetworkDimension::new(18, 3).is_bft()); - assert!(NetworkDimension::new(19, 3).is_bft()); - assert!(NetworkDimension::new(16, 5).is_bft()); - assert!(NetworkDimension::new(17, 5).is_bft()); - assert!(NetworkDimension::new(18, 5).is_bft()); - assert!(NetworkDimension::new(19, 5).is_bft()); - assert!(!NetworkDimension::new(16, 6).is_bft()); - assert!(!NetworkDimension::new(17, 6).is_bft()); - assert!(!NetworkDimension::new(18, 6).is_bft()); - assert!(NetworkDimension::new(19, 6).is_bft()); - assert!(!NetworkDimension::new(19, 19).is_bft()); - assert!(!NetworkDimension::new(19, 21).is_bft()); +struct RngAdapter4To5(pub T); - // Edge cases: - assert!(NetworkDimension::new(1, 0).is_bft()); - assert!(!NetworkDimension::new(0, 0).is_bft()); - assert!(!NetworkDimension::new(1, 1).is_bft()); -} - -proptest!{ - /// Ensure that `.average_higher()` produces valid new dimensions. - #[test] - fn average_higher_is_bft(size in 4..40usize) { - let mut faulty: usize = size/3; - if faulty > 0 { - faulty -= 1; - } - - let high = NetworkDimension::new(size, faulty); - let low = NetworkDimension::new(size/4, faulty/12); - - println!("high: {:?}, low: {:?}", high, low); - assert!(high.is_bft()); - assert!(low.is_bft()); - - let average_higher = low.average_higher(high); - println!("average_higher: {:?}", average_higher); - assert!(average_higher.is_bft()); +impl Rng4 for RngAdapter4To5 +where + T: Rng4, +{ + #[inline] + fn next_u32(&mut self) -> u32 { + self.0.next_u32() } } -/// Ensure `.average_higher()` works for edge cases. -#[test] -fn average_higher_handles_edge_cases() { - let high = NetworkDimension::new(1, 0); - let low = NetworkDimension::new(1, 0); - let average_higher = low.average_higher(high); - assert!(average_higher.is_bft()); +impl RngCore for RngAdapter4To5 +where + T: Rng4, +{ + #[inline] + fn next_u32(&mut self) -> u32 { + self.0.next_u32() + } - let high = NetworkDimension::new(10, 0); - let low = NetworkDimension::new(10, 0); - let average_higher = low.average_higher(high); - assert!(average_higher.is_bft()); + #[inline] + fn next_u64(&mut self) -> u64 { + self.0.next_u64() + } - let high = NetworkDimension::new(10, 3); - let low = NetworkDimension::new(10, 3); - let average_higher = low.average_higher(high); - assert!(average_higher.is_bft()); + #[inline] + fn fill_bytes(&mut self, bytes: &mut [u8]) { + self.0.fill_bytes(bytes); + } - let high = NetworkDimension::new(11, 3); - let low = NetworkDimension::new(10, 3); - let average_higher = low.average_higher(high); - assert!(average_higher.is_bft()); + #[inline] + fn try_fill_bytes(&mut self, bytes: &mut [u8]) -> Result<(), rand_core::Error> { + self.0.fill_bytes(bytes); + Ok(()) + } } proptest!{ /// Ensures all generated network dimensions are actually sane. #[test] - fn generated_network_dimensions_are_sane(nt in NetworkDimension::range(1, 400)) { - assert!(nt.is_bft()); + fn generated_network_dimensions_are_sane(_nt in NetworkDimension::range(1, 400)) { + // Nothing to do here, assert already in `NetworkDimension::new`. + } +} + +#[derive(Debug)] +enum Op { + Simplify, + Complicate, +} + +fn any_op() -> impl Strategy { + any::().prop_map(|v| if v { Op::Simplify } else { Op::Complicate }) +} + +proptest!{ + /// Verifies generated network dimensions can be grown and shrunk multiple times. + #[test] + fn network_dimensions_shrink_and_grow( + // dim in NetworkDimension::range(1, 400).no_shrink(), + seed in any::<[u32; 4]>().no_shrink(), + // num_ops in 10..10000, + ops in proptest::collection::vec(any_op(), 1..100) + ) { + let mut rng5 = RngAdapter4To5(rand::XorShiftRng::from_seed(seed)); + + let mut tree = NetworkDimensionTree::gen(&mut rng5, 1, 40); + println!("Start: {:?}", tree); + + for op in ops { + println!("Op: {:?}", op); + match op { + Op::Simplify => tree.simplify(), + Op::Complicate => tree.complicate(), + }; + println!("Result: {:?}", tree); + } } } -/// Verifies generated network dimensions can be grown and shrunk multiple times. #[test] -fn network_dimensions_shrink_and_grow() { - let mut runner = TestRunner::new(Default::default()); +fn network_succ_works() { + let expected = (&[ + (1, 0), + (2, 0), + (3, 0), + (4, 0), + (4, 1), + (5, 0), + (5, 1), + (6, 0), + (6, 1), + (7, 0), + (7, 1), + (7, 2), + (8, 0), + (8, 1), + (8, 2), + (9, 0), + (9, 1), + (9, 2), + (10, 0), + (10, 1), + (10, 2), + (10, 3), + ]) + .iter() + .map(|&(n, f)| NetworkDimension::new(n, f)); - let mut tree = NetworkDimensionTree::gen(runner.rng(), 1, 40); - assert!(tree.current().is_bft()); + let mut dim = NetworkDimension::new(1, 0); - // We complicate and simplify a few times. - for _ in 0..10 { - tree.complicate(); - assert!(tree.current().is_bft()); - } - - for _ in 0..20 { - tree.simplify(); - assert!(tree.current().is_bft()); - } - - for _ in 0..10 { - tree.complicate(); - assert!(tree.current().is_bft()); - } - - for _ in 0..10 { - tree.simplify(); - assert!(tree.current().is_bft()); + for exp in expected { + assert_eq!(dim, exp); + dim = dim.succ(); } } + +#[test] +fn test_max_sum() { + assert_eq!(max_sum(0), 0); + assert_eq!(max_sum(1), 1); + assert_eq!(max_sum(2), 1); + assert_eq!(max_sum(3), 2); + assert_eq!(max_sum(4), 2); + assert_eq!(max_sum(5), 2); + assert_eq!(max_sum(6), 3); + assert_eq!(max_sum(7), 3); + assert_eq!(max_sum(8), 3); + assert_eq!(max_sum(9), 3); + assert_eq!(max_sum(10), 4); + assert_eq!(max_sum(5049), 99); + assert_eq!(max_sum(5050), 100); + assert_eq!(max_sum(5051), 100); + assert_eq!(max_sum(5150), 100); + assert_eq!(max_sum(5151), 101); +} + +#[test] +fn network_to_u32_is_correct() { + assert_eq!(u32::from(NetworkDimension::new(1, 0)), 0u32); + assert_eq!(u32::from(NetworkDimension::new(2, 0)), 1u32); + assert_eq!(u32::from(NetworkDimension::new(3, 0)), 2u32); + assert_eq!(u32::from(NetworkDimension::new(4, 0)), 3u32); + assert_eq!(u32::from(NetworkDimension::new(4, 1)), 4u32); + assert_eq!(u32::from(NetworkDimension::new(5, 0)), 5u32); + assert_eq!(u32::from(NetworkDimension::new(5, 1)), 6u32); + assert_eq!(u32::from(NetworkDimension::new(6, 0)), 7u32); + assert_eq!(u32::from(NetworkDimension::new(6, 1)), 8u32); + assert_eq!(u32::from(NetworkDimension::new(7, 0)), 9u32); + assert_eq!(u32::from(NetworkDimension::new(7, 1)), 10u32); + assert_eq!(u32::from(NetworkDimension::new(7, 2)), 11u32); + assert_eq!(u32::from(NetworkDimension::new(8, 0)), 12u32); + assert_eq!(u32::from(NetworkDimension::new(8, 1)), 13u32); + assert_eq!(u32::from(NetworkDimension::new(8, 2)), 14u32); + assert_eq!(u32::from(NetworkDimension::new(9, 0)), 15u32); + assert_eq!(u32::from(NetworkDimension::new(9, 1)), 16u32); + assert_eq!(u32::from(NetworkDimension::new(9, 2)), 17u32); + assert_eq!(u32::from(NetworkDimension::new(10, 0)), 18u32); + assert_eq!(u32::from(NetworkDimension::new(10, 1)), 19u32); + assert_eq!(u32::from(NetworkDimension::new(10, 2)), 20u32); + assert_eq!(u32::from(NetworkDimension::new(10, 3)), 21u32); +} + +#[test] +fn network_from_u32_is_correct() { + assert_eq!(NetworkDimension::new(1, 0), NetworkDimension::from(0u32)); + assert_eq!(NetworkDimension::new(2, 0), NetworkDimension::from(1u32)); + assert_eq!(NetworkDimension::new(3, 0), NetworkDimension::from(2u32)); + assert_eq!(NetworkDimension::new(4, 0), NetworkDimension::from(3u32)); + assert_eq!(NetworkDimension::new(4, 1), NetworkDimension::from(4u32)); + assert_eq!(NetworkDimension::new(5, 0), NetworkDimension::from(5u32)); + assert_eq!(NetworkDimension::new(5, 1), NetworkDimension::from(6u32)); + assert_eq!(NetworkDimension::new(6, 0), NetworkDimension::from(7u32)); + assert_eq!(NetworkDimension::new(6, 1), NetworkDimension::from(8u32)); + assert_eq!(NetworkDimension::new(7, 0), NetworkDimension::from(9u32)); + assert_eq!(NetworkDimension::new(7, 1), NetworkDimension::from(10u32)); + assert_eq!(NetworkDimension::new(7, 2), NetworkDimension::from(11u32)); + assert_eq!(NetworkDimension::new(8, 0), NetworkDimension::from(12u32)); + assert_eq!(NetworkDimension::new(8, 1), NetworkDimension::from(13u32)); + assert_eq!(NetworkDimension::new(8, 2), NetworkDimension::from(14u32)); + assert_eq!(NetworkDimension::new(9, 0), NetworkDimension::from(15u32)); + assert_eq!(NetworkDimension::new(9, 1), NetworkDimension::from(16u32)); + assert_eq!(NetworkDimension::new(9, 2), NetworkDimension::from(17u32)); + assert_eq!(NetworkDimension::new(10, 0), NetworkDimension::from(18u32)); + assert_eq!(NetworkDimension::new(10, 1), NetworkDimension::from(19u32)); + assert_eq!(NetworkDimension::new(10, 2), NetworkDimension::from(20u32)); + assert_eq!(NetworkDimension::new(10, 3), NetworkDimension::from(21u32)); + assert_eq!(NetworkDimension::new(11, 0), NetworkDimension::from(22u32)); + assert_eq!(NetworkDimension::new(11, 1), NetworkDimension::from(23u32)); + assert_eq!(NetworkDimension::new(11, 2), NetworkDimension::from(24u32)); + assert_eq!(NetworkDimension::new(11, 3), NetworkDimension::from(25u32)); + assert_eq!(NetworkDimension::new(12, 0), NetworkDimension::from(26u32)); + assert_eq!(NetworkDimension::new(12, 1), NetworkDimension::from(27u32)); + assert_eq!(NetworkDimension::new(12, 2), NetworkDimension::from(28u32)); + assert_eq!(NetworkDimension::new(12, 3), NetworkDimension::from(29u32)); + assert_eq!(NetworkDimension::new(13, 0), NetworkDimension::from(30u32)); + assert_eq!(NetworkDimension::new(13, 1), NetworkDimension::from(31u32)); + assert_eq!(NetworkDimension::new(13, 2), NetworkDimension::from(32u32)); + assert_eq!(NetworkDimension::new(13, 3), NetworkDimension::from(33u32)); + assert_eq!(NetworkDimension::new(13, 4), NetworkDimension::from(34u32)); +}