Skip to content

Commit ec51ec4

Browse files
authored
Issue #53 Dummy Adapter (#54)
* Workspace - add `Adapter` crate * domain - validator - make `fixtures` module `pub`, instead of `pub(crate)` * adapter - Cargo.toml - add `domain` to deps and enable `fixtures` feature for dev. dep. * adapter - lib - start impl SanityChecker & add simple trait Adapter * adapter - separate SanityChecker, Adapter and add a feature for DummyAdapter * adapter - Cargo.toml - add the `dummy-adapter` feature * domain - channel - impl SpecValidators, as we need to handle only a leader and a follower inside * adapter - sanity - simplify the check if the adapter exists in the channel validators * Makefile - add `tasks.clippy` to `dev-test-flow` after `rustfmt` for Dev env. * `Clippy` warnings fix - take `&str` instead of `&String` * Clippy: - domina/sentry/adapter - add `deny(clippy::all)` - adapter - add `deny(rust_2018_idioms)` * adapter - `Adapter::whoami()` use `&str` instead of `&String` * domain - channel - fixtures - valid_until should always be in the future * adapter - Cargo.toml - add `chrono` and `time` * domain - Asset - derive `PartialEq` & `Eq` * domain - BigNum - impl `Add` & `Sub` * adapter - Config and ConfigBuilder * [+test] adapter - sanity - impl all the validation checks * [test] adapter - sanity - fix minimum fee test * domain - validator - fixtures - optional `fee` argument * [test] adapter - sanity - SanityCheck correct channel validation test * run `rustfmt` * adapter - Cargo.toml - add `hex` * adapter - add `sign` & `verify` + impl dummy `sign` & `verify` * [+test] adapter - dummy - `sign` & `verify` docs-tests + a unit test * adapter - Adapter - `get_auth` method: - Adapter - AdapterError - dummy - DummyParticipant - dummy - Dummy - use `HashSet` and store the dummy participants - dummy - Dummy - use participants for the `get_auth` method * [+test] adapter - DummyAdapter - fix implementation of `get_auth` * adapter - DummyAdapter - move the success `get_auth` call to doc-test * adapter - add doc-test attribute to enable the `dummy-adapter` feature * adapter - dummy - why does CI fails :( * adapter - add `futures-preview` * adapter - enable `async_await` & await_macro` features + add them to the doc-tests as well * adapter - `sign`, `verify` & `validate_channel` to `Future` based * adapter - `get_auth` to `Future` based * Makefile.toml - Use the new env. variable for `clippy` arguments
1 parent 32bb3dc commit ec51ec4

File tree

18 files changed

+713
-41
lines changed

18 files changed

+713
-41
lines changed

.travis.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ env:
88
- CARGO_MAKE_RUN_CLIPPY="true"
99
matrix:
1010
fast_finish: true
11-
install:
12-
- if [[ $(cargo list | grep -c make) == 0 ]]; then cargo install --debug cargo-make ; else echo "Cargo make already installed, continuing..."; fi
11+
script:
12+
- which cargo-make || cargo install cargo-make
1313
- cargo make ci-flow

Cargo.lock

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[workspace]
22

33
members = [
4-
"sentry",
4+
"adapter",
55
"domain",
6+
"sentry",
67
]

Makefile.toml

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
[env]
22
CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = "true"
3+
CARGO_MAKE_CLIPPY_ARGS = "--all-features -- -D warnings"
34

4-
[tasks.clippy]
5-
args = ["clippy", "--all-features", "--", "-D", "warnings"]
5+
[tasks.dev-test-flow]
6+
dependencies = [
7+
"format-flow",
8+
"clippy",
9+
"pre-build",
10+
"build",
11+
"post-build",
12+
"test-flow",
13+
]

adapter/Cargo.toml

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[package]
2+
name = "adapter"
3+
version = "0.1.0"
4+
authors = ["Lachezar Lechev <[email protected]>"]
5+
edition = "2018"
6+
7+
[features]
8+
# Allows you to use a Dummy implementation of the Adapter for testing purposes
9+
dummy-adapter = []
10+
11+
[dependencies]
12+
domain = {path = "../domain"}
13+
# Futures
14+
futures-preview = { version = "=0.3.0-alpha.16" }
15+
# Time handling
16+
chrono = "0.4"
17+
time = "0.1.42"
18+
# To/From Hex
19+
# @TODO: Move this to the `dummy-adapter` feature if we use it only there!
20+
hex = "0.3.2"
21+
[dev-dependencies]
22+
domain = {path = "../domain", features = ["fixtures"]}

adapter/src/adapter.rs

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use domain::{Asset, BigNum, Channel};
2+
3+
use crate::sanity::SanityChecker;
4+
use futures::{Future, FutureExt};
5+
use std::pin::Pin;
6+
7+
pub type AdapterFuture<T> = Pin<Box<dyn Future<Output = Result<T, AdapterError>> + Send>>;
8+
9+
#[derive(Debug, Eq, PartialEq)]
10+
pub enum AdapterError {
11+
Authentication(String),
12+
}
13+
14+
pub trait Adapter: SanityChecker {
15+
fn config(&self) -> &Config;
16+
17+
fn validate_channel(&self, channel: &Channel) -> AdapterFuture<bool> {
18+
futures::future::ok(Self::check(&self.config(), &channel).is_ok()).boxed()
19+
}
20+
21+
/// Signs the provided state_root
22+
fn sign(&self, state_root: &str) -> AdapterFuture<String>;
23+
24+
/// Verify, based on the signature & state_root, that the signer is the same
25+
fn verify(&self, signer: &str, state_root: &str, signature: &str) -> AdapterFuture<bool>;
26+
27+
/// Gets authentication for specific validator
28+
fn get_auth(&self, validator: &str) -> AdapterFuture<String>;
29+
}
30+
31+
pub struct Config {
32+
pub identity: String,
33+
pub validators_whitelist: Vec<String>,
34+
pub creators_whitelist: Vec<String>,
35+
pub assets_whitelist: Vec<Asset>,
36+
pub minimal_deposit: BigNum,
37+
pub minimal_fee: BigNum,
38+
}
39+
40+
pub struct ConfigBuilder {
41+
identity: String,
42+
validators_whitelist: Vec<String>,
43+
creators_whitelist: Vec<String>,
44+
assets_whitelist: Vec<Asset>,
45+
minimal_deposit: BigNum,
46+
minimal_fee: BigNum,
47+
}
48+
49+
impl ConfigBuilder {
50+
pub fn new(identity: &str) -> Self {
51+
Self {
52+
identity: identity.to_string(),
53+
validators_whitelist: Vec::new(),
54+
creators_whitelist: Vec::new(),
55+
assets_whitelist: Vec::new(),
56+
minimal_deposit: 0.into(),
57+
minimal_fee: 0.into(),
58+
}
59+
}
60+
61+
pub fn set_validators_whitelist(mut self, validators: &[&str]) -> Self {
62+
self.validators_whitelist = validators.iter().map(|slice| slice.to_string()).collect();
63+
self
64+
}
65+
66+
pub fn set_creators_whitelist(mut self, creators: &[&str]) -> Self {
67+
self.creators_whitelist = creators.iter().map(|slice| slice.to_string()).collect();
68+
self
69+
}
70+
71+
pub fn set_assets_whitelist(mut self, assets: &[Asset]) -> Self {
72+
self.assets_whitelist = assets.to_vec();
73+
self
74+
}
75+
76+
pub fn set_minimum_deposit(mut self, minimum: BigNum) -> Self {
77+
self.minimal_deposit = minimum;
78+
self
79+
}
80+
81+
pub fn set_minimum_fee(mut self, minimum: BigNum) -> Self {
82+
self.minimal_fee = minimum;
83+
self
84+
}
85+
86+
pub fn build(self) -> Config {
87+
Config {
88+
identity: self.identity,
89+
validators_whitelist: self.validators_whitelist,
90+
creators_whitelist: self.creators_whitelist,
91+
assets_whitelist: self.assets_whitelist,
92+
minimal_deposit: self.minimal_deposit,
93+
minimal_fee: self.minimal_fee,
94+
}
95+
}
96+
}

adapter/src/dummy.rs

+187
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
use std::collections::HashMap;
2+
3+
use hex::encode;
4+
5+
use crate::adapter::{Adapter, AdapterError, AdapterFuture, Config};
6+
use crate::sanity::SanityChecker;
7+
use futures::future::{err, ok, FutureExt};
8+
9+
#[derive(Debug)]
10+
pub struct DummyParticipant {
11+
pub identity: String,
12+
pub token: String,
13+
}
14+
15+
pub struct DummyAdapter<'a> {
16+
pub config: Config,
17+
/// Dummy participants which will be used for
18+
/// Creator, Validator Leader, Validator Follower and etc.
19+
pub participants: HashMap<&'a str, DummyParticipant>,
20+
}
21+
22+
impl SanityChecker for DummyAdapter<'_> {}
23+
24+
impl Adapter for DummyAdapter<'_> {
25+
fn config(&self) -> &Config {
26+
&self.config
27+
}
28+
29+
/// Example:
30+
///
31+
/// ```
32+
/// use adapter::{ConfigBuilder, Adapter};
33+
/// use adapter::dummy::DummyAdapter;
34+
/// use std::collections::HashMap;
35+
///
36+
/// futures::executor::block_on(async {
37+
/// let config = ConfigBuilder::new("identity").build();
38+
/// let adapter = DummyAdapter { config, participants: HashMap::new() };
39+
///
40+
/// let actual = await!(adapter.sign("abcdefghijklmnopqrstuvwxyz012345")).unwrap();
41+
/// let expected = "Dummy adapter signature for 6162636465666768696a6b6c6d6e6f707172737475767778797a303132333435 by identity";
42+
/// assert_eq!(expected, &actual);
43+
/// });
44+
/// ```
45+
fn sign(&self, state_root: &str) -> AdapterFuture<String> {
46+
let signature = format!(
47+
"Dummy adapter signature for {} by {}",
48+
encode(&state_root),
49+
&self.config.identity
50+
);
51+
ok(signature).boxed()
52+
}
53+
54+
/// Example:
55+
///
56+
/// ```
57+
/// use adapter::{ConfigBuilder, Adapter};
58+
/// use adapter::dummy::DummyAdapter;
59+
/// use std::collections::HashMap;
60+
///
61+
/// futures::executor::block_on(async {
62+
/// let config = ConfigBuilder::new("identity").build();
63+
/// let adapter = DummyAdapter { config, participants: HashMap::new() };
64+
///
65+
/// let signature = "Dummy adapter signature for 6162636465666768696a6b6c6d6e6f707172737475767778797a303132333435 by identity";
66+
/// assert_eq!(Ok(true), await!(adapter.verify("identity", "doesn't matter", signature)));
67+
/// });
68+
/// ```
69+
fn verify(&self, signer: &str, _state_root: &str, signature: &str) -> AdapterFuture<bool> {
70+
// select the `identity` and compare it to the signer
71+
// for empty string this will return array with 1 element - an empty string `[""]`
72+
let is_same = match signature.rsplit(' ').take(1).next() {
73+
Some(from) => from == signer,
74+
None => false,
75+
};
76+
77+
ok(is_same).boxed()
78+
}
79+
80+
/// Finds the auth. token in the HashMap of DummyParticipants if exists
81+
///
82+
/// Example:
83+
///
84+
/// ```
85+
/// use std::collections::HashMap;
86+
/// use adapter::dummy::{DummyParticipant, DummyAdapter};
87+
/// use adapter::{ConfigBuilder, Adapter};
88+
///
89+
/// futures::executor::block_on(async {
90+
/// let mut participants = HashMap::new();
91+
/// participants.insert(
92+
/// "identity_key",
93+
/// DummyParticipant {
94+
/// identity: "identity".to_string(),
95+
/// token: "token".to_string(),
96+
/// },
97+
/// );
98+
///
99+
/// let adapter = DummyAdapter {
100+
/// config: ConfigBuilder::new("identity").build(),
101+
/// participants,
102+
/// };
103+
///
104+
/// assert_eq!(Ok("token".to_string()), await!(adapter.get_auth("identity")));
105+
/// });
106+
/// ```
107+
fn get_auth(&self, validator: &str) -> AdapterFuture<String> {
108+
let participant = self
109+
.participants
110+
.iter()
111+
.find(|&(_, participant)| participant.identity == validator);
112+
let future = match participant {
113+
Some((_, participant)) => ok(participant.token.to_string()),
114+
None => err(AdapterError::Authentication(
115+
"Identity not found".to_string(),
116+
)),
117+
};
118+
119+
future.boxed()
120+
}
121+
}
122+
123+
#[cfg(test)]
124+
mod test {
125+
use crate::adapter::ConfigBuilder;
126+
127+
use super::*;
128+
129+
#[test]
130+
fn dummy_adapter_sings_state_root_and_verifies_it() {
131+
futures::executor::block_on(async {
132+
let config = ConfigBuilder::new("identity").build();
133+
let adapter = DummyAdapter {
134+
config,
135+
participants: HashMap::new(),
136+
};
137+
138+
let expected_signature = "Dummy adapter signature for 6162636465666768696a6b6c6d6e6f707172737475767778797a303132333435 by identity";
139+
let actual_signature = await!(adapter.sign("abcdefghijklmnopqrstuvwxyz012345"))
140+
.expect("Signing shouldn't fail");
141+
142+
assert_eq!(expected_signature, &actual_signature);
143+
144+
let is_verified =
145+
await!(adapter.verify("identity", "doesn't matter", &actual_signature));
146+
147+
assert_eq!(Ok(true), is_verified);
148+
});
149+
}
150+
151+
#[test]
152+
fn get_auth_with_empty_participators() {
153+
futures::executor::block_on(async {
154+
let adapter = DummyAdapter {
155+
config: ConfigBuilder::new("identity").build(),
156+
participants: HashMap::new(),
157+
};
158+
159+
assert_eq!(
160+
Err(AdapterError::Authentication(
161+
"Identity not found".to_string()
162+
)),
163+
await!(adapter.get_auth("non-existing"))
164+
);
165+
166+
let mut participants = HashMap::new();
167+
participants.insert(
168+
"identity_key",
169+
DummyParticipant {
170+
identity: "identity".to_string(),
171+
token: "token".to_string(),
172+
},
173+
);
174+
let adapter = DummyAdapter {
175+
config: ConfigBuilder::new("identity").build(),
176+
participants,
177+
};
178+
179+
assert_eq!(
180+
Err(AdapterError::Authentication(
181+
"Identity not found".to_string()
182+
)),
183+
await!(adapter.get_auth("non-existing"))
184+
);
185+
});
186+
}
187+
}

adapter/src/lib.rs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#![feature(async_await, await_macro)]
2+
#![deny(rust_2018_idioms)]
3+
#![deny(clippy::all)]
4+
#![doc(test(attr(feature(async_await, await_macro))))]
5+
#![doc(test(attr(cfg(feature = "dummy-adapter"))))]
6+
pub use self::adapter::*;
7+
pub use self::sanity::*;
8+
9+
mod adapter;
10+
#[cfg(any(test, feature = "dummy-adapter"))]
11+
pub mod dummy;
12+
mod sanity;

0 commit comments

Comments
 (0)