Skip to content

Commit 7e1a41f

Browse files
authored
Merge pull request #721 from input-output-hk/ensemble/710-era-reader-on-chain
Implement Era Reader on chain adapter
2 parents f013bb7 + 3ec88ac commit 7e1a41f

File tree

14 files changed

+710
-7
lines changed

14 files changed

+710
-7
lines changed

Cargo.lock

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

mithril-common/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mithril-common"
3-
version = "0.2.11"
3+
version = "0.2.12"
44
authors = { workspace = true }
55
edition = { workspace = true }
66
documentation = { workspace = true }

mithril-common/src/beacon_provider.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ impl BeaconProvider for BeaconProviderImpl {
7979

8080
#[cfg(test)]
8181
mod tests {
82-
use crate::chain_observer::{ChainObserver, ChainObserverError};
82+
use crate::chain_observer::{ChainAddress, ChainObserver, ChainObserverError, TxDatum};
8383
use crate::digesters::DumbImmutableFileObserver;
8484
use crate::entities::{Epoch, StakeDistribution};
8585

@@ -89,6 +89,13 @@ mod tests {
8989

9090
#[async_trait]
9191
impl ChainObserver for DumbChainObserver {
92+
async fn get_current_datums(
93+
&self,
94+
_address: &ChainAddress,
95+
) -> Result<Vec<TxDatum>, ChainObserverError> {
96+
Ok(Vec::new())
97+
}
98+
9299
async fn get_current_epoch(&self) -> Result<Option<Epoch>, ChainObserverError> {
93100
Ok(Some(Epoch(42)))
94101
}

mithril-common/src/chain_observer/cli_observer.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
use async_trait::async_trait;
22
use nom::IResult;
3+
use rand_core::RngCore;
34
use serde_json::Value;
5+
use std::collections::HashMap;
46
use std::error::Error;
57
use std::fs;
68
use std::path::PathBuf;
79
use tokio::process::Command;
810

911
use crate::chain_observer::interface::*;
12+
use crate::chain_observer::{ChainAddress, TxDatum};
1013
use crate::crypto_helper::{KESPeriod, OpCert, SerDeShelleyFileFormat};
1114
use crate::entities::{Epoch, StakeDistribution};
1215
use crate::CardanoNetwork;
1316

1417
#[async_trait]
1518
pub trait CliRunner {
19+
async fn launch_utxo(&self, address: &str) -> Result<String, Box<dyn Error + Sync + Send>>;
1620
async fn launch_stake_distribution(&self) -> Result<String, Box<dyn Error + Sync + Send>>;
1721
async fn launch_stake_snapshot(
1822
&self,
@@ -43,6 +47,29 @@ impl CardanoCliRunner {
4347
}
4448
}
4549

50+
fn random_out_file() -> Result<PathBuf, Box<dyn Error + Sync + Send>> {
51+
let mut rng = rand_core::OsRng;
52+
let dir = std::env::temp_dir().join("cardano-cli-runner");
53+
if !dir.exists() {
54+
fs::create_dir_all(&dir)?;
55+
}
56+
Ok(dir.join(format!("{}.out", rng.next_u64())))
57+
}
58+
59+
fn command_for_utxo(&self, address: &str, out_file: PathBuf) -> Command {
60+
let mut command = self.get_command();
61+
command
62+
.arg("query")
63+
.arg("utxo")
64+
.arg("--address")
65+
.arg(address)
66+
.arg("--out-file")
67+
.arg(out_file);
68+
self.post_config_command(&mut command);
69+
70+
command
71+
}
72+
4673
fn command_for_stake_distribution(&self) -> Command {
4774
let mut command = self.get_command();
4875
command.arg("query").arg("stake-distribution");
@@ -110,6 +137,27 @@ impl CardanoCliRunner {
110137

111138
#[async_trait]
112139
impl CliRunner for CardanoCliRunner {
140+
async fn launch_utxo(&self, address: &str) -> Result<String, Box<dyn Error + Sync + Send>> {
141+
let out_file = Self::random_out_file()?;
142+
let output = self
143+
.command_for_utxo(address, out_file.clone())
144+
.output()
145+
.await?;
146+
147+
if output.status.success() {
148+
Ok(fs::read_to_string(out_file)?.trim().to_string())
149+
} else {
150+
let message = String::from_utf8_lossy(&output.stderr);
151+
152+
Err(format!(
153+
"Error launching command {:?}, error = '{}'",
154+
self.command_for_utxo(address, out_file),
155+
message
156+
)
157+
.into())
158+
}
159+
}
160+
113161
async fn launch_stake_distribution(&self) -> Result<String, Box<dyn Error + Sync + Send>> {
114162
let output = self.command_for_stake_distribution().output().await?;
115163

@@ -255,6 +303,30 @@ impl ChainObserver for CardanoCliChainObserver {
255303
}
256304
}
257305

306+
async fn get_current_datums(
307+
&self,
308+
address: &ChainAddress,
309+
) -> Result<Vec<TxDatum>, ChainObserverError> {
310+
let output = self
311+
.cli_runner
312+
.launch_utxo(address)
313+
.await
314+
.map_err(ChainObserverError::General)?;
315+
let v: HashMap<String, Value> = serde_json::from_str(&output).map_err(|e| {
316+
ChainObserverError::InvalidContent(
317+
format!("Error: {e:?}, output was = '{output}'").into(),
318+
)
319+
})?;
320+
321+
Ok(v.values()
322+
.filter_map(|v| {
323+
v.get("inlineDatum")
324+
.filter(|datum| !datum.is_null())
325+
.map(|datum| TxDatum(datum.to_string()))
326+
})
327+
.collect())
328+
}
329+
258330
async fn get_current_stake_distribution(
259331
&self,
260332
) -> Result<Option<StakeDistribution>, ChainObserverError> {
@@ -337,6 +409,45 @@ mod tests {
337409

338410
#[async_trait]
339411
impl CliRunner for TestCliRunner {
412+
async fn launch_utxo(
413+
&self,
414+
_address: &str,
415+
) -> Result<String, Box<dyn Error + Sync + Send>> {
416+
let output = r#"
417+
{
418+
"1fd4d3e131afe3c8b212772a3f3083d2fbc6b2a7b20e54e4ff08e001598818d8#0": {
419+
"address": "addr_test1vpcr3he05gemue6eyy0c9clajqnnww8aa2l3jszjdlszjhq093qrn",
420+
"datum": null,
421+
"inlineDatum": {
422+
"constructor": 0,
423+
"fields": [
424+
{
425+
"bytes": "5b0a20207b0a20202020226e616d65223a20227468616c6573222c0a202020202265706f6368223a203132330a20207d2c0a20207b0a20202020226e616d65223a20227079746861676f726173222c0a202020202265706f6368223a206e756c6c0a20207d0a5d0a"
426+
}
427+
]
428+
},
429+
"inlineDatumhash": "b97cbaa0dc5b41864c83c2f625d9bc2a5f3e6b5cd5071c14a2090e630e188c80",
430+
"referenceScript": null,
431+
"value": {
432+
"lovelace": 10000000
433+
}
434+
},
435+
"1fd4d3e131afe3c8b212772a3f3083d2fbc6b2a7b20e54e4ff08e001598818d8#1": {
436+
"address": "addr_test1vpcr3he05gemue6eyy0c9clajqnnww8aa2l3jszjdlszjhq093qrn",
437+
"datum": null,
438+
"datumhash": null,
439+
"inlineDatum": null,
440+
"referenceScript": null,
441+
"value": {
442+
"lovelace": 9989656678
443+
}
444+
}
445+
}
446+
"#;
447+
448+
Ok(output.to_string())
449+
}
450+
340451
async fn launch_stake_distribution(&self) -> Result<String, Box<dyn Error + Sync + Send>> {
341452
let output = r#"
342453
PoolId Stake frac
@@ -471,6 +582,14 @@ pool1qz2vzszautc2c8mljnqre2857dpmheq7kgt6vav0s38tvvhxm6w 1.051e-6
471582
);
472583
}
473584

585+
#[tokio::test]
586+
async fn test_get_current_datums() {
587+
let observer = CardanoCliChainObserver::new(Box::new(TestCliRunner {}));
588+
let address = "addrtest_123456".to_string();
589+
let datums = observer.get_current_datums(&address).await.unwrap();
590+
assert_eq!(vec![TxDatum("{\"constructor\":0,\"fields\":[{\"bytes\":\"5b0a20207b0a20202020226e616d65223a20227468616c6573222c0a202020202265706f6368223a203132330a20207d2c0a20207b0a20202020226e616d65223a20227079746861676f726173222c0a202020202265706f6368223a206e756c6c0a20207d0a5d0a\"}]}".to_string())], datums);
591+
}
592+
474593
#[tokio::test]
475594
async fn test_get_current_stake_value() {
476595
let observer = CardanoCliChainObserver::new(Box::new(TestCliRunner {}));

mithril-common/src/chain_observer/fake_observer.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use async_trait::async_trait;
22
use tokio::sync::RwLock;
33

44
use crate::chain_observer::interface::*;
5+
use crate::chain_observer::{ChainAddress, TxDatum};
56
use crate::crypto_helper::{KESPeriod, OpCert};
67
use crate::{entities::*, test_utils::fake_data};
78

@@ -16,6 +17,11 @@ pub struct FakeObserver {
1617
///
1718
/// [get_current_epoch]: ChainObserver::get_current_epoch
1819
pub current_beacon: RwLock<Option<Beacon>>,
20+
21+
/// A list of [TxDatum], used by [get_current_datums]
22+
///
23+
/// [get_current_datums]: ChainObserver::get_current_datums
24+
pub datums: RwLock<Vec<TxDatum>>,
1925
}
2026

2127
impl FakeObserver {
@@ -24,6 +30,7 @@ impl FakeObserver {
2430
Self {
2531
signers: RwLock::new(vec![]),
2632
current_beacon: RwLock::new(current_beacon),
33+
datums: RwLock::new(vec![]),
2734
}
2835
}
2936

@@ -44,6 +51,13 @@ impl FakeObserver {
4451
let mut signers = self.signers.write().await;
4552
*signers = new_signers;
4653
}
54+
55+
/// Set the datums that will used to compute the result of
56+
/// [get_current_datums][ChainObserver::get_current_datums].
57+
pub async fn set_datums(&self, new_datums: Vec<TxDatum>) {
58+
let mut datums = self.datums.write().await;
59+
*datums = new_datums;
60+
}
4761
}
4862

4963
impl Default for FakeObserver {
@@ -57,6 +71,14 @@ impl Default for FakeObserver {
5771

5872
#[async_trait]
5973
impl ChainObserver for FakeObserver {
74+
async fn get_current_datums(
75+
&self,
76+
_address: &ChainAddress,
77+
) -> Result<Vec<TxDatum>, ChainObserverError> {
78+
let datums = self.datums.read().await;
79+
Ok(datums.to_vec())
80+
}
81+
6082
async fn get_current_epoch(&self) -> Result<Option<Epoch>, ChainObserverError> {
6183
Ok(self
6284
.current_beacon
@@ -116,4 +138,21 @@ mod tests {
116138
"get current stake distribution should not fail and should not be empty"
117139
);
118140
}
141+
142+
#[tokio::test]
143+
async fn test_get_current_datums() {
144+
let fake_address = "addr_test_123456".to_string();
145+
let fake_datums = vec![
146+
TxDatum("tx_datum_1".to_string()),
147+
TxDatum("tx_datum_2".to_string()),
148+
];
149+
let fake_observer = FakeObserver::new(None);
150+
fake_observer.set_datums(fake_datums.clone()).await;
151+
let datums = fake_observer
152+
.get_current_datums(&fake_address)
153+
.await
154+
.expect("get_current_datums should not fail");
155+
156+
assert_eq!(fake_datums, datums);
157+
}
119158
}

mithril-common/src/chain_observer/interface.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use mockall::automock;
77
use std::error::Error as StdError;
88
use thiserror::Error;
99

10+
use super::{ChainAddress, TxDatum};
11+
1012
/// [ChainObserver] related errors.
1113
#[derive(Debug, Error)]
1214
pub enum ChainObserverError {
@@ -23,6 +25,12 @@ pub enum ChainObserverError {
2325
#[automock]
2426
#[async_trait]
2527
pub trait ChainObserver: Sync + Send {
28+
/// Retrieve the datums associated to and address
29+
async fn get_current_datums(
30+
&self,
31+
address: &ChainAddress,
32+
) -> Result<Vec<TxDatum>, ChainObserverError>;
33+
2634
/// Retrieve the current epoch of the Cardano network
2735
async fn get_current_epoch(&self) -> Result<Option<Epoch>, ChainObserverError>;
2836

mithril-common/src/chain_observer/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ mod cli_observer;
44
#[cfg(any(test, feature = "test_only"))]
55
mod fake_observer;
66
mod interface;
7+
mod model;
78

89
pub use cli_observer::{CardanoCliChainObserver, CardanoCliRunner};
910
#[cfg(any(test, feature = "test_only"))]
1011
pub use fake_observer::FakeObserver;
1112
pub use interface::{ChainObserver, ChainObserverError};
13+
pub use model::{ChainAddress, TxDatum};

0 commit comments

Comments
 (0)