Skip to content

Commit

Permalink
Merge pull request #721 from input-output-hk/ensemble/710-era-reader-…
Browse files Browse the repository at this point in the history
…on-chain

Implement Era Reader on chain adapter
  • Loading branch information
jpraynaud committed Feb 8, 2023
2 parents f013bb7 + 3ec88ac commit 7e1a41f
Show file tree
Hide file tree
Showing 14 changed files with 710 additions and 7 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion mithril-common/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "mithril-common"
version = "0.2.11"
version = "0.2.12"
authors = { workspace = true }
edition = { workspace = true }
documentation = { workspace = true }
Expand Down
9 changes: 8 additions & 1 deletion mithril-common/src/beacon_provider.rs
Expand Up @@ -79,7 +79,7 @@ impl BeaconProvider for BeaconProviderImpl {

#[cfg(test)]
mod tests {
use crate::chain_observer::{ChainObserver, ChainObserverError};
use crate::chain_observer::{ChainAddress, ChainObserver, ChainObserverError, TxDatum};
use crate::digesters::DumbImmutableFileObserver;
use crate::entities::{Epoch, StakeDistribution};

Expand All @@ -89,6 +89,13 @@ mod tests {

#[async_trait]
impl ChainObserver for DumbChainObserver {
async fn get_current_datums(
&self,
_address: &ChainAddress,
) -> Result<Vec<TxDatum>, ChainObserverError> {
Ok(Vec::new())
}

async fn get_current_epoch(&self) -> Result<Option<Epoch>, ChainObserverError> {
Ok(Some(Epoch(42)))
}
Expand Down
119 changes: 119 additions & 0 deletions mithril-common/src/chain_observer/cli_observer.rs
@@ -1,18 +1,22 @@
use async_trait::async_trait;
use nom::IResult;
use rand_core::RngCore;
use serde_json::Value;
use std::collections::HashMap;
use std::error::Error;
use std::fs;
use std::path::PathBuf;
use tokio::process::Command;

use crate::chain_observer::interface::*;
use crate::chain_observer::{ChainAddress, TxDatum};
use crate::crypto_helper::{KESPeriod, OpCert, SerDeShelleyFileFormat};
use crate::entities::{Epoch, StakeDistribution};
use crate::CardanoNetwork;

#[async_trait]
pub trait CliRunner {
async fn launch_utxo(&self, address: &str) -> Result<String, Box<dyn Error + Sync + Send>>;
async fn launch_stake_distribution(&self) -> Result<String, Box<dyn Error + Sync + Send>>;
async fn launch_stake_snapshot(
&self,
Expand Down Expand Up @@ -43,6 +47,29 @@ impl CardanoCliRunner {
}
}

fn random_out_file() -> Result<PathBuf, Box<dyn Error + Sync + Send>> {
let mut rng = rand_core::OsRng;
let dir = std::env::temp_dir().join("cardano-cli-runner");
if !dir.exists() {
fs::create_dir_all(&dir)?;
}
Ok(dir.join(format!("{}.out", rng.next_u64())))
}

fn command_for_utxo(&self, address: &str, out_file: PathBuf) -> Command {
let mut command = self.get_command();
command
.arg("query")
.arg("utxo")
.arg("--address")
.arg(address)
.arg("--out-file")
.arg(out_file);
self.post_config_command(&mut command);

command
}

fn command_for_stake_distribution(&self) -> Command {
let mut command = self.get_command();
command.arg("query").arg("stake-distribution");
Expand Down Expand Up @@ -110,6 +137,27 @@ impl CardanoCliRunner {

#[async_trait]
impl CliRunner for CardanoCliRunner {
async fn launch_utxo(&self, address: &str) -> Result<String, Box<dyn Error + Sync + Send>> {
let out_file = Self::random_out_file()?;
let output = self
.command_for_utxo(address, out_file.clone())
.output()
.await?;

if output.status.success() {
Ok(fs::read_to_string(out_file)?.trim().to_string())
} else {
let message = String::from_utf8_lossy(&output.stderr);

Err(format!(
"Error launching command {:?}, error = '{}'",
self.command_for_utxo(address, out_file),
message
)
.into())
}
}

async fn launch_stake_distribution(&self) -> Result<String, Box<dyn Error + Sync + Send>> {
let output = self.command_for_stake_distribution().output().await?;

Expand Down Expand Up @@ -255,6 +303,30 @@ impl ChainObserver for CardanoCliChainObserver {
}
}

async fn get_current_datums(
&self,
address: &ChainAddress,
) -> Result<Vec<TxDatum>, ChainObserverError> {
let output = self
.cli_runner
.launch_utxo(address)
.await
.map_err(ChainObserverError::General)?;
let v: HashMap<String, Value> = serde_json::from_str(&output).map_err(|e| {
ChainObserverError::InvalidContent(
format!("Error: {e:?}, output was = '{output}'").into(),
)
})?;

Ok(v.values()
.filter_map(|v| {
v.get("inlineDatum")
.filter(|datum| !datum.is_null())
.map(|datum| TxDatum(datum.to_string()))
})
.collect())
}

async fn get_current_stake_distribution(
&self,
) -> Result<Option<StakeDistribution>, ChainObserverError> {
Expand Down Expand Up @@ -337,6 +409,45 @@ mod tests {

#[async_trait]
impl CliRunner for TestCliRunner {
async fn launch_utxo(
&self,
_address: &str,
) -> Result<String, Box<dyn Error + Sync + Send>> {
let output = r#"
{
"1fd4d3e131afe3c8b212772a3f3083d2fbc6b2a7b20e54e4ff08e001598818d8#0": {
"address": "addr_test1vpcr3he05gemue6eyy0c9clajqnnww8aa2l3jszjdlszjhq093qrn",
"datum": null,
"inlineDatum": {
"constructor": 0,
"fields": [
{
"bytes": "5b0a20207b0a20202020226e616d65223a20227468616c6573222c0a202020202265706f6368223a203132330a20207d2c0a20207b0a20202020226e616d65223a20227079746861676f726173222c0a202020202265706f6368223a206e756c6c0a20207d0a5d0a"
}
]
},
"inlineDatumhash": "b97cbaa0dc5b41864c83c2f625d9bc2a5f3e6b5cd5071c14a2090e630e188c80",
"referenceScript": null,
"value": {
"lovelace": 10000000
}
},
"1fd4d3e131afe3c8b212772a3f3083d2fbc6b2a7b20e54e4ff08e001598818d8#1": {
"address": "addr_test1vpcr3he05gemue6eyy0c9clajqnnww8aa2l3jszjdlszjhq093qrn",
"datum": null,
"datumhash": null,
"inlineDatum": null,
"referenceScript": null,
"value": {
"lovelace": 9989656678
}
}
}
"#;

Ok(output.to_string())
}

async fn launch_stake_distribution(&self) -> Result<String, Box<dyn Error + Sync + Send>> {
let output = r#"
PoolId Stake frac
Expand Down Expand Up @@ -471,6 +582,14 @@ pool1qz2vzszautc2c8mljnqre2857dpmheq7kgt6vav0s38tvvhxm6w 1.051e-6
);
}

#[tokio::test]
async fn test_get_current_datums() {
let observer = CardanoCliChainObserver::new(Box::new(TestCliRunner {}));
let address = "addrtest_123456".to_string();
let datums = observer.get_current_datums(&address).await.unwrap();
assert_eq!(vec![TxDatum("{\"constructor\":0,\"fields\":[{\"bytes\":\"5b0a20207b0a20202020226e616d65223a20227468616c6573222c0a202020202265706f6368223a203132330a20207d2c0a20207b0a20202020226e616d65223a20227079746861676f726173222c0a202020202265706f6368223a206e756c6c0a20207d0a5d0a\"}]}".to_string())], datums);
}

#[tokio::test]
async fn test_get_current_stake_value() {
let observer = CardanoCliChainObserver::new(Box::new(TestCliRunner {}));
Expand Down
39 changes: 39 additions & 0 deletions mithril-common/src/chain_observer/fake_observer.rs
Expand Up @@ -2,6 +2,7 @@ use async_trait::async_trait;
use tokio::sync::RwLock;

use crate::chain_observer::interface::*;
use crate::chain_observer::{ChainAddress, TxDatum};
use crate::crypto_helper::{KESPeriod, OpCert};
use crate::{entities::*, test_utils::fake_data};

Expand All @@ -16,6 +17,11 @@ pub struct FakeObserver {
///
/// [get_current_epoch]: ChainObserver::get_current_epoch
pub current_beacon: RwLock<Option<Beacon>>,

/// A list of [TxDatum], used by [get_current_datums]
///
/// [get_current_datums]: ChainObserver::get_current_datums
pub datums: RwLock<Vec<TxDatum>>,
}

impl FakeObserver {
Expand All @@ -24,6 +30,7 @@ impl FakeObserver {
Self {
signers: RwLock::new(vec![]),
current_beacon: RwLock::new(current_beacon),
datums: RwLock::new(vec![]),
}
}

Expand All @@ -44,6 +51,13 @@ impl FakeObserver {
let mut signers = self.signers.write().await;
*signers = new_signers;
}

/// Set the datums that will used to compute the result of
/// [get_current_datums][ChainObserver::get_current_datums].
pub async fn set_datums(&self, new_datums: Vec<TxDatum>) {
let mut datums = self.datums.write().await;
*datums = new_datums;
}
}

impl Default for FakeObserver {
Expand All @@ -57,6 +71,14 @@ impl Default for FakeObserver {

#[async_trait]
impl ChainObserver for FakeObserver {
async fn get_current_datums(
&self,
_address: &ChainAddress,
) -> Result<Vec<TxDatum>, ChainObserverError> {
let datums = self.datums.read().await;
Ok(datums.to_vec())
}

async fn get_current_epoch(&self) -> Result<Option<Epoch>, ChainObserverError> {
Ok(self
.current_beacon
Expand Down Expand Up @@ -116,4 +138,21 @@ mod tests {
"get current stake distribution should not fail and should not be empty"
);
}

#[tokio::test]
async fn test_get_current_datums() {
let fake_address = "addr_test_123456".to_string();
let fake_datums = vec![
TxDatum("tx_datum_1".to_string()),
TxDatum("tx_datum_2".to_string()),
];
let fake_observer = FakeObserver::new(None);
fake_observer.set_datums(fake_datums.clone()).await;
let datums = fake_observer
.get_current_datums(&fake_address)
.await
.expect("get_current_datums should not fail");

assert_eq!(fake_datums, datums);
}
}
8 changes: 8 additions & 0 deletions mithril-common/src/chain_observer/interface.rs
Expand Up @@ -7,6 +7,8 @@ use mockall::automock;
use std::error::Error as StdError;
use thiserror::Error;

use super::{ChainAddress, TxDatum};

/// [ChainObserver] related errors.
#[derive(Debug, Error)]
pub enum ChainObserverError {
Expand All @@ -23,6 +25,12 @@ pub enum ChainObserverError {
#[automock]
#[async_trait]
pub trait ChainObserver: Sync + Send {
/// Retrieve the datums associated to and address
async fn get_current_datums(
&self,
address: &ChainAddress,
) -> Result<Vec<TxDatum>, ChainObserverError>;

/// Retrieve the current epoch of the Cardano network
async fn get_current_epoch(&self) -> Result<Option<Epoch>, ChainObserverError>;

Expand Down
2 changes: 2 additions & 0 deletions mithril-common/src/chain_observer/mod.rs
Expand Up @@ -4,8 +4,10 @@ mod cli_observer;
#[cfg(any(test, feature = "test_only"))]
mod fake_observer;
mod interface;
mod model;

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

0 comments on commit 7e1a41f

Please sign in to comment.