|
1 | 1 | use async_trait::async_trait;
|
2 | 2 | use nom::IResult;
|
| 3 | +use rand_core::RngCore; |
3 | 4 | use serde_json::Value;
|
| 5 | +use std::collections::HashMap; |
4 | 6 | use std::error::Error;
|
5 | 7 | use std::fs;
|
6 | 8 | use std::path::PathBuf;
|
7 | 9 | use tokio::process::Command;
|
8 | 10 |
|
9 | 11 | use crate::chain_observer::interface::*;
|
| 12 | +use crate::chain_observer::{ChainAddress, TxDatum}; |
10 | 13 | use crate::crypto_helper::{KESPeriod, OpCert, SerDeShelleyFileFormat};
|
11 | 14 | use crate::entities::{Epoch, StakeDistribution};
|
12 | 15 | use crate::CardanoNetwork;
|
13 | 16 |
|
14 | 17 | #[async_trait]
|
15 | 18 | pub trait CliRunner {
|
| 19 | + async fn launch_utxo(&self, address: &str) -> Result<String, Box<dyn Error + Sync + Send>>; |
16 | 20 | async fn launch_stake_distribution(&self) -> Result<String, Box<dyn Error + Sync + Send>>;
|
17 | 21 | async fn launch_stake_snapshot(
|
18 | 22 | &self,
|
@@ -43,6 +47,29 @@ impl CardanoCliRunner {
|
43 | 47 | }
|
44 | 48 | }
|
45 | 49 |
|
| 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 | + |
46 | 73 | fn command_for_stake_distribution(&self) -> Command {
|
47 | 74 | let mut command = self.get_command();
|
48 | 75 | command.arg("query").arg("stake-distribution");
|
@@ -110,6 +137,27 @@ impl CardanoCliRunner {
|
110 | 137 |
|
111 | 138 | #[async_trait]
|
112 | 139 | 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 | + |
113 | 161 | async fn launch_stake_distribution(&self) -> Result<String, Box<dyn Error + Sync + Send>> {
|
114 | 162 | let output = self.command_for_stake_distribution().output().await?;
|
115 | 163 |
|
@@ -255,6 +303,30 @@ impl ChainObserver for CardanoCliChainObserver {
|
255 | 303 | }
|
256 | 304 | }
|
257 | 305 |
|
| 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 | + |
258 | 330 | async fn get_current_stake_distribution(
|
259 | 331 | &self,
|
260 | 332 | ) -> Result<Option<StakeDistribution>, ChainObserverError> {
|
@@ -337,6 +409,45 @@ mod tests {
|
337 | 409 |
|
338 | 410 | #[async_trait]
|
339 | 411 | 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 | + |
340 | 451 | async fn launch_stake_distribution(&self) -> Result<String, Box<dyn Error + Sync + Send>> {
|
341 | 452 | let output = r#"
|
342 | 453 | PoolId Stake frac
|
@@ -471,6 +582,14 @@ pool1qz2vzszautc2c8mljnqre2857dpmheq7kgt6vav0s38tvvhxm6w 1.051e-6
|
471 | 582 | );
|
472 | 583 | }
|
473 | 584 |
|
| 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 | + |
474 | 593 | #[tokio::test]
|
475 | 594 | async fn test_get_current_stake_value() {
|
476 | 595 | let observer = CardanoCliChainObserver::new(Box::new(TestCliRunner {}));
|
|
0 commit comments