From dd8f8906a293c59426681236bbaf2b96718d9435 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Mon, 25 Aug 2025 16:22:59 +0200 Subject: [PATCH 1/8] refactor(cardano-node-chain): update 'get_current_kes_period' signature in 'ChainObserver' trait Remove the obsolete input for an Operational Certificate. --- .../src/chain_observer/interface.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/internal/cardano-node/mithril-cardano-node-chain/src/chain_observer/interface.rs b/internal/cardano-node/mithril-cardano-node-chain/src/chain_observer/interface.rs index 4f1aa1f8a7d..7e5dc6225ae 100644 --- a/internal/cardano-node/mithril-cardano-node-chain/src/chain_observer/interface.rs +++ b/internal/cardano-node/mithril-cardano-node-chain/src/chain_observer/interface.rs @@ -2,7 +2,7 @@ use async_trait::async_trait; use thiserror::Error; use mithril_common::StdError; -use mithril_common::crypto_helper::{KesPeriod, OpCert}; +use mithril_common::crypto_helper::KesPeriod; use mithril_common::entities::{ChainPoint, Epoch, StakeDistribution}; use crate::entities::{ChainAddress, TxDatum}; @@ -43,11 +43,8 @@ pub trait ChainObserver: Sync + Send { &self, ) -> Result, ChainObserverError>; - /// Retrieve the KES period of an operational certificate - async fn get_current_kes_period( - &self, - _opcert: &OpCert, - ) -> Result, ChainObserverError> { + /// Retrieve the current KES period + async fn get_current_kes_period(&self) -> Result, ChainObserverError> { Ok(None) } } From 2b62a25d3ac5bf92e4314f6d0f0fe218e86de382 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Mon, 25 Aug 2025 16:25:14 +0200 Subject: [PATCH 2/8] refactor(cardano-node-chain): update 'PallasChainObserver' implementation of 'ChainObserver' --- .../src/chain_observer/pallas_observer.rs | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/internal/cardano-node/mithril-cardano-node-chain/src/chain_observer/pallas_observer.rs b/internal/cardano-node/mithril-cardano-node-chain/src/chain_observer/pallas_observer.rs index e6983138d0e..c09c7277c89 100644 --- a/internal/cardano-node/mithril-cardano-node-chain/src/chain_observer/pallas_observer.rs +++ b/internal/cardano-node/mithril-cardano-node-chain/src/chain_observer/pallas_observer.rs @@ -21,7 +21,7 @@ use pallas_network::{ use pallas_primitives::ToCanonicalJson; use pallas_traverse::Era; -use mithril_common::crypto_helper::{KesPeriod, OpCert, encode_bech32}; +use mithril_common::crypto_helper::{KesPeriod, encode_bech32}; use mithril_common::entities::{BlockNumber, ChainPoint, Epoch, SlotNumber, StakeDistribution}; use mithril_common::{CardanoNetwork, StdResult}; @@ -491,10 +491,7 @@ impl ChainObserver for PallasChainObserver { Ok(stake_distribution) } - async fn get_current_kes_period( - &self, - _opcert: &OpCert, - ) -> Result, ChainObserverError> { + async fn get_current_kes_period(&self) -> Result, ChainObserverError> { let mut client = self.get_client().await?; let current_kes_period = self.get_kes_period(&mut client).await?; @@ -512,7 +509,6 @@ impl ChainObserver for PallasChainObserver { mod tests { use std::fs; - use kes_summed_ed25519::{kes::Sum6Kes, traits::KesSk}; use pallas_codec::utils::{AnyCbor, AnyUInt, KeyValuePairs, TagWrap}; use pallas_crypto::hash::Hash; use pallas_network::facades::NodeServer; @@ -528,7 +524,6 @@ mod tests { }; use tokio::net::UnixListener; - use mithril_common::crypto_helper::ColdKeyGenerator; use mithril_common::test::TempDir; use super::*; @@ -781,15 +776,7 @@ mod tests { let observer = PallasChainObserver::new(socket_path.as_path(), CardanoNetwork::TestNet(10)); - let keypair = ColdKeyGenerator::create_deterministic_keypair([0u8; 32]); - let mut dummy_key_buffer = [0u8; Sum6Kes::SIZE + 4]; - let mut dummy_seed = [0u8; 32]; - let (_, kes_verification_key) = Sum6Kes::keygen(&mut dummy_key_buffer, &mut dummy_seed); - let operational_certificate = OpCert::new(kes_verification_key, 0, 0, keypair); - observer - .get_current_kes_period(&operational_certificate) - .await - .unwrap() + observer.get_current_kes_period().await.unwrap() }); let (_, client_res) = tokio::join!(server, client); From 63268995567abf07322f3d69dee390a912ed4ca5 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Mon, 25 Aug 2025 16:25:26 +0200 Subject: [PATCH 3/8] refactor(cardano-node-chain): update 'FakeChainObserver' implementation of 'ChainObserver' --- .../src/test/double/chain_observer.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/internal/cardano-node/mithril-cardano-node-chain/src/test/double/chain_observer.rs b/internal/cardano-node/mithril-cardano-node-chain/src/test/double/chain_observer.rs index 803675aa78f..c4f2e38c0ad 100644 --- a/internal/cardano-node/mithril-cardano-node-chain/src/test/double/chain_observer.rs +++ b/internal/cardano-node/mithril-cardano-node-chain/src/test/double/chain_observer.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; use tokio::sync::RwLock; -use mithril_common::crypto_helper::{KesPeriod, OpCert}; +use mithril_common::crypto_helper::KesPeriod; use mithril_common::entities::{ BlockNumber, ChainPoint, Epoch, SignerWithStake, SlotNumber, StakeDistribution, TimePoint, }; @@ -202,10 +202,7 @@ impl ChainObserver for FakeChainObserver { )) } - async fn get_current_kes_period( - &self, - _opcert: &OpCert, - ) -> Result, ChainObserverError> { + async fn get_current_kes_period(&self) -> Result, ChainObserverError> { Ok(Some(0)) } } From c061eeeab1a0eb06d5c2fa18869f7902c6bfc41f Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Mon, 25 Aug 2025 16:28:25 +0200 Subject: [PATCH 4/8] refactor(cardano-node-chain): update 'CardanoCliChainObserver' implementation of 'ChainObserver' --- .../src/chain_observer/cli_observer.rs | 79 ++++++++++--------- .../src/test/test_cli_runner.rs | 32 ++++---- 2 files changed, 57 insertions(+), 54 deletions(-) diff --git a/internal/cardano-node/mithril-cardano-node-chain/src/chain_observer/cli_observer.rs b/internal/cardano-node/mithril-cardano-node-chain/src/chain_observer/cli_observer.rs index 3b2124aa627..e2041a04779 100644 --- a/internal/cardano-node/mithril-cardano-node-chain/src/chain_observer/cli_observer.rs +++ b/internal/cardano-node/mithril-cardano-node-chain/src/chain_observer/cli_observer.rs @@ -9,7 +9,7 @@ use std::fs; use std::path::PathBuf; use tokio::process::Command; -use mithril_common::crypto_helper::{KesPeriod, OpCert, SerDeShelleyFileFormat, encode_bech32}; +use mithril_common::crypto_helper::{KesPeriod, encode_bech32}; use mithril_common::entities::{BlockNumber, ChainPoint, Epoch, SlotNumber, StakeDistribution}; use mithril_common::{CardanoNetwork, StdResult}; @@ -38,7 +38,7 @@ pub trait CliRunner { /// Launches the chain point. async fn launch_chain_point(&self) -> StdResult; /// Launches the kes period. - async fn launch_kes_period(&self, opcert_file: &str) -> StdResult; + async fn launch_kes_period(&self) -> StdResult<(String, u64)>; } /// A runner able to request data from a Cardano node using the @@ -141,14 +141,9 @@ impl CardanoCliRunner { command } - fn command_for_kes_period(&self, opcert_file: &str) -> Command { + fn command_for_kes_period(&self) -> Command { let mut command = self.get_command(); - command - .arg(CARDANO_ERA) - .arg("query") - .arg("kes-period-info") - .arg("--op-cert-file") - .arg(opcert_file); + command.arg(CARDANO_ERA).arg("query").arg("tip"); self.post_config_command(&mut command); command @@ -172,6 +167,20 @@ impl CardanoCliRunner { } } } + + /// Get slots per kes period + /// + /// This implementation is aligned with current value for the KES period on the testnet and mainnet networks of Cardano. + /// If this value changes in the future, the implementation should be updated accordingly. + /// The value can be retrieved in the 'slotsPerKESPeriod' field of the 'shelly-genesis.json' configuration file. + fn get_slots_per_kes_period(&self) -> u64 { + match self.network { + CardanoNetwork::MainNet => 129600, + CardanoNetwork::TestNet(1) => 129600, + CardanoNetwork::TestNet(2) => 129600, + CardanoNetwork::TestNet(_) => 129600, + } + } } #[async_trait] @@ -289,17 +298,20 @@ impl CliRunner for CardanoCliRunner { } } - async fn launch_kes_period(&self, opcert_file: &str) -> StdResult { - let output = self.command_for_kes_period(opcert_file).output().await?; + async fn launch_kes_period(&self) -> StdResult<(String, u64)> { + let output = self.command_for_kes_period().output().await?; if output.status.success() { - Ok(std::str::from_utf8(&output.stdout)?.trim().to_string()) + Ok(( + std::str::from_utf8(&output.stdout)?.trim().to_string(), + self.get_slots_per_kes_period(), + )) } else { let message = String::from_utf8_lossy(&output.stderr); Err(anyhow!( "Error launching command {:?}, error = '{}'", - self.command_for_kes_period(opcert_file), + self.command_for_kes_period(), message )) } @@ -526,29 +538,26 @@ impl ChainObserver for CardanoCliChainObserver { } } - async fn get_current_kes_period( - &self, - opcert: &OpCert, - ) -> Result, ChainObserverError> { + async fn get_current_kes_period(&self) -> Result, ChainObserverError> { let dir = std::env::temp_dir().join("mithril_kes_period"); fs::create_dir_all(&dir).map_err(|e| ChainObserverError::General(e.into()))?; - let opcert_file = dir.join(format!("opcert_kes_period-{}", opcert.compute_hash())); - opcert - .to_file(&opcert_file) - .map_err(|e| ChainObserverError::General(e.into()))?; - let output = self + let (output, slots_per_kes_period) = self .cli_runner - .launch_kes_period(opcert_file.to_str().unwrap()) + .launch_kes_period() .await .map_err(ChainObserverError::General)?; + if slots_per_kes_period == 0 { + return Err(anyhow!("slots_per_kes_period must be greater than 0")) + .with_context(|| "CardanoCliChainObserver failed to calculate kes period")?; + } let first_left_curly_bracket_index = output.find('{').unwrap_or_default(); let output_cleaned = output.split_at(first_left_curly_bracket_index).1; let v: Value = serde_json::from_str(output_cleaned) .with_context(|| format!("output was = '{output}'")) .map_err(ChainObserverError::InvalidContent)?; - if let Value::Number(kes_period) = &v["qKesCurrentKesPeriod"] { - Ok(kes_period.as_u64().map(|p| p as KesPeriod)) + if let Value::Number(slot) = &v["slot"] { + Ok(slot.as_u64().map(|slot| (slot / slots_per_kes_period) as KesPeriod)) } else { Ok(None) } @@ -557,12 +566,9 @@ impl ChainObserver for CardanoCliChainObserver { #[cfg(test)] mod tests { - use kes_summed_ed25519::{kes::Sum6Kes, traits::KesSk}; use std::collections::BTreeMap; use std::ffi::OsStr; - use mithril_common::crypto_helper::ColdKeyGenerator; - use crate::test::test_cli_runner::{TestCliRunner, test_expected}; use super::*; @@ -807,17 +813,12 @@ mod tests { #[tokio::test] async fn test_get_current_kes_period() { - let keypair = ColdKeyGenerator::create_deterministic_keypair([0u8; 32]); - let mut dummy_key_buffer = [0u8; Sum6Kes::SIZE + 4]; - let mut dummy_seed = [0u8; 32]; - let (_, kes_verification_key) = Sum6Kes::keygen(&mut dummy_key_buffer, &mut dummy_seed); - let operational_certificate = OpCert::new(kes_verification_key, 0, 0, keypair); let observer = CardanoCliChainObserver::new(Box::::default()); - let kes_period = observer - .get_current_kes_period(&operational_certificate) - .await - .unwrap() - .unwrap(); - assert_eq!(test_expected::launch_kes_period::KES_PERIOD, kes_period); + let kes_period = observer.get_current_kes_period().await.unwrap().unwrap(); + assert_eq!( + (test_expected::launch_chain_point::SLOT_NUMBER.0 + / test_expected::launch_kes_period::SLOTS_PER_KES_PERIOD) as u32, + kes_period + ); } } diff --git a/internal/cardano-node/mithril-cardano-node-chain/src/test/test_cli_runner.rs b/internal/cardano-node/mithril-cardano-node-chain/src/test/test_cli_runner.rs index 0330434815e..996c4478f9a 100644 --- a/internal/cardano-node/mithril-cardano-node-chain/src/test/test_cli_runner.rs +++ b/internal/cardano-node/mithril-cardano-node-chain/src/test/test_cli_runner.rs @@ -29,7 +29,7 @@ pub(crate) mod test_expected { pub(crate) const BYTES: &str = "5b0a20207b0a20202020226e616d65223a20227468616c6573222c0a202020202265706f6368223a203132330a20207d2c0a20207b0a20202020226e616d65223a20227079746861676f726173222c0a202020202265706f6368223a206e756c6c0a20207d0a5d0a"; } pub(crate) mod launch_kes_period { - pub(crate) const KES_PERIOD: u32 = 404; + pub(crate) const SLOTS_PER_KES_PERIOD: u64 = 10000; } pub(crate) mod launch_stake_snapshot { pub(crate) const DEFAULT_POOL_STAKE_MARK: u64 = 3_000_000; @@ -256,25 +256,27 @@ pool1qz2vzszautc2c8mljnqre2857dpmheq7kgt6vav0s38tvvhxm6w 1.051e-6 } /// launches the kes period. - async fn launch_kes_period(&self, _opcert_file: &str) -> StdResult { + async fn launch_kes_period(&self) -> StdResult<(String, u64)> { let output = format!( 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": {}, - "qKesOnDiskOperationalCertificateNumber": 6, - "qKesRemainingSlotsInKesPeriod": 3760228, - "qKesMaxKESEvolutions": 62, - "qKesKesKeyExpiry": "2022-03-20T21:44:51Z", - "qKesEndKesInterval": 434, - "qKesStartKesInterval": 372, - "qKesSlotsPerKesPeriod": 129600 + "block": {}, + "epoch": 299, + "era": "Conway", + "hash": "{}", + "slot": {}, + "slotInEpoch": 53017, + "slotsToEpochEnd": 33383, + "syncProgress": "100.00" }}"#, - test_expected::launch_kes_period::KES_PERIOD + test_expected::launch_chain_point::BLOCK_NUMBER, + test_expected::launch_chain_point::BLOCK_HASH, + test_expected::launch_chain_point::SLOT_NUMBER, ); - Ok(output) + Ok(( + output, + test_expected::launch_kes_period::SLOTS_PER_KES_PERIOD, + )) } } From 598f8e589978bfc3d0f7df7cc3d86cf59f023ef8 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Mon, 25 Aug 2025 16:29:40 +0200 Subject: [PATCH 5/8] refactor(aggregator): update 'ChainObserver' implementation --- .../src/services/signer_registration/verifier.rs | 2 +- mithril-aggregator/src/test/double/mocks.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/mithril-aggregator/src/services/signer_registration/verifier.rs b/mithril-aggregator/src/services/signer_registration/verifier.rs index 7e0fb62f3c6..bc5712bf498 100644 --- a/mithril-aggregator/src/services/signer_registration/verifier.rs +++ b/mithril-aggregator/src/services/signer_registration/verifier.rs @@ -45,7 +45,7 @@ impl SignerRegistrationVerifier for MithrilSignerRegistrationVerifier { let kes_period = match &signer.operational_certificate { Some(operational_certificate) => Some( self.chain_observer - .get_current_kes_period(operational_certificate) + .get_current_kes_period() .await? .unwrap_or_default() - operational_certificate.start_kes_period as KesPeriod, diff --git a/mithril-aggregator/src/test/double/mocks.rs b/mithril-aggregator/src/test/double/mocks.rs index ceac43a6d2c..bf8474cffe5 100644 --- a/mithril-aggregator/src/test/double/mocks.rs +++ b/mithril-aggregator/src/test/double/mocks.rs @@ -3,7 +3,7 @@ use async_trait::async_trait; use mithril_cardano_node_chain::chain_observer::{ChainObserver, ChainObserverError}; use mithril_cardano_node_chain::entities::{ChainAddress, TxDatum}; use mithril_common::certificate_chain::CertificateVerifier; -use mithril_common::crypto_helper::{KesPeriod, OpCert, ProtocolGenesisVerificationKey}; +use mithril_common::crypto_helper::{KesPeriod, ProtocolGenesisVerificationKey}; use mithril_common::entities::{Certificate, ChainPoint, Epoch, StakeDistribution}; use mithril_persistence::store::StakeStorer; @@ -63,7 +63,6 @@ mock! { async fn get_current_kes_period( &self, - opcert: &OpCert, ) -> Result, ChainObserverError>; } } From ab3d44a86d5d82570583012dc38124d73e74bb4b Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Mon, 25 Aug 2025 16:30:15 +0200 Subject: [PATCH 6/8] refactor(signer): update 'ChainObserver' implementation --- mithril-signer/src/runtime/runner.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mithril-signer/src/runtime/runner.rs b/mithril-signer/src/runtime/runner.rs index cf40424e685..fefa145ba31 100644 --- a/mithril-signer/src/runtime/runner.rs +++ b/mithril-signer/src/runtime/runner.rs @@ -175,7 +175,7 @@ impl Runner for SignerRunner { Some(operational_certificate) => Some( self.services .chain_observer - .get_current_kes_period(&operational_certificate) + .get_current_kes_period() .await? .unwrap_or_default() - operational_certificate.start_kes_period as KesPeriod, From 2b0a804f5e258303d0b907d0e8f03e89be2b8f21 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Mon, 25 Aug 2025 16:30:46 +0200 Subject: [PATCH 7/8] refactor(dmq): update 'ChainObserver' implementation --- internal/mithril-dmq/src/message.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/mithril-dmq/src/message.rs b/internal/mithril-dmq/src/message.rs index 79ad7589e96..7abb40ab496 100644 --- a/internal/mithril-dmq/src/message.rs +++ b/internal/mithril-dmq/src/message.rs @@ -69,7 +69,7 @@ impl DmqMessageBuilder { .with_context(|| "Failed to KES sign message while building DMQ message")?; let kes_period = self .chain_observer - .get_current_kes_period(&operational_certificate) + .get_current_kes_period() .await .with_context(|| "Failed to get KES period while building DMQ message")? .unwrap_or_default(); From 3d8b21566fef97c2c5276b662d8df89e279c13c2 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 26 Aug 2025 11:47:13 +0200 Subject: [PATCH 8/8] chore: upgrade crate versions * mithril-cardano-node-chain from `0.1.7` to `0.1.8` * mithril-dmq from `0.1.8` to `0.1.9` * mithril-aggregator from `0.7.81` to `0.7.82` * mithril-signer from `0.2.265` to `0.2.266` --- Cargo.lock | 8 ++++---- .../cardano-node/mithril-cardano-node-chain/Cargo.toml | 2 +- internal/mithril-dmq/Cargo.toml | 2 +- mithril-aggregator/Cargo.toml | 2 +- mithril-signer/Cargo.toml | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f855aa7efbf..d3781f079a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3928,7 +3928,7 @@ dependencies = [ [[package]] name = "mithril-aggregator" -version = "0.7.81" +version = "0.7.82" dependencies = [ "anyhow", "async-trait", @@ -4052,7 +4052,7 @@ dependencies = [ [[package]] name = "mithril-cardano-node-chain" -version = "0.1.7" +version = "0.1.8" dependencies = [ "anyhow", "async-trait", @@ -4243,7 +4243,7 @@ dependencies = [ [[package]] name = "mithril-dmq" -version = "0.1.8" +version = "0.1.9" dependencies = [ "anyhow", "async-trait", @@ -4411,7 +4411,7 @@ dependencies = [ [[package]] name = "mithril-signer" -version = "0.2.265" +version = "0.2.266" dependencies = [ "anyhow", "async-trait", diff --git a/internal/cardano-node/mithril-cardano-node-chain/Cargo.toml b/internal/cardano-node/mithril-cardano-node-chain/Cargo.toml index e7024a7dc69..adf78d0ab29 100644 --- a/internal/cardano-node/mithril-cardano-node-chain/Cargo.toml +++ b/internal/cardano-node/mithril-cardano-node-chain/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-cardano-node-chain" -version = "0.1.7" +version = "0.1.8" authors.workspace = true documentation.workspace = true edition.workspace = true diff --git a/internal/mithril-dmq/Cargo.toml b/internal/mithril-dmq/Cargo.toml index e8e02336f40..341b48dffbc 100644 --- a/internal/mithril-dmq/Cargo.toml +++ b/internal/mithril-dmq/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "mithril-dmq" description = "Mechanisms to publish and consume messages of a 'Decentralized Message Queue network' through a DMQ node" -version = "0.1.8" +version = "0.1.9" authors.workspace = true documentation.workspace = true edition.workspace = true diff --git a/mithril-aggregator/Cargo.toml b/mithril-aggregator/Cargo.toml index 9a1cdf81136..8781b9ba5e0 100644 --- a/mithril-aggregator/Cargo.toml +++ b/mithril-aggregator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-aggregator" -version = "0.7.81" +version = "0.7.82" description = "A Mithril Aggregator server" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-signer/Cargo.toml b/mithril-signer/Cargo.toml index d3974c37417..12c70e25697 100644 --- a/mithril-signer/Cargo.toml +++ b/mithril-signer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-signer" -version = "0.2.265" +version = "0.2.266" description = "A Mithril Signer" authors = { workspace = true } edition = { workspace = true }