Skip to content

Commit 35263a4

Browse files
committed
add chain cli observer
1 parent 9981f96 commit 35263a4

File tree

5 files changed

+169
-4
lines changed

5 files changed

+169
-4
lines changed

mithril-common/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ thiserror = "1.0.31"
2828
tokio = { version = "1.17.0", features = ["full"] }
2929
walkdir = "2"
3030
warp = "0.3"
31+
nom = "7.1"
3132

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
#![allow(dead_code)]
2+
use async_trait::async_trait;
3+
use nom::IResult;
4+
use std::error::Error;
5+
use std::path::PathBuf;
6+
use tokio::process::Command;
7+
8+
use crate::chain_observer::interface::*;
9+
use crate::entities::{Epoch, StakeDistribution};
10+
11+
#[async_trait]
12+
pub trait CliRunner {
13+
async fn launch_stake_distribution(&self) -> Result<String, Box<dyn Error + Sync + Send>>;
14+
async fn launch_epoch(&self) -> Result<String, Box<dyn Error + Sync + Send>>;
15+
}
16+
struct CardanoCliRunner {
17+
cli_path: PathBuf,
18+
socket_path: PathBuf,
19+
}
20+
21+
impl CardanoCliRunner {
22+
pub fn new(cli_path: PathBuf, socket_path: PathBuf) -> Self {
23+
Self {
24+
cli_path,
25+
socket_path,
26+
}
27+
}
28+
29+
fn get_command(&self) -> Command {
30+
let mut command = Command::new(&self.cli_path);
31+
command.env(
32+
"CARDANO_NODE_SOCKET_PATH",
33+
&*self.socket_path.to_string_lossy(),
34+
);
35+
36+
command
37+
}
38+
}
39+
40+
#[async_trait]
41+
impl CliRunner for CardanoCliRunner {
42+
async fn launch_stake_distribution(&self) -> Result<String, Box<dyn Error + Sync + Send>> {
43+
let output = self
44+
.get_command()
45+
.arg("query")
46+
.arg("stake-distribution")
47+
.output()
48+
.await?;
49+
50+
Ok(std::str::from_utf8(&output.stdout)?.to_string())
51+
}
52+
53+
async fn launch_epoch(&self) -> Result<String, Box<dyn Error + Sync + Send>> {
54+
todo!()
55+
}
56+
}
57+
58+
pub struct CliChainObserver {
59+
cli_runner: Box<dyn CliRunner + Send + Sync>,
60+
}
61+
62+
impl CliChainObserver {
63+
pub fn new(cli_runner: Box<dyn CliRunner + Send + Sync>) -> Self {
64+
Self { cli_runner }
65+
}
66+
67+
// This is the only way I found to tell the compiler the correct types
68+
// and lifetimes for the function `double`.
69+
fn parse_string<'a>(&'a self, string: &'a str) -> IResult<&str, f64> {
70+
nom::number::complete::double(string)
71+
}
72+
}
73+
74+
#[async_trait]
75+
impl ChainObserver for CliChainObserver {
76+
async fn get_current_epoch(&self) -> Result<Option<Epoch>, ChainObserverError> {
77+
todo!()
78+
}
79+
80+
async fn get_current_stake_distribution(
81+
&self,
82+
) -> Result<Option<StakeDistribution>, ChainObserverError> {
83+
let output = self
84+
.cli_runner
85+
.launch_stake_distribution()
86+
.await
87+
.map_err(ChainObserverError::General)?;
88+
let mut stake_distribution = StakeDistribution::new();
89+
90+
for (num, line) in output.lines().enumerate() {
91+
let words: Vec<&str> = line.split_ascii_whitespace().collect();
92+
93+
if num < 3 || words.len() != 2 {
94+
continue;
95+
}
96+
97+
if let Ok((_, f)) = self.parse_string(words[1]) {
98+
let stake: u64 = (f * 1_000_000_000.0).round() as u64;
99+
// TODO: the stake distribution shall not be indexed by position
100+
// use the real poolId instead, for now this must be a u32.
101+
//
102+
// The position is num - 2 since we ignore the first two lines
103+
// of the CLI output.
104+
if stake > 0 {
105+
let _ = stake_distribution.insert(num as u64 - 2, stake);
106+
}
107+
}
108+
}
109+
110+
Ok(Some(stake_distribution))
111+
}
112+
}
113+
114+
#[cfg(test)]
115+
mod tests {
116+
use super::*;
117+
118+
struct TestCliRunner {}
119+
120+
#[async_trait]
121+
impl CliRunner for TestCliRunner {
122+
async fn launch_stake_distribution(&self) -> Result<String, Box<dyn Error + Sync + Send>> {
123+
let output = r#"
124+
PoolId Stake frac
125+
------------------------------------------------------------------------------
126+
pool1qqyjr9pcrv97gwrueunug829fs5znw6p2wxft3fvqkgu5f4qlrg 2.493e-3
127+
pool1qqfnw2fwajdnam7xsqhhrje5cgd8jcltzfrx655rd23eqlxjfef 2.164e-5
128+
pool1qqnjh80kudcjphrxftj74x22q3a4uvw8wknlxptgs7gdqtstqad 8.068e-7
129+
pool1qquwwu6680fr72y4779r2kpc7mxtch8rp2uhuqcc7v9p6q4f7ph 7.073e-7
130+
pool1qpqvz90w7qsex2al2ejjej0rfgrwsguch307w8fraw7a7adf6g8 2.474e-11
131+
pool1qptl80vq84xm28pt3t2lhpfzqag28csjhktxz5k6a74n260clmt 5.600e-7
132+
pool1qpuckgzxwgdru9vvq3ydmuqa077ur783yn2uywz7zq2c29p506e 5.161e-5
133+
pool1qz2vzszautc2c8mljnqre2857dpmheq7kgt6vav0s38tvvhxm6w 1.051e-6
134+
"#;
135+
136+
Ok(output.to_string())
137+
}
138+
139+
async fn launch_epoch(&self) -> Result<String, Box<dyn Error + Sync + Send>> {
140+
todo!()
141+
}
142+
}
143+
#[tokio::test]
144+
async fn test_get_current_stake_distribution() {
145+
let observer = CliChainObserver::new(Box::new(TestCliRunner {}));
146+
let results = observer
147+
.get_current_stake_distribution()
148+
.await
149+
.unwrap()
150+
.unwrap();
151+
152+
assert_eq!(7, results.len());
153+
assert_eq!(2_493_000, *results.get(&1).unwrap());
154+
assert_eq!(1_051, *results.get(&8).unwrap());
155+
assert!(results.get(&5).is_none());
156+
}
157+
}

mithril-common/src/chain_observer/fake_observer.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,16 @@ mod tests {
4848
async fn test_get_current_epoch() {
4949
let beacon = fake_data::beacon();
5050
let fake_observer = FakeObserver::new();
51-
let current_epoch = fake_observer.get_current_epoch().await;
52-
assert_eq!(Ok(Some(beacon.epoch)), current_epoch);
51+
let current_epoch = fake_observer.get_current_epoch().await.unwrap();
52+
53+
assert_eq!(Some(beacon.epoch), current_epoch);
5354
}
5455

5556
#[tokio::test]
5657
async fn test_get_current_stake_distribution() {
5758
let fake_observer = FakeObserver::new();
5859
let stake_distribution = fake_observer.get_current_stake_distribution().await;
60+
5961
assert!(
6062
stake_distribution.unwrap().unwrap().len() > 0,
6163
"get current stake distribution should not fail and should not be empty"

mithril-common/src/chain_observer/interface.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
use crate::entities::*;
22
use async_trait::async_trait;
33
use mockall::automock;
4+
use std::error::Error as StdError;
45
use thiserror::Error;
56

6-
#[derive(Debug, Error, PartialEq)]
7-
pub enum ChainObserverError {}
7+
#[derive(Debug, Error)]
8+
pub enum ChainObserverError {
9+
#[error("general error {0}")]
10+
General(Box<dyn StdError + Sync + Send>),
11+
}
812

913
#[automock]
1014
#[async_trait]

mithril-common/src/chain_observer/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod cli_observer;
12
mod fake_observer;
23
mod interface;
34

0 commit comments

Comments
 (0)