Skip to content

Commit a92e638

Browse files
authored
Issue #79 heartbeat factory service (#85)
* domain - validator - message - Heartbeat - add `state_root` * validator - Cargo.toml - add `chrono` * adapter - AdapterError - impl Error + Display and update `sign()` * domain - validator - message - Heartbeat add `state_root` and `new()` * validator - application - heartbeat - HeartbeatFactory * domain - validator - message - Heartbeat add `new()` and a private field * [+test] validator - application - heartbeat - HeartbeatFactory * `rustfmt`
1 parent 1df4fa8 commit a92e638

File tree

7 files changed

+114
-8
lines changed

7 files changed

+114
-8
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

adapter/src/adapter.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ use domain::validator::message::State;
66
use domain::{Asset, BigNum, Channel};
77

88
use crate::sanity::SanityChecker;
9+
use std::error::Error;
10+
use std::fmt;
911

1012
pub type AdapterFuture<T> = Pin<Box<dyn Future<Output = Result<T, AdapterError>> + Send>>;
1113

@@ -14,6 +16,16 @@ pub enum AdapterError {
1416
Authentication(String),
1517
}
1618

19+
impl Error for AdapterError {}
20+
21+
impl fmt::Display for AdapterError {
22+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23+
match self {
24+
AdapterError::Authentication(error) => write!(f, "Authentication error: {}", error),
25+
}
26+
}
27+
}
28+
1729
pub trait Adapter: SanityChecker + State {
1830
fn config(&self) -> &Config;
1931

@@ -22,7 +34,7 @@ pub trait Adapter: SanityChecker + State {
2234
}
2335

2436
/// Signs the provided state_root
25-
fn sign(&self, state_root: &str) -> AdapterFuture<String>;
37+
fn sign(&self, state_root: &Self::StateRoot) -> AdapterFuture<Self::Signature>;
2638

2739
/// Verify, based on the signature & state_root, that the signer is the same
2840
fn verify(

adapter/src/dummy.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,12 @@ impl<'a> Adapter for DummyAdapter<'a> {
4444
/// let config = ConfigBuilder::new("identity").build();
4545
/// let adapter = DummyAdapter { config, participants: HashMap::new() };
4646
///
47-
/// let actual = await!(adapter.sign("abcdefghijklmnopqrstuvwxyz012345")).unwrap();
47+
/// let actual = await!(adapter.sign(&"abcdefghijklmnopqrstuvwxyz012345".to_string())).unwrap();
4848
/// let expected = "Dummy adapter signature for 6162636465666768696a6b6c6d6e6f707172737475767778797a303132333435 by identity";
4949
/// assert_eq!(expected, &actual);
5050
/// # });
5151
/// ```
52-
fn sign(&self, state_root: &str) -> AdapterFuture<String> {
52+
fn sign(&self, state_root: &Self::StateRoot) -> AdapterFuture<Self::Signature> {
5353
let signature = format!(
5454
"Dummy adapter signature for {} by {}",
5555
encode(&state_root),
@@ -148,8 +148,9 @@ mod test {
148148
};
149149

150150
let expected_signature = "Dummy adapter signature for 6162636465666768696a6b6c6d6e6f707172737475767778797a303132333435 by identity";
151-
let actual_signature = await!(adapter.sign("abcdefghijklmnopqrstuvwxyz012345"))
152-
.expect("Signing shouldn't fail");
151+
let actual_signature =
152+
await!(adapter.sign(&"abcdefghijklmnopqrstuvwxyz012345".to_string()))
153+
.expect("Signing shouldn't fail");
153154

154155
assert_eq!(expected_signature, &actual_signature);
155156

domain/src/validator/message.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,23 @@ pub struct RejectState {
8282
#[derive(Serialize, Deserialize, Debug, Clone)]
8383
#[serde(rename_all = "camelCase")]
8484
pub struct Heartbeat<S: State> {
85-
signature: S::Signature,
86-
timestamp: DateTime<Utc>,
85+
pub signature: S::Signature,
86+
pub state_root: S::StateRoot,
87+
pub timestamp: DateTime<Utc>,
88+
// we always want to create heartbeat with Timestamp NOW, so add a hidden field
89+
// and force the creation of Heartbeat always to be from the `new()` method
90+
_secret: (),
91+
}
92+
93+
impl<S: State> Heartbeat<S> {
94+
pub fn new(signature: S::Signature, state_root: S::StateRoot) -> Self {
95+
Self {
96+
signature,
97+
state_root,
98+
timestamp: Utc::now(),
99+
_secret: (),
100+
}
101+
}
87102
}
88103

89104
#[derive(Serialize, Deserialize, Debug, Clone)]
@@ -142,10 +157,15 @@ pub mod fixtures {
142157
}
143158
}
144159

145-
pub fn get_heartbeat<S: State>(signature: S::Signature) -> Heartbeat<S> {
160+
pub fn get_heartbeat<S: State>(
161+
state_root: S::StateRoot,
162+
signature: S::Signature,
163+
) -> Heartbeat<S> {
146164
Heartbeat {
165+
state_root,
147166
signature,
148167
timestamp: past_datetime(None),
168+
_secret: (),
149169
}
150170
}
151171

validator/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ path = "src/lib.rs"
1313
domain = { version = "0.1", path = "../domain", features = ["repositories"] }
1414
adapter = { version = "0.1", path = "../adapter", features = ["dummy-adapter"] }
1515
memory-repository = { version = "0.1", path = "../memory-repository" }
16+
chrono = { version = "0.4", features = ["serde"] }
1617
# Futures
1718
futures-preview = { version = "=0.3.0-alpha.16", features = ["compat", "io-compat"] }
1819
futures_legacy = { version = "0.1", package = "futures" }

validator/src/application.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
pub mod heartbeat;
12
pub mod validator;
23
pub mod worker;
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use std::error::Error;
2+
use std::fmt;
3+
4+
use adapter::{Adapter, AdapterError};
5+
use domain::validator::message::{Heartbeat, State};
6+
7+
pub struct HeartbeatFactory<A: Adapter + State> {
8+
adapter: A,
9+
}
10+
11+
#[derive(Debug)]
12+
pub enum HeartbeatFactoryError {
13+
Adapter(AdapterError),
14+
}
15+
16+
impl Error for HeartbeatFactoryError {}
17+
18+
impl fmt::Display for HeartbeatFactoryError {
19+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20+
match self {
21+
HeartbeatFactoryError::Adapter(error) => write!(f, "Adapter error: {}", error),
22+
}
23+
}
24+
}
25+
26+
impl<A: Adapter + State> HeartbeatFactory<A> {
27+
#[allow(clippy::needless_lifetimes)]
28+
pub async fn create(
29+
&self,
30+
state_root: A::StateRoot,
31+
) -> Result<Heartbeat<A>, HeartbeatFactoryError> {
32+
let signature =
33+
await!(self.adapter.sign(&state_root)).map_err(HeartbeatFactoryError::Adapter)?;
34+
35+
Ok(Heartbeat::new(signature, state_root))
36+
}
37+
}
38+
39+
#[cfg(test)]
40+
mod test {
41+
use std::collections::HashMap;
42+
43+
use adapter::dummy::DummyAdapter;
44+
use adapter::ConfigBuilder;
45+
46+
use super::*;
47+
use chrono::Utc;
48+
49+
#[test]
50+
fn creates_heartbeat() {
51+
futures::executor::block_on(async {
52+
let adapter = DummyAdapter {
53+
config: ConfigBuilder::new("identity").build(),
54+
participants: HashMap::default(),
55+
};
56+
57+
let factory = HeartbeatFactory { adapter };
58+
59+
let state_root = "my dummy StateRoot".to_string();
60+
61+
let adapter_signature = await!(factory.adapter.sign(&state_root))
62+
.expect("Adapter should sign the StateRoot");
63+
let heartbeat =
64+
await!(factory.create(state_root)).expect("Heartbeat should be created");
65+
66+
assert!(Utc::now() >= heartbeat.timestamp);
67+
assert_eq!(adapter_signature, heartbeat.signature);
68+
});
69+
}
70+
}

0 commit comments

Comments
 (0)