diff --git a/.travis.yml b/.travis.yml index 6a8d630c4..b32efe190 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,6 @@ env: - CARGO_MAKE_RUN_CLIPPY="true" matrix: fast_finish: true -install: - - if [[ $(cargo list | grep -c make) == 0 ]]; then cargo install --debug cargo-make ; else echo "Cargo make already installed, continuing..."; fi +script: + - which cargo-make || cargo install cargo-make - cargo make ci-flow \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index ea77334a3..ca3253242 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,16 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "adapter" +version = "0.1.0" +dependencies = [ + "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "domain 0.1.0", + "futures-preview 0.3.0-alpha.16 (registry+https://github.com/rust-lang/crates.io-index)", + "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "adler32" version = "1.0.3" diff --git a/Cargo.toml b/Cargo.toml index 15ac0885c..8218ca7d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ - "sentry", + "adapter", "domain", + "sentry", ] \ No newline at end of file diff --git a/Makefile.toml b/Makefile.toml index 59c5ad42d..fd37b40a7 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -1,5 +1,13 @@ [env] CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = "true" +CARGO_MAKE_CLIPPY_ARGS = "--all-features -- -D warnings" -[tasks.clippy] -args = ["clippy", "--all-features", "--", "-D", "warnings"] +[tasks.dev-test-flow] +dependencies = [ + "format-flow", + "clippy", + "pre-build", + "build", + "post-build", + "test-flow", +] \ No newline at end of file diff --git a/adapter/Cargo.toml b/adapter/Cargo.toml new file mode 100644 index 000000000..70897999e --- /dev/null +++ b/adapter/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "adapter" +version = "0.1.0" +authors = ["Lachezar Lechev "] +edition = "2018" + +[features] +# Allows you to use a Dummy implementation of the Adapter for testing purposes +dummy-adapter = [] + +[dependencies] +domain = {path = "../domain"} +# Futures +futures-preview = { version = "=0.3.0-alpha.16" } +# Time handling +chrono = "0.4" +time = "0.1.42" +# To/From Hex +# @TODO: Move this to the `dummy-adapter` feature if we use it only there! +hex = "0.3.2" +[dev-dependencies] +domain = {path = "../domain", features = ["fixtures"]} diff --git a/adapter/src/adapter.rs b/adapter/src/adapter.rs new file mode 100644 index 000000000..fc7c1a1e5 --- /dev/null +++ b/adapter/src/adapter.rs @@ -0,0 +1,96 @@ +use domain::{Asset, BigNum, Channel}; + +use crate::sanity::SanityChecker; +use futures::{Future, FutureExt}; +use std::pin::Pin; + +pub type AdapterFuture = Pin> + Send>>; + +#[derive(Debug, Eq, PartialEq)] +pub enum AdapterError { + Authentication(String), +} + +pub trait Adapter: SanityChecker { + fn config(&self) -> &Config; + + fn validate_channel(&self, channel: &Channel) -> AdapterFuture { + futures::future::ok(Self::check(&self.config(), &channel).is_ok()).boxed() + } + + /// Signs the provided state_root + fn sign(&self, state_root: &str) -> AdapterFuture; + + /// Verify, based on the signature & state_root, that the signer is the same + fn verify(&self, signer: &str, state_root: &str, signature: &str) -> AdapterFuture; + + /// Gets authentication for specific validator + fn get_auth(&self, validator: &str) -> AdapterFuture; +} + +pub struct Config { + pub identity: String, + pub validators_whitelist: Vec, + pub creators_whitelist: Vec, + pub assets_whitelist: Vec, + pub minimal_deposit: BigNum, + pub minimal_fee: BigNum, +} + +pub struct ConfigBuilder { + identity: String, + validators_whitelist: Vec, + creators_whitelist: Vec, + assets_whitelist: Vec, + minimal_deposit: BigNum, + minimal_fee: BigNum, +} + +impl ConfigBuilder { + pub fn new(identity: &str) -> Self { + Self { + identity: identity.to_string(), + validators_whitelist: Vec::new(), + creators_whitelist: Vec::new(), + assets_whitelist: Vec::new(), + minimal_deposit: 0.into(), + minimal_fee: 0.into(), + } + } + + pub fn set_validators_whitelist(mut self, validators: &[&str]) -> Self { + self.validators_whitelist = validators.iter().map(|slice| slice.to_string()).collect(); + self + } + + pub fn set_creators_whitelist(mut self, creators: &[&str]) -> Self { + self.creators_whitelist = creators.iter().map(|slice| slice.to_string()).collect(); + self + } + + pub fn set_assets_whitelist(mut self, assets: &[Asset]) -> Self { + self.assets_whitelist = assets.to_vec(); + self + } + + pub fn set_minimum_deposit(mut self, minimum: BigNum) -> Self { + self.minimal_deposit = minimum; + self + } + + pub fn set_minimum_fee(mut self, minimum: BigNum) -> Self { + self.minimal_fee = minimum; + self + } + + pub fn build(self) -> Config { + Config { + identity: self.identity, + validators_whitelist: self.validators_whitelist, + creators_whitelist: self.creators_whitelist, + assets_whitelist: self.assets_whitelist, + minimal_deposit: self.minimal_deposit, + minimal_fee: self.minimal_fee, + } + } +} diff --git a/adapter/src/dummy.rs b/adapter/src/dummy.rs new file mode 100644 index 000000000..ba2a2ca69 --- /dev/null +++ b/adapter/src/dummy.rs @@ -0,0 +1,187 @@ +use std::collections::HashMap; + +use hex::encode; + +use crate::adapter::{Adapter, AdapterError, AdapterFuture, Config}; +use crate::sanity::SanityChecker; +use futures::future::{err, ok, FutureExt}; + +#[derive(Debug)] +pub struct DummyParticipant { + pub identity: String, + pub token: String, +} + +pub struct DummyAdapter<'a> { + pub config: Config, + /// Dummy participants which will be used for + /// Creator, Validator Leader, Validator Follower and etc. + pub participants: HashMap<&'a str, DummyParticipant>, +} + +impl SanityChecker for DummyAdapter<'_> {} + +impl Adapter for DummyAdapter<'_> { + fn config(&self) -> &Config { + &self.config + } + + /// Example: + /// + /// ``` + /// use adapter::{ConfigBuilder, Adapter}; + /// use adapter::dummy::DummyAdapter; + /// use std::collections::HashMap; + /// + /// futures::executor::block_on(async { + /// let config = ConfigBuilder::new("identity").build(); + /// let adapter = DummyAdapter { config, participants: HashMap::new() }; + /// + /// let actual = await!(adapter.sign("abcdefghijklmnopqrstuvwxyz012345")).unwrap(); + /// let expected = "Dummy adapter signature for 6162636465666768696a6b6c6d6e6f707172737475767778797a303132333435 by identity"; + /// assert_eq!(expected, &actual); + /// }); + /// ``` + fn sign(&self, state_root: &str) -> AdapterFuture { + let signature = format!( + "Dummy adapter signature for {} by {}", + encode(&state_root), + &self.config.identity + ); + ok(signature).boxed() + } + + /// Example: + /// + /// ``` + /// use adapter::{ConfigBuilder, Adapter}; + /// use adapter::dummy::DummyAdapter; + /// use std::collections::HashMap; + /// + /// futures::executor::block_on(async { + /// let config = ConfigBuilder::new("identity").build(); + /// let adapter = DummyAdapter { config, participants: HashMap::new() }; + /// + /// let signature = "Dummy adapter signature for 6162636465666768696a6b6c6d6e6f707172737475767778797a303132333435 by identity"; + /// assert_eq!(Ok(true), await!(adapter.verify("identity", "doesn't matter", signature))); + /// }); + /// ``` + fn verify(&self, signer: &str, _state_root: &str, signature: &str) -> AdapterFuture { + // select the `identity` and compare it to the signer + // for empty string this will return array with 1 element - an empty string `[""]` + let is_same = match signature.rsplit(' ').take(1).next() { + Some(from) => from == signer, + None => false, + }; + + ok(is_same).boxed() + } + + /// Finds the auth. token in the HashMap of DummyParticipants if exists + /// + /// Example: + /// + /// ``` + /// use std::collections::HashMap; + /// use adapter::dummy::{DummyParticipant, DummyAdapter}; + /// use adapter::{ConfigBuilder, Adapter}; + /// + /// futures::executor::block_on(async { + /// let mut participants = HashMap::new(); + /// participants.insert( + /// "identity_key", + /// DummyParticipant { + /// identity: "identity".to_string(), + /// token: "token".to_string(), + /// }, + /// ); + /// + /// let adapter = DummyAdapter { + /// config: ConfigBuilder::new("identity").build(), + /// participants, + /// }; + /// + /// assert_eq!(Ok("token".to_string()), await!(adapter.get_auth("identity"))); + /// }); + /// ``` + fn get_auth(&self, validator: &str) -> AdapterFuture { + let participant = self + .participants + .iter() + .find(|&(_, participant)| participant.identity == validator); + let future = match participant { + Some((_, participant)) => ok(participant.token.to_string()), + None => err(AdapterError::Authentication( + "Identity not found".to_string(), + )), + }; + + future.boxed() + } +} + +#[cfg(test)] +mod test { + use crate::adapter::ConfigBuilder; + + use super::*; + + #[test] + fn dummy_adapter_sings_state_root_and_verifies_it() { + futures::executor::block_on(async { + let config = ConfigBuilder::new("identity").build(); + let adapter = DummyAdapter { + config, + participants: HashMap::new(), + }; + + let expected_signature = "Dummy adapter signature for 6162636465666768696a6b6c6d6e6f707172737475767778797a303132333435 by identity"; + let actual_signature = await!(adapter.sign("abcdefghijklmnopqrstuvwxyz012345")) + .expect("Signing shouldn't fail"); + + assert_eq!(expected_signature, &actual_signature); + + let is_verified = + await!(adapter.verify("identity", "doesn't matter", &actual_signature)); + + assert_eq!(Ok(true), is_verified); + }); + } + + #[test] + fn get_auth_with_empty_participators() { + futures::executor::block_on(async { + let adapter = DummyAdapter { + config: ConfigBuilder::new("identity").build(), + participants: HashMap::new(), + }; + + assert_eq!( + Err(AdapterError::Authentication( + "Identity not found".to_string() + )), + await!(adapter.get_auth("non-existing")) + ); + + let mut participants = HashMap::new(); + participants.insert( + "identity_key", + DummyParticipant { + identity: "identity".to_string(), + token: "token".to_string(), + }, + ); + let adapter = DummyAdapter { + config: ConfigBuilder::new("identity").build(), + participants, + }; + + assert_eq!( + Err(AdapterError::Authentication( + "Identity not found".to_string() + )), + await!(adapter.get_auth("non-existing")) + ); + }); + } +} diff --git a/adapter/src/lib.rs b/adapter/src/lib.rs new file mode 100644 index 000000000..7ebbd8d97 --- /dev/null +++ b/adapter/src/lib.rs @@ -0,0 +1,12 @@ +#![feature(async_await, await_macro)] +#![deny(rust_2018_idioms)] +#![deny(clippy::all)] +#![doc(test(attr(feature(async_await, await_macro))))] +#![doc(test(attr(cfg(feature = "dummy-adapter"))))] +pub use self::adapter::*; +pub use self::sanity::*; + +mod adapter; +#[cfg(any(test, feature = "dummy-adapter"))] +pub mod dummy; +mod sanity; diff --git a/adapter/src/sanity.rs b/adapter/src/sanity.rs new file mode 100644 index 000000000..9806c605e --- /dev/null +++ b/adapter/src/sanity.rs @@ -0,0 +1,269 @@ +use std::{error, fmt}; + +use chrono::Utc; + +use domain::channel::{SpecValidator, SpecValidators}; +use domain::{Asset, Channel}; + +use crate::adapter::Config; + +pub trait SanityChecker { + fn check(config: &Config, channel: &Channel) -> Result<(), SanityError> { + let adapter_channel_validator = match channel.spec.validators.find(&config.identity) { + // check if the channel validators include our adapter identity + SpecValidator::None => return Err(SanityError::AdapterNotIncluded), + SpecValidator::Leader(validator) | SpecValidator::Follower(validator) => validator, + }; + + if channel.valid_until < Utc::now() { + return Err(SanityError::PassedValidUntil); + } + + if !all_validators_listed(&channel.spec.validators, &config.validators_whitelist) { + return Err(SanityError::UnlistedValidator); + } + + if !creator_listed(&channel, &config.creators_whitelist) { + return Err(SanityError::UnlistedCreator); + } + + if !asset_listed(&channel, &config.assets_whitelist) { + return Err(SanityError::UnlistedAsset); + } + + if channel.deposit_amount < config.minimal_deposit { + return Err(SanityError::MinimumDepositNotMet); + } + + if adapter_channel_validator.fee < config.minimal_fee { + return Err(SanityError::MinimumValidatorFeeNotMet); + } + + Ok(()) + } +} + +fn all_validators_listed(validators: &SpecValidators, whitelist: &[String]) -> bool { + if whitelist.is_empty() { + true + } else { + let found_validators = whitelist + .iter() + .filter(|&allowed| { + allowed == &validators.leader().id || allowed == &validators.follower().id + }) + // this will ensure that if we find the 2 validators earlier + // we don't go over the other values of the whitelist + .take(2); + // the found validators should be exactly 2, if they are not, then 1 or 2 are missing + found_validators.count() == 2 + } +} + +fn creator_listed(channel: &Channel, whitelist: &[String]) -> bool { + // if the list is empty, return true, as we don't have a whitelist to restrict us to + // or if we have a list, check if it includes the `channel.creator` + whitelist.is_empty() || whitelist.iter().any(|allowed| allowed == &channel.creator) +} + +fn asset_listed(channel: &Channel, whitelist: &[Asset]) -> bool { + // if the list is empty, return true, as we don't have a whitelist to restrict us to + // or if we have a list, check if it includes the `channel.deposit_asset` + whitelist.is_empty() + || whitelist + .iter() + .any(|allowed| allowed == &channel.deposit_asset) +} + +#[derive(Debug, PartialEq, Eq)] +pub enum SanityError { + /// When the Adapter address is not listed in the `channel.spec.validators` + /// which in terms means, that the adapter shouldn't handle this Channel + AdapterNotIncluded, + /// when `channel.valid_until` has passed (< now), the channel should be handled + PassedValidUntil, + UnlistedValidator, + UnlistedCreator, + UnlistedAsset, + MinimumDepositNotMet, + MinimumValidatorFeeNotMet, +} + +impl fmt::Display for SanityError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Sanity error",) + } +} + +impl error::Error for SanityError { + fn cause(&self) -> Option<&dyn error::Error> { + None + } +} + +#[cfg(test)] +mod test { + use time::Duration; + + use domain::channel::fixtures::get_channel_spec; + use domain::fixtures::{get_channel, get_validator}; + + use crate::adapter::ConfigBuilder; + + use super::*; + + pub struct DummySanityChecker {} + impl SanityChecker for DummySanityChecker {} + + #[test] + fn sanity_check_disallows_channels_without_current_adapter() { + let channel = get_channel("channel_1", &None, None); + let config = ConfigBuilder::new("non_existent_validator").build(); + assert_eq!( + Err(SanityError::AdapterNotIncluded), + DummySanityChecker::check(&config, &channel) + ) + } + + #[test] + fn sanity_check_disallows_channels_with_passed_valid_until() { + let passed_valid_until = Utc::now() - Duration::seconds(1); + let channel = get_channel("channel_1", &Some(passed_valid_until), None); + + let identity = channel.spec.validators.leader().id.clone(); + let config = ConfigBuilder::new(&identity).build(); + + assert_eq!( + Err(SanityError::PassedValidUntil), + DummySanityChecker::check(&config, &channel) + ) + } + + #[test] + fn sanity_check_disallows_channels_with_unlisted_in_whitelist_validators() { + let channel = get_channel("channel_1", &None, None); + + // as identity use the leader, otherwise we won't pass the AdapterNotIncluded check + let identity = channel.spec.validators.leader().id.clone(); + let config = ConfigBuilder::new(&identity) + .set_validators_whitelist(&["my validator"]) + .build(); + + // make sure we don't use the leader or follower validators as a whitelisted validator + assert_ne!( + &identity, "my validator", + "The whitelisted validator and the leader have the same id" + ); + assert_ne!( + &channel.spec.validators.follower().id, + "my validator", + "The whitelisted validator and the follower have the same id" + ); + + assert_eq!( + Err(SanityError::UnlistedValidator), + DummySanityChecker::check(&config, &channel) + ) + } + + #[test] + fn sanity_check_disallows_channels_with_unlisted_creator() { + let channel = get_channel("channel_1", &None, None); + + // as identity use the leader, otherwise we won't pass the AdapterNotIncluded check + let identity = channel.spec.validators.leader().id.clone(); + let config = ConfigBuilder::new(&identity) + .set_creators_whitelist(&["creator"]) + .build(); + + assert_ne!( + &channel.creator, "creator", + "The channel creator should be different than the whitelisted creator" + ); + + assert_eq!( + Err(SanityError::UnlistedCreator), + DummySanityChecker::check(&config, &channel) + ) + } + + #[test] + fn sanity_check_disallows_channels_with_unlisted_asset() { + let channel = get_channel("channel_1", &None, None); + + // as identity use the leader, otherwise we won't pass the AdapterNotIncluded check + let identity = channel.spec.validators.leader().id.clone(); + let config = ConfigBuilder::new(&identity) + .set_assets_whitelist(&["ASSET".into()]) + .build(); + + assert_ne!( + &channel.deposit_asset, + &"ASSET".into(), + "The channel deposit_asset should be different than the whitelisted asset" + ); + + assert_eq!( + Err(SanityError::UnlistedAsset), + DummySanityChecker::check(&config, &channel) + ) + } + + #[test] + fn sanity_check_disallows_channel_deposit_less_than_minimum_deposit() { + let channel = get_channel("channel_1", &None, None); + + // as identity use the leader, otherwise we won't pass the AdapterNotIncluded check + let identity = channel.spec.validators.leader().id.clone(); + let config = ConfigBuilder::new(&identity) + // set the minimum deposit to the `channel.deposit_amount + 1` + .set_minimum_deposit(&channel.deposit_amount + &1.into()) + .build(); + + assert_eq!( + Err(SanityError::MinimumDepositNotMet), + DummySanityChecker::check(&config, &channel) + ) + } + + #[test] + fn sanity_check_disallows_validator_fee_less_than_minimum_fee() { + let channel = get_channel("channel_1", &None, None); + + let leader = channel.spec.validators.leader(); + // as identity use the leader, otherwise we won't pass the AdapterNotIncluded check + let identity = leader.id.clone(); + let config = ConfigBuilder::new(&identity) + // set the minimum deposit to the `leader.fee + 1` + .set_minimum_fee(&leader.fee + &1.into()) + .build(); + + assert_eq!( + Err(SanityError::MinimumValidatorFeeNotMet), + DummySanityChecker::check(&config, &channel) + ) + } + + #[test] + fn sanity_check_allows_for_valid_values() { + let validators = [ + get_validator("my leader", Some(10.into())), + get_validator("my follower", Some(15.into())), + ]; + let spec = get_channel_spec("channel_1", Some(validators.into())); + let channel = get_channel("channel_1", &None, Some(spec)); + + // as identity use the leader, otherwise we won't pass the AdapterNotIncluded check + let config = ConfigBuilder::new("my leader") + .set_validators_whitelist(&["my leader", "my follower"]) + .set_creators_whitelist(&[&channel.creator]) + .set_assets_whitelist(&[channel.deposit_asset.clone()]) + // set the minimum deposit to the `channel.deposit_amount - 1` + .set_minimum_deposit(&channel.deposit_amount - &1.into()) + // set the minimum fee to the `leader.fee - 1`, i.e. `10 - 1 = 9` + .set_minimum_fee(9.into()) + .build(); + + assert!(DummySanityChecker::check(&config, &channel).is_ok()) + } +} diff --git a/domain/src/asset.rs b/domain/src/asset.rs index 3547852c1..3a1cd4816 100644 --- a/domain/src/asset.rs +++ b/domain/src/asset.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Asset(String); impl From for Asset { diff --git a/domain/src/bignum.rs b/domain/src/bignum.rs index 73aceb2c1..71d854e8a 100644 --- a/domain/src/bignum.rs +++ b/domain/src/bignum.rs @@ -1,11 +1,11 @@ use std::convert::TryFrom; use std::error::Error; use std::iter::Sum; +use std::ops::{Add, Div, Mul, Sub}; use std::str::FromStr; use num_bigint::BigUint; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::ops::{Div, Mul}; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct BigNum( @@ -40,6 +40,24 @@ impl BigNum { } } +impl Add<&BigNum> for &BigNum { + type Output = BigNum; + + fn add(self, rhs: &BigNum) -> Self::Output { + let big_uint = &self.0 + &rhs.0; + BigNum(big_uint.to_owned()) + } +} + +impl Sub<&BigNum> for &BigNum { + type Output = BigNum; + + fn sub(self, rhs: &BigNum) -> Self::Output { + let big_uint = &self.0 - &rhs.0; + BigNum(big_uint.to_owned()) + } +} + impl Div<&BigNum> for &BigNum { type Output = BigNum; diff --git a/domain/src/channel.rs b/domain/src/channel.rs index 4f16ec059..066e64bb7 100644 --- a/domain/src/channel.rs +++ b/domain/src/channel.rs @@ -123,8 +123,7 @@ pub struct Channel { pub struct ChannelSpec { #[serde(default, skip_serializing_if = "Option::is_none")] pub title: Option, - // TODO: Make a custom ser/deser 2 validators(leader, follower) array - pub validators: Vec, + pub validators: SpecValidators, /// Maximum payment per impression pub max_per_impression: BigNum, /// Minimum payment offered per impression @@ -161,6 +160,55 @@ pub struct ChannelSpec { pub ad_units: Vec, } +pub enum SpecValidator<'a> { + Leader(&'a ValidatorDesc), + Follower(&'a ValidatorDesc), + None, +} + +impl<'a> SpecValidator<'a> { + pub fn is_some(&self) -> bool { + match &self { + SpecValidator::None => false, + _ => true, + } + } + + pub fn is_none(&self) -> bool { + !self.is_some() + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(transparent)] +pub struct SpecValidators([ValidatorDesc; 2]); + +impl SpecValidators { + pub fn leader(&self) -> &ValidatorDesc { + &self.0[0] + } + + pub fn follower(&self) -> &ValidatorDesc { + &self.0[1] + } + + pub fn find(&self, validator_id: &str) -> SpecValidator<'_> { + if self.leader().id == validator_id { + SpecValidator::Leader(&self.leader()) + } else if self.follower().id == validator_id { + SpecValidator::Follower(&self.follower()) + } else { + SpecValidator::None + } + } +} + +impl From<[ValidatorDesc; 2]> for SpecValidators { + fn from(slice: [ValidatorDesc; 2]) -> Self { + Self(slice) + } +} + pub struct ChannelListParams { /// page to show, should be >= 1 pub page: u32, diff --git a/domain/src/channel_fixtures.rs b/domain/src/channel_fixtures.rs index 2f9eedbfc..6953de5e9 100644 --- a/domain/src/channel_fixtures.rs +++ b/domain/src/channel_fixtures.rs @@ -1,13 +1,13 @@ use chrono::{DateTime, Utc}; use fake::faker::*; +use time::Duration; use crate::asset::fixtures::get_asset; -use crate::fixtures::{get_targeting_tags, get_validators}; +use crate::fixtures::{get_targeting_tags, get_validator}; use crate::test_util; use crate::BigNum; -use crate::ValidatorDesc; -use super::{Channel, ChannelId, ChannelSpec}; +use super::{Channel, ChannelId, ChannelSpec, SpecValidators}; /// It will get the length of channel_id bytes and will fill enough bytes in front /// If > 32 bytes &str is passed it will `panic!` @@ -32,11 +32,13 @@ pub fn get_channel( ) -> Channel { let channel_id = get_channel_id(id); let deposit_amount = BigNum::from(::between(100, 5000)); - let valid_until: DateTime = - valid_until.unwrap_or(test_util::time::datetime_between(&Utc::now(), None)); + let valid_until: DateTime = valid_until.unwrap_or_else(|| { + let future_from = Utc::now() + Duration::days(7); + test_util::time::datetime_between(&future_from, None) + }); let creator = ::name(); let deposit_asset = get_asset(); - let spec = spec.unwrap_or_else(|| get_channel_spec(id, ValidatorsOption::Count(3))); + let spec = spec.unwrap_or_else(|| get_channel_spec(id, None)); Channel { id: channel_id, @@ -61,22 +63,18 @@ pub fn get_channels(count: usize, valid_until_ge: Option>) -> Vec< .collect() } -#[derive(Clone)] -#[allow(dead_code)] -pub enum ValidatorsOption { - Count(usize), - Some(Vec), - None, -} +pub fn get_channel_spec(prefix: &str, validators_option: Option) -> ChannelSpec { + use crate::EventSubmission; + use test_util::take_one; -pub fn get_channel_spec(prefix: &str, validators_option: ValidatorsOption) -> ChannelSpec { let validators = match validators_option { - ValidatorsOption::Count(count) => get_validators(count, Some(prefix)), - ValidatorsOption::Some(validators) => validators, - ValidatorsOption::None => vec![], + Some(validators) => validators, + None => [ + get_validator(&format!("{} leader", prefix), None), + get_validator(&format!("{} follower", prefix), None), + ] + .into(), }; - use crate::EventSubmission; - use test_util::take_one; let title_string = Some(::sentence(3, 4)); diff --git a/domain/src/lib.rs b/domain/src/lib.rs index 10e1e43b3..418d719a6 100644 --- a/domain/src/lib.rs +++ b/domain/src/lib.rs @@ -1,4 +1,5 @@ #![deny(rust_2018_idioms)] +#![deny(clippy::all)] use std::error; use std::fmt; diff --git a/domain/src/validator.rs b/domain/src/validator.rs index b50ea9180..1a91e416d 100644 --- a/domain/src/validator.rs +++ b/domain/src/validator.rs @@ -11,15 +11,15 @@ pub struct ValidatorDesc { } #[cfg(any(test, feature = "fixtures"))] -pub(crate) mod fixtures { +pub mod fixtures { use fake::faker::*; use crate::BigNum; use super::ValidatorDesc; - pub fn get_validator(validator_id: &str) -> ValidatorDesc { - let fee = BigNum::from(::between(1, 13)); + pub fn get_validator(validator_id: &str, fee: Option) -> ValidatorDesc { + let fee = fee.unwrap_or_else(|| BigNum::from(::between(1, 13))); let url = format!("http://{}-validator-url.com/validator", validator_id); ValidatorDesc { @@ -35,7 +35,7 @@ pub(crate) mod fixtures { .map(|c| { let validator_id = format!("{}validator-{}", prefix, c + 1); - get_validator(&validator_id) + get_validator(&validator_id, None) }) .collect() } diff --git a/sentry/src/infrastructure/persistence/channel/memory.rs b/sentry/src/infrastructure/persistence/channel/memory.rs index 6e4b84428..b9e0cd0d4 100644 --- a/sentry/src/infrastructure/persistence/channel/memory.rs +++ b/sentry/src/infrastructure/persistence/channel/memory.rs @@ -44,11 +44,7 @@ impl ChannelRepository for MemoryChannelRepository { Some(validator_id) => { // check if there is any validator in the current // `channel.spec.validators` that has the same `id` - channel - .spec - .validators - .iter() - .any(|validator| &validator.id == validator_id) + channel.spec.validators.find(&validator_id).is_some() } // if None -> the current channel has passed, since we don't need to filter by anything None => true, diff --git a/sentry/src/infrastructure/persistence/channel/memory_test.rs b/sentry/src/infrastructure/persistence/channel/memory_test.rs index 0fd2c5b2c..31248bd01 100644 --- a/sentry/src/infrastructure/persistence/channel/memory_test.rs +++ b/sentry/src/infrastructure/persistence/channel/memory_test.rs @@ -1,6 +1,7 @@ use chrono::Utc; use time::Duration; +use domain::channel::SpecValidators; use domain::fixtures::*; use domain::{Channel, ChannelListParams, ChannelRepository, RepositoryError}; @@ -127,10 +128,13 @@ fn listing_channels_can_handles_validator_filtration_and_keeps_valid_until_filtr // as they might otherwise have valid_until < valid_until_ge let valid_until_ge = Utc::now(); - let validators = vec![get_validator("validator-1"), get_validator("validator-2")]; - let validators_opt = ValidatorsOption::Some(validators); - let channel_2_spec = get_channel_spec("channel 2", validators_opt.clone()); - let channel_5_spec = get_channel_spec("channel 5", validators_opt.clone()); + let validators: SpecValidators = [ + get_validator("validator-1", None), + get_validator("validator-2", None), + ] + .into(); + let channel_2_spec = get_channel_spec("channel 2", Some(validators.clone())); + let channel_5_spec = get_channel_spec("channel 5", Some(validators)); let channels = [ get_channel("channel 1", &None, None), diff --git a/sentry/src/lib.rs b/sentry/src/lib.rs index a6bb11d2f..94f644033 100644 --- a/sentry/src/lib.rs +++ b/sentry/src/lib.rs @@ -1,4 +1,5 @@ #![feature(async_await, await_macro)] #![deny(rust_2018_idioms)] +#![deny(clippy::all)] pub mod application; pub mod infrastructure;