diff --git a/Cargo.lock b/Cargo.lock index e0cf3a0f455..8ccdb3413c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1118,6 +1118,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "cryptoxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "382ce8820a5bb815055d3553a610e8cb542b2d767bbacea99038afda96cd760d" + [[package]] name = "csv" version = "1.3.0" @@ -3141,6 +3147,27 @@ dependencies = [ "unicase", ] +[[package]] +name = "minicbor" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7005aaf257a59ff4de471a9d5538ec868a21586534fff7f85dd97d4043a6139" +dependencies = [ + "half 1.8.2", + "minicbor-derive", +] + +[[package]] +name = "minicbor-derive" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1154809406efdb7982841adb6311b3d095b46f78342dd646736122fe6b19e267" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -3169,7 +3196,7 @@ dependencies = [ [[package]] name = "mithril-aggregator" -version = "0.4.12" +version = "0.4.13" dependencies = [ "anyhow", "async-trait", @@ -3279,7 +3306,7 @@ dependencies = [ [[package]] name = "mithril-common" -version = "0.2.138" +version = "0.2.139" dependencies = [ "anyhow", "async-trait", @@ -3298,6 +3325,8 @@ dependencies = [ "mithril-stm", "mockall", "nom", + "pallas-codec", + "pallas-network", "rand_chacha", "rand_core 0.6.4", "rayon", @@ -3325,7 +3354,7 @@ dependencies = [ [[package]] name = "mithril-end-to-end" -version = "0.2.23" +version = "0.2.24" dependencies = [ "anyhow", "async-recursion", @@ -3373,7 +3402,7 @@ dependencies = [ [[package]] name = "mithril-signer" -version = "0.2.91" +version = "0.2.92" dependencies = [ "anyhow", "async-trait", @@ -3896,6 +3925,48 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "pallas-codec" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443cc4e8d59a8845282a8d35c4021a434a1911d3bc82914c3a8fcda8247d1ae" +dependencies = [ + "hex", + "minicbor", + "serde", + "thiserror", +] + +[[package]] +name = "pallas-crypto" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1116c3da7bc5ae8aa3a87a3ccc42a46d07994458d084c7d3c1dc732320bfc0" +dependencies = [ + "cryptoxide", + "hex", + "pallas-codec", + "rand_core 0.6.4", + "serde", + "thiserror", +] + +[[package]] +name = "pallas-network" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d5fb0899c37f74b2b3366d44eef85d44aea2b1cd8e9548263977baa252d9f4" +dependencies = [ + "byteorder", + "hex", + "itertools", + "pallas-codec", + "pallas-crypto", + "thiserror", + "tokio", + "tracing", +] + [[package]] name = "parking" version = "2.2.0" diff --git a/mithril-aggregator/Cargo.toml b/mithril-aggregator/Cargo.toml index 977483268da..710bb5ff49e 100644 --- a/mithril-aggregator/Cargo.toml +++ b/mithril-aggregator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-aggregator" -version = "0.4.12" +version = "0.4.13" description = "A Mithril Aggregator server" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-aggregator/src/dependency_injection/builder.rs b/mithril-aggregator/src/dependency_injection/builder.rs index b8c5f21b702..7952755fbf1 100644 --- a/mithril-aggregator/src/dependency_injection/builder.rs +++ b/mithril-aggregator/src/dependency_injection/builder.rs @@ -4,7 +4,9 @@ use anyhow::Context; use mithril_common::{ api_version::APIVersionProvider, certificate_chain::{CertificateVerifier, MithrilCertificateVerifier}, - chain_observer::{CardanoCliChainObserver, CardanoCliRunner, ChainObserver, FakeObserver}, + chain_observer::{ + CardanoCliChainObserver, CardanoCliRunner, ChainObserver, FakeObserver, PallasChainObserver, + }, crypto_helper::{ ProtocolGenesisSigner, ProtocolGenesisVerificationKey, ProtocolGenesisVerifier, }, @@ -463,9 +465,17 @@ impl DependenciesBuilder { async fn build_chain_observer(&mut self) -> Result> { let chain_observer: Arc = match self.configuration.environment { - ExecutionEnvironment::Production => Arc::new(CardanoCliChainObserver::new( - self.get_cardano_cli_runner().await?, - )), + ExecutionEnvironment::Production => { + let fallback = CardanoCliChainObserver::new(self.get_cardano_cli_runner().await?); + let observer = PallasChainObserver::new( + &self.configuration.cardano_node_socket_path, + self.configuration.get_network().with_context(|| { + "Dependencies Builder can not get Cardano network while building cardano cli runner" + })?, + fallback, + ); + Arc::new(observer) + } _ => Arc::new(FakeObserver::default()), }; diff --git a/mithril-common/Cargo.toml b/mithril-common/Cargo.toml index aab12a0a458..0121f112f03 100644 --- a/mithril-common/Cargo.toml +++ b/mithril-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-common" -version = "0.2.138" +version = "0.2.139" description = "Common types, interfaces, and utilities for Mithril nodes." authors = { workspace = true } edition = { workspace = true } @@ -34,6 +34,7 @@ kes-summed-ed25519 = { version = "0.2.1", features = [ "sk_clone_enabled", ] } nom = "7.1.3" +pallas-network = "0.20.0" rand_chacha = "0.3.1" rand_core = { version = "0.6.4", features = ["getrandom"] } rayon = "1.8.0" @@ -73,6 +74,7 @@ mithril-stm = { path = "../mithril-stm", version = "0.3", default-features = fal [dev-dependencies] criterion = { version = "0.5.1", features = ["html_reports", "async_tokio"] } mockall = "0.11.4" +pallas-codec = "0.20.0" reqwest = { version = "0.11.22", features = ["json"] } slog-async = "2.8.0" slog-scope = "4.4.0" diff --git a/mithril-common/src/chain_observer/cli_observer.rs b/mithril-common/src/chain_observer/cli_observer.rs index 0bbba0f585b..5e6d367ed09 100644 --- a/mithril-common/src/chain_observer/cli_observer.rs +++ b/mithril-common/src/chain_observer/cli_observer.rs @@ -10,19 +10,27 @@ use std::fs; use std::path::PathBuf; use tokio::process::Command; -use crate::chain_observer::interface::*; +use crate::chain_observer::interface::{ChainObserver, ChainObserverError}; use crate::chain_observer::{ChainAddress, TxDatum}; use crate::crypto_helper::{KESPeriod, OpCert, SerDeShelleyFileFormat}; use crate::entities::{Epoch, StakeDistribution}; use crate::{CardanoNetwork, StdResult}; +/// `CliRunner` trait defines the asynchronous methods +/// for interaction with the Cardano CLI. #[async_trait] pub trait CliRunner { + /// Launches a UTxO. async fn launch_utxo(&self, address: &str) -> StdResult; + /// Launches the stake distribution. async fn launch_stake_distribution(&self) -> StdResult; + /// Launches the stake snapshot. async fn launch_stake_snapshot(&self, stake_pool_id: &str) -> StdResult; + /// Launches the stake snapshot for all pools. async fn launch_stake_snapshot_all_pools(&self) -> StdResult; + /// Launches the epoch info. async fn launch_epoch(&self) -> StdResult; + /// Launches the kes period. async fn launch_kes_period(&self, opcert_file: &str) -> StdResult; } @@ -469,187 +477,10 @@ mod tests { use std::collections::BTreeMap; use super::*; - use crate::crypto_helper::ColdKeyGenerator; + use crate::{chain_observer::test_cli_runner::TestCliRunner, crypto_helper::ColdKeyGenerator}; use kes_summed_ed25519::{kes::Sum6Kes, traits::KesSk}; - struct TestCliRunner { - is_legacy: bool, - } - - impl TestCliRunner { - fn new(is_legacy: bool) -> Self { - Self { is_legacy } - } - - fn legacy() -> Self { - Self::new(true) - } - } - - impl Default for TestCliRunner { - fn default() -> Self { - Self::new(false) - } - } - - #[async_trait] - impl CliRunner for TestCliRunner { - async fn launch_utxo(&self, _address: &str) -> StdResult { - 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) -> StdResult { - let output = r#" - PoolId Stake frac ------------------------------------------------------------------------------- -pool1qqyjr9pcrv97gwrueunug829fs5znw6p2wxft3fvqkgu5f4qlrg 2.493e-3 -pool1qqfnw2fwajdnam7xsqhhrje5cgd8jcltzfrx655rd23eqlxjfef 2.164e-5 -pool1qqnjh80kudcjphrxftj74x22q3a4uvw8wknlxptgs7gdqtstqad 8.068e-7 -pool1qquwwu6680fr72y4779r2kpc7mxtch8rp2uhuqcc7v9p6q4f7ph 7.073e-7 -pool1qpqvz90w7qsex2al2ejjej0rfgrwsguch307w8fraw7a7adf6g8 2.474e-11 -pool1qptl80vq84xm28pt3t2lhpfzqag28csjhktxz5k6a74n260clmt 5.600e-7 -pool1qpuckgzxwgdru9vvq3ydmuqa077ur783yn2uywz7zq2c29p506e 5.161e-5 -pool1qz2vzszautc2c8mljnqre2857dpmheq7kgt6vav0s38tvvhxm6w 1.051e-6 -"#; - - Ok(output.to_string()) - } - - async fn launch_stake_snapshot(&self, stake_pool_id: &str) -> StdResult { - let mut output = r#" -{ - "poolStakeGo": 1000000, - "poolStakeSet": 2000000, - "poolStakeMark": 3000000, - "activeStakeGo": 5000000, - "activeStakeSet": 7000000, - "activeStakeMark": 9000000 -} -"#; - if stake_pool_id == "pool1qpqvz90w7qsex2al2ejjej0rfgrwsguch307w8fraw7a7adf6g8" { - output = r#" - { - "poolStakeGo": 0, - "poolStakeSet": 0, - "poolStakeMark": 0, - "activeStakeGo": 5000000, - "activeStakeSet": 7000000, - "activeStakeMark": 9000000 - } - "# - } - - Ok(output.to_string()) - } - - async fn launch_stake_snapshot_all_pools(&self) -> StdResult { - let output = r#" -{ - "pools": { - "00000036d515e12e18cd3c88c74f09a67984c2c279a5296aa96efe89": { - "stakeGo": 300000000000, - "stakeMark": 300000000001, - "stakeSet": 300000000002 - }, - "000000f66e28b0f18aef20555f4c4954234e3270dfbbdcc13f54e799": { - "stakeGo": 600000000000, - "stakeMark": 600000000001, - "stakeSet": 600000000002 - }, - "00000110093effbf3ce788aebd3e7506b80322bd3995ad432e61fad5": { - "stakeGo": 1200000000000, - "stakeMark": 1200000000001, - "stakeSet": 1200000000002 - }, - "00000ffff93effbf3ce788aebd3e7506b80322bd3995ad432e61fad5": { - "stakeGo": 0, - "stakeMark": 0, - "stakeSet": 1300000000002 - } - }, - "total": { - "stakeGo": 2100000000000, - "stakeMark": 2100000000003, - "stakeSet": 2100000000006 - } -} - "#; - if self.is_legacy { - Err(anyhow!( - "launch_stake_snapshot_all_pools is not implemented in legacy cli runner" - )) - } else { - Ok(output.to_string()) - } - } - - async fn launch_epoch(&self) -> StdResult { - let output = r#" -{ - "era": "Alonzo", - "syncProgress": "100.00", - "hash": "f6d1b8c328697c7a4a8e7f718c79510acbcd411ff4ca19401ded13534d45a38d", - "epoch": 120, - "slot": 0, - "block": 0 -}"#; - - Ok(output.to_string()) - } - - async fn launch_kes_period(&self, _opcert_file: &str) -> StdResult { - let output = r#" -✓ The operational certificate counter agrees with the node protocol state counter -✓ Operational certificate's kes period is within the correct KES period interval -{ - "qKesNodeStateOperationalCertificateNumber": 6, - "qKesCurrentKesPeriod": 404, - "qKesOnDiskOperationalCertificateNumber": 6, - "qKesRemainingSlotsInKesPeriod": 3760228, - "qKesMaxKESEvolutions": 62, - "qKesKesKeyExpiry": "2022-03-20T21:44:51Z", - "qKesEndKesInterval": 434, - "qKesStartKesInterval": 372, - "qKesSlotsPerKesPeriod": 129600 -}"#; - - Ok(output.to_string()) - } - } - #[tokio::test] async fn test_get_current_epoch() { let observer = CardanoCliChainObserver::new(Box::::default()); diff --git a/mithril-common/src/chain_observer/mod.rs b/mithril-common/src/chain_observer/mod.rs index be68b8cc2a4..71193bb448c 100644 --- a/mithril-common/src/chain_observer/mod.rs +++ b/mithril-common/src/chain_observer/mod.rs @@ -4,6 +4,13 @@ mod cli_observer; mod fake_observer; mod interface; mod model; +mod pallas_observer; + +#[cfg(test)] +mod test_cli_runner; + +#[cfg(test)] +pub use cli_observer::CliRunner; pub use cli_observer::{CardanoCliChainObserver, CardanoCliRunner}; pub use fake_observer::FakeObserver; @@ -13,3 +20,4 @@ pub use interface::{ChainObserver, ChainObserverError}; pub use model::{ ChainAddress, TxDatum, TxDatumBuilder, TxDatumError, TxDatumFieldTypeName, TxDatumFieldValue, }; +pub use pallas_observer::PallasChainObserver; diff --git a/mithril-common/src/chain_observer/pallas_observer.rs b/mithril-common/src/chain_observer/pallas_observer.rs new file mode 100644 index 00000000000..fafdaf53977 --- /dev/null +++ b/mithril-common/src/chain_observer/pallas_observer.rs @@ -0,0 +1,247 @@ +use anyhow::{anyhow, Context}; +use async_trait::async_trait; +use pallas_network::facades::NodeClient; +use pallas_network::miniprotocols::localstate::{queries_v16, Client}; +use std::path::{Path, PathBuf}; + +use crate::chain_observer::interface::*; +use crate::chain_observer::{ChainAddress, TxDatum}; +use crate::crypto_helper::{KESPeriod, OpCert}; +use crate::entities::StakeDistribution; +use crate::CardanoNetwork; +use crate::{entities::Epoch, StdResult}; + +use super::CardanoCliChainObserver; + +/// A runner that uses Pallas library to interact with a Cardano node using N2C Ouroboros mini-protocols +pub struct PallasChainObserver { + socket: PathBuf, + network: CardanoNetwork, + fallback: super::cli_observer::CardanoCliChainObserver, +} + +impl From for ChainObserverError { + fn from(err: anyhow::Error) -> Self { + ChainObserverError::General(err) + } +} + +impl PallasChainObserver { + /// Creates a new PallasObserver while accepting a fallback CliRunner + pub fn new(socket: &Path, network: CardanoNetwork, fallback: CardanoCliChainObserver) -> Self { + Self { + socket: socket.to_owned(), + network, + fallback, + } + } + + /// Creates and returns a new `NodeClient` connected to the specified socket. + async fn new_client(&self) -> StdResult { + let magic = self.network.code(); + let client = NodeClient::connect(&self.socket, magic).await?; + + Ok(client) + } + + /// Returns a reference to the fallback `CardanoCliChainObserver` instance. + fn get_fallback(&self) -> &CardanoCliChainObserver { + &self.fallback + } + + /// Creates and returns a new `NodeClient`, handling any potential errors. + async fn get_client(&self) -> StdResult { + self.new_client() + .await + .map_err(|err| anyhow!(err)) + .with_context(|| "PallasChainObserver Failed to create new client") + } + + /// Fetches the current epoch number using the provided `statequery` client. + async fn get_epoch(&self, statequery: &mut Client) -> StdResult { + statequery + .acquire(None) + .await + .map_err(|err| anyhow!(err)) + .with_context(|| "PallasChainObserver Failed to acquire statequery")?; + + let era = queries_v16::get_current_era(statequery) + .await + .map_err(|err| anyhow!(err)) + .with_context(|| "PallasChainObserver Failed to get current era")?; + + let epoch = queries_v16::get_block_epoch_number(statequery, era) + .await + .map_err(|err| anyhow!(err)) + .with_context(|| "PallasChainObserver Failed to get block epoch number")?; + + Ok(epoch) + } + + /// Processes a state query with the `NodeClient`, releasing the state query. + async fn process_statequery(&self, client: &mut NodeClient) -> StdResult<()> { + let statequery = client.statequery(); + statequery + .send_release() + .await + .map_err(|err| anyhow!(err)) + .with_context(|| "PallasChainObserver send release failed")?; + + statequery + .send_done() + .await + .map_err(|err| anyhow!(err)) + .with_context(|| "PallasChainObserver send done failed")?; + + Ok(()) + } + + /// Synchronizes the `NodeClient` with the cardano server using `chainsync`. + async fn sync(&self, client: &mut NodeClient) -> StdResult<()> { + client + .chainsync() + .send_done() + .await + .map_err(|err| anyhow!(err)) + .with_context(|| "PallasChainObserver chainsync send done failed")?; + Ok(()) + } + + /// Post-processes a state query afterwards. + async fn post_process_statequery(&self, client: &mut NodeClient) -> StdResult<()> { + self.process_statequery(client).await?; + self.sync(client).await?; + + Ok(()) + } +} + +#[async_trait] +impl ChainObserver for PallasChainObserver { + async fn get_current_epoch(&self) -> Result, ChainObserverError> { + let mut client = self.get_client().await?; + + let epoch = self.get_epoch(client.statequery()).await?; + + self.post_process_statequery(&mut client).await?; + + drop(client.plexer_handle); + + Ok(Some(Epoch(epoch as u64))) + } + + async fn get_current_datums( + &self, + address: &ChainAddress, + ) -> Result, ChainObserverError> { + let fallback = self.get_fallback(); + fallback.get_current_datums(address).await + } + + async fn get_current_stake_distribution( + &self, + ) -> Result, ChainObserverError> { + let fallback = self.get_fallback(); + fallback.get_current_stake_distribution().await + } + + async fn get_current_kes_period( + &self, + opcert: &OpCert, + ) -> Result, ChainObserverError> { + let fallback = self.get_fallback(); + fallback.get_current_kes_period(opcert).await + } +} + +#[cfg(test)] +mod tests { + use std::fs; + + use pallas_codec::utils::AnyCbor; + use pallas_network::miniprotocols::localstate::{self, ClientQueryRequest}; + use tokio::net::UnixListener; + + use super::*; + use crate::{chain_observer::test_cli_runner::TestCliRunner, CardanoNetwork}; + + /// pallas responses mock server. + async fn mock_server(server: &mut pallas_network::facades::NodeServer) -> AnyCbor { + let query: localstate::queries_v16::Request = + match server.statequery().recv_while_acquired().await.unwrap() { + ClientQueryRequest::Query(q) => q.into_decode().unwrap(), + x => panic!("unexpected message from client: {x:?}"), + }; + + match query { + localstate::queries_v16::Request::LedgerQuery( + localstate::queries_v16::LedgerQuery::HardForkQuery( + localstate::queries_v16::HardForkQuery::GetCurrentEra, + ), + ) => AnyCbor::from_encode(4), + localstate::queries_v16::Request::LedgerQuery( + localstate::queries_v16::LedgerQuery::BlockQuery( + _, + localstate::queries_v16::BlockQuery::GetEpochNo, + ), + ) => AnyCbor::from_encode([8]), + _ => panic!("unexpected query from client: {query:?}"), + } + } + + /// Creates a new work directory in the system's temporary folder. + fn create_temp_dir(folder_name: &str) -> PathBuf { + let temp_dir = std::env::temp_dir().join(folder_name); + if temp_dir.exists() { + fs::remove_dir_all(&temp_dir).expect("Previous work dir removal failed"); + } + fs::create_dir_all(&temp_dir).expect("Work dir creation failed"); + temp_dir + } + + /// Sets up a mock server. + async fn setup_server() -> tokio::task::JoinHandle<()> { + tokio::spawn({ + async move { + let temp_dir = create_temp_dir("pallas_chain_observer_test"); + let socket_path = temp_dir.join("node.socket").as_path().to_owned(); + if socket_path.exists() { + fs::remove_file(&socket_path).expect("Previous socket removal failed"); + } + + let unix_listener = UnixListener::bind(socket_path.as_path()).unwrap(); + let mut server = pallas_network::facades::NodeServer::accept(&unix_listener, 10) + .await + .unwrap(); + + server.statequery().recv_while_idle().await.unwrap(); + server.statequery().send_acquired().await.unwrap(); + + let result = mock_server(&mut server).await; + server.statequery().send_result(result).await.unwrap(); + + let result = mock_server(&mut server).await; + server.statequery().send_result(result).await.unwrap(); + } + }) + } + + #[tokio::test] + async fn get_current_epoch_with_fallback() { + let server = setup_server().await; + let client = tokio::spawn(async move { + let socket_path = std::env::temp_dir().join("pallas_chain_observer_test/node.socket"); + let fallback = CardanoCliChainObserver::new(Box::::default()); + let observer = super::PallasChainObserver::new( + socket_path.as_path(), + CardanoNetwork::TestNet(10), + fallback, + ); + observer.get_current_epoch().await.unwrap().unwrap() + }); + + let (_, client_res) = tokio::join!(server, client); + let epoch = client_res.expect("Client failed"); + assert_eq!(epoch, 8); + } +} diff --git a/mithril-common/src/chain_observer/test_cli_runner.rs b/mithril-common/src/chain_observer/test_cli_runner.rs new file mode 100644 index 00000000000..639922a33e1 --- /dev/null +++ b/mithril-common/src/chain_observer/test_cli_runner.rs @@ -0,0 +1,191 @@ +//! test cli runner + +use anyhow::anyhow; +use async_trait::async_trait; + +use crate::{chain_observer::CliRunner, StdResult}; + +/// `TestCliRunner` is a struct to run Cardano CLI tests +pub(crate) struct TestCliRunner { + is_legacy: bool, +} + +impl TestCliRunner { + fn new(is_legacy: bool) -> Self { + Self { is_legacy } + } + + /// Creates a new `TestCliRunner` instance in legacy mode. + pub fn legacy() -> Self { + Self::new(true) + } +} + +impl Default for TestCliRunner { + fn default() -> Self { + Self::new(false) + } +} + +#[async_trait] +impl CliRunner for TestCliRunner { + /// launches a UTxO. + async fn launch_utxo(&self, _address: &str) -> StdResult { + 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()) + } + + /// launches the stake distribution. + async fn launch_stake_distribution(&self) -> StdResult { + let output = r#" + PoolId Stake frac +------------------------------------------------------------------------------ +pool1qqyjr9pcrv97gwrueunug829fs5znw6p2wxft3fvqkgu5f4qlrg 2.493e-3 +pool1qqfnw2fwajdnam7xsqhhrje5cgd8jcltzfrx655rd23eqlxjfef 2.164e-5 +pool1qqnjh80kudcjphrxftj74x22q3a4uvw8wknlxptgs7gdqtstqad 8.068e-7 +pool1qquwwu6680fr72y4779r2kpc7mxtch8rp2uhuqcc7v9p6q4f7ph 7.073e-7 +pool1qpqvz90w7qsex2al2ejjej0rfgrwsguch307w8fraw7a7adf6g8 2.474e-11 +pool1qptl80vq84xm28pt3t2lhpfzqag28csjhktxz5k6a74n260clmt 5.600e-7 +pool1qpuckgzxwgdru9vvq3ydmuqa077ur783yn2uywz7zq2c29p506e 5.161e-5 +pool1qz2vzszautc2c8mljnqre2857dpmheq7kgt6vav0s38tvvhxm6w 1.051e-6 +"#; + + Ok(output.to_string()) + } + + /// launches the stake snapshot. + async fn launch_stake_snapshot(&self, stake_pool_id: &str) -> StdResult { + let mut output = r#" +{ + "poolStakeGo": 1000000, + "poolStakeSet": 2000000, + "poolStakeMark": 3000000, + "activeStakeGo": 5000000, + "activeStakeSet": 7000000, + "activeStakeMark": 9000000 +} +"#; + if stake_pool_id == "pool1qpqvz90w7qsex2al2ejjej0rfgrwsguch307w8fraw7a7adf6g8" { + output = r#" + { + "poolStakeGo": 0, + "poolStakeSet": 0, + "poolStakeMark": 0, + "activeStakeGo": 5000000, + "activeStakeSet": 7000000, + "activeStakeMark": 9000000 + } + "# + } + + Ok(output.to_string()) + } + + /// launches the stake snapshot for all pools. + async fn launch_stake_snapshot_all_pools(&self) -> StdResult { + let output = r#" +{ + "pools": { + "00000036d515e12e18cd3c88c74f09a67984c2c279a5296aa96efe89": { + "stakeGo": 300000000000, + "stakeMark": 300000000001, + "stakeSet": 300000000002 + }, + "000000f66e28b0f18aef20555f4c4954234e3270dfbbdcc13f54e799": { + "stakeGo": 600000000000, + "stakeMark": 600000000001, + "stakeSet": 600000000002 + }, + "00000110093effbf3ce788aebd3e7506b80322bd3995ad432e61fad5": { + "stakeGo": 1200000000000, + "stakeMark": 1200000000001, + "stakeSet": 1200000000002 + }, + "00000ffff93effbf3ce788aebd3e7506b80322bd3995ad432e61fad5": { + "stakeGo": 0, + "stakeMark": 0, + "stakeSet": 1300000000002 + } + }, + "total": { + "stakeGo": 2100000000000, + "stakeMark": 2100000000003, + "stakeSet": 2100000000006 + } +} + "#; + if self.is_legacy { + Err(anyhow!( + "launch_stake_snapshot_all_pools is not implemented in legacy cli runner" + )) + } else { + Ok(output.to_string()) + } + } + + /// launches the epoch info. + async fn launch_epoch(&self) -> StdResult { + let output = r#" +{ + "era": "Alonzo", + "syncProgress": "100.00", + "hash": "f6d1b8c328697c7a4a8e7f718c79510acbcd411ff4ca19401ded13534d45a38d", + "epoch": 120, + "slot": 0, + "block": 0 +}"#; + + Ok(output.to_string()) + } + + /// launches the kes period. + async fn launch_kes_period(&self, _opcert_file: &str) -> StdResult { + let output = r#" +✓ The operational certificate counter agrees with the node protocol state counter +✓ Operational certificate's kes period is within the correct KES period interval +{ + "qKesNodeStateOperationalCertificateNumber": 6, + "qKesCurrentKesPeriod": 404, + "qKesOnDiskOperationalCertificateNumber": 6, + "qKesRemainingSlotsInKesPeriod": 3760228, + "qKesMaxKESEvolutions": 62, + "qKesKesKeyExpiry": "2022-03-20T21:44:51Z", + "qKesEndKesInterval": 434, + "qKesStartKesInterval": 372, + "qKesSlotsPerKesPeriod": 129600 +}"#; + + Ok(output.to_string()) + } +} diff --git a/mithril-common/src/entities/cardano_network.rs b/mithril-common/src/entities/cardano_network.rs index efafd50f3d4..4707dc9ca70 100644 --- a/mithril-common/src/entities/cardano_network.rs +++ b/mithril-common/src/entities/cardano_network.rs @@ -4,6 +4,7 @@ use thiserror::Error; use crate::MagicId; +const MAINNET_MAGIC_ID: MagicId = 764824073; const TESTNET_MAGIC_ID: MagicId = 1097911063; const PREPROD_MAGIC_ID: MagicId = 1; const PREVIEW_MAGIC_ID: MagicId = 2; @@ -62,6 +63,15 @@ impl CardanoNetwork { ))), } } + + /// Returns the code (magic) of the network + pub fn code(&self) -> MagicId { + match *self { + CardanoNetwork::MainNet => MAINNET_MAGIC_ID, + CardanoNetwork::DevNet(magic_id) => magic_id, + CardanoNetwork::TestNet(magic_id) => magic_id, + } + } } impl Display for CardanoNetwork { diff --git a/mithril-common/src/test_utils/mod.rs b/mithril-common/src/test_utils/mod.rs index 99ff1555eb6..19718da6429 100644 --- a/mithril-common/src/test_utils/mod.rs +++ b/mithril-common/src/test_utils/mod.rs @@ -8,10 +8,13 @@ #[cfg(feature = "apispec")] pub mod apispec; + pub mod fake_data; pub mod fake_keys; + mod fixture_builder; mod mithril_fixture; + #[cfg(feature = "test_http_server")] pub mod test_http_server; diff --git a/mithril-signer/Cargo.toml b/mithril-signer/Cargo.toml index 481519acb84..dacb44b255c 100644 --- a/mithril-signer/Cargo.toml +++ b/mithril-signer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-signer" -version = "0.2.91" +version = "0.2.92" description = "A Mithril Signer" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-signer/src/runtime/signer_services.rs b/mithril-signer/src/runtime/signer_services.rs index bef42a70ab8..555e3c9a707 100644 --- a/mithril-signer/src/runtime/signer_services.rs +++ b/mithril-signer/src/runtime/signer_services.rs @@ -5,7 +5,9 @@ use std::{fs, sync::Arc}; use mithril_common::{ api_version::APIVersionProvider, - chain_observer::{CardanoCliChainObserver, CardanoCliRunner, ChainObserver}, + chain_observer::{ + CardanoCliChainObserver, CardanoCliRunner, ChainObserver, PallasChainObserver, + }, crypto_helper::{OpCert, ProtocolPartyId, SerDeShelleyFileFormat}, database::{ApplicationNodeType, DatabaseVersionChecker}, digesters::{ @@ -56,14 +58,21 @@ impl<'a> ProductionServiceBuilder<'a> { pub fn new(config: &'a Configuration) -> Self { let chain_observer_builder: fn(&Configuration) -> StdResult = |config: &Configuration| { - Ok(Arc::new(CardanoCliChainObserver::new(Box::new( - CardanoCliRunner::new( - config.cardano_cli_path.clone(), - config.cardano_node_socket_path.clone(), - config.get_network()?, - ), - )))) + let fallback = CardanoCliChainObserver::new(Box::new(CardanoCliRunner::new( + config.cardano_cli_path.clone(), + config.cardano_node_socket_path.clone(), + config.get_network()?, + ))); + + let observer = PallasChainObserver::new( + &config.cardano_node_socket_path, + config.get_network()?, + fallback, + ); + + Ok(Arc::new(observer)) }; + let immutable_file_observer_builder: fn( &Configuration, ) diff --git a/mithril-test-lab/mithril-end-to-end/Cargo.toml b/mithril-test-lab/mithril-end-to-end/Cargo.toml index 597d71258c2..078b6950824 100644 --- a/mithril-test-lab/mithril-end-to-end/Cargo.toml +++ b/mithril-test-lab/mithril-end-to-end/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-end-to-end" -version = "0.2.23" +version = "0.2.24" authors = { workspace = true } edition = { workspace = true } documentation = { workspace = true } diff --git a/mithril-test-lab/mithril-end-to-end/src/assertions/wait.rs b/mithril-test-lab/mithril-end-to-end/src/assertions/wait.rs index c792082fa01..2177940be2b 100644 --- a/mithril-test-lab/mithril-end-to-end/src/assertions/wait.rs +++ b/mithril-test-lab/mithril-end-to-end/src/assertions/wait.rs @@ -1,11 +1,8 @@ use crate::{attempt, utils::AttemptResult}; use anyhow::{anyhow, Context}; use mithril_common::{ - chain_observer::{CardanoCliChainObserver, ChainObserver}, - digesters::ImmutableFile, - entities::Epoch, - messages::EpochSettingsMessage, - StdResult, + chain_observer::ChainObserver, digesters::ImmutableFile, entities::Epoch, + messages::EpochSettingsMessage, StdResult, }; use reqwest::StatusCode; use slog_scope::{info, warn}; @@ -73,7 +70,7 @@ pub async fn wait_for_epoch_settings(aggregator_endpoint: &str) -> StdResult, + chain_observer: Arc, target_epoch: Epoch, wait_reason: String, ) -> StdResult<()> { diff --git a/mithril-test-lab/mithril-end-to-end/src/end_to_end_spec.rs b/mithril-test-lab/mithril-end-to-end/src/end_to_end_spec.rs index ec728824d7c..a1d8d1d2c5c 100644 --- a/mithril-test-lab/mithril-end-to-end/src/end_to_end_spec.rs +++ b/mithril-test-lab/mithril-end-to-end/src/end_to_end_spec.rs @@ -1,6 +1,5 @@ use crate::assertions; use crate::MithrilInfrastructure; -use mithril_common::chain_observer::ChainObserver; use mithril_common::StdResult; pub struct Spec<'a> { diff --git a/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs b/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs index 1e34935baff..36aa8b1d2ed 100644 --- a/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs +++ b/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs @@ -1,6 +1,8 @@ use crate::{Aggregator, Client, Devnet, RelayAggregator, RelaySigner, Signer, DEVNET_MAGIC_ID}; use anyhow::anyhow; -use mithril_common::chain_observer::{CardanoCliChainObserver, CardanoCliRunner}; +use mithril_common::chain_observer::{ + CardanoCliChainObserver, CardanoCliRunner, ChainObserver, PallasChainObserver, +}; use mithril_common::entities::ProtocolParameters; use mithril_common::{CardanoNetwork, StdResult}; use slog_scope::info; @@ -16,7 +18,7 @@ pub struct MithrilInfrastructure { signers: Vec, relay_aggregators: Vec, relay_signers: Vec, - cardano_chain_observer: Arc, + cardano_chain_observer: Arc, run_only_mode: bool, } @@ -104,14 +106,18 @@ impl MithrilInfrastructure { signers.push(signer); } - let cardano_chain_observer = Arc::new(CardanoCliChainObserver::new(Box::new( - CardanoCliRunner::new( - devnet.cardano_cli_path(), - bft_node.socket_path.clone(), - CardanoNetwork::DevNet(DEVNET_MAGIC_ID), - ), + let fallback = CardanoCliChainObserver::new(Box::new(CardanoCliRunner::new( + devnet.cardano_cli_path(), + bft_node.socket_path.clone(), + CardanoNetwork::DevNet(DEVNET_MAGIC_ID), ))); + let cardano_chain_observer = Arc::new(PallasChainObserver::new( + &bft_node.socket_path, + CardanoNetwork::DevNet(DEVNET_MAGIC_ID), + fallback, + )); + Ok(Self { work_dir: work_dir.to_path_buf(), bin_dir: bin_dir.to_path_buf(), @@ -153,7 +159,7 @@ impl MithrilInfrastructure { &self.relay_signers } - pub fn chain_observer(&self) -> Arc { + pub fn chain_observer(&self) -> Arc { self.cardano_chain_observer.clone() } diff --git a/mithril-test-lab/mithril-end-to-end/src/run_only.rs b/mithril-test-lab/mithril-end-to-end/src/run_only.rs index db9b5c8464a..e33e5731177 100644 --- a/mithril-test-lab/mithril-end-to-end/src/run_only.rs +++ b/mithril-test-lab/mithril-end-to-end/src/run_only.rs @@ -1,6 +1,5 @@ use crate::assertions; use crate::MithrilInfrastructure; -use mithril_common::chain_observer::ChainObserver; use mithril_common::StdResult; pub struct RunOnly<'a> {